109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import { ref, onMounted } from 'vue'
|
|
import { showToast } from 'vant'
|
|
import { expenseItemApi } from '@/api'
|
|
|
|
export interface ExpenseItem {
|
|
id: string
|
|
parentId: string
|
|
itemName: string
|
|
itemCode: string
|
|
level: number
|
|
sortOrder: number
|
|
status: number | string
|
|
childCount: number
|
|
children?: ExpenseItem[]
|
|
}
|
|
|
|
function normalizeItem(raw: any): ExpenseItem {
|
|
const children = Array.isArray(raw?.children) ? raw.children.map(normalizeItem) : undefined
|
|
return {
|
|
id: String(raw?.id ?? ''),
|
|
parentId: raw?.parentId == null || raw?.parentId === '' ? '0' : String(raw.parentId),
|
|
itemName: raw?.itemName ?? '',
|
|
itemCode: raw?.itemCode ?? '',
|
|
level: Number(raw?.level ?? 0),
|
|
sortOrder: Number(raw?.sortOrder ?? 0),
|
|
status: raw?.status,
|
|
childCount: Number(raw?.childCount ?? children?.length ?? 0),
|
|
children,
|
|
}
|
|
}
|
|
|
|
export function useExpenseItemTree() {
|
|
const expenseTree = ref<ExpenseItem[]>([])
|
|
const loading = ref(false)
|
|
const flatList = ref<ExpenseItem[]>([])
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
try {
|
|
const res: any = await expenseItemApi.tree()
|
|
const tree: ExpenseItem[] = (res.data ?? []).map(normalizeItem)
|
|
expenseTree.value = tree
|
|
flatList.value = flattenTree(tree)
|
|
} catch (e: any) {
|
|
showToast(e?.message || '加载费用项目失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(load)
|
|
|
|
return { expenseTree, flatList, loading, load }
|
|
}
|
|
|
|
function flattenTree(tree: ExpenseItem[]): ExpenseItem[] {
|
|
const result: ExpenseItem[] = []
|
|
for (const node of tree) {
|
|
result.push(node)
|
|
if (node.children?.length) result.push(...flattenTree(node.children))
|
|
}
|
|
return result
|
|
}
|
|
|
|
export function formatExpenseName(
|
|
id: string | number | null | undefined,
|
|
flatList: ExpenseItem[],
|
|
): string {
|
|
if (id == null || id === '') return '-'
|
|
const sid = String(id)
|
|
const parts: string[] = []
|
|
let cur: ExpenseItem | undefined = flatList.find((i) => String(i.id) === sid)
|
|
const guard = new Set<string>()
|
|
while (cur && !guard.has(cur.id)) {
|
|
guard.add(cur.id)
|
|
parts.unshift(cur.itemName)
|
|
const pid = cur.parentId
|
|
cur =
|
|
pid != null && pid !== '' && pid !== '0' ? flatList.find((i) => String(i.id) === pid) : undefined
|
|
}
|
|
return parts.length ? parts.join('/') : '-'
|
|
}
|
|
|
|
/** 从列表/详情行上常见的 camel/snake 字段取费用项 ID */
|
|
export function pickExpenseItemId(row: Record<string, any> | null | undefined): string | null {
|
|
if (!row || typeof row !== 'object') return null
|
|
const raw = row.expenseId ?? row.expense_id ?? row.expenseItemId ?? row.expense_item_id
|
|
if (raw == null || raw === '') return null
|
|
return String(raw)
|
|
}
|
|
|
|
/**
|
|
* 优先用费用树拼出「一级/二级」;无 ID 或树未加载时用接口返回的 expenseName。
|
|
*/
|
|
export function resolveExpenseDisplay(
|
|
row: Record<string, any> | null | undefined,
|
|
flatList: ExpenseItem[],
|
|
): string {
|
|
if (!row) return '-'
|
|
const id = pickExpenseItemId(row)
|
|
const nameFb = row.expenseName ?? row.expense_name
|
|
if (id != null && flatList.length) {
|
|
const p = formatExpenseName(id, flatList)
|
|
if (p && p !== '-') return p
|
|
}
|
|
const fb = nameFb != null ? String(nameFb).trim() : ''
|
|
return fb || '-'
|
|
}
|