feat: 个人用户拜访编辑新增照片查看/删除/上传功能

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-13 00:14:40 +08:00
parent 32ca029580
commit c55ea18aaf
+57 -1
View File
@@ -5,6 +5,7 @@ import { useThemeStore } from '@/stores/theme'
import { customersApi } from '@/api/customers' import { customersApi } from '@/api/customers'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { getManagerColor as mgrColorFn, getMgrTextColor, loadManagerColors } from '@/utils/managerColor' import { getManagerColor as mgrColorFn, getMgrTextColor, loadManagerColors } from '@/utils/managerColor'
import { compressImage } from '@/utils/image'
import api from '@/api/index' import api from '@/api/index'
const auth = useAuthStore() const auth = useAuthStore()
@@ -31,11 +32,49 @@ const indivEditVisible = ref(false)
const indivEditType = ref('') const indivEditType = ref('')
const indivEditForm = ref<any>({}) const indivEditForm = ref<any>({})
const indivEditSaving = ref(false) const indivEditSaving = ref(false)
const indivPhotoUrls = ref<Record<string, string>>({})
function openIndivEdit(type: string, row: any) { async function openIndivEdit(type: string, row: any) {
indivEditType.value = type indivEditType.value = type
indivEditForm.value = { ...row } indivEditForm.value = { ...row }
indivEditVisible.value = true indivEditVisible.value = true
// Load photo URLs for visit editing
if (type === 'visit' && row.photos?.length) {
for (const key of row.photos) {
if (!indivPhotoUrls.value[key]) {
try {
const urlRes = await api.get('/upload/download-url', { params: { object_key: key } })
indivPhotoUrls.value[key] = urlRes.data.download_url
} catch (_) { indivPhotoUrls.value[key] = '' }
}
}
}
}
async function handleIndivPhotoUpload(event: Event) {
const target = event.target as HTMLInputElement
if (!target.files?.length) return
const form = indivEditForm.value
if (!form.photos) form.photos = []
if (form.photos.length >= 9) { ElMessage.warning('最多9张照片'); return }
for (const file of Array.from(target.files)) {
if (form.photos.length >= 9) break
try {
const compressed = await compressImage(file, { maxPixels: 1920, quality: 0.8 })
const presignRes = await api.get('/upload/presigned-url', { params: { filename: compressed.name, content_type: compressed.type || 'image/jpeg' } })
await api.put(presignRes.data.upload_url, compressed, { headers: { 'Content-Type': compressed.type || 'image/jpeg' } })
form.photos.push(presignRes.data.object_key)
indivPhotoUrls.value[presignRes.data.object_key] = URL.createObjectURL(compressed)
} catch (_) { ElMessage.error('上传失败') }
}
target.value = ''
}
function removeIndivPhoto(idx: number) {
const form = indivEditForm.value
const key = form.photos[idx]
delete indivPhotoUrls.value[key]
form.photos.splice(idx, 1)
} }
async function handleIndivEditSave() { async function handleIndivEditSave() {
@@ -53,6 +92,7 @@ async function handleIndivEditSave() {
time_range: f.time_range || '', time_range: f.time_range || '',
visit_date: f.visit_date, visit_date: f.visit_date,
manager_id: f.manager_id, manager_id: f.manager_id,
photos: f.photos || [],
}) })
} else if (type === 'plan') { } else if (type === 'plan') {
await api.put(`/work-plans/${f.id}`, { plan_content: f.plan_content, status: f.status, plan_date: f.plan_date, manager_id: f.manager_id }) await api.put(`/work-plans/${f.id}`, { plan_content: f.plan_content, status: f.status, plan_date: f.plan_date, manager_id: f.manager_id })
@@ -710,6 +750,22 @@ async function handleImport() {
<el-form-item label="相关人员"><el-input v-model="indivEditForm.companion_names_str" placeholder="多个姓名用、分隔" /></el-form-item> <el-form-item label="相关人员"><el-input v-model="indivEditForm.companion_names_str" placeholder="多个姓名用、分隔" /></el-form-item>
<el-form-item label="沟通内容"><el-input v-model="indivEditForm.communication_content" type="textarea" :rows="4" /></el-form-item> <el-form-item label="沟通内容"><el-input v-model="indivEditForm.communication_content" type="textarea" :rows="4" /></el-form-item>
<el-form-item label="客户需求"><el-input v-model="indivEditForm.customer_demand" type="textarea" :rows="2" /></el-form-item> <el-form-item label="客户需求"><el-input v-model="indivEditForm.customer_demand" type="textarea" :rows="2" /></el-form-item>
<el-form-item v-if="indivEditForm.photos?.length" label="照片">
<div style="display:flex;gap:8px;flex-wrap:wrap">
<div v-for="(key, idx) in indivEditForm.photos" :key="key" style="position:relative">
<img :src="indivPhotoUrls[key]" style="width:80px;height:80px;object-fit:cover;border:1px solid var(--warm-border)" v-if="indivPhotoUrls[key]" />
<span v-else style="width:80px;height:80px;display:flex;align-items:center;justify-content:center;background:var(--paper);border:1px solid var(--warm-border);font-size:12px;color:var(--warm-gray)">加载中</span>
<button type="button" style="position:absolute;top:-6px;right:-6px;background:var(--vermilion);color:#fff;border:none;width:18px;height:18px;cursor:pointer;font-size:12px;line-height:1" @click="removeIndivPhoto(idx)">×</button>
</div>
</div>
</el-form-item>
<el-form-item label="添加照片">
<label style="cursor:pointer;display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border:1px dashed var(--warm-border);color:var(--warm-gray);font-size:13px">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
上传照片最多9张
<input type="file" accept="image/*" multiple style="display:none" @change="handleIndivPhotoUpload" />
</label>
</el-form-item>
</template> </template>
<template v-else-if="indivEditType === 'plan'"> <template v-else-if="indivEditType === 'plan'">
<el-form-item label="客户经理"> <el-form-item label="客户经理">