refactor(credit): 将客户跟进状态改为步骤状态

- 在 CreditMpCustomerParam 接口中添加 step 字段
- 替换原有的跟进状态相关常量和选项为步骤状态配置
- 新增 STEP_STATUS_TEXT 映射对象定义五个步骤状态
- 移除废弃的 getRowIdKey 函数和 FOLLOW_MAP_STORAGE_KEY 相关逻辑
- 添加 getStepCode、getStepText 和 getStepTagType 工具函数处理步骤状态
- 将 followStatus 相关状态管理替换为 stepCode 状态管理
- 更新 applyFilters 函数使用步骤状态进行筛选
- 修改界面渲染逻辑使用新的步骤状态替代原有跟进状态
- 更新弹窗组件从 followVisible 切换为 stepVisible 控制
- 移除状态选择按钮的注释并调整步骤状态选择逻辑
This commit is contained in:
2026-03-20 00:13:46 +08:00
parent 26253aa0d7
commit d3223224e1
2 changed files with 79 additions and 79 deletions

View File

@@ -55,5 +55,6 @@ export interface CreditMpCustomer {
*/ */
export interface CreditMpCustomerParam extends PageParam { export interface CreditMpCustomerParam extends PageParam {
id?: number; id?: number;
step?: number;
keywords?: string; keywords?: string;
} }

View File

@@ -27,11 +27,16 @@ import { hasRole } from '@/utils/permission'
const PAGE_SIZE = 10 const PAGE_SIZE = 10
type FollowStatus = '全部' | '未联系' | '加微前沟通' | '跟进中' | '已成交' | '无意向' const STEP_STATUS_TEXT: Record<number, string> = {
0: '未受理',
1: '已受理',
2: '材料提交',
3: '合同签订',
4: '执行回款',
5: '完结'
}
const FOLLOW_STATUS_OPTIONS: FollowStatus[] = ['全部', '未联系', '加微前沟通', '跟进中', '已成交', '无意向'] const STEP_OPTIONS = [0, 1, 2, 3, 4, 5].map(code => ({ code, text: STEP_STATUS_TEXT[code] }))
const FOLLOW_MAP_STORAGE_KEY = 'credit_company_follow_status_map'
const safeParseJSON = <T,>(v: any): T | null => { const safeParseJSON = <T,>(v: any): T | null => {
try { try {
@@ -44,8 +49,6 @@ const safeParseJSON = <T,>(v: any): T | null => {
} }
} }
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()
if (!text) return [] if (!text) return []
@@ -68,6 +71,42 @@ const getRowStatus = (c: CreditMpCustomer) => {
return String((c as any)?.statusTxt || (c as any)?.statusText || '').trim() return String((c as any)?.statusTxt || (c as any)?.statusText || '').trim()
} }
const getStepCode = (row: CreditMpCustomer): number | null => {
const anyRow = row as any
const raw =
anyRow?.step ??
anyRow?.stepStatus ??
anyRow?.statusStep ??
anyRow?.stepNum ??
anyRow?.stepCode ??
anyRow?.stepId ??
undefined
const n = Number(raw)
if (Number.isInteger(n) && n in STEP_STATUS_TEXT) return n
// 兼容:后端直接返回文字到 statusTxt
const txt = getRowStatus(row)
if (!txt) return null
const found = Object.entries(STEP_STATUS_TEXT).find(([, t]) => t === txt)
return found ? Number(found[0]) : null
}
const getStepText = (row: CreditMpCustomer) => {
const code = getStepCode(row)
if (code != null) return STEP_STATUS_TEXT[code] || '处理中'
const txt = getRowStatus(row)
return txt || '处理中'
}
const getStepTagType = (code: number | null): any => {
if (code === 5) return 'success'
if (code === 0) return 'primary'
if (code === 4) return 'warning'
if (code === 3) return 'danger'
return 'primary'
}
export default function CreditCompanyPage() { export default function CreditCompanyPage() {
const serverPageRef = useRef(1) const serverPageRef = useRef(1)
const staffLoadingPromiseRef = useRef<Promise<User[]> | null>(null) const staffLoadingPromiseRef = useRef<Promise<User[]> | null>(null)
@@ -81,8 +120,8 @@ export default function CreditCompanyPage() {
const [cityVisible, setCityVisible] = useState(false) const [cityVisible, setCityVisible] = useState(false)
const [cityText, setCityText] = useState<string>('全部') const [cityText, setCityText] = useState<string>('全部')
const [followVisible, setFollowVisible] = useState(false) const [stepVisible, setStepVisible] = useState(false)
const [followStatus, setFollowStatus] = useState<FollowStatus>('全部') const [stepCode, setStepCode] = useState<number | null>(null)
const [statusVisible, setStatusVisible] = useState(false) const [statusVisible, setStatusVisible] = useState(false)
const [statusText, setStatusText] = useState<string>('全部') const [statusText, setStatusText] = useState<string>('全部')
@@ -96,11 +135,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 [followMap, setFollowMap] = useState<Record<string, FollowStatus>>(() => {
const raw = Taro.getStorageSync(FOLLOW_MAP_STORAGE_KEY)
return safeParseJSON<Record<string, FollowStatus>>(raw) || {}
})
const currentUser = useMemo(() => { const currentUser = useMemo(() => {
return safeParseJSON<User>(Taro.getStorageSync('User')) || ({} as User) return safeParseJSON<User>(Taro.getStorageSync('User')) || ({} as User)
}, []) }, [])
@@ -139,49 +173,11 @@ export default function CreditCompanyPage() {
return map return map
}, [staffList]) }, [staffList])
const getFollowStatus = useCallback(
(c: CreditMpCustomer): FollowStatus => {
const k = getRowIdKey(c)
const stored = k ? followMap[k] : undefined
if (stored) return stored
return '未联系'
},
[followMap]
)
const setFollowStatusFor = async (c: CreditMpCustomer) => {
if (!c?.id) return
try {
const res = await Taro.showActionSheet({
itemList: FOLLOW_STATUS_OPTIONS.filter(s => s !== '全部')
})
const next = FOLLOW_STATUS_OPTIONS.filter(s => s !== '全部')[res.tapIndex] as FollowStatus | undefined
if (!next) return
setFollowMap(prev => {
const k = getRowIdKey(c)
const merged = { ...prev, [k]: next }
Taro.setStorageSync(FOLLOW_MAP_STORAGE_KEY, merged)
return merged
})
// 若当前正在按状态筛选,则即时移除不匹配项,避免“改完状态但列表不变”的割裂感。
if (followStatus !== '全部' && next !== followStatus && c.id) {
const cid = Number(c.id)
setList(prev => prev.filter(x => Number(x.id) !== cid))
setSelectedIds(prev => prev.filter(id => id !== cid))
}
} catch (e) {
const msg = String((e as any)?.errMsg || (e as any)?.message || e || '')
if (msg.includes('cancel')) return
}
}
const applyFilters = useCallback( const applyFilters = useCallback(
(incoming: CreditMpCustomer[]) => { (incoming: CreditMpCustomer[]) => {
const city = cityText === '全部' ? '' : cityText const city = cityText === '全部' ? '' : cityText
const status = statusText === '全部' ? '' : statusText const status = statusText === '全部' ? '' : statusText
const follow = followStatus === '全部' ? '' : followStatus const step = stepCode == null ? null : stepCode
return incoming.filter(c => { return incoming.filter(c => {
if (c?.deleted === 1) return false if (c?.deleted === 1) return false
@@ -196,14 +192,15 @@ export default function CreditCompanyPage() {
if (!txt.includes(status)) return false if (!txt.includes(status)) return false
} }
if (follow) { if (step != null) {
if (getFollowStatus(c) !== follow) return false const code = getStepCode(c)
if (code == null || code !== step) return false
} }
return true return true
}) })
}, },
[cityText, followStatus, getFollowStatus, statusText] [cityText, statusText, stepCode]
) )
const reload = useCallback( const reload = useCallback(
@@ -437,12 +434,12 @@ export default function CreditCompanyPage() {
<Button size="small" fill="outline" onClick={() => setCityVisible(true)}> <Button size="small" fill="outline" onClick={() => setCityVisible(true)}>
{cityText === '全部' ? '地区' : cityText} {cityText === '全部' ? '地区' : cityText}
</Button> </Button>
<Button size="small" fill="outline" onClick={() => setFollowVisible(true)}> <Button size="small" fill="outline" onClick={() => setStepVisible(true)}>
{followStatus === '全部' ? '跟进状态' : followStatus} {stepCode == null ? '跟进状态' : STEP_STATUS_TEXT[stepCode]}
</Button>
<Button size="small" fill="outline" onClick={() => setStatusVisible(true)}>
{statusText === '全部' ? '状态' : statusText}
</Button> </Button>
{/*<Button size="small" fill="outline" onClick={() => setStatusVisible(true)}>*/}
{/* {statusText === '全部' ? '状态' : statusText}*/}
{/*</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}>
@@ -481,7 +478,8 @@ export default function CreditCompanyPage() {
{list.map((c, idx) => { {list.map((c, idx) => {
const id = Number(c.id) const id = Number(c.id)
const selected = !!id && selectedIds.includes(id) const selected = !!id && selectedIds.includes(id)
const follow = getFollowStatus(c) const sCode = getStepCode(c)
const sText = getStepText(c)
const ownerName = const ownerName =
// 兼容后端可能直接下发跟进人字段 // 兼容后端可能直接下发跟进人字段
(c as any)?.realName || (c as any)?.realName ||
@@ -536,13 +534,10 @@ export default function CreditCompanyPage() {
)} )}
</View> </View>
<Tag <Tag
type={follow === '无意向' ? 'danger' : follow === '已成交' ? 'success' : 'primary'} type={getStepTagType(sCode)}
onClick={(e: any) => { onClick={(e: any) => e?.stopPropagation?.()}
e?.stopPropagation?.()
setFollowStatusFor(c)
}}
> >
{follow} {sText}
</Tag> </Tag>
</View> </View>
@@ -650,27 +645,31 @@ export default function CreditCompanyPage() {
onClose={() => setCityVisible(false)} onClose={() => setCityVisible(false)}
/> />
<Popup <Popup visible={stepVisible} position="bottom" style={{ height: '45vh' }} onClose={() => setStepVisible(false)}>
visible={followVisible}
position="bottom"
style={{ height: '45vh' }}
onClose={() => setFollowVisible(false)}
>
<View className="p-4"> <View className="p-4">
<View className="flex items-center justify-between mb-3"> <View className="flex items-center justify-between mb-3">
<Text className="text-base font-medium"></Text> <Text className="text-base font-medium"></Text>
<Text className="text-sm text-gray-500" onClick={() => setFollowVisible(false)}> <Text className="text-sm text-gray-500" onClick={() => setStepVisible(false)}>
</Text> </Text>
</View> </View>
<CellGroup> <CellGroup>
{FOLLOW_STATUS_OPTIONS.map(s => ( <Cell
key="ALL"
title={<Text className={stepCode == null ? 'text-blue-600' : ''}></Text>}
onClick={() => {
setStepCode(null)
setStepVisible(false)
reload(true).then()
}}
/>
{STEP_OPTIONS.map(s => (
<Cell <Cell
key={s} key={s.code}
title={<Text className={s === followStatus ? 'text-blue-600' : ''}>{s}</Text>} title={<Text className={s.code === stepCode ? 'text-blue-600' : ''}>{s.text}</Text>}
onClick={() => { onClick={() => {
setFollowStatus(s) setStepCode(s.code)
setFollowVisible(false) setStepVisible(false)
reload(true).then() reload(true).then()
}} }}
/> />