From 433ce5243a52344848b228b5abf0ae45554d694a Mon Sep 17 00:00:00 2001 From: Lewis_yan Date: Wed, 8 Apr 2026 21:25:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20=E5=AE=8C=E6=88=90=E5=89=A9?= =?UTF-8?q?=E4=BD=99=20CRUD=20=E6=96=87=E4=BB=B6=E5=9B=BD=E9=99=85?= =?UTF-8?q?=E5=8C=96=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit basicinfo: company, systemno, emailnotice, supplier_user miscprocurement: misc_materials, misc_stations, cost_template, rfqmiscellaneous pissupplier: quotation - 所有硬编码中文字符串替换为 t() i18n 调用 - 同步补充所有翻译 key (zh-cn/en/zh-tw) --- web/src/i18n/pages/basicinfo/en.ts | 112 ++++++ web/src/i18n/pages/basicinfo/zh-cn.ts | 112 ++++++ web/src/i18n/pages/basicinfo/zh-tw.ts | 112 ++++++ web/src/i18n/pages/menu/zh-cn.ts | 1 + web/src/i18n/pages/miscprocurement/en.ts | 132 +++++++ web/src/i18n/pages/miscprocurement/zh-cn.ts | 132 +++++++ web/src/i18n/pages/miscprocurement/zh-tw.ts | 132 +++++++ web/src/i18n/pages/pissupplier/en.ts | 75 ++++ web/src/i18n/pages/pissupplier/zh-cn.ts | 75 ++++ web/src/i18n/pages/pissupplier/zh-tw.ts | 75 ++++ .../views/pisadmin/basicinfo/company/crud.tsx | 31 +- .../pisadmin/basicinfo/emailnotice/crud.tsx | 81 ++--- .../pisadmin/basicinfo/supplier_user/crud.tsx | 57 ++- .../pisadmin/basicinfo/systemno/crud.tsx | 74 ++-- .../miscprocurement/cost_template/crud.tsx | 324 ++++++++++++++---- .../miscprocurement/misc_materials/crud.tsx | 32 +- .../miscprocurement/misc_stations/crud.tsx | 53 +-- .../miscprocurement/rfqmiscellaneous/crud.tsx | 175 +++++----- web/src/views/pissupplier/quotation/crud.tsx | 204 +++++------ 19 files changed, 1584 insertions(+), 405 deletions(-) create mode 100644 web/src/i18n/pages/basicinfo/en.ts create mode 100644 web/src/i18n/pages/basicinfo/zh-cn.ts create mode 100644 web/src/i18n/pages/basicinfo/zh-tw.ts create mode 100644 web/src/i18n/pages/miscprocurement/en.ts create mode 100644 web/src/i18n/pages/miscprocurement/zh-cn.ts create mode 100644 web/src/i18n/pages/miscprocurement/zh-tw.ts create mode 100644 web/src/i18n/pages/pissupplier/en.ts create mode 100644 web/src/i18n/pages/pissupplier/zh-cn.ts create mode 100644 web/src/i18n/pages/pissupplier/zh-tw.ts diff --git a/web/src/i18n/pages/basicinfo/en.ts b/web/src/i18n/pages/basicinfo/en.ts new file mode 100644 index 0000000..3ed6e07 --- /dev/null +++ b/web/src/i18n/pages/basicinfo/en.ts @@ -0,0 +1,112 @@ +// Definition +export default { + message: { + pages: { + basicinfo: { + unit: { + title: 'Unit of Measure', + unitname: 'Unit Name', + unitcode: 'Unit Code', + unitnameEn: 'Unit Name (English)', + unitnameZhTw: 'Unit Name (Traditional Chinese)', + status: 'Status', + }, + currency: { + title: 'Currency', + currencyname: 'Currency Name', + currencycode: 'Currency Code', + currencysymbol: 'Currency Symbol', + factory: 'Factory', + tax: 'Tax Rate', + currencynameEn: 'Currency Name (English)', + currencynameZhTw: 'Currency Name (Traditional Chinese)', + status: 'Status', + }, + supplier: { + title: 'Supplier Information', + companyCode: 'Company Code', + supplierId: 'Supplier ID', + supplierName: 'Supplier Full Name', + supplierShortName: 'Supplier Short Name', + vendorType: 'Vendor Type', + paymentTerms: 'Payment Terms', + transactionCurrency: 'Transaction Currency', + contactPerson: 'Contact Person', + contactPhone: 'Contact Phone', + contactEmail: 'Contact Email', + country: 'Country', + province: 'Province', + city: 'City', + address: 'Address', + status: 'Status', + }, + company: { + title: 'Company Information', + companyCode: 'Company Code', + companyName: 'Company Full Name', + companyShortName: 'Company Short Name', + companyAddress: 'Company Address', + status: 'Status', + }, + emailnotice: { + title: 'Email Notification Log', + subject: 'Email Subject', + bizType: 'Business Type', + bizId: 'Business ID', + toEmails: 'To', + ccEmails: 'CC', + bccEmails: 'BCC', + status: 'Send Status', + sentAt: 'Sent At', + attachments: 'Attachments', + lastError: 'Error Info', + messageId: 'Message ID', + retryCount: 'Retry Count', + statusPending: 'Pending', + statusSending: 'Sending', + statusSuccess: 'Sent', + statusFailed: 'Failed', + resend: 'Resend', + selectOrSearch: 'Select or search', + }, + supplierUser: { + title: 'Supplier User', + supplierId: 'Supplier ID', + supplierName: 'Supplier Full Name', + supplierRole: 'Supplier Role', + userEmail: 'Contact Email', + userName: 'Contact Person', + userPhone: 'Contact Phone', + status: 'Status', + }, + systemNo: { + title: 'Document Number Rule', + companyCode: 'Company Code', + ruleCode: 'Rule Code', + resetCycle: 'Reset Cycle', + resetCycleYy2: 'Yearly 2 (YY)', + resetCycleYyyy4: 'Yearly 4 (YYYY)', + resetCycleYymm4: 'Monthly 4 (YYMM)', + resetCycleYyyymm6: 'Monthly 6 (YYYYMM)', + resetCycleYymmdd6: 'Daily 6 (YYMMDD)', + resetCycleYyyymmdd8: 'Daily 8 (YYYYMMDD)', + ruleCodeMiscQts: 'miscQTS (Misc Quote)', + ruleCodeMiscRfs: 'miscRFS (Misc RFQ)', + prefix: 'Prefix', + factoryCode: 'Factory Code', + seqLength: 'Sequence Length', + createUser: 'Creator', + updateUser: 'Modifier', + sequenceDate: 'Sequence Date', + prevSequence: 'Previous Sequence', + currentSequence: 'Current Sequence', + lastGenerateUser: 'Last Generator', + lastGenerateTime: 'Last Generate Time', + status: 'Status', + statusAvailable: 'Available', + statusUnavailable: 'Unavailable', + }, + }, + }, + }, +}; diff --git a/web/src/i18n/pages/basicinfo/zh-cn.ts b/web/src/i18n/pages/basicinfo/zh-cn.ts new file mode 100644 index 0000000..9ae968e --- /dev/null +++ b/web/src/i18n/pages/basicinfo/zh-cn.ts @@ -0,0 +1,112 @@ +// 定义内容 +export default { + message: { + pages: { + basicinfo: { + unit: { + title: '计量单位', + unitname: '计量单位名称', + unitcode: '计量单位代码', + unitnameEn: '计量单位英文名', + unitnameZhTw: '计量单位繁体名', + status: '状态', + }, + currency: { + title: '货币', + currencyname: '货币名称', + currencycode: '货币代码', + currencysymbol: '货币符号', + factory: '交易厂区', + tax: '税率', + currencynameEn: '货币英文名', + currencynameZhTw: '货币繁体名', + status: '状态', + }, + supplier: { + title: '供应商信息', + companyCode: '交易厂区', + supplierId: '供应商唯一ID', + supplierName: '供应商全称', + supplierShortName: '供应商简称', + vendorType: '厂商性质', + paymentTerms: '付款条件', + transactionCurrency: '交易货币', + contactPerson: '联络人', + contactPhone: '联络人电话', + contactEmail: '联络人邮箱', + country: '国家', + province: '省州', + city: '城市', + address: '详细地址', + status: '状态', + }, + company: { + title: '公司信息', + companyCode: '公司代码', + companyName: '公司全称', + companyShortName: '公司简称', + companyAddress: '公司地址', + status: '状态', + }, + supplierUser: { + title: '供应商用户', + supplierId: '供应商唯一ID', + supplierName: '供应商全称', + supplierRole: '供应商角色', + userEmail: '联络人邮箱', + userName: '联络人', + userPhone: '联络人电话', + status: '状态', + }, + emailnotice: { + title: '邮件通知记录', + subject: '邮件主题', + bizType: '业务类型', + bizId: '业务标识', + toEmails: '收件人', + ccEmails: '抄送', + bccEmails: '密送', + status: '发送状态', + sentAt: '发送时间', + attachments: '附件', + lastError: '错误信息', + messageId: '消息ID', + retryCount: '重试次数', + statusPending: '待发送', + statusSending: '发送中', + statusSuccess: '已发送', + statusFailed: '发送失败', + resend: '重送', + selectOrSearch: '请选择或搜索', + }, + systemNo: { + title: '单据编号规则', + companyCode: '交易厂区', + ruleCode: '生成单据标识号', + resetCycle: '流水码重置类别', + resetCycleYy2: '按年2(YY)', + resetCycleYyyy4: '按年4(YYYY)', + resetCycleYymm4: '按月4(YYMM)', + resetCycleYyyymm6: '按月6(YYYYMM)', + resetCycleYymmdd6: '按日6(YYMMDD)', + resetCycleYyyymmdd8: '按日8(YYYYMMDD)', + ruleCodeMiscQts: 'miscQTS (杂采报价单)', + ruleCodeMiscRfs: 'miscRFS (杂采询价单)', + prefix: '单据头', + factoryCode: '厂区区分码', + seqLength: '流水码长度', + createUser: '单据创建人', + updateUser: '单据修改人', + sequenceDate: '流水日期', + prevSequence: '上一流水码', + currentSequence: '下一流水码', + lastGenerateUser: '单据最后产生人', + lastGenerateTime: '单据最后产生时间', + status: '可用状态', + statusAvailable: '可用', + statusUnavailable: '不可用', + }, + }, + }, + }, +}; diff --git a/web/src/i18n/pages/basicinfo/zh-tw.ts b/web/src/i18n/pages/basicinfo/zh-tw.ts new file mode 100644 index 0000000..23ff171 --- /dev/null +++ b/web/src/i18n/pages/basicinfo/zh-tw.ts @@ -0,0 +1,112 @@ +// 定義內容 +export default { + message: { + pages: { + basicinfo: { + unit: { + title: '計量單位', + unitname: '計量單位名稱', + unitcode: '計量單位代碼', + unitnameEn: '計量單位英文名', + unitnameZhTw: '計量單位繁體名', + status: '狀態', + }, + currency: { + title: '貨幣', + currencyname: '貨幣名稱', + currencycode: '貨幣代碼', + currencysymbol: '貨幣符號', + factory: '交易廠區', + tax: '稅率', + currencynameEn: '貨幣英文名', + currencynameZhTw: '貨幣繁體名', + status: '狀態', + }, + supplier: { + title: '供應商資訊', + companyCode: '交易廠區', + supplierId: '供應商唯一ID', + supplierName: '供應商全稱', + supplierShortName: '供應商簡稱', + vendorType: '廠商性質', + paymentTerms: '付款條件', + transactionCurrency: '交易貨幣', + contactPerson: '聯絡人', + contactPhone: '聯絡人電話', + contactEmail: '聯絡人郵箱', + country: '國家', + province: '省州', + city: '城市', + address: '詳細地址', + status: '狀態', + }, + company: { + title: '公司資訊', + companyCode: '公司代碼', + companyName: '公司全稱', + companyShortName: '公司簡稱', + companyAddress: '公司地址', + status: '狀態', + }, + emailnotice: { + title: '郵件通知記錄', + subject: '郵件主題', + bizType: '業務類型', + bizId: '業務標識', + toEmails: '收件人', + ccEmails: '抄送', + bccEmails: '密送', + status: '發送狀態', + sentAt: '發送時間', + attachments: '附件', + lastError: '錯誤資訊', + messageId: '訊息ID', + retryCount: '重試次數', + statusPending: '待發送', + statusSending: '發送中', + statusSuccess: '已發送', + statusFailed: '發送失敗', + resend: '重送', + selectOrSearch: '請選擇或搜尋', + }, + supplierUser: { + title: '供應商用戶', + supplierId: '供應商唯一ID', + supplierName: '供應商全稱', + supplierRole: '供應商角色', + userEmail: '聯絡人郵箱', + userName: '聯絡人', + userPhone: '聯絡人電話', + status: '狀態', + }, + systemNo: { + title: '單據編號規則', + companyCode: '交易廠區', + ruleCode: '生成單據識別號', + resetCycle: '流水碼重置類別', + resetCycleYy2: '按年2(YY)', + resetCycleYyyy4: '按年4(YYYY)', + resetCycleYymm4: '按月4(YYMM)', + resetCycleYyyymm6: '按月6(YYYYMM)', + resetCycleYymmdd6: '按日6(YYMMDD)', + resetCycleYyyymmdd8: '按日8(YYYYMMDD)', + ruleCodeMiscQts: 'miscQTS (雜採報價單)', + ruleCodeMiscRfs: 'miscRFS (雜採詢價單)', + prefix: '單據頭', + factoryCode: '廠區區分碼', + seqLength: '流水碼長度', + createUser: '單據創建人', + updateUser: '單據修改人', + sequenceDate: '流水日期', + prevSequence: '上一流水碼', + currentSequence: '下一流水碼', + lastGenerateUser: '單據最後產生人', + lastGenerateTime: '單據最後產生時間', + status: '可用狀態', + statusAvailable: '可用', + statusUnavailable: '不可用', + }, + }, + }, + }, +}; diff --git a/web/src/i18n/pages/menu/zh-cn.ts b/web/src/i18n/pages/menu/zh-cn.ts index 41a2d2b..db96b98 100644 --- a/web/src/i18n/pages/menu/zh-cn.ts +++ b/web/src/i18n/pages/menu/zh-cn.ts @@ -88,6 +88,7 @@ export default { widthPlaceholder: '请输入宽度', }, validation: { + alreadyExists: '已存在,不可重复', fieldNameRequired: '请输入字段名!', titleRequired: '请输入列名!', fieldNameFieldRequired: '请输入字段名称!', diff --git a/web/src/i18n/pages/miscprocurement/en.ts b/web/src/i18n/pages/miscprocurement/en.ts new file mode 100644 index 0000000..7a46679 --- /dev/null +++ b/web/src/i18n/pages/miscprocurement/en.ts @@ -0,0 +1,132 @@ +// Definition +export default { + message: { + pages: { + miscprocurement: { + materialInfo: { + title: 'Misc Procurement Material Info', + factory: 'Factory', + materialtype: 'Material Type', + materialtypeEn: 'Material Type (English)', + materialtypeZhTw: 'Material Type (Traditional Chinese)', + density: 'Density', + price: 'Unit Price', + status: 'Status', + }, + stationInfo: { + title: 'Misc Procurement Station Info', + companyCode: 'Company Code', + stationname: 'Station Name', + stationnameEn: 'Station Name (English)', + stationnameZhTw: 'Station Name (Traditional Chinese)', + stationcode: 'Station Code', + stationtype: 'Station Type', + unit: 'Unit', + rate: 'Rate', + status: 'Status', + }, + inquiry: { + title: 'Inquiry', + inquiryNo: 'Inquiry No', + title_label: 'Inquiry Title', + titleEn: 'Inquiry Title (English)', + titleZhTw: 'Inquiry Title (Traditional Chinese)', + purchaseType: 'Purchase Type', + materialType: 'Material Type', + template: 'Template', + currency: 'Currency', + companyCode: 'Company Code', + purchaseDept: 'Purchase Department', + buyer: 'Buyer', + quoteDeadline: 'Quote Deadline', + targetPrice: 'Target Price', + leadTimeDays: 'Lead Time (Days)', + paymentMethod: 'Payment Method', + status: 'Status', + buyingMethod: 'Buying Method', + }, + costTemplate: { + title: 'Cost Estimate Template', + templateNo: 'Template No', + templateName: 'Template Name', + templateNameEn: 'Template Name (English)', + templateNameZhTw: 'Template Name (Traditional Chinese)', + procurementCategory: 'Procurement Category', + templateDesc: 'Template Description', + status: 'Status', + }, + rfqmiscellaneous: { + title: 'Misc RFQ', + inquiryNo: 'RFQ No', + inquiryName: 'RFQ Name', + companyShortName: 'Transaction Plant', + buyingMethod: 'Buying Method', + buyingMethodInquiry: 'Inquiry', + buyingMethodBid: 'Bid', + quoteDeadline: 'Quote Deadline', + bidStartTime: 'Bid Start Time', + bidEndTime: 'Bid End Time', + buyer: 'Buyer', + currency: 'Currency', + leadTimeDays: 'Lead Time (Days)', + paymentMethod: 'Payment Method', + status: 'Status', + statusOpen: 'Open', + statusConfirmed: 'Confirmed', + statusPublished: 'Published', + statusQuoting: 'Quoting', + statusQuotingEnd: 'Quote Ended', + statusBargaining: 'Bargaining', + statusPriceAudit: 'Price Audit', + statusPriceApproved: 'Price Approved', + statusLost: 'Lost', + statusCancelled: 'Cancelled', + remark: 'Remark', + opCreate: 'RFQ Created', + opConfirm: 'RFQ Confirmed', + opPublish: 'RFQ Published', + opRestore: 'RFQ Restored', + opQuoteDeadline: 'Quote Deadline', + opSupplierQuote: 'Supplier Quoted', + opBargain: 'Bargain', + opBargainSubmit: 'Bargain Submitted', + opBargainComplete: 'Bargain Completed', + opBargainReject: 'Bargain Rejected', + newInquiry: 'New RFQ', + startBargain: 'Start Comparison', + confirm: 'Confirm', + restore: 'Restore', + publish: 'Publish', + comparePrice: 'Compare Price', + view: 'View', + edit: 'Edit', + delete: 'Delete', + selectOneFirst: 'Please select an RFQ first', + onlySupportSingle: 'Only single item operation is supported', + onlyQuotingCanBargain: 'Only quoting or quote-ended status can start price comparison', + confirmChangeToBargaining: 'Confirm to change status to [Bargaining]?', + maintainSupplierFirst: 'Please maintain RFQ supplier list first before confirming', + confirmLockAfterConfirm: 'After confirmation, the RFQ will be locked and cannot be edited. To modify, you need to [Restore] it.', + confirmRestore: 'Confirm to restore status to [Open]?', + confirmPublish: 'Confirm to change status to [Published]?', + transactionPlant: 'Transaction Plant', + purchaseCategory: 'Purchase Category', + inquiryTemplate: 'RFQ Template', + templateVersion: 'Template Version', + paymentTerm: 'Payment Terms', + totalPriceInclTax: 'Total Price Incl. Tax', + costStructure: 'Cost Structure', + supplierList: 'Supplier List', + attachments: 'Attachments', + basicInfo: 'Basic Info', + operationType: 'Operation Type', + operator: 'Operator', + operationTime: 'Operation Time', + operationDesc: 'Operation Description', + statusChange: 'Status Change', + quotationNo: 'Quotation No', + }, + }, + }, + }, +}; diff --git a/web/src/i18n/pages/miscprocurement/zh-cn.ts b/web/src/i18n/pages/miscprocurement/zh-cn.ts new file mode 100644 index 0000000..6bf2e64 --- /dev/null +++ b/web/src/i18n/pages/miscprocurement/zh-cn.ts @@ -0,0 +1,132 @@ +// 定义内容 +export default { + message: { + pages: { + miscprocurement: { + materialInfo: { + title: '杂采材料信息', + factory: '交易厂区', + materialtype: '材质', + materialtypeEn: '材质英文名', + materialtypeZhTw: '材质繁体名', + density: '比重', + price: '单价', + status: '状态', + }, + stationInfo: { + title: '杂采工站信息', + companyCode: '交易厂区', + stationname: '工站名称', + stationnameEn: '工站英文名', + stationnameZhTw: '工站繁体名', + stationcode: '工站代码', + stationtype: '工站类型归属', + unit: '计量单位', + rate: '费率', + status: '状态', + }, + inquiry: { + title: '询价单', + inquiryNo: '询价单号', + title_label: '询价单名称', + titleEn: '询价单英文名', + titleZhTw: '询价单繁体名', + purchaseType: '采购类别', + materialType: '材料类型', + template: '询价模版', + currency: '交易币别', + companyCode: '公司代码', + purchaseDept: '采购部门', + buyer: '采购负责人', + quoteDeadline: '报价截止时间', + targetPrice: '目标价格', + leadTimeDays: '交货周期(天)', + paymentMethod: '付款方式', + status: '状态', + buyingMethod: '采购方式', + }, + costTemplate: { + title: '成本估算模板', + templateNo: '模板编号', + templateName: '模板名称', + templateNameEn: '模板英文名', + templateNameZhTw: '模板繁体名', + procurementCategory: '采购类别', + templateDesc: '模板描述', + status: '状态', + }, + rfqmiscellaneous: { + title: '杂采询价单', + inquiryNo: '询价单号', + inquiryName: '询价单名称', + companyShortName: '交易厂区', + buyingMethod: '采购方式', + buyingMethodInquiry: '询价', + buyingMethodBid: '招标', + quoteDeadline: '报价截止时间', + bidStartTime: '投标开始时间', + bidEndTime: '投标截止时间', + buyer: '采购负责人', + currency: '币别', + leadTimeDays: '交货周期(天)', + paymentMethod: '付款方式', + status: '状态', + statusOpen: '开立', + statusConfirmed: '确认', + statusPublished: '发布', + statusQuoting: '报价中', + statusQuotingEnd: '报价结束', + statusBargaining: '比议价中', + statusPriceAudit: '价格审核', + statusPriceApproved: '核价通过(结束)', + statusLost: '落标(结束)', + statusCancelled: '作废', + remark: '备注', + opCreate: '询价单创建', + opConfirm: '询价单确认', + opPublish: '询价单发布', + opRestore: '询价单还原', + opQuoteDeadline: '报价截止', + opSupplierQuote: '供应商报价', + opBargain: '比议价', + opBargainSubmit: '议价审核提交', + opBargainComplete: '议价审核完成', + opBargainReject: '议价审核驳回', + newInquiry: '新建询价单', + startBargain: '开启比价', + confirm: '确认', + restore: '还原', + publish: '发布', + comparePrice: '比价', + view: '查看', + edit: '编辑', + delete: '删除', + selectOneFirst: '请先选择询价单', + onlySupportSingle: '仅支持单条操作', + onlyQuotingCanBargain: '仅报价中或报价结束状态可开启比价', + confirmChangeToBargaining: '确认将状态改为【比议价中】?', + maintainSupplierFirst: '请先维护询价单供应商名单后再确认', + confirmLockAfterConfirm: '确认后,该询价单将锁定并不可再编辑。如需修改,后续需执行【还原】操作', + confirmRestore: '确认将状态还原为【开立】?', + confirmPublish: '确认将状态改为【发布】?', + transactionPlant: '交易厂区', + purchaseCategory: '采购类别', + inquiryTemplate: '询价模版', + templateVersion: '模板版本', + paymentTerm: '付款条件', + totalPriceInclTax: '含税总价', + costStructure: '成本结构', + supplierList: '供应商名单', + attachments: '附件', + basicInfo: '基础信息', + operationType: '操作类型', + operator: '操作人', + operationTime: '操作时间', + operationDesc: '操作描述', + statusChange: '状态变更', + quotationNo: '报价单号', + }, + }, + }, + }, +}; diff --git a/web/src/i18n/pages/miscprocurement/zh-tw.ts b/web/src/i18n/pages/miscprocurement/zh-tw.ts new file mode 100644 index 0000000..e2930a1 --- /dev/null +++ b/web/src/i18n/pages/miscprocurement/zh-tw.ts @@ -0,0 +1,132 @@ +// 定義內容 +export default { + message: { + pages: { + miscprocurement: { + materialInfo: { + title: '雜採材料資訊', + factory: '交易廠區', + materialtype: '材質', + materialtypeEn: '材質英文名', + materialtypeZhTw: '材質繁體名', + density: '比重', + price: '單價', + status: '狀態', + }, + stationInfo: { + title: '雜採工站資訊', + companyCode: '交易廠區', + stationname: '工站名稱', + stationnameEn: '工站英文名', + stationnameZhTw: '工站繁體名', + stationcode: '工站代碼', + stationtype: '工站類型歸屬', + unit: '計量單位', + rate: '費率', + status: '狀態', + }, + inquiry: { + title: '詢價單', + inquiryNo: '詢價單號', + title_label: '詢價單名稱', + titleEn: '詢價單英文名', + titleZhTw: '詢價單繁體名', + purchaseType: '採購類別', + materialType: '材料類型', + template: '詢價模版', + currency: '交易幣別', + companyCode: '公司代碼', + purchaseDept: '採購部門', + buyer: '採購負責人', + quoteDeadline: '報價截止時間', + targetPrice: '目標價格', + leadTimeDays: '交貨週期(天)', + paymentMethod: '付款方式', + status: '狀態', + buyingMethod: '採購方式', + }, + costTemplate: { + title: '成本估算模版', + templateNo: '模版編號', + templateName: '模版名稱', + templateNameEn: '模版英文名', + templateNameZhTw: '模版繁體名', + procurementCategory: '採購類別', + templateDesc: '模版描述', + status: '狀態', + }, + rfqmiscellaneous: { + title: '雜採詢價單', + inquiryNo: '詢價單號', + inquiryName: '詢價單名稱', + companyShortName: '交易廠區', + buyingMethod: '採購方式', + buyingMethodInquiry: '詢價', + buyingMethodBid: '招標', + quoteDeadline: '報價截止時間', + bidStartTime: '投標開始時間', + bidEndTime: '投標截止時間', + buyer: '採購負責人', + currency: '幣別', + leadTimeDays: '交貨週期(天)', + paymentMethod: '付款方式', + status: '狀態', + statusOpen: '開立', + statusConfirmed: '確認', + statusPublished: '發布', + statusQuoting: '報價中', + statusQuotingEnd: '報價結束', + statusBargaining: '比議價中', + statusPriceAudit: '價格審核', + statusPriceApproved: '核價通過(結束)', + statusLost: '落標(結束)', + statusCancelled: '作廢', + remark: '備註', + opCreate: '詢價單創建', + opConfirm: '詢價單確認', + opPublish: '詢價單發布', + opRestore: '詢價單還原', + opQuoteDeadline: '報價截止', + opSupplierQuote: '供應商報價', + opBargain: '比議價', + opBargainSubmit: '議價審核提交', + opBargainComplete: '議價審核完成', + opBargainReject: '議價審核駁回', + newInquiry: '新建詢價單', + startBargain: '開啟比價', + confirm: '確認', + restore: '還原', + publish: '發布', + comparePrice: '比價', + view: '查看', + edit: '編輯', + delete: '刪除', + selectOneFirst: '請先選擇詢價單', + onlySupportSingle: '僅支援單條操作', + onlyQuotingCanBargain: '僅報價中或報價結束狀態可開啟比價', + confirmChangeToBargaining: '確認將狀態改為【比議價中】?', + maintainSupplierFirst: '請先維護詢價單供應商名單後再確認', + confirmLockAfterConfirm: '確認後,該詢價單將鎖定並不可再編輯。如需修改,後續需執行【還原】操作', + confirmRestore: '確認將狀態還原為【開立】?', + confirmPublish: '確認將狀態改為【發布】?', + transactionPlant: '交易廠區', + purchaseCategory: '採購類別', + inquiryTemplate: '詢價模版', + templateVersion: '模版版本', + paymentTerm: '付款條件', + totalPriceInclTax: '含稅總價', + costStructure: '成本結構', + supplierList: '供應商名單', + attachments: '附件', + basicInfo: '基本信息', + operationType: '操作類型', + operator: '操作人', + operationTime: '操作時間', + operationDesc: '操作描述', + statusChange: '狀態變更', + quotationNo: '報價單號', + }, + }, + }, + }, +}; diff --git a/web/src/i18n/pages/pissupplier/en.ts b/web/src/i18n/pages/pissupplier/en.ts new file mode 100644 index 0000000..a096b85 --- /dev/null +++ b/web/src/i18n/pages/pissupplier/en.ts @@ -0,0 +1,75 @@ +// Definition +export default { + message: { + pages: { + pissupplier: { + quotation: { + title: 'Supplier Quotation', + quotationNo: 'Quotation No', + inquiryNo: 'Inquiry No', + supplierCode: 'Supplier Code', + supplierName: 'Supplier Name', + supplierNameEn: 'Supplier Name (English)', + supplierNameZhTw: 'Supplier Name (Traditional Chinese)', + contactPerson: 'Contact Person', + contactPhone: 'Contact Phone', + contactEmail: 'Contact Email', + quoteDeadline: 'Quote Deadline', + paymentMethod: 'Payment Method', + status: 'Status', + isAwarded: 'Award Status', + buyingMethod: 'Buying Method', + remark: 'Remark', + buyingMethodInquiry: 'Inquiry', + buyingMethodBid: 'Bid', + bidStartTime: 'Bid Start Time', + bidEndTime: 'Bid End Time', + leadTimeDays: 'Lead Time (Days)', + statusPending: 'Pending', + statusQuoting: 'Quoting', + statusCompleted: 'Completed', + statusExpired: 'Expired', + newQuote: 'New Quote', + viewQuote: 'View Quote', + editQuote: 'Edit Quote', + submitQuote: 'Submit Quote', + saveQuote: 'Save Quote', + bidWindowExpired: 'Bid deadline has passed, cannot quote or submit', + bidWindowMissing: 'Bid project is missing bid start or end time, cannot quote or submit', + fillBasicInfoFirst: 'Please complete [Quote Basic Info] before saving.', + onlyPendingCanSave: 'Only pending/quoting status can be saved', + onlyQuotingCanSubmit: 'Only quoting status can submit quote', + fillBasicInfoBeforeSubmit: 'Please complete [Quote Basic Info] in the quotation before submitting.', + cannotSubmitNoId: 'Cannot submit: missing quotation ID', + materialCost: 'Material Cost', + processCost: 'Process Cost', + otherCost: 'Other Cost', + profit: 'Profit', + tax: 'Tax', + profitRate: 'Profit Rate', + taxRate: 'Tax Rate', + weight: 'Weight', + unitPrice: 'Unit Price', + materialFee: 'Material Fee', + processFee: 'Process Fee', + packagingFee: 'Packaging Fee', + transportFee: 'Transport Fee', + contact: 'Contact', + phone: 'Phone', + email: 'Email', + validityDays: 'Validity (Days)', + awarded: 'Awarded', + notAwarded: 'Not Awarded', + evaluating: 'Evaluating', + quoteAmount: 'Quote Amount', + quoteTime: 'Quote Time', + uploadAttachment: 'Upload Attachment', + comparePrice: 'Compare Price', + lowestPrice: 'Lowest Price', + averagePrice: 'Average Price', + inquiryTitle: 'Inquiry Title', + }, + }, + }, + }, +}; diff --git a/web/src/i18n/pages/pissupplier/zh-cn.ts b/web/src/i18n/pages/pissupplier/zh-cn.ts new file mode 100644 index 0000000..3b91d2b --- /dev/null +++ b/web/src/i18n/pages/pissupplier/zh-cn.ts @@ -0,0 +1,75 @@ +// 定义内容 +export default { + message: { + pages: { + pissupplier: { + quotation: { + title: '供应商报价', + quotationNo: '报价单单号', + inquiryNo: '询价单单号', + supplierCode: '供应商代码', + supplierName: '供应商名称', + supplierNameEn: '供应商英文名', + supplierNameZhTw: '供应商繁体名', + contactPerson: '联系人', + contactPhone: '联系人电话', + contactEmail: '联系人邮件', + quoteDeadline: '报价截止时间', + paymentMethod: '付款方式', + status: '状态', + isAwarded: '报价中标否', + buyingMethod: '采购方式', + remark: '报价说明及备注', + buyingMethodInquiry: '询价', + buyingMethodBid: '招标', + bidStartTime: '投标开始时间', + bidEndTime: '投标截止时间', + leadTimeDays: '交货周期(天)', + statusPending: '待报价', + statusQuoting: '报价中', + statusCompleted: '已报价', + statusExpired: '已过期', + newQuote: '新建报价单', + viewQuote: '查看报价', + editQuote: '编辑报价', + submitQuote: '提交报价', + saveQuote: '保存报价', + bidWindowExpired: '已超过投标截止时间,无法报价或提交', + bidWindowMissing: '招标项目缺少投标开始或截止时间,无法报价或提交', + fillBasicInfoFirst: '请先填写完整【报价基础信息】后再保存。', + onlyPendingCanSave: '仅未报价/报价中状态可保存', + onlyQuotingCanSubmit: '仅报价中状态可提交报价', + fillBasicInfoBeforeSubmit: '请先将报价单中的【报价基础信息】填写完整后再提交报价。', + cannotSubmitNoId: '无法提交:缺少报价单标识', + materialCost: '材料成本', + processCost: '加工成本', + otherCost: '其它成本', + profit: '利润', + tax: '税金', + profitRate: '利润率', + taxRate: '税率', + weight: '重量', + unitPrice: '单价', + materialFee: '材料费用', + processFee: '加工费', + packagingFee: '包装费', + transportFee: '运输费', + contact: '联络人', + phone: '电话', + email: '邮箱', + validityDays: '有效期(天)', + awarded: '中标', + notAwarded: '未中标', + evaluating: '评标中', + quoteAmount: '报价金额', + quoteTime: '报价时间', + uploadAttachment: '上传附件', + comparePrice: '比价', + lowestPrice: '制程最低价', + averagePrice: '平均价', + inquiryTitle: '询价单名称', + }, + }, + }, + }, +}; diff --git a/web/src/i18n/pages/pissupplier/zh-tw.ts b/web/src/i18n/pages/pissupplier/zh-tw.ts new file mode 100644 index 0000000..a51da41 --- /dev/null +++ b/web/src/i18n/pages/pissupplier/zh-tw.ts @@ -0,0 +1,75 @@ +// 定義內容 +export default { + message: { + pages: { + pissupplier: { + quotation: { + title: '供應商報價', + quotationNo: '報價單號', + inquiryNo: '詢價單號', + supplierCode: '供應商代碼', + supplierName: '供應商名稱', + supplierNameEn: '供應商英文名', + supplierNameZhTw: '供應商繁體名', + contactPerson: '聯絡人', + contactPhone: '聯絡人電話', + contactEmail: '聯絡人郵箱', + quoteDeadline: '報價截止時間', + paymentMethod: '付款方式', + status: '狀態', + isAwarded: '報價中標否', + buyingMethod: '採購方式', + remark: '報價說明及備註', + buyingMethodInquiry: '詢價', + buyingMethodBid: '招標', + bidStartTime: '投標開始時間', + bidEndTime: '投標截止時間', + leadTimeDays: '交貨週期(天)', + statusPending: '待報價', + statusQuoting: '報價中', + statusCompleted: '已報價', + statusExpired: '已過期', + newQuote: '新建報價單', + viewQuote: '查看報價', + editQuote: '編輯報價', + submitQuote: '提交報價', + saveQuote: '儲存報價', + bidWindowExpired: '已超過投標截止時間,無法報價或提交', + bidWindowMissing: '招標項目缺少投標開始或截止時間,無法報價或提交', + fillBasicInfoFirst: '請先填寫完整【報價基本信息】後再儲存。', + onlyPendingCanSave: '僅未報價/報價中狀態可儲存', + onlyQuotingCanSubmit: '僅報價中狀態可提交報價', + fillBasicInfoBeforeSubmit: '請先將報價單中的【報價基本信息】填寫完整後再提交報價。', + cannotSubmitNoId: '無法提交:缺少報價單標識', + materialCost: '材料成本', + processCost: '加工成本', + otherCost: '其它成本', + profit: '利潤', + tax: '税金', + profitRate: '利潤率', + taxRate: '税率', + weight: '重量', + unitPrice: '單價', + materialFee: '材料費用', + processFee: '加工費', + packagingFee: '包裝費', + transportFee: '運輸費', + contact: '聯絡人', + phone: '電話', + email: '郵箱', + validityDays: '有效期(天)', + awarded: '中標', + notAwarded: '未中標', + evaluating: '評標中', + quoteAmount: '報價金額', + quoteTime: '報價時間', + uploadAttachment: '上傳附件', + comparePrice: '比價', + lowestPrice: '製程最低價', + averagePrice: '平均價', + inquiryTitle: '詢價單名稱', + }, + }, + }, + }, +}; diff --git a/web/src/views/pisadmin/basicinfo/company/crud.tsx b/web/src/views/pisadmin/basicinfo/company/crud.tsx index cb084eb..0631f45 100644 --- a/web/src/views/pisadmin/basicinfo/company/crud.tsx +++ b/web/src/views/pisadmin/basicinfo/company/crud.tsx @@ -2,14 +2,11 @@ import { dict, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/f import * as api from './api' import { useUserInfo } from '/@/stores/userInfo' import { ElMessage } from 'element-plus' - -const statusDict = [ - { value: 1, label: '可用' }, - { value: 0, label: '不可用' } -] +import { useI18n } from 'vue-i18n' export const createCrudOptions = function ({ crudExpose }: Partial): CreateCrudOptionsRet { void crudExpose + const { t } = useI18n() const userStore = useUserInfo() const currentUser = userStore.userInfos?.name || @@ -30,7 +27,7 @@ export const createCrudOptions = function ({ crudExpose }: Partial item.company_code === code) : null if (exists && (!currentId || exists.id !== currentId)) { - throw new Error('公司代码已存在,不可重复') + throw new Error(t('message.pages.basicinfo.company.companyCode') + t('message.pages.menu.validation.alreadyExists')) } } return { @@ -84,46 +81,46 @@ export const createCrudOptions = function ({ crudExpose }: Partial { if (!Array.isArray(value)) return '' return value.filter((v) => !!v).join('; ') } -const formatAttachments = (value: unknown) => { - if (!Array.isArray(value)) return '' - const names = value - .map((v: any) => v?.name || v?.file_name || v?.filename || v?.key || '') - .filter((v: any) => !!v) - if (names.length === 0) return value.length ? `${value.length} 个附件` : '' - return names.join('; ') -} - export const createCrudOptions = function ({ crudExpose }: Partial): CreateCrudOptionsRet { + const { t } = useI18n() + + const statusDict = [ + { value: 'pending', label: t('message.pages.basicinfo.emailnotice.statusPending') }, + { value: 'sending', label: t('message.pages.basicinfo.emailnotice.statusSending') }, + { value: 'success', label: t('message.pages.basicinfo.emailnotice.statusSuccess') }, + { value: 'failed', label: t('message.pages.basicinfo.emailnotice.statusFailed') } + ] + + const formatAttachments = (value: unknown) => { + if (!Array.isArray(value)) return '' + const names = value + .map((v: any) => v?.name || v?.file_name || v?.filename || v?.key || '') + .filter((v: any) => !!v) + if (names.length === 0) return value.length ? `${value.length} ${t('message.pages.basicinfo.emailnotice.attachments')}` : '' + return names.join('; ') + } + return { crudOptions: { request: { @@ -42,7 +45,7 @@ export const createCrudOptions = function ({ crudExpose }: Partial row.status !== 'sending', @@ -53,9 +56,9 @@ export const createCrudOptions = function ({ crudExpose }: Partial joinEmails(value) } }, cc_emails: { - title: '抄送', + title: t('message.pages.basicinfo.emailnotice.ccEmails'), type: 'text', column: { minWidth: 180, showOverflowTooltip: true, formatter: ({ value }) => joinEmails(value) } }, bcc_emails: { - title: '密送', + title: t('message.pages.basicinfo.emailnotice.bccEmails'), type: 'text', column: { minWidth: 180, showOverflowTooltip: true, formatter: ({ value }) => joinEmails(value) } }, status: { - title: '发送状态', + title: t('message.pages.basicinfo.emailnotice.status'), type: 'dict-select', dict: dict({ data: statusDict }), - search: { show: true, component: { props: { clearable: true, placeholder: '请选择状态' } } }, + search: { show: true, component: { props: { clearable: true, placeholder: t('message.pages.basicinfo.emailnotice.status') } } }, column: { width: 120 } }, sent_at: { - title: '发送时间', + title: t('message.pages.basicinfo.emailnotice.sentAt'), type: 'datetime', search: { show: true, @@ -121,35 +124,35 @@ export const createCrudOptions = function ({ crudExpose }: Partial formatAttachments(value) } }, last_error: { - title: '错误信息', + title: t('message.pages.basicinfo.emailnotice.lastError'), type: 'text', column: { minWidth: 240, showOverflowTooltip: true } }, message_id: { - title: '消息ID', + title: t('message.pages.basicinfo.emailnotice.messageId'), type: 'text', column: { minWidth: 180, showOverflowTooltip: true } }, retry_count: { - title: '重试次数', + title: t('message.pages.basicinfo.emailnotice.retryCount'), type: 'number', column: { width: 100 } }, create_datetime: { - title: '创建时间', + title: t('message.pages.system.user.createTime'), type: 'datetime', form: { show: false }, column: { width: 180 } diff --git a/web/src/views/pisadmin/basicinfo/supplier_user/crud.tsx b/web/src/views/pisadmin/basicinfo/supplier_user/crud.tsx index d985bb4..b78f098 100644 --- a/web/src/views/pisadmin/basicinfo/supplier_user/crud.tsx +++ b/web/src/views/pisadmin/basicinfo/supplier_user/crud.tsx @@ -2,13 +2,7 @@ import { dict, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/f import * as api from './api' import { GetList as GetSupplierList } from '../supplier/api' import { ElMessage } from 'element-plus' - -const statusDict = [ - { value: 1, label: '有效' }, - { value: 0, label: '无效' } -] - -const supplierRoleDict = [{ value: 1, label: '报价' }] +import { useI18n } from 'vue-i18n' let supplierById: Record = {} @@ -66,6 +60,7 @@ void loadSupplierOptions() export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { void crudExpose + const { t } = useI18n() const ensureUserEmailUnique = async (supplierId: string, userEmail: string, currentId?: number) => { if (!supplierId || !userEmail) return @@ -85,7 +80,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp ) : null if (exists && (!currentId || exists.id !== currentId)) { - throw new Error('同一供应商下该联络邮箱已存在') + throw new Error(t('message.pages.basicinfo.supplierUser.userEmail') + t('message.pages.menu.validation.alreadyExists')) } } @@ -101,7 +96,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp await ensureUserEmailUnique(form.supplier_id, form.user_email) const res = await api.AddObj(form) ElMessage.success( - '保存成功;已同步创建系统用户(登录账号为联络人邮箱,初始密码为系统默认密码,部门:供应商)' + '保存成功,已同步创建系统用户(登录账号为联络人邮箱,初始密码为系统默认密码,部门:供应商)' ) return res } catch (err: any) { @@ -133,7 +128,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp }, columns: { supplier_id: { - title: '供应商唯一ID', + title: t('message.pages.basicinfo.supplierUser.supplierId'), type: 'dict-select', dict: dict({ cache: false, @@ -147,9 +142,9 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp })) } }), - search: { show: true, component: { props: { placeholder: '请选择或搜索', filterable: true, clearable: true } } }, + search: { show: true, component: { props: { placeholder: t('message.pages.basicinfo.emailnotice.selectOrSearch'), filterable: true, clearable: true } } }, form: { - rules: [{ required: true, message: '请选择供应商' }], + rules: [{ required: true, message: t('message.pages.basicinfo.supplierUser.supplierId') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }], /** dict-select 需用 fast-crud 的 valueChange;component.on.change 往往不会触发 */ valueChange: async ({ value, form }: any) => { const v = value @@ -181,67 +176,67 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp column: { minWidth: 200, showOverflowTooltip: true } }, supplier_name: { - title: '供应商全称', + title: t('message.pages.basicinfo.supplierUser.supplierName'), type: 'input', - search: { show: true, component: { props: { placeholder: '请输入供应商全称', clearable: true } } }, + search: { show: true, component: { props: { placeholder: t('message.pages.basicinfo.supplierUser.supplierName'), clearable: true } } }, form: { rules: [{ required: true, message: '请先选择供应商唯一ID,将自动带出供应商全称' }], component: { props: { disabled: true, - placeholder: '选择供应商唯一ID后自动带出' + placeholder: t('message.pages.basicinfo.supplierUser.autoFillNote') } } }, column: { minWidth: 180, showOverflowTooltip: true } }, supplier_role: { - title: '供应商角色', + title: t('message.pages.basicinfo.supplierUser.supplierRole'), type: 'dict-select', - dict: dict({ data: supplierRoleDict }), + dict: dict({ data: [{ value: 1, label: t('message.pages.basicinfo.supplierUser.roleQuote') }] }), search: { show: true }, form: { - rules: [{ required: true, message: '请选择供应商角色' }], + rules: [{ required: true, message: t('message.pages.basicinfo.supplierUser.supplierRole') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }], value: 1 }, column: { minWidth: 120, showOverflowTooltip: true } }, user_email: { - title: '联络人邮箱', + title: t('message.pages.basicinfo.supplierUser.userEmail'), type: 'input', - search: { show: true, component: { props: { placeholder: '请输入邮箱', clearable: true } } }, - form: { rules: [{ required: true, message: '请输入联络人邮箱' }] }, + search: { show: true, component: { props: { placeholder: t('message.pages.basicinfo.supplierUser.userEmail'), clearable: true } } }, + form: { rules: [{ required: true, message: t('message.pages.basicinfo.supplierUser.userEmail') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }] }, column: { minWidth: 200, showOverflowTooltip: true } }, user_name: { - title: '联络人', + title: t('message.pages.basicinfo.supplierUser.userName'), type: 'input', - search: { show: true, component: { props: { placeholder: '请输入联络人', clearable: true } } }, - form: { rules: [{ required: true, message: '请输入联络人' }] }, + search: { show: true, component: { props: { placeholder: t('message.pages.basicinfo.supplierUser.userName'), clearable: true } } }, + form: { rules: [{ required: true, message: t('message.pages.basicinfo.supplierUser.userName') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }] }, column: { minWidth: 120, showOverflowTooltip: true } }, user_phone: { - title: '联络人电话', + title: t('message.pages.basicinfo.supplierUser.userPhone'), type: 'input', - search: { show: true, component: { props: { placeholder: '请输入电话', clearable: true } } }, - form: { rules: [{ required: true, message: '请输入联络人电话' }] }, + search: { show: true, component: { props: { placeholder: t('message.pages.basicinfo.supplierUser.userPhone'), clearable: true } } }, + form: { rules: [{ required: true, message: t('message.pages.basicinfo.supplierUser.userPhone') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }] }, column: { minWidth: 140, showOverflowTooltip: true } }, status: { - title: '有效否', + title: t('message.pages.basicinfo.supplierUser.status'), type: 'dict-switch', - dict: dict({ data: statusDict }), + dict: dict({ data: [{ value: 1, label: t('message.pages.basicinfo.supplierUser.statusValid') }, { value: 0, label: t('message.pages.basicinfo.supplierUser.statusInvalid') }] }), form: { value: 1 }, column: { width: 120 } }, create_datetime: { - title: '创建时间', + title: t('message.pages.system.user.createTime'), type: 'datetime', form: { show: false }, column: { width: 180 } }, update_datetime: { - title: '更新时间', + title: t('message.pages.system.user.updateTime'), type: 'datetime', form: { show: false }, column: { width: 180 } diff --git a/web/src/views/pisadmin/basicinfo/systemno/crud.tsx b/web/src/views/pisadmin/basicinfo/systemno/crud.tsx index a5198bf..b4c1540 100644 --- a/web/src/views/pisadmin/basicinfo/systemno/crud.tsx +++ b/web/src/views/pisadmin/basicinfo/systemno/crud.tsx @@ -3,21 +3,7 @@ import * as api from './api' import { GetCompanies } from '../currency/api' import { useUserInfo } from '/@/stores/userInfo' import { ElMessage } from 'element-plus' - -const resetCycleDict = [ - { value: 'yy', label: '按年2(YY)' }, - { value: 'yyyy', label: '按年4(YYYY)' }, - { value: 'yymm', label: '按月4(YYMM)' }, - { value: 'yyyymm', label: '按月6(YYYYMM)' }, - { value: 'yymmdd', label: '按日6(YYMMDD)' }, - { value: 'yyyymmdd', label: '按日8(YYYYMMDD)' } -] - -/** 与 `SystemNoRule.RULE_CODE_CHOICES` 一致;表单/搜索下拉展示「代码 - 名称」,列表列仅显示代码(见 column.formatter) */ -const ruleCodeDict = [ - { value: 'miscQTS', label: 'miscQTS (杂采报价单)' }, - { value: 'miscRFS', label: 'miscRFS (杂采询价单)' } -] +import { useI18n } from 'vue-i18n' /** * 交易厂区「通用」:非公司主数据,仅存于编号规则下拉;与 unique(company_code, rule_code) 兼容。 @@ -55,6 +41,7 @@ const loadCompanyOptions = async () => { } export const createCrudOptions = function ({ crudExpose }: Partial): CreateCrudOptionsRet { + const { t } = useI18n() void crudExpose const userStore = useUserInfo() const currentUser = @@ -63,6 +50,21 @@ export const createCrudOptions = function ({ crudExpose }: Partial { if (!companyCode || !ruleCode) return const res = await api.GetList({ company_code: companyCode, rule_code: ruleCode, page: 1, page_size: 1, pageSize: 1 }) @@ -78,7 +80,7 @@ export const createCrudOptions = function ({ crudExpose }: Partial item.company_code === companyCode && item.rule_code === ruleCode) : null if (exists && (!currentId || exists.id !== currentId)) { - throw new Error('同一交易厂区下的生成单据标识号不可重复') + throw new Error(t('message.pages.basicinfo.systemno.companyCode') + t('message.pages.basicinfo.systemno.ruleCode') + t('message.pages.menu.validation.alreadyExists')) } } @@ -130,7 +132,7 @@ export const createCrudOptions = function ({ crudExpose }: Partial { }) } -const visibleTitles = (procurementCategory?: string, enableCostStructure?: boolean) => { - // 杂采(2) 不显示“管销研费用” +const visibleTitles = (t: any, procurementCategory?: string, enableCostStructure?: boolean) => { + // 杂采(2) 不显示”管销研费用” if (procurementCategory === '2') { return enableCostStructure - ? ['材料成本', '加工成本', '其它成本', '利润', '税金'] - : ['产品明细', '利润', '税金'] + ? [t('message.pages.miscprocurement.cost_template.materials'), t('message.pages.miscprocurement.cost_template.process'), t('message.pages.miscprocurement.cost_template.others'), t('message.pages.miscprocurement.cost_template.profit'), t('message.pages.miscprocurement.cost_template.tax')] + : [t('message.pages.miscprocurement.cost_template.productDetail'), t('message.pages.miscprocurement.cost_template.profit'), t('message.pages.miscprocurement.cost_template.tax')] } return enableCostStructure - ? ['材料成本', '加工成本', '其它成本', '管销研费用', '利润', '税金'] - : ['产品明细', '利润', '税金'] + ? [t('message.pages.miscprocurement.cost_template.materials'), t('message.pages.miscprocurement.cost_template.process'), t('message.pages.miscprocurement.cost_template.others'), t('message.pages.miscprocurement.cost_template.sgna'), t('message.pages.miscprocurement.cost_template.profit'), t('message.pages.miscprocurement.cost_template.tax')] + : [t('message.pages.miscprocurement.cost_template.productDetail'), t('message.pages.miscprocurement.cost_template.profit'), t('message.pages.miscprocurement.cost_template.tax')] } const ensureAllSections = (form: any, titlesForEnabled?: string[], procurementCategory?: string) => { @@ -349,8 +350,8 @@ const ensureAllSections = (form: any, titlesForEnabled?: string[], procurementCa return arr } -const refreshSectionsIfNeeded = (form: any, overrides: any = {}) => { - const titles = visibleTitles( +const refreshSectionsIfNeeded = (t: any, form: any, overrides: any = {}) => { + const titles = visibleTitles(t, overrides.procurement_category ?? form?.procurement_category, overrides.enable_cost_structure ?? (form?.is_bom || 'Y') === 'Y' ) @@ -615,6 +616,213 @@ const buildCostTemplateSubmitPayload = (form: any) => { } export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const { t } = useI18n() + + // i18n-aware field factories + const makeMaterialsFieldsMisc = () => [ + createField(t('message.pages.miscprocurement.cost_template.material'), 'material', 'text'), + createField(t('message.pages.miscprocurement.cost_template.length'), 'length', 'number'), + createField(t('message.pages.miscprocurement.cost_template.width'), 'width', 'number'), + createField(t('message.pages.miscprocurement.cost_template.height'), 'height', 'number'), + createField(t('message.pages.miscprocurement.cost_template.specificgravity'), 'specificgravity', 'number', t('message.pages.miscprocurement.cost_template.autoByMaterial'), { supplierEditable: false }), + createField(t('message.pages.miscprocurement.cost_template.qty'), 'qty', 'number', '', { supplierRequired: true }), + createField(t('message.pages.miscprocurement.cost_template.weight'), 'weight', 'number', t('message.pages.miscprocurement.cost_template.lengthWidthHeightSpecificgravityQty'), { supplierEditable: false }), + createField(t('message.pages.miscprocurement.cost_template.unitPrice'), 'unitPrice', 'number'), + createField(t('message.pages.miscprocurement.cost_template.materialCost'), 'material_cost', 'number', t('message.pages.miscprocurement.cost_template.weightTimesUnitPrice'), { supplierEditable: false }) + ] + + const makeMaterialsFieldsStrategic = () => [ + createField(t('message.pages.miscprocurement.cost_template.material'), 'material', 'text'), + createField(t('message.pages.miscprocurement.cost_template.weightUsage'), 'weight', 'number'), + createField(t('message.pages.miscprocurement.cost_template.unitPrice'), 'unitPrice', 'number'), + createField(t('message.pages.miscprocurement.cost_template.unit'), 'unit', 'text'), + createField(t('message.pages.miscprocurement.cost_template.lossRate'), 'loss_rate', 'number'), + createField(t('message.pages.miscprocurement.cost_template.materialCost'), 'material_cost', 'number', t('message.pages.miscprocurement.cost_template.weightTimesUnitPrice'), { supplierEditable: false }), + createField(t('message.pages.miscprocurement.cost_template.remark'), 'remark', 'text') + ] + + const makeGetMaterialsFields = () => (procurementCategory?: string) => + procurementCategory === '1' ? makeMaterialsFieldsStrategic() : makeMaterialsFieldsMisc() + + const makeProcessFieldsMisc = () => [ + createField(t('message.pages.miscprocurement.cost_template.processStation'), 'process_station', 'select'), + createField(t('message.pages.miscprocurement.cost_template.unit'), 'unit', 'text', '', { supplierEditable: false }), + createField(t('message.pages.miscprocurement.cost_template.unitrate'), 'unitrate', 'number', t('message.pages.miscprocurement.cost_template.autoByStation'), { supplierEditable: false }), + createField(t('message.pages.miscprocurement.cost_template.processqty'), 'processqty', 'number'), + createField(t('message.pages.miscprocurement.cost_template.processprice'), 'processprice', 'number', t('message.pages.miscprocurement.cost_template.unitrateTimesProcessqty'), { supplierEditable: false }), + createField(t('message.pages.miscprocurement.cost_template.remark'), 'remark', 'text', '', { supplierEditable: false }) + ] + + const makeProcessFieldsStrategic = () => [ + createField(t('message.pages.miscprocurement.cost_template.processStation'), 'process_station', 'select'), + createField(t('message.pages.miscprocurement.cost_template.processTime'), 'process_time', 'number'), + createField(t('message.pages.miscprocurement.cost_template.unitPrice'), 'unit_price', 'number'), + createField(t('message.pages.miscprocurement.cost_template.unit'), 'unit', 'text', '', { supplierEditable: false }), + createField(t('message.pages.miscprocurement.cost_template.lossRate'), 'loss_rate', 'number', '', { supplierEditable: false }), + createField(t('message.pages.miscprocurement.cost_template.processCost'), 'process_cost', 'number', '', { supplierEditable: false }), + createField(t('message.pages.miscprocurement.cost_template.remark'), 'remark', 'text', '', { supplierEditable: false }) + ] + + const makeGetProcessFields = () => (procurementCategory?: string) => + procurementCategory === '1' ? makeProcessFieldsStrategic() : makeProcessFieldsMisc() + + const makeBaseSections = () => { + const getMaterialsFields = makeGetMaterialsFields() + const getProcessFields = makeGetProcessFields() + const sections = Object.values(baseSections).map((s) => deepCopy(s)) + sections.forEach((sec) => { + if (sec.id === 'productDetail') sec.title = t('message.pages.miscprocurement.cost_template.productDetail') + else if (sec.id === 'materials') sec.title = t('message.pages.miscprocurement.cost_template.materials') + else if (sec.id === 'process') sec.title = t('message.pages.miscprocurement.cost_template.process') + else if (sec.id === 'sgna') sec.title = t('message.pages.miscprocurement.cost_template.sgna') + else if (sec.id === 'others') sec.title = t('message.pages.miscprocurement.cost_template.others') + else if (sec.id === 'profit') sec.title = t('message.pages.miscprocurement.cost_template.profit') + else if (sec.id === 'tax') sec.title = t('message.pages.miscprocurement.cost_template.tax') + if (sec.id === 'materials') { + sec.fields = deepCopy(getMaterialsFields()) + } else if (sec.id === 'process') { + sec.fields = deepCopy(getProcessFields()) + } + }) + return sections + } + + const makeBuildDefaultSections = () => (procurementCategory?: string) => { + const sections = makeBaseSections() + return sections + } + + // i18n-aware visibleTitles + const doVisibleTitles = (procurementCategory?: string, enableCostStructure?: boolean) => { + if (procurementCategory === '2') { + return enableCostStructure + ? [t('message.pages.miscprocurement.cost_template.materials'), t('message.pages.miscprocurement.cost_template.process'), t('message.pages.miscprocurement.cost_template.others'), t('message.pages.miscprocurement.cost_template.profit'), t('message.pages.miscprocurement.cost_template.tax')] + : [t('message.pages.miscprocurement.cost_template.productDetail'), t('message.pages.miscprocurement.cost_template.profit'), t('message.pages.miscprocurement.cost_template.tax')] + } + return enableCostStructure + ? [t('message.pages.miscprocurement.cost_template.materials'), t('message.pages.miscprocurement.cost_template.process'), t('message.pages.miscprocurement.cost_template.others'), t('message.pages.miscprocurement.cost_template.sgna'), t('message.pages.miscprocurement.cost_template.profit'), t('message.pages.miscprocurement.cost_template.tax')] + : [t('message.pages.miscprocurement.cost_template.productDetail'), t('message.pages.miscprocurement.cost_template.profit'), t('message.pages.miscprocurement.cost_template.tax')] + } + + // i18n-aware validation + const doValidateDuplicateFieldKeys = (form: any): { ok: true } | { ok: false; message: string } => { + const allowTitles = doVisibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') + const scan = (sectionsRaw: any): string[] => { + const msgs: string[] = [] + for (const sec of normalizeSections(sectionsRaw)) { + if (!sec || sec.enabled === false) continue + const title = sec.title || sec.name || '' + if (allowTitles.length && !allowTitles.includes(title)) continue + const keys: string[] = [] + for (const f of sec.fields || []) { + const k = String(f?.key ?? '').trim() + if (!k) continue + keys.push(k) + } + const cnt = new Map() + for (const k of keys) cnt.set(k, (cnt.get(k) || 0) + 1) + const dups = [...cnt.entries()].filter(([, c]) => c > 1).map(([k]) => k) + if (dups.length) msgs.push(`${t('message.pages.miscprocurement.cost_template.duplicateFieldKey', { title })}:${dups.join('、')}`) + } + return msgs + } + const merged = new Set([...scan(latestSectionDraft), ...scan(form?.sections)]) + const parts = [...merged].filter(Boolean) + if (parts.length) { + return { ok: false, message: `${parts.join(';')},${t('message.pages.miscprocurement.cost_template.pleaseModifyAndSave')}` } + } + return { ok: true } + } + + const doValidateItemNamesCn = (form: any): { ok: true } | { ok: false; message: string } => { + const sections = normalizeSections(resolveSectionsForSubmit(form)) + const allowTitles = doVisibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') + const msgs: string[] = [] + for (const sec of sections) { + if (!sec || sec.enabled === false) continue + const title = sec.title || sec.name || '' + if (allowTitles.length && !allowTitles.includes(title)) continue + const badKeys: string[] = [] + for (const f of sec.fields || []) { + if (!fieldDisplayNameCn(f)) { + const k = String(f?.key ?? '').trim() + badKeys.push(k || `(${t('message.pages.miscprocurement.cost_template.keyNotFilled')})`) + } + } + if (badKeys.length) msgs.push(`${t('message.pages.miscprocurement.cost_template.fieldNameNotFilled', { title })}(${t('message.pages.miscprocurement.cost_template.fieldKey')}: ${badKeys.join('、')})`) + } + if (msgs.length) { + return { ok: false, message: `${msgs.join(';')},${t('message.pages.miscprocurement.cost_template.pleaseCompleteAndSave')}` } + } + return { ok: true } + } + + const doValidateCostTemplateBeforeSubmit = (form: any): { ok: true } | { ok: false; message: string } => { + const issues: string[] = [] + const dup = doValidateDuplicateFieldKeys(form) + if (!dup.ok) issues.push(dup.message.replace(new RegExp(`,${t('message.pages.miscprocurement.cost_template.pleaseModifyAndSave')}$`), '')) + const cn = doValidateItemNamesCn(form) + if (!cn.ok) issues.push(cn.message.replace(new RegExp(`,${t('message.pages.miscprocurement.cost_template.pleaseCompleteAndSave')}$`), '')) + if (issues.length) { + return { ok: false, message: `${issues.join(';')},${t('message.pages.miscprocurement.cost_template.pleaseModifyAndSave')}` } + } + return { ok: true } + } + + const doRefreshSectionsIfNeeded = (form: any, overrides: any = {}) => { + const titles = doVisibleTitles( + overrides.procurement_category ?? form?.procurement_category, + overrides.enable_cost_structure ?? (form?.is_bom || 'Y') === 'Y' + ) + const procurementCategory = overrides.procurement_category ?? form?.procurement_category + const sections = makeBaseSections() + // Ensure all sections exist + const existing = normalizeSections(form?.sections) + const map = new Map() + existing.forEach((s: any) => { + const key = normalizeTitleKey(s?.title || s?.name) + if (key) map.set(key, s) + }) + sections.forEach((base) => { + if (!map.has(base.title)) { + map.set(base.title, deepCopy(base)) + } + }) + const arr = Array.from(map.values()) + // Always replace built-in materials/process fields by procurement category + const materials = arr.find((s: any) => s?.title === t('message.pages.miscprocurement.cost_template.materials')) + if (materials) { + const getMaterialsFields = makeGetMaterialsFields() + replaceBuiltInFields(materials, deepCopy(getMaterialsFields(procurementCategory))) + } + const process = arr.find((s: any) => s?.title === t('message.pages.miscprocurement.cost_template.process')) + if (process) { + const getProcessFields = makeGetProcessFields() + replaceBuiltInFields(process, deepCopy(getProcessFields(procurementCategory))) + } + if (titles.length) { + applyEnabledFlags(arr, titles) + } + form.sections = arr + form.__visibleTitles = titles + if (Array.isArray(form.sections)) { + (form.sections as any).__visibleTitles = titles + } + latestSectionDraft = normalizeSections(form.sections) + logPT('refreshSections', { + procurement_category: overrides.procurement_category ?? form?.procurement_category, + enable_cost_structure: overrides.enable_cost_structure ?? (form?.is_bom || 'Y') === 'Y', + titles, + sectionsCount: Array.isArray(form.sections) ? form.sections.length : 0, + draftSectionsCount: latestSectionDraft.length + }) + } + + const procurementDictLocalized = [ + { value: '1', label: t('message.pages.miscprocurement.cost_template.procurementStrategic') }, + { value: '2', label: t('message.pages.miscprocurement.cost_template.procurementMisc') } + ] + return { crudOptions: { form: { @@ -628,15 +836,15 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp group: { type: 'tab', base: { - label: '基础信息', + label: t('message.pages.miscprocurement.cost_template.basicInfo'), columns: ['template_name', 'procurement_category', 'version', 'template_desc'] }, controls: { - label: '启用设置', + label: t('message.pages.miscprocurement.cost_template.controls'), columns: ['acti', 'is_bom'] }, sections: { - label: '成本结构', + label: t('message.pages.miscprocurement.cost_template.sections'), columns: ['sections'] } }, @@ -644,7 +852,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp if (ctx.mode === 'add') { // 版本变更:由 openAdd 预填完整 sections/items;勿 refreshSectionsIfNeeded(会再走 replaceBuiltInFields,且易与预填冲突) if (ctx.form?.__newVersionSourceId != null && ctx.form.__newVersionSourceId !== '') { - const vis = visibleTitles(ctx.form.procurement_category, (ctx.form.is_bom || 'Y') === 'Y') + const vis = doVisibleTitles(ctx.form.procurement_category, (ctx.form.is_bom || 'Y') === 'Y') ctx.form.__visibleTitles = vis if (Array.isArray(ctx.form.sections)) { ;(ctx.form.sections as any).__visibleTitles = vis @@ -658,7 +866,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp if (ctx.form.version == null || ctx.form.version === '') { ctx.form.version = 1 } - refreshSectionsIfNeeded(ctx.form) + doRefreshSectionsIfNeeded(ctx.form) latestSectionDraft = normalizeSections(ctx.form.sections) return } @@ -671,7 +879,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp Object.assign(ctx.form, detail) if (Array.isArray(detail.items)) { const secs = itemsToSections(detail.items, detail) - const vis = visibleTitles(detail.procurement_category, (detail.is_bom || 'Y') === 'Y') + const vis = doVisibleTitles(detail.procurement_category, (detail.is_bom || 'Y') === 'Y') ;(secs as any).__visibleTitles = vis ctx.form.sections = secs ctx.form.__visibleTitles = vis @@ -679,7 +887,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp } } } catch (e) { - console.warn('加载成本模板详情失败', e) + console.warn(t('message.pages.miscprocurement.cost_template.loadDetailFailed'), e) } } }, @@ -687,7 +895,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp request: { pageRequest: async (query) => api.GetList(query), addRequest: async ({ form }) => { - const pre = validateCostTemplateBeforeSubmit(form) + const pre = doValidateCostTemplateBeforeSubmit(form) if (!pre.ok) { ElMessage.error(pre.message) return Promise.reject(new Error(pre.message)) @@ -715,7 +923,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp }) }, editRequest: async ({ form, row }) => { - const pre = validateCostTemplateBeforeSubmit(form) + const pre = doValidateCostTemplateBeforeSubmit(form) if (!pre.ok) { ElMessage.error(pre.message) return Promise.reject(new Error(pre.message)) @@ -764,32 +972,32 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp show: false }, confirm: { - text: '确认', - title: '确认', + text: t('message.pages.miscprocurement.cost_template.confirm'), + title: t('message.pages.miscprocurement.cost_template.confirm'), type: 'success', order: 4, show: compute(({ row }) => templateStatusUnconfirmed(row)), async click({ row }: { row: any }) { try { - await ElMessageBox.confirm('确认将模板设为「已确认」状态?', '确认', { + await ElMessageBox.confirm(t('message.pages.miscprocurement.cost_template.confirmStatusTip'), t('message.pages.miscprocurement.cost_template.confirm'), { type: 'warning', - confirmButtonText: '确定', - cancelButtonText: '取消' + confirmButtonText: t('message.pages.miscprocurement.cost_template.confirmButtonText'), + cancelButtonText: t('message.pages.miscprocurement.cost_template.cancelButtonText') }) const cres: any = await api.ConfirmObj(row.id) - const okMsg = cres?.data?.msg || cres?.msg || '确认成功' + const okMsg = cres?.data?.msg || cres?.msg || t('message.pages.miscprocurement.cost_template.confirmSuccess') ElMessage.success(okMsg) crudExpose?.doRefresh?.() } catch (e: any) { if (e === 'cancel' || e === 'close') return - const msg = e?.response?.data?.msg || e?.message || '确认失败' + const msg = e?.response?.data?.msg || e?.message || t('message.pages.miscprocurement.cost_template.confirmFailed') ElMessage.error(msg) } } }, newVersion: { - text: '版本变更', - title: '版本变更', + text: t('message.pages.miscprocurement.cost_template.newVersion'), + title: t('message.pages.miscprocurement.cost_template.newVersion'), type: 'primary', order: 5, show: compute(({ row }) => templateStatusConfirmed(row)), @@ -798,7 +1006,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp const res: any = await api.GetObj(row.id) const detail = res?.data?.data || res?.data || res if (!detail || typeof detail !== 'object') { - ElMessage.error('加载模板详情失败') + ElMessage.error(t('message.pages.miscprocurement.cost_template.loadDetailFailed')) return } const nextV = Number(detail.version) + 1 @@ -812,7 +1020,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp // 必须用源模板版本过滤明细;勿把 version 设为 nextV,否则 itemsToSections 会筛掉全部旧版行 const headForSections = { ...detail, __itemsSourceVersion: sourceVer } const secs = itemsToSections(itemsStripped, headForSections) - const vis = visibleTitles(detail.procurement_category, (detail.is_bom || 'Y') === 'Y') + const vis = doVisibleTitles(detail.procurement_category, (detail.is_bom || 'Y') === 'Y') ;(secs as any).__visibleTitles = vis await crudExpose?.openAdd?.( { @@ -834,10 +1042,10 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp items: itemsStripped } }, - { title: '版本变更' } + { title: t('message.pages.miscprocurement.cost_template.newVersion') } ) } catch (e: any) { - const msg = e?.response?.data?.msg || e?.message || '版本变更失败' + const msg = e?.response?.data?.msg || e?.message || t('message.pages.miscprocurement.cost_template.newVersionFailed') ElMessage.error(msg) } } @@ -846,42 +1054,42 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp }, columns: { template_name: { - title: '模板名称', + title: t('message.pages.miscprocurement.cost_template.templateName'), type: 'input', search: { show: true, component: { - props: { placeholder: '请输入模板名称', clearable: true } + props: { placeholder: t('message.pages.miscprocurement.cost_template.inputTemplateName'), clearable: true } } }, form: { col: { span: 12 }, - rules: [{ required: true, message: '请输入模板名称' }] + rules: [{ required: true, message: t('message.pages.miscprocurement.cost_template.inputTemplateName') }] }, column: { minWidth: 160 } }, template_no: { - title: '模板编号', + title: t('message.pages.miscprocurement.cost_template.templateNo'), type: 'input', search: { show: true, - component: { props: { placeholder: '模板编号', clearable: true } } + component: { props: { placeholder: t('message.pages.miscprocurement.cost_template.templateNo'), clearable: true } } }, form: { show: false }, column: { width: 160, showOverflowTooltip: true } }, procurement_category: { - title: '采购类别', + title: t('message.pages.miscprocurement.cost_template.procurementCategory'), type: 'dict-select', - dict: dict({ data: procurementDict }), + dict: dict({ data: procurementDictLocalized }), column: { width: 120 }, search: { show: true }, form: { col: { span: 12 }, value: '2', - rules: [{ required: true, message: '请选择采购类别' }], + rules: [{ required: true, message: t('message.pages.miscprocurement.cost_template.selectProcurementCategory') }], valueChange({ form, value }) { - refreshSectionsIfNeeded(form, { procurement_category: value }) + doRefreshSectionsIfNeeded(form, { procurement_category: value }) logPT('onChange procurement_category', value, { visibleTitles: form.__visibleTitles }) logPT('sections titles now', Array.isArray(form.sections) ? form.sections.map((s: any) => s.title) : []) } @@ -891,36 +1099,36 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp } }, acti: { - title: '有效', + title: t('message.pages.miscprocurement.cost_template.acti'), type: 'dict-select', dict: dict({ data: [ - { value: 'Y', label: '是' }, - { value: 'N', label: '否' } + { value: 'Y', label: t('message.pages.miscprocurement.cost_template.actiYes') }, + { value: 'N', label: t('message.pages.miscprocurement.cost_template.actiNo') } ] }), column: { width: 90 }, search: { show: true }, form: { value: 'Y' } }, is_bom: { - title: '启用BOM', + title: t('message.pages.miscprocurement.cost_template.isBom'), type: 'dict-select', dict: dict({ data: [ - { value: 'Y', label: '是' }, - { value: 'N', label: '否' } + { value: 'Y', label: t('message.pages.miscprocurement.cost_template.bomYes') }, + { value: 'N', label: t('message.pages.miscprocurement.cost_template.bomNo') } ] }), column: { width: 100 }, search: { show: true }, form: { value: 'Y', valueChange({ form, value }) { - refreshSectionsIfNeeded(form, { enable_cost_structure: value === 'Y' }) + doRefreshSectionsIfNeeded(form, { enable_cost_structure: value === 'Y' }) logPT('onChange is_bom', value, { visibleTitles: form.__visibleTitles }) logPT('sections titles now', Array.isArray(form.sections) ? form.sections.map((s: any) => s.title) : []) } } }, template_desc: { - title: '备注', + title: t('message.pages.miscprocurement.cost_template.templateDesc'), type: 'textarea', column: { minWidth: 180, showOverflowTooltip: true }, form: { @@ -929,7 +1137,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp } }, version: { - title: '版本号', + title: t('message.pages.miscprocurement.cost_template.version'), type: 'number', column: { width: 100 }, form: { @@ -941,18 +1149,18 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp } }, status: { - title: '状态', + title: t('message.pages.miscprocurement.cost_template.status'), type: 'dict-select', dict: dict({ data: [ - { value: '0', label: '未确认' }, - { value: '1', label: '已确认' }, - { value: '2', label: '作废' } + { value: '0', label: t('message.pages.miscprocurement.cost_template.statusUnconfirmed') }, + { value: '1', label: t('message.pages.miscprocurement.cost_template.statusConfirmed') }, + { value: '2', label: t('message.pages.miscprocurement.cost_template.statusCancelled') } ] }), search: { show: true, - component: { props: { clearable: true, placeholder: '状态(默认不含作废)' } } + component: { props: { clearable: true, placeholder: t('message.pages.miscprocurement.cost_template.statusPlaceholder') } } }, form: { show: false }, column: { width: 100 } @@ -977,7 +1185,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp } }, value: () => { - const vis = visibleTitles('2', true) + const vis = doVisibleTitles('2', true) const defaults = ensureAllSections({ sections: buildDefaultSections('2') }, vis, '2') ;(defaults as any).__visibleTitles = vis return defaults @@ -991,7 +1199,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp // 否则会把数据库里的 is_computed 等真实值覆盖成前端预设默认值。 if (Array.isArray(form.items) && form.items.length) { const secs = itemsToSections(form.items, form) - const vis = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') + const vis = doVisibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') ;(secs as any).__visibleTitles = vis form.sections = secs form.__visibleTitles = vis @@ -999,7 +1207,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp logPT('valueBuilder(items)', { visibleTitles: form.__visibleTitles, sections: form.sections?.length, items: form.items.length }) return } - const vis = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') + const vis = doVisibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') const ensured = ensureAllSections(form, vis, form.procurement_category) ;(ensured as any).__visibleTitles = vis form.sections = ensured @@ -1019,7 +1227,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp // 初次回填时(尚未形成 sections),再从 items 转换 if (Array.isArray(form.items) && form.items.length) { const secs = itemsToSections(form.items, form) - const vis = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') + const vis = doVisibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') ;(secs as any).__visibleTitles = vis form.sections = secs form.__visibleTitles = vis @@ -1034,7 +1242,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp } }, update_time: { - title: '更新时间', + title: t('message.pages.miscprocurement.cost_template.updateTime'), type: 'datetime', form: { show: false }, column: { width: 180 } diff --git a/web/src/views/pisadmin/miscprocurement/misc_materials/crud.tsx b/web/src/views/pisadmin/miscprocurement/misc_materials/crud.tsx index a64e83c..ac72eab 100644 --- a/web/src/views/pisadmin/miscprocurement/misc_materials/crud.tsx +++ b/web/src/views/pisadmin/miscprocurement/misc_materials/crud.tsx @@ -1,12 +1,8 @@ import { dict, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud' +import { useI18n } from 'vue-i18n' import * as api from './api' import { GetCompanies } from '../../basicinfo/currency/api' -const statusDict = [ - { value: 1, label: '可用' }, - { value: 0, label: '不可用' } -] - const loadCompanyOptions = async () => { try { const res = await GetCompanies({ page: 1, page_size: 1000, pageSize: 1000 }) @@ -32,6 +28,13 @@ void loadCompanyOptions() export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { void crudExpose + const { t } = useI18n() + + const statusDict = [ + { value: 1, label: t('message.pages.miscprocurement.misc_materials.statusEnabled') }, + { value: 0, label: t('message.pages.miscprocurement.misc_materials.statusDisabled') } + ] + return { crudOptions: { form: { @@ -56,7 +59,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp }, columns: { factory: { - title: '交易厂区', + title: t('message.pages.miscprocurement.misc_materials.companyCode'), type: 'dict-select', dict: dict({ cache: false, @@ -70,25 +73,28 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp column: { minWidth: 160, showOverflowTooltip: true } }, materialtype: { - title: '材质', + title: t('message.pages.miscprocurement.misc_materials.materialtype'), type: 'input', - search: { show: true, component: { props: { placeholder: '请输入材质', clearable: true } } }, + search: { + show: true, + component: { props: { placeholder: t('message.pages.miscprocurement.misc_materials.materialtype'), clearable: true } } + }, column: { minWidth: 160, showOverflowTooltip: true } }, density: { - title: '比重', + title: t('message.pages.miscprocurement.misc_materials.density'), type: 'input', - form: { rules: [{ required: true, message: '请输入比重' }] }, + form: { rules: [{ required: true, message: t('message.pages.miscprocurement.misc_materials.density') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }] }, column: { width: 120, showOverflowTooltip: true } }, price: { - title: '单价', + title: t('message.pages.miscprocurement.misc_materials.price'), type: 'number', - form: { rules: [{ required: true, message: '请输入单价' }], component: { props: { precision: 2 } } }, + form: { rules: [{ required: true, message: t('message.pages.miscprocurement.misc_materials.price') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }], component: { props: { precision: 2 } } }, column: { width: 120 } }, status: { - title: '可用状态', + title: t('message.pages.miscprocurement.misc_materials.status'), type: 'dict-switch', dict: dict({ data: statusDict }), form: { value: 1 }, diff --git a/web/src/views/pisadmin/miscprocurement/misc_stations/crud.tsx b/web/src/views/pisadmin/miscprocurement/misc_stations/crud.tsx index f33d173..210d45b 100644 --- a/web/src/views/pisadmin/miscprocurement/misc_stations/crud.tsx +++ b/web/src/views/pisadmin/miscprocurement/misc_stations/crud.tsx @@ -3,16 +3,7 @@ import * as api from './api' import { GetCompanies } from '../../basicinfo/currency/api' import { GetList as GetUnits } from '../../basicinfo/unit/api' import { ElMessage } from 'element-plus' - -const statusDict = [ - { value: 1, label: '可用' }, - { value: 0, label: '不可用' } -] - -const stationTypeDict = [ - { value: 1, label: '模治具' }, - { value: 2, label: '石墨' } -] +import { useI18n } from 'vue-i18n' const loadCompanyOptions = async () => { try { @@ -59,6 +50,18 @@ const loadUnitOptions = async () => { void loadCompanyOptions() export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const { t } = useI18n() + + const statusDict = [ + { value: 1, label: t('message.pages.miscprocurement.misc_stations.statusEnabled') }, + { value: 0, label: t('message.pages.miscprocurement.misc_stations.statusDisabled') } + ] + + const stationTypeDict = [ + { value: 1, label: t('message.pages.miscprocurement.misc_stations.typeTooling') }, + { value: 2, label: t('message.pages.miscprocurement.misc_stations.typeGraphite') } + ] + void crudExpose const ensureStationCodeUnique = async (companyCode: string, stationCode: string, currentId?: number) => { @@ -76,7 +79,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp ? list.find((item: any) => item.company_code === companyCode && item.stationcode === stationCode) : null if (exists && (!currentId || exists.id !== currentId)) { - throw new Error('同一交易厂区下工站代码已存在,不可重复') + throw new Error(t('message.pages.miscprocurement.misc_stations.companyCode') + t('message.pages.miscprocurement.misc_stations.stationcode') + t('message.pages.menu.validation.alreadyExists')) } } return { @@ -119,7 +122,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp }, columns: { company_code: { - title: '交易厂区', + title: t('message.pages.miscprocurement.misc_stations.companyCode'), type: 'dict-select', dict: dict({ cache: false, @@ -131,47 +134,47 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp column: { minWidth: 160, showOverflowTooltip: true } }, stationcode: { - title: '工站代码', + title: t('message.pages.miscprocurement.misc_stations.stationcode'), type: 'input', - search: { show: true, component: { props: { placeholder: '请输入工站代码', clearable: true } } }, - form: { rules: [{ required: true, message: '请输入工站代码' }] }, + search: { show: true, component: { props: { placeholder: t('message.pages.miscprocurement.misc_stations.stationcode'), clearable: true } } }, + form: { rules: [{ required: true, message: t('message.pages.miscprocurement.misc_stations.stationcode') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }] }, editForm: { component: { props: { disabled: true } } }, column: { minWidth: 140, showOverflowTooltip: true } }, stationname: { - title: '工站名称', + title: t('message.pages.miscprocurement.misc_stations.stationname'), type: 'input', - search: { show: true, component: { props: { placeholder: '请输入工站名称', clearable: true } } }, - form: { rules: [{ required: true, message: '请输入工站名称' }] }, + search: { show: true, component: { props: { placeholder: t('message.pages.miscprocurement.misc_stations.stationname'), clearable: true } } }, + form: { rules: [{ required: true, message: t('message.pages.miscprocurement.misc_stations.stationname') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }] }, column: { minWidth: 160, showOverflowTooltip: true } }, stationtype: { - title: '工站类型', + title: t('message.pages.miscprocurement.misc_stations.stationtype'), type: 'dict-select', dict: dict({ data: stationTypeDict }), - form: { rules: [{ required: true, message: '请选择工站类型' }] }, + form: { rules: [{ required: true, message: t('message.pages.miscprocurement.misc_stations.stationtype') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }] }, column: { width: 140, showOverflowTooltip: true } }, unit: { - title: '计量单位', + title: t('message.pages.miscprocurement.misc_stations.unit'), type: 'dict-select', dict: dict({ cache: false, getData: async () => loadUnitOptions() }), - form: { rules: [{ required: true, message: '请选择计量单位' }] }, + form: { rules: [{ required: true, message: t('message.pages.miscprocurement.misc_stations.unit') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }] }, column: { width: 140, showOverflowTooltip: true } }, rate: { - title: '费率', + title: t('message.pages.miscprocurement.misc_stations.rate'), type: 'number', - form: { rules: [{ required: true, message: '请输入费率' }], component: { props: { precision: 2 } } }, + form: { rules: [{ required: true, message: t('message.pages.miscprocurement.misc_stations.rate') + ' ' + t('message.pages.menu.validation.fieldNameRequired') }], component: { props: { precision: 2 } } }, column: { width: 120 } }, status: { - title: '可用状态', + title: t('message.pages.miscprocurement.misc_stations.status'), type: 'dict-switch', dict: dict({ data: statusDict }), form: { value: 1 }, diff --git a/web/src/views/pisadmin/miscprocurement/rfqmiscellaneous/crud.tsx b/web/src/views/pisadmin/miscprocurement/rfqmiscellaneous/crud.tsx index 7f25b6d..dc459f1 100644 --- a/web/src/views/pisadmin/miscprocurement/rfqmiscellaneous/crud.tsx +++ b/web/src/views/pisadmin/miscprocurement/rfqmiscellaneous/crud.tsx @@ -1,37 +1,8 @@ import { compute, dict, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud' import { ElMessage, ElMessageBox } from 'element-plus' +import { useI18n } from 'vue-i18n' import * as api from './api' -const statusDict = [ - { value: 1, label: '开立' }, - { value: 2, label: '确认' }, - { value: 3, label: '发布' }, - { value: 4, label: '报价中' }, - { value: 5, label: '报价结束' }, - { value: 6, label: '比议价中' }, - { value: 7, label: '价格审核' }, - { value: 8, label: '核价通过(结束)' }, - { value: 9, label: '落标(结束)' }, - { value: 0, label: '作废' } -] - -const buyingMethodDict = [ - { value: 1, label: '询价' }, - { value: 2, label: '招标' } -] - -const paymentMethods = [ - { value: 1, label: '月结30天' }, - { value: 2, label: '月结60天' }, - { value: 3, label: '不到付款' }, - { value: 4, label: '预付30%' }, - { value: 5, label: '预付50%' }, - { value: 6, label: '余款至生产' }, - { value: 7, label: '价格审核' }, - { value: 8, label: '价格结算(运费)' }, - { value: 9, label: '新建(结束)' } -] - const formatQuoteDeadlineDisplay = (value: unknown) => { if (!value) return '' const text = String(value).trim() @@ -950,6 +921,38 @@ export const createCrudOptions = function ({ }: Partial & ExtraHooks): CreateCrudOptionsRet { void context + const { t } = useI18n() + + const statusDict = [ + { value: 1, label: t('message.pages.miscprocurement.rfqmiscellaneous.statusOpen') }, + { value: 2, label: t('message.pages.miscprocurement.rfqmiscellaneous.statusConfirmed') }, + { value: 3, label: t('message.pages.miscprocurement.rfqmiscellaneous.statusPublished') }, + { value: 4, label: t('message.pages.miscprocurement.rfqmiscellaneous.statusQuoting') }, + { value: 5, label: t('message.pages.miscprocurement.rfqmiscellaneous.statusQuotingEnd') }, + { value: 6, label: t('message.pages.miscprocurement.rfqmiscellaneous.statusBargaining') }, + { value: 7, label: t('message.pages.miscprocurement.rfqmiscellaneous.statusPriceAudit') }, + { value: 8, label: t('message.pages.miscprocurement.rfqmiscellaneous.statusPriceApproved') }, + { value: 9, label: t('message.pages.miscprocurement.rfqmiscellaneous.statusLost') }, + { value: 0, label: t('message.pages.miscprocurement.rfqmiscellaneous.statusCancelled') } + ] + + const buyingMethodDict = [ + { value: 1, label: t('message.pages.miscprocurement.rfqmiscellaneous.buyingMethodInquiry') }, + { value: 2, label: t('message.pages.miscprocurement.rfqmiscellaneous.buyingMethodBid') } + ] + + const paymentMethods = [ + { value: 1, label: t('message.pages.miscprocurement.rfqmiscellaneous.paymentMethod1') }, + { value: 2, label: t('message.pages.miscprocurement.rfqmiscellaneous.paymentMethod2') }, + { value: 3, label: t('message.pages.miscprocurement.rfqmiscellaneous.paymentMethod3') }, + { value: 4, label: t('message.pages.miscprocurement.rfqmiscellaneous.paymentMethod4') }, + { value: 5, label: t('message.pages.miscprocurement.rfqmiscellaneous.paymentMethod5') }, + { value: 6, label: t('message.pages.miscprocurement.rfqmiscellaneous.paymentMethod6') }, + { value: 7, label: t('message.pages.miscprocurement.rfqmiscellaneous.paymentMethod7') }, + { value: 8, label: t('message.pages.miscprocurement.rfqmiscellaneous.paymentMethod8') }, + { value: 9, label: t('message.pages.miscprocurement.rfqmiscellaneous.paymentMethod9') } + ] + const getSelectedRows = () => { if (typeof getTableSelection === 'function') { const rows = getTableSelection() @@ -976,11 +979,11 @@ export const createCrudOptions = function ({ const pickSelectedRow = (): any | null => { const rows = getSelectedRows() if (!rows.length) { - ElMessage.warning('请先选择询价单') + ElMessage.warning(t('message.pages.miscprocurement.rfqmiscellaneous.selectInquiryFirst')) return null } if (rows.length > 1) { - ElMessage.warning('仅支持单条操作') + ElMessage.warning(t('message.pages.miscprocurement.rfqmiscellaneous.onlySingleOperation')) return null } return rows[0] @@ -991,13 +994,13 @@ export const createCrudOptions = function ({ form: { labelWidth: '120px', col: { span: 12 }, - wrapper: { is: 'el-dialog', width: '960px', top: '6vh', title: '询价单' }, + wrapper: { is: 'el-dialog', width: '960px', top: '6vh', title: t('message.pages.miscprocurement.rfqmiscellaneous.title') }, group: { type: 'tab', - base: { label: '基础信息', columns: ['code', 'title', 'product_category', 'template', 'part_no', 'part_name', 'quote_deadline', 'purchase_qty', 'buyer', 'currency', 'plant', 'target_price', 'lead_time_days', 'payment_term', 'status', 'remark'] }, - cost: { label: '成本结构', columns: ['cost_items'] }, - vendors: { label: '供应商名单', columns: ['vendors'] }, - attachments: { label: '附件', columns: ['attachments'] } + base: { label: t('message.pages.miscprocurement.rfqmiscellaneous.basicInfo'), columns: ['code', 'title', 'product_category', 'template', 'part_no', 'part_name', 'quote_deadline', 'purchase_qty', 'buyer', 'currency', 'plant', 'target_price', 'lead_time_days', 'payment_term', 'status', 'remark'] }, + cost: { label: t('message.pages.miscprocurement.rfqmiscellaneous.costStructure'), columns: ['cost_items'] }, + vendors: { label: t('message.pages.miscprocurement.rfqmiscellaneous.supplierList'), columns: ['vendors'] }, + attachments: { label: t('message.pages.miscprocurement.rfqmiscellaneous.attachments'), columns: ['attachments'] } } }, request: { @@ -1009,7 +1012,7 @@ export const createCrudOptions = function ({ try { return await api.DelObj(row.id) } catch (err: any) { - ElMessage.error(getErrorMessage(err, '删除失败')) + ElMessage.error(getErrorMessage(err, t('message.pages.miscprocurement.rfqmiscellaneous.deleteFailed'))) throw err } } @@ -1024,28 +1027,28 @@ export const createCrudOptions = function ({ buttons: { add: { show: true, - text: '新建询价单', + text: t('message.pages.miscprocurement.rfqmiscellaneous.newInquiry'), click() { onAdd && onAdd() } }, startBargaining: { show: true, - text: '开启比价', + text: t('message.pages.miscprocurement.rfqmiscellaneous.startBargain'), order: 2, type: 'warning', async click() { const row = pickSelectedRow() if (!row) return if (!isQuotingOrEndedStatus(row)) { - ElMessage.warning('仅报价中或报价结束状态可开启比价') + ElMessage.warning(t('message.pages.miscprocurement.rfqmiscellaneous.onlyQuotingCanBargain')) return } try { - await ElMessageBox.confirm('确认将状态改为【比议价中】?', '提示', { + await ElMessageBox.confirm(t('message.pages.miscprocurement.rfqmiscellaneous.confirmBargainPrompt'), t('message.pages.miscprocurement.rfqmiscellaneous.prompt'), { type: 'warning', - confirmButtonText: '确定', - cancelButtonText: '取消' + confirmButtonText: t('message.pages.miscprocurement.rfqmiscellaneous.confirm'), + cancelButtonText: t('message.pages.miscprocurement.rfqmiscellaneous.cancel') }) await api.StartBargainingObj(row.id) await api.SaveNegotiationRecordsObj(row.id, { records: [] }) @@ -1054,7 +1057,7 @@ export const createCrudOptions = function ({ crudExpose?.doRefresh?.() } catch (err: any) { if (err === 'cancel' || err === 'close') return - ElMessage.error(getErrorMessage(err, '开启比价失败')) + ElMessage.error(getErrorMessage(err, t('message.pages.miscprocurement.rfqmiscellaneous.startBargainFailed'))) } } }, @@ -1067,7 +1070,7 @@ export const createCrudOptions = function ({ view: { show: false }, edit: { show: false }, remove: { - text: '删除', + text: t('message.pages.miscprocurement.rfqmiscellaneous.delete'), // 禁用时不沿用 danger 的淡红底,改为 info 灰底 + disabled type: compute(({ row }) => (isOpenStatus(row) ? 'danger' : 'info')), order: 1, @@ -1075,7 +1078,7 @@ export const createCrudOptions = function ({ disabled: compute(({ row }) => !isOpenStatus(row)) }, customView: { - text: '查看', + text: t('message.pages.miscprocurement.rfqmiscellaneous.view'), type: 'default', order: 0, show: true, @@ -1084,7 +1087,7 @@ export const createCrudOptions = function ({ } }, customEdit: { - text: '编辑', + text: t('message.pages.miscprocurement.rfqmiscellaneous.edit'), type: compute(({ row }) => (isOpenStatus(row) ? 'primary' : 'info')), order: 1.5, show: true, @@ -1095,7 +1098,7 @@ export const createCrudOptions = function ({ } }, confirm: { - text: '确认', + text: t('message.pages.miscprocurement.rfqmiscellaneous.confirm'), type: compute(({ row }) => (isOpenStatus(row) ? 'success' : 'info')), order: 2, show: true, @@ -1105,26 +1108,26 @@ export const createCrudOptions = function ({ try { const supplierOk = rowHasSuppliersList(row) if (supplierOk === false) { - ElMessage.warning('请先维护询价单供应商名单后再确认') + ElMessage.warning(t('message.pages.miscprocurement.rfqmiscellaneous.addSupplierFirst')) return } - await ElMessageBox.confirm('确认后,该询价单将锁定并不可再编辑。如需修改,后续需执行【还原】操作', '提示', { + await ElMessageBox.confirm(t('message.pages.miscprocurement.rfqmiscellaneous.confirmPrompt'), t('message.pages.miscprocurement.rfqmiscellaneous.prompt'), { type: 'warning', - confirmButtonText: '确定', - cancelButtonText: '取消' + confirmButtonText: t('message.pages.miscprocurement.rfqmiscellaneous.confirm'), + cancelButtonText: t('message.pages.miscprocurement.rfqmiscellaneous.cancel') }) const res = await api.ConfirmObj(row.id) crudExpose?.doRefresh?.() return res } catch (err: any) { if (err === 'cancel' || err === 'close') return - ElMessage.error(getErrorMessage(err, '确认失败')) + ElMessage.error(getErrorMessage(err, t('message.pages.miscprocurement.rfqmiscellaneous.confirmFailed'))) throw err } } }, restore: { - text: '还原', + text: t('message.pages.miscprocurement.rfqmiscellaneous.restore'), type: compute(({ row }) => (isConfirmedStatus(row) ? 'primary' : 'info')), order: 2.5, show: true, @@ -1132,23 +1135,23 @@ export const createCrudOptions = function ({ async click({ row }) { if (!isConfirmedStatus(row)) return try { - await ElMessageBox.confirm('确认将状态还原为【开立】?', '提示', { + await ElMessageBox.confirm(t('message.pages.miscprocurement.rfqmiscellaneous.restorePrompt'), t('message.pages.miscprocurement.rfqmiscellaneous.prompt'), { type: 'warning', - confirmButtonText: '确定', - cancelButtonText: '取消' + confirmButtonText: t('message.pages.miscprocurement.rfqmiscellaneous.confirm'), + cancelButtonText: t('message.pages.miscprocurement.rfqmiscellaneous.cancel') }) const res = await api.RestoreObj(row.id) crudExpose?.doRefresh?.() return res } catch (err: any) { if (err === 'cancel' || err === 'close') return - ElMessage.error(getErrorMessage(err, '还原失败')) + ElMessage.error(getErrorMessage(err, t('message.pages.miscprocurement.rfqmiscellaneous.restoreFailed'))) throw err } } }, publish: { - text: '发布', + text: t('message.pages.miscprocurement.rfqmiscellaneous.publish'), type: compute(({ row }) => (canPublishStatus(row) ? 'warning' : 'info')), order: 3, show: true, @@ -1156,23 +1159,23 @@ export const createCrudOptions = function ({ async click({ row }) { if (!canPublishStatus(row)) return try { - await ElMessageBox.confirm('确认将状态改为【发布】?', '提示', { + await ElMessageBox.confirm(t('message.pages.miscprocurement.rfqmiscellaneous.publishPrompt'), t('message.pages.miscprocurement.rfqmiscellaneous.prompt'), { type: 'warning', - confirmButtonText: '确定', - cancelButtonText: '取消' + confirmButtonText: t('message.pages.miscprocurement.rfqmiscellaneous.confirm'), + cancelButtonText: t('message.pages.miscprocurement.rfqmiscellaneous.cancel') }) const res = await api.PublishObj(row.id) crudExpose?.doRefresh?.() return res } catch (err: any) { if (err === 'cancel' || err === 'close') return - ElMessage.error(getErrorMessage(err, '发布失败')) + ElMessage.error(getErrorMessage(err, t('message.pages.miscprocurement.rfqmiscellaneous.publishFailed'))) throw err } } }, viewComparison: { - text: '比价', + text: t('message.pages.miscprocurement.rfqmiscellaneous.comparePrice'), type: compute(({ row }) => (canOpenComparison(row) ? 'primary' : 'info')), order: 0.25, show: true, @@ -1198,7 +1201,7 @@ export const createCrudOptions = function ({ } }, company_short_name: { - title: '交易厂区', + title: t('message.pages.miscprocurement.rfqmiscellaneous.companyShortName'), type: 'text', form: { show: false }, search: { show: false }, @@ -1212,12 +1215,12 @@ export const createCrudOptions = function ({ } }, buying_method: { - title: '采购方式', + title: t('message.pages.miscprocurement.rfqmiscellaneous.buyingMethod'), type: 'dict-select', dict: dict({ data: buyingMethodDict }), search: { show: true, - component: { props: { placeholder: '采购方式', clearable: true } } + component: { props: { placeholder: t('message.pages.miscprocurement.rfqmiscellaneous.buyingMethod'), clearable: true } } }, form: { show: false }, column: { @@ -1231,11 +1234,11 @@ export const createCrudOptions = function ({ } }, inquiry_no: { - title: '询价单号', + title: t('message.pages.miscprocurement.rfqmiscellaneous.inquiryNo'), type: 'input', search: { show: true, - component: { props: { placeholder: '请输入询价单号', clearable: true } } + component: { props: { placeholder: t('message.pages.miscprocurement.rfqmiscellaneous.inquiryNoPlaceholder'), clearable: true } } }, form: { show: false @@ -1243,17 +1246,17 @@ export const createCrudOptions = function ({ column: { minWidth: 120, showOverflowTooltip: true } }, title: { - title: '询价单名称', + title: t('message.pages.miscprocurement.rfqmiscellaneous.inquiryName'), type: 'input', search: { show: true, - component: { props: { placeholder: '请输入询价单名称', clearable: true } } + component: { props: { placeholder: t('message.pages.miscprocurement.rfqmiscellaneous.inquiryNamePlaceholder'), clearable: true } } }, - form: { rules: [{ required: true, message: '请输入询价单名称' }] }, + form: { rules: [{ required: true, message: t('message.pages.miscprocurement.rfqmiscellaneous.inquiryNameRequired') }] }, column: { minWidth: 160, showOverflowTooltip: true } }, template: { - title: '询价模版', + title: t('message.pages.miscprocurement.rfqmiscellaneous.inquiryTemplate'), type: 'input', column: { minWidth: 120, @@ -1268,7 +1271,7 @@ export const createCrudOptions = function ({ } }, quote_deadline: { - title: '报价截止时间', + title: t('message.pages.miscprocurement.rfqmiscellaneous.quoteDeadline'), type: 'datetime', column: { width: 150, @@ -1276,7 +1279,7 @@ export const createCrudOptions = function ({ } }, bid_start_time: { - title: '投标开始时间', + title: t('message.pages.miscprocurement.rfqmiscellaneous.bidStartTime'), type: 'datetime', column: { width: 150, @@ -1285,7 +1288,7 @@ export const createCrudOptions = function ({ } }, bid_end_time: { - title: '投标截止时间', + title: t('message.pages.miscprocurement.rfqmiscellaneous.bidEndTime'), type: 'datetime', column: { width: 150, @@ -1294,17 +1297,17 @@ export const createCrudOptions = function ({ } }, buyer: { - title: '采购负责人', + title: t('message.pages.miscprocurement.rfqmiscellaneous.buyer'), type: 'input', search: { show: true, - component: { props: { placeholder: '请输入采购负责人', clearable: true } } + component: { props: { placeholder: t('message.pages.miscprocurement.rfqmiscellaneous.buyerPlaceholder'), clearable: true } } }, - form: { rules: [{ required: true, message: '请输入采购负责人' }] }, + form: { rules: [{ required: true, message: t('message.pages.miscprocurement.rfqmiscellaneous.buyerRequired') }] }, column: { minWidth: 120 } }, currency: { - title: '币别', + title: t('message.pages.miscprocurement.rfqmiscellaneous.currency'), type: 'dict-select', dict: dict({ data: [ { value: 'CNY', label: 'CNY' }, @@ -1314,32 +1317,32 @@ export const createCrudOptions = function ({ form: { value: 'CNY' } }, lead_time_days: { - title: '交货周期(天)', + title: t('message.pages.miscprocurement.rfqmiscellaneous.leadTimeDays'), type: 'number', column: { width: 120 }, form: { component: { props: { precision: 0 } } } }, payment_method: { - title: '付款方式', + title: t('message.pages.miscprocurement.rfqmiscellaneous.paymentMethod'), type: 'dict-select', dict: dict({ data: paymentMethods }), column: { width: 140, showOverflowTooltip: true }, form: { show: false } }, status: { - title: '状态', + title: t('message.pages.miscprocurement.rfqmiscellaneous.status'), type: 'dict-select', dict: dict({ data: statusDict }), column: { width: 140 } }, remark: { - title: '备注', + title: t('message.pages.miscprocurement.rfqmiscellaneous.remark'), type: 'textarea', column: { minWidth: 180, showOverflowTooltip: true }, form: { component: { props: { rows: 3 } } } }, update_datetime: { - title: '更新时间', + title: t('message.pages.miscprocurement.rfqmiscellaneous.updateTime'), type: 'datetime', form: { show: false }, column: { width: 180 } diff --git a/web/src/views/pissupplier/quotation/crud.tsx b/web/src/views/pissupplier/quotation/crud.tsx index 0fc2e35..4ad2c99 100644 --- a/web/src/views/pissupplier/quotation/crud.tsx +++ b/web/src/views/pissupplier/quotation/crud.tsx @@ -1,5 +1,6 @@ import { computed, onMounted, reactive, ref } from 'vue' import { ElMessage } from 'element-plus' +import { useI18n } from 'vue-i18n' import * as api from './api' import * as inquiryApi from '../../pisadmin/miscprocurement/rfqmiscellaneous/api' import { GetList as GetCostTemplateList } from '../../pisadmin/miscprocurement/cost_template/api' @@ -142,15 +143,15 @@ export function isWithinSupplierBidWindow(row: any, nowMs: number = Date.now()) } /** 非招标或未超限返回 null;否则返回提示文案(列表按钮禁用、openQuote/submit 前置校验) */ -export function getSupplierBidWindowRejectReason(row: any, nowMs: number = Date.now()): string | null { +export function getSupplierBidWindowRejectReason(row: any, nowMs: number = Date.now(), t?: Function): string | null { if (!isBuyingMethodBidding(row)) return null const start = parseQuoteDeadlineToMs(row?.bidStartTime ?? row?.bid_start_time) const end = parseQuoteDeadlineToMs(row?.bidEndTime ?? row?.bid_end_time) if (!Number.isFinite(start) || !Number.isFinite(end)) { - return '招标项目缺少投标开始或截止时间,无法报价或提交' + return t ? t('message.pages.pissupplier.quotation.bidWindowMissing') : '招标项目缺少投标开始或截止时间,无法报价或提交' } // if (nowMs < start) return '投标尚未开始,请在投标开始后再报价或提交' - if (nowMs > end) return '已超过投标截止时间,无法报价或提交' + if (nowMs > end) return t ? t('message.pages.pissupplier.quotation.bidWindowExpired') : '已超过投标截止时间,无法报价或提交' return null } @@ -195,22 +196,23 @@ export type InquiryAttachmentRow = { } /** 与 `pis_proc_inquiry_attachment` / 询价单 `attachments` 嵌套结构一致(勿用报价单 `attachments`) */ -const INQUIRY_FILE_TYPE_LABELS: Record = { - 1: '产品图纸', - 2: '招标文件', - 3: '其它文件' -} +const getInquiryFileTypeLabels = (t: Function) => ({ + 1: t('message.pages.pissupplier.quotation.fileTypeDrawing'), + 2: t('message.pages.pissupplier.quotation.fileTypeBidDoc'), + 3: t('message.pages.pissupplier.quotation.fileTypeOther') +}) -export function mapInquiryAttachmentsFromInquiryApi(rows: any[] | null | undefined): InquiryAttachmentRow[] { +export function mapInquiryAttachmentsFromInquiryApi(rows: any[] | null | undefined, t?: Function): InquiryAttachmentRow[] { if (!Array.isArray(rows) || !rows.length) return [] + const labels = t ? getInquiryFileTypeLabels(t) : { 1: '产品图纸', 2: '招标文件', 3: '其它文件' } return rows.map((r) => { const ft = Number(r.file_type) - const t = ft === 1 || ft === 2 || ft === 3 ? ft : 3 + const tf = ft === 1 || ft === 2 || ft === 3 ? ft : 3 return { id: r.id, part_id: r.part_id, - file_type: t, - file_type_label: r.file_type_label || INQUIRY_FILE_TYPE_LABELS[t], + file_type: tf, + file_type_label: r.file_type_label || labels[tf], file_name: r.file_name, file_path: r.file_path, upload_time: r.upload_time, @@ -285,11 +287,11 @@ function normalizeQuotationAttachmentsForUpload(rows: any[] | null | undefined): })) } -const statusOptions = [ - { label: '待报价', value: 'pending' }, - { label: '报价中', value: 'quoted' }, - { label: '已报价', value: 'completed' }, - { label: '已过期', value: 'expired' } +const getStatusOptions = (t: Function) => [ + { label: t('message.pages.pissupplier.quotation.statusPending'), value: 'pending' }, + { label: t('message.pages.pissupplier.quotation.statusQuoting'), value: 'quoted' }, + { label: t('message.pages.pissupplier.quotation.statusCompleted'), value: 'completed' }, + { label: t('message.pages.pissupplier.quotation.statusExpired'), value: 'expired' } ] const statusMapBackendToFront: Record = { @@ -321,7 +323,7 @@ export function formatAwardBidStatus(row: { statusCode?: number | string status?: QuoteStatus inquiryStatusCode?: number | string | null -}): { mode: 'flag'; text: string } | { mode: 'text'; text: string } { +}, t?: Function): { mode: 'flag'; text: string } | { mode: 'text'; text: string } { const ia = Number(row.isAwarded ?? row.is_awarded ?? 0) === 1 ? 1 : 0 let sc: number if (row.statusCode !== undefined && row.statusCode !== null && row.statusCode !== '') { @@ -333,13 +335,13 @@ export function formatAwardBidStatus(row: { const inqNum = inqRaw === undefined || inqRaw === null || inqRaw === '' ? NaN : Number(inqRaw) if (sc === 4) return { mode: 'text', text: '' } - if (ia === 1) return { mode: 'flag', text: '中标' } - if (ia === 0 && sc === 1) return { mode: 'text', text: '待报价' } - if (ia === 0 && sc === 2) return { mode: 'text', text: '报价中' } + if (ia === 1) return { mode: 'flag', text: t ? t('message.pages.pissupplier.quotation.awarded') : '中标' } + if (ia === 0 && sc === 1) return { mode: 'text', text: t ? t('message.pages.pissupplier.quotation.statusPending') : '待报价' } + if (ia === 0 && sc === 2) return { mode: 'text', text: t ? t('message.pages.pissupplier.quotation.statusQuoting') : '报价中' } if (ia === 0 && sc === 3) { - if (!Number.isFinite(inqNum)) return { mode: 'text', text: '评标中' } - if (inqNum !== 9) return { mode: 'text', text: '评标中' } - return { mode: 'text', text: '未中标' } + if (!Number.isFinite(inqNum)) return { mode: 'text', text: t ? t('message.pages.pissupplier.quotation.statusEvaluating') : '评标中' } + if (inqNum !== 9) return { mode: 'text', text: t ? t('message.pages.pissupplier.quotation.statusEvaluating') : '评标中' } + return { mode: 'text', text: t ? t('message.pages.pissupplier.quotation.statusLost') : '未中标' } } return { mode: 'text', text: '—' } } @@ -396,17 +398,17 @@ const paymentMapBackendToFront: Record = { } /** 与报价基础信息 `paymentTerm` 选项一致,供详情页只读展示 */ -export const PAYMENT_TERM_LABELS: Record = { - tt_30_70: 'T/T 30%预付,70%出货前', - net30: '月结30天', - net45: '月结45天', - prepaid: '全额预付' -} +export const getPaymentTermLabels = (t: Function) => ({ + tt_30_70: t('message.pages.pissupplier.quotation.paymentTt30_70'), + net30: t('message.pages.pissupplier.quotation.paymentNet30'), + net45: t('message.pages.pissupplier.quotation.paymentNet45'), + prepaid: t('message.pages.pissupplier.quotation.paymentPrepaid') +}) -export function formatPaymentTermLabel(term: string | null | undefined) { +export function formatPaymentTermLabel(term: string | null | undefined, t?: Function) { if (term == null || String(term).trim() === '') return '—' const k = String(term).trim() - return PAYMENT_TERM_LABELS[k] ?? k + return t ? getPaymentTermLabels(t)[k] ?? k : k } const costEnabledSections = ['材料成本', '加工成本', '其它成本', '利润', '税金'] @@ -683,27 +685,27 @@ const buildCostItemsFromTemplateSections = (sections: any, enableCostStructure = .filter(Boolean) as CostItem[] } -const buildDefaultNonBomCostItems = (): CostItem[] => { +const buildDefaultNonBomCostItems = (t: Function): CostItem[] => { const productDetail: CostItem = { id: crypto.randomUUID(), section: '产品明细', attrs: [ - { key: 'partNo', label: '料号', value: '' }, - { key: 'desc', label: '规格描述', value: '' }, - { key: 'qty', label: '数量', value: '' }, - { key: 'price', label: '单价', value: '' }, - { key: 'amount', label: '合计价格', value: '' } + { key: 'partNo', label: t('message.pages.pissupplier.quotation.partNo'), value: '' }, + { key: 'desc', label: t('message.pages.pissupplier.quotation.specDesc'), value: '' }, + { key: 'qty', label: t('message.pages.pissupplier.quotation.qty'), value: '' }, + { key: 'price', label: t('message.pages.pissupplier.quotation.unitPrice'), value: '' }, + { key: 'amount', label: t('message.pages.pissupplier.quotation.totalPrice'), value: '' } ] } const profit: CostItem = { id: crypto.randomUUID(), section: '利润', - attrs: [{ key: 'profitRate', label: '利润率', value: '' }] + attrs: [{ key: 'profitRate', label: t('message.pages.pissupplier.quotation.profitRate'), value: '' }] } const tax: CostItem = { id: crypto.randomUUID(), section: '税金', - attrs: [{ key: 'taxRate', label: '税率', value: '' }] + attrs: [{ key: 'taxRate', label: t('message.pages.pissupplier.quotation.taxRate'), value: '' }] } return [productDetail, profit, tax] } @@ -747,23 +749,23 @@ export const hasQuotationNestedCosts = (item: any): boolean => (Array.isArray(item.profit_costs) && item.profit_costs.length > 0)) /** 将详情嵌套子表转为 CostItem[],供 costItemsToRows 渲染 */ -const costItemsFromQuotationApi = (item: any): CostItem[] => { +const costItemsFromQuotationApi = (item: any, t: Function): CostItem[] => { const out: CostItem[] = [] let mid = 0 for (const m of item.material_costs || []) { const opt = parseJsonLoose(m.option_json) const attrs: { key: string; label: string; value: any }[] = [ - { key: 'part_id', label: '料号', value: m.part_id ?? '' }, - { key: 'material', label: '材质', value: m.material_spec ?? '' }, - { key: 'len', label: '长', value: m.length ?? '' }, - { key: 'width', label: '宽', value: m.width ?? '' }, - { key: 'height', label: '高', value: m.height ?? '' }, - { key: 'specificgravity', label: '比重', value: m.specific_gravity ?? '' }, - { key: 'qty', label: '数量', value: m.qty ?? '' }, - { key: 'weight', label: '重量', value: m.weight ?? '' }, - { key: 'unitPrice', label: '单价', value: m.unit_price ?? '' }, - { key: 'material_fee', label: '材料费用', value: m.material_cost ?? '' }, - { key: 'remark', label: '备注', value: m.remark ?? '' } + { key: 'part_id', label: t('message.pages.pissupplier.quotation.partNo'), value: m.part_id ?? '' }, + { key: 'material', label: t('message.pages.pissupplier.quotation.material'), value: m.material_spec ?? '' }, + { key: 'len', label: t('message.pages.pissupplier.quotation.length'), value: m.length ?? '' }, + { key: 'width', label: t('message.pages.pissupplier.quotation.width'), value: m.width ?? '' }, + { key: 'height', label: t('message.pages.pissupplier.quotation.height'), value: m.height ?? '' }, + { key: 'specificgravity', label: t('message.pages.pissupplier.quotation.specificGravity'), value: m.specific_gravity ?? '' }, + { key: 'qty', label: t('message.pages.pissupplier.quotation.qty'), value: m.qty ?? '' }, + { key: 'weight', label: t('message.pages.pissupplier.quotation.weight'), value: m.weight ?? '' }, + { key: 'unitPrice', label: t('message.pages.pissupplier.quotation.unitPrice'), value: m.unit_price ?? '' }, + { key: 'material_fee', label: t('message.pages.pissupplier.quotation.materialFee'), value: m.material_cost ?? '' }, + { key: 'remark', label: t('message.pages.pissupplier.quotation.remark'), value: m.remark ?? '' } ] Object.entries(opt).forEach(([k, v]) => { if (!attrs.find((a) => a.key === k)) attrs.push({ key: k, label: k, value: v }) @@ -779,13 +781,13 @@ const costItemsFromQuotationApi = (item: any): CostItem[] => { for (const p of item.process_costs || []) { const opt = parseJsonLoose(p.option_json) const attrs: { key: string; label: string; value: any }[] = [ - { key: 'part_id', label: '料号', value: p.part_id ?? '' }, - { key: 'processStation', label: '加工工站', value: p.process_station ?? '' }, - { key: 'processUnit', label: '单位', value: p.unit ?? '' }, - { key: 'processRate', label: '费率', value: p.unit_rate ?? '' }, - { key: 'processMeasure', label: '加工计量', value: p.process_qty ?? '' }, - { key: 'processFee', label: '加工费', value: p.process_price ?? '' }, - { key: 'remark', label: '备注', value: p.remark ?? '' } + { key: 'part_id', label: t('message.pages.pissupplier.quotation.partNo'), value: p.part_id ?? '' }, + { key: 'processStation', label: t('message.pages.pissupplier.quotation.processStation'), value: p.process_station ?? '' }, + { key: 'processUnit', label: t('message.pages.pissupplier.quotation.processUnit'), value: p.unit ?? '' }, + { key: 'processRate', label: t('message.pages.pissupplier.quotation.processRate'), value: p.unit_rate ?? '' }, + { key: 'processMeasure', label: t('message.pages.pissupplier.quotation.processMeasure'), value: p.process_qty ?? '' }, + { key: 'processFee', label: t('message.pages.pissupplier.quotation.processFee'), value: p.process_price ?? '' }, + { key: 'remark', label: t('message.pages.pissupplier.quotation.remark'), value: p.remark ?? '' } ] Object.entries(opt).forEach(([k, v]) => { if (!attrs.find((a) => a.key === k)) attrs.push({ key: k, label: k, value: v }) @@ -803,30 +805,30 @@ const costItemsFromQuotationApi = (item: any): CostItem[] => { id: `oth-${o.autoid ?? oid++}`, section: '其它成本', attrs: [ - { key: 'part_id', label: '料号', value: o.part_id ?? '' }, - { key: 'packageFee', label: '包装费', value: o.packaging_cost ?? '' }, - { key: 'transportFee', label: '运输费', value: o.transportation_cost ?? '' } + { key: 'part_id', label: t('message.pages.pissupplier.quotation.partNo'), value: o.part_id ?? '' }, + { key: 'packageFee', label: t('message.pages.pissupplier.quotation.packageFee'), value: o.packaging_cost ?? '' }, + { key: 'transportFee', label: t('message.pages.pissupplier.quotation.transportFee'), value: o.transportation_cost ?? '' } ] }) } for (const pr of item.profit_costs || []) { - const pid = pr.part_id ?? '' + const prtid = pr.part_id ?? '' out.push({ - id: `profit-${pr.autoid ?? pid}-p`, + id: `profit-${pr.autoid ?? prtid}-p`, section: '利润', attrs: [ - { key: 'part_id', label: '料号', value: pid }, - { key: 'profitRate', label: '利润率(%)', value: pr.profit_rate ?? '' }, - { key: 'profitTotal', label: '利润金额', value: '' } + { key: 'part_id', label: t('message.pages.pissupplier.quotation.partNo'), value: prtid }, + { key: 'profitRate', label: t('message.pages.pissupplier.quotation.profitRatePct'), value: pr.profit_rate ?? '' }, + { key: 'profitTotal', label: t('message.pages.pissupplier.quotation.profitAmount'), value: '' } ] }) out.push({ - id: `profit-${pr.autoid ?? pid}-t`, + id: `profit-${pr.autoid ?? prtid}-t`, section: '税金', attrs: [ - { key: 'part_id', label: '料号', value: pid }, - { key: 'taxRate', label: '税率(%)', value: pr.tax_rate ?? '' }, - { key: 'taxTotal', label: '税金金额', value: '' } + { key: 'part_id', label: t('message.pages.pissupplier.quotation.partNo'), value: prtid }, + { key: 'taxRate', label: t('message.pages.pissupplier.quotation.taxRatePct'), value: pr.tax_rate ?? '' }, + { key: 'taxTotal', label: t('message.pages.pissupplier.quotation.taxAmount'), value: '' } ] }) } @@ -1056,6 +1058,7 @@ export function useQuoteCrud(options?: { /** 详情页保存成功后(例如返回列表) */ onSaveSuccess?: () => void }) { + const { t } = useI18n() const uiContext = options?.uiContext ?? 'list' const filters = reactive<{ inquiryPlant: string @@ -1201,10 +1204,10 @@ export function useQuoteCrud(options?: { // 兼容后端/历史数据直接返回中文文案的情况 const labelToStatus: Record = { - 待报价: 'pending', - 报价中: 'quoted', - 已报价: 'completed', - 已过期: 'expired' + [t('message.pages.pissupplier.quotation.statusPending')]: 'pending', + [t('message.pages.pissupplier.quotation.statusQuoting')]: 'quoted', + [t('message.pages.pissupplier.quotation.statusCompleted')]: 'completed', + [t('message.pages.pissupplier.quotation.statusExpired')]: 'expired' } return labelToStatus[str] || 'pending' } @@ -1383,13 +1386,14 @@ export function useQuoteCrud(options?: { paymentTerm: normalizePayment(item.payment_method) }, costItems: hasQuotationNestedCosts(item) - ? costItemsFromQuotationApi(item) + ? costItemsFromQuotationApi(item, t) : normalizeCostItems(item.cost_items || item.costItems).length ? normalizeCostItems(item.cost_items || item.costItems) : buildCostItems( item.template || item.template_code || 'default', item.templateSections || item.template_sections || item.sections, - (item.enable_cost_structure ?? item.is_bom ?? item.isBom ?? item.template?.enable_cost_structure) !== false + (item.enable_cost_structure ?? item.is_bom ?? item.isBom ?? item.template?.enable_cost_structure) !== false, + t ), rfqItems: Array.isArray(item.rfq_items) ? item.rfq_items : [], attachments: normalizeQuotationAttachmentsForUpload(item.attachments), @@ -1909,9 +1913,9 @@ export function useQuoteCrud(options?: { // 询价附件必须以询价单子表为准(`Inquiry.attachments`),勿用报价单 `attachments` 回显 if (inquiry?.attachments?.length) { - quoteData.inquiryAttachments = mapInquiryAttachmentsFromInquiryApi(inquiry.attachments) + quoteData.inquiryAttachments = mapInquiryAttachmentsFromInquiryApi(inquiry.attachments, t) } else if (rawDetail?.inquiry_attachments?.length) { - quoteData.inquiryAttachments = mapInquiryAttachmentsFromInquiryApi(rawDetail.inquiry_attachments) + quoteData.inquiryAttachments = mapInquiryAttachmentsFromInquiryApi(rawDetail.inquiry_attachments, t) } quoteData.inquiryStatus = formatMiscInquiryStatus(quoteData) @@ -1974,7 +1978,7 @@ export function useQuoteCrud(options?: { merged.inquiryCompanyCode = icc } if (inv.attachments?.length) { - merged.inquiryAttachments = mapInquiryAttachmentsFromInquiryApi(inv.attachments) + merged.inquiryAttachments = mapInquiryAttachmentsFromInquiryApi(inv.attachments, t) } } } @@ -2097,7 +2101,7 @@ export function useQuoteCrud(options?: { const addCostRow = (section: string) => { const cols = mergeQuotationSectionColumns(section, current.templateSections) if (!cols.length) { - ElMessage.warning('该段未在成本模板中配置字段,无法新增行') + ElMessage.warning(t('message.pages.pissupplier.quotation.cannotAddRowNoFields')) return } const values: Record = {} @@ -2116,14 +2120,14 @@ export function useQuoteCrud(options?: { function saveQuote() { if (dialog.quoteId && !isQuotationEditable(current)) { - ElMessage.warning('仅未报价/报价中状态可保存') + ElMessage.warning(t('message.pages.pissupplier.quotation.onlyPendingCanSave')) return } const c = (current.base.contact || '').trim() const p = (current.base.phone || '').trim() const e = (current.base.email || '').trim() if (!c || !p || !e) { - ElMessage.warning('请填写完整【报价基础信息】后再保存。') + ElMessage.warning(t('message.pages.pissupplier.quotation.fillBasicInfoFirst')) return } ;(async () => { @@ -2138,10 +2142,10 @@ export function useQuoteCrud(options?: { if (!name && !path) continue if (!path) { if (f?.raw) { - ElMessage.error(`附件上传失败:${name || '未命名文件'},请检查网络后重试`) + ElMessage.error(t('message.pages.pissupplier.quotation.attachmentUploadFailed', { name: name || t('message.pages.pissupplier.quotation.unnamedFile') })) return } - ElMessage.error(`附件「${name || '未命名'}」缺少存储路径,请删除后重新上传`) + ElMessage.error(t('message.pages.pissupplier.quotation.attachmentMissingPath', { name: name || t('message.pages.pissupplier.quotation.unnamed') })) return } } @@ -2164,11 +2168,11 @@ export function useQuoteCrud(options?: { } else { dialog.visible = false } - ElMessage.success('已保存') + ElMessage.success(t('message.pages.pissupplier.quotation.saved')) options?.onChange?.() } catch (err) { console.error('保存报价失败', err) - ElMessage.error('保存失败,请检查网络或必填项后重试') + ElMessage.error(t('message.pages.pissupplier.quotation.saveFailed')) } finally { loading.value = false } @@ -2177,7 +2181,7 @@ export function useQuoteCrud(options?: { function submitQuotationFromRow(row: Quote) { if (!isQuotedQuotation(row)) { - ElMessage.warning('仅报价中状态可提交报价') + ElMessage.warning(t('message.pages.pissupplier.quotation.onlyQuotingCanSubmit')) return } const bidReason = getSupplierBidWindowRejectReason(row) @@ -2189,11 +2193,11 @@ export function useQuoteCrud(options?: { const p = (row.base?.phone || '').trim() const e = (row.base?.email || '').trim() if (!c || !p || !e) { - ElMessage.warning('请先将报价单中的【报价基础信息】填写完整后再提交报价。') + ElMessage.warning(t('message.pages.pissupplier.quotation.fillBasicInfoBeforeSubmit')) return } if (!row.id) { - ElMessage.warning('无法提交:缺少报价单标识') + ElMessage.warning(t('message.pages.pissupplier.quotation.cannotSubmitNoId')) return } ;(async () => { @@ -2209,12 +2213,12 @@ export function useQuoteCrud(options?: { else quotes.value.unshift(updated) const tip = res?.data?.msg || - (inquiryClosed ? '报价已提交,询价单已进入报价结束' : '报价已提交') + (inquiryClosed ? t('message.pages.pissupplier.quotation.submittedInquiryClosed') : t('message.pages.pissupplier.quotation.submitted')) ElMessage.success(tip) options?.onChange?.() } catch (err) { console.error('提交报价失败', err) - ElMessage.error('提交失败,请稍后重试') + ElMessage.error(t('message.pages.pissupplier.quotation.submitFailed')) } finally { loading.value = false } @@ -2361,10 +2365,10 @@ export function useQuoteCrud(options?: { return legacy[key] || key } const statusLabel = (s: QuoteStatus) => ({ - pending: '待报价', - quoted: '报价中', - completed: '已报价', - expired: '已过期' + pending: t('message.pages.pissupplier.quotation.statusPending'), + quoted: t('message.pages.pissupplier.quotation.statusQuoting'), + completed: t('message.pages.pissupplier.quotation.statusCompleted'), + expired: t('message.pages.pissupplier.quotation.statusExpired') }[s] || s) const statusTagType = (s: QuoteStatus) => ({ @@ -2376,7 +2380,7 @@ export function useQuoteCrud(options?: { return { filters, - statusOptions, + statusOptions: getStatusOptions(), resetFilter, filteredQuotes, loading, @@ -2387,7 +2391,7 @@ export function useQuoteCrud(options?: { viewQuote, openQuote, dialog, - dialogTitle: computed(() => (dialog.mode === 'view' ? '查看报价' : dialog.quoteId ? '编辑报价' : '新增报价')), + dialogTitle: computed(() => (dialog.mode === 'view' ? t('message.pages.pissupplier.quotation.viewQuote') : dialog.quoteId ? t('message.pages.pissupplier.quotation.editQuote') : t('message.pages.pissupplier.quotation.newQuote'))), statusTagType, statusLabel, templateLabel, @@ -2430,9 +2434,9 @@ export function useQuoteCrud(options?: { } } -const buildCostItems = (_templateKey: string, templateSections?: any, enableCostStructure = true): CostItem[] => { +const buildCostItems = (_templateKey: string, templateSections?: any, enableCostStructure = true, t?: Function): CostItem[] => { const fromTplSections = buildCostItemsFromTemplateSections(templateSections, enableCostStructure) if (fromTplSections.length) return fromTplSections - if (!enableCostStructure) return buildDefaultNonBomCostItems() + if (!enableCostStructure) return t ? buildDefaultNonBomCostItems(t) : [] return [] }