提交
This commit is contained in:
1 parent
15ed25e33b
commit
dc31968204
143 files changed
+15823
-9989
No files matched your search
@@ -1728,7 +1728,7 @@ export function fetchDeleteCheckupInstitutionPackage(packageId: string) {
|
||||
return request.del({ url: `/web/admin/checkup-institution-packages/${packageId}` })
|
||||
}
|
||||
|
||||
// ============ 保险公司管理 ============
|
||||
// ============ 保险公司及险种价格管理 ============
|
||||
|
||||
export function fetchInsuranceCompanyList(params?: {
|
||||
page?: number
|
||||
@@ -1767,7 +1767,7 @@ export function fetchDeleteInsuranceCompanyInvoice(id: string) {
|
||||
return request.del({ url: `/web/admin/insurance-company-invoices/${id}` })
|
||||
}
|
||||
|
||||
// 保险项目价格
|
||||
// 保险险种价格
|
||||
export function fetchInsuranceProjectPrices(companyId: string) {
|
||||
return request.get({ url: `/web/admin/insurance-companies/${companyId}/project-prices` })
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ export function fetchCollectionProtocolDetail(id: string) {
|
||||
payment: Record<string, unknown> | null
|
||||
paymentDetails: Array<Record<string, unknown>> | null
|
||||
attachments?: Array<Record<string, unknown>>
|
||||
storageCert?: Record<string, unknown> | null
|
||||
}>({
|
||||
url: `/web/admin/collection-protocols/${id}`
|
||||
})
|
||||
|
||||
@@ -47,6 +47,27 @@ export type SampleReceptionCheckResult = {
|
||||
orderNum?: number
|
||||
}
|
||||
|
||||
export function fetchSampleReceptionUpdate(
|
||||
receptionId: string,
|
||||
data: {
|
||||
receptionTime: string
|
||||
handlerUserId?: string
|
||||
handlerName?: string
|
||||
receiveTemperature?: string
|
||||
volumeMl?: number
|
||||
netWeightG?: number
|
||||
checks: Array<{ checkItemId: string; checked: boolean }>
|
||||
acceptConclusion: 'js' | 'bj'
|
||||
receiveAttachments?: string
|
||||
remark?: string
|
||||
}
|
||||
) {
|
||||
return request.put({
|
||||
url: `/web/admin/sample-receptions/${receptionId}`,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchSampleReceptionSubmit(data: {
|
||||
protocolSampleId?: string
|
||||
sourceType?: 'cell_storage' | 'research_donation'
|
||||
|
||||
@@ -678,6 +678,49 @@ body .el-table thead {
|
||||
}
|
||||
// -------------------------------- el-table 全局样式 end --------------------------------
|
||||
|
||||
// border 模式:仅保留表头列之间的分割线以支持拖拽调整列宽
|
||||
// 覆盖 Element Plus el-table--border 的所有默认边框
|
||||
.page-container .el-table--border,
|
||||
.console-container .el-table--border,
|
||||
.el-dialog .el-table--border {
|
||||
// 数据行:移除纵向边框
|
||||
td.el-table__cell {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
// 表头:仅列之间保留淡色分割线,去掉底部边框
|
||||
th.el-table__cell {
|
||||
border-right: 1px solid var(--mint-border) !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
th.el-table__cell:first-child {
|
||||
border-left: none !important;
|
||||
}
|
||||
|
||||
th.el-table__cell:last-child {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
// 移除表格外边框(左 ::before、右 ::after)
|
||||
&::after,
|
||||
&::before {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
// 移除表格顶部边框
|
||||
.el-table__inner-wrapper::after {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
// 移除边框补丁元素
|
||||
.el-table__border-left-patch,
|
||||
.el-table__border-bottom-patch,
|
||||
.el-table__border-right-patch {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.page-container .el-table,
|
||||
.console-container .el-table {
|
||||
--el-table-header-bg-color: var(--mint-table-head);
|
||||
|
||||
@@ -180,6 +180,14 @@
|
||||
border-top: 1px dashed #e2e8f0;
|
||||
}
|
||||
|
||||
.sample-card__section-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.sample-qc-empty,
|
||||
.sample-qc-hint {
|
||||
font-size: 13px;
|
||||
@@ -216,10 +224,6 @@
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&--task {
|
||||
border-left: 3px solid #0ea5e9;
|
||||
}
|
||||
}
|
||||
|
||||
.qc-detail-card__header {
|
||||
|
||||
@@ -357,6 +357,10 @@
|
||||
// 标签页操作逻辑
|
||||
const useTabOperations = (adjustPositionAfterClose: () => void) => {
|
||||
const clickTab = (item: WorkTab) => {
|
||||
if (item.path === activeTab.value) {
|
||||
useCommon().refresh()
|
||||
return
|
||||
}
|
||||
router.push({
|
||||
path: item.path,
|
||||
query: item.query as LocationQueryRaw
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_sample_qc_info_cards_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/components/business/sample-qc-info-cards/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_payment_info_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/payment-info/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_material_stock_log_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material-stock-log/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_material_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L1"}, {"id": "material_index_getcategorylabel", "label": "getCategoryLabel()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L432"}, {"id": "material_index_getcategorytagtype", "label": "getCategoryTagType()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L438"}, {"id": "material_index_handleselectionchange", "label": "handleSelectionChange()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L444"}, {"id": "material_index_loaddictoptions", "label": "loadDictOptions()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L483"}, {"id": "material_index_loaddata", "label": "loadData()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L492"}, {"id": "material_index_handlesearch", "label": "handleSearch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L509"}, {"id": "material_index_handlereset", "label": "handleReset()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L514"}, {"id": "material_index_resetform", "label": "resetForm()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L522"}, {"id": "material_index_handleadd", "label": "handleAdd()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L533"}, {"id": "material_index_handleedit", "label": "handleEdit()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L541"}, {"id": "material_index_handlesubmit", "label": "handleSubmit()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L556"}, {"id": "material_index_handleview", "label": "handleView()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L584"}, {"id": "material_index_handledelete", "label": "handleDelete()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L589"}, {"id": "material_index_handlestockin", "label": "handleStockIn()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L596"}, {"id": "material_index_handlestockinsubmit", "label": "handleStockInSubmit()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L604"}, {"id": "material_index_handlestockout", "label": "handleStockOut()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L622"}, {"id": "material_index_handlestockoutsubmit", "label": "handleStockOutSubmit()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L633"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_material_index_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L356", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_material_index_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/material/index.vue", "source_location": "L357", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_material_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_system_view_dialog_index_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/vLine truncated
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "label": "DonationViewDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L1"}, {"id": "list_donationviewdialog_dash", "label": "dash()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L684"}, {"id": "list_donationviewdialog_formatymd", "label": "formatYmd()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L689"}, {"id": "list_donationviewdialog_formatym", "label": "formatYm()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L695"}, {"id": "list_donationviewdialog_auditordisplay", "label": "auditorDisplay()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L701"}, {"id": "list_donationviewdialog_sampletypename", "label": "sampleTypeName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L707"}, {"id": "list_donationviewdialog_unitname", "label": "unitName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L711"}, {"id": "list_donationviewdialog_sitename", "label": "siteName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L715"}, {"id": "list_donationviewdialog_expressname", "label": "expressName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L719"}, {"id": "list_donationviewdialog_if", "label": "if()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L724"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L512", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolattachmentreadonly_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L513", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_sample_qc_info_cards_index_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L514", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_research_donation", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L515", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "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/research-donation/list/DonationViewDialog.vue", "source_location": "L520", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_download_attachment", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L529", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": Line truncated
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_basic_info_insurance_company_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/basic-info/insurance-company/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolviewdialog_vue", "label": "ProtocolViewDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L1"}, {"id": "protocol_protocolviewdialog_dash", "label": "dash()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L284"}, {"id": "protocol_protocolviewdialog_handleclosed", "label": "handleClosed()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L1583"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolviewdialog_vue", "target": "protocol_protocolviewdialog_dash", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L284", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolviewdialog_vue", "target": "protocol_protocolviewdialog_handleclosed", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L1583", "weight": 1.0}], "raw_calls": [{"caller_nid": "protocol_protocolviewdialog_dash", "callee": "unitName", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L284"}]}
|
||||
+1
@@ -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": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_collection_protocol_ts", "label": "collection-protocol.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L1"}, {"id": "api_collection_protocol_fetchcollectionprotocollist", "label": "fetchCollectionProtocolList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L14"}, {"id": "api_collection_protocol_fetchcollectionprotocoldetail", "label": "fetchCollectionProtocolDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L26"}, {"id": "api_collection_protocol_fetchcollectionprotocolauditlist", "label": "fetchCollectionProtocolAuditList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L41"}, {"id": "api_collection_protocol_fetchsubmitprotocolaudit", "label": "fetchSubmitProtocolAudit()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L53"}, {"id": "api_collection_protocol_fetchreceiptconfirmlist", "label": "fetchReceiptConfirmList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L65"}, {"id": "api_collection_protocol_fetchreceiptconfirmedlist", "label": "fetchReceiptConfirmedList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L72"}, {"id": "api_collection_protocol_fetchreceiptconfirmsubmit", "label": "fetchReceiptConfirmSubmit()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L79"}, {"id": "api_collection_protocol_fetchrefundconfirmlist", "label": "fetchRefundConfirmList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L90"}, {"id": "api_collection_protocol_fetchrefundconfirmedlist", "label": "fetchRefundConfirmedList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L97"}, {"id": "api_collection_protocol_fetchrefundconfirmsubmit", "label": "fetchRefundConfirmSubmit()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L104"}, {"id": "api_collection_protocol_fetchsubmitreceiptfinance", "label": "fetchSubmitReceiptFinance()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L115"}, {"id": "api_collection_protocol_fetchsubmitreceiptfinancebatch", "label": "fetchSubmitReceiptFinanceBatch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L122"}, {"id": "api_collection_protocol_fetchsubmitrefundfinance", "label": "fetchSubmitRefundFinance()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L130"}, {"id": "api_collection_protocol_fetchsubmitrefundfinancebatch", "label": "fetchSubmitRefundFinanceBatch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L145"}, {"id": "api_collection_protocol_fetchpaymentdetaillist", "label": "fetchPaymentDetailList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L158"}, {"id": "api_collection_protocol_fetchcreatecollectionprotocol", "label": "fetchCreateCollectionProtocol()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L171"}, {"id": "api_collection_protocol_fetchupdatecollectionprotocol", "label": "fetchUpdateCollectionProtocol()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L179"}, {"id": "api_collection_protocol_fetchdeletecollectionprotocol", "label": "fetchDeleteCollectionProtocol()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts", "source_location": "L190"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_collection_protocol_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/collection-protocol.ts", "source_location": "L1", "weight": 1.0}, {"Line truncated
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_material_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-material/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolviewdialog_vue", "label": "ProtocolViewDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L1"}, {"id": "protocol_protocolviewdialog_dash", "label": "dash()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L310"}, {"id": "protocol_protocolviewdialog_handleclosed", "label": "handleClosed()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L1613"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolviewdialog_vue", "target": "protocol_protocolviewdialog_dash", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L310", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolviewdialog_vue", "target": "protocol_protocolviewdialog_handleclosed", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L1613", "weight": 1.0}], "raw_calls": [{"caller_nid": "protocol_protocolviewdialog_dash", "callee": "unitName", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue", "source_location": "L310"}]}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_fullmoon_todo_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/fullmoon-todo/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -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": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_admin_ts", "label": "admin.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L1"}, {"id": "api_admin_fetchgetmenulist", "label": "fetchGetMenuList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L10"}, {"id": "api_admin_fetchgetrolelist", "label": "fetchGetRoleList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L19"}, {"id": "api_admin_fetchuseroptions", "label": "fetchUserOptions()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L26"}, {"id": "api_admin_fetchroleoptions", "label": "fetchRoleOptions()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L33"}, {"id": "api_admin_fetchtenantlist", "label": "fetchTenantList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L43"}, {"id": "api_admin_fetchcreatetenant", "label": "fetchCreateTenant()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L56"}, {"id": "api_admin_fetchupdatetenant", "label": "fetchUpdateTenant()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L71"}, {"id": "api_admin_fetchdeletetenant", "label": "fetchDeleteTenant()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L89"}, {"id": "api_admin_fetchuserlist", "label": "fetchUserList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L100"}, {"id": "api_admin_fetchuserdetail", "label": "fetchUserDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L119"}, {"id": "api_admin_fetchcreateuser", "label": "fetchCreateUser()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L125"}, {"id": "api_admin_fetchupdateuser", "label": "fetchUpdateUser()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L147"}, {"id": "api_admin_fetchdeleteuser", "label": "fetchDeleteUser()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L170"}, {"id": "api_admin_fetchresetpassword", "label": "fetchResetPassword()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L177"}, {"id": "api_admin_fetchuploaduseravatar", "label": "fetchUploadUserAvatar()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L185"}, {"id": "api_admin_fetchregionlist", "label": "fetchRegionList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L196"}, {"id": "api_admin_fetchactiveregions", "label": "fetchActiveRegions()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L214"}, {"id": "api_admin_fetchnotdeletedregions", "label": "fetchNotDeletedRegions()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L231"}, {"id": "api_admin_fetchcategorylist", "label": "fetchCategoryList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L259"}, {"id": "api_admin_fetchcreatecategory", "label": "fetchCreateCategory()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L277"}, {"id": "api_admin_fetchupdatecategory", "label": "fetchUpdateCategory()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L290"}, {"id": "api_admin_fetchdeletecategory", "label": "fetchDeleteCategory()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L307"}, {"id": "api_admin_fetchuploadcategoryimage", "label": "fetchUploadCategoryImage()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L313"}, {"id": "api_admin_fetchunitlist", "label": "fetchUnitList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts", "source_location": "L324"}, {"id": "api_admin_fetchcreateunit", "label": "fetchCrLine truncated
|
||||
+1
@@ -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": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_material_usage_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-material-usage/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_storage_cert_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/storage-cert/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_audit_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol-audit/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_fullmoon_todo_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/fullmoon-todo/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "label": "DonationViewDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L1"}, {"id": "list_donationviewdialog_dash", "label": "dash()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L663"}, {"id": "list_donationviewdialog_formatymd", "label": "formatYmd()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L668"}, {"id": "list_donationviewdialog_formatym", "label": "formatYm()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L674"}, {"id": "list_donationviewdialog_auditordisplay", "label": "auditorDisplay()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L680"}, {"id": "list_donationviewdialog_sampletypename", "label": "sampleTypeName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L686"}, {"id": "list_donationviewdialog_unitname", "label": "unitName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L690"}, {"id": "list_donationviewdialog_sitename", "label": "siteName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L694"}, {"id": "list_donationviewdialog_expressname", "label": "expressName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L698"}, {"id": "list_donationviewdialog_if", "label": "if()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L703"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L491", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolattachmentreadonly_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L492", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_sample_qc_info_cards_index_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L493", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_research_donation", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L494", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "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/research-donation/list/DonationViewDialog.vue", "source_location": "L499", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_download_attachment", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/research-donation/list/DonationViewDialog.vue", "source_location": "L508", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_research_donation_list_donationviewdialog_vue", "target": Line truncated
|
||||
+1
@@ -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": "L370"}], "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
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
@@ -1,6 +1,10 @@
|
||||
import './utils/patch-el-select-filterable'
|
||||
import App from './App.vue'
|
||||
import { createApp } from 'vue'
|
||||
import { ElTable } from 'element-plus'
|
||||
|
||||
ElTable.props.border = { type: Boolean, default: true }
|
||||
|
||||
import { initStore } from './store' // Store
|
||||
import { initRouter } from './router' // Router
|
||||
import language from './locales' // 国际化
|
||||
|
||||
@@ -85,7 +85,7 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [
|
||||
name: 'Unit',
|
||||
path: 'unit',
|
||||
component: '/system/unit/index',
|
||||
meta: { title: '物品单位', keepAlive: true, menuCode: 'system:unit:list' }
|
||||
meta: { title: '物品单位管理', keepAlive: true, menuCode: 'system:unit:list' }
|
||||
},
|
||||
{
|
||||
id: '110',
|
||||
@@ -183,7 +183,7 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [
|
||||
name: 'BasicInfoInsuranceCompany',
|
||||
path: 'insurance-company',
|
||||
component: '/basic-info/insurance-company/index',
|
||||
meta: { title: '保险公司及项目价格', keepAlive: true }
|
||||
meta: { title: '保险公司及险种价格', keepAlive: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -209,7 +209,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Collection } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Collection, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
fetchCellSampleTypeList,
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -363,7 +363,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Tickets } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Tickets, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按地区名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -332,7 +332,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Location } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Location, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">支持模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -226,7 +226,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, User } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, User, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
fetchCustomerList,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!-- 保险公司 — 项目价格配置 -->
|
||||
<!-- 保险公司 — 险种价格配置 -->
|
||||
<!-- eslint-disable vue/no-v-model-argument -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
<h1>保险公司及项目价格</h1>
|
||||
<p class="page-desc">维护保险公司基础信息、开票信息及项目价格管理。</p>
|
||||
<h1>保险公司及险种价格</h1>
|
||||
<p class="page-desc">维护保险公司基础信息、开票信息及险种价格管理。</p>
|
||||
</header>
|
||||
|
||||
<section class="panel">
|
||||
@@ -80,7 +80,7 @@
|
||||
<el-table-column label="操作" width="400" 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 primary" @click="openItemDialog(row)"><el-icon><Setting /></el-icon>项目价格配置</el-button>
|
||||
<el-button class="btn-op-item primary" @click="openItemDialog(row)"><el-icon><Setting /></el-icon>险种价格配置</el-button>
|
||||
<el-button class="btn-op-item" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
</template>
|
||||
@@ -161,7 +161,7 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 项目价格配置弹窗 -->
|
||||
<!-- 险种价格配置弹窗 -->
|
||||
<el-dialog
|
||||
v-model="itemDialogVisible"
|
||||
:title="itemDialogTitle"
|
||||
@@ -173,7 +173,7 @@
|
||||
@closed="onItemDialogClosed"
|
||||
>
|
||||
<div class="item-dialog-head">
|
||||
<span class="muted">当前公司:{{ currentCompanyName }}</span>
|
||||
<span class="muted">当前保险公司:{{ currentCompanyName }}</span>
|
||||
</div>
|
||||
|
||||
<div class="list-toolbar item-toolbar">
|
||||
@@ -182,7 +182,7 @@
|
||||
已选 <span class="sel-num">{{ itemSelected.length }}</span> 项
|
||||
</div>
|
||||
<el-button class="btn-main" type="primary" @click="handleAddItem">
|
||||
<el-icon><Plus /></el-icon>新增项目
|
||||
<el-icon><Plus /></el-icon>新增险种
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -198,8 +198,8 @@
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" header-align="center" />
|
||||
<el-table-column type="index" label="序号" width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="projectFee" label="项目费用(元)" width="120" align="center" header-align="center">
|
||||
<el-table-column prop="projectName" label="险种名称" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="projectFee" label="险种费用(元)" width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.projectFee != null ? `¥${row.projectFee}` : '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="90" align="center" header-align="center">
|
||||
@@ -228,7 +228,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑项目嵌套弹窗 -->
|
||||
<!-- 新增/编辑险种嵌套弹窗 -->
|
||||
<el-dialog
|
||||
v-model="itemFormVisible"
|
||||
:title="itemFormTitle"
|
||||
@@ -241,10 +241,10 @@
|
||||
>
|
||||
<el-form ref="itemFormRef" :model="itemForm" :rules="itemFormRules" label-position="top" class="biz-dialog-form">
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="itemForm.projectName" />
|
||||
<el-form-item label="险种名称" prop="projectName">
|
||||
<el-input v-model="itemForm.projectName" placeholder="请输入险种名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目费用(元)" prop="projectFee">
|
||||
<el-form-item label="险种费用(元)" prop="projectFee">
|
||||
<el-input-number v-model="itemForm.projectFee" :min="0" :precision="2" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
@@ -344,10 +344,10 @@
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 项目价格配置弹窗
|
||||
// 险种价格配置弹窗
|
||||
// ---------------------------------------------------------------------------
|
||||
const itemDialogVisible = ref(false)
|
||||
const itemDialogTitle = ref('项目价格配置')
|
||||
const itemDialogTitle = ref('险种价格配置')
|
||||
const currentCompanyId = ref('')
|
||||
const currentCompanyName = ref('')
|
||||
const itemTable = ref<any[]>([])
|
||||
@@ -356,7 +356,7 @@
|
||||
const itemPagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
|
||||
const itemFormVisible = ref(false)
|
||||
const itemFormTitle = ref('新增项目')
|
||||
const itemFormTitle = ref('新增险种')
|
||||
const itemIsEdit = ref(false)
|
||||
const itemSubmitLoading = ref(false)
|
||||
const itemFormRef = ref()
|
||||
@@ -368,7 +368,7 @@
|
||||
remark: ''
|
||||
})
|
||||
const itemFormRules = {
|
||||
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
|
||||
projectName: [{ required: true, message: '请输入险种名称', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
}
|
||||
|
||||
@@ -527,7 +527,7 @@
|
||||
|
||||
async function handleDelete(row: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除「${row.companyName}」?其下属项目价格将一并删除。`, '提示', {
|
||||
await ElMessageBox.confirm(`确定删除「${row.companyName}」?其下属险种价格将一并删除。`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
@@ -582,14 +582,14 @@
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 项目价格配置弹窗
|
||||
// 险种价格配置弹窗
|
||||
// ---------------------------------------------------------------------------
|
||||
function openItemDialog(row: any) {
|
||||
currentCompanyId.value = row.id
|
||||
currentCompanyName.value = row.companyName || ''
|
||||
itemPagination.page = 1
|
||||
itemSelected.value = []
|
||||
itemDialogTitle.value = `项目价格配置 — ${row.companyName}`
|
||||
itemDialogTitle.value = `险种价格配置 — ${row.companyName}`
|
||||
itemDialogVisible.value = true
|
||||
loadItems()
|
||||
}
|
||||
@@ -627,14 +627,14 @@
|
||||
|
||||
function handleAddItem() {
|
||||
itemIsEdit.value = false
|
||||
itemFormTitle.value = '新增项目'
|
||||
itemFormTitle.value = '新增险种'
|
||||
resetItemForm()
|
||||
itemFormVisible.value = true
|
||||
}
|
||||
|
||||
function handleEditItem(row: any) {
|
||||
itemIsEdit.value = true
|
||||
itemFormTitle.value = '编辑项目'
|
||||
itemFormTitle.value = '编辑险种'
|
||||
editingItemId.value = row.id
|
||||
itemForm.projectName = row.projectName || ''
|
||||
itemForm.projectFee = row.projectFee ?? 0
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -378,7 +378,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Ticket } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Ticket, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
<h1>满月待办管理</h1>
|
||||
<p class="page-desc">集中管理满月待办保险办理情况,支持筛选与批量办理。</p>
|
||||
<p class="page-desc">
|
||||
展示全部已生效采集协议;已满月且未办保险的行以红色背景标识,其余为正常底色。
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-top">
|
||||
<h2 class="title-bar-heading">待办列表</h2>
|
||||
<h2>待办列表</h2>
|
||||
</div>
|
||||
|
||||
<div class="filter-block">
|
||||
@@ -27,7 +29,7 @@
|
||||
<el-select v-model="searchForm.todoStatus" placeholder="全部" clearable style="width: 100%">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="未办保险" value="0" />
|
||||
<el-option label="已办报销" value="1" />
|
||||
<el-option label="已办保险" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@@ -44,14 +46,34 @@
|
||||
</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
|
||||
class="btn-ghost"
|
||||
:disabled="selectedRows.length === 0"
|
||||
@click="handleBatchEdit"
|
||||
>
|
||||
<el-icon><EditPen /></el-icon>批量办理保险
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
:row-class-name="fullmoonRowClassName"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -60,29 +82,34 @@
|
||||
header-align="center"
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column prop="protocolNo" label="采集协议号" width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="motherName" label="母亲姓名" width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="newbornDeliveryTime" label="新生儿分娩时间" width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="fullmoonDate" label="满月日期" width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="todoStatus" label="待办状态" width="100" align="center" header-align="center">
|
||||
<el-table-column prop="protocolNo" label="采集协议号" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="motherName" label="母亲姓名" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="newbornDeliveryTime" label="新生儿分娩时间" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="fullmoonDate" label="满月日期" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="todoStatus" label="待办状态" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.todoStatus == 1 ? 'success' : 'warning'" size="small">
|
||||
{{ row.todoStatus == 1 ? '已办报销' : '未办保险' }}
|
||||
{{ row.todoStatus == 1 ? '已办保险' : '未办保险' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="insuranceSubscribed" label="是否已订购保险" width="120" align="center" header-align="center">
|
||||
<el-table-column prop="insuranceSubscribed" label="是否已订购保险" min-width="130" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.insuranceSubscribed == 1 ? 'success' : 'info'" size="small">
|
||||
{{ row.insuranceSubscribed == 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="insuranceCompanyName" label="保险公司" width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="insuranceType" label="保险险种" width="120" align="center" header-align="center" />
|
||||
<el-table-column label="操作" width="160" fixed="right" align="center" header-align="center">
|
||||
<el-table-column prop="insuranceCompanyName" label="保险公司" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="insuranceType" label="保险险种" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column label="操作" width="260" fixed="right" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">办理保险</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">
|
||||
<el-icon><EditPen /></el-icon>办理保险
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -99,16 +126,31 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查看弹窗(与采集协议单录入等一致) -->
|
||||
<ProtocolViewDialog
|
||||
v-model="viewVisible"
|
||||
:protocol-id="viewProtocolId"
|
||||
:list-row="viewRow"
|
||||
show-audit-history
|
||||
show-storage-cert
|
||||
show-fullmoon-insurance
|
||||
/>
|
||||
|
||||
<!-- 办理保险弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="办理保险"
|
||||
width="720px"
|
||||
:title="dialogTitle"
|
||||
width="960px"
|
||||
class="dialog-form-layout"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@closed="handleInsuranceDialogClosed"
|
||||
>
|
||||
<div v-if="isBatchMode" class="batch-insurance-tip">
|
||||
将为已选 <strong>{{ batchEditingRows.length }}</strong> 条记录统一填写保险信息(适用于团体险等场景)。
|
||||
<div v-if="batchProtocolSummary" class="batch-protocol-nos">{{ batchProtocolSummary }}</div>
|
||||
</div>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
@@ -118,7 +160,7 @@
|
||||
scroll-to-error
|
||||
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="保险公司" prop="insuranceCompanyId">
|
||||
<el-select
|
||||
v-model="formData.insuranceCompanyId"
|
||||
@@ -136,7 +178,23 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="保险险种" prop="insuranceType">
|
||||
<el-input v-model="formData.insuranceType" placeholder="请输入保险险种" />
|
||||
<el-select
|
||||
v-model="formData.insuranceType"
|
||||
placeholder="请先选择保险公司,再选择险种"
|
||||
filterable
|
||||
clearable
|
||||
:disabled="!formData.insuranceCompanyId"
|
||||
:loading="insuranceProjectLoading"
|
||||
style="width: 100%"
|
||||
@change="handleInsuranceTypeChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in insuranceProjectOptions"
|
||||
:key="p.id || p.projectName"
|
||||
:label="formatInsuranceProjectLabel(p)"
|
||||
:value="p.projectName"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="保险单号">
|
||||
<el-input v-model="formData.insurancePolicyNo" placeholder="请输入保险单号" />
|
||||
@@ -188,22 +246,81 @@
|
||||
<el-form-item label="待办状态">
|
||||
<el-radio-group v-model="formData.todoStatus">
|
||||
<el-radio :value="0">未办保险</el-radio>
|
||||
<el-radio :value="1">已办报销</el-radio>
|
||||
<el-radio :value="1">已办保险</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="保险单附件" class="form-col-span-2">
|
||||
<el-upload
|
||||
v-model:file-list="formData.attachments"
|
||||
action=""
|
||||
:auto-upload="false"
|
||||
:limit="10"
|
||||
multiple
|
||||
>
|
||||
<el-button type="primary" plain>选择文件</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持上传多个附件,单个文件不超过 20MB</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<div class="attachment-upload">
|
||||
<div class="attachment-list">
|
||||
<el-upload
|
||||
ref="attachmentUploadRef"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:multiple="true"
|
||||
accept="image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar"
|
||||
@change="handleAttachmentChange"
|
||||
>
|
||||
<div class="upload-card">
|
||||
<el-icon :size="28" color="#10b981"><Plus /></el-icon>
|
||||
<span class="upload-card-text">上传附件</span>
|
||||
</div>
|
||||
</el-upload>
|
||||
<div
|
||||
v-for="(file, index) in formData.attachments"
|
||||
:key="file.id || index"
|
||||
class="attachment-item"
|
||||
:class="{ 'is-uploading': file._uploading }"
|
||||
>
|
||||
<div
|
||||
class="attachment-preview"
|
||||
@click="
|
||||
isImageFile(file) && file.fileUrl
|
||||
? handlePreviewAttachment(file)
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
<template v-if="isImageFile(file) && file.fileUrl">
|
||||
<img :src="fullFileUrl(file.fileUrl)" :alt="file.fileName" />
|
||||
</template>
|
||||
<template v-else-if="isImageFile(file) && !file.fileUrl">
|
||||
<el-icon :size="24" color="#94a3b8"><Loading /></el-icon>
|
||||
</template>
|
||||
<template v-else-if="isVideoFile(file)">
|
||||
<div class="attachment-preview__icon attachment-preview__icon--video">
|
||||
<el-icon :size="28"><VideoPlay /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="attachment-preview__icon attachment-preview__icon--doc">
|
||||
<el-icon :size="28"><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="handleDownloadAttachment(file)"
|
||||
><Download
|
||||
/></el-icon>
|
||||
<el-icon
|
||||
class="action-btn action-btn--danger"
|
||||
title="删除"
|
||||
@click="removeAttachment(index)"
|
||||
><Delete
|
||||
/></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="upload-hint"
|
||||
>支持图片、视频、PDF、Word、Excel等文件,单文件不超过 20MB</span
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="办理备注" class="form-col-span-2">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入办理备注" />
|
||||
@@ -219,18 +336,48 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 附件预览弹窗 -->
|
||||
<el-dialog
|
||||
v-model="previewVisible"
|
||||
width="800px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:show-close="true"
|
||||
style="backdrop-filter: none"
|
||||
>
|
||||
<img v-if="previewUrl" :src="previewUrl" alt="预览" style="width: 100%" />
|
||||
</el-dialog>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, nextTick, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Close, Check, Search, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Close,
|
||||
Check,
|
||||
Search,
|
||||
RefreshLeft,
|
||||
View,
|
||||
EditPen,
|
||||
Plus,
|
||||
Loading,
|
||||
VideoPlay,
|
||||
Document,
|
||||
Download,
|
||||
Delete
|
||||
} from '@element-plus/icons-vue'
|
||||
import ProtocolViewDialog from '../protocol/ProtocolViewDialog.vue'
|
||||
import type { CollectionProtocolRow } from '@/api/collection-protocol'
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import {
|
||||
fetchFullmoonTodoList,
|
||||
fetchUpdateFullmoonTodoInsurance,
|
||||
fetchInsuranceCompanyList
|
||||
fetchInsuranceCompanyList,
|
||||
fetchInsuranceProjectPrices
|
||||
} from '@/api/admin'
|
||||
|
||||
defineOptions({ name: 'CellStorageFullmoonTodo' })
|
||||
@@ -240,9 +387,57 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
const searchForm = reactive({ keyword: '', todoStatus: '' })
|
||||
const tableData = ref<any[]>([])
|
||||
const tableRef = ref()
|
||||
const selectedRows = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
|
||||
function handleSelectionChange(rows: any[]) {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
function clearTableSelection() {
|
||||
tableRef.value?.clearSelection?.()
|
||||
selectedRows.value = []
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 查看弹窗
|
||||
// ---------------------------------------------------------------------------
|
||||
const viewVisible = ref(false)
|
||||
const viewProtocolId = ref<string | null>(null)
|
||||
const viewRow = ref<CollectionProtocolRow | null>(null)
|
||||
|
||||
function handleView(row: any) {
|
||||
const pid = String(row.protocolId || row.id || '')
|
||||
if (!pid) {
|
||||
ElMessage.warning('缺少关联协议单,无法查看')
|
||||
return
|
||||
}
|
||||
viewProtocolId.value = pid
|
||||
viewRow.value = row as CollectionProtocolRow
|
||||
viewVisible.value = true
|
||||
}
|
||||
|
||||
/** 已满月且待办状态非「已办保险」时标红 */
|
||||
function isInsuranceTodoPendingRow(row: Record<string, unknown>) {
|
||||
if (row.isFullmoonPassed === true) {
|
||||
return String(row.todoStatus ?? '0') !== '1'
|
||||
}
|
||||
const fullmoonDate = String(row.fullmoonDate || '').trim()
|
||||
if (!fullmoonDate) return false
|
||||
const fullmoon = new Date(fullmoonDate.replace(/-/g, '/'))
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
fullmoon.setHours(0, 0, 0, 0)
|
||||
if (Number.isNaN(fullmoon.getTime()) || fullmoon > today) return false
|
||||
return String(row.todoStatus ?? '0') !== '1'
|
||||
}
|
||||
|
||||
function fullmoonRowClassName({ row }: { row: Record<string, unknown> }) {
|
||||
return isInsuranceTodoPendingRow(row) ? 'row-insurance-pending' : ''
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 保险公司下拉
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -257,6 +452,59 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 险种价格下拉(按保险公司联动)
|
||||
// ---------------------------------------------------------------------------
|
||||
type InsuranceProjectOption = {
|
||||
id: string
|
||||
projectName: string
|
||||
projectFee: number | null
|
||||
status?: string
|
||||
}
|
||||
|
||||
const insuranceProjectOptions = ref<InsuranceProjectOption[]>([])
|
||||
const insuranceProjectLoading = ref(false)
|
||||
|
||||
function formatInsuranceProjectLabel(p: InsuranceProjectOption) {
|
||||
if (p.projectFee != null && !Number.isNaN(Number(p.projectFee))) {
|
||||
return `${p.projectName}(¥${p.projectFee})`
|
||||
}
|
||||
return p.projectName
|
||||
}
|
||||
|
||||
async function loadInsuranceProjects(companyId: string, keepType?: string) {
|
||||
if (!companyId) {
|
||||
insuranceProjectOptions.value = []
|
||||
return
|
||||
}
|
||||
insuranceProjectLoading.value = true
|
||||
try {
|
||||
const res = await fetchInsuranceProjectPrices(companyId)
|
||||
const list = Array.isArray(res) ? res : (res as any)?.list || []
|
||||
let options: InsuranceProjectOption[] = list
|
||||
.filter((p: any) => String(p.status ?? '1') === '1')
|
||||
.map((p: any) => ({
|
||||
id: p.id || '',
|
||||
projectName: p.projectName || '',
|
||||
projectFee: p.projectFee != null ? Number(p.projectFee) : null,
|
||||
status: p.status
|
||||
}))
|
||||
.filter((p: InsuranceProjectOption) => p.projectName)
|
||||
const typeName = (keepType || '').trim()
|
||||
if (typeName && !options.some((p) => p.projectName === typeName)) {
|
||||
options = [{ id: '', projectName: typeName, projectFee: null }, ...options]
|
||||
}
|
||||
insuranceProjectOptions.value = options
|
||||
} catch {
|
||||
const typeName = (keepType || '').trim()
|
||||
insuranceProjectOptions.value = typeName
|
||||
? [{ id: '', projectName: typeName, projectFee: null }]
|
||||
: []
|
||||
} finally {
|
||||
insuranceProjectLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 主表数据加载
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -295,6 +543,21 @@
|
||||
const submitLoading = ref(false)
|
||||
const formRef = ref()
|
||||
const editingRow = ref<any>(null)
|
||||
const isBatchMode = ref(false)
|
||||
const batchEditingRows = ref<any[]>([])
|
||||
|
||||
const dialogTitle = computed(() => (isBatchMode.value ? '批量办理保险' : '办理保险'))
|
||||
|
||||
const batchProtocolSummary = computed(() => {
|
||||
const nos = batchEditingRows.value.map((r) => r.protocolNo).filter(Boolean)
|
||||
if (!nos.length) return ''
|
||||
if (nos.length <= 5) return `协议号:${nos.join('、')}`
|
||||
return `协议号:${nos.slice(0, 5).join('、')} 等 ${nos.length} 条`
|
||||
})
|
||||
|
||||
function resolveProtocolId(row: any) {
|
||||
return String(row?.protocolId || row?.id || '').trim()
|
||||
}
|
||||
|
||||
const formData = reactive({
|
||||
insuranceCompanyId: '' as string,
|
||||
@@ -313,13 +576,14 @@
|
||||
|
||||
const formRules = {
|
||||
insuranceCompanyId: [{ required: true, message: '请选择保险公司', trigger: 'change' }],
|
||||
insuranceType: [{ required: true, message: '请输入保险险种', trigger: 'blur' }]
|
||||
insuranceType: [{ required: true, message: '请选择保险险种', trigger: 'change' }]
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
function resetFormFields() {
|
||||
formData.insuranceCompanyId = ''
|
||||
formData.insuranceCompanyName = ''
|
||||
formData.insuranceType = ''
|
||||
insuranceProjectOptions.value = []
|
||||
formData.insurancePolicyNo = ''
|
||||
formData.policyEffectiveDate = ''
|
||||
formData.policyExpiryDate = ''
|
||||
@@ -329,24 +593,88 @@
|
||||
formData.remark = ''
|
||||
formData.todoStatus = 0
|
||||
formData.attachments = []
|
||||
editingRow.value = null
|
||||
}
|
||||
|
||||
function handleEdit(row: any) {
|
||||
function resetDialogContext() {
|
||||
resetFormFields()
|
||||
editingRow.value = null
|
||||
isBatchMode.value = false
|
||||
batchEditingRows.value = []
|
||||
}
|
||||
|
||||
function handleInsuranceDialogClosed() {
|
||||
resetDialogContext()
|
||||
}
|
||||
|
||||
function parseInsuranceAttachmentsFromUrls(urls: string) {
|
||||
return String(urls || '')
|
||||
.split(',')
|
||||
.map((u) => u.trim())
|
||||
.filter(Boolean)
|
||||
.map((url, index) => {
|
||||
const rawName = url.split('/').pop() || ''
|
||||
const fileName = decodeURIComponent(rawName) || `附件${index + 1}`
|
||||
return { id: `existing_${index}`, fileName, fileUrl: url, fileType: '' }
|
||||
})
|
||||
}
|
||||
|
||||
function buildInsuranceAttachmentsValue() {
|
||||
return formData.attachments
|
||||
.filter((f: any) => f.fileUrl && !f._uploading)
|
||||
.map((f: any) => f.fileUrl)
|
||||
.join(',')
|
||||
}
|
||||
|
||||
function buildInsurancePayload() {
|
||||
return {
|
||||
insuranceCompanyId: formData.insuranceCompanyId || undefined,
|
||||
insuranceCompanyName: formData.insuranceCompanyName || undefined,
|
||||
insuranceType: formData.insuranceType?.trim() || undefined,
|
||||
insurancePolicyNo: formData.insurancePolicyNo?.trim() || undefined,
|
||||
insuranceEffectiveDate: formData.policyEffectiveDate || undefined,
|
||||
insuranceExpiryDate: formData.policyExpiryDate || undefined,
|
||||
insuranceSubscribed: String(formData.insuranceSubscribed ?? 0),
|
||||
insuranceFee: formData.insuranceFee || undefined,
|
||||
handleDate: formData.processDate || undefined,
|
||||
handleRemark: formData.remark?.trim() || undefined,
|
||||
todoStatus: String(formData.todoStatus ?? 0),
|
||||
insuranceAttachments: buildInsuranceAttachmentsValue()
|
||||
}
|
||||
}
|
||||
|
||||
function handleBatchEdit() {
|
||||
if (selectedRows.value.length === 0) {
|
||||
ElMessage.warning('请先选择待办记录')
|
||||
return
|
||||
}
|
||||
resetFormFields()
|
||||
isBatchMode.value = true
|
||||
batchEditingRows.value = [...selectedRows.value]
|
||||
editingRow.value = null
|
||||
dialogVisible.value = true
|
||||
formRef.value?.clearValidate?.()
|
||||
}
|
||||
|
||||
async function handleEdit(row: any) {
|
||||
resetDialogContext()
|
||||
editingRow.value = row
|
||||
resetForm()
|
||||
// 回填已有数据
|
||||
formData.insuranceCompanyId = row.insuranceCompanyId || ''
|
||||
formData.insuranceCompanyName = row.insuranceCompanyName || ''
|
||||
formData.insuranceType = row.insuranceType || ''
|
||||
formData.insurancePolicyNo = row.insurancePolicyNo || ''
|
||||
formData.policyEffectiveDate = row.policyEffectiveDate || ''
|
||||
formData.policyExpiryDate = row.policyExpiryDate || ''
|
||||
formData.insuranceSubscribed = row.insuranceSubscribed ?? 0
|
||||
formData.policyEffectiveDate =
|
||||
row.insuranceEffectiveDate || row.policyEffectiveDate || ''
|
||||
formData.policyExpiryDate = row.insuranceExpiryDate || row.policyExpiryDate || ''
|
||||
formData.insuranceSubscribed = Number(row.insuranceSubscribed ?? 0)
|
||||
formData.insuranceFee = row.insuranceFee ?? 0
|
||||
formData.processDate = row.processDate || ''
|
||||
formData.remark = row.remark || ''
|
||||
formData.todoStatus = row.todoStatus ?? 0
|
||||
formData.processDate = row.handleDate || row.processDate || ''
|
||||
formData.remark = row.handleRemark || row.remark || ''
|
||||
formData.todoStatus = Number(row.todoStatus ?? 0)
|
||||
formData.attachments = parseInsuranceAttachmentsFromUrls(row.insuranceAttachments || '')
|
||||
if (formData.insuranceCompanyId) {
|
||||
await loadInsuranceProjects(formData.insuranceCompanyId, formData.insuranceType)
|
||||
}
|
||||
dialogVisible.value = true
|
||||
formRef.value?.clearValidate?.()
|
||||
}
|
||||
@@ -354,37 +682,171 @@
|
||||
function handleInsuranceCompanyChange(val: string) {
|
||||
const found = insuranceCompanyOptions.value.find((c) => c.id === val)
|
||||
formData.insuranceCompanyName = found ? found.companyName : ''
|
||||
formData.insuranceType = ''
|
||||
formData.insuranceFee = 0
|
||||
loadInsuranceProjects(val)
|
||||
}
|
||||
|
||||
function handleInsuranceTypeChange(val: string) {
|
||||
const found = insuranceProjectOptions.value.find((p) => p.projectName === val)
|
||||
if (found?.projectFee != null && !Number.isNaN(Number(found.projectFee))) {
|
||||
formData.insuranceFee = Number(found.projectFee)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await formRef.value?.validate?.()
|
||||
if (!editingRow.value?.protocolId) return
|
||||
if (formData.attachments.some((f: any) => f._uploading)) {
|
||||
ElMessage.warning('附件上传中,请稍候')
|
||||
return
|
||||
}
|
||||
const payload = buildInsurancePayload()
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const payload = {
|
||||
insuranceCompanyId: formData.insuranceCompanyId || undefined,
|
||||
insuranceCompanyName: formData.insuranceCompanyName || undefined,
|
||||
insuranceType: formData.insuranceType?.trim() || undefined,
|
||||
insurancePolicyNo: formData.insurancePolicyNo?.trim() || undefined,
|
||||
policyEffectiveDate: formData.policyEffectiveDate || undefined,
|
||||
policyExpiryDate: formData.policyExpiryDate || undefined,
|
||||
insuranceSubscribed: formData.insuranceSubscribed,
|
||||
insuranceFee: formData.insuranceFee || undefined,
|
||||
processDate: formData.processDate || undefined,
|
||||
remark: formData.remark?.trim() || undefined,
|
||||
todoStatus: formData.todoStatus
|
||||
if (isBatchMode.value) {
|
||||
const rows = batchEditingRows.value
|
||||
let successCount = 0
|
||||
const failedNos: string[] = []
|
||||
for (const row of rows) {
|
||||
const protocolId = resolveProtocolId(row)
|
||||
if (!protocolId) {
|
||||
failedNos.push(row.protocolNo || '未知协议')
|
||||
continue
|
||||
}
|
||||
try {
|
||||
await fetchUpdateFullmoonTodoInsurance(protocolId, payload)
|
||||
successCount++
|
||||
} catch {
|
||||
failedNos.push(row.protocolNo || protocolId)
|
||||
}
|
||||
}
|
||||
if (successCount === 0) {
|
||||
ElMessage.error('批量办理失败,请重试')
|
||||
return
|
||||
}
|
||||
ElMessage.success(
|
||||
failedNos.length
|
||||
? `已成功办理 ${successCount} 条,${failedNos.length} 条失败`
|
||||
: `已成功为 ${successCount} 条记录办理保险`
|
||||
)
|
||||
dialogVisible.value = false
|
||||
clearTableSelection()
|
||||
await loadData()
|
||||
return
|
||||
}
|
||||
await fetchUpdateFullmoonTodoInsurance(editingRow.value.protocolId, payload)
|
||||
const protocolId = resolveProtocolId(editingRow.value)
|
||||
if (!protocolId) {
|
||||
ElMessage.warning('缺少关联协议,无法保存')
|
||||
return
|
||||
}
|
||||
await fetchUpdateFullmoonTodoInsurance(protocolId, payload)
|
||||
ElMessage.success('保存成功')
|
||||
dialogVisible.value = false
|
||||
await loadData()
|
||||
} catch {
|
||||
/* 校验或接口错误已由全局处理 */
|
||||
/* 单条校验或接口错误已由全局处理 */
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 附件上传(卡片化布局)
|
||||
// ---------------------------------------------------------------------------
|
||||
const attachmentUploadRef = ref()
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
function fullFileUrl(suffix: string) {
|
||||
if (!suffix) return ''
|
||||
if (suffix.startsWith('http://') || suffix.startsWith('https://') || suffix.startsWith('/'))
|
||||
return suffix
|
||||
return `${minioBaseUrl}/${suffix}`
|
||||
}
|
||||
|
||||
function isImageFile(file: any) {
|
||||
const url = (file.fileUrl || '').toLowerCase()
|
||||
const name = (file.fileName || '').toLowerCase()
|
||||
return (
|
||||
/\.(jpe?g|png|gif|bmp|webp|svg|ico|tiff?)(\?|$)/.test(url) ||
|
||||
/\.(jpe?g|png|gif|bmp|webp|svg|ico|tiff?)$/.test(name) ||
|
||||
(file.fileType || '').startsWith('image/')
|
||||
)
|
||||
}
|
||||
|
||||
function isVideoFile(file: any) {
|
||||
const url = (file.fileUrl || '').toLowerCase()
|
||||
const name = (file.fileName || '').toLowerCase()
|
||||
return (
|
||||
/\.(mp4|webm|ogg|mov|avi)(\?|$)/.test(url) ||
|
||||
/\.(mp4|webm|ogg|mov|avi)$/.test(name) ||
|
||||
(file.fileType || '').startsWith('video/')
|
||||
)
|
||||
}
|
||||
|
||||
async function handleAttachmentChange(uploadFile: any) {
|
||||
const file = uploadFile.raw || uploadFile
|
||||
if (!file) return
|
||||
|
||||
const maxSizeMb = 20
|
||||
if (file.size / 1024 / 1024 > maxSizeMb) {
|
||||
ElMessage.error(`文件大小不能超过 ${maxSizeMb}MB`)
|
||||
attachmentUploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
|
||||
const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
||||
formData.attachments.push({
|
||||
id: tempId,
|
||||
fileName: file.name,
|
||||
fileUrl: '',
|
||||
fileSize: file.size,
|
||||
fileSizeDisplay: `${(file.size / 1024).toFixed(1)} KB`,
|
||||
fileType: file.type || '',
|
||||
_uploading: true
|
||||
})
|
||||
const idx = formData.attachments.length - 1
|
||||
|
||||
try {
|
||||
const fd = new FormData()
|
||||
fd.append('folder', 'fullmoon-todo')
|
||||
fd.append('file', file)
|
||||
const res = await fetchUploadAttachment(fd)
|
||||
formData.attachments[idx] = {
|
||||
...formData.attachments[idx],
|
||||
id: res.id || tempId,
|
||||
fileName: file.name || res.fileName,
|
||||
fileUrl: res.fileUrl,
|
||||
fileSize: res.fileSize || file.size,
|
||||
fileSizeDisplay: res.fileSizeDisplay || `${(file.size / 1024).toFixed(1)} KB`,
|
||||
fileType: res.fileType || file.type || '',
|
||||
_uploading: false
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('文件上传失败')
|
||||
formData.attachments.splice(idx, 1)
|
||||
} finally {
|
||||
nextTick(() => attachmentUploadRef.value?.clearFiles())
|
||||
}
|
||||
}
|
||||
|
||||
function removeAttachment(index: number) {
|
||||
formData.attachments.splice(index, 1)
|
||||
}
|
||||
|
||||
/* 附件预览 */
|
||||
const previewVisible = ref(false)
|
||||
const previewUrl = ref('')
|
||||
|
||||
function handlePreviewAttachment(file: any) {
|
||||
if (file._uploading) return
|
||||
previewUrl.value = fullFileUrl(file.fileUrl)
|
||||
previewVisible.value = true
|
||||
}
|
||||
|
||||
function handleDownloadAttachment(file: any) {
|
||||
downloadAttachmentFile(file, { fullFileUrl })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
loadInsuranceCompanies()
|
||||
@@ -392,9 +854,164 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.batch-insurance-tip {
|
||||
margin-bottom: 12px;
|
||||
padding: 10px 12px;
|
||||
background: #f0fdf4;
|
||||
border: 1px solid #a7f3d0;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
color: #334155;
|
||||
line-height: 1.5;
|
||||
|
||||
strong {
|
||||
color: #059669;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-protocol-nos {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.biz-dialog-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 附件卡片化布局 */
|
||||
.attachment-upload {
|
||||
width: 100%;
|
||||
}
|
||||
.attachment-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.upload-card {
|
||||
width: 140px;
|
||||
height: 96px;
|
||||
border: 1px dashed #a7f3d0;
|
||||
border-radius: 8px;
|
||||
background: #f0fdf4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
border-color: #10b981;
|
||||
border-style: solid;
|
||||
background: #ecfdf5;
|
||||
}
|
||||
}
|
||||
.upload-card-text {
|
||||
font-size: 12px;
|
||||
color: #10b981;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
.upload-hint {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
margin-top: 8px;
|
||||
line-height: 1;
|
||||
}
|
||||
.attachment-item {
|
||||
width: 140px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
&.is-uploading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
&:hover {
|
||||
border-color: #10b981;
|
||||
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
}
|
||||
.attachment-preview {
|
||||
width: 100%;
|
||||
height: 96px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8fafc;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.attachment-preview__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&--video {
|
||||
background: #ede9fe;
|
||||
color: #7c3aed;
|
||||
}
|
||||
&--doc {
|
||||
background: #e0f2fe;
|
||||
color: #0284c7;
|
||||
}
|
||||
}
|
||||
.attachment-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
.attachment-name {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-size: 11px;
|
||||
color: #334155;
|
||||
line-height: 1.4;
|
||||
word-break: break-all;
|
||||
}
|
||||
.attachment-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.action-btn {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #10b981;
|
||||
color: #fff;
|
||||
}
|
||||
&--danger:hover {
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.list-table :deep(tr.row-insurance-pending > td.el-table__cell) {
|
||||
background-color: #fee2e2 !important;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.list-table :deep(tr.row-insurance-pending:hover > td.el-table__cell) {
|
||||
background-color: #fecaca !important;
|
||||
}
|
||||
</style>
|
||||
@@ -39,8 +39,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按物料名称/编码查询,可按变动类型和日期范围筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -53,6 +53,7 @@
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
@@ -126,6 +127,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="操作" width="160" 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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination list-pager-bar">
|
||||
<el-pagination
|
||||
@@ -139,12 +147,23 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查看弹窗 -->
|
||||
<SystemViewDialog
|
||||
v-model:visible="viewVisible"
|
||||
:title="viewRow?.materialName || '变动记录详情'"
|
||||
icon="Document"
|
||||
:meta="viewRow?.createTime || ''"
|
||||
:sections="viewSections"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { RefreshLeft, Search, View } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { fetchMaterialStockLogList } from '@/api/material'
|
||||
import type { MaterialStockLog } from '@/api/material'
|
||||
|
||||
@@ -156,6 +175,41 @@
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
|
||||
const viewVisible = ref(false)
|
||||
const viewRow = ref<MaterialStockLog | null>(null)
|
||||
|
||||
const viewSections = computed(() => {
|
||||
if (!viewRow.value) return []
|
||||
const row = viewRow.value
|
||||
return [
|
||||
{
|
||||
title: '变动信息',
|
||||
fields: [
|
||||
{ label: '物料编码', value: row.materialCode },
|
||||
{ label: '物料名称', value: row.materialName },
|
||||
{
|
||||
label: '变动类型',
|
||||
value: row.changeType === 'rk' ? '入库' : '出库',
|
||||
tagType: row.changeType === 'rk' ? 'success' : 'warning'
|
||||
},
|
||||
{ label: '变动数量', value: `${row.changeType === 'rk' ? '+' : ''}${row.changeQty}` },
|
||||
{ label: '变动前库存', value: String(row.stockBefore ?? 0) },
|
||||
{ label: '变动后库存', value: String(row.stockAfter ?? 0) },
|
||||
{ label: '领用人', value: row.changeType === 'ck' ? (row.receiver || '-') : '-' },
|
||||
{ label: '用途', value: row.changeType === 'ck' ? (row.purpose || '-') : '-' },
|
||||
{ label: '操作人', value: row.createBy },
|
||||
{ label: '操作时间', value: row.createTime },
|
||||
{ label: '备注', value: row.remark || '—', span: true }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
function handleView(row: MaterialStockLog) {
|
||||
viewRow.value = row
|
||||
viewVisible.value = true
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询,可按分类、状态筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -125,15 +125,15 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||||
<el-table-column label="操作" width="400" fixed="right" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleStockIn(row)">入库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockOut(row)">出库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockIn(row)"><el-icon><Upload /></el-icon>入库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockOut(row)"><el-icon><Download /></el-icon>出库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -354,7 +354,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Box } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Box, RefreshLeft, Search, Upload, Download, Edit, Delete } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { fetchDictDataOptions } from '@/api/admin'
|
||||
import {
|
||||
|
||||
@@ -403,6 +403,7 @@
|
||||
:protocol-id="viewProtocolId"
|
||||
:list-row="viewRow"
|
||||
show-audit-history
|
||||
show-storage-cert
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
@@ -854,11 +855,11 @@
|
||||
}
|
||||
|
||||
.list-table :deep(tr.row-due-soon > td.el-table__cell) {
|
||||
background-color: #fca5a5 !important;
|
||||
color: #7f1d1d;
|
||||
background-color: #fee2e2 !important;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.list-table :deep(tr.row-due-soon:hover > td.el-table__cell) {
|
||||
background-color: #f87171 !important;
|
||||
background-color: #fecaca !important;
|
||||
}
|
||||
</style>
|
||||
@@ -289,6 +289,7 @@
|
||||
:protocol-id="viewProtocolId"
|
||||
:list-row="viewRow"
|
||||
show-audit-history
|
||||
show-storage-cert
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<div v-loading="loading" class="protocol-view-body">
|
||||
<div v-loading="detailLoading" class="protocol-view-body">
|
||||
<div class="detail-tabs">
|
||||
<div
|
||||
v-for="tab in formTabs"
|
||||
@@ -28,41 +28,77 @@
|
||||
<!-- KPI 摘要条 -->
|
||||
<div class="kpi-strip">
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">协议编号</div>
|
||||
<div class="kpi-value">{{ dash(protocol?.protocolNo) }}</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">协议状态</div>
|
||||
<div class="kpi-value">
|
||||
<el-tag :type="protocolStatusTagType(String(protocol?.protocolStatus || ''))" size="small">
|
||||
{{ protocolStatusLabel(String(protocol?.protocolStatus || '')) }}
|
||||
</el-tag>
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><Document /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">协议编号</div>
|
||||
<div class="kpi-value kpi-value--emphasis">{{ dash(protocol?.protocolNo) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">母亲姓名</div>
|
||||
<div class="kpi-value">{{ dash(partyCustomer?.motherName) }}</div>
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">协议状态</div>
|
||||
<div class="kpi-value">
|
||||
<el-tag :type="protocolStatusTagType(String(protocol?.protocolStatus || ''))" size="small">
|
||||
{{ protocolStatusLabel(String(protocol?.protocolStatus || '')) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">登记日期</div>
|
||||
<div class="kpi-value">{{ formatYmd(protocol?.registerDate) }}</div>
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><User /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">母亲姓名</div>
|
||||
<div class="kpi-value">{{ dash(partyCustomer?.motherName) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">登记日期</div>
|
||||
<div class="kpi-value">{{ formatYmd(protocol?.registerDate) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div id="view-section-basic" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>基本信息</h3></div>
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"><div class="data-label">协议号</div><div class="data-value">{{ dash(protocol?.protocolNo) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">协议状态</div><div class="data-value"><el-tag :type="protocolStatusTagType(String(protocol?.protocolStatus || ''))" size="small">{{ protocolStatusLabel(String(protocol?.protocolStatus || '')) }}</el-tag></div></div>
|
||||
<div class="data-item"><div class="data-label">登记日期</div><div class="data-value">{{ formatYmd(protocol?.registerDate) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">采集专员</div><div class="data-value">{{ collectorDisplay }}</div></div>
|
||||
<div class="data-item"><div class="data-label">创建时间</div><div class="data-value">{{ dash(protocol?.createTime) }}</div></div>
|
||||
<template v-if="showStorageCert">
|
||||
<div class="data-item">
|
||||
<div class="data-label">制作状态</div>
|
||||
<div class="data-value">
|
||||
<el-tag
|
||||
:type="storageCertMakeStatusTagType(String(storageCertInfo?.makeStatus || '0'))"
|
||||
size="small"
|
||||
>
|
||||
{{ storageCertMakeStatusLabel(String(storageCertInfo?.makeStatus || '0')) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">制作日期</div>
|
||||
<div class="data-value">{{ formatYmd(storageCertInfo?.makeDate) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 协议状态变更记录 -->
|
||||
<div v-if="hasAnyStatusChange" class="status-change-section">
|
||||
<!-- 协议状态变更记录(非储存证查看:仍在基本信息内) -->
|
||||
<div v-if="!showStorageCert && hasAnyStatusChange" class="status-change-section">
|
||||
<div class="status-change-title">状态变更记录</div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div class="data-grid data-grid--4">
|
||||
<template v-if="protocol?.effectiveTime">
|
||||
<div class="data-item"><div class="data-label">协议生效时间</div><div class="data-value">{{ dash(protocol.effectiveTime) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">协议生效描述</div><div class="data-value data-value--muted">{{ dash(protocol.effectiveDesc) }}</div></div>
|
||||
@@ -81,7 +117,16 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 附件行 -->
|
||||
<!-- 储存证附件(储存证查看:在协议附件上方) -->
|
||||
<div v-if="showStorageCert" class="attachment-row">
|
||||
<div class="data-label">储存证附件</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="storageCertAttachments"
|
||||
@preview="openPreview"
|
||||
@download="downloadFile"
|
||||
/>
|
||||
</div>
|
||||
<!-- 协议附件 -->
|
||||
<div class="attachment-row" v-if="protocolAttachments.length">
|
||||
<div class="data-label">协议附件</div>
|
||||
<ProtocolAttachmentReadonly :files="protocolAttachments" @preview="openPreview" @download="downloadFile" />
|
||||
@@ -92,6 +137,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态变更记录(储存证查看:独立区域) -->
|
||||
<div v-if="showStorageCert" id="view-section-status-change" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>状态变更记录</h3></div>
|
||||
<el-empty v-if="!hasAnyStatusChange" description="暂无状态变更记录" :image-size="64" />
|
||||
<div v-else class="data-grid data-grid--4">
|
||||
<template v-if="protocol?.effectiveTime">
|
||||
<div class="data-item"><div class="data-label">协议生效时间</div><div class="data-value">{{ dash(protocol.effectiveTime) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">协议生效描述</div><div class="data-value data-value--muted">{{ dash(protocol.effectiveDesc) }}</div></div>
|
||||
</template>
|
||||
<template v-if="protocol?.expireTime">
|
||||
<div class="data-item"><div class="data-label">协议过期时间</div><div class="data-value">{{ dash(protocol.expireTime) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">协议过期描述</div><div class="data-value data-value--muted">{{ dash(protocol.expireDesc) }}</div></div>
|
||||
</template>
|
||||
<template v-if="protocol?.voidTime">
|
||||
<div class="data-item"><div class="data-label">协议作废时间</div><div class="data-value">{{ dash(protocol.voidTime) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">协议作废描述</div><div class="data-value data-value--muted">{{ dash(protocol.voidDesc) }}</div></div>
|
||||
</template>
|
||||
<template v-if="protocol?.toResearchTime">
|
||||
<div class="data-item"><div class="data-label">协议转科研时间</div><div class="data-value">{{ dash(protocol.toResearchTime) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">协议转科研描述</div><div class="data-value data-value--muted">{{ dash(protocol.toResearchDesc) }}</div></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 客户信息 -->
|
||||
<div id="view-section-party" class="section-card section-card--green">
|
||||
<div class="section-title"><h3>客户信息</h3></div>
|
||||
@@ -99,7 +168,7 @@
|
||||
<!-- 母亲信息 -->
|
||||
<div class="client-sub-card">
|
||||
<div class="sub-card-header"><span class="sub-card-icon">🤱</span><h4>母亲信息</h4></div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"><div class="data-label">关联客户</div><div class="data-value">{{ partyCustomer?.customerId ? dash(partyCustomer?.motherName) : '—' }}</div></div>
|
||||
<div class="data-item"><div class="data-label">母亲姓名</div><div class="data-value">{{ dash(partyCustomer?.motherName) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">母亲联系电话</div><div class="data-value">{{ dash(partyCustomer?.motherPhone) }}</div></div>
|
||||
@@ -112,7 +181,7 @@
|
||||
<!-- 父亲信息 -->
|
||||
<div class="client-sub-card">
|
||||
<div class="sub-card-header"><span class="sub-card-icon">👨</span><h4>父亲信息</h4></div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"><div class="data-label">父亲姓名</div><div class="data-value">{{ dash(partyCustomer?.fatherName) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">父亲电话</div><div class="data-value">{{ dash(partyCustomer?.fatherPhone) }}</div></div>
|
||||
<div class="data-item data-item--full"><div class="data-label">父亲身份证号</div><div class="data-value">{{ dash(partyCustomer?.fatherIdCard) }}</div></div>
|
||||
@@ -121,7 +190,7 @@
|
||||
<!-- 新生儿信息 -->
|
||||
<div class="client-sub-card">
|
||||
<div class="sub-card-header"><span class="sub-card-icon">👶</span><h4>新生儿信息</h4></div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"><div class="data-label">新生儿姓名</div><div class="data-value">{{ dash(partyCustomer?.newbornName) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">新生儿身份证</div><div class="data-value">{{ dash(partyCustomer?.newbornIdCard) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">新生儿分娩时间</div><div class="data-value">{{ dash(partyCustomer?.newbornDeliveryTime) }}</div></div>
|
||||
@@ -136,10 +205,67 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新生儿保险信息(满月待办查看:客户信息下方独立区域) -->
|
||||
<div
|
||||
v-if="showFullmoonInsurance"
|
||||
id="view-section-fullmoon-insurance"
|
||||
class="section-card section-card--white"
|
||||
>
|
||||
<div class="section-title"><h3>新生儿保险信息</h3></div>
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"><div class="data-label">满月日期</div><div class="data-value">{{ dash(fullmoonInsuranceInfo?.fullmoonDate) }}</div></div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">待办状态</div>
|
||||
<div class="data-value">
|
||||
<el-tag
|
||||
:type="fullmoonTodoStatusTagType(String(fullmoonInsuranceInfo?.todoStatus || '0'))"
|
||||
size="small"
|
||||
>
|
||||
{{ fullmoonTodoStatusLabel(String(fullmoonInsuranceInfo?.todoStatus || '0')) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">是否已订购保险</div>
|
||||
<div class="data-value">
|
||||
<el-tag
|
||||
:type="insuranceSubscribedTagType(String(fullmoonInsuranceInfo?.insuranceSubscribed || '0'))"
|
||||
size="small"
|
||||
>
|
||||
{{ insuranceSubscribedLabel(String(fullmoonInsuranceInfo?.insuranceSubscribed || '0')) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item"><div class="data-label">保险公司</div><div class="data-value">{{ dash(fullmoonInsuranceInfo?.insuranceCompanyName) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">保险险种</div><div class="data-value">{{ dash(fullmoonInsuranceInfo?.insuranceType) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">保险单号</div><div class="data-value">{{ dash(fullmoonInsuranceInfo?.insurancePolicyNo) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">保单费用(元)</div><div class="data-value">{{ formatMoney(fullmoonInsuranceInfo?.insuranceFee) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">保险单生效时间</div><div class="data-value">{{ formatYmd(fullmoonInsuranceInfo?.insuranceEffectiveDate) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">保险单失效时间</div><div class="data-value">{{ formatYmd(fullmoonInsuranceInfo?.insuranceExpiryDate) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">办理日期</div><div class="data-value">{{ formatYmd(fullmoonInsuranceInfo?.handleDate) }}</div></div>
|
||||
</div>
|
||||
<div v-if="fullmoonInsuranceInfo?.handleRemark" class="remark-row">
|
||||
<div class="data-item data-item--full">
|
||||
<div class="data-label">办理备注</div>
|
||||
<div class="data-value data-value--muted">{{ dash(fullmoonInsuranceInfo?.handleRemark) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attachment-row">
|
||||
<div class="data-label">保险单附件</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
v-if="fullmoonInsuranceAttachments.length"
|
||||
:files="fullmoonInsuranceAttachments"
|
||||
@preview="openPreview"
|
||||
@download="downloadFile"
|
||||
/>
|
||||
<div v-else class="data-value data-value--muted">—</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 采集信息 -->
|
||||
<div id="view-section-collection" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>采集信息</h3></div>
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"><div class="data-label">采集地区</div><div class="data-value">{{ regionDisplay }}</div></div>
|
||||
<div class="data-item"><div class="data-label">采集医院</div><div class="data-value">{{ hospitalDisplay }}</div></div>
|
||||
<div class="data-item"><div class="data-label">采集医护</div><div class="data-value">{{ dash(collectionInfo?.medicalStaff) }}</div></div>
|
||||
@@ -149,8 +275,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 样本及运输 -->
|
||||
<div id="view-section-sample" class="section-card section-card--white">
|
||||
<!-- 样本及运输(字典异步补齐,不阻塞整弹窗) -->
|
||||
<div
|
||||
id="view-section-sample"
|
||||
v-loading="lookupLoading"
|
||||
class="section-card section-card--white"
|
||||
>
|
||||
<div class="section-title"><h3>样本及运输信息</h3></div>
|
||||
<div v-if="samples.length" class="sample-cards">
|
||||
<div
|
||||
@@ -170,7 +300,7 @@
|
||||
<div class="sample-card__body">
|
||||
<div class="sample-card__section">
|
||||
<div class="sample-card__section-label">样本信息</div>
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item">
|
||||
<div class="data-label">样本编码</div>
|
||||
<div class="data-value data-value--code">{{ dash(row.sampleCode) }}</div>
|
||||
@@ -195,7 +325,7 @@
|
||||
</div>
|
||||
<div v-if="row.transport?.transportType" class="sample-card__section sample-card__section--transport">
|
||||
<div class="sample-card__section-label">运输信息</div>
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item">
|
||||
<div class="data-label">运输类型</div>
|
||||
<div class="data-value">{{ transportTypeLabel(String(row.transport?.transportType || '')) }}</div>
|
||||
@@ -217,7 +347,7 @@
|
||||
<div class="sample-card__section sample-card__section--reception">
|
||||
<div class="sample-card__section-label">样本接收信息</div>
|
||||
<template v-if="row.sampleReception">
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item">
|
||||
<div class="data-label">接收时间</div>
|
||||
<div class="data-value">{{ dash(row.sampleReception.receptionTime) }}</div>
|
||||
@@ -236,6 +366,10 @@
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">净重(g)</div>
|
||||
<div class="data-value">{{ formatMetric(row.sampleReception.netWeightG) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">结论</div>
|
||||
<div class="data-value">
|
||||
@@ -642,7 +776,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { Close } from '@element-plus/icons-vue'
|
||||
import { Calendar, CircleCheck, Close, Document, User } from '@element-plus/icons-vue'
|
||||
import ProtocolAttachmentReadonly from './ProtocolAttachmentReadonly.vue'
|
||||
import SampleQcInfoCards from '@/components/business/sample-qc-info-cards/index.vue'
|
||||
import {
|
||||
@@ -684,8 +818,12 @@
|
||||
listRow?: CollectionProtocolRow | null
|
||||
/** 展示审核记录 Tab(采集协议单审核页) */
|
||||
showAuditHistory?: boolean
|
||||
/** 储存证管理查看:展示储存证附件,状态变更记录独立区域 */
|
||||
showStorageCert?: boolean
|
||||
/** 满月待办查看:客户信息下方独立展示新生儿保险信息 */
|
||||
showFullmoonInsurance?: boolean
|
||||
}>(),
|
||||
{ showAuditHistory: false }
|
||||
{ showAuditHistory: false, showStorageCert: false, showFullmoonInsurance: false }
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -702,13 +840,18 @@
|
||||
|
||||
const formTabs = computed(() => {
|
||||
const tabs: Array<{ key: string; label: string }> = [...baseFormTabs]
|
||||
if (props.showFullmoonInsurance) {
|
||||
const partyIdx = tabs.findIndex((t) => t.key === 'party')
|
||||
tabs.splice(partyIdx + 1, 0, { key: 'fullmoon-insurance', label: '新生儿保险' })
|
||||
}
|
||||
if (props.showAuditHistory) {
|
||||
tabs.push({ key: 'audit', label: '审核记录' })
|
||||
}
|
||||
return tabs
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const lookupLoading = ref(false)
|
||||
const auditLoading = ref(false)
|
||||
const bundle = ref<Record<string, any> | null>(null)
|
||||
const auditRecords = ref<Array<Record<string, unknown>>>([])
|
||||
@@ -760,6 +903,98 @@
|
||||
return Array.isArray(list) ? list : []
|
||||
})
|
||||
|
||||
const storageCertInfo = computed(() => {
|
||||
const sc = bundle.value?.storageCert as Record<string, any> | undefined | null
|
||||
if (sc) return sc
|
||||
if (!props.showStorageCert || !props.listRow) return null
|
||||
return {
|
||||
makeStatus: props.listRow.makeStatus ?? '0',
|
||||
certImageUrls: props.listRow.certImageUrls ?? '',
|
||||
makeDate: props.listRow.makeDate ?? '',
|
||||
remark: props.listRow.remark ?? ''
|
||||
}
|
||||
})
|
||||
|
||||
const storageCertAttachments = computed(() => {
|
||||
const urls = String(storageCertInfo.value?.certImageUrls || '').trim()
|
||||
if (!urls) return []
|
||||
return urls
|
||||
.split(',')
|
||||
.map((u) => u.trim())
|
||||
.filter(Boolean)
|
||||
.map((url, index) => {
|
||||
const rawName = url.split('/').pop() || ''
|
||||
const fileName = decodeURIComponent(rawName) || `附件${index + 1}`
|
||||
return { id: `cert_${index}`, fileName, fileUrl: url, fileType: '' }
|
||||
})
|
||||
})
|
||||
|
||||
const fullmoonInsuranceInfo = computed(() => {
|
||||
const fm = bundle.value?.fullmoonTodo as Record<string, any> | undefined
|
||||
if (fm) return fm
|
||||
if (!props.showFullmoonInsurance || !props.listRow) return null
|
||||
const row = props.listRow as Record<string, any>
|
||||
return {
|
||||
fullmoonDate: row.fullmoonDate ?? '',
|
||||
todoStatus: row.todoStatus ?? '0',
|
||||
insuranceSubscribed: row.insuranceSubscribed ?? '0',
|
||||
insuranceCompanyName: row.insuranceCompanyName ?? '',
|
||||
insuranceType: row.insuranceType ?? '',
|
||||
insurancePolicyNo: row.insurancePolicyNo ?? '',
|
||||
insuranceEffectiveDate: row.insuranceEffectiveDate ?? row.policyEffectiveDate ?? '',
|
||||
insuranceExpiryDate: row.insuranceExpiryDate ?? row.policyExpiryDate ?? '',
|
||||
insuranceFee: row.insuranceFee,
|
||||
handleDate: row.handleDate ?? row.processDate ?? '',
|
||||
handleRemark: row.handleRemark ?? row.remark ?? '',
|
||||
insuranceAttachments: row.insuranceAttachments ?? ''
|
||||
}
|
||||
})
|
||||
|
||||
const fullmoonInsuranceAttachments = computed(() => {
|
||||
const urls = String(fullmoonInsuranceInfo.value?.insuranceAttachments || '').trim()
|
||||
if (!urls) return []
|
||||
return urls
|
||||
.split(',')
|
||||
.map((u) => u.trim())
|
||||
.filter(Boolean)
|
||||
.map((url, index) => {
|
||||
const rawName = url.split('/').pop() || ''
|
||||
const fileName = decodeURIComponent(rawName) || `附件${index + 1}`
|
||||
return { id: `ins_${index}`, fileName, fileUrl: url, fileType: '' }
|
||||
})
|
||||
})
|
||||
|
||||
function fullmoonTodoStatusLabel(status: string) {
|
||||
return status === '1' ? '已办保险' : '未办保险'
|
||||
}
|
||||
|
||||
function fullmoonTodoStatusTagType(status: string) {
|
||||
return status === '1' ? 'success' : 'warning'
|
||||
}
|
||||
|
||||
function insuranceSubscribedLabel(val: string) {
|
||||
return val === '1' ? '是' : '否'
|
||||
}
|
||||
|
||||
function insuranceSubscribedTagType(val: string) {
|
||||
return val === '1' ? 'success' : 'info'
|
||||
}
|
||||
|
||||
function formatMoney(val: unknown) {
|
||||
if (val === null || val === undefined || val === '') return '—'
|
||||
const n = Number(val)
|
||||
if (Number.isNaN(n)) return String(val)
|
||||
return `¥${n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
||||
}
|
||||
|
||||
function storageCertMakeStatusLabel(makeStatus: string) {
|
||||
return makeStatus === '1' ? '已制作' : '未制作'
|
||||
}
|
||||
|
||||
function storageCertMakeStatusTagType(makeStatus: string) {
|
||||
return makeStatus === '1' ? 'success' : 'warning'
|
||||
}
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
const no = protocol.value?.protocolNo || props.listRow?.protocolNo
|
||||
return no ? `查看协议单 — ${no}` : '查看协议单'
|
||||
@@ -820,6 +1055,12 @@
|
||||
return String(val)
|
||||
}
|
||||
|
||||
function formatMetric(v: unknown) {
|
||||
if (v === null || v === undefined || v === '') return '—'
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? String(n) : '—'
|
||||
}
|
||||
|
||||
function formatYmd(val: unknown) {
|
||||
const s = String(val || '').replace(/-/g, '')
|
||||
if (s.length === 8) return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`
|
||||
@@ -1249,7 +1490,7 @@
|
||||
downloadAttachmentFile(file, { fullFileUrl })
|
||||
}
|
||||
|
||||
async function loadLookups(data: Record<string, any>) {
|
||||
async function loadBaseLookups() {
|
||||
const [stRes, uRes, ecRes, siteRes] = await Promise.all([
|
||||
fetchCellSampleTypeList({ page: 1, pageSize: 100 }),
|
||||
fetchItemUnitList({ page: 1, pageSize: 100 }),
|
||||
@@ -1260,7 +1501,9 @@
|
||||
unitOptions.value = (uRes.list || []) as any
|
||||
expressCompanyOptions.value = ecRes.list || []
|
||||
siteOptions.value = siteRes.list || []
|
||||
}
|
||||
|
||||
async function loadConditionalLookups(data: Record<string, any>) {
|
||||
const ci = data.collectionInfo as Record<string, any> | null
|
||||
const p = data.protocol as Record<string, any> | undefined
|
||||
const tasks: Promise<void>[] = []
|
||||
@@ -1291,10 +1534,22 @@
|
||||
})
|
||||
)
|
||||
}
|
||||
await Promise.all(tasks)
|
||||
if (tasks.length) {
|
||||
await Promise.all(tasks)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAuditRecords() {
|
||||
async function finishLookups(data: Record<string, any>, baseLookupsPromise: Promise<void>) {
|
||||
lookupLoading.value = true
|
||||
try {
|
||||
await baseLookupsPromise
|
||||
await loadConditionalLookups(data)
|
||||
} finally {
|
||||
lookupLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAuditRecords(protocolNoHint?: string) {
|
||||
if (!props.showAuditHistory || !props.protocolId) {
|
||||
auditRecords.value = []
|
||||
return
|
||||
@@ -1302,7 +1557,12 @@
|
||||
auditLoading.value = true
|
||||
try {
|
||||
const pno =
|
||||
String(protocol.value?.protocolNo || props.listRow?.protocolNo || '').trim() || undefined
|
||||
String(
|
||||
protocolNoHint ||
|
||||
protocol.value?.protocolNo ||
|
||||
props.listRow?.protocolNo ||
|
||||
''
|
||||
).trim() || undefined
|
||||
const res = await fetchCollectionProtocolAuditList({
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
@@ -1319,16 +1579,35 @@
|
||||
|
||||
async function loadDetail() {
|
||||
if (!props.protocolId) return
|
||||
loading.value = true
|
||||
detailLoading.value = true
|
||||
lookupLoading.value = true
|
||||
bundle.value = null
|
||||
auditRecords.value = []
|
||||
|
||||
const protocolNoHint = String(props.listRow?.protocolNo || '').trim()
|
||||
const baseLookupsPromise = loadBaseLookups().catch(() => {})
|
||||
const auditEarlyPromise =
|
||||
props.showAuditHistory && protocolNoHint
|
||||
? loadAuditRecords(protocolNoHint)
|
||||
: null
|
||||
|
||||
let data: Record<string, any> | null = null
|
||||
try {
|
||||
const data = await fetchCollectionProtocolDetail(props.protocolId)
|
||||
bundle.value = data as Record<string, any>
|
||||
await loadLookups(data as Record<string, any>)
|
||||
await loadAuditRecords()
|
||||
data = (await fetchCollectionProtocolDetail(props.protocolId)) as Record<string, any>
|
||||
bundle.value = data
|
||||
} finally {
|
||||
loading.value = false
|
||||
detailLoading.value = false
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
lookupLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
void finishLookups(data, baseLookupsPromise)
|
||||
|
||||
if (props.showAuditHistory && !auditEarlyPromise) {
|
||||
void loadAuditRecords()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1345,6 +1624,9 @@
|
||||
bundle.value = null
|
||||
auditRecords.value = []
|
||||
activeTab.value = 'basic'
|
||||
detailLoading.value = false
|
||||
lookupLoading.value = false
|
||||
auditLoading.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -1396,59 +1678,77 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- KPI 摘要条 ---- */
|
||||
/* ---- KPI 摘要条(统一薄荷绿通栏 + 图标徽章) ---- */
|
||||
.kpi-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
gap: 0;
|
||||
margin-bottom: 24px;
|
||||
padding: 6px;
|
||||
background: linear-gradient(135deg, #ecfdf5 0%, #f8fafc 52%, #ecfdf5 100%);
|
||||
border: 1px solid #a7f3d0;
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 18px 20px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
border-left: 3px solid #10b981;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 14px 18px;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: radial-gradient(
|
||||
circle at top right,
|
||||
rgba(16, 185, 129, 0.06),
|
||||
transparent 70%
|
||||
);
|
||||
pointer-events: none;
|
||||
&:not(:last-child) {
|
||||
border-right: 1px solid rgba(167, 243, 208, 0.75);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.1);
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
}
|
||||
|
||||
.kpi-card__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #a7f3d0;
|
||||
color: #059669;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.kpi-card__main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.kpi-label {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
font-size: 16px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
color: #0f172a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
&--emphasis {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #047857;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Section Card ---- */
|
||||
@@ -1703,6 +2003,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.sample-card__section--qc) {
|
||||
margin-top: 2px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed #e2e8f0;
|
||||
}
|
||||
|
||||
.sample-reception-empty {
|
||||
font-size: 13px;
|
||||
color: #94a3b8;
|
||||
@@ -1890,10 +2196,10 @@
|
||||
|
||||
/* ---- PDC Info Row ---- */
|
||||
.pdc-info-row {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 14px 20px;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pdc-info-item {
|
||||
@@ -2237,8 +2543,8 @@
|
||||
|
||||
.refund-area-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px 20px;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 14px 20px;
|
||||
|
||||
.data-label {
|
||||
font-size: 11px;
|
||||
@@ -2441,11 +2747,19 @@
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.kpi-strip .kpi-card {
|
||||
border-right: none !important;
|
||||
border-bottom: 1px solid rgba(167, 243, 208, 0.75);
|
||||
|
||||
&:nth-last-child(-n + 2) {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.client-sub-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.data-grid--3,
|
||||
.data-grid--4 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
@@ -2460,11 +2774,18 @@
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.kpi-strip .kpi-card:nth-last-child(-n + 2) {
|
||||
border-bottom: 1px solid rgba(167, 243, 208, 0.75);
|
||||
}
|
||||
|
||||
.kpi-strip .kpi-card:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.client-sub-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.data-grid--3,
|
||||
.data-grid--4 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
@@ -2474,12 +2795,11 @@
|
||||
}
|
||||
|
||||
.pdc-info-row {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.refund-area-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.finance-area-grid {
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
:row-class-name="storageCertRowClassName"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
@@ -56,9 +57,17 @@
|
||||
/>
|
||||
<el-table-column prop="protocolNo" label="采集协议号" width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="motherName" label="母亲姓名" width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="newbornDeliveryTime" label="新生儿分娩时间" width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="sampleInfo" label="样本信息" min-width="180" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="storageInDate" label="入库日期" width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="motherPhone" label="母亲联系方式" min-width="130" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.motherPhone || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="motherIdCard" label="身份证号" min-width="170" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ maskIdCard(row.motherIdCard) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="newbornInfo" label="新生儿信息" min-width="200" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="sampleInfo" label="样本信息" min-width="200" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="storageInDate" label="入库日期" width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.storageInDate || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="makeStatus" label="制作状态" width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.makeStatus == '1' ? 'success' : 'warning'" size="small">
|
||||
@@ -67,7 +76,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="makeDate" label="制作日期" width="120" align="center" header-align="center" />
|
||||
<el-table-column label="操作" width="200" fixed="right" align="center" header-align="center">
|
||||
<el-table-column label="操作" width="260" fixed="right" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleUpload(row)">
|
||||
<el-icon><Upload /></el-icon>上传储存证
|
||||
@@ -95,7 +104,7 @@
|
||||
<el-dialog
|
||||
v-model="uploadDialogVisible"
|
||||
title="上传储存证"
|
||||
width="640px"
|
||||
width="720px"
|
||||
class="dialog-form-layout"
|
||||
align-center
|
||||
destroy-on-close
|
||||
@@ -118,18 +127,74 @@
|
||||
<el-input :model-value="uploadRow?.motherName" disabled />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="储存证图片" prop="certImageUrls">
|
||||
<el-upload
|
||||
v-model:file-list="uploadFileList"
|
||||
action=""
|
||||
:auto-upload="false"
|
||||
list-type="picture-card"
|
||||
accept="image/*"
|
||||
:limit="9"
|
||||
:on-exceed="() => ElMessage.warning('最多上传 9 张图片')"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
<el-form-item label="储存证附件" class="form-col-span-2">
|
||||
<div class="attachment-upload">
|
||||
<div class="attachment-list">
|
||||
<el-upload
|
||||
ref="certAttachmentUploadRef"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:multiple="true"
|
||||
accept="image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar"
|
||||
@change="handleCertAttachmentChange"
|
||||
>
|
||||
<div class="upload-card">
|
||||
<el-icon :size="28" color="#10b981"><Plus /></el-icon>
|
||||
<span class="upload-card-text">上传附件</span>
|
||||
</div>
|
||||
</el-upload>
|
||||
<div
|
||||
v-for="(file, index) in certAttachments"
|
||||
:key="file.id || index"
|
||||
class="attachment-item"
|
||||
:class="{ 'is-uploading': file._uploading }"
|
||||
>
|
||||
<div
|
||||
class="attachment-preview"
|
||||
@click="
|
||||
isImageFile(file) && file.fileUrl
|
||||
? handlePreviewAttachment(file)
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
<template v-if="isImageFile(file) && file.fileUrl">
|
||||
<img :src="fullFileUrl(file.fileUrl)" :alt="file.fileName" />
|
||||
</template>
|
||||
<template v-else-if="isImageFile(file) && !file.fileUrl">
|
||||
<el-icon :size="24" color="#94a3b8"><Loading /></el-icon>
|
||||
</template>
|
||||
<template v-else-if="isVideoFile(file)">
|
||||
<div class="attachment-preview__icon attachment-preview__icon--video">
|
||||
<el-icon :size="28"><VideoPlay /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="attachment-preview__icon attachment-preview__icon--doc">
|
||||
<el-icon :size="28"><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="handleDownloadAttachment(file)"
|
||||
><Download
|
||||
/></el-icon>
|
||||
<el-icon
|
||||
class="action-btn action-btn--danger"
|
||||
title="删除"
|
||||
@click="removeCertAttachment(index)"
|
||||
><Delete
|
||||
/></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="upload-hint">支持图片、视频、PDF、Word、Excel等文件,单文件不超过 20MB</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="制作日期" prop="makeDate">
|
||||
@@ -157,84 +222,44 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看弹窗 -->
|
||||
<el-dialog
|
||||
v-model="viewDialogVisible"
|
||||
title="查看储存证"
|
||||
width="720px"
|
||||
class="dialog-form-layout"
|
||||
v-model="previewVisible"
|
||||
title="文件预览"
|
||||
width="800px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="view-info-grid">
|
||||
<div class="view-info-item">
|
||||
<span class="view-label">采集协议号</span>
|
||||
<span class="view-value">{{ viewRow?.protocolNo || '—' }}</span>
|
||||
</div>
|
||||
<div class="view-info-item">
|
||||
<span class="view-label">母亲姓名</span>
|
||||
<span class="view-value">{{ viewRow?.motherName || '—' }}</span>
|
||||
</div>
|
||||
<div class="view-info-item">
|
||||
<span class="view-label">新生儿分娩时间</span>
|
||||
<span class="view-value">{{ viewRow?.newbornDeliveryTime || '—' }}</span>
|
||||
</div>
|
||||
<div class="view-info-item">
|
||||
<span class="view-label">样本信息</span>
|
||||
<span class="view-value">{{ viewRow?.sampleInfo || '—' }}</span>
|
||||
</div>
|
||||
<div class="view-info-item">
|
||||
<span class="view-label">入库日期</span>
|
||||
<span class="view-value">{{ viewRow?.storageInDate || '—' }}</span>
|
||||
</div>
|
||||
<div class="view-info-item">
|
||||
<span class="view-label">制作状态</span>
|
||||
<span class="view-value">
|
||||
<el-tag :type="viewRow?.makeStatus == '1' ? 'success' : 'warning'" size="small">
|
||||
{{ viewRow?.makeStatus == '1' ? '已制作' : '未制作' }}
|
||||
</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
<div class="view-info-item">
|
||||
<span class="view-label">制作日期</span>
|
||||
<span class="view-value">{{ viewRow?.makeDate || '—' }}</span>
|
||||
</div>
|
||||
<div class="view-info-item">
|
||||
<span class="view-label">备注</span>
|
||||
<span class="view-value">{{ viewRow?.remark || '—' }}</span>
|
||||
</div>
|
||||
<div v-if="previewType === 'image'" class="preview-container">
|
||||
<img
|
||||
:src="previewUrl"
|
||||
alt="预览"
|
||||
style="max-width: 100%; max-height: 70vh; display: block; margin: 0 auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="viewCertImages.length" class="view-cert-images">
|
||||
<div class="view-cert-label">储存证图片</div>
|
||||
<div class="view-cert-grid">
|
||||
<el-image
|
||||
v-for="(url, idx) in viewCertImages"
|
||||
:key="idx"
|
||||
:src="url"
|
||||
:preview-src-list="viewCertImages"
|
||||
:initial-index="idx"
|
||||
fit="cover"
|
||||
class="view-cert-img"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="preview-container">
|
||||
<video
|
||||
:src="previewUrl"
|
||||
controls
|
||||
style="max-width: 100%; max-height: 70vh; display: block; margin: 0 auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="viewDialogVisible = false">
|
||||
<el-icon><Close /></el-icon>关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看弹窗(与采集协议单录入一致) -->
|
||||
<ProtocolViewDialog
|
||||
v-model="viewVisible"
|
||||
:protocol-id="viewProtocolId"
|
||||
:list-row="viewRow"
|
||||
show-audit-history
|
||||
show-storage-cert
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { UploadUserFile } from 'element-plus'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
@@ -242,11 +267,32 @@
|
||||
Search,
|
||||
RefreshLeft,
|
||||
View,
|
||||
Upload
|
||||
Upload,
|
||||
Delete,
|
||||
Document,
|
||||
VideoPlay,
|
||||
Loading,
|
||||
Download
|
||||
} from '@element-plus/icons-vue'
|
||||
import ProtocolViewDialog from '../protocol/ProtocolViewDialog.vue'
|
||||
import { fetchStorageCertList } from '@/api/admin'
|
||||
import { fetchUpdateCollectionProtocol } from '@/api/collection-protocol'
|
||||
import {
|
||||
fetchUpdateCollectionProtocol,
|
||||
type CollectionProtocolRow
|
||||
} from '@/api/collection-protocol'
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import { maskIdCard } from '@/utils/mask-sensitive'
|
||||
|
||||
interface CertAttachmentItem {
|
||||
id: string
|
||||
fileName: string
|
||||
fileUrl: string
|
||||
fileSize?: number
|
||||
fileSizeDisplay?: string
|
||||
fileType?: string
|
||||
_uploading?: boolean
|
||||
}
|
||||
|
||||
defineOptions({ name: 'CellStorageStorageCert' })
|
||||
|
||||
@@ -264,29 +310,47 @@
|
||||
const uploadDialogVisible = ref(false)
|
||||
const uploadRow = ref<any>(null)
|
||||
const uploadFormRef = ref()
|
||||
const uploadFileList = ref<UploadUserFile[]>([])
|
||||
const certAttachmentUploadRef = ref()
|
||||
const certAttachments = ref<CertAttachmentItem[]>([])
|
||||
const submitLoading = ref(false)
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
const uploadForm = reactive({
|
||||
makeDate: '',
|
||||
remark: ''
|
||||
})
|
||||
const uploadFormRules = {
|
||||
certImageUrls: [{ required: true, message: '请上传储存证图片', trigger: 'change' }],
|
||||
makeDate: [{ required: true, message: '请选择制作日期', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const previewVisible = ref(false)
|
||||
const previewUrl = ref('')
|
||||
const previewType = ref<'image' | 'video'>('image')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 查看弹窗
|
||||
// 查看弹窗(与采集协议单录入一致)
|
||||
// ---------------------------------------------------------------------------
|
||||
const viewDialogVisible = ref(false)
|
||||
const viewRow = ref<any>(null)
|
||||
const viewCertImages = computed(() => {
|
||||
if (!viewRow.value?.certImageUrls) return []
|
||||
return viewRow.value.certImageUrls
|
||||
.split(',')
|
||||
.map((u: string) => u.trim())
|
||||
.filter(Boolean)
|
||||
})
|
||||
const viewVisible = ref(false)
|
||||
const viewProtocolId = ref<string | null>(null)
|
||||
const viewRow = ref<CollectionProtocolRow | null>(null)
|
||||
|
||||
/** 新生儿已满月且储存证未制作时标红(列表仅含已生效协议) */
|
||||
function isStorageCertMakePendingRow(row: Record<string, unknown>) {
|
||||
if (String(row.makeStatus ?? '0') === '1') return false
|
||||
const delivery = String(row.newbornDeliveryTime || '').trim()
|
||||
if (!delivery) return false
|
||||
const deliveryDate = new Date(delivery.replace(/-/g, '/'))
|
||||
if (Number.isNaN(deliveryDate.getTime())) return false
|
||||
const fullmoon = new Date(deliveryDate)
|
||||
fullmoon.setDate(fullmoon.getDate() + 30)
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
fullmoon.setHours(0, 0, 0, 0)
|
||||
return fullmoon <= today
|
||||
}
|
||||
|
||||
function storageCertRowClassName({ row }: { row: Record<string, unknown> }) {
|
||||
return isStorageCertMakePendingRow(row) ? 'row-storage-cert-pending' : ''
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 数据加载
|
||||
@@ -322,53 +386,155 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// 上传储存证
|
||||
// ---------------------------------------------------------------------------
|
||||
function fullFileUrl(suffix: string) {
|
||||
if (!suffix) return ''
|
||||
if (suffix.startsWith('http://') || suffix.startsWith('https://') || suffix.startsWith('/')) {
|
||||
return suffix
|
||||
}
|
||||
return `${minioBaseUrl}/${suffix}`
|
||||
}
|
||||
|
||||
function isImageFile(file: CertAttachmentItem) {
|
||||
const url = (file.fileUrl || '').toLowerCase()
|
||||
const name = (file.fileName || '').toLowerCase()
|
||||
return (
|
||||
/\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?|$)/.test(url) ||
|
||||
/\.(jpg|jpeg|png|gif|bmp|webp|svg)$/.test(name) ||
|
||||
(file.fileType || '').startsWith('image/')
|
||||
)
|
||||
}
|
||||
|
||||
function isVideoFile(file: CertAttachmentItem) {
|
||||
const url = (file.fileUrl || '').toLowerCase()
|
||||
const name = (file.fileName || '').toLowerCase()
|
||||
return (
|
||||
/\.(mp4|webm|ogg|mov|avi)(\?|$)/.test(url) ||
|
||||
/\.(mp4|webm|ogg|mov|avi)$/.test(name) ||
|
||||
(file.fileType || '').startsWith('video/')
|
||||
)
|
||||
}
|
||||
|
||||
function parseCertUrlsToAttachments(urls: string): CertAttachmentItem[] {
|
||||
return urls
|
||||
.split(',')
|
||||
.map((u) => u.trim())
|
||||
.filter(Boolean)
|
||||
.map((url, index) => {
|
||||
const rawName = url.split('/').pop() || ''
|
||||
const fileName = decodeURIComponent(rawName) || `附件${index + 1}`
|
||||
return {
|
||||
id: `existing_${index}`,
|
||||
fileName,
|
||||
fileUrl: url,
|
||||
fileType: '',
|
||||
_uploading: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function certAttachmentsToUrls() {
|
||||
return certAttachments.value
|
||||
.filter((f) => f.fileUrl && !f._uploading)
|
||||
.map((f) => f.fileUrl)
|
||||
.join(',')
|
||||
}
|
||||
|
||||
async function handleCertAttachmentChange(uploadFile: any) {
|
||||
const file = uploadFile.raw || uploadFile
|
||||
if (!file) return
|
||||
|
||||
const maxSizeMb = 20
|
||||
if (file.size / 1024 / 1024 > maxSizeMb) {
|
||||
ElMessage.error(`文件大小不能超过 ${maxSizeMb}MB`)
|
||||
certAttachmentUploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
|
||||
const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
||||
certAttachments.value.push({
|
||||
id: tempId,
|
||||
fileName: file.name,
|
||||
fileUrl: '',
|
||||
fileSize: file.size,
|
||||
fileSizeDisplay: `${(file.size / 1024).toFixed(1)} KB`,
|
||||
fileType: file.type || '',
|
||||
_uploading: true
|
||||
})
|
||||
const idx = certAttachments.value.length - 1
|
||||
|
||||
try {
|
||||
const fd = new FormData()
|
||||
fd.append('folder', 'storage-cert')
|
||||
fd.append('file', file)
|
||||
const res = await fetchUploadAttachment(fd)
|
||||
certAttachments.value[idx] = {
|
||||
...certAttachments.value[idx],
|
||||
id: res.id || tempId,
|
||||
fileName: file.name || res.fileName,
|
||||
fileUrl: res.fileUrl,
|
||||
fileSize: res.fileSize || file.size,
|
||||
fileSizeDisplay: res.fileSizeDisplay || `${(file.size / 1024).toFixed(1)} KB`,
|
||||
fileType: res.fileType || file.type || '',
|
||||
_uploading: false
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('文件上传失败')
|
||||
certAttachments.value.splice(idx, 1)
|
||||
} finally {
|
||||
nextTick(() => certAttachmentUploadRef.value?.clearFiles())
|
||||
}
|
||||
}
|
||||
|
||||
function removeCertAttachment(index: number) {
|
||||
certAttachments.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function handlePreviewAttachment(file: CertAttachmentItem) {
|
||||
if (file._uploading) return
|
||||
previewUrl.value = fullFileUrl(file.fileUrl)
|
||||
previewType.value = isVideoFile(file) ? 'video' : 'image'
|
||||
previewVisible.value = true
|
||||
}
|
||||
|
||||
function handleDownloadAttachment(file: CertAttachmentItem) {
|
||||
downloadAttachmentFile(file, { fullFileUrl })
|
||||
}
|
||||
|
||||
function handleUpload(row: any) {
|
||||
uploadRow.value = row
|
||||
uploadFileList.value = []
|
||||
uploadForm.makeDate = getTodayStr()
|
||||
uploadForm.remark = ''
|
||||
certAttachments.value = parseCertUrlsToAttachments(String(row.certImageUrls || ''))
|
||||
uploadForm.makeDate = String(row.makeDate || '').trim() || getTodayStr()
|
||||
uploadForm.remark = String(row.remark || '').trim()
|
||||
uploadDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleSubmitUpload() {
|
||||
// 手动校验图片
|
||||
if (uploadFileList.value.length === 0) {
|
||||
ElMessage.warning('请上传储存证图片')
|
||||
if (certAttachments.value.some((f) => f._uploading)) {
|
||||
ElMessage.warning('附件上传中,请稍候')
|
||||
return
|
||||
}
|
||||
const valid = await uploadFormRef.value?.validate?.().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
const uploaded = certAttachments.value.filter((f) => f.fileUrl && !f._uploading)
|
||||
const hasAttachments = uploaded.length > 0
|
||||
|
||||
if (hasAttachments) {
|
||||
const valid = await uploadFormRef.value?.validate?.().catch(() => false)
|
||||
if (!valid) return
|
||||
}
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
// 1. 逐个上传图片文件
|
||||
const uploadedUrls: string[] = []
|
||||
for (const f of uploadFileList.value) {
|
||||
const file = f.raw
|
||||
if (!file) continue
|
||||
const fd = new FormData()
|
||||
fd.append('business_type', 'storage_cert')
|
||||
fd.append('business_id', uploadRow.value?.protocolId || 'temp')
|
||||
fd.append('file', file)
|
||||
const res: any = await fetchUploadAttachment(fd)
|
||||
if (res?.fileUrl) {
|
||||
uploadedUrls.push(res.fileUrl)
|
||||
}
|
||||
}
|
||||
const existingUrls = uploadRow.value?.certImageUrls || ''
|
||||
const finalUrls = [existingUrls, ...uploadedUrls].filter(Boolean).join(',')
|
||||
|
||||
// 2. 调用协议更新接口保存储存证数据
|
||||
await fetchUpdateCollectionProtocol(uploadRow.value.protocolId, {
|
||||
const protocolId = uploadRow.value?.protocolId || uploadRow.value?.id
|
||||
await fetchUpdateCollectionProtocol(protocolId, {
|
||||
storageCert: {
|
||||
makeStatus: '1',
|
||||
certImageUrls: finalUrls,
|
||||
makeDate: uploadForm.makeDate,
|
||||
makeStatus: hasAttachments ? '1' : '0',
|
||||
certImageUrls: hasAttachments ? certAttachmentsToUrls() : '',
|
||||
makeDate: hasAttachments ? uploadForm.makeDate : '',
|
||||
remark: uploadForm.remark?.trim() || undefined
|
||||
}
|
||||
})
|
||||
|
||||
ElMessage.success('上传成功')
|
||||
ElMessage.success(hasAttachments ? '上传成功' : '保存成功')
|
||||
uploadDialogVisible.value = false
|
||||
await loadData()
|
||||
} catch {
|
||||
@@ -378,12 +544,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 查看储存证
|
||||
// ---------------------------------------------------------------------------
|
||||
function handleView(row: any) {
|
||||
viewRow.value = row
|
||||
viewDialogVisible.value = true
|
||||
viewProtocolId.value = String(row.protocolId || row.id)
|
||||
viewRow.value = row as CollectionProtocolRow
|
||||
viewVisible.value = true
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -409,52 +573,184 @@
|
||||
}
|
||||
}
|
||||
|
||||
.view-info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px 24px;
|
||||
margin-bottom: 20px;
|
||||
.form-col-span-2 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.view-info-item {
|
||||
.attachment-upload {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.upload-card {
|
||||
width: 140px;
|
||||
height: 96px;
|
||||
border: 1px dashed #a7f3d0;
|
||||
border-radius: 8px;
|
||||
background: #f0fdf4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
background 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: #10b981;
|
||||
border-style: solid;
|
||||
background: #ecfdf5;
|
||||
}
|
||||
}
|
||||
|
||||
.view-label {
|
||||
.upload-card-text {
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #909399);
|
||||
color: #10b981;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.view-value {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-primary, #303133);
|
||||
word-break: break-all;
|
||||
.upload-hint {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
line-height: 1;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.view-cert-images {
|
||||
border-top: 1px solid var(--el-border-color-lighter, #ebeef5);
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.view-cert-label {
|
||||
font-size: 13px;
|
||||
color: var(--mint-text-muted, #909399);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.view-cert-grid {
|
||||
.attachment-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.view-cert-img {
|
||||
width: 120px;
|
||||
.attachment-item {
|
||||
width: 140px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
box-shadow 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: #10b981;
|
||||
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
|
||||
&.is-uploading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-preview {
|
||||
width: 100%;
|
||||
height: 96px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--el-border-color-lighter, #ebeef5);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8fafc;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-preview__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&--video {
|
||||
background: #ede9fe;
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
&--doc {
|
||||
background: #e0f2fe;
|
||||
color: #0284c7;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 11px;
|
||||
color: #334155;
|
||||
line-height: 1.4;
|
||||
word-break: break-all;
|
||||
min-height: calc(11px * 1.4 * 2);
|
||||
}
|
||||
|
||||
.attachment-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
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: #10b981;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&--danger:hover {
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list-table :deep(tr.row-storage-cert-pending > td.el-table__cell) {
|
||||
background-color: #fee2e2 !important;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.list-table :deep(tr.row-storage-cert-pending:hover > td.el-table__cell) {
|
||||
background-color: #fecaca !important;
|
||||
}
|
||||
</style>
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编号、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -371,7 +371,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus, Close, Check, View, Edit, Delete, User, OfficeBuilding } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Edit, Delete, User, OfficeBuilding, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import { buildTreePathLabelFromFlat } from '@/utils/tree-path'
|
||||
import { cleanRoomGradeLabel, cleanRoomGradeTagType } from '@/utils/status-tag-registry'
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按预约编号、来访单位、联系人模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -166,7 +166,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus, Close, Check, View, Calendar } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Calendar, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按项目名称、编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -204,7 +204,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -155,7 +155,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -155,7 +155,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按渠道名称、统一社会信用代码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -1029,20 +1029,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
CircleClose,
|
||||
Search,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
Document,
|
||||
VideoPlay,
|
||||
Loading,
|
||||
Download
|
||||
} from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, CircleClose, Search, View, Edit, Delete, Document, VideoPlay, Loading, Download, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import ChannelViewDialog from './ChannelViewDialog.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按体系名称、编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -276,7 +276,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编号、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -388,7 +388,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus, Close, Check, View } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchConsumablePackList,
|
||||
fetchConsumablePackDetail,
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -177,7 +177,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -150,7 +150,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -150,7 +150,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -49,8 +49,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按路线名称、编码、产品类型筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -624,7 +624,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus, Delete, Rank, Close, Check, CopyDocument } from '@element-plus/icons-vue'
|
||||
import { Plus, Delete, Rank, Close, Check, CopyDocument, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { VueDraggable as draggable } from 'vue-draggable-plus'
|
||||
import {
|
||||
fetchProcessRouteList,
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按工序代码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -630,7 +630,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus, Close, Check, Rank } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, Rank, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import Sortable from 'sortablejs'
|
||||
import type { SortableEvent } from 'sortablejs'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -177,7 +177,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编号、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -302,18 +302,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
ScaleToOriginal,
|
||||
Document,
|
||||
Download,
|
||||
Delete,
|
||||
VideoPlay,
|
||||
Loading
|
||||
} from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal, Document, Download, Delete, VideoPlay, Loading, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<!-- 物料变动记录 -->
|
||||
<!-- eslint-disable vue/no-v-model-argument -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
@@ -44,6 +45,7 @@
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@@ -61,29 +63,14 @@
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="list-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<div class="list-toolbar-end">
|
||||
<el-tooltip content="列设置" placement="top">
|
||||
<el-button class="btn-ghost" circle @click="showColumnSettings = true">
|
||||
<el-icon><Setting /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="刷新" placement="top">
|
||||
<el-button class="btn-ghost" circle @click="loadData">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
@@ -93,44 +80,99 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="materialCode" label="物料编码" min-width="120" />
|
||||
<el-table-column prop="materialName" label="物料名称" min-width="140" />
|
||||
<el-table-column prop="changeType" label="变动类型" min-width="100" align="center">
|
||||
<el-table-column
|
||||
prop="materialCode"
|
||||
label="物料编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
label="物料名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="changeType"
|
||||
label="变动类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.changeType === 'use' ? 'warning' : 'success'"
|
||||
size="small"
|
||||
>
|
||||
<el-tag :type="row.changeType === 'use' ? 'warning' : 'success'" size="small">
|
||||
{{ row.changeType === 'use' ? '使用' : '入库' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="changeQty" label="变动数量" min-width="120" align="right">
|
||||
<el-table-column
|
||||
prop="changeQty"
|
||||
label="变动数量"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span :class="row.changeQty < 0 ? 'qty-negative' : 'qty-positive'">
|
||||
{{ row.changeQty < 0 ? row.changeQty : `+${row.changeQty}` }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stockAfter" label="变动后库存" min-width="120" align="right" />
|
||||
<el-table-column prop="qcTaskNo" label="关联任务号" min-width="160">
|
||||
<el-table-column
|
||||
prop="stockAfter"
|
||||
label="变动后库存"
|
||||
min-width="120"
|
||||
align="right"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="qcTaskNo"
|
||||
label="关联任务号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.qcTaskNo || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sourceType" label="来源类型" min-width="120" align="center">
|
||||
<el-table-column
|
||||
prop="sourceType"
|
||||
label="来源类型"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="getSourceTagType(row.sourceType)"
|
||||
size="small"
|
||||
>
|
||||
<el-tag :type="getSourceTagType(row.sourceType)" size="small">
|
||||
{{ getSourceLabel(row.sourceType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="createBy" label="操作人" min-width="100" />
|
||||
<el-table-column prop="createTime" label="变动时间" min-width="170" />
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createBy"
|
||||
label="操作人"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="变动时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination list-pager-bar">
|
||||
@@ -139,31 +181,36 @@
|
||||
:page-size="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
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>
|
||||
</section>
|
||||
|
||||
<!-- 列设置抽屉 -->
|
||||
<el-drawer v-model="showColumnSettings" title="列设置" size="300px">
|
||||
<p>列设置功能预留</p>
|
||||
</el-drawer>
|
||||
<!-- 查看弹窗 -->
|
||||
<SystemViewDialog
|
||||
v-model:visible="viewVisible"
|
||||
:title="viewRow?.materialName || '变动记录详情'"
|
||||
icon="Document"
|
||||
:meta="viewRow?.createTime || ''"
|
||||
:sections="viewSections"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { Search, RefreshLeft, Refresh, Setting } from '@element-plus/icons-vue'
|
||||
import { Search, RefreshLeft, View } from '@element-plus/icons-vue'
|
||||
import { fetchQcMaterialUsageList } from '@/api/qc-material-usage'
|
||||
import type { QcMaterialUsage } from '@/api/qc-material-usage'
|
||||
import { fetchQcMaterialOptions } from '@/api/qc-material'
|
||||
import type { QcMaterialOption } from '@/api/qc-material'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
|
||||
defineOptions({ name: 'QcMaterialUsage' })
|
||||
defineOptions({ name: 'QualityInspectionQcMaterialUsage' })
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -180,7 +227,6 @@ const tableData = ref<QcMaterialUsage[]>([])
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
const materialOptions = ref<QcMaterialOption[]>([])
|
||||
const showColumnSettings = ref(false)
|
||||
|
||||
function getSourceTagType(sourceType: string) {
|
||||
if (sourceType === 'task_complete') return 'primary'
|
||||
@@ -237,6 +283,44 @@ function handleReset() {
|
||||
loadData()
|
||||
}
|
||||
|
||||
const viewVisible = ref(false)
|
||||
const viewRow = ref<QcMaterialUsage | null>(null)
|
||||
|
||||
const viewSections = computed(() => {
|
||||
if (!viewRow.value) return []
|
||||
const row = viewRow.value
|
||||
return [
|
||||
{
|
||||
title: '变动信息',
|
||||
fields: [
|
||||
{ label: '物料编码', value: row.materialCode },
|
||||
{ label: '物料名称', value: row.materialName },
|
||||
{
|
||||
label: '变动类型',
|
||||
value: row.changeType === 'use' ? '使用' : '入库',
|
||||
tagType: row.changeType === 'use' ? 'warning' : 'success'
|
||||
},
|
||||
{ label: '变动数量', value: row.changeQty < 0 ? String(row.changeQty) : `+${row.changeQty}` },
|
||||
{ label: '变动后库存', value: String(row.stockAfter ?? 0) },
|
||||
{ label: '关联任务号', value: row.qcTaskNo || '-' },
|
||||
{
|
||||
label: '来源类型',
|
||||
value: getSourceLabel(row.sourceType),
|
||||
tagType: getSourceTagType(row.sourceType) as any
|
||||
},
|
||||
{ label: '备注', value: row.remark || '—', span: true },
|
||||
{ label: '操作人', value: row.createBy },
|
||||
{ label: '变动时间', value: row.createTime }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
function handleView(row: QcMaterialUsage) {
|
||||
viewRow.value = row
|
||||
viewVisible.value = true
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadMaterialOptions()
|
||||
if (route.query.materialId) {
|
||||
|
||||
@@ -28,8 +28,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按物料名称、编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -42,14 +46,14 @@
|
||||
</div>
|
||||
<el-button class="btn-main" type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增
|
||||
新增物料
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
:disabled="selectedRows.length === 0"
|
||||
@click="handleStockInFromToolbar"
|
||||
>
|
||||
入库
|
||||
<el-icon><Upload /></el-icon>入库
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,6 +66,7 @@
|
||||
stripe
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
@@ -105,12 +110,23 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center" header-align="center" />
|
||||
<el-table-column label="操作" width="260" fixed="right" align="center" header-align="center">
|
||||
<el-table-column label="操作" width="400" fixed="right" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockIn(row)">入库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleViewUsage(row)">详情</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockIn(row)">
|
||||
<el-icon><Upload /></el-icon>入库
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleViewUsage(row)">
|
||||
<el-icon><List /></el-icon>变动记录
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -240,14 +256,35 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<SystemViewDialog
|
||||
v-model:visible="viewVisible"
|
||||
:title="viewRow?.materialName || '物料详情'"
|
||||
:icon="Box"
|
||||
:sections="viewSections"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
Search,
|
||||
RefreshLeft,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
Upload,
|
||||
List,
|
||||
Box
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog, { type ViewSection } from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
fetchQcMaterialList,
|
||||
fetchCreateQcMaterial,
|
||||
@@ -259,6 +296,7 @@
|
||||
|
||||
defineOptions({ name: 'QualityInspectionQcMaterial' })
|
||||
|
||||
const router = useRouter()
|
||||
const searchForm = reactive({ keyword: '', status: '' })
|
||||
const tableData = ref<QcMaterial[]>([])
|
||||
const selectedRows = ref<QcMaterial[]>([])
|
||||
@@ -305,6 +343,45 @@
|
||||
qty: [{ required: true, message: '请输入入库数量', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const viewVisible = ref(false)
|
||||
const viewRow = ref<QcMaterial | null>(null)
|
||||
const viewSections = computed<ViewSection[]>(() => {
|
||||
if (!viewRow.value) return []
|
||||
const row = viewRow.value
|
||||
const stockLow = (row.stockQty ?? 0) <= (row.warningQty ?? 0)
|
||||
return [
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
{ label: '物料编码', value: row.materialCode },
|
||||
{ label: '物料名称', value: row.materialName },
|
||||
{ label: '规格型号', value: row.specModel },
|
||||
{ label: '单位', value: row.unit },
|
||||
{
|
||||
label: '状态',
|
||||
value: row.status === '1' ? '启用' : '停用',
|
||||
tagType: row.status === '1' ? 'success' : 'info'
|
||||
},
|
||||
{ label: '当前库存', value: String(row.stockQty ?? 0) },
|
||||
{ label: '预警库存', value: String(row.warningQty ?? 0) },
|
||||
{
|
||||
label: '库存状态',
|
||||
value: stockLow ? '库存预警' : '正常',
|
||||
tagType: stockLow ? 'danger' : 'success'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '其他信息',
|
||||
fields: [
|
||||
{ label: '备注', value: row.remark, span: true },
|
||||
{ label: '创建时间', value: row.createTime },
|
||||
{ label: '更新时间', value: row.updateTime }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const handleSelectionChange = (rows: QcMaterial[]) => {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
@@ -439,9 +516,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 查看变动记录
|
||||
function handleView(row: QcMaterial) {
|
||||
viewRow.value = row
|
||||
viewVisible.value = true
|
||||
}
|
||||
|
||||
function handleViewUsage(row: QcMaterial) {
|
||||
window.location.hash = `/sqxp/quality-inspection/qc-material-usage?materialId=${row.id}`
|
||||
router.push({
|
||||
path: '/quality-inspection/qc-material-usage',
|
||||
query: { materialId: row.id }
|
||||
})
|
||||
}
|
||||
|
||||
// 删除
|
||||
|
||||
@@ -49,7 +49,11 @@
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">
|
||||
{{ activeTab === 'pending' ? '勾选多个样本后可批量接收(同快递可一次登记,每项单独填写)。' : '双击行可查看接收详情(已接收)。' }}
|
||||
{{
|
||||
activeTab === 'pending'
|
||||
? '勾选多个样本后可批量接收(同快递可一次登记,每项单独填写)。'
|
||||
: '双击行可查看接收详情;也可点击「编辑」修改接收登记。'
|
||||
}}
|
||||
</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">
|
||||
@@ -73,7 +77,7 @@
|
||||
:disabled="selectedPendingRows.length === 0"
|
||||
@click="openBatchReceive"
|
||||
>
|
||||
批量接收
|
||||
<el-icon><Files /></el-icon>批量接收
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -174,16 +178,19 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140" fixed="right" align="center" header-align="center">
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="activeTab === 'pending'"
|
||||
class="btn-op-item primary"
|
||||
@click="openReceive(row)"
|
||||
>
|
||||
样本接收
|
||||
<el-icon><CircleCheck /></el-icon>样本接收
|
||||
</el-button>
|
||||
<el-button v-else class="btn-op-item" @click="openView(row)">查看</el-button>
|
||||
<template v-else>
|
||||
<el-button class="btn-op-item" @click="openView(row)">查看</el-button>
|
||||
<el-button class="btn-op-item" @click="openEdit(row)">编辑</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -202,12 +209,13 @@
|
||||
|
||||
<el-dialog
|
||||
v-model="receiveDialogVisible"
|
||||
title="样本接收"
|
||||
:title="receiveDialogTitle"
|
||||
width="920px"
|
||||
class="dialog-form-layout sample-reception-dialog"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@closed="handleReceiveDialogClosed"
|
||||
>
|
||||
<div v-if="currentRow" class="sample-reception-readonly">
|
||||
<h4 class="detail-section-title">样本信息</h4>
|
||||
@@ -327,7 +335,7 @@
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="submitReceive">
|
||||
<el-icon><Check /></el-icon>提交
|
||||
<el-icon><Check /></el-icon>{{ receiveDialogMode === 'edit' ? '保存' : '提交' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -538,12 +546,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Close, Check, Search, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import { Close, Check, Search, RefreshLeft, CircleCheck, Files } from '@element-plus/icons-vue'
|
||||
import { fetchUserOptions } from '@/api/admin'
|
||||
import {
|
||||
fetchSampleReceptionPendingList,
|
||||
fetchSampleReceptionReceivedList,
|
||||
fetchSampleReceptionSubmit,
|
||||
fetchSampleReceptionUpdate,
|
||||
fetchSampleReceptionDetail,
|
||||
type SampleReceptionCheckResult
|
||||
} from '@/api/sample-reception'
|
||||
import {
|
||||
@@ -575,6 +585,8 @@
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
|
||||
const receiveDialogVisible = ref(false)
|
||||
const receiveDialogMode = ref<'create' | 'edit'>('create')
|
||||
const editingReceptionId = ref('')
|
||||
const viewDialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const currentRow = ref<Record<string, unknown> | null>(null)
|
||||
@@ -628,6 +640,10 @@
|
||||
acceptConclusion: [{ required: true, message: '请选择是否接收', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const receiveDialogTitle = computed(() =>
|
||||
receiveDialogMode.value === 'edit' ? '编辑样本接收' : '样本接收'
|
||||
)
|
||||
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
const viewCheckResults = computed((): SampleReceptionCheckResult[] => {
|
||||
@@ -663,6 +679,76 @@
|
||||
return Number.isFinite(n) ? String(n) : '-'
|
||||
}
|
||||
|
||||
function apiFilesToFinanceItems(
|
||||
files?: Array<{ id?: string; fileUrl?: string; fileName?: string; fileType?: string }>
|
||||
): FinanceAttachmentItem[] {
|
||||
if (!files?.length) return []
|
||||
return files.map((f, i) => ({
|
||||
id: String(f.id || `att_${i}`),
|
||||
fileName: String(f.fileName || ''),
|
||||
fileUrl: String(f.fileUrl || ''),
|
||||
fileType: String(f.fileType || '')
|
||||
}))
|
||||
}
|
||||
|
||||
function applyCheckResultsFromDetail(results: SampleReceptionCheckResult[]) {
|
||||
const map = new Map(
|
||||
(results || []).map((r) => [String(r.checkItemId || ''), !!r.checked])
|
||||
)
|
||||
for (const item of activeCheckItems.value) {
|
||||
checkValues[item.id] = map.has(item.id) ? !!map.get(item.id) : false
|
||||
}
|
||||
}
|
||||
|
||||
function populateReceiveFormFromDetail(detail: Record<string, unknown>) {
|
||||
receiveForm.receptionTime = String(detail.receptionTime || '')
|
||||
receiveForm.handlerUserId = String(detail.handlerUserId || '')
|
||||
receiveForm.handlerName = String(detail.handlerName || '')
|
||||
receiveForm.receiveTemperature = String(detail.receiveTemperature || '')
|
||||
const vol = detail.volumeMl
|
||||
receiveForm.volumeMl =
|
||||
vol === null || vol === undefined || vol === '' ? undefined : Number(vol)
|
||||
const net = detail.netWeightG
|
||||
receiveForm.netWeightG =
|
||||
net === null || net === undefined || net === '' ? undefined : Number(net)
|
||||
receiveForm.acceptConclusion =
|
||||
String(detail.acceptConclusion || 'js') === 'bj' ? 'bj' : 'js'
|
||||
receiveForm.remark = String(detail.remark || '')
|
||||
const checkList = detail.checkResults
|
||||
applyCheckResultsFromDetail(
|
||||
Array.isArray(checkList) ? (checkList as SampleReceptionCheckResult[]) : []
|
||||
)
|
||||
if (Array.isArray(detail.receiveAttachmentFiles)) {
|
||||
attachments.value = apiFilesToFinanceItems(
|
||||
detail.receiveAttachmentFiles as Array<{
|
||||
id?: string
|
||||
fileUrl?: string
|
||||
fileName?: string
|
||||
fileType?: string
|
||||
}>
|
||||
)
|
||||
} else {
|
||||
const urls = String(detail.receiveAttachments || '')
|
||||
.split(',')
|
||||
.map((u) => u.trim())
|
||||
.filter(Boolean)
|
||||
attachments.value = urls.map((url, i) => {
|
||||
const name = url.includes('/') ? url.split('/').pop() || `附件${i + 1}` : url
|
||||
return { id: `url_${i}`, fileUrl: url, fileName: name }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function resetReceiveDialogState() {
|
||||
receiveDialogMode.value = 'create'
|
||||
editingReceptionId.value = ''
|
||||
currentRow.value = null
|
||||
}
|
||||
|
||||
function handleReceiveDialogClosed() {
|
||||
resetReceiveDialogState()
|
||||
}
|
||||
|
||||
function toOptionalMetric(v: number | undefined) {
|
||||
if (v === null || v === undefined) return undefined
|
||||
const n = Number(v)
|
||||
@@ -941,6 +1027,8 @@
|
||||
}
|
||||
|
||||
async function openReceive(row: Record<string, unknown>) {
|
||||
receiveDialogMode.value = 'create'
|
||||
editingReceptionId.value = ''
|
||||
currentRow.value = row
|
||||
if (!userOptions.value.length) {
|
||||
await loadUserOptions()
|
||||
@@ -953,6 +1041,40 @@
|
||||
receiveDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function openEdit(row: Record<string, unknown>) {
|
||||
const receptionId = String(row.id || '')
|
||||
if (!receptionId) {
|
||||
ElMessage.warning('缺少接收记录标识,无法编辑')
|
||||
return
|
||||
}
|
||||
receiveDialogMode.value = 'edit'
|
||||
editingReceptionId.value = receptionId
|
||||
currentRow.value = row
|
||||
if (!userOptions.value.length) {
|
||||
await loadUserOptions()
|
||||
} else {
|
||||
ensureCurrentUserInOptions()
|
||||
}
|
||||
await loadActiveCheckItems()
|
||||
let detail: Record<string, unknown> = row
|
||||
try {
|
||||
detail = (await fetchSampleReceptionDetail(receptionId)) as Record<string, unknown>
|
||||
} catch {
|
||||
/* 列表行已含主要字段,详情失败时回退 */
|
||||
}
|
||||
populateReceiveFormFromDetail(detail)
|
||||
if (detail.handlerUserId) {
|
||||
const hid = String(detail.handlerUserId)
|
||||
if (!userOptions.value.some((o) => o.value === hid)) {
|
||||
userOptions.value = [
|
||||
{ label: String(detail.handlerName || hid), value: hid },
|
||||
...userOptions.value
|
||||
]
|
||||
}
|
||||
}
|
||||
receiveDialogVisible.value = true
|
||||
}
|
||||
|
||||
function openView(row: Record<string, unknown>) {
|
||||
viewRow.value = row
|
||||
viewDialogVisible.value = true
|
||||
@@ -975,33 +1097,43 @@
|
||||
return
|
||||
}
|
||||
submitLoading.value = true
|
||||
const payload = {
|
||||
receptionTime: receiveForm.receptionTime,
|
||||
handlerUserId: receiveForm.handlerUserId,
|
||||
handlerName: receiveForm.handlerName,
|
||||
receiveTemperature: receiveForm.receiveTemperature || undefined,
|
||||
volumeMl: toOptionalMetric(receiveForm.volumeMl),
|
||||
netWeightG: toOptionalMetric(receiveForm.netWeightG),
|
||||
checks: activeCheckItems.value.map((item) => ({
|
||||
checkItemId: item.id,
|
||||
checked: !!checkValues[item.id]
|
||||
})),
|
||||
acceptConclusion: receiveForm.acceptConclusion,
|
||||
receiveAttachments: financeAttachmentsToUrlString(attachments.value) || undefined,
|
||||
remark: receiveForm.remark || undefined
|
||||
}
|
||||
try {
|
||||
await fetchSampleReceptionSubmit({
|
||||
sourceType: (currentRow.value.sourceType as string) || 'cell_storage',
|
||||
donationSampleId: currentRow.value.sourceType === 'research_donation'
|
||||
? String(currentRow.value.protocolSampleId)
|
||||
: undefined,
|
||||
protocolSampleId: currentRow.value.sourceType !== 'research_donation'
|
||||
? String(currentRow.value.protocolSampleId)
|
||||
: undefined,
|
||||
receptionTime: receiveForm.receptionTime,
|
||||
handlerUserId: receiveForm.handlerUserId,
|
||||
handlerName: receiveForm.handlerName,
|
||||
receiveTemperature: receiveForm.receiveTemperature || undefined,
|
||||
volumeMl: toOptionalMetric(receiveForm.volumeMl),
|
||||
netWeightG: toOptionalMetric(receiveForm.netWeightG),
|
||||
checks: activeCheckItems.value.map((item) => ({
|
||||
checkItemId: item.id,
|
||||
checked: !!checkValues[item.id]
|
||||
})),
|
||||
acceptConclusion: receiveForm.acceptConclusion,
|
||||
receiveAttachments: financeAttachmentsToUrlString(attachments.value) || undefined,
|
||||
remark: receiveForm.remark || undefined
|
||||
})
|
||||
receiveDialogVisible.value = false
|
||||
pagination.page = 1
|
||||
activeTab.value = 'received'
|
||||
loadData()
|
||||
if (receiveDialogMode.value === 'edit') {
|
||||
await fetchSampleReceptionUpdate(editingReceptionId.value, payload)
|
||||
ElMessage.success('接收信息已更新')
|
||||
receiveDialogVisible.value = false
|
||||
loadData()
|
||||
} else {
|
||||
await fetchSampleReceptionSubmit({
|
||||
sourceType: (currentRow.value.sourceType as string) || 'cell_storage',
|
||||
donationSampleId: currentRow.value.sourceType === 'research_donation'
|
||||
? String(currentRow.value.protocolSampleId)
|
||||
: undefined,
|
||||
protocolSampleId: currentRow.value.sourceType !== 'research_donation'
|
||||
? String(currentRow.value.protocolSampleId)
|
||||
: undefined,
|
||||
...payload
|
||||
})
|
||||
receiveDialogVisible.value = false
|
||||
pagination.page = 1
|
||||
activeTab.value = 'received'
|
||||
loadData()
|
||||
}
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -28,31 +28,52 @@
|
||||
<!-- KPI 摘要条 -->
|
||||
<div class="kpi-strip">
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">捐赠编号</div>
|
||||
<div class="kpi-value">{{ dash(donation?.donationNo) }}</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">状态</div>
|
||||
<div class="kpi-value">
|
||||
<el-tag :type="donationStatusTagType(String(donation?.donationStatus || ''))" size="small">
|
||||
{{ donationStatusLabel(String(donation?.donationStatus || '')) }}
|
||||
</el-tag>
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><Document /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">捐赠编号</div>
|
||||
<div class="kpi-value kpi-value--emphasis">{{ dash(donation?.donationNo) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">母亲姓名</div>
|
||||
<div class="kpi-value">{{ dash(party?.motherName) }}</div>
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">状态</div>
|
||||
<div class="kpi-value">
|
||||
<el-tag :type="donationStatusTagType(String(donation?.donationStatus || ''))" size="small">
|
||||
{{ donationStatusLabel(String(donation?.donationStatus || '')) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">捐赠日期</div>
|
||||
<div class="kpi-value">{{ formatYmd(donation?.donationDate) }}</div>
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><User /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">母亲姓名</div>
|
||||
<div class="kpi-value">{{ dash(party?.motherName) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">捐赠日期</div>
|
||||
<div class="kpi-value">{{ formatYmd(donation?.donationDate) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div id="view-section-basic" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>基本信息</h3></div>
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"
|
||||
><div class="data-label">捐赠编号</div
|
||||
><div class="data-value">{{ dash(donation?.donationNo) }}</div></div
|
||||
@@ -82,60 +103,6 @@
|
||||
><div class="data-value">{{ dash(donation?.createTime) }}</div></div
|
||||
>
|
||||
</div>
|
||||
<!-- 状态变更记录 -->
|
||||
<div v-if="hasAnyStatusChange" class="status-change-section">
|
||||
<div class="status-change-title">状态变更记录</div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<template v-if="donation?.effectiveTime">
|
||||
<div class="data-item"
|
||||
><div class="data-label">生效时间</div
|
||||
><div class="data-value">{{ dash(donation.effectiveTime) }}</div></div
|
||||
>
|
||||
<div class="data-item"
|
||||
><div class="data-label">生效描述</div
|
||||
><div class="data-value data-value--muted">{{
|
||||
dash(donation.effectiveDesc)
|
||||
}}</div></div
|
||||
>
|
||||
</template>
|
||||
<template v-if="donation?.expireTime">
|
||||
<div class="data-item"
|
||||
><div class="data-label">过期时间</div
|
||||
><div class="data-value">{{ dash(donation.expireTime) }}</div></div
|
||||
>
|
||||
<div class="data-item"
|
||||
><div class="data-label">过期描述</div
|
||||
><div class="data-value data-value--muted">{{
|
||||
dash(donation.expireDesc)
|
||||
}}</div></div
|
||||
>
|
||||
</template>
|
||||
<template v-if="donation?.voidTime">
|
||||
<div class="data-item"
|
||||
><div class="data-label">作废时间</div
|
||||
><div class="data-value">{{ dash(donation.voidTime) }}</div></div
|
||||
>
|
||||
<div class="data-item"
|
||||
><div class="data-label">作废描述</div
|
||||
><div class="data-value data-value--muted">{{
|
||||
dash(donation.voidDesc)
|
||||
}}</div></div
|
||||
>
|
||||
</template>
|
||||
<template v-if="donation?.convertTime">
|
||||
<div class="data-item"
|
||||
><div class="data-label">转细胞存储时间</div
|
||||
><div class="data-value">{{ dash(donation.convertTime) }}</div></div
|
||||
>
|
||||
<div class="data-item"
|
||||
><div class="data-label">转细胞存储描述</div
|
||||
><div class="data-value data-value--muted">{{
|
||||
dash(donation.convertDesc)
|
||||
}}</div></div
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 备注 -->
|
||||
<div class="remark-row" v-if="donation?.remark">
|
||||
<div class="data-item data-item--full"
|
||||
@@ -143,6 +110,80 @@
|
||||
><div class="data-value data-value--muted">{{ dash(donation?.remark) }}</div></div
|
||||
>
|
||||
</div>
|
||||
<!-- 知情同意书 -->
|
||||
<div v-if="consentAttachmentFiles.length" class="attachment-row">
|
||||
<div class="data-label">知情同意书</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="consentAttachmentFiles"
|
||||
@preview="openPreview"
|
||||
@download="downloadFile"
|
||||
/>
|
||||
</div>
|
||||
<!-- 其他附件 -->
|
||||
<div v-if="otherAttachmentFiles.length" class="attachment-row">
|
||||
<div class="data-label">其他附件</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="otherAttachmentFiles"
|
||||
@download="downloadFile"
|
||||
@preview="openPreview"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态变更记录(独立区域) -->
|
||||
<div id="view-section-status-change" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>状态变更记录</h3></div>
|
||||
<el-empty v-if="!hasAnyStatusChange" description="暂无状态变更记录" :image-size="64" />
|
||||
<div v-else class="data-grid data-grid--4">
|
||||
<template v-if="donation?.effectiveTime">
|
||||
<div class="data-item"
|
||||
><div class="data-label">生效时间</div
|
||||
><div class="data-value">{{ dash(donation.effectiveTime) }}</div></div
|
||||
>
|
||||
<div class="data-item"
|
||||
><div class="data-label">生效描述</div
|
||||
><div class="data-value data-value--muted">{{
|
||||
dash(donation.effectiveDesc)
|
||||
}}</div></div
|
||||
>
|
||||
</template>
|
||||
<template v-if="donation?.expireTime">
|
||||
<div class="data-item"
|
||||
><div class="data-label">过期时间</div
|
||||
><div class="data-value">{{ dash(donation.expireTime) }}</div></div
|
||||
>
|
||||
<div class="data-item"
|
||||
><div class="data-label">过期描述</div
|
||||
><div class="data-value data-value--muted">{{
|
||||
dash(donation.expireDesc)
|
||||
}}</div></div
|
||||
>
|
||||
</template>
|
||||
<template v-if="donation?.voidTime">
|
||||
<div class="data-item"
|
||||
><div class="data-label">作废时间</div
|
||||
><div class="data-value">{{ dash(donation.voidTime) }}</div></div
|
||||
>
|
||||
<div class="data-item"
|
||||
><div class="data-label">作废描述</div
|
||||
><div class="data-value data-value--muted">{{
|
||||
dash(donation.voidDesc)
|
||||
}}</div></div
|
||||
>
|
||||
</template>
|
||||
<template v-if="donation?.convertTime">
|
||||
<div class="data-item"
|
||||
><div class="data-label">转细胞存储时间</div
|
||||
><div class="data-value">{{ dash(donation.convertTime) }}</div></div
|
||||
>
|
||||
<div class="data-item"
|
||||
><div class="data-label">转细胞存储描述</div
|
||||
><div class="data-value data-value--muted">{{
|
||||
dash(donation.convertDesc)
|
||||
}}</div></div
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 客户信息 -->
|
||||
@@ -154,7 +195,7 @@
|
||||
<div class="sub-card-header"
|
||||
><span class="sub-card-icon">🤱</span><h4>母亲信息</h4></div
|
||||
>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"
|
||||
><div class="data-label">关联客户</div
|
||||
><div class="data-value">{{
|
||||
@@ -194,7 +235,7 @@
|
||||
<div class="sub-card-header"
|
||||
><span class="sub-card-icon">👨</span><h4>父亲信息</h4></div
|
||||
>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"
|
||||
><div class="data-label">父亲姓名</div
|
||||
><div class="data-value">{{ dash(party?.fatherName) }}</div></div
|
||||
@@ -214,7 +255,7 @@
|
||||
<div class="sub-card-header"
|
||||
><span class="sub-card-icon">👶</span><h4>新生儿信息</h4></div
|
||||
>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"
|
||||
><div class="data-label">新生儿姓名</div
|
||||
><div class="data-value">{{ dash(party?.newbornName) }}</div></div
|
||||
@@ -263,7 +304,7 @@
|
||||
<!-- 采集信息 -->
|
||||
<div id="view-section-collection" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>采集信息</h3></div>
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item"
|
||||
><div class="data-label">采集地区</div
|
||||
><div class="data-value">{{ regionDisplay }}</div></div
|
||||
@@ -308,7 +349,7 @@
|
||||
<div class="sample-card__body">
|
||||
<div class="sample-card__section">
|
||||
<div class="sample-card__section-label">样本信息</div>
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item">
|
||||
<div class="data-label">样本编码</div>
|
||||
<div class="data-value data-value--code">{{ dash(row.sampleCode) }}</div>
|
||||
@@ -334,7 +375,7 @@
|
||||
class="sample-card__section sample-card__section--transport"
|
||||
>
|
||||
<div class="sample-card__section-label">运输信息</div>
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item">
|
||||
<div class="data-label">运输类型</div>
|
||||
<div class="data-value">{{
|
||||
@@ -357,6 +398,79 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sample-card__section sample-card__section--reception">
|
||||
<div class="sample-card__section-label">样本接收信息</div>
|
||||
<template v-if="row.sampleReception">
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item">
|
||||
<div class="data-label">接收时间</div>
|
||||
<div class="data-value">{{ dash(row.sampleReception.receptionTime) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">经手人</div>
|
||||
<div class="data-value">{{ dash(row.sampleReception.handlerName) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">接收温度(℃)</div>
|
||||
<div class="data-value">
|
||||
{{
|
||||
row.sampleReception.receiveTemperature
|
||||
? row.sampleReception.receiveTemperature + '℃'
|
||||
: '—'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">净重(g)</div>
|
||||
<div class="data-value">{{ formatMetric(row.sampleReception.netWeightG) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">结论</div>
|
||||
<div class="data-value">
|
||||
<el-tag
|
||||
:type="acceptConclusionTagType(String(row.sampleReception.acceptConclusion || ''))"
|
||||
size="small"
|
||||
>
|
||||
{{ acceptConclusionLabel(String(row.sampleReception.acceptConclusion || '')) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item data-item--full">
|
||||
<div class="data-label">检查项</div>
|
||||
<div
|
||||
v-if="sampleReceptionCheckList(row.sampleReception).length"
|
||||
class="check-list check-list-view"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="chk in sampleReceptionCheckList(row.sampleReception)"
|
||||
:key="chk.itemCode + String(chk.checkItemId)"
|
||||
:model-value="chk.checked"
|
||||
disabled
|
||||
>
|
||||
{{ chk.itemName }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<div v-else class="data-value">—</div>
|
||||
</div>
|
||||
<div v-if="row.sampleReception.remark" class="data-item data-item--full">
|
||||
<div class="data-label">备注</div>
|
||||
<div class="data-value data-value--muted">{{ dash(row.sampleReception.remark) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="sampleReceptionAttachmentFiles(row.sampleReception).length"
|
||||
class="attachment-row sample-card__attachments"
|
||||
>
|
||||
<div class="data-label">接收附件</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="sampleReceptionAttachmentFiles(row.sampleReception)"
|
||||
@preview="openPreview"
|
||||
@download="downloadFile"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="sample-reception-empty">暂未接收</div>
|
||||
</div>
|
||||
<SampleQcInfoCards :qc-info="row.qcInfo" :sample-reception="row.sampleReception" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -364,34 +478,6 @@
|
||||
<el-empty v-else description="暂无样本" :image-size="48" />
|
||||
</div>
|
||||
|
||||
<!-- 附件 -->
|
||||
<div id="view-section-attachment" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>附件</h3></div>
|
||||
<!-- 知情同意书 -->
|
||||
<div v-if="consentAttachmentFiles.length" class="attachment-row">
|
||||
<div class="data-label">知情同意书</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="consentAttachmentFiles"
|
||||
@preview="openPreview"
|
||||
@download="downloadFile"
|
||||
/>
|
||||
</div>
|
||||
<!-- 其他附件 -->
|
||||
<div v-if="otherAttachmentFiles.length" class="attachment-row">
|
||||
<div class="data-label">其他附件</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="otherAttachmentFiles"
|
||||
@download="downloadFile"
|
||||
@preview="openPreview"
|
||||
/>
|
||||
</div>
|
||||
<el-empty
|
||||
v-if="!consentAttachmentFiles.length && !otherAttachmentFiles.length"
|
||||
description="暂无附件"
|
||||
:image-size="48"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 审核记录 -->
|
||||
<div
|
||||
v-if="showAuditHistory"
|
||||
@@ -486,7 +572,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { Close } from '@element-plus/icons-vue'
|
||||
import { Calendar, CircleCheck, Close, Document, User } from '@element-plus/icons-vue'
|
||||
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
|
||||
import SampleQcInfoCards from '@/components/business/sample-qc-info-cards/index.vue'
|
||||
import {
|
||||
@@ -514,7 +600,9 @@
|
||||
deliveryModeLabel,
|
||||
sampleStatusLabel,
|
||||
sampleStatusTagType,
|
||||
transportTypeLabel
|
||||
transportTypeLabel,
|
||||
acceptConclusionLabel,
|
||||
acceptConclusionTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
|
||||
defineOptions({ name: 'DonationViewDialog' })
|
||||
@@ -536,10 +624,10 @@
|
||||
|
||||
const baseFormTabs = [
|
||||
{ key: 'basic', label: '基本信息' },
|
||||
{ key: 'status-change', label: '状态变更记录' },
|
||||
{ key: 'party', label: '客户信息' },
|
||||
{ key: 'collection', label: '采集信息' },
|
||||
{ key: 'sample', label: '样本及运输信息' },
|
||||
{ key: 'attachment', label: '附件' }
|
||||
{ key: 'sample', label: '样本及运输信息' }
|
||||
] as const
|
||||
|
||||
const formTabs = computed(() => {
|
||||
@@ -662,6 +750,12 @@
|
||||
return String(val)
|
||||
}
|
||||
|
||||
function formatMetric(v: unknown) {
|
||||
if (v === null || v === undefined || v === '') return '—'
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? String(n) : '—'
|
||||
}
|
||||
|
||||
function formatYmd(val: unknown) {
|
||||
const s = String(val || '').replace(/-/g, '')
|
||||
if (s.length === 8) return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`
|
||||
@@ -696,6 +790,27 @@
|
||||
return expressCompanyOptions.value.find((x) => x.id === id)?.expressName || dash(id)
|
||||
}
|
||||
|
||||
function sampleReceptionCheckList(reception: Record<string, any>) {
|
||||
const list = reception?.checkResults
|
||||
return Array.isArray(list) ? list : []
|
||||
}
|
||||
|
||||
function sampleReceptionAttachmentFiles(reception: Record<string, any>) {
|
||||
const list = reception?.receiveAttachmentFiles
|
||||
if (Array.isArray(list) && list.length) return list
|
||||
const urls = String(reception?.receiveAttachments || '')
|
||||
if (!urls.trim()) return []
|
||||
return urls
|
||||
.split(',')
|
||||
.map((u, i) => {
|
||||
const url = u.trim()
|
||||
if (!url) return null
|
||||
const name = url.includes('/') ? url.split('/').pop() || `附件${i + 1}` : url
|
||||
return { id: `recv_${i}`, fileUrl: url, fileName: name }
|
||||
})
|
||||
.filter(Boolean) as Array<Record<string, unknown>>
|
||||
}
|
||||
|
||||
function sampleTransportExtra(transport: Record<string, any> | undefined) {
|
||||
if (!transport?.transportType) return '—'
|
||||
if (transport.transportType === 'kdys') {
|
||||
@@ -897,57 +1012,77 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- KPI 摘要条 ---- */
|
||||
/* ---- KPI 摘要条(与采集协议单查看弹窗一致:一行四列 + 薄荷绿通栏) ---- */
|
||||
.kpi-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
gap: 0;
|
||||
margin-bottom: 24px;
|
||||
padding: 6px;
|
||||
background: linear-gradient(135deg, #ecfdf5 0%, #f8fafc 52%, #ecfdf5 100%);
|
||||
border: 1px solid #a7f3d0;
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 18px 20px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
border-left: 3px solid #10b981;
|
||||
transition:
|
||||
transform 0.2s,
|
||||
box-shadow 0.2s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 14px 18px;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: radial-gradient(circle at top right, rgba(16, 185, 129, 0.06), transparent 70%);
|
||||
pointer-events: none;
|
||||
&:not(:last-child) {
|
||||
border-right: 1px solid rgba(167, 243, 208, 0.75);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.1);
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
}
|
||||
|
||||
.kpi-card__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #a7f3d0;
|
||||
color: #059669;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.kpi-card__main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.kpi-label {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
font-size: 16px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
color: #0f172a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
&--emphasis {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #047857;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Section Card ---- */
|
||||
@@ -1200,13 +1335,68 @@
|
||||
}
|
||||
|
||||
.sample-card__section {
|
||||
&--transport {
|
||||
&--transport,
|
||||
&--reception {
|
||||
margin-top: 2px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed #e2e8f0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.sample-card__section--qc) {
|
||||
margin-top: 2px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed #e2e8f0;
|
||||
}
|
||||
|
||||
.sample-reception-empty {
|
||||
font-size: 13px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.check-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 20px;
|
||||
}
|
||||
|
||||
.check-list-view {
|
||||
pointer-events: none;
|
||||
|
||||
:deep(.el-checkbox.is-disabled) {
|
||||
opacity: 1;
|
||||
cursor: default;
|
||||
|
||||
.el-checkbox__label {
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.el-checkbox__input.is-disabled {
|
||||
opacity: 1;
|
||||
cursor: default;
|
||||
|
||||
.el-checkbox__inner {
|
||||
background-color: var(--el-checkbox-bg-color);
|
||||
border-color: var(--el-checkbox-input-border-color);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.is-checked .el-checkbox__inner {
|
||||
background-color: var(--el-checkbox-checked-bg-color);
|
||||
border-color: var(--el-checkbox-checked-input-border-color);
|
||||
|
||||
&::after {
|
||||
border-color: var(--el-checkbox-checked-icon-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sample-card__attachments {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.sample-card__section-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
@@ -1232,6 +1422,15 @@
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.kpi-strip .kpi-card {
|
||||
border-right: none !important;
|
||||
border-bottom: 1px solid rgba(167, 243, 208, 0.75);
|
||||
|
||||
&:nth-last-child(-n + 2) {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.client-sub-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -1247,6 +1446,14 @@
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.kpi-strip .kpi-card:nth-last-child(-n + 2) {
|
||||
border-bottom: 1px solid rgba(167, 243, 208, 0.75);
|
||||
}
|
||||
|
||||
.kpi-strip .kpi-card:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.client-sub-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">支持按分类名称与状态筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -440,7 +440,7 @@
|
||||
import { ref, reactive, onMounted, nextTick, watch, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import { Plus, Search, Rank, Close, Check, View, Files } from '@element-plus/icons-vue'
|
||||
import { Plus, Search, Rank, Close, Check, View, Files, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import { VueDraggable as draggable } from 'vue-draggable-plus'
|
||||
import type { UploadFile } from 'element-plus'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按组织名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -225,7 +225,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, OfficeBuilding } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, OfficeBuilding, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
fetchDeptTree,
|
||||
|
||||
@@ -25,10 +25,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按字典名称、类型编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">
|
||||
搜索
|
||||
</el-button>
|
||||
<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>
|
||||
@@ -356,7 +354,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Collection } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Collection, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按快递公司名称、编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -150,7 +150,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Van } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Van, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按原因、类型模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -176,7 +176,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Close, Check } from '@element-plus/icons-vue'
|
||||
import { Close, Check, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { leaveStatusLabel, leaveStatusTagType } from '@/utils/status-tag-registry'
|
||||
import {
|
||||
fetchLeaveList,
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按菜单名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -270,7 +270,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Menu as MenuIcon } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Menu as MenuIcon, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
fetchMenuTree,
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按职位名称、编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -272,7 +272,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, User, View, Briefcase } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, User, View, Briefcase, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">支持按商品名称、SKU 与状态筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -791,21 +791,7 @@
|
||||
type UploadFile
|
||||
} from 'element-plus'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
Plus,
|
||||
Delete,
|
||||
VideoPlay,
|
||||
Document,
|
||||
Picture,
|
||||
Setting,
|
||||
Search,
|
||||
CopyDocument,
|
||||
Rank,
|
||||
Loading,
|
||||
Operation,
|
||||
Close,
|
||||
Check
|
||||
} from '@element-plus/icons-vue'
|
||||
import { Plus, Delete, VideoPlay, Document, Picture, Setting, Search, CopyDocument, Rank, Loading, Operation, Close, Check, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import { VueDraggable as draggable } from 'vue-draggable-plus'
|
||||
import ProductViewDialog from '@/components/business/product-view-dialog/index.vue'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按角色名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -368,7 +368,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, DataAnalysis, View, Stamp } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, DataAnalysis, View, Stamp, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按站点名称、编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -229,7 +229,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Location } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Location, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按租户编号、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -226,7 +226,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, OfficeBuilding } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, OfficeBuilding, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按名称、编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -150,7 +150,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">支持按关键词、状态筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
||||
<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>
|
||||
@@ -215,7 +215,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, UserFilled } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, UserFilled, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
fetchUserList,
|
||||
|
||||
Reference in new issue
Block a user