This commit is contained in:
zlwu committed 2026-05-23 11:41:57 +08:00
1 parent 19de2040d0
commit 3779de12ce
78 files changed
+23068 -13824

No files matched your search

+165
View File
@@ -0,0 +1,165 @@
import request from '@/utils/http'
type PageList<T> = {
list: T[]
total: number
page: number
pageSize: number
totalPages: number
}
export type StorageReleaseReview = {
id: string
sampleCode?: string
sampleName?: string
protocolNo?: string
receptionSource?: string
receptionSourceLabel?: string
reviewConclusion?: string
reviewConclusionLabel?: string
reviewRemark?: string
reviewAttachments?: string
reviewBy?: string
reviewByName?: string
reviewTime?: string
storageStatus?: string
storageStatusLabel?: string
storageBy?: string
storageByName?: string
storageTime?: string
remark?: string
createTime?: string
updateTime?: string
}
export type ProtocolInfo = {
protocolNo?: string
protocolStatus?: string
motherName?: string
motherPhone?: string
fatherName?: string
fatherPhone?: string
newbornName?: string
sampleCode?: string
sampleTypeName?: string
collectAmount?: number | null
storageYears?: number | null
}
export type TransportInfo = {
transportType?: string
waybillNo?: string
expressFee?: number | null
etaTime?: string
carrierName?: string
carrierPhone?: string
}
export type ReceptionInfo = {
receptionTime?: string
handlerName?: string
receiveTemperature?: string
volumeMl?: number | null
netWeightG?: number | null
acceptConclusion?: string
}
export type QcInfoTask = {
id?: string
taskType?: string
taskTypeLabel?: string
taskStatus?: string
taskStatusLabel?: string
conclusion?: string
conclusionLabel?: string
}
export type QcInfo = {
workOrderNo?: string
workOrderStatus?: string
tasks?: QcInfoTask[]
}
export type ProductionInfoTask = {
id?: string
taskNo?: string
taskName?: string
taskStatus?: string
taskStatusLabel?: string
startedTime?: string
completedTime?: string
}
export type ProductionInfo = {
workOrderNo?: string
workOrderStatus?: string
tasks?: ProductionInfoTask[]
}
export type StorageReleaseReviewDetail = {
review: StorageReleaseReview
protocolInfo?: ProtocolInfo
transportInfo?: TransportInfo
receptionInfo?: ReceptionInfo
qcInfo?: QcInfo
productionInfo?: ProductionInfo
}
export function fetchStorageReleaseReviewList(params?: {
page?: number
pageSize?: number
receptionSource?: string
reviewConclusion?: string
storageStatus?: string
keyword?: string
}) {
return request.get<PageList<StorageReleaseReview>>({
url: '/web/admin/storage-release-reviews',
params
})
}
export function fetchStorageReleaseReviewDetail(id: string) {
return request.get<StorageReleaseReviewDetail>({
url: `/web/admin/storage-release-reviews/${id}`
})
}
export function submitStorageReleaseReview(
id: string,
data: {
reviewConclusion: string
reviewRemark?: string
reviewAttachments?: string
}
) {
return request.put<StorageReleaseReview>({
url: `/web/admin/storage-release-reviews/${id}/review`,
data
})
}
export function batchReviewStorageRelease(data: {
ids: string[]
reviewConclusion: string
reviewRemark?: string
reviewAttachments?: string
}) {
return request.put<void>({
url: '/web/admin/storage-release-reviews/batch-review',
data
})
}
export function storeSample(id: string) {
return request.put<StorageReleaseReview>({
url: `/web/admin/storage-release-reviews/${id}/store`
})
}
export function batchStoreSample(data: { ids: string[] }) {
return request.put<void>({
url: '/web/admin/storage-release-reviews/batch-store',
data
})
}
@@ -361,9 +361,14 @@
useCommon().refresh()
return
}
router.push({
path: item.path,
query: item.query as LocationQueryRaw
// 强制重新加载:先将目标组件从 KeepAlive 缓存中排除
store.addKeepAliveExclude(item)
// 等待 Vue 处理 exclude 变更、清除缓存后再导航
nextTick(() => {
router.push({
path: item.path,
query: item.query as LocationQueryRaw
})
})
}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L1"}, {"id": "production_work_order_index_handleview", "label": "handleView()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L454"}, {"id": "production_work_order_index_handlecomplete", "label": "handleComplete()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L458"}, {"id": "production_work_order_index_handleopendispatch", "label": "handleOpenDispatch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L468"}, {"id": "production_work_order_index_handleedittask", "label": "handleEditTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L485"}, {"id": "production_work_order_index_opentaskeditor", "label": "openTaskEditor()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L500"}, {"id": "production_work_order_index_openedittaskinfo", "label": "openEditTaskInfo()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L512"}, {"id": "production_work_order_index_handleedittaskinfosuccess", "label": "handleEditTaskInfoSuccess()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L517"}, {"id": "production_work_order_index_handledeletetask", "label": "handleDeleteTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L523"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L454", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handlecomplete", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L458", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleopendispatch", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L468", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleedittask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L485", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_opentaskeditor", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L500", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_openedittaskinfo", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L512", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleedittaskinfosuccess", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L517", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_taskinputenhancedtable_vue", "label": "TaskInputEnhancedTable.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_taskoutputproductdialog_vue", "label": "TaskOutputProductDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L1"}, {"id": "production_work_order_taskoutputproductdialog_fullfileurl", "label": "fullFileUrl()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L342"}, {"id": "production_work_order_taskoutputproductdialog_initaddform", "label": "initAddForm()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L408"}, {"id": "production_work_order_taskoutputproductdialog_applyproductcodetolines", "label": "applyProductCodeToLines()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L426"}, {"id": "production_work_order_taskoutputproductdialog_ondialogopened", "label": "onDialogOpened()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L431"}, {"id": "production_work_order_taskoutputproductdialog_createquantityvalue", "label": "createQuantityValue()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L437"}, {"id": "production_work_order_taskoutputproductdialog_stripproductcodesuffix", "label": "stripProductCodeSuffix()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L443"}, {"id": "production_work_order_taskoutputproductdialog_buildlineproductcode", "label": "buildLineProductCode()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L449"}, {"id": "production_work_order_taskoutputproductdialog_synclinecodesfrombase", "label": "syncLineCodesFromBase()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L455"}, {"id": "production_work_order_taskoutputproductdialog_syncproductlines", "label": "syncProductLines()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L462"}, {"id": "production_work_order_taskoutputproductdialog_oncreatecountchange", "label": "onCreateCountChange()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L475"}, {"id": "production_work_order_taskoutputproductdialog_onbaseproductcodeinput", "label": "onBaseProductCodeInput()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L481"}, {"id": "production_work_order_taskoutputproductdialog_onlineproductcodeinput", "label": "onLineProductCodeInput()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L496"}, {"id": "production_work_order_taskoutputproductdialog_resetform", "label": "resetForm()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L541"}, {"id": "production_work_order_taskoutputproductdialog_filenamefromurl", "label": "fileNameFromUrl()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L562"}, {"id": "production_work_order_taskoutputproductdialog_syncimagestoform", "label": "syncImagesToForm()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L568"}, {"id": "production_work_order_taskoutputproductdialog_syncformtoimages", "label": "syncFormToImages()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L575"}, {"id": "production_work_order_taskoutputproducLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_stepdetailpanel_vue", "label": "StepDetailPanel.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue", "source_location": "L1"}, {"id": "production_work_order_stepdetailpanel_for", "label": "for()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue", "source_location": "L699"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_stepdetailpanel_vue", "target": "production_work_order_stepdetailpanel_for", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue", "source_location": "L699", "weight": 1.0}], "raw_calls": [{"caller_nid": "production_work_order_stepdetailpanel_for", "callee": "push", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue", "source_location": "L701"}, {"caller_nid": "production_work_order_stepdetailpanel_for", "callee": "decodeURIComponent", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue", "source_location": "L704"}, {"caller_nid": "production_work_order_stepdetailpanel_for", "callee": "pop", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue", "source_location": "L704"}, {"caller_nid": "production_work_order_stepdetailpanel_for", "callee": "split", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue", "source_location": "L704"}, {"caller_nid": "production_work_order_stepdetailpanel_for", "callee": "String", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue", "source_location": "L704"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_types_import_components_d_ts", "label": "components.d.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_components_core_layouts_art_work_tab_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L1"}, {"id": "art_work_tab_index_settransition", "label": "setTransition()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L204"}, {"id": "art_work_tab_index_handlewheelscroll", "label": "handleWheelScroll()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L283"}, {"id": "art_work_tab_index_handletouchstart", "label": "handleTouchStart()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L300"}, {"id": "art_work_tab_index_handletouchmove", "label": "handleTouchMove()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L304"}, {"id": "art_work_tab_index_handletouchend", "label": "handleTouchEnd()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L318"}, {"id": "art_work_tab_index_setupeventlisteners", "label": "setupEventListeners()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L322"}, {"id": "art_work_tab_index_cleanupeventlisteners", "label": "cleanupEventListeners()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L331"}, {"id": "art_work_tab_index_clicktab", "label": "clickTab()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L359"}, {"id": "art_work_tab_index_closeworktab", "label": "closeWorktab()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L375"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_components_core_layouts_art_work_tab_index_vue", "target": "art_work_tab_index_settransition", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L204", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_components_core_layouts_art_work_tab_index_vue", "target": "art_work_tab_index_handlewheelscroll", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L283", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_components_core_layouts_art_work_tab_index_vue", "target": "art_work_tab_index_handletouchstart", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L300", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_components_core_layouts_art_work_tab_index_vue", "target": "art_work_tab_index_handletouchmove", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L304", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_components_core_layouts_art_work_tab_index_vue", "target": "art_work_tab_index_handletouchend", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L318", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_components_core_layouts_art_work_tab_index_vue", "target": "art_work_tab_index_setupeventlisteners", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L322", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_components_core_layouts_art_work_tab_index_vue", "target": "art_work_tab_index_cleanupeventlisteners", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/core/layouts/art-work-tab/index.vue", "source_location": "L331", "weight": 1.0}, {"source": "users_wuzilong_work_Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_storagereleasereviewdialog_vue", "label": "StorageReleaseReviewDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L1"}, {"id": "storage_release_review_storagereleasereviewdialog_fullfileurl", "label": "fullFileUrl()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L173"}, {"id": "storage_release_review_storagereleasereviewdialog_isimagefile", "label": "isImageFile()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L179"}, {"id": "storage_release_review_storagereleasereviewdialog_removeattachment", "label": "removeAttachment()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L185"}, {"id": "storage_release_review_storagereleasereviewdialog_getlist", "label": "_getList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L191"}, {"id": "storage_release_review_storagereleasereviewdialog_resetform", "label": "resetForm()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L262"}, {"id": "storage_release_review_storagereleasereviewdialog_handlesubmit", "label": "handleSubmit()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L271"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_storagereleasereviewdialog_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L120", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_storagereleasereviewdialog_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L121", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_storagereleasereviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_business", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L122", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_storagereleasereviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_storage_release_review", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L123", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_storagereleasereviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_fullscreen_loading", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L129", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_storagereleasereviewdialog_vue", "target": "storage_release_review_storagereleasereviewdialog_fullfileurl", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue", "source_location": "L173", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_storagereleasereviewdialog_vue", "target": "storage_release_review_storagereleasereviewdialog_isimagefile",Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_taskoutputproductdialog_vue", "label": "TaskOutputProductDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "label": "useTaskStepEditor.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L1"}, {"id": "production_work_order_usetaskstepeditor_toapiflag", "label": "toApiFlag()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L35"}, {"id": "production_work_order_usetaskstepeditor_fromapiflag", "label": "fromApiFlag()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L39"}, {"id": "production_work_order_usetaskstepeditor_normalizestepsubtables", "label": "normalizeStepSubTables()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L42"}, {"id": "production_work_order_usetaskstepeditor_parseordernum", "label": "parseOrderNum()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L57"}, {"id": "production_work_order_usetaskstepeditor_usetaskstepeditor", "label": "useTaskStepEditor()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L64"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_task", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L16", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_admin", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L24", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L25", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_admin", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L26", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_tree_path", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L32", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "production_work_order_usetaskstepeditor_toapiflag", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L35", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "production_work_order_usetaskstepeditor_fromapiflag", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L39", "weight": 1.0}, {"source": "users_wuLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "label": "useTaskStepEditor.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L1"}, {"id": "production_work_order_usetaskstepeditor_toapiflag", "label": "toApiFlag()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L35"}, {"id": "production_work_order_usetaskstepeditor_fromapiflag", "label": "fromApiFlag()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L39"}, {"id": "production_work_order_usetaskstepeditor_normalizestepsubtables", "label": "normalizeStepSubTables()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L42"}, {"id": "production_work_order_usetaskstepeditor_parseordernum", "label": "parseOrderNum()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L57"}, {"id": "production_work_order_usetaskstepeditor_usetaskstepeditor", "label": "useTaskStepEditor()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L64"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_task", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L16", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_admin", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L24", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L25", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_admin", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L26", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_tree_path", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L32", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "production_work_order_usetaskstepeditor_toapiflag", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L35", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "production_work_order_usetaskstepeditor_fromapiflag", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L39", "weight": 1.0}, {"source": "users_wuLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_router_modules_sqxp_ts", "label": "sqxp.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/router/modules/sqxp.ts", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_router_modules_sqxp_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_types_router", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/router/modules/sqxp.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_storagereleaseviewdialog_vue", "label": "StorageReleaseViewDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseViewDialog.vue", "source_location": "L1"}, {"id": "storage_release_review_storagereleaseviewdialog_formatval", "label": "formatVal()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseViewDialog.vue", "source_location": "L175"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_storage_release_review_storagereleaseviewdialog_vue", "target": "storage_release_review_storagereleaseviewdialog_formatval", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseViewDialog.vue", "source_location": "L175", "weight": 1.0}], "raw_calls": [{"caller_nid": "storage_release_review_storagereleaseviewdialog_formatval", "callee": "String", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseViewDialog.vue", "source_location": "L177"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_taskoutputlist_vue", "label": "TaskOutputList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputList.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_types_import_components_d_ts", "label": "components.d.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L1"}, {"id": "production_work_order_index_handleview", "label": "handleView()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L454"}, {"id": "production_work_order_index_handlecomplete", "label": "handleComplete()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L458"}, {"id": "production_work_order_index_handleopendispatch", "label": "handleOpenDispatch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L468"}, {"id": "production_work_order_index_handleedittask", "label": "handleEditTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L485"}, {"id": "production_work_order_index_opentaskeditor", "label": "openTaskEditor()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L500"}, {"id": "production_work_order_index_openedittaskinfo", "label": "openEditTaskInfo()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L512"}, {"id": "production_work_order_index_handleedittaskinfosuccess", "label": "handleEditTaskInfoSuccess()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L517"}, {"id": "production_work_order_index_handledeletetask", "label": "handleDeleteTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L523"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L454", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handlecomplete", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L458", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleopendispatch", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L468", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleedittask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L485", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_opentaskeditor", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L500", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_openedittaskinfo", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L512", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleedittaskinfosuccess", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L517", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_taskoutputlist_vue", "label": "TaskOutputList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputList.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http_index_ts", "label": "index.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L1"}, {"id": "http_index_subscribetokenrefresh", "label": "subscribeTokenRefresh()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L45"}, {"id": "http_index_onrefreshed", "label": "onRefreshed()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L49"}, {"id": "http_index_onrefreshfailed", "label": "onRefreshFailed()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L54"}, {"id": "http_index_createhttperror", "label": "createHttpError()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L188"}, {"id": "http_index_handleunauthorizedresponse", "label": "handleUnauthorizedResponse()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L195"}, {"id": "http_index_triggerlogoutwithdebounce", "label": "triggerLogoutWithDebounce()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L256"}, {"id": "http_index_resetunauthorizederror", "label": "resetUnauthorizedError()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L266"}, {"id": "http_index_logout", "label": "logOut()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L273"}, {"id": "http_index_resolveuserid", "label": "resolveUserId()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L279"}, {"id": "http_index_parseuseridfromtoken", "label": "parseUserIdFromToken()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L286"}, {"id": "http_index_ispathexcluded", "label": "isPathExcluded()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L300"}, {"id": "http_index_getencryptskipreason", "label": "getEncryptSkipReason()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L305"}, {"id": "http_index_normalizerequestdata", "label": "normalizeRequestData()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L324"}, {"id": "http_index_decryptresponseifneeded", "label": "decryptResponseIfNeeded()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L337"}, {"id": "http_index_shouldretry", "label": "shouldRetry()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L361"}, {"id": "http_index_getmaxretriesforconfig", "label": "getMaxRetriesForConfig()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L372"}, {"id": "http_index_retryrequest", "label": "retryRequest()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L378"}, {"id": "http_index_delay", "label": "delay()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L395"}, {"id": "http_index_request", "label": "request()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L400"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http_index_ts", "target": "axios", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L17", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http_index_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_store_modules_user", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/http/index.ts", "source_location": "L18", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http_index_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http_status", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_storage_release_review_ts", "label": "storage-release-review.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L1"}, {"id": "api_storage_release_review_fetchstoragereleasereviewlist", "label": "fetchStorageReleaseReviewList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L108"}, {"id": "api_storage_release_review_fetchstoragereleasereviewdetail", "label": "fetchStorageReleaseReviewDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L122"}, {"id": "api_storage_release_review_submitstoragereleasereview", "label": "submitStorageReleaseReview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L128"}, {"id": "api_storage_release_review_batchreviewstoragerelease", "label": "batchReviewStorageRelease()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L142"}, {"id": "api_storage_release_review_storesample", "label": "storeSample()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L154"}, {"id": "api_storage_release_review_batchstoresample", "label": "batchStoreSample()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L160"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_storage_release_review_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_storage_release_review_ts", "target": "api_storage_release_review_fetchstoragereleasereviewlist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L108", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_storage_release_review_ts", "target": "api_storage_release_review_fetchstoragereleasereviewdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L122", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_storage_release_review_ts", "target": "api_storage_release_review_submitstoragereleasereview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L128", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_storage_release_review_ts", "target": "api_storage_release_review_batchreviewstoragerelease", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L142", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_storage_release_review_ts", "target": "api_storage_release_review_storesample", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L154", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_storage_release_review_ts", "target": "api_storage_release_review_batchstoresample", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L160", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_storage_release_review_fetchstoragereleasereviewlist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L116"}, {"caller_nid": "api_storage_release_review_fetchstoragereleasereviewdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L123"}, {"caller_nid": "api_storage_release_review_submitstoragereleasereview", "callee": "put", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "source_location": "L136"}, {"caller_nid": "api_storage_release_review_batchreviewstoragerelease", "callee": "put", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts", "sLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_taskinputenhancedtable_vue", "label": "TaskInputEnhancedTable.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_taskexecutiondialog_vue", "label": "TaskExecutionDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_status_tag_registry_ts", "label": "status-tag-registry.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts", "source_location": "L1"}, {"id": "utils_status_tag_registry_createlabelgetter", "label": "createLabelGetter()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts", "source_location": "L29"}, {"id": "utils_status_tag_registry_createtagtypegetter", "label": "createTagTypeGetter()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts", "source_location": "L33"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_status_tag_registry_ts", "target": "utils_status_tag_registry_createlabelgetter", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts", "source_location": "L29", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_status_tag_registry_ts", "target": "utils_status_tag_registry_createtagtypegetter", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts", "source_location": "L33", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_taskoutputlist_vue", "label": "TaskOutputList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputList.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "label": "useTaskStepEditor.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L1"}, {"id": "production_work_order_usetaskstepeditor_toapiflag", "label": "toApiFlag()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L35"}, {"id": "production_work_order_usetaskstepeditor_fromapiflag", "label": "fromApiFlag()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L39"}, {"id": "production_work_order_usetaskstepeditor_normalizestepsubtables", "label": "normalizeStepSubTables()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L42"}, {"id": "production_work_order_usetaskstepeditor_parseordernum", "label": "parseOrderNum()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L57"}, {"id": "production_work_order_usetaskstepeditor_usetaskstepeditor", "label": "useTaskStepEditor()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L64"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_task", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L16", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_admin", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L24", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L25", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_admin", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L26", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_tree_path", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L32", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "production_work_order_usetaskstepeditor_toapiflag", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L35", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_usetaskstepeditor_ts", "target": "production_work_order_usetaskstepeditor_fromapiflag", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts", "source_location": "L39", "weight": 1.0}, {"source": "users_wuLine truncated
+11
View File
@@ -396,6 +396,17 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [
path: 'qc-material-usage',
component: '/quality-inspection/qc-material-usage/index',
meta: { title: '质检物料变动记录', keepAlive: true, menuCode: 'biz:qcMaterialUsage:list' }
},
{
id: '70000000000000000000000000000011',
name: 'QualityInspectionStorageReleaseReview',
path: 'storage-release-review',
component: '/quality-inspection/storage-release-review/index',
meta: {
title: '入库放行审核',
keepAlive: true,
menuCode: 'biz:storageReleaseReview:list'
}
}
]
},
+6
View File
@@ -204,6 +204,12 @@ async function handleUnauthorizedResponse(
throw createHttpError(message || $t('httpMsg.unauthorized'), ApiStatus.unauthorized)
}
// refresh-token 接口本身返回 401,说明 refreshToken 已失效,直接登出,避免死锁
if (config.url && /\/auth\/refresh-token/.test(config.url)) {
triggerLogoutWithDebounce(message)
throw createHttpError(message || $t('httpMsg.unauthorized'), ApiStatus.unauthorized)
}
if ((config as ExtendedAxiosRequestConfig)._retry) {
triggerLogoutWithDebounce(message)
throw createHttpError(message || $t('httpMsg.unauthorized'), ApiStatus.unauthorized)
@@ -325,4 +325,22 @@ export const packType: StatusRegistry = {
export const packTypeLabel = createLabelGetter(packType)
export const packTypeTagType = createTagTypeGetter(packType)
// ══════════════════════════════════════════════════════════════════════════════
// 入库放行审核域
// ══════════════════════════════════════════════════════════════════════════════
export const storageReleaseConclusion: StatusRegistry = {
tg: { label: '通过', tagType: 'success' },
btg: { label: '不通过', tagType: 'danger' }
}
export const storageReleaseConclusionLabel = createLabelGetter(storageReleaseConclusion)
export const storageReleaseConclusionTagType = createTagTypeGetter(storageReleaseConclusion)
export const storageStatus: StatusRegistry = {
drk: { label: '待入库', tagType: 'warning' },
yrk: { label: '已入库', tagType: 'success' }
}
export const storageStatusLabel = createLabelGetter(storageStatus)
export const storageStatusTagType = createTagTypeGetter(storageStatus)
@@ -31,6 +31,7 @@
style="width: 100%"
>
<el-option label="已冻存" value="frozen" />
<el-option label="生产中" value="in_production" />
<el-option label="已售卖" value="sold" />
<el-option label="已使用" value="used" />
<el-option label="已作废" value="voided" />
@@ -680,9 +681,11 @@
function productStatusTag(status: string) {
const map: Record<string, { label: string; type: string }> = {
frozen: { label: '已冻存', type: 'success' },
in_production: { label: '生产中', type: 'warning' },
sold: { label: '已售卖', type: 'warning' },
used: { label: '已使用', type: 'info' },
voided: { label: '已作废', type: 'danger' },
thawed: { label: '已复苏', type: 'info' },
}
return map[status] || { label: status, type: 'info' }
}
@@ -724,6 +727,8 @@
/* ---- 编辑时状态选项 ---- */
const editStatusOptions = [
{ label: '已冻存', value: 'frozen' },
{ label: '生产中', value: 'in_production' },
{ label: '已复苏', value: 'thawed' },
{ label: '已售卖', value: 'sold' },
{ label: '已使用', value: 'used' },
{ label: '已作废', value: 'voided' },
@@ -1190,7 +1195,7 @@
submitLoading.value = true
try {
syncImagesToForm()
const shared = {
const shared: Record<string, any> = {
productName: formData.productName.trim(),
productTypeId: formData.productTypeId || undefined,
specId: formData.specId || undefined,
@@ -1201,10 +1206,14 @@
freezeDate: formData.freezeDate || undefined,
unit: formData.unit?.trim() || undefined,
expiryDate: formData.expiryDate || undefined,
productStatus: formData.productStatus || undefined,
productImages: formData.productImages?.trim() || undefined,
remark: formData.remark?.trim() || undefined,
}
// 仅「已冻存」可变更状态;生产中/已复苏等系统态勿提交,避免后端校验失败
const editableStatuses = ['frozen', 'sold', 'used', 'voided']
if (!isEdit.value || editableStatuses.includes(formData.productStatus)) {
shared.productStatus = formData.productStatus || undefined
}
if (isEdit.value && editingId.value) {
await fetchUpdateCellProduct(editingId.value, {
...shared,
@@ -50,13 +50,14 @@
<el-form label-position="top" class="route-config-step-form">
<div class="form-grid-4">
<el-form-item label="工序名称">
<el-input v-model="editor.selectedStep.value.processName" />
<el-input v-model="editor.selectedStep.value.processName" :disabled="readonly" />
</el-form-item>
<el-form-item label="工序分类">
<el-select
v-model="editor.selectedStep.value.categoryId"
placeholder="选择分类"
clearable
:disabled="readonly"
>
<el-option
v-for="cat in editor.categoryOptions.value"
@@ -67,10 +68,14 @@
</el-select>
</el-form-item>
<el-form-item label="设备名称">
<el-input v-model="editor.selectedStep.value.equipmentName" />
<el-input v-model="editor.selectedStep.value.equipmentName" :disabled="readonly" />
</el-form-item>
<el-form-item label="预计时长(min)">
<el-input v-model="editor.selectedStep.value.estimatedDuration" placeholder="如 2h" />
<el-input
v-model="editor.selectedStep.value.estimatedDuration"
placeholder="如 2h"
:disabled="readonly"
/>
</el-form-item>
<el-form-item label="描述" class="step-form-col-half">
<el-input
@@ -78,6 +83,7 @@
type="textarea"
:rows="2"
placeholder="工序描述"
:disabled="readonly"
/>
</el-form-item>
<el-form-item label="关联SOP" class="step-form-col-half">
@@ -86,6 +92,7 @@
multiple
placeholder="选择SOP文档"
filterable
:disabled="readonly"
@change="editor.onSopIdsChange"
>
<el-option
@@ -111,7 +118,7 @@
<el-tabs v-model="editor.activeSubTab.value" class="route-config-tabs">
<el-tab-pane label="试剂" name="reagent">
<div class="route-config-tab-toolbar">
<div v-if="!readonly" class="route-config-tab-toolbar">
<el-button class="btn-main" type="primary" size="small" @click="editor.addReagentRow">
<el-icon><Plus /></el-icon>添加试剂
</el-button>
@@ -119,8 +126,8 @@
<el-table
:data="editor.selectedStep.value.reagents"
stripe
class="inner-table"
max-height="300"
class="list-table inner-table"
max-height="400"
>
<el-table-column
type="index"
@@ -129,14 +136,21 @@
align="center"
:index="(i: number) => i + 1"
/>
<el-table-column label="试剂编码" min-width="110" align="center">
<el-table-column label="试剂编码" min-width="110" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input v-model="row.reagentCode" placeholder="编码" class="inner-cell-input" />
<el-input
v-if="!readonly"
v-model="row.reagentCode"
placeholder="编码"
class="inner-cell-input"
/>
<span v-else>{{ row.reagentCode || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="试剂名称" min-width="160" align="center">
<el-table-column label="试剂名称" min-width="160" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-select
v-if="!readonly"
v-model="row.reagentId"
placeholder="请选择试剂"
clearable
@@ -156,20 +170,29 @@
:value="item.id"
/>
</el-select>
<span v-else>{{ row.reagentName || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="规格" min-width="100" align="center">
<el-table-column label="规格" min-width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input v-model="row.spec" placeholder="规格" class="inner-cell-input" />
<el-input
v-if="!readonly"
v-model="row.spec"
placeholder="规格"
class="inner-cell-input"
/>
<span v-else>{{ row.spec || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="标准用量" min-width="110" align="center">
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.standardUsage"
placeholder="用量"
class="inner-cell-input"
/>
<span v-else>{{ row.standardUsage ?? '—' }}</span>
</template>
</el-table-column>
<el-table-column
@@ -180,16 +203,19 @@
>
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.actualUsage"
placeholder="实际用量"
class="inner-cell-input"
@change="editor.saveReagents"
/>
<span v-else>{{ row.actualUsage ?? '—' }}</span>
</template>
</el-table-column>
<el-table-column label="单位" min-width="120" align="center">
<el-table-column label="单位" min-width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-select
v-if="!readonly"
v-model="row.unit"
placeholder="请选择单位"
clearable
@@ -208,19 +234,29 @@
:value="item.value"
/>
</el-select>
<span v-else>{{ row.unit || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="关键物料" width="90" align="center">
<template #default="{ row }">
<el-switch v-model="row.isKeyMaterial" />
<el-switch v-if="!readonly" v-model="row.isKeyMaterial" />
<el-tag v-else :type="row.isKeyMaterial ? 'success' : 'info'" size="small">
{{ row.isKeyMaterial ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" min-width="120" align="center">
<el-table-column label="备注" min-width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
<el-input
v-if="!readonly"
v-model="row.remark"
placeholder="备注"
class="inner-cell-input"
/>
<span v-else>{{ row.remark || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<el-table-column v-if="!readonly" label="操作" width="80" align="center">
<template #default="{ $index }">
<el-button
link
@@ -240,7 +276,7 @@
</el-tab-pane>
<el-tab-pane label="耗材" name="consumable">
<div class="route-config-tab-toolbar">
<div v-if="!readonly" class="route-config-tab-toolbar">
<el-button
class="btn-main"
type="primary"
@@ -253,8 +289,8 @@
<el-table
:data="editor.selectedStep.value.consumables"
stripe
class="inner-table"
max-height="300"
class="list-table inner-table"
max-height="400"
>
<el-table-column
type="index"
@@ -263,18 +299,21 @@
align="center"
:index="(i: number) => i + 1"
/>
<el-table-column label="耗材编码" min-width="110" align="center">
<el-table-column label="耗材编码" min-width="110" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.consumableCode"
placeholder="编码"
class="inner-cell-input"
/>
<span v-else>{{ row.consumableCode || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="耗材名称" min-width="160" align="center">
<el-table-column label="耗材名称" min-width="160" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-select
v-if="!readonly"
v-model="row.consumableId"
placeholder="请选择耗材"
clearable
@@ -294,24 +333,29 @@
:value="item.id"
/>
</el-select>
<span v-else>{{ row.consumableName || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="规格型号" min-width="110" align="center">
<el-table-column label="规格型号" min-width="110" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.specModel"
placeholder="规格型号"
class="inner-cell-input"
/>
<span v-else>{{ row.specModel || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="标准用量" min-width="110" align="center">
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.standardUsage"
placeholder="用量"
class="inner-cell-input"
/>
<span v-else>{{ row.standardUsage ?? '—' }}</span>
</template>
</el-table-column>
<el-table-column
@@ -322,16 +366,19 @@
>
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.actualUsage"
placeholder="实际用量"
class="inner-cell-input"
@change="editor.saveConsumables"
/>
<span v-else>{{ row.actualUsage ?? '—' }}</span>
</template>
</el-table-column>
<el-table-column label="单位" min-width="120" align="center">
<el-table-column label="单位" min-width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-select
v-if="!readonly"
v-model="row.unit"
placeholder="请选择单位"
clearable
@@ -350,19 +397,29 @@
:value="item.value"
/>
</el-select>
<span v-else>{{ row.unit || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="必需" width="80" align="center">
<template #default="{ row }">
<el-switch v-model="row.isRequired" />
<el-switch v-if="!readonly" v-model="row.isRequired" />
<el-tag v-else :type="row.isRequired ? 'success' : 'info'" size="small">
{{ row.isRequired ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" min-width="120" align="center">
<el-table-column label="备注" min-width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
<el-input
v-if="!readonly"
v-model="row.remark"
placeholder="备注"
class="inner-cell-input"
/>
<span v-else>{{ row.remark || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<el-table-column v-if="!readonly" label="操作" width="80" align="center">
<template #default="{ $index }">
<el-button
link
@@ -382,7 +439,7 @@
</el-tab-pane>
<el-tab-pane label="产出物" name="output">
<div class="route-config-tab-toolbar">
<div v-if="!readonly" class="route-config-tab-toolbar">
<el-button class="btn-main" type="primary" size="small" @click="editor.addOutputRow">
<el-icon><Plus /></el-icon>添加产出物
</el-button>
@@ -390,8 +447,8 @@
<el-table
:data="editor.selectedStep.value.outputs"
stripe
class="inner-table"
max-height="300"
class="list-table inner-table"
max-height="400"
>
<el-table-column
type="index"
@@ -400,9 +457,10 @@
align="center"
:index="(i: number) => i + 1"
/>
<el-table-column label="产出类型" min-width="150" align="center">
<el-table-column label="产出类型" min-width="150" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-select
v-if="!readonly"
v-model="row.outputTypeId"
placeholder="选择类型"
filterable
@@ -416,39 +474,46 @@
:value="ot.id"
/>
</el-select>
<span v-else>{{ outputTypeLabel(row.outputTypeId) }}</span>
</template>
</el-table-column>
<el-table-column label="名称" min-width="150" align="center">
<el-table-column label="名称" min-width="150" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.outputName"
placeholder="名称"
class="inner-cell-input"
@change="editor.saveOutputs"
/>
<span v-else>{{ row.outputName || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="条码" min-width="150" align="center">
<el-table-column label="条码" min-width="150" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.outputBarcode"
placeholder="条码"
class="inner-cell-input"
@change="editor.saveOutputs"
/>
<span v-else>{{ row.outputBarcode || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="备注" min-width="120" align="center">
<el-table-column label="备注" min-width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.remark"
placeholder="备注"
class="inner-cell-input"
@change="editor.saveOutputs"
/>
<span v-else>{{ row.remark || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<el-table-column v-if="!readonly" label="操作" width="80" align="center">
<template #default="{ $index }">
<el-button link type="danger" size="small" @click="editor.removeOutputRow($index)"
>删除</el-button
@@ -464,7 +529,7 @@
</el-tab-pane>
<el-tab-pane label="数据采集" name="dataCollection">
<div class="route-config-tab-toolbar">
<div v-if="!readonly" class="route-config-tab-toolbar">
<el-button
class="btn-main"
type="primary"
@@ -477,8 +542,8 @@
<el-table
:data="editor.selectedStep.value.dataCollections"
stripe
class="inner-table"
max-height="300"
class="list-table inner-table"
max-height="400"
>
<el-table-column
type="index"
@@ -487,19 +552,22 @@
align="center"
:index="(i: number) => i + 1"
/>
<el-table-column label="采集名称" min-width="180" align="center">
<el-table-column label="采集名称" min-width="180" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.collectionName"
placeholder="采集项名称"
class="inner-cell-input"
@change="editor.saveDataCollections"
/>
<span v-else>{{ row.collectionName || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="单位" min-width="120" align="center">
<el-table-column label="单位" min-width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-select
v-if="!readonly"
v-model="row.unit"
placeholder="请选择单位"
clearable
@@ -519,6 +587,7 @@
:value="item.value"
/>
</el-select>
<span v-else>{{ row.unit || '—' }}</span>
</template>
</el-table-column>
<el-table-column
@@ -529,24 +598,28 @@
>
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.collectionValue"
placeholder="输入采集值"
class="inner-cell-input"
@change="editor.saveDataCollections"
/>
<span v-else>{{ row.collectionValue ?? '—' }}</span>
</template>
</el-table-column>
<el-table-column label="备注" min-width="120" align="center">
<el-table-column label="备注" min-width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-input
v-if="!readonly"
v-model="row.remark"
placeholder="备注"
class="inner-cell-input"
@change="editor.saveDataCollections"
/>
<span v-else>{{ row.remark || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<el-table-column v-if="!readonly" label="操作" width="80" align="center">
<template #default="{ $index }">
<el-button
link
@@ -588,6 +661,11 @@
readonly?: boolean
}>()
function outputTypeLabel(outputTypeId: string) {
const item = props.editor.outputTypeOptions.value.find((ot) => ot.id === outputTypeId)
return item?.typeName || item?.name || outputTypeId || '—'
}
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
function sopAttachmentFullUrl(url: string) {
@@ -162,7 +162,7 @@
<el-tag v-if="editor.virtualOutputStatus.value === 'filled'" type="success" size="small"
>已填写 {{ editor.taskOutputs.value.length }} 项</el-tag
>
<el-tag v-else type="danger" size="small">未填写</el-tag>
<el-tag v-else type="info" size="small">暂无</el-tag>
</div>
<!-- 分割线 -->
@@ -266,7 +266,7 @@
<!-- 任务输出详情 -->
<template v-else-if="editor.selectedVirtualStep.value === 'output'">
<div class="execution-detail-header">
<h3>📤 产出(细胞产品)<span class="virtual-step-required">必填</span></h3>
<h3>📤 产出(细胞产品)</h3>
<div v-if="!readonly" class="execution-detail-actions">
<el-button
class="btn-main"
@@ -16,7 +16,15 @@
</div>
<!-- 列表 -->
<el-table :data="items" size="small" stripe border v-loading="loading" empty-text="暂无原材料">
<el-table
:data="items"
class="list-table"
stripe
border
height="420"
v-loading="loading"
empty-text="暂无原材料"
>
<el-table-column type="index" label="序号" width="56" align="center" />
<el-table-column label="类型" width="100" align="center">
<template #default="{ row }">
@@ -88,7 +96,7 @@
<el-dialog
v-model="selectDialogVisible"
title="选择细胞产品"
width="1160px"
width="1400px"
class="dialog-form-layout"
align-center
destroy-on-close
@@ -157,6 +165,28 @@
</el-tag>
</template>
</el-table-column>
<el-table-column
label="批次号"
prop="batchNo"
min-width="120"
show-overflow-tooltip
/>
<el-table-column label="规格" prop="specName" min-width="100" show-overflow-tooltip />
<el-table-column
label="预计细胞数量(亿)"
prop="expectedCellCount"
min-width="140"
align="center"
>
<template #default="{ row }">
{{ row.expectedCellCount ?? '—' }}
</template>
</el-table-column>
<el-table-column label="活性" prop="viability" width="80" align="center">
<template #default="{ row }">
{{ row.viability != null ? row.viability : '—' }}
</template>
</el-table-column>
</el-table>
</div>
</div>
@@ -192,6 +222,10 @@
passage?: string
storageSlotName?: string
productStatus?: string
batchNo?: string
specName?: string
expectedCellCount?: number
viability?: number
}[]
cellProductOptionsLoading: boolean
}>(),
@@ -12,9 +12,10 @@
<!-- 列表 -->
<el-table
:data="items"
size="small"
class="list-table"
stripe
border
height="420"
v-loading="loading"
empty-text="暂无产出产品"
>
@@ -38,6 +39,26 @@
{{ row.storageSlotName || '—' }}
</template>
</el-table-column>
<el-table-column label="预计细胞数量(亿)" min-width="130" align="center">
<template #default="{ row }">
{{ row.expectedCellCount ?? '—' }}
</template>
</el-table-column>
<el-table-column label="活性" width="80" align="center">
<template #default="{ row }">
{{ row.viability != null ? row.viability : '—' }}
</template>
</el-table-column>
<el-table-column label="冻存日期" min-width="110" align="center">
<template #default="{ row }">
{{ formatDateOnly(row.freezeDate) }}
</template>
</el-table-column>
<el-table-column label="有效期" min-width="110" align="center">
<template #default="{ row }">
{{ formatDateOnly(row.expiryDate) }}
</template>
</el-table-column>
<el-table-column label="数量" width="80" align="center">
<template #default="{ row }">
{{ row.quantity ?? '—' }}
@@ -149,17 +170,42 @@
emit('update:items', newItems)
}
function handleDialogConfirm(data: Record<string, any>) {
function formatDateOnly(val: unknown) {
if (!val) return '—'
const s = String(val)
return s.length >= 10 ? s.slice(0, 10) : s
}
function enrichOutputRow(row: Record<string, any>) {
const productType = props.productTypeOptions.find((o) => o.id === row.productTypeId)
const spec = props.specOptions.find((o) => o.id === row.specId)
const slot = props.storageSlotOptions.find((o) => o.id === row.storageSlotId)
const productCode = row.productCode || row.outputBarcode || ''
const productName = row.productName || row.outputName || ''
return {
...row,
productCode,
productName,
outputBarcode: productCode,
outputName: productName,
productTypeName: row.productTypeName || productType?.productTypeName || '',
specName: row.specName || spec?.specName || '',
storageSlotName: row.storageSlotName || slot?.name || ''
}
}
function handleDialogConfirm(data: Record<string, any> | Record<string, any>[]) {
const rows = (Array.isArray(data) ? data : [data]).map(enrichOutputRow)
const newItems = [...props.items]
if (editingIndex.value >= 0) {
// 编辑:替换原有项,保留 cellProductId
const row = rows[0]
if (!row) return
newItems[editingIndex.value] = {
...newItems[editingIndex.value],
...data
...row
}
} else {
// 新增:添加到列表
newItems.push(data)
newItems.push(...rows)
}
emit('update:items', newItems)
}
@@ -2,12 +2,13 @@
<el-dialog
:model-value="modelValue"
:title="isEdit ? '编辑产出产品' : '新增产出产品'"
width="1000px"
width="1080px"
class="dialog-form-layout task-output-product-dialog"
align-center
destroy-on-close
:close-on-click-modal="false"
@update:model-value="emit('update:modelValue', $event)"
@opened="onDialogOpened"
>
<el-form
ref="formRef"
@@ -21,7 +22,12 @@
<div class="form-grid-3">
<!-- 产品编码 -->
<el-form-item label="产品编码" prop="productCode">
<el-input v-model="formData.productCode" placeholder="请输入产品编码" />
<el-input
v-model="formData.productCode"
placeholder="请输入产品编码"
:readonly="isEdit"
@input="onBaseProductCodeInput"
/>
</el-form-item>
<!-- 产品名称 -->
<el-form-item label="产品名称" prop="productName">
@@ -74,8 +80,8 @@
<el-form-item label="批次号" prop="batchNo">
<el-input v-model="formData.batchNo" placeholder="请输入批次号" />
</el-form-item>
<!-- 储物格位置 -->
<el-form-item label="储物格位置">
<!-- 储物格位置(编辑单条) -->
<el-form-item v-if="isEdit" label="储物格位置">
<el-select
v-model="formData.storageSlotId"
placeholder="请选择储物格"
@@ -122,14 +128,16 @@
style="width: 100%"
/>
</el-form-item>
<!-- 数量 -->
<el-form-item label="数量">
<!-- 入库份数 / 数量 -->
<el-form-item :label="isEdit ? '数量' : '入库份数'" prop="quantity">
<el-input-number
v-model="formData.quantity"
:min="0"
:min="isEdit ? 0 : 1"
:max="isEdit ? undefined : 50"
:precision="0"
controls-position="right"
style="width: 100%"
@change="onCreateCountChange"
/>
</el-form-item>
<!-- 单位 -->
@@ -164,6 +172,21 @@
style="width: 100%"
/>
</el-form-item>
<!-- 状态(编辑单条,对齐细胞产品库) -->
<el-form-item v-if="isEdit" label="状态">
<el-select
v-model="formData.productStatus"
style="width: 100%"
:disabled="formData.productStatus !== 'frozen'"
>
<el-option
v-for="opt in editStatusOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<!-- 产品图片 (full width) -->
<el-form-item label="产品图片" class="form-col-span-2">
<div class="attachment-upload">
@@ -228,6 +251,45 @@
<el-form-item label="备注" class="form-col-span-2">
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="可选" />
</el-form-item>
<!-- 入库明细(新增多份) -->
<div v-if="!isEdit" class="product-lines-section">
<div class="product-lines-head">
<span class="product-lines-title">入库明细</span>
<span class="form-label-tip"
>每份填写编码与储物格;多份时编码为「产品编码-01、-02…」</span
>
</div>
<el-table :data="productLines" size="small" class="product-lines-table">
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column label="产品编码" min-width="200">
<template #default="{ row }">
<el-input
v-model="row.productCode"
placeholder="产品编码"
@input="onLineProductCodeInput"
/>
</template>
</el-table-column>
<el-table-column label="储物格位置" min-width="280">
<template #default="{ row }">
<el-select
v-model="row.storageSlotId"
placeholder="请选择储物格"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="item in storageSlotOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-form>
<template #footer>
@@ -258,10 +320,13 @@
import { Plus, Close, Check, Delete, Download, Loading } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { fetchUploadAttachment } from '@/api/business'
import { fetchNextCellProductCode } from '@/api/cell-product'
import { downloadAttachmentFile } from '@/utils/download-attachment'
defineOptions({ name: 'TaskOutputProductDialog' })
type ProductLine = { productCode: string; storageSlotId: string }
type ProductImageFile = {
id?: string
fileName: string
@@ -282,12 +347,21 @@
const emit = defineEmits<{
'update:modelValue': [value: boolean]
confirm: [data: Record<string, any>]
confirm: [data: Record<string, any> | Record<string, any>[]]
searchUnit: [query: string]
}>()
const passageOptions = ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8']
const editStatusOptions = [
{ label: '已冻存', value: 'frozen' },
{ label: '生产中', value: 'in_production' },
{ label: '已售卖', value: 'sold' },
{ label: '已使用', value: 'used' },
{ label: '已作废', value: 'voided' },
{ label: '已复苏', value: 'thawed' }
]
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
function fullFileUrl(suffix: string) {
if (!suffix) return ''
@@ -296,13 +370,14 @@
return `${minioBaseUrl}/${suffix}`
}
const isEdit = computed(() => !!props.editItem?.cellProductId)
const isEdit = computed(() => !!props.editItem)
const formRef = ref()
const submitLoading = ref(false)
const productImageUploadRef = ref()
const previewVisible = ref(false)
const previewUrl = ref('')
const productImageFiles = ref<ProductImageFile[]>([])
const productLines = ref<ProductLine[]>([{ productCode: '', storageSlotId: '' }])
const formData = reactive({
cellProductId: '',
@@ -319,44 +394,181 @@
quantity: 1 as number,
unit: '',
expiryDate: '',
productStatus: 'frozen',
productImages: '',
remark: ''
})
const formRules = {
productCode: [{ required: true, message: '请输入产品编码', trigger: 'blur' }],
productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }]
productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
batchNo: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
}
// Watch editItem to populate form
watch(
() => props.editItem,
(item) => {
if (item) {
formData.cellProductId = item.cellProductId || ''
formData.productCode = item.productCode || ''
formData.productName = item.productName || ''
formData.productTypeId = item.productTypeId || ''
formData.specId = item.specId || ''
formData.passage = item.passage || ''
formData.batchNo = item.batchNo || ''
formData.storageSlotId = item.storageSlotId || ''
formData.expectedCellCount = item.expectedCellCount ?? undefined
formData.viability = item.viability ?? undefined
formData.freezeDate = item.freezeDate || ''
formData.quantity = item.quantity ?? 1
formData.unit = item.unit || ''
formData.expiryDate = item.expiryDate || ''
formData.productImages = item.productImages || ''
formData.remark = item.remark || ''
syncFormToImages(item.productImages || '')
} else {
resetForm()
function normalizeDateValue(val: unknown): string {
if (!val) return ''
const s = String(val)
return s.length >= 10 ? s.slice(0, 10) : s
}
function populateFormFromEditItem(item: Record<string, any>) {
formData.cellProductId = item.cellProductId || ''
formData.productCode = item.productCode || item.outputBarcode || ''
formData.productName = item.productName || item.outputName || ''
formData.productTypeId = item.productTypeId || ''
formData.specId = item.specId || ''
formData.passage = item.passage || ''
formData.batchNo = item.batchNo || ''
formData.storageSlotId = item.storageSlotId || ''
formData.expectedCellCount = item.expectedCellCount ?? undefined
formData.viability = item.viability ?? undefined
formData.freezeDate = normalizeDateValue(item.freezeDate)
formData.quantity = item.quantity ?? 1
formData.unit = item.unit || ''
formData.expiryDate = normalizeDateValue(item.expiryDate)
formData.productStatus = item.productStatus || 'frozen'
formData.productImages = item.productImages || ''
formData.remark = item.remark || ''
syncFormToImages(item.productImages || '')
}
async function initAddForm() {
resetForm()
formData.quantity = 1
emit('searchUnit', '')
await nextTick()
formRef.value?.clearValidate?.()
try {
const res = await fetchNextCellProductCode()
if (res?.code) {
formData.productCode = res.code
}
},
{ immediate: true }
} catch {
/* 自动生成失败时用户可手动输入 */
}
applyProductCodeToLines()
}
/** 按入库份数将主表单产品编码同步到入库明细(每份均带 -01、-02…,单份时为 -01) */
function applyProductCodeToLines() {
syncProductLines()
syncLineCodesFromBase()
}
function onDialogOpened() {
if (props.editItem) {
populateFormFromEditItem(props.editItem)
emit('searchUnit', props.editItem.unit || '')
nextTick(() => formRef.value?.clearValidate?.())
} else {
void initAddForm()
}
}
function createQuantityValue() {
const n = Math.floor(Number(formData.quantity) || 0)
if (n < 1) return 1
return Math.min(n, 50)
}
function stripProductCodeSuffix(code: string) {
const c = (code || '').trim()
const m = c.match(/^(.+)-(\d{2})$/)
return m ? m[1].trim() : c
}
function buildLineProductCode(base: string, index: number) {
const b = stripProductCodeSuffix(base)
if (!b) return ''
return `${b}-${String(index + 1).padStart(2, '0')}`
}
function syncLineCodesFromBase() {
const base = formData.productCode
productLines.value.forEach((line, index) => {
line.productCode = buildLineProductCode(base, index)
})
}
function syncProductLines() {
const count = createQuantityValue()
const prev = productLines.value.map((l) => ({ ...l }))
const lines: ProductLine[] = []
for (let i = 0; i < count; i++) {
lines.push({
productCode: buildLineProductCode(formData.productCode, i),
storageSlotId: prev[i]?.storageSlotId || ''
})
}
productLines.value = lines.length ? lines : [{ productCode: '', storageSlotId: '' }]
}
function onCreateCountChange() {
if (!isEdit.value) {
applyProductCodeToLines()
}
}
function onBaseProductCodeInput() {
if (!isEdit.value) {
applyProductCodeToLines()
}
}
watch(
() => formData.quantity,
() => {
if (!isEdit.value && props.modelValue) {
applyProductCodeToLines()
}
}
)
function onLineProductCodeInput() {
if (!isEdit.value && createQuantityValue() <= 1) {
formData.productCode = stripProductCodeSuffix(productLines.value[0]?.productCode || '')
}
}
async function validateProductLines(): Promise<boolean> {
if (isEdit.value) return true
const count = createQuantityValue()
for (let i = 0; i < productLines.value.length; i++) {
const line = productLines.value[i]
if (!line.productCode?.trim()) {
ElMessage.warning(`请填写第 ${i + 1} 份产品编码`)
return false
}
if (!line.storageSlotId) {
ElMessage.warning(`请选择第 ${i + 1} 份产品的储物格位置`)
return false
}
}
if (productLines.value.length !== count) {
ElMessage.warning('入库明细行数与入库份数不一致,请调整入库份数')
return false
}
return true
}
function buildSharedResult(): Record<string, any> {
return {
productName: formData.productName.trim(),
productTypeId: formData.productTypeId || undefined,
specId: formData.specId || undefined,
passage: formData.passage || undefined,
batchNo: formData.batchNo.trim(),
expectedCellCount: formData.expectedCellCount ?? undefined,
viability: formData.viability ?? undefined,
freezeDate: formData.freezeDate || undefined,
unit: formData.unit?.trim() || undefined,
expiryDate: formData.expiryDate || undefined,
productImages: formData.productImages?.trim() || undefined,
productStatus: formData.productStatus || 'frozen',
remark: formData.remark?.trim() || undefined
}
}
function resetForm() {
formData.cellProductId = ''
formData.productCode = ''
@@ -372,9 +584,11 @@
formData.quantity = 1
formData.unit = ''
formData.expiryDate = ''
formData.productStatus = 'frozen'
formData.productImages = ''
formData.remark = ''
productImageFiles.value = []
productLines.value = [{ productCode: '', storageSlotId: '' }]
}
function fileNameFromUrl(url: string, index: number) {
@@ -464,30 +678,29 @@
}
async function handleSubmit() {
if (!(await validateProductLines())) return
await formRef.value?.validate?.()
syncImagesToForm()
submitLoading.value = true
try {
const result: Record<string, any> = {
cellProductId: formData.cellProductId || undefined,
productCode: formData.productCode.trim(),
productName: formData.productName.trim(),
productTypeId: formData.productTypeId || undefined,
specId: formData.specId || undefined,
passage: formData.passage || undefined,
batchNo: formData.batchNo.trim() || undefined,
storageSlotId: formData.storageSlotId || undefined,
expectedCellCount: formData.expectedCellCount ?? undefined,
viability: formData.viability ?? undefined,
freezeDate: formData.freezeDate || undefined,
quantity: formData.quantity,
unit: formData.unit?.trim() || undefined,
expiryDate: formData.expiryDate || undefined,
productImages: formData.productImages?.trim() || undefined,
productStatus: 'frozen',
remark: formData.remark?.trim() || undefined
const shared = buildSharedResult()
if (isEdit.value) {
emit('confirm', {
...shared,
cellProductId: formData.cellProductId || undefined,
productCode: formData.productCode.trim(),
storageSlotId: formData.storageSlotId || undefined,
quantity: formData.quantity
})
} else {
const results = productLines.value.map((line) => ({
...shared,
productCode: line.productCode.trim(),
storageSlotId: line.storageSlotId,
quantity: 1
}))
emit('confirm', results)
}
emit('confirm', result)
emit('update:modelValue', false)
} finally {
submitLoading.value = false
@@ -651,4 +864,27 @@
.preview-container {
text-align: center;
}
.product-lines-section {
grid-column: 1 / -1;
margin-top: 4px;
}
.product-lines-head {
display: flex;
align-items: baseline;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 10px;
}
.product-lines-title {
font-size: 13px;
font-weight: 600;
color: var(--mint-text, #334155);
}
.product-lines-table {
width: 100%;
}
</style>
@@ -4,7 +4,7 @@
<div class="page-container">
<header class="page-head">
<h1>入库生产工单</h1>
<p class="page-desc">入库生产工单管理,查看工单详情、派发生产任务并完成待配置工单。</p>
<p class="page-desc">入库生产工单管理,查看工单详情、派发生产任务并完成工单。</p>
</header>
<section class="panel">
@@ -102,7 +102,7 @@
><el-icon><EditPen /></el-icon>编辑任务</el-button
>
<el-button
v-if="row.workOrderStatus === 'dpz'"
v-if="row.workOrderStatus === 'dpz' || row.workOrderStatus === 'jxz'"
class="btn-op-item primary"
@click="handleOpenDispatch(row)"
>
@@ -107,6 +107,10 @@ export function useTaskStepEditor() {
passage?: string
storageSlotName?: string
productStatus?: string
batchNo?: string
specName?: string
expectedCellCount?: number
viability?: number
}>
>([])
const cellProductOptionsLoading = ref(false)
@@ -391,7 +395,11 @@ export function useTaskStepEditor() {
productTypeName: p.productTypeName || '',
passage: p.passage || '',
storageSlotName: p.storageSlotName || '',
productStatus: p.productStatus || ''
productStatus: p.productStatus || '',
batchNo: p.batchNo || '',
specName: p.specName || '',
expectedCellCount: p.expectedCellCount ?? undefined,
viability: p.viability ?? undefined
}))
for (const row of taskInputs.value) {
if (
@@ -570,6 +578,25 @@ export function useTaskStepEditor() {
loadStorageSlotOptions()
])
}
function enrichTaskOutputRow(row: Record<string, any>) {
const productType = productTypeOptions.value.find((o) => o.id === row.productTypeId)
const spec = specOptions.value.find((o) => o.id === row.specId)
const slot = storageSlotOptions.value.find((o) => o.id === row.storageSlotId)
const productCode = row.productCode || row.outputBarcode || ''
const productName = row.productName || row.outputName || ''
return {
...row,
productCode,
productName,
outputBarcode: productCode,
outputName: productName,
productTypeName: row.productTypeName || productType?.productTypeName || '',
specName: row.specName || spec?.specName || '',
storageSlotName: row.storageSlotName || slot?.name || ''
}
}
async function loadTaskDetail(overrideId?: string) {
const id = overrideId || taskId.value
if (!id) return
@@ -586,7 +613,9 @@ export function useTaskStepEditor() {
if (mode.value === 'execution') {
taskDetail.value = detail
taskInputs.value = (detail.inputs || []).map((i: Record<string, any>) => ({ ...i }))
taskOutputs.value = (detail.outputs || []).map((o: Record<string, any>) => ({ ...o }))
taskOutputs.value = (detail.outputs || []).map((o: Record<string, any>) =>
enrichTaskOutputRow(o)
)
for (const row of taskInputs.value) {
if (
row.inputType === 'cell_product' &&
@@ -1007,10 +1036,6 @@ export function useTaskStepEditor() {
}
async function saveTaskOutputs() {
if (!taskDetail.value?.id) return
if (!taskOutputs.value.length) {
ElMessage.warning('请至少添加一项产出(细胞产品)')
return
}
outputSaving.value = true
try {
await fetchSetTaskOutputs(taskDetail.value.id, taskOutputs.value)
@@ -0,0 +1,478 @@
<!-- 入库放行审核 — 审核弹窗(支持单条和批量) -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<el-dialog
:model-value="modelValue"
:title="dialogTitle"
width="640px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
style="backdrop-filter: none"
@update:model-value="emit('update:modelValue', $event)"
@closed="resetForm"
>
<div v-if="isSingleMode && singleDetail" class="review-info-summary">
<div class="review-summary-row">
<span class="review-summary-label">协议编号</span>
<span class="review-summary-value">{{ singleDetail.review?.protocolNo || '—' }}</span>
</div>
<div class="review-summary-row">
<span class="review-summary-label">样本编码</span>
<span class="review-summary-value">{{ singleDetail.review?.sampleCode || '—' }}</span>
</div>
<div class="review-summary-row">
<span class="review-summary-label">样本名称</span>
<span class="review-summary-value">{{ singleDetail.review?.sampleName || '—' }}</span>
</div>
</div>
<p v-else-if="reviewIds.length > 1" class="audit-target-hint">
将对已选
<strong>{{ reviewIds.length }}</strong> 条记录提交相同审核结果。
</p>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
class="biz-dialog-form"
@submit.prevent
>
<el-form-item label="审核结论" prop="reviewConclusion">
<el-radio-group v-model="form.reviewConclusion">
<el-radio value="tg">通过</el-radio>
<el-radio value="btg">不通过</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="form.reviewRemark"
type="textarea"
:rows="3"
placeholder="可选"
maxlength="1000"
show-word-limit
/>
</el-form-item>
<el-form-item label="附件">
<div class="attachment-upload-area">
<div class="attachment-list">
<el-upload
:auto-upload="false"
:show-file-list="false"
:multiple="true"
accept="image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip"
@change="handleAttachmentChange"
>
<div class="upload-card">
<el-icon :size="20" color="#10b981"><Plus /></el-icon>
<span class="upload-card-text">上传附件</span>
</div>
</el-upload>
<div
v-for="(file, fIdx) in attachmentFiles"
:key="file.id || fIdx"
class="attachment-item"
:class="{ 'is-uploading': file._uploading }"
>
<div class="attachment-preview">
<template v-if="isImageFile(file) && file.fileUrl">
<img :src="fullFileUrl(file.fileUrl)" :alt="file.fileName" />
</template>
<template v-else>
<div class="attachment-preview__icon attachment-preview__icon--doc">
<el-icon :size="22"><Document /></el-icon>
</div>
</template>
</div>
<div class="attachment-footer">
<span class="attachment-name" :title="file.fileName">{{ file.fileName }}</span>
<div class="attachment-actions">
<el-icon class="action-btn" title="删除" @click.stop="removeAttachment(fIdx)">
<Delete />
</el-icon>
</div>
</div>
</div>
</div>
<p class="upload-hint">支持图片、文档等,单文件不超过 20MB。</p>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="emit('update:modelValue', false)">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button class="btn-main" type="primary" :loading="submitting" @click="handleSubmit">
<el-icon><Check /></el-icon>
确定
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { Check, Close, Plus, Document, Delete } from '@element-plus/icons-vue'
import { fetchUploadAttachment } from '@/api/business'
import {
fetchStorageReleaseReviewDetail,
submitStorageReleaseReview,
batchReviewStorageRelease,
type StorageReleaseReviewDetail
} from '@/api/storage-release-review'
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
type AttachmentItem = {
id: string
fileName: string
fileUrl: string
fileType: string
_uploading?: boolean
}
defineOptions({ name: 'StorageReleaseReviewDialog' })
const props = defineProps<{
modelValue: boolean
reviewIds: string[]
}>()
const emit = defineEmits<{
(e: 'update:modelValue', val: boolean): void
(e: 'submitted'): void
}>()
const formRef = ref<FormInstance>()
const submitting = ref(false)
const singleDetail = ref<StorageReleaseReviewDetail | null>(null)
const attachmentFiles = ref<AttachmentItem[]>([])
const isSingleMode = computed(() => props.reviewIds.length === 1)
const form = reactive({
reviewConclusion: 'tg',
reviewRemark: ''
})
const rules: FormRules = {
reviewConclusion: [{ required: true, message: '请选择审核结论', trigger: 'change' }]
}
const dialogTitle = computed(() =>
props.reviewIds.length > 1 ? `批量审核(${props.reviewIds.length} 条)` : '入库放行审核'
)
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
function fullFileUrl(url: string) {
if (!url) return ''
if (/^https?:\/\//i.test(url)) return url
return minioBaseUrl ? `${minioBaseUrl}/${url.replace(/^\//, '')}` : url
}
function isImageFile(file: AttachmentItem) {
const t = file.fileType || ''
const n = file.fileName || ''
return t.startsWith('image/') || /\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(n)
}
function removeAttachment(index: number) {
attachmentFiles.value.splice(index, 1)
}
let _pendingList: AttachmentItem[] | null = null
function _getList(): AttachmentItem[] {
if (_pendingList) return _pendingList
return attachmentFiles.value
}
async function handleAttachmentChange(uploadFile: { raw?: File }) {
const file = uploadFile.raw
if (!file) return
const maxSizeMb = 20
if (file.size / 1024 / 1024 > maxSizeMb) {
ElMessage.error(`文件大小不能超过 ${maxSizeMb}MB`)
return
}
const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
const uploading: AttachmentItem = {
id: tempId,
fileName: file.name,
fileUrl: '',
fileType: file.type || '',
_uploading: true
}
const current = _getList()
const listWithTemp = [...current, uploading]
_pendingList = listWithTemp
attachmentFiles.value = listWithTemp
try {
const fd = new FormData()
fd.append('folder', 'storage-release-review')
fd.append('file', file)
const res = await fetchUploadAttachment(fd)
const latest = _pendingList || attachmentFiles.value
const updated = latest.map((a) =>
a.id === tempId
? {
id: res.id || tempId,
fileName: file.name || res.fileName,
fileUrl: res.fileUrl,
fileType: res.fileType || file.type || '',
_uploading: false
}
: a
)
_pendingList = updated
attachmentFiles.value = updated
} catch {
const latest = _pendingList || attachmentFiles.value
const cleaned = latest.filter((a) => a.id !== tempId)
_pendingList = cleaned
attachmentFiles.value = cleaned
}
}
watch(
() => [props.modelValue, props.reviewIds] as const,
async ([vis, ids]) => {
if (!vis || !ids.length) {
singleDetail.value = null
return
}
if (ids.length === 1) {
try {
singleDetail.value = await fetchStorageReleaseReviewDetail(ids[0])
} catch {
singleDetail.value = null
}
}
},
{ immediate: true }
)
function resetForm() {
form.reviewConclusion = 'tg'
form.reviewRemark = ''
attachmentFiles.value = []
_pendingList = null
singleDetail.value = null
formRef.value?.clearValidate()
}
async function handleSubmit() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid || !props.reviewIds.length) return
const attachmentUrls = attachmentFiles.value
.filter((f) => f.fileUrl)
.map((f) => f.fileUrl)
.join(',')
const payload = {
reviewConclusion: form.reviewConclusion,
reviewRemark: form.reviewRemark.trim() || undefined,
reviewAttachments: attachmentUrls || undefined
}
submitting.value = true
try {
if (props.reviewIds.length === 1) {
await submitStorageReleaseReview(props.reviewIds[0], payload)
ElMessage.success('审核成功')
} else {
await withFullscreenLoading(async () => {
await batchReviewStorageRelease({
ids: props.reviewIds,
...payload
})
ElMessage.success(`批量审核成功,共 ${props.reviewIds.length} 条`)
}, fullscreenLoadingText.save)
}
emit('update:modelValue', false)
emit('submitted')
} finally {
submitting.value = false
}
}
</script>
<style scoped lang="scss">
.audit-target-hint {
margin: 0 0 16px;
font-size: 13px;
line-height: 1.5;
color: var(--mint-text-muted, #64748b);
strong {
color: #1e293b;
font-weight: 600;
}
}
.review-info-summary {
margin-bottom: 16px;
padding: 12px 16px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px 20px;
}
.review-summary-row {
display: flex;
gap: 8px;
font-size: 13px;
}
.review-summary-label {
color: #64748b;
flex-shrink: 0;
}
.review-summary-value {
color: #1e293b;
font-weight: 500;
}
.attachment-upload-area {
width: 100%;
}
.attachment-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.upload-card {
width: 120px;
height: 88px;
border: 1px dashed #a7f3d0;
border-radius: 8px;
background: #f0fdf4;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
cursor: pointer;
transition: border-color 0.15s;
&:hover {
border-color: #10b981;
}
}
.upload-card-text {
font-size: 11px;
color: #64748b;
}
.attachment-item {
width: 120px;
display: flex;
flex-direction: column;
border: 1px solid #e2e8f0;
border-radius: 8px;
background: #fff;
overflow: hidden;
&.is-uploading {
opacity: 0.6;
}
}
.attachment-preview {
width: 100%;
height: 64px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background: #f8fafc;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.attachment-preview__icon {
width: 36px;
height: 36px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
&--doc {
background: #e0f2fe;
color: #0284c7;
}
}
.attachment-footer {
display: flex;
flex-direction: column;
flex: 1;
padding: 4px 6px;
}
.attachment-name {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
font-size: 11px;
color: #334155;
line-height: 1.4;
word-break: break-all;
}
.attachment-actions {
display: flex;
justify-content: center;
margin-top: 2px;
}
.action-btn {
width: 20px;
height: 20px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #64748b;
cursor: pointer;
background: #f1f5f9;
transition:
background 0.15s,
color 0.15s;
&:hover {
background: #ef4444;
color: #fff;
}
}
.upload-hint {
margin: 8px 0 0;
font-size: 12px;
color: #94a3b8;
}
</style>
@@ -0,0 +1,403 @@
<!-- 入库放行审核 — 只读查看 -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<SystemViewDialog
v-model:visible="innerVisible"
title="入库放行审核详情"
width="960px"
:grid-columns="2"
:icon="CircleCheck"
:meta="detail?.review?.protocolNo || ''"
:sections="viewSections"
:loading="loading"
append-to-body
>
<template #qc-tasks>
<div v-if="detail?.qcInfo?.tasks?.length" class="view-section-slot">
<div class="qc-task-cards">
<div v-for="task in detail.qcInfo.tasks" :key="task.id" class="qc-task-card">
<div class="qc-task-card__header">
<span class="qc-task-card__type">
<el-tag type="info" size="small">{{
task.taskTypeLabel || task.taskType || '—'
}}</el-tag>
</span>
<span class="qc-task-card__conclusion">
<el-tag
v-if="task.conclusion"
:type="task.conclusion === 'tg' ? 'success' : 'danger'"
size="small"
>{{ task.conclusionLabel || task.conclusion }}</el-tag
>
<span v-else class="qc-kv__muted">—</span>
</span>
</div>
<div class="qc-task-card__body">
<div class="qc-kv">
<span class="qc-kv__label">任务状态</span>
<span class="qc-kv__value">
<el-tag
:type="
task.taskStatus === 'ywc'
? 'success'
: task.taskStatus === 'jxz'
? 'warning'
: 'info'
"
size="small"
>{{ task.taskStatusLabel || task.taskStatus || '—' }}</el-tag
>
</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="view-section-slot"><p class="view-empty-hint">暂无质检任务</p></div>
</template>
<template #production-tasks>
<div v-if="detail?.productionInfo?.tasks?.length" class="view-section-slot">
<div class="qc-task-cards">
<div v-for="task in detail.productionInfo.tasks" :key="task.id" class="qc-task-card">
<div class="qc-task-card__header">
<span class="qc-task-card__name">{{ task.taskName || task.taskNo || '—' }}</span>
<span class="qc-task-card__conclusion">
<el-tag
:type="
task.taskStatus === 'ywc'
? 'success'
: task.taskStatus === 'jxz'
? 'warning'
: 'info'
"
size="small"
>{{ task.taskStatusLabel || task.taskStatus || '—' }}</el-tag
>
</span>
</div>
<div class="qc-task-card__body">
<div class="qc-kv">
<span class="qc-kv__label">任务号</span>
<span class="qc-kv__value">{{ task.taskNo || '—' }}</span>
</div>
<div class="qc-kv">
<span class="qc-kv__label">开始时间</span>
<span class="qc-kv__value">{{ task.startedTime || '—' }}</span>
</div>
<div class="qc-kv">
<span class="qc-kv__label">完成时间</span>
<span class="qc-kv__value">{{ task.completedTime || '—' }}</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="view-section-slot"><p class="view-empty-hint">暂无生产任务</p></div>
</template>
<template #review-attachments>
<div class="view-section-slot">
<ProtocolAttachmentReadonly
:files="reviewAttachmentFiles"
@preview="handleAttachmentPreview"
@download="handleAttachmentDownload"
/>
</div>
</template>
</SystemViewDialog>
</template>
<script setup lang="ts">
import { CircleCheck } from '@element-plus/icons-vue'
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
import {
fetchStorageReleaseReviewDetail,
type StorageReleaseReview,
type StorageReleaseReviewDetail,
type ProtocolInfo,
type TransportInfo,
type ReceptionInfo,
type QcInfo,
type ProductionInfo
} from '@/api/storage-release-review'
import {
receptionSourceLabel,
receptionSourceTagType,
storageReleaseConclusionLabel,
storageReleaseConclusionTagType,
acceptConclusionLabel,
acceptConclusionTagType,
transportTypeLabel
} from '@/utils/status-tag-registry'
import { downloadAttachmentFile } from '@/utils/download-attachment'
const props = defineProps<{
visible: boolean
reviewId: string
}>()
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void
}>()
const innerVisible = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val)
})
const loading = ref(false)
const detail = ref<StorageReleaseReviewDetail | null>(null)
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
watch(
() => [props.visible, props.reviewId] as const,
async ([vis, id]) => {
if (!vis || !id) {
detail.value = null
return
}
loading.value = true
try {
detail.value = await fetchStorageReleaseReviewDetail(id)
} catch {
detail.value = null
} finally {
loading.value = false
}
},
{ immediate: true }
)
function formatVal(v: unknown) {
if (v === null || v === undefined || v === '') return '—'
return String(v)
}
const reviewAttachmentFiles = computed(() => {
const urls = detail.value?.review?.reviewAttachments || ''
if (!urls.trim()) return []
return urls
.split(',')
.map((u) => u.trim())
.filter(Boolean)
.map((fileUrl, i) => ({
id: `att_${i}`,
fileUrl,
fileName: fileUrl.split('/').pop() || ''
}))
})
const viewSections = computed<ViewSection[]>(() => {
const d = detail.value
if (!d) return []
const rev: Partial<StorageReleaseReview> = d.review || {}
const proto: Partial<ProtocolInfo> = d.protocolInfo || {}
const transport: Partial<TransportInfo> = d.transportInfo || {}
const reception: Partial<ReceptionInfo> = d.receptionInfo || {}
const qc: Partial<QcInfo> = d.qcInfo || {}
const production: Partial<ProductionInfo> = d.productionInfo || {}
const reviewConclusionValue = rev.reviewConclusion || ''
const reviewConclusionDisplay = reviewConclusionValue
? storageReleaseConclusionLabel(reviewConclusionValue)
: '待审核'
const reviewConclusionTagType = reviewConclusionValue
? storageReleaseConclusionTagType(reviewConclusionValue)
: 'info'
return [
{
title: '基础信息',
fields: [
{ label: '协议编号', value: proto.protocolNo || rev.protocolNo || '—' },
{
label: '协议类别',
value: receptionSourceLabel(rev.receptionSource || ''),
tagType: receptionSourceTagType(rev.receptionSource || '')
},
{ label: '样本编码', value: proto.sampleCode || rev.sampleCode || '—' },
{ label: '样本名称', value: rev.sampleName || '—' },
{ label: '样本类型', value: proto.sampleTypeName || '—' },
{ label: '母亲姓名', value: proto.motherName || '—' },
{ label: '母亲电话', value: proto.motherPhone || '—' },
{ label: '新生儿姓名', value: proto.newbornName || '—' }
]
},
{
title: '运输信息',
fields: transport.transportType
? [
{ label: '运输方式', value: transportTypeLabel(transport.transportType) },
...(transport.transportType === 'kdys'
? [{ label: '快递单号', value: transport.waybillNo || '—' }]
: [
{ label: '携带人', value: transport.carrierName || '—' },
{ label: '携带人电话', value: transport.carrierPhone || '—' }
]),
{ label: '预计到达', value: transport.etaTime || '—' }
]
: []
},
{
title: '接收信息',
fields: reception.receptionTime
? [
{ label: '接收时间', value: reception.receptionTime || '—' },
{ label: '接收经手人', value: reception.handlerName || '—' },
{
label: '接收结论',
value: acceptConclusionLabel(reception.acceptConclusion || ''),
tagType: acceptConclusionTagType(reception.acceptConclusion || '')
},
{ label: '接收温度(℃)', value: formatVal(reception.receiveTemperature) },
{ label: '体积(ml)', value: formatVal(reception.volumeMl) }
]
: []
},
{
title: '质检信息',
slot: 'qc-tasks',
fields: [
{ label: '工单号', value: qc.workOrderNo || '—' },
{
label: '工单状态',
value:
qc.workOrderStatus === 'ywc'
? '已完成'
: qc.workOrderStatus === 'ypz'
? '已配置'
: qc.workOrderStatus || '—',
tagType:
qc.workOrderStatus === 'ywc'
? 'success'
: qc.workOrderStatus === 'ypz'
? 'warning'
: 'info'
}
]
},
{
title: '生产信息',
slot: 'production-tasks',
fields: [
{ label: '工单号', value: production.workOrderNo || '—' },
{
label: '工单状态',
value:
production.workOrderStatus === 'ywc'
? '已完成'
: production.workOrderStatus === 'jxz'
? '执行中'
: production.workOrderStatus || '—',
tagType:
production.workOrderStatus === 'ywc'
? 'success'
: production.workOrderStatus === 'jxz'
? 'warning'
: 'info'
}
]
},
{
title: '放行审核',
slot: 'review-attachments',
fields: [
{
label: '审核结论',
value: reviewConclusionDisplay,
tagType: reviewConclusionTagType
},
{ label: '审核人', value: rev.reviewByName || '—' },
{ label: '审核时间', value: rev.reviewTime || '—' },
{ label: '审核备注', value: rev.reviewRemark || '—', span: true }
]
}
]
})
function attachmentFullUrl(url: string) {
const u = String(url || '').trim()
if (!u) return ''
if (/^https?:\/\//i.test(u)) return u
return minioBaseUrl ? `${minioBaseUrl}/${u.replace(/^\//, '')}` : u
}
function handleAttachmentPreview(file: Record<string, unknown>) {
const url = attachmentFullUrl(String(file.fileUrl || ''))
if (url) window.open(url, '_blank')
}
function handleAttachmentDownload(file: Record<string, unknown>) {
downloadAttachmentFile(file, { fullFileUrl: attachmentFullUrl })
}
</script>
<style scoped lang="scss">
.view-empty-hint {
margin: 0;
font-size: 13px;
color: #94a3b8;
}
.qc-task-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 10px;
}
.qc-task-card {
border: 1px solid #e2e8f0;
border-radius: 8px;
background: #fff;
overflow: hidden;
}
.qc-task-card__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 10px 12px;
border-bottom: 1px solid #f1f5f9;
}
.qc-task-card__name {
font-size: 13px;
font-weight: 600;
color: #1e293b;
}
.qc-task-card__body {
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 8px;
}
.qc-kv {
display: flex;
align-items: flex-start;
gap: 8px;
font-size: 12px;
}
.qc-kv__label {
color: #94a3b8;
flex-shrink: 0;
min-width: 56px;
}
.qc-kv__value {
color: #1e293b;
font-weight: 500;
}
.qc-kv__muted {
color: #94a3b8;
font-weight: 400;
}
</style>
@@ -0,0 +1,398 @@
<!-- 入库放行审核 -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<div class="page-container">
<header class="page-head">
<h1>入库放行审核</h1>
<p class="page-desc">审核入库放行记录,管理样本入库流程。</p>
</header>
<section class="panel">
<div class="panel-top">
<h2>审核列表</h2>
</div>
<div class="filter-block">
<el-form :model="searchForm" class="filter-form" @submit.prevent>
<div class="filter-grid">
<el-form-item label="关键词">
<el-input
v-model="searchForm.keyword"
placeholder="协议号/样本编码/样本名称"
clearable
/>
</el-form-item>
<el-form-item label="协议类别">
<el-select
v-model="searchForm.receptionSource"
clearable
placeholder="全部"
style="width: 100%"
>
<el-option
v-for="item in receptionSourceOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="审核结论">
<el-select
v-model="searchForm.reviewConclusion"
clearable
placeholder="全部"
style="width: 100%"
>
<el-option label="待审核" value="" />
<el-option
v-for="item in reviewConclusionOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="入库状态">
<el-select
v-model="searchForm.storageStatus"
clearable
placeholder="全部"
style="width: 100%"
>
<el-option
v-for="item in storageStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</div>
<div class="filter-actions-row">
<span class="filter-hint">支持按协议号、样本编码、样本名称搜索。</span>
<div class="filter-actions-end">
<el-button class="btn-ghost" @click="handleReset">
<el-icon><RefreshLeft /></el-icon>重置
</el-button>
<el-button class="btn-main" type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>搜索
</el-button>
</div>
</div>
</el-form>
</div>
<div class="list-toolbar">
<div class="list-toolbar-row">
<div class="list-toolbar-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
<el-button
type="primary"
class="btn-main"
:disabled="!canBatchReview"
@click="openReviewDialog()"
>
<el-icon><CircleCheck /></el-icon>
批量审核
</el-button>
<el-button class="btn-ghost" :disabled="!canBatchStore" @click="handleBatchStore">
<el-icon><Box /></el-icon>
批量入库
</el-button>
</div>
</div>
<div class="list-body list-wrap">
<el-table
class="list-table"
:data="tableData"
v-loading="loading"
stripe
row-key="id"
@selection-change="handleSelectionChange"
@row-dblclick="handleView"
>
<el-table-column type="selection" width="50" align="center" header-align="center" />
<el-table-column
prop="protocolNo"
label="协议编号"
width="160"
align="center"
header-align="center"
/>
<el-table-column
prop="sampleCode"
label="样本编码"
width="140"
align="center"
header-align="center"
/>
<el-table-column
prop="sampleName"
label="样本名称"
width="140"
align="center"
header-align="center"
/>
<el-table-column
prop="receptionSource"
label="协议类别"
width="100"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-tag :type="receptionSourceTagType(row.receptionSource || '')" size="small">
{{ receptionSourceLabel(row.receptionSource || '') }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="reviewConclusion"
label="审核结论"
width="100"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-tag
v-if="row.reviewConclusion"
:type="storageReleaseConclusionTagType(row.reviewConclusion)"
size="small"
>
{{ storageReleaseConclusionLabel(row.reviewConclusion) }}
</el-tag>
<el-tag v-else type="info" size="small">待审核</el-tag>
</template>
</el-table-column>
<el-table-column
prop="storageStatus"
label="入库状态"
width="100"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-tag :type="storageStatusTagType(row.storageStatus || 'drk')" size="small">
{{ storageStatusLabel(row.storageStatus || 'drk') }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="reviewByName"
label="审核人"
width="100"
align="center"
header-align="center"
/>
<el-table-column
prop="reviewTime"
label="审核时间"
width="170"
align="center"
header-align="center"
/>
<el-table-column
label="操作"
min-width="260"
fixed="right"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-button class="btn-op-item" @click="handleView(row)">
<el-icon><View /></el-icon>查看
</el-button>
<el-button class="btn-op-item" @click="openReviewDialog(row)">
<el-icon><CircleCheck /></el-icon>审核
</el-button>
<el-button
v-if="row.reviewConclusion === 'tg' && row.storageStatus === 'drk'"
class="btn-op-item"
@click="handleStore(row)"
>
<el-icon><Box /></el-icon>样本入库
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
@size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
@current-change="(val) => ((pagination.page = val), loadData())"
/>
</div>
</div>
<StorageReleaseViewDialog v-model:visible="viewVisible" :review-id="viewReviewId" />
<StorageReleaseReviewDialog
v-model="reviewDialogVisible"
:review-ids="reviewDialogIds"
@submitted="onReviewSubmitted"
/>
</section>
</div>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import { View, Search, RefreshLeft, CircleCheck, Box } from '@element-plus/icons-vue'
import StorageReleaseViewDialog from './StorageReleaseViewDialog.vue'
import StorageReleaseReviewDialog from './StorageReleaseReviewDialog.vue'
import {
fetchStorageReleaseReviewList,
storeSample,
batchStoreSample,
type StorageReleaseReview
} from '@/api/storage-release-review'
import {
receptionSourceLabel,
receptionSourceTagType,
storageReleaseConclusionLabel,
storageReleaseConclusionTagType,
storageStatusLabel,
storageStatusTagType
} from '@/utils/status-tag-registry'
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
defineOptions({ name: 'QualityInspectionStorageReleaseReview' })
const receptionSourceOptions = [
{ value: 'cell_storage', label: '细胞存储' },
{ value: 'research_donation', label: '科研捐赠' }
]
const reviewConclusionOptions = [
{ value: 'tg', label: '通过' },
{ value: 'btg', label: '不通过' }
]
const storageStatusOptions = [
{ value: 'drk', label: '待入库' },
{ value: 'yrk', label: '已入库' }
]
const searchForm = reactive({
receptionSource: '',
reviewConclusion: '',
storageStatus: '',
keyword: ''
})
const tableData = ref<StorageReleaseReview[]>([])
const selectedRows = ref<StorageReleaseReview[]>([])
const loading = ref(false)
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
const handleSelectionChange = (rows: StorageReleaseReview[]) => {
selectedRows.value = rows
}
const canBatchReview = computed(() => selectedRows.value.length > 0)
const canBatchStore = computed(
() =>
selectedRows.value.length > 0 &&
selectedRows.value.every((r) => r.reviewConclusion === 'tg' && r.storageStatus === 'drk')
)
async function loadData() {
loading.value = true
try {
const res = await fetchStorageReleaseReviewList({
page: pagination.page,
pageSize: pagination.pageSize,
receptionSource: searchForm.receptionSource || undefined,
reviewConclusion: searchForm.reviewConclusion || undefined,
storageStatus: searchForm.storageStatus || undefined,
keyword: searchForm.keyword || undefined
})
tableData.value = res.list || []
pagination.total = res.total ?? 0
} finally {
loading.value = false
}
}
function handleSearch() {
pagination.page = 1
loadData()
}
function handleReset() {
searchForm.receptionSource = ''
searchForm.reviewConclusion = ''
searchForm.storageStatus = ''
searchForm.keyword = ''
pagination.page = 1
loadData()
}
const viewVisible = ref(false)
const viewReviewId = ref('')
function handleView(row: StorageReleaseReview) {
viewReviewId.value = String(row.id)
viewVisible.value = true
}
const reviewDialogVisible = ref(false)
const reviewDialogIds = ref<string[]>([])
function openReviewDialog(row?: StorageReleaseReview) {
const rows = row ? [row] : selectedRows.value
if (!rows.length) {
ElMessage.warning('请先选择要审核的记录')
return
}
reviewDialogIds.value = rows.map((r) => String(r.id))
reviewDialogVisible.value = true
}
function onReviewSubmitted() {
selectedRows.value = []
loadData()
}
async function handleStore(row: StorageReleaseReview) {
await ElMessageBox.confirm(`确认将样本「${row.sampleCode || ''}」执行入库操作?`, '入库确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
await storeSample(String(row.id))
ElMessage.success('入库成功')
loadData()
}
async function handleBatchStore() {
if (!selectedRows.value.length) return
await ElMessageBox.confirm(
`确认将选中的 ${selectedRows.value.length} 条记录执行入库操作?`,
'批量入库',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
await withFullscreenLoading(async () => {
const ids = selectedRows.value.map((r) => String(r.id))
await batchStoreSample({ ids })
ElMessage.success(`批量入库成功,共 ${ids.length} 条`)
selectedRows.value = []
loadData()
}, fullscreenLoadingText.save)
}
onMounted(() => {
loadData()
})
</script>