feat: 历史周报+图片预览+照片管理+导入优化+用户合并

新增:
- 历史周报: Dashboard/周报详情支持周选择器翻看往周,归档只读
- 图片预览: 全屏大图(移动端+PC端),点击遮罩关闭
- PC端照片管理: 编辑时可上传/删除照片
- 客户导入改为更新模式: 重名自动更新信息,显示操作明细
- 导入跳过原因: 旧周报导入也显示每条跳过原因

优化:
- 填报进度权限: 经理只看到自己,支局长/领导看全员
- 客户经理暖灰色hash标签列
- 用户去重合并(4组),数据完整迁移
- CLAUDE.md 更新到最新状态

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-23 16:59:31 +08:00
parent 8926475b20
commit aa3fbca710
15 changed files with 323 additions and 74 deletions
+12 -1
View File
@@ -11,6 +11,8 @@ const todayVisitCount = ref(0)
const visits = ref<any[]>([])
const todayNoteCount = ref(0)
const dailyNotes = ref<any[]>([])
const previewImageUrl = ref('')
const previewDialogVisible = ref(false)
const photoUrls = ref<Record<string, string>>({})
const loading = ref(false)
@@ -52,6 +54,8 @@ async function loadToday() {
} finally { loading.value = false }
}
function previewPhoto(url: string) { previewImageUrl.value = url; previewDialogVisible.value = true }
onMounted(loadToday)
</script>
@@ -174,7 +178,7 @@ onMounted(loadToday)
<span class="demand-marker"></span> {{ v.customer_demand }}
</p>
<div v-if="v.photos?.length" class="record-photos">
<img v-for="key in v.photos" :key="key" :src="photoUrls[key] || ''" class="record-photo" />
<img v-for="key in v.photos" :key="key" :src="photoUrls[key] || ''" class="record-photo" @click.stop="previewPhoto(photoUrls[key])" />
</div>
<div class="record-footer" v-if="v.time_range">
<span class="record-time">{{ v.time_range }}</span>
@@ -190,6 +194,13 @@ onMounted(loadToday)
<p class="empty-hint">点击上方按钮开始填报</p>
</div>
</div>
<teleport to="body">
<div v-if="previewDialogVisible" class="image-preview-overlay" @click="previewDialogVisible = false">
<img :src="previewImageUrl" class="image-preview-full" @click.stop />
<button class="image-preview-close" @click="previewDialogVisible = false"></button>
</div>
</teleport>
</div>
</template>
+16 -1
View File
@@ -34,6 +34,8 @@ const customers = ref<any[]>([])
const managers = ref<any[]>([])
const uploadedPhotos = ref<string[]>([])
const photoPreviews = ref<string[]>([])
const previewDialogVisible = ref(false)
const previewImageUrl = ref('')
const uploading = ref(false)
const customerSearch = ref('')
@@ -136,6 +138,11 @@ function onTimeRangeChange(val: [string, string] | null) {
form.value.time_range = val ? val.join('-') : ''
}
function previewPhoto(url: string) {
previewImageUrl.value = url
previewDialogVisible.value = true
}
function removePhoto(index: number) {
if (index < photoPreviews.value.length) {
URL.revokeObjectURL(photoPreviews.value[index])
@@ -301,7 +308,7 @@ async function handleDelete() {
</template>
<div class="photo-area">
<div v-for="(key, idx) in uploadedPhotos" :key="key" class="photo-item">
<img :src="photoPreviews[idx]" class="photo-thumb" v-if="photoPreviews[idx]" />
<img :src="photoPreviews[idx]" class="photo-thumb" v-if="photoPreviews[idx]" @click.stop="previewPhoto(photoPreviews[idx])" />
<span class="photo-label">照片{{ idx + 1 }}</span>
<button type="button" class="photo-remove-btn" @click="removePhoto(idx)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -338,6 +345,14 @@ async function handleDelete() {
</button>
</div>
</el-form>
<!-- Image Preview Dialog -->
<teleport to="body">
<div v-if="previewDialogVisible" class="image-preview-overlay" @click="previewDialogVisible = false">
<img :src="previewImageUrl" class="image-preview-full" @click.stop />
<button class="image-preview-close" @click="previewDialogVisible = false"></button>
</div>
</teleport>
</div>
</template>