diff --git a/src/app.config.ts b/src/app.config.ts index 94adef1..5ba6c05 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -125,6 +125,7 @@ export default { "company/index", "company/add", "company/detail", + "company/follow-step1", "company/edit" ] } diff --git a/src/credit/company/detail.tsx b/src/credit/company/detail.tsx index e1277ef..7df249a 100644 --- a/src/credit/company/detail.tsx +++ b/src/credit/company/detail.tsx @@ -332,8 +332,11 @@ export default function CreditCompanyDetailPage() { block style={{ background: '#ef4444', borderColor: '#ef4444' }} onClick={() => { - console.log('follow company:', companyId) - Taro.showToast({ title: '开始跟进该客户', icon: 'none' }) + if (!companyId) { + Taro.showToast({ title: '缺少客户ID', icon: 'none' }) + return + } + Taro.navigateTo({ url: `/credit/company/follow-step1?id=${companyId}` }) }} > 跟进 diff --git a/src/credit/company/follow-step1.config.ts b/src/credit/company/follow-step1.config.ts new file mode 100644 index 0000000..d4db67b --- /dev/null +++ b/src/credit/company/follow-step1.config.ts @@ -0,0 +1,7 @@ +export default definePageConfig({ + navigationBarTitleText: '客户跟进', + navigationBarTextStyle: 'black', + navigationBarBackgroundColor: '#ffffff', + navigationStyle: 'custom' +}) + diff --git a/src/credit/company/follow-step1.tsx b/src/credit/company/follow-step1.tsx new file mode 100644 index 0000000..f68ff76 --- /dev/null +++ b/src/credit/company/follow-step1.tsx @@ -0,0 +1,427 @@ +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 { getCreditCompany } from '@/api/credit/creditCompany' +import type { CreditCompany } from '@/api/credit/creditCompany/model' + +type Intention = '无意向' | '有意向' + +type FollowStep1Draft = { + submitted: boolean + submittedAt: string + companyId: number + contact: string + phone: string + audioSelected: boolean + smsShots: number + callShots: number + remark: string + intention: Intention + // 业务规则模拟: + // 多次沟通 + 有意向 => 无需审核(可直接进入下一步,下一步不在本页实现) + // 多次沟通 + 无意向 => 需管理员审核 + communicationCount: number + needApproval: boolean + isApproved: 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 parseContactFromComments = (comments?: string) => { + const txt = String(comments || '').trim() + if (!txt) return '' + const m = txt.match(/联系人:([^;;]+)/) + return String(m?.[1] || '').trim() +} + +const makeThumb = () => ({ + id: `${Date.now()}-${Math.random().toString(16).slice(2)}` +}) + +const getDraftKey = (companyId: number) => `credit_company_follow_step1:${companyId}` +const getCountKey = (companyId: number) => `credit_company_follow_comm_count:${companyId}` + +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 + } +} + +export default function CreditCompanyFollowStep1Page() { + 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 [submitted, setSubmitted] = useState(false) + const [audioSelected, setAudioSelected] = useState(false) + const [smsShots, setSmsShots] = useState>([]) + const [callShots, setCallShots] = useState>([]) + const [remark, setRemark] = useState('') + const [intention, setIntention] = useState(undefined) + + const loadDraft = useCallback(() => { + if (!companyId) return + const raw = Taro.getStorageSync(getDraftKey(companyId)) + const saved = safeParseJSON(raw) + if (!saved?.submitted) return + setSubmitted(true) + setAudioSelected(!!saved.audioSelected) + setSmsShots(Array.from({ length: Math.max(0, Math.min(6, Number(saved.smsShots || 0))) }, makeThumb)) + setCallShots(Array.from({ length: Math.max(0, Math.min(6, Number(saved.callShots || 0))) }, makeThumb)) + setRemark(String(saved.remark || '')) + setIntention(saved.intention) + }, [companyId]) + + const reloadCompany = 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(() => { + reloadCompany().then() + loadDraft() + }) + + const contact = useMemo(() => parseContactFromComments(company?.comments), [company?.comments]) + + const phone = useMemo(() => { + const arr = uniq([...splitPhones(company?.tel), ...splitPhones(company?.moreTel)]) + return String(arr[0] || '').trim() + }, [company?.moreTel, company?.tel]) + + const canEdit = !submitted + + const addSmsShot = () => { + if (!canEdit) return + if (smsShots.length >= 6) { + Taro.showToast({ title: '短信截图已达上限(6张)', icon: 'none' }) + return + } + setSmsShots(prev => prev.concat(makeThumb())) + } + + const addCallShot = () => { + if (!canEdit) return + if (callShots.length >= 6) { + Taro.showToast({ title: '电话沟通截图已达上限(6张)', icon: 'none' }) + return + } + setCallShots(prev => prev.concat(makeThumb())) + } + + const chooseAudio = async () => { + if (!canEdit) return + // 本步骤仅做“选择录音文件”的交互模拟 + setAudioSelected(true) + Taro.showToast({ title: '已选择录音文件(模拟)', icon: 'none' }) + } + + const submit = async () => { + if (!companyId) { + Taro.showToast({ title: '缺少客户ID', icon: 'none' }) + return + } + if (!intention) { + Taro.showToast({ title: '请选择意向(无意向/有意向)', icon: 'none' }) + return + } + if (!remark.trim()) { + Taro.showToast({ title: '请填写沟通情况', icon: 'none' }) + return + } + + if (!smsShots.length && !callShots.length) { + Taro.showToast({ title: '建议至少上传1张截图(非必填)', icon: 'none' }) + } + + const prevCountRaw = Taro.getStorageSync(getCountKey(companyId)) + const prevCount = Number(prevCountRaw || 0) + const communicationCount = (Number.isFinite(prevCount) ? prevCount : 0) + 1 + Taro.setStorageSync(getCountKey(companyId), communicationCount) + + const needApproval = communicationCount > 1 && intention === '无意向' + const isApproved = false // 模拟:默认未审核;后续步骤可检查该标志 + + const payload: FollowStep1Draft = { + submitted: true, + submittedAt: dayjs().format('YYYY-MM-DD HH:mm:ss'), + companyId, + contact, + phone, + audioSelected, + smsShots: smsShots.length, + callShots: callShots.length, + remark: remark.trim(), + intention, + communicationCount, + needApproval, + isApproved + } + + Taro.setStorageSync(getDraftKey(companyId), payload) + + setSubmitted(true) + + await Taro.showModal({ + title: '提示', + content: '跟进信息已提交\n请等待管理员审核', + showCancel: false + }) + } + + const headerOffset = statusBarHeight + 80 + + return ( + + + + + 12:00 + + 信号 + Wi-Fi + 电池 + + + + + Taro.navigateBack()}> + 返回 + + 客户跟进 + + { + try { + const res = await Taro.showActionSheet({ itemList: ['刷新'] }) + if (res.tapIndex === 0) reloadCompany() + } catch (e) { + const msg = String((e as any)?.errMsg || (e as any)?.message || e || '') + if (msg.includes('cancel')) return + } + }} + > + ... + + + + + + + + + + {loading ? ( + + + 加载中... + + ) : error ? ( + + {error} + + + + + ) : !company ? ( + + + + ) : ( + + 加微信前沟通 + + + + + 联系人 + + {contact || '—'} + + + + + 联系电话 + + {phone || '—'} + + + + + 电话录音 + + + + + + + + + 短信截图 + {smsShots.length}/6 + + + {smsShots.map((x, idx) => ( + + 图{idx + 1} + + ))} + = 6 ? 'border-gray-200 text-gray-200' : 'border-gray-300 text-gray-400' + }`} + onClick={addSmsShot} + > + + + + + + + + + 电话沟通截图 + {callShots.length}/6 + + + {callShots.map((x, idx) => ( + + 图{idx + 1} + + ))} + = 6 ? 'border-gray-200 text-gray-200' : 'border-gray-300 text-gray-400' + }`} + onClick={addCallShot} + > + + + + + + + + + 沟通情况* + + +