refactor(credit): 将信用客户模块重构为小程序客户模块

- 替换 CreditCompany 相关 API 为 CreditMpCustomer API
- 更新页面路由路径和组件名称
- 修改数据模型字段映射关系
- 调整界面布局使用 Cell 组件展示客户信息
- 更新搜索框提示文本内容
- 修改分配员工逻辑和确认提示
- 调整筛选条件从行业改为状态
- 更新联系电话获取逻辑从备注中提取
- 修改详情页编辑跳转路径
- 移除原公司详情页面的复杂业务逻辑
- 优化员工加载逻辑并添加缓存机制
- 更新客户列表项点击事件处理方式
This commit is contained in:
2026-03-20 00:02:52 +08:00
parent 33ca00cc9d
commit 26253aa0d7
3 changed files with 148 additions and 297 deletions

View File

@@ -28,6 +28,8 @@ export interface CreditMpCustomer {
files?: string; files?: string;
// 是否有数据 // 是否有数据
hasData?: string; hasData?: string;
// 步骤
step?: number;
// 备注 // 备注
comments?: string; comments?: string;
// 是否推荐 // 是否推荐

View File

@@ -1,129 +1,36 @@
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import Taro, { useDidShow, useRouter } from '@tarojs/taro' import Taro, { useDidShow, useRouter } from '@tarojs/taro'
import { View, Text } from '@tarojs/components' import { View, Text } from '@tarojs/components'
import { Button, ConfigProvider, Empty, Loading } from '@nutui/nutui-react-taro' import { Button, Cell, CellGroup, ConfigProvider, Empty, Loading } from '@nutui/nutui-react-taro'
import { Setting } from '@nutui/icons-react-taro' import { Setting } from '@nutui/icons-react-taro'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { getCreditCompany } from '@/api/credit/creditCompany' import { getCreditMpCustomer } from '@/api/credit/creditMpCustomer'
import type { CreditCompany } from '@/api/credit/creditCompany/model' import type { CreditMpCustomer } from '@/api/credit/creditMpCustomer/model'
type CustomerStatus = '保护期内' | '已签约' | '已完成' | '保护期外' const fmtTime = (t?: string) => {
const txt = String(t || '').trim()
type FollowPerson = {
name: string
time?: string
isCurrent?: boolean
}
const splitPhones = (raw?: string) => {
const text = String(raw || '').trim()
if (!text) return []
return text
.split(/[\s,;;、\n\r]+/g)
.map(s => s.trim())
.filter(Boolean)
}
const uniq = <T,>(arr: T[]) => Array.from(new Set(arr))
const getCompanyIndustry = (c?: CreditCompany) => {
if (!c) return ''
return String(
c.nationalStandardIndustryCategories6 ||
c.nationalStandardIndustryCategories2 ||
c.nationalStandardIndustryCategories ||
c.institutionType ||
''
).trim()
}
const parseContactFromComments = (comments?: string) => {
const txt = String(comments || '').trim()
if (!txt) return '' if (!txt) return ''
const m = txt.match(/联系人:([^;]+)/) const d = dayjs(txt)
return String(m?.[1] || '').trim() return d.isValid() ? d.format('YYYY-MM-DD HH:mm:ss') : txt
} }
const getStatusTextClass = (s: CustomerStatus) => { const buildLocation = (row?: CreditMpCustomer | null) => {
if (s === '保护期内') return 'text-green-600' if (!row) return ''
if (s === '已签约') return 'text-orange-500' return [row.province, row.city, row.region].filter(Boolean).join(' ')
if (s === '已完成') return 'text-blue-600'
return 'text-gray-500'
} }
const normalizeFollowPeople = (company: CreditCompany): FollowPerson[] => { const buildDesc = (row?: CreditMpCustomer | null) => {
// 兼容后端可能下发多种字段;未下发时给出静态示例,保证 UI/逻辑可演示。 if (!row) return ''
const anyCompany = company as any const price = row.price ? `${row.price}` : ''
const years = row.years ? `${row.years}` : ''
const incoming: FollowPerson[] = [] const loc = buildLocation(row)
return [price, years, loc].filter(Boolean).join(' · ')
const arr1 = anyCompany?.followPeople as Array<any> | undefined
if (Array.isArray(arr1) && arr1.length) {
for (const it of arr1) {
const name = String(it?.name || it?.realName || it?.userRealName || '').trim()
if (!name) continue
incoming.push({ name, time: it?.time || it?.date, isCurrent: !!it?.isCurrent })
}
} }
const arr2 = anyCompany?.followHistory as Array<any> | undefined export default function CreditMpCustomerDetailPage() {
if (Array.isArray(arr2) && arr2.length) {
for (const it of arr2) {
const name = String(it?.name || it?.realName || it?.userRealName || it || '').trim()
if (!name) continue
incoming.push({ name, time: it?.time || it?.date, isCurrent: !!it?.isCurrent })
}
}
const currentName = String(
anyCompany?.followRealName || anyCompany?.userRealName || anyCompany?.realName || ''
).trim()
if (currentName) incoming.push({ name: currentName, isCurrent: true })
const cleaned = incoming.filter(x => x?.name).map(x => ({ ...x, name: String(x.name).trim() }))
const deduped: FollowPerson[] = []
const seen = new Set<string>()
for (const p of cleaned) {
const k = p.name
if (seen.has(k)) continue
seen.add(k)
deduped.push(p)
}
if (deduped.length) {
// 规则:往期跟进人(灰色)按时间顺序在前;当前跟进人(蓝色)放在最后。
const current = deduped.filter(p => p.isCurrent)
const history = deduped.filter(p => !p.isCurrent)
history.sort((a, b) => {
const at = a.time ? dayjs(a.time).valueOf() : 0
const bt = b.time ? dayjs(b.time).valueOf() : 0
return at - bt
})
// 多个 current 时取最后一个为“当前”
const currentOne = current.length ? current[current.length - 1] : undefined
return currentOne ? history.concat([{ ...currentOne, isCurrent: true }]) : history
}
// 无真实跟进人信息:尽量展示一个可识别的占位,避免“凭空造历史数据”造成误导。
const uid = Number(company?.userId)
if (Number.isFinite(uid) && uid > 0) return [{ name: `员工${uid}`, isCurrent: true }]
return []
}
const getAssignDateText = (company?: CreditCompany) => {
const anyCompany = company as any
const raw = anyCompany?.assignDate || anyCompany?.assignTime || anyCompany?.updateTime || anyCompany?.createTime
const t = String(raw || '').trim()
if (!t) return ''
const d = dayjs(t)
if (!d.isValid()) return t
return d.format('YYYY-MM-DD')
}
export default function CreditCompanyDetailPage() {
const router = useRouter() const router = useRouter()
const companyId = useMemo(() => { const rowId = useMemo(() => {
const id = Number((router?.params as any)?.id) const id = Number((router?.params as any)?.id)
return Number.isFinite(id) && id > 0 ? id : undefined return Number.isFinite(id) && id > 0 ? id : undefined
}, [router?.params]) }, [router?.params])
@@ -139,61 +46,31 @@ export default function CreditCompanyDetailPage() {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [company, setCompany] = useState<CreditCompany | null>(null) const [row, setRow] = useState<CreditMpCustomer | null>(null)
const reload = useCallback(async () => { const reload = useCallback(async () => {
setError(null) setError(null)
setLoading(true) setLoading(true)
try { try {
if (!companyId) throw new Error('缺少客户ID') if (!rowId) throw new Error('缺少客户ID')
const res = await getCreditCompany(companyId) const res = await getCreditMpCustomer(rowId)
setCompany(res as CreditCompany) setRow((res || null) as CreditMpCustomer | null)
} catch (e) { } catch (e) {
console.error('加载客户详情失败:', e) console.error('加载客户详情失败:', e)
setCompany(null) setRow(null)
setError(String((e as any)?.message || '加载失败')) setError(String((e as any)?.message || '加载失败'))
} finally { } finally {
setLoading(false) setLoading(false)
} }
}, [companyId]) }, [rowId])
useDidShow(() => { useDidShow(() => {
reload().then() reload().then()
}) })
const name = useMemo(() => {
const c = company || ({} as CreditCompany)
return String(c.matchName || c.name || '').trim()
}, [company])
const code = useMemo(() => String(company?.code || '').trim(), [company?.code])
const industry = useMemo(() => getCompanyIndustry(company || undefined), [company])
const contact = useMemo(() => parseContactFromComments(company?.comments), [company?.comments])
const phones = useMemo(() => {
const arr = [...splitPhones(company?.tel), ...splitPhones(company?.moreTel)]
return uniq(arr)
}, [company?.moreTel, company?.tel])
const address = useMemo(() => {
const c = company || ({} as CreditCompany)
const region = [c.province, c.city, c.region].filter(Boolean).join('')
const addr = String(c.address || '').trim()
return (region + addr).trim() || region || addr
}, [company])
const followPeople = useMemo(() => (company ? normalizeFollowPeople(company) : []), [company])
const assignDate = useMemo(() => getAssignDateText(company || undefined), [company])
const customerStatus = useMemo(() => {
const anyCompany = company as any
const raw = String(anyCompany?.customerStatus || anyCompany?.statusText || '').trim()
const allowed: CustomerStatus[] = ['保护期内', '已签约', '已完成', '保护期外']
if (allowed.includes(raw as any)) return raw as CustomerStatus
return '保护期内'
}, [company])
const headerOffset = statusBarHeight + 80 const headerOffset = statusBarHeight + 80
const title = String(row?.toUser || '').trim() || '客户详情'
const desc = buildDesc(row)
return ( return (
<View className="bg-pink-50 min-h-screen"> <View className="bg-pink-50 min-h-screen">
@@ -219,7 +96,7 @@ export default function CreditCompanyDetailPage() {
onClick={async () => { onClick={async () => {
try { try {
const res = await Taro.showActionSheet({ itemList: ['编辑客户', '刷新'] }) const res = await Taro.showActionSheet({ itemList: ['编辑客户', '刷新'] })
if (res.tapIndex === 0 && companyId) Taro.navigateTo({ url: `/credit/my-customer/edit?id=${companyId}` }) if (res.tapIndex === 0 && rowId) Taro.navigateTo({ url: `/credit/creditMpCustomer/add?id=${rowId}` })
if (res.tapIndex === 1) reload() if (res.tapIndex === 1) reload()
} catch (e) { } catch (e) {
const msg = String((e as any)?.errMsg || (e as any)?.message || e || '') const msg = String((e as any)?.errMsg || (e as any)?.message || e || '')
@@ -254,95 +131,36 @@ export default function CreditCompanyDetailPage() {
</Button> </Button>
</View> </View>
</View> </View>
) : !company ? ( ) : !row ? (
<View className="bg-white rounded-xl border border-pink-100 py-10"> <View className="bg-white rounded-xl border border-pink-100 py-10">
<Empty description="暂无客户信息" /> <Empty description="暂无客户信息" />
</View> </View>
) : ( ) : (
<View className="bg-white rounded-xl border border-pink-100 p-4"> <View className="bg-white rounded-xl border border-pink-100 p-4">
<View className="text-base font-semibold text-gray-900">{name || '—'}</View> <View className="text-base font-semibold text-gray-900">{title}</View>
{!!desc && (
<View className="mt-3 space-y-3 text-sm"> <View className="mt-2 text-xs text-gray-500">
<View className="flex items-start justify-between gap-3"> <Text>{desc}</Text>
<Text className="text-gray-500"></Text>
<Text className="text-gray-400 text-right break-all">{code || '—'}</Text>
</View> </View>
<View className="flex items-start justify-between gap-3">
<Text className="text-gray-500"></Text>
<Text className="text-red-500 text-right break-all">{industry || '—'}</Text>
</View>
<View className="flex items-start justify-between gap-3">
<Text className="text-gray-500"></Text>
<Text className="text-gray-400 text-right break-all">{contact || '—'}</Text>
</View>
<View className="flex items-start justify-between gap-3">
<Text className="text-gray-500"></Text>
<View className="text-right">
{phones.length ? (
<Text className="text-gray-400 break-all">{phones.join('')}</Text>
) : (
<Text className="text-gray-400"></Text>
)} )}
</View>
</View>
<View className="flex items-start justify-between gap-3"> <View className="mt-3">
<Text className="text-gray-500"></Text> <CellGroup>
<Text className="text-gray-700 text-right break-all">{address || '—'}</Text> <Cell title="订单号" description={String(row.id ?? '—')} />
</View> <Cell title="状态" description={String(row.statusTxt || '—')} />
<Cell title="分配人ID" description={row.userId ? String(row.userId) : '未分配'} />
<View className="flex items-start justify-between gap-3"> <Cell title="创建时间" description={fmtTime(row.createTime) || '—'} />
<Text className="text-gray-500"></Text> <Cell title="更新时间" description={fmtTime(row.updateTime) || '—'} />
<View className="text-right"> {!!row.url && <Cell title="链接" description={String(row.url)} />}
{followPeople.length ? ( {!!row.files && <Cell title="文件" description={String(row.files)} />}
followPeople.map((p, idx) => ( {!!row.comments && <Cell title="备注" description={String(row.comments)} />}
<Text </CellGroup>
key={`${p.name}-${idx}`}
className={p.isCurrent ? 'text-blue-600 break-all' : 'text-gray-400 break-all'}
>
{idx === followPeople.length - 1 ? p.name : `${p.name}`}
</Text>
))
) : (
<Text className="text-gray-400"></Text>
)}
</View>
</View>
<View className="flex items-start justify-between gap-3">
<Text className="text-gray-500"></Text>
<Text className="text-gray-400 text-right">{assignDate || '—'}</Text>
</View>
<View className="flex items-start justify-between gap-3">
<Text className="text-gray-500"></Text>
<Text className={`${getStatusTextClass(customerStatus)} text-right`}>{customerStatus}</Text>
</View>
</View> </View>
</View> </View>
)} )}
</View> </View>
<View className="fixed z-50 bottom-0 left-0 right-0 bg-pink-50 px-4 py-4 safe-area-bottom">
<Button
type="primary"
block
style={{ background: '#ef4444', borderColor: '#ef4444' }}
onClick={() => {
if (!companyId) {
Taro.showToast({ title: '缺少客户ID', icon: 'none' })
return
}
Taro.navigateTo({ url: `/credit/my-customer/follow-step1?id=${companyId}` })
}}
>
</Button>
</View>
</ConfigProvider> </ConfigProvider>
</View> </View>
) )
} }

View File

@@ -19,9 +19,8 @@ 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 { updateCreditCompany } from '@/api/credit/creditCompany' import { getCreditMpCustomer, pageCreditMpCustomer, updateCreditMpCustomer } from '@/api/credit/creditMpCustomer'
import type { CreditCompany } from '@/api/credit/creditCompany/model' import type { CreditMpCustomer, CreditMpCustomerParam } from '@/api/credit/creditMpCustomer/model'
import { pageCreditMpCustomer } from '@/api/credit/creditMpCustomer'
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'
import { hasRole } from '@/utils/permission' import { hasRole } from '@/utils/permission'
@@ -45,7 +44,7 @@ const safeParseJSON = <T,>(v: any): T | null => {
} }
} }
const getCompanyIdKey = (c: CreditCompany) => String(c?.id || '') const getRowIdKey = (c: CreditMpCustomer) => String(c?.id || '')
const splitPhones = (raw?: string) => { const splitPhones = (raw?: string) => {
const text = String(raw || '').trim() const text = String(raw || '').trim()
@@ -57,25 +56,22 @@ const splitPhones = (raw?: string) => {
.filter(Boolean) .filter(Boolean)
} }
const getCompanyPhones = (c: CreditCompany) => { const getRowPhones = (c: CreditMpCustomer) => {
const arr = [...splitPhones(c.tel), ...splitPhones(c.moreTel)] // CreditMpCustomer 标准字段无电话:尝试从备注中提取手机号(提取不到则为空)
const raw = String(c.comments || '')
const picked = raw.match(/1\d{10}/g) || []
const arr = splitPhones(picked.join(','))
return Array.from(new Set(arr)) return Array.from(new Set(arr))
} }
const getCompanyIndustry = (c: CreditCompany) => { const getRowStatus = (c: CreditMpCustomer) => {
// 兼容:不同数据源字段可能不一致,优先取更具体的大类 return String((c as any)?.statusTxt || (c as any)?.statusText || '').trim()
return String(
c.nationalStandardIndustryCategories6 ||
c.nationalStandardIndustryCategories2 ||
c.nationalStandardIndustryCategories ||
c.institutionType ||
''
).trim()
} }
export default function CreditCompanyPage() { export default function CreditCompanyPage() {
const serverPageRef = useRef(1) const serverPageRef = useRef(1)
const [list, setList] = useState<CreditCompany[]>([]) const staffLoadingPromiseRef = useRef<Promise<User[]> | null>(null)
const [list, setList] = useState<CreditMpCustomer[]>([])
const [hasMore, setHasMore] = useState(true) const [hasMore, setHasMore] = useState(true)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
@@ -88,8 +84,8 @@ export default function CreditCompanyPage() {
const [followVisible, setFollowVisible] = useState(false) const [followVisible, setFollowVisible] = useState(false)
const [followStatus, setFollowStatus] = useState<FollowStatus>('全部') const [followStatus, setFollowStatus] = useState<FollowStatus>('全部')
const [industryVisible, setIndustryVisible] = useState(false) const [statusVisible, setStatusVisible] = useState(false)
const [industryText, setIndustryText] = useState<string>('全部') const [statusText, setStatusText] = useState<string>('全部')
const [selectMode, setSelectMode] = useState(false) const [selectMode, setSelectMode] = useState(false)
const [selectedIds, setSelectedIds] = useState<number[]>([]) const [selectedIds, setSelectedIds] = useState<number[]>([])
@@ -144,8 +140,8 @@ export default function CreditCompanyPage() {
}, [staffList]) }, [staffList])
const getFollowStatus = useCallback( const getFollowStatus = useCallback(
(c: CreditCompany): FollowStatus => { (c: CreditMpCustomer): FollowStatus => {
const k = getCompanyIdKey(c) const k = getRowIdKey(c)
const stored = k ? followMap[k] : undefined const stored = k ? followMap[k] : undefined
if (stored) return stored if (stored) return stored
return '未联系' return '未联系'
@@ -153,7 +149,7 @@ export default function CreditCompanyPage() {
[followMap] [followMap]
) )
const setFollowStatusFor = async (c: CreditCompany) => { const setFollowStatusFor = async (c: CreditMpCustomer) => {
if (!c?.id) return if (!c?.id) return
try { try {
const res = await Taro.showActionSheet({ const res = await Taro.showActionSheet({
@@ -163,7 +159,7 @@ export default function CreditCompanyPage() {
if (!next) return if (!next) return
setFollowMap(prev => { setFollowMap(prev => {
const k = getCompanyIdKey(c) const k = getRowIdKey(c)
const merged = { ...prev, [k]: next } const merged = { ...prev, [k]: next }
Taro.setStorageSync(FOLLOW_MAP_STORAGE_KEY, merged) Taro.setStorageSync(FOLLOW_MAP_STORAGE_KEY, merged)
return merged return merged
@@ -182,9 +178,9 @@ export default function CreditCompanyPage() {
} }
const applyFilters = useCallback( const applyFilters = useCallback(
(incoming: CreditCompany[]) => { (incoming: CreditMpCustomer[]) => {
const city = cityText === '全部' ? '' : cityText const city = cityText === '全部' ? '' : cityText
const industry = industryText === '全部' ? '' : industryText const status = statusText === '全部' ? '' : statusText
const follow = followStatus === '全部' ? '' : followStatus const follow = followStatus === '全部' ? '' : followStatus
return incoming.filter(c => { return incoming.filter(c => {
@@ -195,9 +191,9 @@ export default function CreditCompanyPage() {
if (!full.includes(city) && String(c.city || '') !== city) return false if (!full.includes(city) && String(c.city || '') !== city) return false
} }
if (industry) { if (status) {
const ind = getCompanyIndustry(c) const txt = getRowStatus(c)
if (!ind.includes(industry)) return false if (!txt.includes(status)) return false
} }
if (follow) { if (follow) {
@@ -207,7 +203,7 @@ export default function CreditCompanyPage() {
return true return true
}) })
}, },
[cityText, followStatus, getFollowStatus, industryText] [cityText, followStatus, getFollowStatus, statusText]
) )
const reload = useCallback( const reload = useCallback(
@@ -240,8 +236,8 @@ export default function CreditCompanyPage() {
keywords: searchValue?.trim() || undefined keywords: searchValue?.trim() || undefined
} }
const res = await pageCreditMpCustomer(params as any) const res = await pageCreditMpCustomer(params as CreditMpCustomerParam)
const incoming = (res?.list || []) as CreditCompany[] const incoming = (res?.list || []) as CreditMpCustomer[]
const filtered = applyFilters(incoming) const filtered = applyFilters(incoming)
if (resetPage) { if (resetPage) {
@@ -292,11 +288,11 @@ export default function CreditCompanyPage() {
ensureStaffLoaded().then() ensureStaffLoaded().then()
}) })
const visibleIndustryOptions = useMemo(() => { const visibleStatusOptions = useMemo(() => {
const uniq = new Set<string>() const uniq = new Set<string>()
for (const m of list) { for (const m of list) {
const ind = getCompanyIndustry(m) const txt = getRowStatus(m)
if (ind) uniq.add(ind) if (txt) uniq.add(txt)
} }
const arr = Array.from(uniq) const arr = Array.from(uniq)
arr.sort() arr.sort()
@@ -313,7 +309,7 @@ export default function CreditCompanyPage() {
const copyPhones = async () => { const copyPhones = async () => {
const pool = selectMode && selectedIds.length ? list.filter(c => selectedIds.includes(Number(c.id))) : list const pool = selectMode && selectedIds.length ? list.filter(c => selectedIds.includes(Number(c.id))) : list
const phones = pool.flatMap(c => getCompanyPhones(c)) const phones = pool.flatMap(c => getRowPhones(c))
const unique = Array.from(new Set(phones)).filter(Boolean) const unique = Array.from(new Set(phones)).filter(Boolean)
if (!unique.length) { if (!unique.length) {
Taro.showToast({ title: '暂无可复制的电话', icon: 'none' }) Taro.showToast({ title: '暂无可复制的电话', icon: 'none' })
@@ -338,23 +334,33 @@ export default function CreditCompanyPage() {
} }
const openAddCustomer = () => { const openAddCustomer = () => {
Taro.navigateTo({ url: '/credit/my-customer/add' }) Taro.navigateTo({ url: '/credit/creditMpCustomer/add' })
} }
const ensureStaffLoaded = async () => { const ensureStaffLoaded = useCallback(async (): Promise<User[]> => {
if (staffLoading) return if (staffList.length) return staffList
if (staffList.length) return if (staffLoadingPromiseRef.current) return staffLoadingPromiseRef.current
setStaffLoading(true) setStaffLoading(true)
const p = (async () => {
try { try {
const res = await listUsers({ isAdmin: true } as any) const res = await listUsers({ isStaff: true } as any)
setStaffList((res || []) as User[]) const arr = (res || []) as User[]
setStaffList(arr)
return arr
} catch (e) { } catch (e) {
console.error('加载员工列表失败:', e) console.error('加载员工列表失败:', e)
Taro.showToast({ title: '加载员工失败', icon: 'none' }) Taro.showToast({ title: '加载员工失败', icon: 'none' })
return []
} finally { } finally {
setStaffLoading(false) setStaffLoading(false)
staffLoadingPromiseRef.current = null
} }
} })()
staffLoadingPromiseRef.current = p
return p
}, [staffList])
const openAssign = async () => { const openAssign = async () => {
if (!canAssign) { if (!canAssign) {
@@ -371,7 +377,11 @@ export default function CreditCompanyPage() {
Taro.showToast({ title: '请先勾选客户', icon: 'none' }) Taro.showToast({ title: '请先勾选客户', icon: 'none' })
return return
} }
await ensureStaffLoaded() const staff = await ensureStaffLoaded()
if (!staff.length) {
Taro.showToast({ title: '暂无可分配员工', icon: 'none' })
return
}
setStaffPopupVisible(true) setStaffPopupVisible(true)
} }
@@ -387,8 +397,16 @@ export default function CreditCompanyPage() {
setAssigning(true) setAssigning(true)
try { try {
const staffName = staffNameMap.get(Number(staffSelectedId)) || `员工${staffSelectedId}`
const confirmRes = await Taro.showModal({
title: '确认分配',
content: `确定将 ${selectedIds.length} 个客户分配给「${staffName}」吗?`
})
if (!confirmRes.confirm) return
for (const id of selectedIds) { for (const id of selectedIds) {
await updateCreditCompany({ id, userId: staffSelectedId } as any) const detail = await getCreditMpCustomer(Number(id))
await updateCreditMpCustomer({ ...(detail || {}), id, userId: staffSelectedId } as any)
} }
Taro.showToast({ title: '分配成功', icon: 'success' }) Taro.showToast({ title: '分配成功', icon: 'success' })
setStaffPopupVisible(false) setStaffPopupVisible(false)
@@ -408,7 +426,7 @@ export default function CreditCompanyPage() {
<ConfigProvider> <ConfigProvider>
<View className="py-2"> <View className="py-2">
<SearchBar <SearchBar
placeholder="公司名称 / 统一代码" placeholder="拖欠方 / 关键词"
value={searchValue} value={searchValue}
onChange={setSearchValue} onChange={setSearchValue}
onSearch={() => reload(true)} onSearch={() => reload(true)}
@@ -422,8 +440,8 @@ export default function CreditCompanyPage() {
<Button size="small" fill="outline" onClick={() => setFollowVisible(true)}> <Button size="small" fill="outline" onClick={() => setFollowVisible(true)}>
{followStatus === '全部' ? '跟进状态' : followStatus} {followStatus === '全部' ? '跟进状态' : followStatus}
</Button> </Button>
<Button size="small" fill="outline" onClick={() => setIndustryVisible(true)}> <Button size="small" fill="outline" onClick={() => setStatusVisible(true)}>
{industryText === '全部' ? '行业' : industryText} {statusText === '全部' ? '状态' : statusText}
</Button> </Button>
<View className="flex-1" /> <View className="flex-1" />
<Button size="small" fill="outline" icon={<Copy />} onClick={copyPhones}> <Button size="small" fill="outline" icon={<Copy />} onClick={copyPhones}>
@@ -470,17 +488,24 @@ export default function CreditCompanyPage() {
(c as any)?.userRealName || (c as any)?.userRealName ||
(c as any)?.followRealName || (c as any)?.followRealName ||
(c.userId ? staffNameMap.get(Number(c.userId)) : undefined) (c.userId ? staffNameMap.get(Number(c.userId)) : undefined)
const name = c.matchName || c.name || `企业${c.id || ''}` const name = String(c.toUser || '').trim() || `客户${c.id || ''}`
const industry = getCompanyIndustry(c) const status = getRowStatus(c)
const phones = getCompanyPhones(c) const price = c.price ? `${c.price}` : ''
const years = c.years ? `${c.years}` : ''
const location = [c.province, c.city, c.region].filter(Boolean).join(' ')
const desc = [price, years, location].filter(Boolean).join(' · ')
const phones = getRowPhones(c)
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={() => { onClick={() => {
if (selectMode) return if (selectMode) {
toggleSelectId(id, !selected)
return
}
if (!c?.id) return if (!c?.id) return
Taro.navigateTo({ url: `/credit/my-customer/detail?id=${c.id}` }) Taro.navigateTo({ url: `/credit/mp-customer/detail?id=${c.id}` })
}} }}
> >
<View className="flex gap-3 items-start w-full"> <View className="flex gap-3 items-start w-full">
@@ -504,9 +529,9 @@ export default function CreditCompanyPage() {
<View className="text-base font-bold text-gray-900 truncate"> <View className="text-base font-bold text-gray-900 truncate">
{name} {name}
</View> </View>
{!!industry && ( {!!desc && (
<View className="text-xs text-gray-500 truncate mt-1"> <View className="text-xs text-gray-500 truncate mt-1">
{industry} {desc}
</View> </View>
)} )}
</View> </View>
@@ -523,6 +548,11 @@ export default function CreditCompanyPage() {
<View className="mt-2 flex items-center justify-between gap-2"> <View className="mt-2 flex items-center justify-between gap-2">
<View className="text-xs text-gray-600 truncate"> <View className="text-xs text-gray-600 truncate">
{!!status && (
<Text className="mr-2">
{status}
</Text>
)}
<Text className="mr-2"> <Text className="mr-2">
{primaryPhone ? primaryPhone : '暂无电话'} {primaryPhone ? primaryPhone : '暂无电话'}
</Text> </Text>
@@ -649,21 +679,22 @@ export default function CreditCompanyPage() {
</View> </View>
</Popup> </Popup>
<Popup <Popup visible={statusVisible} position="bottom" style={{ height: '45vh' }} onClose={() => setStatusVisible(false)}>
visible={industryVisible}
position="bottom"
style={{ height: '45vh' }}
onClose={() => setIndustryVisible(false)}
>
<View className="p-4"> <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={() => setStatusVisible(false)}>
</Text>
</View>
<CellGroup> <CellGroup>
{visibleIndustryOptions.map(s => ( {visibleStatusOptions.map(s => (
<Cell <Cell
key={s} key={s}
title={<Text className={s === industryText ? 'text-blue-600' : ''}>{s}</Text>} title={<Text className={s === statusText ? 'text-blue-600' : ''}>{s}</Text>}
onClick={() => { onClick={() => {
setIndustryText(s) setStatusText(s)
setIndustryVisible(false) setStatusVisible(false)
reload(true).then() reload(true).then()
}} }}
/> />