From c55ea18aafb5e0761ca9879e4246e931fd6eb91b Mon Sep 17 00:00:00 2001 From: v6ole Date: Mon, 13 Jul 2026 00:14:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=AA=E4=BA=BA=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=8B=9C=E8=AE=BF=E7=BC=96=E8=BE=91=E6=96=B0=E5=A2=9E=E7=85=A7?= =?UTF-8?q?=E7=89=87=E6=9F=A5=E7=9C=8B/=E5=88=A0=E9=99=A4/=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- frontend/src/views/desktop/CustomerManage.vue | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/desktop/CustomerManage.vue b/frontend/src/views/desktop/CustomerManage.vue index 0fbce8a..52e15db 100644 --- a/frontend/src/views/desktop/CustomerManage.vue +++ b/frontend/src/views/desktop/CustomerManage.vue @@ -5,6 +5,7 @@ import { useThemeStore } from '@/stores/theme' import { customersApi } from '@/api/customers' import { ElMessage, ElMessageBox } from 'element-plus' import { getManagerColor as mgrColorFn, getMgrTextColor, loadManagerColors } from '@/utils/managerColor' +import { compressImage } from '@/utils/image' import api from '@/api/index' const auth = useAuthStore() @@ -31,11 +32,49 @@ const indivEditVisible = ref(false) const indivEditType = ref('') const indivEditForm = ref({}) const indivEditSaving = ref(false) +const indivPhotoUrls = ref>({}) -function openIndivEdit(type: string, row: any) { +async function openIndivEdit(type: string, row: any) { indivEditType.value = type indivEditForm.value = { ...row } 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() { @@ -53,6 +92,7 @@ async function handleIndivEditSave() { time_range: f.time_range || '', visit_date: f.visit_date, manager_id: f.manager_id, + photos: f.photos || [], }) } 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 }) @@ -710,6 +750,22 @@ async function handleImport() { + +
+
+ + 加载中 + +
+
+
+ + +