Files
order_site/apps/frontend/src/pages/admin/panels/shared.tsx
T

253 lines
7.8 KiB
TypeScript

import { Descriptions, Space, Tag, Typography } from 'antd'
import ImagePreviewList from '@/components/files/ImagePreviewList'
import { DeadlineCountdown, formatTimeoutPolicyLabel } from '@/components/DeadlineCountdown'
import type { CollectField, UploadedFile, WorkOrder } from '@/types/worker-platform'
export { DeadlineCountdown, formatTimeoutPolicyLabel }
type MappedRequirementField = {
key: string
label: string
required: boolean
type: 'text' | 'select'
options: string[]
}
export function getRequirementFields(order: WorkOrder | null): CollectField[] {
if (!order) return []
const rawFields = Array.isArray(order.requirement?.fields) ? order.requirement.fields : []
return rawFields
.map((item) => {
const source = asRecord(item)
const key = String(source.key || '').trim()
if (!key) return null
return {
key,
label: String(source.label || key).trim(),
required: source.required !== false,
type: source.type === 'select' ? ('select' as const) : ('text' as const),
options: Array.isArray(source.options)
? source.options.map((option) => String(option || '').trim()).filter(Boolean)
: [],
}
})
.filter((item): item is MappedRequirementField => Boolean(item))
}
export type DetailFieldItem = {
key: string
label: string
required: boolean
value: string
}
export function getMaterialFields(order: WorkOrder): Record<string, string> {
const material = asRecord(order.material)
const collect = asRecord(material.collect)
const fields = asRecord(collect.fields || material.fields)
return Object.fromEntries(
Object.entries(fields).map(([key, value]) => [key, String(value || '')]),
)
}
export function getMaterialDetailItems(order: WorkOrder): DetailFieldItem[] {
const values = getMaterialFields(order)
const requiredFields = getRequirementFields(order)
const usedKeys = new Set<string>()
const items = requiredFields.map((field) => {
usedKeys.add(field.key)
return {
key: field.key,
label: field.label,
required: field.required,
value: String(values[field.key] || ''),
}
})
Object.entries(values).forEach(([key, value]) => {
if (usedKeys.has(key)) return
items.push({
key,
label: key,
required: false,
value: String(value || ''),
})
})
return items
}
export function renderDetailFieldValues(items: DetailFieldItem[], emptyText: string) {
if (items.length === 0) {
return <Typography.Text type="secondary">{emptyText}</Typography.Text>
}
return (
<Descriptions column={1} bordered size="small">
{items.map((item) => (
<Descriptions.Item
key={item.key}
label={
<Space size={6} wrap>
<span>{item.label}</span>
{item.required ? <Tag color="orange">必填</Tag> : null}
</Space>
}
>
<span className="worker-order-detail-value">
{String(item.value || '').trim() || '-'}
</span>
</Descriptions.Item>
))}
</Descriptions>
)
}
export function renderRequirementFieldValues(order: WorkOrder) {
const fields = getRequirementFields(order)
if (fields.length === 0) {
return <Typography.Text type="secondary">当前工单未配置资料要求</Typography.Text>
}
return (
<Descriptions column={1} bordered size="small">
{fields.map((field) => (
<Descriptions.Item key={field.key} label={field.label}>
<Space size={6} wrap>
<Typography.Text code>{field.key}</Typography.Text>
<Tag color={field.required ? 'orange' : 'default'}>
{field.required ? '必填' : '选填'}
</Tag>
</Space>
</Descriptions.Item>
))}
</Descriptions>
)
}
export function renderMaterialSummary(order: WorkOrder) {
const fieldLabelMap = new Map(
getRequirementFields(order).map((field) => [field.key, field.label]),
)
const fields = getMaterialFields(order)
const entries = Object.entries(fields).filter(([, value]) => String(value || '').trim())
const screenshots = getMaterialScreenshots(order)
if (entries.length === 0 && screenshots.length === 0) {
return <Typography.Text type="secondary">-</Typography.Text>
}
return (
<div className="cell-stack">
{entries.map(([key, value]) => (
<Typography.Text key={key} style={{ fontSize: 12 }} ellipsis>
{fieldLabelMap.get(key) || key}: {value}
</Typography.Text>
))}
{screenshots.length > 0 ? <ImagePreviewList files={screenshots} size={36} /> : null}
</div>
)
}
export function getAcceptanceFiles(row: WorkOrder): UploadedFile[] {
return Array.isArray(row.acceptance?.files) ? row.acceptance.files : []
}
export function getAcceptanceImageUrls(row: WorkOrder): string[] {
return Array.isArray(row.acceptance?.imageUrls)
? row.acceptance.imageUrls.map((item) => String(item || '').trim()).filter(Boolean)
: []
}
export function getAcceptanceSubmittedAt(order: WorkOrder): string | null {
const acceptanceSubmittedAt =
typeof order.acceptance?.submittedAt === 'string' ? order.acceptance.submittedAt : null
return order.submittedAt || acceptanceSubmittedAt || null
}
export function getMaterialScreenshots(order: WorkOrder): UploadedFile[] {
const material = asRecord(order.material)
const collect = asRecord(material.collect)
const screenshots = Array.isArray(collect.screenshots) ? collect.screenshots : []
return screenshots
.map((item) => {
const file = asRecord(item)
const url = String(file.url || '').trim()
if (!url) return null
return {
objectKey: String(file.objectKey || file.object_key || '').trim(),
filename: String(file.originalFilename || file.filename || '').trim(),
url,
mediumUrl: String(file.mediumUrl || file.medium_url || '').trim(),
thumbnailUrl: String(file.thumbnailUrl || file.thumbnail_url || '').trim(),
} as UploadedFile
})
.filter((item): item is UploadedFile => Boolean(item))
}
export function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === 'object' && !Array.isArray(value)
? (value as Record<string, unknown>)
: {}
}
export function formatMoney(value: number | undefined) {
return `¥${((Number(value || 0) || 0) / 100).toFixed(2)}`
}
export function formatStatus(status: string) {
const labels: Record<string, string> = {
pending_material: '待完善',
unassigned: '未分配',
open: '待抢单',
in_progress: '代练中',
pending_acceptance: '待验收',
problem: '问题单',
vip_evidence_pending: '待补图',
accepted: '已验收',
cancelled: '已取消',
}
return labels[status] || status
}
export function resolveStatusColor(status: string) {
const colors: Record<string, string> = {
pending_material: 'orange',
unassigned: 'cyan',
open: 'blue',
in_progress: 'geekblue',
pending_acceptance: 'purple',
problem: 'red',
vip_evidence_pending: 'gold',
accepted: 'green',
cancelled: 'default',
}
return colors[status] || 'default'
}
export function formatWorkerStatus(status: string) {
const labels: Record<string, string> = {
pending_review: '待处理',
active: '已启用',
rejected: '已冻结',
disabled: '已停用',
}
return labels[status] || status
}
export function formatSharingShareStatus(status: string) {
const labels: Record<string, string> = {
joined: '已参与',
submitted: '已提交验收',
accepted: '已验收',
cancelled: '已取消',
}
return labels[status] || status
}
export function resolveSharingShareStatusColor(status: string) {
if (status === 'accepted') return 'green'
if (status === 'submitted') return 'gold'
if (status === 'cancelled') return 'default'
return 'purple'
}