feat(credit): 重构订单管理页面并优化公司模块功能
- 新增 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 - 优化信用订单页面结构,替换为新的订单管理界面 - 实现订单搜索、筛选、日期范围选择等功能 - 添加订单统计信息展示(总数、本金、利息) - 实现模拟数据加载和分页功能
This commit is contained in:
@@ -122,7 +122,9 @@ export default {
|
|||||||
"pages": [
|
"pages": [
|
||||||
"order/index",
|
"order/index",
|
||||||
"order/add",
|
"order/add",
|
||||||
"company/index"
|
"company/index",
|
||||||
|
"company/add",
|
||||||
|
"company/edit"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
6
src/credit/company/add.config.ts
Normal file
6
src/credit/company/add.config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '添加客户',
|
||||||
|
navigationBarTextStyle: 'black',
|
||||||
|
navigationBarBackgroundColor: '#ffffff',
|
||||||
|
navigationStyle: 'custom'
|
||||||
|
})
|
||||||
262
src/credit/company/add.tsx
Normal file
262
src/credit/company/add.tsx
Normal file
@@ -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<string[]>([''])
|
||||||
|
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 (
|
||||||
|
<View className="bg-pink-50 min-h-screen">
|
||||||
|
<ConfigProvider>
|
||||||
|
<View
|
||||||
|
className="fixed z-50 top-0 left-0 right-0 bg-pink-50"
|
||||||
|
style={{ paddingTop: `${statusBarHeight}px` }}
|
||||||
|
>
|
||||||
|
<View className="px-4 h-10 flex items-center justify-between text-sm text-gray-900">
|
||||||
|
<Text className="font-medium">12:00</Text>
|
||||||
|
<View className="flex items-center gap-2 text-xs text-gray-600">
|
||||||
|
<Text>信号</Text>
|
||||||
|
<Text>Wi-Fi</Text>
|
||||||
|
<Text>电池</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="px-4 pb-2 flex items-center justify-between">
|
||||||
|
<Text
|
||||||
|
className="text-sm text-gray-700"
|
||||||
|
onClick={() => Taro.navigateBack()}
|
||||||
|
>
|
||||||
|
返回
|
||||||
|
</Text>
|
||||||
|
<Text className="text-base font-semibold text-gray-900">添加客户</Text>
|
||||||
|
<View className="flex items-center gap-3">
|
||||||
|
<View className="w-7 h-7 rounded-full border border-gray-300 flex items-center justify-center text-gray-700">
|
||||||
|
<Text>...</Text>
|
||||||
|
</View>
|
||||||
|
<View className="w-7 h-7 rounded-full border border-gray-300" />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ paddingTop: `${statusBarHeight + 80}px` }} className="max-w-md mx-auto">
|
||||||
|
<View className="px-3 pt-2 pb-2 text-sm text-gray-500">
|
||||||
|
<Text>所有输入框右侧默认提示:请输入</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<CellGroup>
|
||||||
|
<Cell title="客户名称*">
|
||||||
|
<Input value={name} onChange={setName} placeholder="请输入" />
|
||||||
|
</Cell>
|
||||||
|
<Cell title="统一代码">
|
||||||
|
<Input value={code} onChange={setCode} placeholder="请输入" />
|
||||||
|
</Cell>
|
||||||
|
<Cell title="所属行业">
|
||||||
|
<Input value={industry} onChange={setIndustry} placeholder="请输入" />
|
||||||
|
</Cell>
|
||||||
|
<Cell title="客户联系人">
|
||||||
|
<Input value={contact} onChange={setContact} placeholder="请输入" />
|
||||||
|
</Cell>
|
||||||
|
|
||||||
|
<Cell title="客户联系方式*">
|
||||||
|
<View className="w-full">
|
||||||
|
{phones.map((p, idx) => (
|
||||||
|
<View key={idx} className={idx === 0 ? '' : 'mt-2'}>
|
||||||
|
<View className="flex items-center gap-2">
|
||||||
|
<View className="flex-1">
|
||||||
|
<Input value={p} onChange={(v) => updatePhone(idx, v)} placeholder="请输入" />
|
||||||
|
</View>
|
||||||
|
{idx > 0 && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
fill="outline"
|
||||||
|
type="danger"
|
||||||
|
icon={<Close />}
|
||||||
|
onClick={() => removePhone(idx)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
<View className="mt-2">
|
||||||
|
<Text className="text-red-500" onClick={addPhone}>
|
||||||
|
+添加其他手机号码
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Cell>
|
||||||
|
|
||||||
|
<Cell
|
||||||
|
title="所在省份"
|
||||||
|
description={shortRegion(province) || '请选择'}
|
||||||
|
onClick={() => setRegionVisible(true)}
|
||||||
|
/>
|
||||||
|
<Cell
|
||||||
|
title="所在地级市"
|
||||||
|
description={shortRegion(city) || '请选择'}
|
||||||
|
onClick={() => setRegionVisible(true)}
|
||||||
|
/>
|
||||||
|
<Cell title="详细地址">
|
||||||
|
<Input value={detailAddress} onChange={setDetailAddress} placeholder="请输入" />
|
||||||
|
</Cell>
|
||||||
|
<Cell title="项目名称">
|
||||||
|
<Input value={projectName} onChange={setProjectName} placeholder="请输入" />
|
||||||
|
</Cell>
|
||||||
|
</CellGroup>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Address
|
||||||
|
visible={regionVisible}
|
||||||
|
options={cityOptions as any}
|
||||||
|
title="选择省市"
|
||||||
|
onChange={(value: any[]) => {
|
||||||
|
const arr = (value || []).filter(Boolean)
|
||||||
|
if (arr[0]) setProvince(arr[0])
|
||||||
|
if (arr[1]) setCity(arr[1])
|
||||||
|
setRegionVisible(false)
|
||||||
|
}}
|
||||||
|
onClose={() => setRegionVisible(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FixedButton
|
||||||
|
text={submitting ? '提交中...' : '确定'}
|
||||||
|
background="#ef4444"
|
||||||
|
disabled={submitting}
|
||||||
|
onClick={submit}
|
||||||
|
/>
|
||||||
|
</ConfigProvider>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
7
src/credit/company/edit.config.ts
Normal file
7
src/credit/company/edit.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '修改客户信息',
|
||||||
|
navigationBarTextStyle: 'black',
|
||||||
|
navigationBarBackgroundColor: '#ffffff',
|
||||||
|
navigationStyle: 'custom'
|
||||||
|
})
|
||||||
|
|
||||||
364
src/credit/company/edit.tsx
Normal file
364
src/credit/company/edit.tsx
Normal file
@@ -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 = <T,>(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<string, PendingEdit> => {
|
||||||
|
try {
|
||||||
|
const raw = Taro.getStorageSync(PENDING_EDIT_STORAGE_KEY)
|
||||||
|
return safeParseJSON<Record<string, PendingEdit>>(raw) || {}
|
||||||
|
} catch (_e) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const savePendingMap = (map: Record<string, PendingEdit>) => {
|
||||||
|
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<CreditCompany | null>(null)
|
||||||
|
const [staffList, setStaffList] = useState<User[]>([])
|
||||||
|
const [statusPickerVisible, setStatusPickerVisible] = useState(false)
|
||||||
|
|
||||||
|
const [customerStatus, setCustomerStatus] = useState<CustomerStatus>('保护期内')
|
||||||
|
const [remark, setRemark] = useState('')
|
||||||
|
const [pending, setPending] = useState(false)
|
||||||
|
const [pendingAt, setPendingAt] = useState<string | undefined>(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<number, string>()
|
||||||
|
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 (
|
||||||
|
<View className="bg-pink-50 min-h-screen">
|
||||||
|
<ConfigProvider>
|
||||||
|
<View className="fixed z-50 top-0 left-0 right-0 bg-pink-50" style={{ paddingTop: `${statusBarHeight}px` }}>
|
||||||
|
<View className="px-4 h-10 flex items-center justify-between text-sm text-gray-900">
|
||||||
|
<Text className="font-medium">12:00</Text>
|
||||||
|
<View className="flex items-center gap-2 text-xs text-gray-600">
|
||||||
|
<Text>信号</Text>
|
||||||
|
<Text>Wi-Fi</Text>
|
||||||
|
<Text>电池</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="px-4 pb-2 flex items-center justify-between">
|
||||||
|
<Text className="text-sm text-gray-700" onClick={() => Taro.navigateBack()}>
|
||||||
|
返回
|
||||||
|
</Text>
|
||||||
|
<Text className="text-base font-semibold text-gray-900">修改客户信息</Text>
|
||||||
|
<View className="flex items-center gap-3">
|
||||||
|
<View className="w-7 h-7 rounded-full border border-gray-300 flex items-center justify-center text-gray-700">
|
||||||
|
<Text>...</Text>
|
||||||
|
</View>
|
||||||
|
<View className="w-7 h-7 rounded-full border border-gray-300" />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ paddingTop: `${statusBarHeight + 80}px` }} className="max-w-md mx-auto px-3">
|
||||||
|
{loading ? (
|
||||||
|
<View className="py-12 text-center text-gray-500">
|
||||||
|
<Text>加载中...</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CellGroup>
|
||||||
|
<Cell title="客户名称" description={companyName || '-'} />
|
||||||
|
<Cell title="统一代码" description={String(company?.code || '-')} />
|
||||||
|
<Cell
|
||||||
|
title="所属行业"
|
||||||
|
description={
|
||||||
|
<Text className="text-red-500">
|
||||||
|
{industry || '-'}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Cell title="客户联系人" description={String((company as any)?.contactName || '-')} />
|
||||||
|
<Cell
|
||||||
|
title="客户联系方式"
|
||||||
|
description={
|
||||||
|
phones.length ? (
|
||||||
|
<View className="flex flex-col">
|
||||||
|
{phones.map(p => (
|
||||||
|
<Text key={p} className="text-gray-600">
|
||||||
|
{p}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Cell title="地址" description={String(company?.address || '-')} />
|
||||||
|
<Cell
|
||||||
|
title="跟进人"
|
||||||
|
description={
|
||||||
|
<Text className="text-blue-600">
|
||||||
|
{followRealName || '未分配'}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Cell title="分配日期" description={assignDate || '-'} />
|
||||||
|
<Cell
|
||||||
|
title="客户状态"
|
||||||
|
description={
|
||||||
|
<View className="flex items-center gap-2">
|
||||||
|
<Text className={customerStatus === '保护期内' ? 'text-green-600' : 'text-gray-700'}>
|
||||||
|
{customerStatus}
|
||||||
|
</Text>
|
||||||
|
{pending && <Tag type="warning">待审核</Tag>}
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (pending) return
|
||||||
|
setStatusPickerVisible(true)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
|
<View className="mt-3 px-1">
|
||||||
|
<View className="text-sm text-gray-700 mb-2">修改内容备注信息</View>
|
||||||
|
<TextArea
|
||||||
|
placeholder="请输入客户最新信息情况,管理员审核通过后,客户信息才可变更!"
|
||||||
|
value={remark}
|
||||||
|
onChange={(v) => setRemark(v)}
|
||||||
|
rows={4}
|
||||||
|
maxLength={500}
|
||||||
|
showCount
|
||||||
|
autoHeight
|
||||||
|
disabled={pending}
|
||||||
|
/>
|
||||||
|
{pending && (
|
||||||
|
<View className="mt-2 text-xs text-gray-500">
|
||||||
|
已提交审核{pendingAt ? `:${pendingAt}` : ''}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Popup
|
||||||
|
visible={statusPickerVisible}
|
||||||
|
position="bottom"
|
||||||
|
style={{ height: '45vh' }}
|
||||||
|
onClose={() => setStatusPickerVisible(false)}
|
||||||
|
>
|
||||||
|
<View className="p-4">
|
||||||
|
<View className="flex items-center justify-between mb-3">
|
||||||
|
<Text className="text-base font-medium">选择客户状态</Text>
|
||||||
|
<Text className="text-sm text-gray-500" onClick={() => setStatusPickerVisible(false)}>
|
||||||
|
关闭
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<CellGroup>
|
||||||
|
{STATUS_OPTIONS.map(s => (
|
||||||
|
<Cell
|
||||||
|
key={s}
|
||||||
|
title={<Text className={s === customerStatus ? 'text-blue-600' : ''}>{s}</Text>}
|
||||||
|
onClick={() => {
|
||||||
|
setCustomerStatus(s)
|
||||||
|
setStatusPickerVisible(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</CellGroup>
|
||||||
|
</View>
|
||||||
|
</Popup>
|
||||||
|
|
||||||
|
<FixedButton
|
||||||
|
text={submitting ? '处理中...' : pending ? '撤回修改' : '确定修改'}
|
||||||
|
background="#ef4444"
|
||||||
|
disabled={loading || submitting}
|
||||||
|
onClick={submitOrWithdraw}
|
||||||
|
/>
|
||||||
|
</ConfigProvider>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -8,10 +8,8 @@ import {
|
|||||||
CellGroup,
|
CellGroup,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
ConfigProvider,
|
ConfigProvider,
|
||||||
Dialog,
|
|
||||||
Empty,
|
Empty,
|
||||||
InfiniteLoading,
|
InfiniteLoading,
|
||||||
Input,
|
|
||||||
Loading,
|
Loading,
|
||||||
Popup,
|
Popup,
|
||||||
PullToRefresh,
|
PullToRefresh,
|
||||||
@@ -21,7 +19,7 @@ import {
|
|||||||
import { Copy, Phone } from '@nutui/icons-react-taro'
|
import { Copy, Phone } from '@nutui/icons-react-taro'
|
||||||
|
|
||||||
import RegionData from '@/api/json/regions-data.json'
|
import RegionData from '@/api/json/regions-data.json'
|
||||||
import { addCreditCompany, pageCreditCompany, updateCreditCompany } from '@/api/credit/creditCompany'
|
import { pageCreditCompany, updateCreditCompany } from '@/api/credit/creditCompany'
|
||||||
import type { CreditCompany } from '@/api/credit/creditCompany/model'
|
import type { CreditCompany } from '@/api/credit/creditCompany/model'
|
||||||
import { listUsers } from '@/api/system/user'
|
import { listUsers } from '@/api/system/user'
|
||||||
import type { User } from '@/api/system/user/model'
|
import type { User } from '@/api/system/user/model'
|
||||||
@@ -101,17 +99,6 @@ export default function CreditCompanyPage() {
|
|||||||
const [staffSelectedId, setStaffSelectedId] = useState<number | undefined>(undefined)
|
const [staffSelectedId, setStaffSelectedId] = useState<number | undefined>(undefined)
|
||||||
const [assigning, setAssigning] = useState(false)
|
const [assigning, setAssigning] = useState(false)
|
||||||
|
|
||||||
const [addDialogVisible, setAddDialogVisible] = useState(false)
|
|
||||||
const [addStep, setAddStep] = useState<'search' | 'form'>('search')
|
|
||||||
const [addChecking, setAddChecking] = useState(false)
|
|
||||||
const [addSubmitting, setAddSubmitting] = useState(false)
|
|
||||||
const [addName, setAddName] = useState('')
|
|
||||||
const [addCode, setAddCode] = useState('')
|
|
||||||
const [addPhone, setAddPhone] = useState('')
|
|
||||||
const [addCityVisible, setAddCityVisible] = useState(false)
|
|
||||||
const [addCityText, setAddCityText] = useState('')
|
|
||||||
const [addIndustry, setAddIndustry] = useState('')
|
|
||||||
|
|
||||||
const [followMap, setFollowMap] = useState<Record<string, FollowStatus>>(() => {
|
const [followMap, setFollowMap] = useState<Record<string, FollowStatus>>(() => {
|
||||||
const raw = Taro.getStorageSync(FOLLOW_MAP_STORAGE_KEY)
|
const raw = Taro.getStorageSync(FOLLOW_MAP_STORAGE_KEY)
|
||||||
return safeParseJSON<Record<string, FollowStatus>>(raw) || {}
|
return safeParseJSON<Record<string, FollowStatus>>(raw) || {}
|
||||||
@@ -350,83 +337,7 @@ export default function CreditCompanyPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openAddCustomer = () => {
|
const openAddCustomer = () => {
|
||||||
setAddStep('search')
|
Taro.navigateTo({ url: '/credit/company/add' })
|
||||||
setAddName('')
|
|
||||||
setAddCode('')
|
|
||||||
setAddPhone('')
|
|
||||||
setAddCityText('')
|
|
||||||
setAddIndustry('')
|
|
||||||
setAddDialogVisible(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkCompanyExists = async () => {
|
|
||||||
if (addChecking) return
|
|
||||||
const name = addName.trim()
|
|
||||||
const code = addCode.trim()
|
|
||||||
const keywords = (name || code).trim()
|
|
||||||
if (!keywords) {
|
|
||||||
Taro.showToast({ title: '请输入公司名称或统一代码', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setAddChecking(true)
|
|
||||||
try {
|
|
||||||
const res = await pageCreditCompany({ page: 1, limit: 1, keywords } as any)
|
|
||||||
const exists = ((res?.list || []) as CreditCompany[]).some(c => c?.deleted !== 1)
|
|
||||||
if (exists) {
|
|
||||||
await Taro.showModal({
|
|
||||||
title: '提示',
|
|
||||||
content: '该公司信息已存在,请联系管理员核实',
|
|
||||||
showCancel: false
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 未存在:进入录入表单
|
|
||||||
setAddStep('form')
|
|
||||||
} catch (e) {
|
|
||||||
console.error('查询公司失败:', e)
|
|
||||||
Taro.showToast({ title: '查询失败,请重试', icon: 'none' })
|
|
||||||
} finally {
|
|
||||||
setAddChecking(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitAddCustomer = async () => {
|
|
||||||
if (addSubmitting) return
|
|
||||||
const name = addName.trim()
|
|
||||||
const code = addCode.trim()
|
|
||||||
const phone = addPhone.trim()
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
Taro.showToast({ title: '请输入公司名称', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setAddSubmitting(true)
|
|
||||||
try {
|
|
||||||
const regionParts = addCityText.split(' ').map(s => s.trim()).filter(Boolean)
|
|
||||||
const province = regionParts.length >= 2 ? regionParts[0] : undefined
|
|
||||||
const city = regionParts.length >= 2 ? regionParts[1] : (regionParts[0] || undefined)
|
|
||||||
const payload: CreditCompany = {
|
|
||||||
name,
|
|
||||||
matchName: name,
|
|
||||||
code: code || undefined,
|
|
||||||
tel: phone || undefined,
|
|
||||||
province,
|
|
||||||
city,
|
|
||||||
nationalStandardIndustryCategories: addIndustry || undefined
|
|
||||||
}
|
|
||||||
await addCreditCompany(payload)
|
|
||||||
Taro.showToast({ title: '添加成功', icon: 'success' })
|
|
||||||
setAddDialogVisible(false)
|
|
||||||
await reload(true)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('添加客户失败:', e)
|
|
||||||
Taro.showToast({ title: '添加失败,请重试', icon: 'none' })
|
|
||||||
} finally {
|
|
||||||
setAddSubmitting(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ensureStaffLoaded = async () => {
|
const ensureStaffLoaded = async () => {
|
||||||
@@ -564,10 +475,21 @@ export default function CreditCompanyPage() {
|
|||||||
const primaryPhone = phones[0]
|
const primaryPhone = phones[0]
|
||||||
return (
|
return (
|
||||||
<CellGroup key={c.id || idx} className="mb-3">
|
<CellGroup key={c.id || idx} className="mb-3">
|
||||||
<Cell>
|
<Cell
|
||||||
|
onClick={() => {
|
||||||
|
if (selectMode) return
|
||||||
|
if (!c?.id) return
|
||||||
|
Taro.navigateTo({ url: `/credit/company/edit?id=${c.id}` })
|
||||||
|
}}
|
||||||
|
>
|
||||||
<View className="flex gap-3 items-start w-full">
|
<View className="flex gap-3 items-start w-full">
|
||||||
{selectMode && (
|
{selectMode && (
|
||||||
<View className="pt-1">
|
<View
|
||||||
|
className="pt-1"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selected}
|
checked={selected}
|
||||||
onChange={(checked) => toggleSelectId(id, checked)}
|
onChange={(checked) => toggleSelectId(id, checked)}
|
||||||
@@ -641,11 +563,11 @@ export default function CreditCompanyPage() {
|
|||||||
|
|
||||||
<View className="h-20 w-full" />
|
<View className="h-20 w-full" />
|
||||||
<View className="fixed z-50 bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-3 py-3 safe-area-bottom">
|
<View className="fixed z-50 bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-3 py-3 safe-area-bottom">
|
||||||
<View className="flex justify-between items-center gap-3">
|
<View className="flex items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ background: '#ef4444' }}
|
style={{ background: '#ef4444' }}
|
||||||
block
|
className="flex-1"
|
||||||
onClick={openAddCustomer}
|
onClick={openAddCustomer}
|
||||||
>
|
>
|
||||||
添加客户
|
添加客户
|
||||||
@@ -654,7 +576,7 @@ export default function CreditCompanyPage() {
|
|||||||
type="primary"
|
type="primary"
|
||||||
style={{ background: canAssign ? '#3b82f6' : '#94a3b8' }}
|
style={{ background: canAssign ? '#3b82f6' : '#94a3b8' }}
|
||||||
disabled={!canAssign || assigning}
|
disabled={!canAssign || assigning}
|
||||||
block
|
className="flex-1"
|
||||||
onClick={openAssign}
|
onClick={openAssign}
|
||||||
>
|
>
|
||||||
{selectMode ? '分配所选' : '分配客户'}
|
{selectMode ? '分配所选' : '分配客户'}
|
||||||
@@ -812,67 +734,6 @@ export default function CreditCompanyPage() {
|
|||||||
</View>
|
</View>
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
<Dialog
|
|
||||||
title={addStep === 'search' ? '添加客户(查重)' : '添加客户'}
|
|
||||||
visible={addDialogVisible}
|
|
||||||
confirmText={
|
|
||||||
addStep === 'search'
|
|
||||||
? (addChecking ? '查询中...' : '查询')
|
|
||||||
: (addSubmitting ? '提交中...' : '提交')
|
|
||||||
}
|
|
||||||
cancelText="取消"
|
|
||||||
onCancel={() => {
|
|
||||||
if (addChecking || addSubmitting) return
|
|
||||||
setAddDialogVisible(false)
|
|
||||||
}}
|
|
||||||
onConfirm={() => {
|
|
||||||
if (addStep === 'search') return checkCompanyExists()
|
|
||||||
return submitAddCustomer()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View className="text-sm text-gray-700">
|
|
||||||
<View className="text-xs text-gray-500 mb-2">
|
|
||||||
先查询系统是否存在公司;不存在则可直接添加企业信息。
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<CellGroup>
|
|
||||||
<Cell title="公司名称">
|
|
||||||
<Input value={addName} onChange={setAddName} placeholder="请输入公司名称" />
|
|
||||||
</Cell>
|
|
||||||
<Cell title="统一代码">
|
|
||||||
<Input value={addCode} onChange={setAddCode} placeholder="请输入统一代码(可选)" />
|
|
||||||
</Cell>
|
|
||||||
|
|
||||||
{addStep === 'form' && (
|
|
||||||
<>
|
|
||||||
<Cell title="电话">
|
|
||||||
<Input value={addPhone} onChange={setAddPhone} placeholder="请输入联系电话(可选)" />
|
|
||||||
</Cell>
|
|
||||||
<Cell
|
|
||||||
title="地区"
|
|
||||||
description={addCityText || '选择到城市'}
|
|
||||||
onClick={() => setAddCityVisible(true)}
|
|
||||||
/>
|
|
||||||
<Cell title="行业">
|
|
||||||
<Input value={addIndustry} onChange={setAddIndustry} placeholder="请输入行业(可选)" />
|
|
||||||
</Cell>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</CellGroup>
|
|
||||||
|
|
||||||
<Address
|
|
||||||
visible={addCityVisible}
|
|
||||||
options={cityOptions as any}
|
|
||||||
title="选择地区(到城市)"
|
|
||||||
onChange={(value: any[]) => {
|
|
||||||
const txt = value.filter(Boolean).slice(0, 2).join(' ')
|
|
||||||
setAddCityText(txt)
|
|
||||||
setAddCityVisible(false)
|
|
||||||
}}
|
|
||||||
onClose={() => setAddCityVisible(false)}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</Dialog>
|
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '需求列表',
|
navigationBarTitleText: '订单管理',
|
||||||
navigationBarTextStyle: 'black',
|
navigationBarTextStyle: 'black',
|
||||||
navigationBarBackgroundColor: '#ffffff'
|
navigationBarBackgroundColor: '#ffffff',
|
||||||
|
navigationStyle: 'custom'
|
||||||
})
|
})
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -56,7 +56,6 @@ const Find = () => {
|
|||||||
const [storeList, setStoreList] = useState<ShopStoreView[]>([])
|
const [storeList, setStoreList] = useState<ShopStoreView[]>([])
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [hasMore, setHasMore] = useState(true)
|
const [hasMore, setHasMore] = useState(true)
|
||||||
const [total, setTotal] = useState(0)
|
|
||||||
const [userLngLat, setUserLngLat] = useState<LngLat | null>(null)
|
const [userLngLat, setUserLngLat] = useState<LngLat | null>(null)
|
||||||
|
|
||||||
const pageRef = useRef(1)
|
const pageRef = useRef(1)
|
||||||
@@ -86,7 +85,6 @@ const Find = () => {
|
|||||||
latestListRef.current = []
|
latestListRef.current = []
|
||||||
setStoreList([])
|
setStoreList([])
|
||||||
setHasMore(true)
|
setHasMore(true)
|
||||||
setTotal(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -112,7 +110,6 @@ const Find = () => {
|
|||||||
setStoreList(nextList)
|
setStoreList(nextList)
|
||||||
|
|
||||||
const count = typeof res?.count === 'number' ? res.count : nextList.length
|
const count = typeof res?.count === 'number' ? res.count : nextList.length
|
||||||
setTotal(count)
|
|
||||||
setHasMore(nextList.length < count)
|
setHasMore(nextList.length < count)
|
||||||
|
|
||||||
if (resList.length > 0) {
|
if (resList.length > 0) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const MyPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bannerHeight = toCssSize(ad?.height)
|
const bannerHeight = toCssSize(ad?.height)
|
||||||
|
console.log(bannerHeight)
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
const flash = await getCmsAdByCode('flash')
|
const flash = await getCmsAdByCode('flash')
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function User() {
|
|||||||
}
|
}
|
||||||
// TabBar 页在小程序里通常不会销毁;从“注册/申请”页返回时需要触发子组件重新初始化/拉取最新状态。
|
// TabBar 页在小程序里通常不会销毁;从“注册/申请”页返回时需要触发子组件重新初始化/拉取最新状态。
|
||||||
const [dealerViewKey, setDealerViewKey] = useState(0)
|
const [dealerViewKey, setDealerViewKey] = useState(0)
|
||||||
|
console.log(dealerViewKey)
|
||||||
// 下拉刷新处理
|
// 下拉刷新处理
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
if (userCardRef.current?.handleRefresh) {
|
if (userCardRef.current?.handleRefresh) {
|
||||||
|
|||||||
Reference in New Issue
Block a user