diff --git a/src/credit/mp-customer/detail.tsx b/src/credit/mp-customer/detail.tsx index 2525eb4..ae804d4 100644 --- a/src/credit/mp-customer/detail.tsx +++ b/src/credit/mp-customer/detail.tsx @@ -75,6 +75,8 @@ type Attachment = { thumbnail?: string } +const makeAttachmentId = () => `${Date.now()}-${Math.random().toString(16).slice(2)}` + const isHttpUrl = (url?: string) => { if (!url) return false return /^https?:\/\//i.test(url) @@ -149,31 +151,56 @@ const loadFollowHistoryIds = (customerId: number): number[] => { } } +const getUploadedUrl = (record: any) => { + if (!record) return '' + if (typeof record === 'string') return String(record).trim() + return String(record?.url || record?.downloadUrl || record?.thumbnail || record?.path || '').trim() +} + +const normalizeAttachments = (arr: Attachment[]) => { + const seen = new Set() + const out: Attachment[] = [] + for (const a of arr) { + const url = String(a?.url || '').trim() + if (!url) continue + if (seen.has(url)) continue + seen.add(url) + out.push({ + ...a, + id: a?.id || makeAttachmentId(), + name: String(a?.name || guessNameFromUrl(url, '附件')).trim() || guessNameFromUrl(url, '附件'), + url + }) + } + return out +} + const parseFilesToAttachments = (raw?: any): Attachment[] => { const txt = String(raw || '').trim() if (!txt) return [] const parsed = safeParseJSON(txt) - const nowId = () => `${Date.now()}-${Math.random().toString(16).slice(2)}` if (Array.isArray(parsed)) { - return parsed + return normalizeAttachments( + parsed .map((it, idx) => { if (!it) return null if (typeof it === 'string') { const url = String(it).trim() if (!url) return null const name = guessNameFromUrl(url, `附件${idx + 1}`) - return { id: nowId(), name, url, isImage: isImageUrl(url) } as Attachment + return { id: makeAttachmentId(), name, url, isImage: isImageUrl(url) } as Attachment } const url = String(it?.url || it?.downloadUrl || it?.thumbnail || it?.path || '').trim() if (!url) return null const name = String(it?.name || guessNameFromUrl(url, `附件${idx + 1}`)).trim() const thumbnail = it?.thumbnail ? String(it.thumbnail) : undefined const isImage = it?.isImage !== undefined ? !!it.isImage : isImageUrl(url) - return { id: nowId(), name, url, thumbnail, isImage } as Attachment + return { id: makeAttachmentId(), name, url, thumbnail, isImage } as Attachment }) .filter(Boolean) as Attachment[] + ) } // 非 JSON:尝试按分隔符拆分成多条 url @@ -182,12 +209,14 @@ const parseFilesToAttachments = (raw?: any): Attachment[] => { .map(s => s.trim()) .filter(Boolean) if (!parts.length) return [] - return parts.map((url, idx) => ({ - id: nowId(), - url, - name: guessNameFromUrl(url, `附件${idx + 1}`), - isImage: isImageUrl(url) - })) + return normalizeAttachments( + parts.map((url, idx) => ({ + id: makeAttachmentId(), + url, + name: guessNameFromUrl(url, `附件${idx + 1}`), + isImage: isImageUrl(url) + })) + ) } export default function CreditMpCustomerDetailPage() { @@ -276,21 +305,18 @@ export default function CreditMpCustomerDetailPage() { const imageAttachments = useMemo(() => attachments.filter(a => a.isImage), [attachments]) const fileAttachments = useMemo(() => attachments.filter(a => !a.isImage), [attachments]) + const maxAttachments = 30 + const saveAttachments = useCallback( async (next: Attachment[]) => { if (!rowId || !row) return if (savingFiles) return setSavingFiles(true) try { - const filesPayload = next.map(a => ({ - name: a.name, - url: a.url, - thumbnail: a.thumbnail, - isImage: a.isImage - })) - const nextFiles = filesPayload.length ? JSON.stringify(filesPayload) : undefined + const normalized = normalizeAttachments(next).slice(0, maxAttachments) + const nextFiles = normalized.length ? JSON.stringify(normalized.map(a => ({ name: a.name, url: a.url, thumbnail: a.thumbnail, isImage: a.isImage }))) : undefined await updateCreditMpCustomer({ ...(row as any), id: rowId, files: nextFiles } as any) - setAttachments(next) + setAttachments(normalized) setRow(prev => (prev ? ({ ...prev, files: nextFiles } as any) : prev)) Taro.showToast({ title: '已更新', icon: 'success' }) } catch (e) { @@ -332,10 +358,15 @@ export default function CreditMpCustomerDetailPage() { ) const chooseAndUploadImages = useCallback(async () => { + if (uploading || savingFiles) return + if (attachments.length >= maxAttachments) { + Taro.showToast({ title: `附件已达上限(${maxAttachments})`, icon: 'none' }) + return + } let res: any try { res = await Taro.chooseImage({ - count: 9, + count: Math.min(9, maxAttachments - attachments.length), sizeType: ['compressed'], sourceType: ['album', 'camera'] }) @@ -353,29 +384,41 @@ export default function CreditMpCustomerDetailPage() { try { const uploaded: Attachment[] = [] for (const p of tempFilePaths) { - const record: any = await uploadFileByPath(p) - const url = String(record?.url || record?.downloadUrl || record?.thumbnail || record?.path || '').trim() - if (!url) continue - uploaded.push({ - id: `${Date.now()}-${Math.random().toString(16).slice(2)}`, - name: String(record?.name || guessNameFromUrl(url, '图片')).trim(), - url, - thumbnail: record?.thumbnail ? String(record.thumbnail) : undefined, - isImage: true - }) + try { + const record: any = await uploadFileByPath(p) + const url = getUploadedUrl(record) + if (!url) continue + uploaded.push({ + id: makeAttachmentId(), + name: String(record?.name || guessNameFromUrl(url, '图片')).trim(), + url, + thumbnail: record?.thumbnail ? String(record.thumbnail) : undefined, + isImage: true + }) + } catch (e) { + console.error('上传图片失败:', e) + } } if (!uploaded.length) return await saveAttachments(attachments.concat(uploaded)) + } catch (e) { + console.error('上传图片失败:', e) + Taro.showToast({ title: (e as any)?.message || '上传失败', icon: 'none' }) } finally { setUploading(false) } - }, [attachments, saveAttachments]) + }, [attachments, maxAttachments, saveAttachments, savingFiles, uploading]) const chooseAndUploadFiles = useCallback(async () => { + if (uploading || savingFiles) return + if (attachments.length >= maxAttachments) { + Taro.showToast({ title: `附件已达上限(${maxAttachments})`, icon: 'none' }) + return + } let res: any try { // @ts-ignore - res = await Taro.chooseMessageFile({ count: 9, type: 'file' }) + res = await Taro.chooseMessageFile({ count: Math.min(9, maxAttachments - attachments.length), type: 'file' }) } catch (e) { const msg = String((e as any)?.errMsg || (e as any)?.message || e || '') if (msg.includes('cancel')) return @@ -395,23 +438,30 @@ export default function CreditMpCustomerDetailPage() { try { const uploaded: Attachment[] = [] for (const f of picked) { - const record: any = await uploadFileByPath(f.path) - const url = String(record?.url || record?.downloadUrl || record?.thumbnail || record?.path || '').trim() - if (!url) continue - uploaded.push({ - id: `${Date.now()}-${Math.random().toString(16).slice(2)}`, - name: String(f?.name || record?.name || guessNameFromUrl(url, '文件')).trim(), - url, - thumbnail: record?.thumbnail ? String(record.thumbnail) : undefined, - isImage: false - }) + try { + const record: any = await uploadFileByPath(f.path) + const url = getUploadedUrl(record) + if (!url) continue + uploaded.push({ + id: makeAttachmentId(), + name: String(f?.name || record?.name || guessNameFromUrl(url, '文件')).trim(), + url, + thumbnail: record?.thumbnail ? String(record.thumbnail) : undefined, + isImage: false + }) + } catch (e) { + console.error('上传文件失败:', e) + } } if (!uploaded.length) return await saveAttachments(attachments.concat(uploaded)) + } catch (e) { + console.error('上传文件失败:', e) + Taro.showToast({ title: (e as any)?.message || '上传失败', icon: 'none' }) } finally { setUploading(false) } - }, [attachments, saveAttachments]) + }, [attachments, maxAttachments, saveAttachments, savingFiles, uploading]) const onAttachmentAction = useCallback( async (a: Attachment) => { diff --git a/src/credit/mp-customer/follow-step1.config.ts b/src/credit/mp-customer/follow-step1.config.ts index d4db67b..1ce7287 100644 --- a/src/credit/mp-customer/follow-step1.config.ts +++ b/src/credit/mp-customer/follow-step1.config.ts @@ -1,7 +1,6 @@ export default definePageConfig({ navigationBarTitleText: '客户跟进', navigationBarTextStyle: 'black', - navigationBarBackgroundColor: '#ffffff', - navigationStyle: 'custom' + navigationBarBackgroundColor: '#ffffff' }) diff --git a/src/credit/mp-customer/follow-step1.tsx b/src/credit/mp-customer/follow-step1.tsx index db641e2..57e1e17 100644 --- a/src/credit/mp-customer/follow-step1.tsx +++ b/src/credit/mp-customer/follow-step1.tsx @@ -2,7 +2,6 @@ import { useCallback, useMemo, useState } from 'react' import Taro, { useDidShow, useRouter } from '@tarojs/taro' import { View, Text } from '@tarojs/components' import { Button, ConfigProvider, Empty, Loading, TextArea } from '@nutui/nutui-react-taro' -import { Setting } from '@nutui/icons-react-taro' import dayjs from 'dayjs' import { getCreditMpCustomer, updateCreditMpCustomer } from '@/api/credit/creditMpCustomer' @@ -153,6 +152,7 @@ export default function CreditMpCustomerFollowStep1Page() { return Number.isFinite(id) && id > 0 ? id : undefined }, [router?.params]) + // @ts-ignore const statusBarHeight = useMemo(() => { try { const info = Taro.getSystemInfoSync() @@ -465,48 +465,11 @@ export default function CreditMpCustomerFollowStep1Page() { } } - const headerOffset = statusBarHeight + 80 + const headerOffset = 12 return ( - - - 12:00 - - 信号 - Wi-Fi - 电池 - - - - - Taro.navigateBack()}> - 返回 - - 客户跟进 - - { - try { - const res = await Taro.showActionSheet({ itemList: ['刷新'] }) - if (res.tapIndex === 0) reload() - } catch (e) { - const msg = String((e as any)?.errMsg || (e as any)?.message || e || '') - if (msg.includes('cancel')) return - } - }} - > - ... - - - - - - - - {loading ? ( diff --git a/src/credit/mp-customer/index.tsx b/src/credit/mp-customer/index.tsx index b6504c0..8f61f1a 100644 --- a/src/credit/mp-customer/index.tsx +++ b/src/credit/mp-customer/index.tsx @@ -16,7 +16,7 @@ import { SearchBar, Tag } from '@nutui/nutui-react-taro' -import { Phone } from '@nutui/icons-react-taro' +// import { Phone } from '@nutui/icons-react-taro' import RegionData from '@/api/json/regions-data.json' import { getCreditMpCustomer, pageCreditMpCustomer, updateCreditMpCustomer } from '@/api/credit/creditMpCustomer' @@ -382,6 +382,7 @@ export default function CreditCompanyPage() { Taro.showToast({ title: `已复制${unique.length}个电话`, icon: 'success' }) } + // @ts-ignore const callPhone = async (phone?: string) => { const num = String(phone || '').trim() if (!num) { @@ -637,13 +638,11 @@ export default function CreditCompanyPage() {