From f4a1fab4cb274f74adba939444e938b28b0de244 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, 5 Mar 2026 12:50:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(credit):=20=E9=87=8D=E6=9E=84=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=85=AC=E5=8F=B8=E6=A8=A1=E5=9D=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 company/add 和 company/edit 页面路由配置 - 移除 Banner.tsx 中的调试日志 - 从 find.tsx 中移除未使用的 total 状态变量 - 更新 credit/order/index.config.ts 页面标题为订单管理并添加自定义导航栏样式 - 从 credit/company/index.tsx 中移除内联添加客户的对话框相关代码 - 将添加客户按钮跳转到独立的 /credit/company/add 页面 - 为公司列表项添加编辑功能点击事件,可跳转至 /credit/company/edit?id=xx - 优化信用订单页面结构,替换为新的订单管理界面 - 实现订单搜索、筛选、日期范围选择等功能 - 添加订单统计信息展示(总数、本金、利息) - 实现模拟数据加载和分页功能 --- src/app.config.ts | 4 +- src/credit/company/add.config.ts | 6 + src/credit/company/add.tsx | 262 ++++++++ src/credit/company/edit.config.ts | 7 + src/credit/company/edit.tsx | 364 +++++++++++ src/credit/company/index.tsx | 175 +----- src/credit/order/index.config.ts | 5 +- src/credit/order/index.tsx | 968 ++++++++++++------------------ src/pages/find/find.tsx | 3 - src/pages/index/Banner.tsx | 1 + src/pages/user/user.tsx | 2 +- 11 files changed, 1035 insertions(+), 762 deletions(-) create mode 100644 src/credit/company/add.config.ts create mode 100644 src/credit/company/add.tsx create mode 100644 src/credit/company/edit.config.ts create mode 100644 src/credit/company/edit.tsx diff --git a/src/app.config.ts b/src/app.config.ts index 769bc7c..36ef2d4 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -122,7 +122,9 @@ export default { "pages": [ "order/index", "order/add", - "company/index" + "company/index", + "company/add", + "company/edit" ] } ], diff --git a/src/credit/company/add.config.ts b/src/credit/company/add.config.ts new file mode 100644 index 0000000..f724d32 --- /dev/null +++ b/src/credit/company/add.config.ts @@ -0,0 +1,6 @@ +export default definePageConfig({ + navigationBarTitleText: '添加客户', + navigationBarTextStyle: 'black', + navigationBarBackgroundColor: '#ffffff', + navigationStyle: 'custom' +}) diff --git a/src/credit/company/add.tsx b/src/credit/company/add.tsx new file mode 100644 index 0000000..1f6502d --- /dev/null +++ b/src/credit/company/add.tsx @@ -0,0 +1,262 @@ +import { useMemo, useState } from 'react' +import Taro from '@tarojs/taro' +import { View, Text } from '@tarojs/components' +import { Address, Button, Cell, CellGroup, ConfigProvider, Input } from '@nutui/nutui-react-taro' +import { Close } from '@nutui/icons-react-taro' + +import RegionData from '@/api/json/regions-data.json' +import { addCreditCompany, pageCreditCompany } from '@/api/credit/creditCompany' +import type { CreditCompany } from '@/api/credit/creditCompany/model' +import FixedButton from '@/components/FixedButton' + +const shortRegion = (label?: string) => { + const v = String(label || '').trim() + if (!v) return '' + return v + .replace(/壮族自治区|回族自治区|维吾尔自治区|自治区/g, '') + .replace(/省|市/g, '') +} + +export default function CreditCompanyAddPage() { + const statusBarHeight = useMemo(() => { + try { + const info = Taro.getSystemInfoSync() + return Number(info?.statusBarHeight || 0) + } catch (_e) { + return 0 + } + }, []) + + const [name, setName] = useState('') + const [code, setCode] = useState('') + const [industry, setIndustry] = useState('') + const [contact, setContact] = useState('') + const [phones, setPhones] = useState(['']) + const [province, setProvince] = useState('广西壮族自治区') + const [city, setCity] = useState('南宁市') + const [detailAddress, setDetailAddress] = useState('') + const [projectName, setProjectName] = useState('') + + const [regionVisible, setRegionVisible] = useState(false) + const [submitting, setSubmitting] = useState(false) + + const cityOptions = useMemo(() => { + // NutUI Address options: [{ text, value, children }] + // @ts-ignore + return (RegionData || []).map(a => ({ + value: a.label, + text: a.label, + children: (a.children || []).map(b => ({ + value: b.label, + text: b.label, + children: (b.children || []).map(c => ({ + value: c.label, + text: c.label + })) + })) + })) + }, []) + + const updatePhone = (idx: number, v: string) => { + setPhones(prev => prev.map((p, i) => (i === idx ? v : p))) + } + + const addPhone = () => { + setPhones(prev => prev.concat('')) + } + + const removePhone = (idx: number) => { + setPhones(prev => prev.filter((_, i) => i !== idx)) + } + + const submit = async () => { + if (submitting) return + + const companyName = name.trim() + if (!companyName) { + Taro.showToast({ title: '请输入客户名称', icon: 'none' }) + return + } + + const firstPhone = String(phones?.[0] || '').trim() + if (!firstPhone) { + Taro.showToast({ title: '请输入客户联系方式', icon: 'none' }) + return + } + + setSubmitting(true) + try { + // 先查重:公司名称/统一代码 + const keywords = (code.trim() || companyName).trim() + const existsRes = await pageCreditCompany({ page: 1, limit: 1, keywords } as any) + const exists = ((existsRes?.list || []) as CreditCompany[]).some(c => c?.deleted !== 1) + if (exists) { + await Taro.showModal({ + title: '提示', + content: '该公司信息已存在,请联系管理员核实', + showCancel: false + }) + return + } + + const moreTel = phones + .slice(1) + .map(p => String(p || '').trim()) + .filter(Boolean) + .join(',') + + const payload: CreditCompany = { + name: companyName, + matchName: companyName, + code: code.trim() || undefined, + tel: firstPhone, + moreTel: moreTel || undefined, + province, + city, + address: detailAddress.trim() || undefined, + nationalStandardIndustryCategories: industry.trim() || undefined, + comments: [ + contact.trim() ? `联系人:${contact.trim()}` : '', + projectName.trim() ? `项目:${projectName.trim()}` : '' + ] + .filter(Boolean) + .join(';') || undefined + } + + await addCreditCompany(payload) + Taro.showToast({ title: '添加成功', icon: 'success' }) + setTimeout(() => { + Taro.navigateBack() + }, 500) + } catch (e) { + console.error('添加客户失败:', e) + Taro.showToast({ title: '添加失败,请重试', icon: 'none' }) + } finally { + setSubmitting(false) + } + } + + return ( + + + + + 12:00 + + 信号 + Wi-Fi + 电池 + + + + + Taro.navigateBack()} + > + 返回 + + 添加客户 + + + ... + + + + + + + + + 所有输入框右侧默认提示:请输入 + + + + + + + + + + + + + + + + + + + {phones.map((p, idx) => ( + + + + updatePhone(idx, v)} placeholder="请输入" /> + + {idx > 0 && ( + + )} + + + ))} + + + +添加其他手机号码 + + + + + + setRegionVisible(true)} + /> + setRegionVisible(true)} + /> + + + + + + + + + +
{ + const arr = (value || []).filter(Boolean) + if (arr[0]) setProvince(arr[0]) + if (arr[1]) setCity(arr[1]) + setRegionVisible(false) + }} + onClose={() => setRegionVisible(false)} + /> + + + + + ) +} diff --git a/src/credit/company/edit.config.ts b/src/credit/company/edit.config.ts new file mode 100644 index 0000000..debc6ad --- /dev/null +++ b/src/credit/company/edit.config.ts @@ -0,0 +1,7 @@ +export default definePageConfig({ + navigationBarTitleText: '修改客户信息', + navigationBarTextStyle: 'black', + navigationBarBackgroundColor: '#ffffff', + navigationStyle: 'custom' +}) + diff --git a/src/credit/company/edit.tsx b/src/credit/company/edit.tsx new file mode 100644 index 0000000..577c70f --- /dev/null +++ b/src/credit/company/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) + }} + /> + + + + 修改内容备注信息 +