diff --git a/src/app.config.ts b/src/app.config.ts index 36ef2d4..94adef1 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -124,6 +124,7 @@ export default { "order/add", "company/index", "company/add", + "company/detail", "company/edit" ] } diff --git a/src/credit/company/detail.config.ts b/src/credit/company/detail.config.ts new file mode 100644 index 0000000..0aa6f01 --- /dev/null +++ b/src/credit/company/detail.config.ts @@ -0,0 +1,7 @@ +export default definePageConfig({ + navigationBarTitleText: '客户详情', + navigationBarTextStyle: 'black', + navigationBarBackgroundColor: '#ffffff', + navigationStyle: 'custom' +}) + diff --git a/src/credit/company/detail.tsx b/src/credit/company/detail.tsx new file mode 100644 index 0000000..e1277ef --- /dev/null +++ b/src/credit/company/detail.tsx @@ -0,0 +1,345 @@ +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/company/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/company/index.tsx b/src/credit/company/index.tsx index 2d9a875..09981bf 100644 --- a/src/credit/company/index.tsx +++ b/src/credit/company/index.tsx @@ -479,7 +479,7 @@ export default function CreditCompanyPage() { onClick={() => { if (selectMode) return if (!c?.id) return - Taro.navigateTo({ url: `/credit/company/edit?id=${c.id}` }) + Taro.navigateTo({ url: `/credit/company/detail?id=${c.id}` }) }} >