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'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,636 +1,408 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import Taro, { useDidShow } from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import { View, Text } from '@tarojs/components'
|
import { View, Text } from '@tarojs/components'
|
||||||
import {
|
import { Button, ConfigProvider, DatePicker, Empty, Input, Popup } from '@nutui/nutui-react-taro'
|
||||||
Tabs,
|
import { Search, Setting } from '@nutui/icons-react-taro'
|
||||||
TabPane,
|
|
||||||
Cell,
|
|
||||||
Space,
|
|
||||||
Button,
|
|
||||||
Dialog,
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
Image,
|
|
||||||
Empty,
|
|
||||||
InfiniteLoading,
|
|
||||||
PullToRefresh,
|
|
||||||
Loading
|
|
||||||
} from '@nutui/nutui-react-taro'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import { pageGltTicketOrder, updateGltTicketOrder } from '@/api/glt/gltTicketOrder'
|
type PayStatus = '全部回款' | '部分回款' | '未回款'
|
||||||
import type { GltTicketOrder, GltTicketOrderParam } from '@/api/glt/gltTicketOrder/model'
|
type AmountSort = '默认' | '从高到低' | '从低到高'
|
||||||
import { uploadFile } from '@/api/system/file'
|
|
||||||
import { listShopStoreRider, updateShopStoreRider } from '@/api/shop/shopStoreRider'
|
|
||||||
import { getCurrentLngLat } from '@/utils/location'
|
|
||||||
|
|
||||||
const PAGE_SIZE = 10
|
type OrderItem = {
|
||||||
|
id: string
|
||||||
|
orderNo: string
|
||||||
|
companyName: string
|
||||||
|
unifiedCode: string
|
||||||
|
projectName: string
|
||||||
|
follower: string
|
||||||
|
payStatus: PayStatus
|
||||||
|
date: string // YYYY-MM-DD
|
||||||
|
principal: number
|
||||||
|
interest: number
|
||||||
|
}
|
||||||
|
|
||||||
type DeliverConfirmMode = 'photoComplete' | 'waitCustomerConfirm'
|
const formatYmd = (d?: Date | null) => {
|
||||||
|
if (!d) return ''
|
||||||
|
return dayjs(d).format('YYYY-MM-DD')
|
||||||
|
}
|
||||||
|
|
||||||
export default function TicketOrdersPage() {
|
const getStatusStyle = (s: PayStatus) => {
|
||||||
const riderId = useMemo(() => {
|
if (s === '全部回款') return 'bg-green-500'
|
||||||
const raw = Taro.getStorageSync('UserId')
|
if (s === '部分回款') return 'bg-orange-500'
|
||||||
const id = Number(raw)
|
return 'bg-red-500'
|
||||||
return Number.isFinite(id) && id > 0 ? id : undefined
|
}
|
||||||
|
|
||||||
|
const makeMockOrders = (page: number): OrderItem[] => {
|
||||||
|
const companies = [
|
||||||
|
{ name: '广西万宇工程建设有限公司', code: '91450100MA00000001' },
|
||||||
|
{ name: '广西远恒建筑有限公司', code: '91450100MA00000002' },
|
||||||
|
{ name: '南宁宏达工程有限公司', code: '91450100MA00000003' },
|
||||||
|
{ name: '桂林鑫盛建设有限公司', code: '91450100MA00000004' }
|
||||||
|
]
|
||||||
|
const followers = ['罗天东', '张三', '李四', '王五']
|
||||||
|
const suffix = ['一期', '二期', '改扩建', '配套', '市政', '装饰', '机电']
|
||||||
|
|
||||||
|
// page=1:给出 16 条示例,统计更直观(项目=订单维度,而非公司维度)
|
||||||
|
const size = page === 1 ? 16 : 6
|
||||||
|
const baseNo = 9099009999 + (page - 1) * 100
|
||||||
|
|
||||||
|
const list: OrderItem[] = []
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
const c = companies[(page + i) % companies.length]
|
||||||
|
const follower = followers[(i + page) % followers.length]
|
||||||
|
const payStatus: PayStatus =
|
||||||
|
page === 1 ? '全部回款' : (['全部回款', '部分回款', '未回款'][(i + page) % 3] as PayStatus)
|
||||||
|
|
||||||
|
const orderNo = String(baseNo + i)
|
||||||
|
const date = dayjs('2025-10-10').subtract((page - 1) * 7 + (i % 6), 'day').format('YYYY-MM-DD')
|
||||||
|
|
||||||
|
// page=1:让本金/利息合计更规整(本金 2000,利息 200)
|
||||||
|
const principal = page === 1 ? (i < 8 ? 100 : 150) : 200 + ((i + page) % 5) * 50
|
||||||
|
const interest = page === 1 ? (i < 8 ? 10 : 15) : 20 + ((i + page) % 4) * 5
|
||||||
|
|
||||||
|
const projectName = `${c.name}${suffix[(i + page) % suffix.length]}项目`
|
||||||
|
|
||||||
|
list.push({
|
||||||
|
id: `${page}-${i}-${orderNo}`,
|
||||||
|
orderNo,
|
||||||
|
companyName: c.name,
|
||||||
|
unifiedCode: c.code,
|
||||||
|
projectName,
|
||||||
|
follower,
|
||||||
|
payStatus,
|
||||||
|
date,
|
||||||
|
principal,
|
||||||
|
interest
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CreditOrderPage() {
|
||||||
|
const statusBarHeight = useMemo(() => {
|
||||||
|
try {
|
||||||
|
const info = Taro.getSystemInfoSync()
|
||||||
|
return Number(info?.statusBarHeight || 0)
|
||||||
|
} catch (_e) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const pageRef = useRef(1)
|
const [rawList, setRawList] = useState<OrderItem[]>(() => makeMockOrders(1))
|
||||||
const listRef = useRef<GltTicketOrder[]>([])
|
const [mockPage, setMockPage] = useState(1)
|
||||||
|
|
||||||
const [tabIndex, setTabIndex] = useState(0)
|
const [searchValue, setSearchValue] = useState('')
|
||||||
const [list, setList] = useState<GltTicketOrder[]>([])
|
const [amountSort, setAmountSort] = useState<AmountSort>('默认')
|
||||||
const [hasMore, setHasMore] = useState(true)
|
const [payFilter, setPayFilter] = useState<PayStatus>('全部回款')
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const [error, setError] = useState<string | null>(null)
|
|
||||||
|
|
||||||
const [deliverDialogVisible, setDeliverDialogVisible] = useState(false)
|
const [datePopupVisible, setDatePopupVisible] = useState(false)
|
||||||
const [deliverSubmitting, setDeliverSubmitting] = useState(false)
|
const [datePickerVisible, setDatePickerVisible] = useState(false)
|
||||||
const [deliverOrder, setDeliverOrder] = useState<GltTicketOrder | null>(null)
|
const [picking, setPicking] = useState<'start' | 'end'>('start')
|
||||||
const [deliverImg, setDeliverImg] = useState<string | undefined>(undefined)
|
const [startDate, setStartDate] = useState<Date | null>(null)
|
||||||
const [deliverConfirmMode, setDeliverConfirmMode] = useState<DeliverConfirmMode>('photoComplete')
|
const [endDate, setEndDate] = useState<Date | null>(null)
|
||||||
|
|
||||||
const riderTabs = useMemo(
|
const filteredList = useMemo(() => {
|
||||||
() => [
|
let list = rawList.slice()
|
||||||
{ index: 0, title: '全部' },
|
|
||||||
{ index: 1, title: '待配送', deliveryStatus: 10 },
|
const q = searchValue.trim()
|
||||||
{ index: 2, title: '配送中', deliveryStatus: 20 },
|
if (q) {
|
||||||
{ index: 3, title: '待确认', deliveryStatus: 30 },
|
const qq = q.toLowerCase()
|
||||||
{ index: 4, title: '已完成', deliveryStatus: 40 }
|
list = list.filter(o => {
|
||||||
],
|
return (
|
||||||
[]
|
String(o.companyName || '').toLowerCase().includes(qq) ||
|
||||||
|
String(o.unifiedCode || '').toLowerCase().includes(qq) ||
|
||||||
|
String(o.orderNo || '').toLowerCase().includes(qq)
|
||||||
)
|
)
|
||||||
|
})
|
||||||
const getOrderStatusText = (order: GltTicketOrder) => {
|
|
||||||
if (order.status === 1) return '已冻结'
|
|
||||||
|
|
||||||
const deliveryStatus = order.deliveryStatus
|
|
||||||
if (deliveryStatus === 40) return '已完成'
|
|
||||||
if (deliveryStatus === 30) return '待客户确认'
|
|
||||||
if (deliveryStatus === 20) return '配送中'
|
|
||||||
if (deliveryStatus === 10) return '待配送'
|
|
||||||
|
|
||||||
// 兼容:如果后端暂未下发 deliveryStatus,就用时间字段推断
|
|
||||||
if (order.receiveConfirmTime) return '已完成'
|
|
||||||
if (order.sendEndTime) return '待客户确认'
|
|
||||||
if (order.sendStartTime) return '配送中'
|
|
||||||
if (order.riderId) return '待配送'
|
|
||||||
return '待派单'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOrderStatusColor = (order: GltTicketOrder) => {
|
if (payFilter) {
|
||||||
const text = getOrderStatusText(order)
|
list = list.filter(o => o.payStatus === payFilter)
|
||||||
if (text === '已完成') return 'text-green-600'
|
|
||||||
if (text === '待客户确认') return 'text-purple-600'
|
|
||||||
if (text === '配送中') return 'text-blue-600'
|
|
||||||
if (text === '待配送') return 'text-amber-600'
|
|
||||||
if (text === '已冻结') return 'text-orange-600'
|
|
||||||
return 'text-gray-500'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const canStartDeliver = (order: GltTicketOrder) => {
|
if (startDate || endDate) {
|
||||||
if (!order.id) return false
|
const start = startDate ? dayjs(startDate).startOf('day') : null
|
||||||
if (order.status === 1) return false
|
const end = endDate ? dayjs(endDate).endOf('day') : null
|
||||||
if (!riderId || order.riderId !== riderId) return false
|
list = list.filter(o => {
|
||||||
if (order.deliveryStatus && order.deliveryStatus !== 10) return false
|
const t = dayjs(o.date, 'YYYY-MM-DD')
|
||||||
return !order.sendStartTime && !order.sendEndTime
|
if (start && t.isBefore(start)) return false
|
||||||
}
|
if (end && t.isAfter(end)) return false
|
||||||
|
|
||||||
const canConfirmDelivered = (order: GltTicketOrder) => {
|
|
||||||
if (!order.id) return false
|
|
||||||
if (order.status === 1) return false
|
|
||||||
if (!riderId || order.riderId !== riderId) return false
|
|
||||||
if (order.receiveConfirmTime) return false
|
|
||||||
if (order.deliveryStatus === 40) return false
|
|
||||||
if (order.sendEndTime) return false
|
|
||||||
|
|
||||||
// 只允许在“配送中”阶段确认送达
|
|
||||||
if (typeof order.deliveryStatus === 'number') return order.deliveryStatus === 20
|
|
||||||
return !!order.sendStartTime
|
|
||||||
}
|
|
||||||
|
|
||||||
const canCompleteByPhoto = (order: GltTicketOrder) => {
|
|
||||||
if (!order.id) return false
|
|
||||||
if (order.status === 1) return false
|
|
||||||
if (!riderId || order.riderId !== riderId) return false
|
|
||||||
if (order.receiveConfirmTime) return false
|
|
||||||
if (order.deliveryStatus === 40) return false
|
|
||||||
// 已送达但未完成:允许补传照片并直接完成
|
|
||||||
return !!order.sendEndTime
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterByTab = useCallback(
|
|
||||||
(orders: GltTicketOrder[]) => {
|
|
||||||
if (tabIndex === 0) return orders
|
|
||||||
|
|
||||||
const current = riderTabs.find(t => t.index === tabIndex)
|
|
||||||
const status = current?.deliveryStatus
|
|
||||||
if (!status) return orders
|
|
||||||
|
|
||||||
// 如果后端已实现 deliveryStatus 筛选,这里基本不会再过滤;否则用兼容逻辑兜底。
|
|
||||||
return orders.filter(o => {
|
|
||||||
const ds = o.deliveryStatus
|
|
||||||
if (typeof ds === 'number') return ds === status
|
|
||||||
if (status === 10) return !!o.riderId && !o.sendStartTime && !o.sendEndTime
|
|
||||||
if (status === 20) return !!o.sendStartTime && !o.sendEndTime
|
|
||||||
if (status === 30) return !!o.sendEndTime && !o.receiveConfirmTime
|
|
||||||
if (status === 40) return !!o.receiveConfirmTime
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
},
|
|
||||||
[riderTabs, tabIndex]
|
|
||||||
)
|
|
||||||
|
|
||||||
const reload = useCallback(
|
|
||||||
async (resetPage = false) => {
|
|
||||||
if (!riderId) return
|
|
||||||
if (loading) return
|
|
||||||
setLoading(true)
|
|
||||||
setError(null)
|
|
||||||
|
|
||||||
const currentPage = resetPage ? 1 : pageRef.current
|
|
||||||
const currentTab = riderTabs.find(t => t.index === tabIndex)
|
|
||||||
|
|
||||||
const params: GltTicketOrderParam = {
|
|
||||||
page: currentPage,
|
|
||||||
limit: PAGE_SIZE,
|
|
||||||
riderId,
|
|
||||||
deliveryStatus: currentTab?.deliveryStatus
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (amountSort !== '默认') {
|
||||||
const res = await pageGltTicketOrder(params as any)
|
list.sort((a, b) => {
|
||||||
const incomingAll = (res?.list || []) as GltTicketOrder[]
|
const av = Number(a.principal || 0)
|
||||||
// 兼容:后端若暂未实现 riderId 过滤,前端兜底过滤掉非本人的订单
|
const bv = Number(b.principal || 0)
|
||||||
const incoming = incomingAll.filter(o => o?.deleted !== 1 && o?.riderId === riderId)
|
return amountSort === '从高到低' ? bv - av : av - bv
|
||||||
|
|
||||||
const prev = resetPage ? [] : listRef.current
|
|
||||||
const next = resetPage ? incoming : prev.concat(incoming)
|
|
||||||
listRef.current = next
|
|
||||||
setList(next)
|
|
||||||
|
|
||||||
const total = typeof res?.count === 'number' ? res.count : undefined
|
|
||||||
const filteredOut = incomingAll.length - incoming.length
|
|
||||||
if (typeof total === 'number' && filteredOut === 0) {
|
|
||||||
setHasMore(next.length < total)
|
|
||||||
} else {
|
|
||||||
setHasMore(incomingAll.length >= PAGE_SIZE)
|
|
||||||
}
|
|
||||||
|
|
||||||
pageRef.current = currentPage + 1
|
|
||||||
} catch (e) {
|
|
||||||
console.error('加载配送订单失败:', e)
|
|
||||||
setError('加载失败,请重试')
|
|
||||||
setHasMore(false)
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[loading, riderId, riderTabs, tabIndex]
|
|
||||||
)
|
|
||||||
|
|
||||||
const reloadMore = useCallback(async () => {
|
|
||||||
if (loading || !hasMore) return
|
|
||||||
await reload(false)
|
|
||||||
}, [hasMore, loading, reload])
|
|
||||||
|
|
||||||
const openDeliverDialog = (order: GltTicketOrder, opts?: { mode?: DeliverConfirmMode }) => {
|
|
||||||
setDeliverOrder(order)
|
|
||||||
setDeliverImg(order.sendEndImg)
|
|
||||||
setDeliverConfirmMode(opts?.mode || (order.sendEndImg ? 'photoComplete' : 'waitCustomerConfirm'))
|
|
||||||
setDeliverDialogVisible(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChooseDeliverImg = async () => {
|
|
||||||
try {
|
|
||||||
const file = await uploadFile()
|
|
||||||
setDeliverImg(file?.url)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('上传送达照片失败:', e)
|
|
||||||
Taro.showToast({ title: '上传失败,请重试', icon: 'none' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleStartDeliver = async (order: GltTicketOrder) => {
|
|
||||||
if (!order?.id) return
|
|
||||||
if (!canStartDeliver(order)) return
|
|
||||||
try {
|
|
||||||
await updateGltTicketOrder({
|
|
||||||
id: order.id,
|
|
||||||
deliveryStatus: 20,
|
|
||||||
sendStartTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
})
|
})
|
||||||
Taro.showToast({ title: '已开始配送', icon: 'success' })
|
|
||||||
pageRef.current = 1
|
|
||||||
await reload(true)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('开始配送失败:', e)
|
|
||||||
Taro.showToast({ title: '开始配送失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleConfirmDelivered = async () => {
|
return list
|
||||||
if (!deliverOrder?.id) return
|
}, [amountSort, endDate, payFilter, rawList, searchValue, startDate])
|
||||||
if (deliverSubmitting) return
|
|
||||||
if (deliverConfirmMode === 'photoComplete' && !deliverImg) {
|
const stats = useMemo(() => {
|
||||||
Taro.showToast({ title: '请先拍照/上传送达照片', icon: 'none' })
|
// 业务说明:一个公司可能对应多个项目(多条订单),因此“订单数量”=项目/订单条数,而不是公司数。
|
||||||
return
|
const total = filteredList.length
|
||||||
}
|
const principal = filteredList.reduce((sum, o) => sum + Number(o.principal || 0), 0)
|
||||||
setDeliverSubmitting(true)
|
const interest = filteredList.reduce((sum, o) => sum + Number(o.interest || 0), 0)
|
||||||
|
return { total, principal, interest }
|
||||||
|
}, [filteredList])
|
||||||
|
|
||||||
|
const timeText = useMemo(() => {
|
||||||
|
const s = formatYmd(startDate)
|
||||||
|
const e = formatYmd(endDate)
|
||||||
|
if (!s && !e) return '时间筛选'
|
||||||
|
if (s && e) return `${s}~${e}`
|
||||||
|
return s ? `${s}~` : `~${e}`
|
||||||
|
}, [endDate, startDate])
|
||||||
|
|
||||||
|
const pickAmountSort = async () => {
|
||||||
try {
|
try {
|
||||||
// 送达时同步记录配送员当前位置(用于门店/后台跟踪骑手位置)
|
const options: AmountSort[] = ['默认', '从高到低', '从低到高']
|
||||||
const loc = await getCurrentLngLat('确认送达需要记录您的当前位置,请在设置中开启定位权限后重试。')
|
const res = await Taro.showActionSheet({ itemList: options })
|
||||||
if (!loc) return
|
const next = options[res.tapIndex]
|
||||||
|
if (next) setAmountSort(next)
|
||||||
|
} catch (e) {
|
||||||
|
const msg = String((e as any)?.errMsg || (e as any)?.message || e || '')
|
||||||
|
if (msg.includes('cancel')) return
|
||||||
|
console.error('选择金额排序失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickPayFilter = async () => {
|
||||||
try {
|
try {
|
||||||
// 优先按 userId 精确查找;后端若未支持该字段,会自动忽略,我们再做兜底。
|
const options: PayStatus[] = ['全部回款', '部分回款', '未回款']
|
||||||
let riderRow =
|
const res = await Taro.showActionSheet({ itemList: options })
|
||||||
(await listShopStoreRider({ userId: riderId, storeId: deliverOrder.storeId, status: 1 } as any))
|
const next = options[res.tapIndex]
|
||||||
?.find(r => String(r?.userId || '') === String(riderId || '')) ||
|
if (next) setPayFilter(next)
|
||||||
null
|
|
||||||
|
|
||||||
// 兜底:按门店筛选后再匹配 userId
|
|
||||||
if (!riderRow && deliverOrder.storeId) {
|
|
||||||
const list = await listShopStoreRider({ storeId: deliverOrder.storeId, status: 1 } as any)
|
|
||||||
riderRow = list?.find(r => String(r?.userId || '') === String(riderId || '')) || null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (riderRow?.id) {
|
|
||||||
await updateShopStoreRider({
|
|
||||||
id: riderRow.id,
|
|
||||||
longitude: loc.lng,
|
|
||||||
latitude: loc.lat
|
|
||||||
} as any)
|
|
||||||
} else {
|
|
||||||
console.warn('未找到 ShopStoreRider 记录,无法更新骑手经纬度:', { riderId, storeId: deliverOrder.storeId })
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 不阻塞送达流程,但记录日志便于排查。
|
const msg = String((e as any)?.errMsg || (e as any)?.message || e || '')
|
||||||
console.warn('更新 ShopStoreRider 经纬度失败:', e)
|
if (msg.includes('cancel')) return
|
||||||
}
|
console.error('选择回款筛选失败:', e)
|
||||||
|
|
||||||
const now = dayjs().format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
// 送达时间:首次“确认送达”写入;补传照片时不要覆盖原送达时间
|
|
||||||
const deliveredAt = deliverOrder.sendEndTime || now
|
|
||||||
// 说明:
|
|
||||||
// - waitCustomerConfirm:只标记“已送达”,进入待客户确认(客户点击确认收货后完成)
|
|
||||||
// - photoComplete:拍照留档后可直接完成(由后端策略决定是否允许)
|
|
||||||
const payload: GltTicketOrder =
|
|
||||||
deliverConfirmMode === 'photoComplete'
|
|
||||||
? {
|
|
||||||
id: deliverOrder.id,
|
|
||||||
deliveryStatus: 40,
|
|
||||||
sendEndTime: deliveredAt,
|
|
||||||
sendEndImg: deliverImg,
|
|
||||||
receiveConfirmTime: now,
|
|
||||||
receiveConfirmType: 20
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
id: deliverOrder.id,
|
|
||||||
deliveryStatus: 30,
|
|
||||||
sendEndTime: deliveredAt,
|
|
||||||
sendEndImg: deliverImg
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateGltTicketOrder(payload)
|
|
||||||
Taro.showToast({ title: '已确认送达', icon: 'success' })
|
|
||||||
setDeliverDialogVisible(false)
|
|
||||||
setDeliverOrder(null)
|
|
||||||
setDeliverImg(undefined)
|
|
||||||
setDeliverConfirmMode('photoComplete')
|
|
||||||
pageRef.current = 1
|
|
||||||
await reload(true)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('确认送达失败:', e)
|
|
||||||
Taro.showToast({ title: '确认送达失败', icon: 'none' })
|
|
||||||
} finally {
|
|
||||||
setDeliverSubmitting(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
const loadMore = () => {
|
||||||
listRef.current = list
|
const nextPage = mockPage + 1
|
||||||
}, [list])
|
const incoming = makeMockOrders(nextPage)
|
||||||
|
setMockPage(nextPage)
|
||||||
|
setRawList(prev => prev.concat(incoming))
|
||||||
|
console.log('加载更多:', { nextPage, appended: incoming.length })
|
||||||
|
Taro.showToast({ title: '已加载更多', icon: 'none' })
|
||||||
|
}
|
||||||
|
|
||||||
useDidShow(() => {
|
const headerOffset = statusBarHeight + 80
|
||||||
pageRef.current = 1
|
|
||||||
listRef.current = []
|
|
||||||
setList([])
|
|
||||||
setHasMore(true)
|
|
||||||
void reload(true)
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
pageRef.current = 1
|
|
||||||
listRef.current = []
|
|
||||||
setList([])
|
|
||||||
setHasMore(true)
|
|
||||||
void reload(true)
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [tabIndex, riderId])
|
|
||||||
|
|
||||||
if (!riderId) {
|
|
||||||
return (
|
return (
|
||||||
<View className="bg-gray-50 min-h-screen p-4">
|
<View className="bg-pink-50 min-h-screen">
|
||||||
<Text>请先登录</Text>
|
<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>
|
||||||
|
|
||||||
|
<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"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const res = await Taro.showActionSheet({ itemList: ['新建订单', '刷新'] })
|
||||||
|
if (res.tapIndex === 0) Taro.navigateTo({ url: '/credit/order/add' })
|
||||||
|
if (res.tapIndex === 1) {
|
||||||
|
setSearchValue('')
|
||||||
|
setAmountSort('默认')
|
||||||
|
setPayFilter('全部回款')
|
||||||
|
setStartDate(null)
|
||||||
|
setEndDate(null)
|
||||||
|
setMockPage(1)
|
||||||
|
setRawList(makeMockOrders(1))
|
||||||
|
Taro.showToast({ title: '已刷新', icon: 'none' })
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const msg = String((e as any)?.errMsg || (e as any)?.message || e || '')
|
||||||
|
if (msg.includes('cancel')) return
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayList = filterByTab(list)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View className="bg-gray-50 min-h-screen">
|
|
||||||
<Tabs value={tabIndex} onChange={paneKey => setTabIndex(Number(paneKey))} align="left">
|
|
||||||
{riderTabs.map(t => (
|
|
||||||
<TabPane key={t.index} title={loading && tabIndex === t.index ? `${t.title}...` : t.title} />
|
|
||||||
))}
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<View className="px-3 pb-4">
|
|
||||||
<PullToRefresh
|
|
||||||
onRefresh={async () => {
|
|
||||||
pageRef.current = 1
|
|
||||||
listRef.current = []
|
|
||||||
setList([])
|
|
||||||
setHasMore(true)
|
|
||||||
await reload(true)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{error ? (
|
<Text>...</Text>
|
||||||
<View className="bg-white rounded-lg p-6">
|
|
||||||
<View className="flex flex-col items-center justify-center">
|
|
||||||
<Text className="text-gray-500 mb-3">{error}</Text>
|
|
||||||
<Button size="small" type="primary" onClick={() => reload(true)}>
|
|
||||||
重新加载
|
|
||||||
</Button>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
<View
|
||||||
) : (
|
className="w-7 h-7 rounded-full border border-gray-300 flex items-center justify-center text-gray-700"
|
||||||
<InfiniteLoading
|
onClick={() => Taro.showToast({ title: '设置(示意)', icon: 'none' })}
|
||||||
hasMore={hasMore}
|
|
||||||
onLoadMore={reloadMore}
|
|
||||||
loadingText={
|
|
||||||
<View className="flex justify-center items-center py-4">
|
|
||||||
<Loading />
|
|
||||||
<View className="ml-2">加载中...</View>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
loadMoreText={
|
|
||||||
displayList.length === 0 ? (
|
|
||||||
<View className="bg-white rounded-lg p-6">
|
|
||||||
<Empty description="暂无配送订单" />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View className="text-center py-4 text-gray-500">没有更多了</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{displayList.map(o => {
|
<Setting size={14} />
|
||||||
const qty = Number(o.totalNum || 0)
|
</View>
|
||||||
const timeText = o.createTime ? dayjs(o.createTime).format('YYYY-MM-DD HH:mm') : '-'
|
</View>
|
||||||
const addr = o.address || (o.addressId ? `地址ID:${o.addressId}` : '-')
|
</View>
|
||||||
const remark = o.buyerRemarks || o.comments || ''
|
|
||||||
const ticketNo = o.userTicketId || '-'
|
|
||||||
|
|
||||||
const flow1Done = !!o.riderId
|
|
||||||
const flow2Done =
|
|
||||||
!!o.sendStartTime || (typeof o.deliveryStatus === 'number' && o.deliveryStatus >= 20)
|
|
||||||
const flow3Done =
|
|
||||||
!!o.sendEndTime || (typeof o.deliveryStatus === 'number' && o.deliveryStatus >= 30)
|
|
||||||
const flow4Done = !!o.receiveConfirmTime || o.deliveryStatus === 40
|
|
||||||
|
|
||||||
const phoneToCall = o.phone
|
|
||||||
const storePhone = o.storePhone
|
|
||||||
const pickupName = o.warehouseName || o.storeName
|
|
||||||
const pickupAddr = o.warehouseAddress || o.storeAddress
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Cell key={String(o.id)} style={{ padding: '16px' }}>
|
|
||||||
<View className="w-full">
|
|
||||||
<View className="flex justify-between items-center">
|
|
||||||
<Text className="text-gray-800 font-bold text-sm">{`订单#${o.id}`}</Text>
|
|
||||||
<Text className={`${getOrderStatusColor(o)} text-sm font-medium`}>{getOrderStatusText(o)}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className="text-gray-400 text-xs mt-1">下单时间:{timeText}</View>
|
<View style={{ paddingTop: `${headerOffset}px` }} className="max-w-md mx-auto">
|
||||||
<View className="text-gray-400 text-xs mt-1">票号:{ticketNo}</View>
|
<View className="px-4 pt-2">
|
||||||
|
<View className="bg-white rounded-full border border-pink-100 px-3 py-2 flex items-center gap-2">
|
||||||
<View className="mt-3 bg-white rounded-lg">
|
<Search size={16} className="text-gray-400" />
|
||||||
<View className="text-sm text-gray-700">
|
<View className="flex-1">
|
||||||
<Text className="text-gray-500">收货地址:</Text>
|
<Input
|
||||||
<Text>{addr}</Text>
|
value={searchValue}
|
||||||
|
onChange={setSearchValue}
|
||||||
|
placeholder="请输入公司名称、统一代码查询、订单号查询"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
</View>
|
||||||
<Text className="text-gray-500">客户:</Text>
|
|
||||||
|
<View className="mt-3 grid grid-cols-3 gap-2">
|
||||||
|
<View
|
||||||
|
className="bg-white rounded-xl border border-pink-100 px-2 py-2 text-xs text-gray-700 flex items-center justify-center"
|
||||||
|
onClick={pickAmountSort}
|
||||||
|
>
|
||||||
|
<Text className="mr-1">金额排序</Text>
|
||||||
|
<Text className="text-gray-400">{amountSort === '从高到低' ? '↓' : amountSort === '从低到高' ? '↑' : '↕'}</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
className="bg-white rounded-xl border border-pink-100 px-2 py-2 text-xs text-gray-700 flex items-center justify-center"
|
||||||
|
onClick={pickPayFilter}
|
||||||
|
>
|
||||||
|
<Text>{payFilter}</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
className="bg-white rounded-xl border border-pink-100 px-2 py-2 text-xs text-gray-700 flex items-center justify-center"
|
||||||
|
onClick={() => setDatePopupVisible(true)}
|
||||||
|
>
|
||||||
|
<Text className="truncate">{timeText}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="mt-2 text-xs text-gray-400 flex items-center justify-between">
|
||||||
|
<Text>总订单量:{stats.total}个</Text>
|
||||||
|
<Text>订单本金:{stats.principal}元</Text>
|
||||||
|
<Text>利息金额:{stats.interest}元</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="px-4 mt-3 pb-6">
|
||||||
|
{!filteredList.length ? (
|
||||||
|
<View className="bg-white rounded-xl border border-pink-100 py-10">
|
||||||
|
<Empty description="暂无订单" />
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
filteredList.map(o => (
|
||||||
|
<View key={o.id} className="bg-white rounded-xl border border-pink-100 p-3 mb-3">
|
||||||
|
<View className="flex items-center justify-between">
|
||||||
|
<Text className="text-xs text-gray-500">订单号:{o.orderNo}</Text>
|
||||||
|
<View className={`px-2 py-1 rounded-full ${getStatusStyle(o.payStatus)}`}>
|
||||||
|
<Text className="text-xs text-white">{o.payStatus}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 项目名称是核心分组/统计维度(一个公司可有多个项目=多条订单),因此需要突出显示 */}
|
||||||
|
<View className="mt-2">
|
||||||
|
<Text className="text-sm font-semibold text-rose-600">{o.projectName}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-xs text-gray-600">
|
||||||
|
<Text>公司:{o.companyName}</Text>
|
||||||
|
<Text>跟进人:{o.follower}</Text>
|
||||||
|
<Text>日期:{o.date}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="mt-2 flex items-center gap-6 text-xs text-gray-700">
|
||||||
<Text>
|
<Text>
|
||||||
{o.nickname || '-'} {o.phone ? `(${o.phone})` : ''}
|
<Text className="text-gray-400">本金:</Text>
|
||||||
|
{o.principal}元
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
<Text className="text-gray-400">利息:</Text>
|
||||||
|
{o.interest}元
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
|
||||||
<Text className="text-gray-500">取货点:</Text>
|
|
||||||
<Text>{pickupName || '-'}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
{pickupAddr ? (
|
))
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
)}
|
||||||
<Text className="text-gray-500">取货地址:</Text>
|
|
||||||
<Text>{pickupAddr}</Text>
|
<View className="mt-2 flex justify-center">
|
||||||
</View>
|
<Button
|
||||||
) : null}
|
fill="none"
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
size="small"
|
||||||
<Text className="text-gray-500">预约配送:</Text>
|
style={{ color: '#bdbdbd' }}
|
||||||
<Text>{o.sendTime ? dayjs(o.sendTime).format('YYYY-MM-DD HH:mm') : '-'}</Text>
|
onClick={loadMore}
|
||||||
</View>
|
>
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
加载更多
|
||||||
<Text className="text-gray-500">数量:</Text>
|
</Button>
|
||||||
<Text>{qty || '-'}</Text>
|
|
||||||
<Text className="text-gray-500 ml-3">门店:</Text>
|
|
||||||
<Text>{o.storeName || '-'}</Text>
|
|
||||||
</View>
|
|
||||||
{o.storePhone ? (
|
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
|
||||||
<Text className="text-gray-500">门店电话:</Text>
|
|
||||||
<Text>{o.storePhone}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
{remark ? (
|
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
|
||||||
<Text className="text-gray-500">备注:</Text>
|
|
||||||
<Text>{remark}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
{o.sendStartTime ? (
|
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
|
||||||
<Text className="text-gray-500">开始配送:</Text>
|
|
||||||
<Text>{dayjs(o.sendStartTime).format('YYYY-MM-DD HH:mm')}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
{o.sendEndTime ? (
|
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
|
||||||
<Text className="text-gray-500">送达时间:</Text>
|
|
||||||
<Text>{dayjs(o.sendEndTime).format('YYYY-MM-DD HH:mm')}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
{o.receiveConfirmTime ? (
|
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
|
||||||
<Text className="text-gray-500">确认收货:</Text>
|
|
||||||
<Text>{dayjs(o.receiveConfirmTime).format('YYYY-MM-DD HH:mm')}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
{o.sendEndImg ? (
|
|
||||||
<View className="text-sm text-gray-700 mt-2">
|
|
||||||
<Text className="text-gray-500">送达照片:</Text>
|
|
||||||
<View className="mt-2">
|
|
||||||
<Image src={o.sendEndImg} width="100%" height="120" />
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 配送流程 */}
|
<Popup
|
||||||
<View className="mt-3 bg-gray-50 rounded-lg p-2 text-xs">
|
visible={datePopupVisible}
|
||||||
<Text className="text-gray-600">流程:</Text>
|
position="bottom"
|
||||||
<Text className={flow1Done ? 'text-green-600 font-medium' : 'text-gray-400'}>1 派单</Text>
|
onClose={() => setDatePopupVisible(false)}
|
||||||
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
style={{ borderTopLeftRadius: '12px', borderTopRightRadius: '12px' }}
|
||||||
<Text className={flow2Done ? 'text-blue-600 font-medium' : 'text-gray-400'}>2 配送中</Text>
|
>
|
||||||
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
<View className="px-4 py-4 bg-white">
|
||||||
<Text className={flow3Done ? 'text-purple-600 font-medium' : 'text-gray-400'}>3 送达留档</Text>
|
<View className="text-base font-semibold text-gray-900">时间筛选</View>
|
||||||
<Text className="mx-1 text-gray-400">{'>'}</Text>
|
<View className="mt-3 text-sm text-gray-700">
|
||||||
<Text className={flow4Done ? 'text-green-600 font-medium' : 'text-gray-400'}>4 完成</Text>
|
<View className="flex items-center justify-between py-2" onClick={() => { setPicking('start'); setDatePickerVisible(true) }}>
|
||||||
|
<Text>开始日期</Text>
|
||||||
|
<Text className="text-gray-500">{formatYmd(startDate) || '未选择'}</Text>
|
||||||
|
</View>
|
||||||
|
<View className="flex items-center justify-between py-2" onClick={() => { setPicking('end'); setDatePickerVisible(true) }}>
|
||||||
|
<Text>结束日期</Text>
|
||||||
|
<Text className="text-gray-500">{formatYmd(endDate) || '未选择'}</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className="mt-3 flex justify-end">
|
<View className="mt-4 flex items-center justify-between gap-3">
|
||||||
<Space>
|
|
||||||
{!!phoneToCall && (
|
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
fill="outline"
|
||||||
onClick={e => {
|
onClick={() => {
|
||||||
e.stopPropagation()
|
setStartDate(null)
|
||||||
Taro.makePhoneCall({ phoneNumber: phoneToCall })
|
setEndDate(null)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
联系客户
|
清空
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
<Button type="primary" onClick={() => setDatePopupVisible(false)}>
|
||||||
{!!addr && addr !== '-' && (
|
完成
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation()
|
|
||||||
void Taro.setClipboardData({ data: addr })
|
|
||||||
Taro.showToast({ title: '地址已复制', icon: 'none' })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
复制地址
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{!!storePhone && (
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation()
|
|
||||||
Taro.makePhoneCall({ phoneNumber: storePhone })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
联系门店
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{canStartDeliver(o) && (
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation()
|
|
||||||
void handleStartDeliver(o)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
开始配送
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{canConfirmDelivered(o) && (
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation()
|
|
||||||
openDeliverDialog(o, { mode: 'waitCustomerConfirm' })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
确认送达
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{canCompleteByPhoto(o) && (
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation()
|
|
||||||
openDeliverDialog(o, { mode: 'photoComplete' })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
补传照片完成
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Cell>
|
</Popup>
|
||||||
)
|
|
||||||
})}
|
|
||||||
</InfiniteLoading>
|
|
||||||
)}
|
|
||||||
</PullToRefresh>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Dialog
|
<DatePicker
|
||||||
title="确认送达"
|
visible={datePickerVisible}
|
||||||
visible={deliverDialogVisible}
|
title={picking === 'start' ? '选择开始日期' : '选择结束日期'}
|
||||||
confirmText={
|
type="date"
|
||||||
deliverSubmitting
|
value={(picking === 'start' ? startDate : endDate) || new Date()}
|
||||||
? '提交中...'
|
startDate={dayjs('2020-01-01').toDate()}
|
||||||
: deliverConfirmMode === 'photoComplete'
|
endDate={dayjs('2035-12-31').toDate()}
|
||||||
? '拍照完成'
|
onClose={() => setDatePickerVisible(false)}
|
||||||
: '确认送达'
|
onCancel={() => setDatePickerVisible(false)}
|
||||||
|
onConfirm={(_options, selectedValue) => {
|
||||||
|
const [y, m, d] = (selectedValue || []).map(v => Number(v))
|
||||||
|
const next = new Date(y, (m || 1) - 1, d || 1, 0, 0, 0)
|
||||||
|
|
||||||
|
if (picking === 'start') {
|
||||||
|
setStartDate(next)
|
||||||
|
if (endDate && dayjs(endDate).isBefore(next, 'day')) setEndDate(null)
|
||||||
|
} else {
|
||||||
|
setEndDate(next)
|
||||||
|
if (startDate && dayjs(next).isBefore(startDate, 'day')) setStartDate(null)
|
||||||
}
|
}
|
||||||
cancelText="取消"
|
setDatePickerVisible(false)
|
||||||
onConfirm={handleConfirmDelivered}
|
|
||||||
onCancel={() => {
|
|
||||||
if (deliverSubmitting) return
|
|
||||||
setDeliverDialogVisible(false)
|
|
||||||
setDeliverOrder(null)
|
|
||||||
setDeliverImg(undefined)
|
|
||||||
setDeliverConfirmMode('photoComplete')
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<View className="text-sm text-gray-700">
|
</ConfigProvider>
|
||||||
<View>到达收货点后,可选择“拍照留档直接完成”或“等待客户确认收货”。</View>
|
|
||||||
|
|
||||||
<View className="mt-3">
|
|
||||||
<RadioGroup value={deliverConfirmMode} onChange={v => setDeliverConfirmMode(v as DeliverConfirmMode)}>
|
|
||||||
<Radio value="photoComplete">拍照留档(直接完成)</Radio>
|
|
||||||
<Radio value="waitCustomerConfirm">客户确认收货(可不拍照)</Radio>
|
|
||||||
</RadioGroup>
|
|
||||||
</View>
|
|
||||||
<View className="mt-3">
|
|
||||||
<Button size="small" onClick={handleChooseDeliverImg}>
|
|
||||||
{deliverImg ? '重新拍照/上传' : '拍照/上传'}
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
{deliverImg && (
|
|
||||||
<View className="mt-3">
|
|
||||||
<Image src={deliverImg} width="100%" height="120" />
|
|
||||||
<View className="mt-2 flex justify-end">
|
|
||||||
<Button size="small" onClick={() => setDeliverImg(undefined)}>
|
|
||||||
移除照片
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<View className="mt-3 text-xs text-gray-500">
|
|
||||||
说明:如选择“客户确认收货”,订单进入“待客户确认”;客户在用户端确认收货或超时自动确认(需后端支持)。
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</Dialog>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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