From f306e7a9f7a92e06fb4f2c053ca50f3e4b8be2e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Thu, 19 Mar 2026 23:06:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(credit):=20=E9=87=8D=E6=9E=84=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97=E5=B9=B6=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E4=BF=A1=E7=94=A8=E5=AE=A2=E6=88=B7=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将公司相关页面重命名为客户管理页面 - 新增信用客户详情、编辑、跟进页面 - 实现客户状态跟踪和跟进流程 - 更新应用配置中的页面路由映射 - 优化订单详情页面的时间轴显示逻辑 --- src/app.config.ts | 13 +- .../detail.config.ts | 0 .../{company => creditMpCustomer}/detail.tsx | 4 +- .../edit.config.ts | 0 .../{company => creditMpCustomer}/edit.tsx | 0 .../follow-step1.config.ts | 0 .../follow-step1.tsx | 0 src/credit/creditMpCustomer/index.tsx | 62 ++- .../{company => mp-customer}/add.config.ts | 0 src/credit/{company => mp-customer}/add.tsx | 0 src/credit/mp-customer/detail.config.ts | 7 + src/credit/mp-customer/detail.tsx | 348 ++++++++++++++ src/credit/mp-customer/edit.config.ts | 7 + src/credit/mp-customer/edit.tsx | 364 +++++++++++++++ src/credit/mp-customer/follow-step1.config.ts | 7 + src/credit/mp-customer/follow-step1.tsx | 427 ++++++++++++++++++ .../{company => mp-customer}/index.config.ts | 0 src/credit/{company => mp-customer}/index.tsx | 9 +- src/credit/my-order/detail.tsx | 127 ++++-- src/credit/my-order/index.tsx | 32 +- src/pages/index/index.tsx | 2 +- src/pages/user/components/UserCard.tsx | 1 + 22 files changed, 1350 insertions(+), 60 deletions(-) rename src/credit/{company => creditMpCustomer}/detail.config.ts (100%) rename src/credit/{company => creditMpCustomer}/detail.tsx (98%) rename src/credit/{company => creditMpCustomer}/edit.config.ts (100%) rename src/credit/{company => creditMpCustomer}/edit.tsx (100%) rename src/credit/{company => creditMpCustomer}/follow-step1.config.ts (100%) rename src/credit/{company => creditMpCustomer}/follow-step1.tsx (100%) rename src/credit/{company => mp-customer}/add.config.ts (100%) rename src/credit/{company => mp-customer}/add.tsx (100%) create mode 100644 src/credit/mp-customer/detail.config.ts create mode 100644 src/credit/mp-customer/detail.tsx create mode 100644 src/credit/mp-customer/edit.config.ts create mode 100644 src/credit/mp-customer/edit.tsx create mode 100644 src/credit/mp-customer/follow-step1.config.ts create mode 100644 src/credit/mp-customer/follow-step1.tsx rename src/credit/{company => mp-customer}/index.config.ts (100%) rename src/credit/{company => mp-customer}/index.tsx (98%) diff --git a/src/app.config.ts b/src/app.config.ts index e11d41f..eed1052 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -42,15 +42,18 @@ export default { "customer/index", "order/index", "order/add", - "company/index", - "company/add", - "company/detail", - "company/follow-step1", - "company/edit", "my-order/index", "my-order/detail", 'creditMpCustomer/index', 'creditMpCustomer/add', + "creditMpCustomer/detail", + "creditMpCustomer/follow-step1", + "creditMpCustomer/edit", + "mp-customer/index", + "mp-customer/add", + "mp-customer/detail", + "mp-customer/follow-step1", + "mp-customer/edit" ] } ], diff --git a/src/credit/company/detail.config.ts b/src/credit/creditMpCustomer/detail.config.ts similarity index 100% rename from src/credit/company/detail.config.ts rename to src/credit/creditMpCustomer/detail.config.ts diff --git a/src/credit/company/detail.tsx b/src/credit/creditMpCustomer/detail.tsx similarity index 98% rename from src/credit/company/detail.tsx rename to src/credit/creditMpCustomer/detail.tsx index 7df249a..b9e4ad0 100644 --- a/src/credit/company/detail.tsx +++ b/src/credit/creditMpCustomer/detail.tsx @@ -219,7 +219,7 @@ export default function CreditCompanyDetailPage() { onClick={async () => { try { const res = await Taro.showActionSheet({ itemList: ['编辑客户', '刷新'] }) - if (res.tapIndex === 0 && companyId) Taro.navigateTo({ url: `/credit/company/edit?id=${companyId}` }) + if (res.tapIndex === 0 && companyId) Taro.navigateTo({ url: `/credit/my-customer/edit?id=${companyId}` }) if (res.tapIndex === 1) reload() } catch (e) { const msg = String((e as any)?.errMsg || (e as any)?.message || e || '') @@ -336,7 +336,7 @@ export default function CreditCompanyDetailPage() { Taro.showToast({ title: '缺少客户ID', icon: 'none' }) return } - Taro.navigateTo({ url: `/credit/company/follow-step1?id=${companyId}` }) + Taro.navigateTo({ url: `/credit/my-customer/follow-step1?id=${companyId}` }) }} > 跟进 diff --git a/src/credit/company/edit.config.ts b/src/credit/creditMpCustomer/edit.config.ts similarity index 100% rename from src/credit/company/edit.config.ts rename to src/credit/creditMpCustomer/edit.config.ts diff --git a/src/credit/company/edit.tsx b/src/credit/creditMpCustomer/edit.tsx similarity index 100% rename from src/credit/company/edit.tsx rename to src/credit/creditMpCustomer/edit.tsx diff --git a/src/credit/company/follow-step1.config.ts b/src/credit/creditMpCustomer/follow-step1.config.ts similarity index 100% rename from src/credit/company/follow-step1.config.ts rename to src/credit/creditMpCustomer/follow-step1.config.ts diff --git a/src/credit/company/follow-step1.tsx b/src/credit/creditMpCustomer/follow-step1.tsx similarity index 100% rename from src/credit/company/follow-step1.tsx rename to src/credit/creditMpCustomer/follow-step1.tsx diff --git a/src/credit/creditMpCustomer/index.tsx b/src/credit/creditMpCustomer/index.tsx index f04b64a..9c0dc3e 100644 --- a/src/credit/creditMpCustomer/index.tsx +++ b/src/credit/creditMpCustomer/index.tsx @@ -1,28 +1,53 @@ -import { useCallback, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import Taro, { useDidShow } from '@tarojs/taro' import { View, Text } from '@tarojs/components' import { Button, Cell, CellGroup, ConfigProvider, Empty, Space } from '@nutui/nutui-react-taro' import { ArrowRight, CheckNormal, Checked } from '@nutui/icons-react-taro' import type { CreditMpCustomer } from '@/api/credit/creditMpCustomer/model' -import { listCreditMpCustomer, removeCreditMpCustomer, updateCreditMpCustomer } from '@/api/credit/creditMpCustomer' +import { pageCreditMpCustomer, removeCreditMpCustomer, updateCreditMpCustomer } from '@/api/credit/creditMpCustomer' export default function CreditMpCustomerListPage() { const [list, setList] = useState([]) + const [count, setCount] = useState(0) + const [page, setPage] = useState(1) + const limit = 20 const [loading, setLoading] = useState(false) + const [loadingMore, setLoadingMore] = useState(false) + + const hasMore = useMemo(() => list.length < count, [count, list.length]) + + const fetchPage = useCallback( + async (opts: { nextPage: number; replace: boolean }) => { + try { + if (opts.replace) setLoading(true) + else setLoadingMore(true) + + const res = await pageCreditMpCustomer({ page: opts.nextPage, limit } as any) + const incoming = (res?.list || []) as CreditMpCustomer[] + const total = Number(res?.count || 0) + setCount(Number.isFinite(total) ? total : 0) + setPage(opts.nextPage) + setList(prev => (opts.replace ? incoming : prev.concat(incoming))) + } catch (e) { + console.error('获取数据失败:', e) + Taro.showToast({ title: (e as any)?.message || '获取数据失败', icon: 'none' }) + } finally { + setLoading(false) + setLoadingMore(false) + } + }, + [limit] + ) const reload = useCallback(async () => { - setLoading(true) - try { - const data = await listCreditMpCustomer() - setList(data || []) - } catch (e) { - console.error('获取数据失败:', e) - Taro.showToast({ title: (e as any)?.message || '获取数据失败', icon: 'none' }) - } finally { - setLoading(false) - } - }, []) + await fetchPage({ nextPage: 1, replace: true }) + }, [fetchPage]) + + const loadMore = useCallback(async () => { + if (loading || loadingMore || !hasMore) return + await fetchPage({ nextPage: page + 1, replace: false }) + }, [fetchPage, hasMore, loading, loadingMore, page]) useDidShow(() => { reload() @@ -83,6 +108,12 @@ export default function CreditMpCustomerListPage() { ) : ( + + 共{count}条 + + 已加载{list.length}条 + + {list.map(row => { const recommended = row.recommend === 1 @@ -118,6 +149,11 @@ export default function CreditMpCustomerListPage() { ) })} + + + 长按任意一条可删除 diff --git a/src/credit/company/add.config.ts b/src/credit/mp-customer/add.config.ts similarity index 100% rename from src/credit/company/add.config.ts rename to src/credit/mp-customer/add.config.ts diff --git a/src/credit/company/add.tsx b/src/credit/mp-customer/add.tsx similarity index 100% rename from src/credit/company/add.tsx rename to src/credit/mp-customer/add.tsx diff --git a/src/credit/mp-customer/detail.config.ts b/src/credit/mp-customer/detail.config.ts new file mode 100644 index 0000000..0aa6f01 --- /dev/null +++ b/src/credit/mp-customer/detail.config.ts @@ -0,0 +1,7 @@ +export default definePageConfig({ + navigationBarTitleText: '客户详情', + navigationBarTextStyle: 'black', + navigationBarBackgroundColor: '#ffffff', + navigationStyle: 'custom' +}) + diff --git a/src/credit/mp-customer/detail.tsx b/src/credit/mp-customer/detail.tsx new file mode 100644 index 0000000..b9e4ad0 --- /dev/null +++ b/src/credit/mp-customer/detail.tsx @@ -0,0 +1,348 @@ +import { useCallback, useMemo, useState } from 'react' +import Taro, { useDidShow, useRouter } from '@tarojs/taro' +import { View, Text } from '@tarojs/components' +import { Button, ConfigProvider, Empty, Loading } from '@nutui/nutui-react-taro' +import { Setting } from '@nutui/icons-react-taro' +import dayjs from 'dayjs' + +import { getCreditCompany } from '@/api/credit/creditCompany' +import type { CreditCompany } from '@/api/credit/creditCompany/model' + +type CustomerStatus = '保护期内' | '已签约' | '已完成' | '保护期外' + +type FollowPerson = { + name: string + time?: string + isCurrent?: boolean +} + +const splitPhones = (raw?: string) => { + const text = String(raw || '').trim() + if (!text) return [] + return text + .split(/[\s,,;;、\n\r]+/g) + .map(s => s.trim()) + .filter(Boolean) +} + +const uniq = (arr: T[]) => Array.from(new Set(arr)) + +const getCompanyIndustry = (c?: CreditCompany) => { + if (!c) return '' + return String( + c.nationalStandardIndustryCategories6 || + c.nationalStandardIndustryCategories2 || + c.nationalStandardIndustryCategories || + c.institutionType || + '' + ).trim() +} + +const parseContactFromComments = (comments?: string) => { + const txt = String(comments || '').trim() + if (!txt) return '' + const m = txt.match(/联系人:([^;;]+)/) + return String(m?.[1] || '').trim() +} + +const getStatusTextClass = (s: CustomerStatus) => { + if (s === '保护期内') return 'text-green-600' + if (s === '已签约') return 'text-orange-500' + if (s === '已完成') return 'text-blue-600' + return 'text-gray-500' +} + +const normalizeFollowPeople = (company: CreditCompany): FollowPerson[] => { + // 兼容后端可能下发多种字段;未下发时给出静态示例,保证 UI/逻辑可演示。 + const anyCompany = company as any + + const incoming: FollowPerson[] = [] + + const arr1 = anyCompany?.followPeople as Array | undefined + if (Array.isArray(arr1) && arr1.length) { + for (const it of arr1) { + const name = String(it?.name || it?.realName || it?.userRealName || '').trim() + if (!name) continue + incoming.push({ name, time: it?.time || it?.date, isCurrent: !!it?.isCurrent }) + } + } + + const arr2 = anyCompany?.followHistory as Array | undefined + if (Array.isArray(arr2) && arr2.length) { + for (const it of arr2) { + const name = String(it?.name || it?.realName || it?.userRealName || it || '').trim() + if (!name) continue + incoming.push({ name, time: it?.time || it?.date, isCurrent: !!it?.isCurrent }) + } + } + + const currentName = String( + anyCompany?.followRealName || anyCompany?.userRealName || anyCompany?.realName || '' + ).trim() + if (currentName) incoming.push({ name: currentName, isCurrent: true }) + + const cleaned = incoming.filter(x => x?.name).map(x => ({ ...x, name: String(x.name).trim() })) + const deduped: FollowPerson[] = [] + const seen = new Set() + for (const p of cleaned) { + const k = p.name + if (seen.has(k)) continue + seen.add(k) + deduped.push(p) + } + + if (deduped.length) { + // 规则:往期跟进人(灰色)按时间顺序在前;当前跟进人(蓝色)放在最后。 + const current = deduped.filter(p => p.isCurrent) + const history = deduped.filter(p => !p.isCurrent) + history.sort((a, b) => { + const at = a.time ? dayjs(a.time).valueOf() : 0 + const bt = b.time ? dayjs(b.time).valueOf() : 0 + return at - bt + }) + // 多个 current 时取最后一个为“当前” + const currentOne = current.length ? current[current.length - 1] : undefined + return currentOne ? history.concat([{ ...currentOne, isCurrent: true }]) : history + } + + // 无真实跟进人信息:尽量展示一个可识别的占位,避免“凭空造历史数据”造成误导。 + const uid = Number(company?.userId) + if (Number.isFinite(uid) && uid > 0) return [{ name: `员工${uid}`, isCurrent: true }] + return [] +} + +const getAssignDateText = (company?: CreditCompany) => { + const anyCompany = company as any + const raw = anyCompany?.assignDate || anyCompany?.assignTime || anyCompany?.updateTime || anyCompany?.createTime + const t = String(raw || '').trim() + if (!t) return '' + const d = dayjs(t) + if (!d.isValid()) return t + return d.format('YYYY-MM-DD') +} + +export default function CreditCompanyDetailPage() { + const router = useRouter() + const companyId = useMemo(() => { + const id = Number((router?.params as any)?.id) + return Number.isFinite(id) && id > 0 ? id : undefined + }, [router?.params]) + + const statusBarHeight = useMemo(() => { + try { + const info = Taro.getSystemInfoSync() + return Number(info?.statusBarHeight || 0) + } catch (_e) { + return 0 + } + }, []) + + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [company, setCompany] = useState(null) + + const reload = useCallback(async () => { + setError(null) + setLoading(true) + try { + if (!companyId) throw new Error('缺少客户ID') + const res = await getCreditCompany(companyId) + setCompany(res as CreditCompany) + } catch (e) { + console.error('加载客户详情失败:', e) + setCompany(null) + setError(String((e as any)?.message || '加载失败')) + } finally { + setLoading(false) + } + }, [companyId]) + + useDidShow(() => { + reload().then() + }) + + const name = useMemo(() => { + const c = company || ({} as CreditCompany) + return String(c.matchName || c.name || '').trim() + }, [company]) + + const code = useMemo(() => String(company?.code || '').trim(), [company?.code]) + const industry = useMemo(() => getCompanyIndustry(company || undefined), [company]) + const contact = useMemo(() => parseContactFromComments(company?.comments), [company?.comments]) + + const phones = useMemo(() => { + const arr = [...splitPhones(company?.tel), ...splitPhones(company?.moreTel)] + return uniq(arr) + }, [company?.moreTel, company?.tel]) + + const address = useMemo(() => { + const c = company || ({} as CreditCompany) + const region = [c.province, c.city, c.region].filter(Boolean).join('') + const addr = String(c.address || '').trim() + return (region + addr).trim() || region || addr + }, [company]) + + const followPeople = useMemo(() => (company ? normalizeFollowPeople(company) : []), [company]) + const assignDate = useMemo(() => getAssignDateText(company || undefined), [company]) + + const customerStatus = useMemo(() => { + const anyCompany = company as any + const raw = String(anyCompany?.customerStatus || anyCompany?.statusText || '').trim() + const allowed: CustomerStatus[] = ['保护期内', '已签约', '已完成', '保护期外'] + if (allowed.includes(raw as any)) return raw as CustomerStatus + return '保护期内' + }, [company]) + + const headerOffset = statusBarHeight + 80 + + return ( + + + + + 12:00 + + 信号 + Wi-Fi + 电池 + + + + + Taro.navigateBack()}> + 返回 + + 客户详情 + + { + try { + const res = await Taro.showActionSheet({ itemList: ['编辑客户', '刷新'] }) + if (res.tapIndex === 0 && companyId) Taro.navigateTo({ url: `/credit/my-customer/edit?id=${companyId}` }) + if (res.tapIndex === 1) reload() + } catch (e) { + const msg = String((e as any)?.errMsg || (e as any)?.message || e || '') + if (msg.includes('cancel')) return + } + }} + > + ... + + Taro.showToast({ title: '设置(示意)', icon: 'none' })} + > + + + + + + + + {loading ? ( + + + 加载中... + + ) : error ? ( + + {error} + + + + + ) : !company ? ( + + + + ) : ( + + {name || '—'} + + + + 统一代码 + {code || '—'} + + + + 所属行业 + {industry || '—'} + + + + 客户联系人 + {contact || '—'} + + + + 客户联系方式 + + {phones.length ? ( + {phones.join(',')} + ) : ( + + )} + + + + + 地址 + {address || '—'} + + + + 跟进人 + + {followPeople.length ? ( + followPeople.map((p, idx) => ( + + {idx === followPeople.length - 1 ? p.name : `${p.name},`} + + )) + ) : ( + + )} + + + + + 分配日期 + {assignDate || '—'} + + + + 客户状态 + {customerStatus} + + + + )} + + + + + + + + ) +} diff --git a/src/credit/mp-customer/edit.config.ts b/src/credit/mp-customer/edit.config.ts new file mode 100644 index 0000000..debc6ad --- /dev/null +++ b/src/credit/mp-customer/edit.config.ts @@ -0,0 +1,7 @@ +export default definePageConfig({ + navigationBarTitleText: '修改客户信息', + navigationBarTextStyle: 'black', + navigationBarBackgroundColor: '#ffffff', + navigationStyle: 'custom' +}) + diff --git a/src/credit/mp-customer/edit.tsx b/src/credit/mp-customer/edit.tsx new file mode 100644 index 0000000..577c70f --- /dev/null +++ b/src/credit/mp-customer/edit.tsx @@ -0,0 +1,364 @@ +import { useCallback, useMemo, useState } from 'react' +import Taro, { useDidShow, useRouter } from '@tarojs/taro' +import { View, Text } from '@tarojs/components' +import { Cell, CellGroup, ConfigProvider, Popup, Tag, TextArea } from '@nutui/nutui-react-taro' + +import { getCreditCompany } from '@/api/credit/creditCompany' +import type { CreditCompany } from '@/api/credit/creditCompany/model' +import { listUsers } from '@/api/system/user' +import type { User } from '@/api/system/user/model' +import FixedButton from '@/components/FixedButton' + +type CustomerStatus = '保护期内' | '已签约' | '已完成' | '保护期外' + +const STATUS_OPTIONS: CustomerStatus[] = ['保护期内', '已签约', '已完成', '保护期外'] +const PENDING_EDIT_STORAGE_KEY = 'credit_company_pending_edit_map' + +const safeParseJSON = (v: any): T | null => { + try { + if (!v) return null + if (typeof v === 'object') return v as T + if (typeof v === 'string') return JSON.parse(v) as T + return null + } catch (_e) { + return null + } +} + +const splitPhones = (raw?: string) => { + const text = String(raw || '').trim() + if (!text) return [] + return text + .split(/[\s,,;;、\n\r]+/g) + .map(s => s.trim()) + .filter(Boolean) +} + +const getCompanyPhones = (c?: CreditCompany | null) => { + if (!c) return [] + const arr = [...splitPhones(c.tel), ...splitPhones(c.moreTel)] + return Array.from(new Set(arr)) +} + +const getCompanyIndustry = (c?: CreditCompany | null) => { + if (!c) return '' + return String( + c.nationalStandardIndustryCategories6 || + c.nationalStandardIndustryCategories2 || + c.nationalStandardIndustryCategories || + c.institutionType || + '' + ).trim() +} + +type PendingEdit = { + status: CustomerStatus + remark: string + submittedAt: string +} + +const loadPendingMap = (): Record => { + try { + const raw = Taro.getStorageSync(PENDING_EDIT_STORAGE_KEY) + return safeParseJSON>(raw) || {} + } catch (_e) { + return {} + } +} + +const savePendingMap = (map: Record) => { + try { + Taro.setStorageSync(PENDING_EDIT_STORAGE_KEY, map) + } catch (_e) { + // ignore + } +} + +export default function CreditCompanyEditPage() { + const router = useRouter() + const companyId = Number(router?.params?.id) + + const statusBarHeight = useMemo(() => { + try { + const info = Taro.getSystemInfoSync() + return Number(info?.statusBarHeight || 0) + } catch (_e) { + return 0 + } + }, []) + + const [loading, setLoading] = useState(false) + const [company, setCompany] = useState(null) + const [staffList, setStaffList] = useState([]) + const [statusPickerVisible, setStatusPickerVisible] = useState(false) + + const [customerStatus, setCustomerStatus] = useState('保护期内') + const [remark, setRemark] = useState('') + const [pending, setPending] = useState(false) + const [pendingAt, setPendingAt] = useState(undefined) + const [submitting, setSubmitting] = useState(false) + + const ensureStaffLoaded = useCallback(async () => { + if (staffList.length) return + try { + const res = await listUsers({ isStaff: true } as any) + setStaffList((res || []) as User[]) + } catch (_e) { + // ignore (只影响“跟进人”展示) + } + }, [staffList.length]) + + const staffNameMap = useMemo(() => { + const map = new Map() + for (const u of staffList) { + if (!u?.userId) continue + map.set(u.userId, String(u.realName || u.nickname || u.username || `员工${u.userId}`)) + } + return map + }, [staffList]) + + const followRealName = useMemo(() => { + if (!company?.userId) return '' + return staffNameMap.get(Number(company.userId)) || '' + }, [company?.userId, staffNameMap]) + + const assignDate = useMemo(() => { + // 兼容:接口未提供“分配日期”字段时,用 updateTime/createTime 兜底展示 + return String(company?.updateTime || company?.createTime || '') + }, [company?.createTime, company?.updateTime]) + + const pendingKey = useMemo(() => (Number.isFinite(companyId) && companyId > 0 ? String(companyId) : ''), [companyId]) + + const loadCompany = useCallback(async () => { + if (!Number.isFinite(companyId) || companyId <= 0) { + Taro.showToast({ title: '参数错误', icon: 'none' }) + return + } + + setLoading(true) + try { + const data = await getCreditCompany(companyId) + setCompany(data) + + // 从本地 pending map 恢复“待审核”状态与内容(纯前端模拟) + const map = loadPendingMap() + const pendingEdit = pendingKey ? map[pendingKey] : undefined + if (pendingEdit) { + setCustomerStatus(pendingEdit.status) + setRemark(pendingEdit.remark) + setPending(true) + setPendingAt(pendingEdit.submittedAt) + } else { + setCustomerStatus('保护期内') + setRemark('') + setPending(false) + setPendingAt(undefined) + } + } catch (e) { + console.error('加载客户信息失败:', e) + Taro.showToast({ title: '加载失败', icon: 'none' }) + } finally { + setLoading(false) + } + }, [companyId, pendingKey]) + + useDidShow(() => { + loadCompany().then() + ensureStaffLoaded().then() + }) + + const submitOrWithdraw = async () => { + if (submitting) return + if (!pendingKey) return + + // 撤回修改 + if (pending) { + setSubmitting(true) + try { + const map = loadPendingMap() + delete map[pendingKey] + savePendingMap(map) + setPending(false) + setPendingAt(undefined) + Taro.showToast({ title: '已撤回,可重新修改', icon: 'success' }) + } finally { + setSubmitting(false) + } + return + } + + // 提交修改(仅备注 + 状态),进入待审核(纯前端模拟) + const r = remark.trim() + if (!r) { + Taro.showToast({ title: '请填写备注信息', icon: 'none' }) + return + } + + setSubmitting(true) + try { + const map = loadPendingMap() + const now = new Date().toISOString() + map[pendingKey] = { status: customerStatus, remark: r, submittedAt: now } + savePendingMap(map) + + setPending(true) + setPendingAt(now) + Taro.showToast({ title: '提交成功,等待审核', icon: 'success' }) + } finally { + setSubmitting(false) + } + } + + const phones = useMemo(() => getCompanyPhones(company), [company]) + const industry = useMemo(() => getCompanyIndustry(company), [company]) + const companyName = String(company?.matchName || company?.name || '') + + return ( + + + + + 12:00 + + 信号 + Wi-Fi + 电池 + + + + + Taro.navigateBack()}> + 返回 + + 修改客户信息 + + + ... + + + + + + + + {loading ? ( + + 加载中... + + ) : ( + <> + + + + + {industry || '-'} + + } + /> + + + {phones.map(p => ( + + {p} + + ))} + + ) : ( + '-' + ) + } + /> + + + {followRealName || '未分配'} + + } + /> + + + + {customerStatus} + + {pending && 待审核} + + } + onClick={() => { + if (pending) return + setStatusPickerVisible(true) + }} + /> + + + + 修改内容备注信息 +