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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
<Cell
|
||||||
key={s}
|
key="ALL"
|
||||||
title={<Text className={s === followStatus ? 'text-blue-600' : ''}>{s}</Text>}
|
title={<Text className={stepCode == null ? 'text-blue-600' : ''}>全部</Text>}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFollowStatus(s)
|
setStepCode(null)
|
||||||
setFollowVisible(false)
|
setStepVisible(false)
|
||||||
|
reload(true).then()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{STEP_OPTIONS.map(s => (
|
||||||
|
<Cell
|
||||||
|
key={s.code}
|
||||||
|
title={<Text className={s.code === stepCode ? 'text-blue-600' : ''}>{s.text}</Text>}
|
||||||
|
onClick={() => {
|
||||||
|
setStepCode(s.code)
|
||||||
|
setStepVisible(false)
|
||||||
reload(true).then()
|
reload(true).then()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user