This commit is contained in:
zlwu committed 2026-06-30 09:19:06 +08:00
1 parent 1ca2668112
commit 5b3edb1700
278 files changed
+53761 -36931

No files matched your search

+18
View File
@@ -1,5 +1,23 @@
import { fetchCellProductSpecList } from '@/api/admin'
/** 从产出/细胞产品行解析规格展示文案(兼容 specName / specText 等字段) */
export function resolveCellProductSpecDisplay(row: Record<string, any> | null | undefined): string {
if (!row || typeof row !== 'object') return ''
const raw =
row.specName ?? row.specText ?? row.spec_name ?? row.spec_text ?? row.cellProductSpecName
return raw == null ? '' : String(raw).trim()
}
/** 规范化细胞产品行:规格统一写入 specName / specText(文本,非主数据 ID) */
export function normalizeCellProductRow<T extends Record<string, any>>(row: T): T {
const specText = resolveCellProductSpecDisplay(row)
return {
...row,
specName: specText,
specText
}
}
/** 按规格名称精确匹配主数据,返回 specId(可选关联) */
export async function resolveCellProductSpecIdByName(
specName: string
+1 -1
View File
@@ -1,4 +1,4 @@
/** 细胞产品类型是否需要代次(仅 MSC) */
/** 细胞产品类型是否需要代次(仅 MSC;NK、存储等类型不展示代次) */
export function cellProductTypeNeedsPassage(
productTypeName?: string | null,
productTypeCode?: string | null
@@ -0,0 +1,7 @@
/** 预计细胞数量:界面与库统一单位为「千万」 */
export const EXPECTED_CELL_COUNT_LABEL = '预计细胞数量(千万)'
export function formatExpectedCellCountDisplay(value: number | null | undefined): string {
if (value == null || Number.isNaN(Number(value))) return '—'
return String(value)
}
+3 -1
View File
@@ -12,7 +12,9 @@ import { useMenuStore } from '@/store/modules/menu'
import type { AppRouteRecord } from '@/types/router'
import { PRODUCTION_WORK_ORDER_VISIBLE_TABS } from '@/utils/production-work-order-tabs'
const VISIBLE_TAB_KEYS = PRODUCTION_WORK_ORDER_VISIBLE_TABS.map((tab) => tab.key)
const VISIBLE_TAB_KEYS = PRODUCTION_WORK_ORDER_VISIBLE_TABS
.filter((tab) => tab.key !== 'all')
.map((tab) => tab.key)
/** 解析菜单项对应的生产角标 menuCode(兼容后端菜单未带 menuCode) */
export function resolveProductionMenuBadgeCode(item: AppRouteRecord): string | undefined {
@@ -6,19 +6,26 @@ import {
resolveTabKeyFromWorkOrder,
tabUsesInfusionTimeFilter,
getProductionWorkOrderTab,
toWorkOrderListQuery
toWorkOrderListQuery,
isAllProductionWorkOrderTab
} from '@/utils/production-work-order-tabs'
import {
toAllTabListQuery,
workOrderMatchesCategory
} from '@/utils/production-work-order-category'
import { matchesWorkOrderStatusFilter } from '@/utils/status-tag-registry'
export type ProductionTaskMineFilters = {
keyword?: string
taskStatus?: string
productionCategory?: string
tabKey: ProductionWorkOrderTabKey
}
export type MineWorkOrderSearchFilters = {
keyword?: string
workOrderStatus?: string | string[]
productionCategory?: string
createTimeStart?: string
createTimeEnd?: string
infusionTimeStart?: string
@@ -143,7 +150,9 @@ async function fetchMineTasksByScope(
filters: ProductionTaskMineFilters
): Promise<ProductionTaskRow[]> {
const tab = getProductionWorkOrderTab(filters.tabKey)!
const listQuery = toWorkOrderListQuery(tab)
const listQuery = isAllProductionWorkOrderTab(filters.tabKey)
? toAllTabListQuery(filters.productionCategory || '')
: toWorkOrderListQuery(tab)
const pageSize = MINE_TASK_FETCH_PAGE_SIZE
let page = 1
const all: ProductionTaskRow[] = []
@@ -223,15 +232,26 @@ export function filterMineEntriesByWorkOrderSearch(
const tab = getProductionWorkOrderTab(tabKey)!
return entries.filter((entry) => {
const row = entry.workOrder
if (resolveTabKeyFromWorkOrder(row) !== tabKey) return false
if (entry.tabKey !== tabKey) return false
if (!isAllProductionWorkOrderTab(tabKey) && resolveTabKeyFromWorkOrder(row) !== tabKey) {
return false
}
if (
isAllProductionWorkOrderTab(tabKey) &&
!workOrderMatchesCategory(row, filters.productionCategory || '')
) {
return false
}
if (!isAllProductionWorkOrderTab(tabKey) && entry.tabKey !== tabKey) return false
if (!matchesWorkOrderStatusFilter(row.workOrderStatus, filters.workOrderStatus)) return false
if (!keywordMatchesWorkOrder(row, filters.keyword || '', tabKey)) return false
if (tabUsesInfusionTimeFilter(tab)) {
if (!dateInRange(row.infusionTime, filters.infusionTimeStart, filters.infusionTimeEnd)) {
const keywordTabKey = isAllProductionWorkOrderTab(tabKey)
? resolveTabKeyFromWorkOrder(row)
: tabKey
if (!keywordMatchesWorkOrder(row, filters.keyword || '', keywordTabKey)) return false
if (isAllProductionWorkOrderTab(tabKey) || !tabUsesInfusionTimeFilter(tab)) {
if (!dateInRange(row.createTime, filters.createTimeStart, filters.createTimeEnd)) {
return false
}
} else if (!dateInRange(row.createTime, filters.createTimeStart, filters.createTimeEnd)) {
} else if (!dateInRange(row.infusionTime, filters.infusionTimeStart, filters.infusionTimeEnd)) {
return false
}
return true
@@ -8,6 +8,7 @@ import {
} from '@/utils/status-tag-registry'
import type { TagType } from '@/utils/status-tag-registry'
import type { ProductionWorkOrderTabKey } from '@/utils/production-work-order-tabs'
import { resolveTabKeyFromWorkOrder } from '@/utils/production-work-order-tabs'
import { formatMoneyDisplay } from '@/utils/money-format'
export type CardMetaFieldKind = 'text' | 'tag'
@@ -23,6 +24,7 @@ export type CardMetaField = {
export type ProductionWorkOrderCardMetaConfig = {
tabKey: ProductionWorkOrderTabKey
showCategoryTag: boolean
showProtocolFeeSummary: boolean
showOutboundFeeSummary: boolean
/** 卡片展示关联申请单/输注计划的细胞产品明细 */
@@ -181,8 +183,22 @@ const temporaryFields: CardMetaField[] = [
]
const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCardMetaConfig> = {
all: {
tabKey: 'all',
showCategoryTag: true,
showProtocolFeeSummary: false,
showOutboundFeeSummary: false,
showCellProducts: false,
metaFields: [],
allowDispatchQc: false,
allowSampleReception: false,
allowDispatchComplete: false,
allowEditRemark: false,
allowDelete: false
},
inbound_cell_storage: {
tabKey: 'inbound_cell_storage',
showCategoryTag: false,
showProtocolFeeSummary: true,
showOutboundFeeSummary: false,
showCellProducts: false,
@@ -195,6 +211,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
},
inbound_research_donation: {
tabKey: 'inbound_research_donation',
showCategoryTag: false,
showProtocolFeeSummary: false,
showOutboundFeeSummary: false,
showCellProducts: false,
@@ -207,6 +224,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
},
outbound_pilot_service: {
tabKey: 'outbound_pilot_service',
showCategoryTag: false,
showProtocolFeeSummary: false,
showOutboundFeeSummary: false,
showCellProducts: true,
@@ -219,6 +237,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
},
outbound_basic_experiment: {
tabKey: 'outbound_basic_experiment',
showCategoryTag: false,
showProtocolFeeSummary: false,
showOutboundFeeSummary: true,
showCellProducts: true,
@@ -231,6 +250,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
},
outbound_clinical_application: {
tabKey: 'outbound_clinical_application',
showCategoryTag: false,
showProtocolFeeSummary: false,
showOutboundFeeSummary: true,
showCellProducts: false,
@@ -243,6 +263,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
},
temporary: {
tabKey: 'temporary',
showCategoryTag: false,
showProtocolFeeSummary: false,
showOutboundFeeSummary: false,
showCellProducts: false,
@@ -266,6 +287,20 @@ export function getCardMetaConfig(tabKey: ProductionWorkOrderTabKey): Production
return CARD_META_BY_TAB[tabKey]
}
export function resolveCardMetaForRow(
row: ProductionWorkOrderItem,
tabKey: ProductionWorkOrderTabKey
): ProductionWorkOrderCardMetaConfig {
if (tabKey !== 'all') return getCardMetaConfig(tabKey)
const rowTabKey = resolveTabKeyFromWorkOrder(row)
const rowMeta = getCardMetaConfig(rowTabKey)
return {
...rowMeta,
tabKey: 'all',
showCategoryTag: true
}
}
export function getCardMetaConfigForLegacyTab(
activeTypeTab: 'inbound' | 'outbound' | 'temporary'
): ProductionWorkOrderCardMetaConfig {
@@ -0,0 +1,103 @@
import type { ProductionWorkOrderItem } from '@/api/production-work-order'
import type { TagType } from '@/utils/status-tag-registry'
import {
resolveTabKeyFromWorkOrder,
type ProductionWorkOrderTabKey,
getProductionWorkOrderTab,
PRODUCTION_WORK_ORDER_VISIBLE_TABS
} from '@/utils/production-work-order-tabs'
/** 「所有」Tab 内生产类型子筛选(不含 all / hidden clinical) */
export type ProductionWorkOrderCategoryKey = Exclude<
ProductionWorkOrderTabKey,
'all' | 'outbound_clinical_application'
>
export const PRODUCTION_WORK_ORDER_CATEGORY_OPTIONS: {
key: ProductionWorkOrderCategoryKey
label: string
}[] = PRODUCTION_WORK_ORDER_VISIBLE_TABS.filter((t) => t.key !== 'all').map((t) => ({
key: t.key as ProductionWorkOrderCategoryKey,
label: t.label.replace(/生产$/, '')
}))
const CATEGORY_LABELS: Record<ProductionWorkOrderCategoryKey, string> = {
inbound_cell_storage: '细胞存储',
inbound_research_donation: '科研捐赠',
outbound_pilot_service: '中试服务',
outbound_basic_experiment: '基础实验',
temporary: '临时'
}
const CATEGORY_TAG_TYPES: Record<ProductionWorkOrderCategoryKey, TagType> = {
inbound_cell_storage: 'primary',
inbound_research_donation: 'primary',
outbound_pilot_service: 'info',
outbound_basic_experiment: 'info',
temporary: 'warning'
}
export function resolveWorkOrderCategoryKey(row: {
workOrderType?: string
receptionSource?: string
bizType?: string
}): ProductionWorkOrderCategoryKey {
const tabKey = resolveTabKeyFromWorkOrder(row)
if (tabKey === 'outbound_clinical_application') return 'outbound_pilot_service'
return tabKey as ProductionWorkOrderCategoryKey
}
export function productionWorkOrderCategoryLabel(row: ProductionWorkOrderItem): string {
return CATEGORY_LABELS[resolveWorkOrderCategoryKey(row)] || '—'
}
export function productionWorkOrderCategoryTagType(row: ProductionWorkOrderItem): TagType {
return CATEGORY_TAG_TYPES[resolveWorkOrderCategoryKey(row)] || 'info'
}
export function toAllTabListQuery(categoryKey: string) {
const key = (categoryKey || '').trim()
if (!key) {
return {
workOrderType: undefined as string | undefined,
receptionSource: undefined as string | undefined,
bizType: undefined as string | undefined
}
}
const tab = getProductionWorkOrderTab(key)
if (!tab || tab.key === 'all') {
return {
workOrderType: undefined,
receptionSource: undefined,
bizType: undefined
}
}
return {
workOrderType: tab.workOrderType,
receptionSource: tab.receptionSource,
bizType: tab.bizType
}
}
export function workOrderMatchesCategory(
row: ProductionWorkOrderItem,
categoryKey: string
): boolean {
const key = (categoryKey || '').trim()
if (!key) return true
return resolveWorkOrderCategoryKey(row) === key
}
const INBOUND_LAB_STORAGE_HEADER_CATEGORIES = new Set<ProductionWorkOrderCategoryKey>([
'inbound_cell_storage',
'inbound_research_donation'
])
/** 任务执行/详情弹窗顶部:细胞存储、科研捐赠入库单展示实验室入库编码 */
export function shouldShowTaskHeaderLabStorageCode(row: {
workOrderType?: string
receptionSource?: string
bizType?: string
}): boolean {
return INBOUND_LAB_STORAGE_HEADER_CATEGORIES.has(resolveWorkOrderCategoryKey(row))
}
@@ -1,6 +1,7 @@
import type { ProductionWorkOrderItem } from '@/api/production-work-order'
export type ProductionWorkOrderTabKey =
| 'all'
| 'inbound_cell_storage'
| 'inbound_research_donation'
| 'outbound_pilot_service'
@@ -8,7 +9,7 @@ export type ProductionWorkOrderTabKey =
| 'outbound_clinical_application'
| 'temporary'
export type ProductionWorkOrderTabGroup = 'inbound' | 'outbound' | 'temporary'
export type ProductionWorkOrderTabGroup = 'all' | 'inbound' | 'outbound' | 'temporary'
export type ProductionWorkOrderTabConfig = {
key: ProductionWorkOrderTabKey
@@ -23,6 +24,13 @@ export type ProductionWorkOrderTabConfig = {
}
export const PRODUCTION_WORK_ORDER_TABS: ProductionWorkOrderTabConfig[] = [
{
key: 'all',
label: '所有',
group: 'all',
workOrderType: 'inbound',
listLayout: 'inbound-grouped'
},
{
key: 'inbound_cell_storage',
label: '细胞存储生产',
@@ -76,7 +84,7 @@ export const PRODUCTION_WORK_ORDER_TABS: ProductionWorkOrderTabConfig[] = [
/** 生产工单 / 我的生产任务 Tab 栏展示项(不含 hidden) */
export const PRODUCTION_WORK_ORDER_VISIBLE_TABS = PRODUCTION_WORK_ORDER_TABS.filter((t) => !t.hidden)
export const DEFAULT_PRODUCTION_WORK_ORDER_TAB: ProductionWorkOrderTabKey = 'inbound_cell_storage'
export const DEFAULT_PRODUCTION_WORK_ORDER_TAB: ProductionWorkOrderTabKey = 'all'
export function getProductionWorkOrderTab(key: string): ProductionWorkOrderTabConfig | undefined {
return PRODUCTION_WORK_ORDER_TABS.find((t) => t.key === key)
@@ -93,6 +101,13 @@ export function parseProductionWorkOrderTabKey(raw?: string | null): ProductionW
}
export function toWorkOrderListQuery(tab: ProductionWorkOrderTabConfig) {
if (tab.key === 'all') {
return {
workOrderType: undefined as string | undefined,
receptionSource: undefined as string | undefined,
bizType: undefined as string | undefined
}
}
return {
workOrderType: tab.workOrderType,
receptionSource: tab.receptionSource,
@@ -123,6 +138,7 @@ export function tabUsesInfusionTimeFilter(tab: ProductionWorkOrderTabConfig): bo
}
export function keywordPlaceholderForTab(tab: ProductionWorkOrderTabConfig): string {
if (tab.key === 'all') return '工单号 / 样本编码 / 申请单号 / 产品名称等'
if (tab.key === 'temporary') return '工单号'
if (tab.key === 'outbound_basic_experiment') {
return '工单号/申请单号/课题名称/客户'
@@ -132,12 +148,16 @@ export function keywordPlaceholderForTab(tab: ProductionWorkOrderTabConfig): str
}
export function filterTimeLabelForTab(tab: ProductionWorkOrderTabConfig): string {
if (tab.key === 'all') return '创建时间'
if (tab.key === 'outbound_basic_experiment') return '交付时间'
if (tab.group === 'outbound') return '输注时间'
return '创建时间'
}
export function filterTimeHintForTab(tab: ProductionWorkOrderTabConfig): string {
if (tab.key === 'all') {
return '出库工单按输注/交付时间筛选请进入对应分类 Tab。'
}
if (tab.group === 'outbound') {
return tab.key === 'outbound_basic_experiment'
? '支持按交付时间筛选。'
@@ -150,5 +170,18 @@ export function workOrderMatchesTab(
row: ProductionWorkOrderItem,
tabKey: ProductionWorkOrderTabKey
): boolean {
if (tabKey === 'all') return true
return resolveTabKeyFromWorkOrder(row) === tabKey
}
export function isAllProductionWorkOrderTab(tabKey: ProductionWorkOrderTabKey): boolean {
return tabKey === 'all'
}
/** 列表 Tab 为「所有」时,按工单解析其分类 Tab Key */
export function resolveEffectiveRowTabKey(
listTabKey: ProductionWorkOrderTabKey,
row: ProductionWorkOrderItem
): ProductionWorkOrderTabKey {
return isAllProductionWorkOrderTab(listTabKey) ? resolveTabKeyFromWorkOrder(row) : listTabKey
}
@@ -4,10 +4,15 @@ export {
payStatusLabel,
payStatusTagType,
payModeLabel,
payModeTagType,
transferTypeLabel,
transferTypeTagType
payModeTagType
} from './status-tag-registry'
export {
loadTransferTypeDict,
getTransferTypeOptions,
TRANSFER_TYPE_DICT,
resolveTransferTypeLabel as transferTypeLabel,
resolveTransferTypeTagType as transferTypeTagType
} from './transfer-type-dict'
export function formatPaymentDueDate(d: unknown) {
const s = String(d || '')
@@ -0,0 +1,141 @@
export type SampleReceptionSourceType = 'cell_storage' | 'research_donation'
export const SAMPLE_TYPE_PERINATAL_HSC = '围产期造血干细胞'
export const SAMPLE_TYPE_UC_MSC = '脐带间充质干细胞'
export type ProductionReceptionFieldKey =
| 'originalBloodWeightG'
| 'hesVolumeMl'
| 'bloodType'
| 'infectiousFourTestResult'
| 'enrichedBloodWeightG'
| 'cryopreservationVolumeMl'
| 'inoculationAmount'
| 'cryopreservationTime'
| 'storageTotalTubes'
| 'availableCellCount1e7'
| 'originalBloodVolumeMl'
| 'nucleatedCellCount1e8'
export type ProductionReceptionFieldDef = {
key: ProductionReceptionFieldKey
label: string
kind: 'number' | 'text' | 'datetime' | 'select' | 'integer'
selectOptions?: Array<{ label: string; value: string }>
}
const INFECTIOUS_OPTIONS = [
{ label: '正常', value: 'zc' },
{ label: '不正常', value: 'bzc' }
]
const FIELD_DEFS: Record<ProductionReceptionFieldKey, ProductionReceptionFieldDef> = {
originalBloodWeightG: { key: 'originalBloodWeightG', label: '原血重量/g', kind: 'number' },
hesVolumeMl: { key: 'hesVolumeMl', label: 'HES用量/ml', kind: 'number' },
bloodType: { key: 'bloodType', label: '血型', kind: 'text' },
infectiousFourTestResult: {
key: 'infectiousFourTestResult',
label: '传染四项检测(检测卡)',
kind: 'select',
selectOptions: INFECTIOUS_OPTIONS
},
enrichedBloodWeightG: { key: 'enrichedBloodWeightG', label: '富集后血量/g', kind: 'number' },
cryopreservationVolumeMl: {
key: 'cryopreservationVolumeMl',
label: '冻存液用量/ml',
kind: 'number'
},
inoculationAmount: { key: 'inoculationAmount', label: '接种量', kind: 'number' },
cryopreservationTime: { key: 'cryopreservationTime', label: '冻存时间', kind: 'datetime' },
storageTotalTubes: { key: 'storageTotalTubes', label: '储存总量(管)', kind: 'integer' },
availableCellCount1e7: {
key: 'availableCellCount1e7',
label: '可提供细胞数量/*10E7',
kind: 'number'
},
originalBloodVolumeMl: { key: 'originalBloodVolumeMl', label: '原血体积/ml', kind: 'number' },
nucleatedCellCount1e8: {
key: 'nucleatedCellCount1e8',
label: '有核细胞数/10E8',
kind: 'number'
}
}
const PROFILE_KEYS: Record<string, ProductionReceptionFieldKey[]> = {
'cell_storage|围产期造血干细胞': [
'originalBloodWeightG',
'hesVolumeMl',
'bloodType',
'infectiousFourTestResult',
'enrichedBloodWeightG',
'cryopreservationVolumeMl'
],
'cell_storage|脐带间充质干细胞': [
'inoculationAmount',
'cryopreservationTime',
'storageTotalTubes',
'availableCellCount1e7'
],
'research_donation|脐带间充质干细胞': ['inoculationAmount', 'cryopreservationTime'],
'research_donation|围产期造血干细胞': ['originalBloodVolumeMl', 'nucleatedCellCount1e8']
}
export function resolveProductionReceptionFields(
sourceType: string,
sampleTypeName: string
): ProductionReceptionFieldDef[] {
const key = `${sourceType === 'research_donation' ? 'research_donation' : 'cell_storage'}|${String(sampleTypeName || '').trim()}`
const keys = PROFILE_KEYS[key] || []
return keys.map((k) => FIELD_DEFS[k])
}
export function infectiousFourTestLabel(value: string) {
return INFECTIOUS_OPTIONS.find((o) => o.value === value)?.label || value || '—'
}
export type ProductionReceptionRecord = {
id?: string
receptionId?: string
handlerUserId?: string
handlerName?: string
processTime?: string
originalBloodWeightG?: number
hesVolumeMl?: number
bloodType?: string
infectiousFourTestResult?: string
infectiousFourTestResultLabel?: string
enrichedBloodWeightG?: number
cryopreservationVolumeMl?: number
inoculationAmount?: number
cryopreservationTime?: string
storageTotalTubes?: number
availableCellCount1e7?: number
originalBloodVolumeMl?: number
nucleatedCellCount1e8?: number
remark?: string
}
export type QcReceptionRecord = {
receptionTime?: string
handlerName?: string
receiveTemperature?: string
acceptConclusion?: string
acceptConclusionLabel?: string
labStorageCode?: string
checkResults?: Array<{ itemName?: string; checked?: boolean }>
remark?: string
receiveAttachmentFiles?: Array<Record<string, unknown>>
}
export function formatProductionFieldValue(
def: ProductionReceptionFieldDef,
record: ProductionReceptionRecord | null | undefined
) {
if (!record) return '—'
const raw = record[def.key as keyof ProductionReceptionRecord]
if (raw === null || raw === undefined || raw === '') return '—'
if (def.key === 'infectiousFourTestResult') {
return infectiousFourTestLabel(String(raw))
}
return String(raw)
}
@@ -0,0 +1,71 @@
import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
import { acceptConclusionLabel } from '@/utils/qc-labels'
import {
formatProductionFieldValue,
resolveProductionReceptionFields,
type ProductionReceptionRecord
} from '@/utils/sample-production-reception-fields'
export function buildQcReceptionViewFields(
r: Record<string, unknown>,
d?: Record<string, unknown>
): ViewSection['fields'] {
const accept = String(r.acceptConclusion || d?.acceptConclusion || '')
return [
{ label: '接收时间', value: String(r.receptionTime || d?.receptionTime || '—') },
{ label: '接收经手人', value: String(r.handlerName || d?.handlerName || '—') },
...(accept === 'js'
? [
{
label: '实验室入库编码',
value: String(r.labStorageCode || d?.labStorageCode || '—')
}
]
: []),
{
label: '接收温度(℃)',
value: r.receiveTemperature ? `${r.receiveTemperature}℃` : '—'
},
{
label: '结论',
value:
String(r.acceptConclusionLabel || '') ||
acceptConclusionLabel(accept) ||
'—',
tagType: accept === 'js' ? 'success' : accept === 'bj' ? 'danger' : 'info'
},
{
label: '检查项',
checkItems: (r.checkResults as ViewSection['fields'][0]['checkItems']) ?? [],
span: true
},
{ label: '备注', value: String(r.remark || '—'), span: true }
]
}
export function buildProductionReceptionViewSection(
productionReception: ProductionReceptionRecord | null | undefined,
sourceType: string,
sampleTypeName: string
): ViewSection {
if (!productionReception) {
return {
title: '生产接收',
fields: [{ label: '状态', value: '生产尚未登记', span: true }]
}
}
const fields: ViewSection['fields'] = [
{ label: '处理时间', value: productionReception.processTime || '—' },
{ label: '处理人', value: productionReception.handlerName || '—' }
]
for (const def of resolveProductionReceptionFields(sourceType, sampleTypeName)) {
fields.push({
label: def.label,
value: formatProductionFieldValue(def, productionReception)
})
}
if (productionReception.remark) {
fields.push({ label: '备注', value: productionReception.remark, span: true })
}
return { title: '生产接收', fields }
}
+2 -1
View File
@@ -95,7 +95,8 @@ export const transferType: StatusRegistry = {
wxzz: { label: '微信转账', tagType: 'success' },
zzbzz: { label: '支付宝转账', tagType: 'primary' },
yhkzz: { label: '银行卡转账', tagType: 'info' },
xj: { label: '现金', tagType: 'warning' }
xj: { label: '现金', tagType: 'warning' },
lszz: { label: '乐刷', tagType: 'primary' }
}
export const transferTypeLabel = createLabelGetter(transferType)
export const transferTypeTagType = createTagTypeGetter(transferType)
+66 -25
View File
@@ -13,6 +13,50 @@ export type LoadStorageSlotSelectOptions = {
includeSlotIds?: string[]
}
/** 远程检索防抖(毫秒):用户停顿后再请求 */
export const STORAGE_SLOT_REMOTE_SEARCH_DEBOUNCE_MS = 1000
/** 储物格下拉每次返回条数 */
export const STORAGE_SLOT_SELECT_PAGE_SIZE = 20
/** 远程检索序号:丢弃过期的空关键词/旧输入响应,避免覆盖最新检索结果 */
let storageSlotRemoteSearchSeq = 0
let lastStorageSlotSearchQuery = ''
export function getLastStorageSlotSearchQuery(): string {
return lastStorageSlotSearchQuery
}
export async function remoteSearchStorageSlotSelectOptions(
query: string,
opts?: LoadStorageSlotSelectOptions
): Promise<StorageSlotSelectOption[] | null> {
const trimmed = (query ?? '').trim()
lastStorageSlotSearchQuery = trimmed
const seq = ++storageSlotRemoteSearchSeq
const includeSlotIds = (opts?.includeSlotIds || [])
.map((id) => String(id).trim())
.filter(Boolean)
.join(',')
try {
const res = await fetchStorageSlotSelectOptions({
keyword: trimmed || undefined,
availableOnly: opts?.availableOnly ? true : undefined,
includeSlotIds: includeSlotIds || undefined,
page: 1,
pageSize: STORAGE_SLOT_SELECT_PAGE_SIZE
})
if (seq !== storageSlotRemoteSearchSeq) return null
return (res?.list || []).map((item) => ({
id: String(item.id || ''),
name: String(item.name || item.fullSlotCode || item.slotCode || item.id || '')
}))
} catch {
if (seq !== storageSlotRemoteSearchSeq) return null
return []
}
}
export function getStorageFacilityOrderNum(item: Record<string, unknown>): number {
const raw = item?.orderNum ?? item?.order_num ?? item?.sortOrder ?? item?.sort_order ?? 0
const parsed = Number(raw)
@@ -246,31 +290,6 @@ export async function loadStorageSlotSelectOptions(
return options
}
export async function remoteSearchStorageSlotSelectOptions(
query: string,
opts?: LoadStorageSlotSelectOptions
): Promise<StorageSlotSelectOption[]> {
const includeSlotIds = (opts?.includeSlotIds || [])
.map((id) => String(id).trim())
.filter(Boolean)
.join(',')
try {
const res = await fetchStorageSlotSelectOptions({
keyword: query?.trim() || undefined,
availableOnly: opts?.availableOnly ? true : undefined,
includeSlotIds: includeSlotIds || undefined,
page: 1,
pageSize: 100
})
return (res?.list || []).map((item) => ({
id: String(item.id || ''),
name: String(item.name || item.fullSlotCode || item.slotCode || item.id || '')
}))
} catch {
return []
}
}
export function ensureStorageSlotsInOptions(
options: StorageSlotSelectOption[],
stubs: Array<{ id?: string; name?: string }>
@@ -317,3 +336,25 @@ export function validateUniqueStorageSlotIds(
}
return true
}
export type OutputStorageSlotRow = {
storageSlotId?: string | null
storageSlotName?: string | null
productStatus?: string | null
}
/** 产出是否尚未分配储物格(已作废产出不计入) */
export function isOutputMissingStorageSlot(row: OutputStorageSlotRow): boolean {
if (String(row.productStatus ?? '').trim() === 'voided') return false
const slotId = String(row.storageSlotId ?? '').trim()
const slotName = String(row.storageSlotName ?? '').trim()
return !slotId && !slotName
}
/** 统计未分配储物格的产出份数 */
export function countOutputsMissingStorageSlot(
items: OutputStorageSlotRow[] | null | undefined
): number {
if (!items?.length) return 0
return items.filter(isOutputMissingStorageSlot).length
}
+53
View File
@@ -0,0 +1,53 @@
import { fetchDictDataOptions } from '@/api/admin'
import {
transferTypeLabel as transferTypeFallbackLabel,
transferTypeTagType as transferTypeTagTypeFallback
} from './status-tag-registry'
/** 转账方式字典类型编码(与 sys_dict_type.dict_type 一致) */
export const TRANSFER_TYPE_DICT = 'zzfs'
let labelMap: Record<string, string> = {}
let optionsCache: Api.Dict.DictDataOption[] = []
let loadPromise: Promise<void> | null = null
export function getTransferTypeOptions(): Api.Dict.DictDataOption[] {
return optionsCache
}
export async function loadTransferTypeDict(force = false): Promise<void> {
if (!force && optionsCache.length) return
if (!force && loadPromise) return loadPromise
loadPromise = (async () => {
try {
const res = await fetchDictDataOptions(TRANSFER_TYPE_DICT)
optionsCache = res || []
labelMap = Object.fromEntries(optionsCache.map((o) => [o.value, o.label]))
} catch (e) {
console.error('加载转账方式字典失败:', e)
if (!optionsCache.length) {
optionsCache = []
labelMap = {}
}
} finally {
loadPromise = null
}
})()
return loadPromise
}
export function resolveTransferTypeLabel(value: string): string {
const code = String(value || '').trim()
if (!code) return '—'
return labelMap[code] || transferTypeFallbackLabel(code) || code
}
export function resolveTransferTypeTagType(value: string): string {
return transferTypeTagTypeFallback(String(value || ''))
}
/** @deprecated 请使用 resolveTransferTypeLabel;保留别名便于渐进迁移 */
export const transferTypeLabel = resolveTransferTypeLabel
/** @deprecated 请使用 resolveTransferTypeTagType */
export const transferTypeTagType = resolveTransferTypeTagType