| | |
| | | "element-plus": "^2.11.2", |
| | | "file-saver": "^2.0.5", |
| | | "highlight.js": "^11.10.0", |
| | | "jsbarcode": "^3.12.3", |
| | | "mitt": "^3.0.1", |
| | | "nprogress": "^0.2.0", |
| | | "ohash": "^2.0.11", |
| | |
| | | highlight.js: |
| | | specifier: ^11.10.0 |
| | | version: 11.11.1 |
| | | jsbarcode: |
| | | specifier: ^3.12.3 |
| | | version: 3.12.3 |
| | | mitt: |
| | | specifier: ^3.0.1 |
| | | version: 3.0.1 |
| | |
| | | specifier: ^3.0.20 |
| | | version: 3.0.23(core-js@3.45.1) |
| | | xlsx: |
| | | specifier: ^0.18.5 |
| | | version: 0.18.5 |
| | | specifier: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz |
| | | version: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz |
| | | devDependencies: |
| | | '@eslint/js': |
| | | specifier: ^9.9.1 |
| | |
| | | engines: {node: '>=0.4.0'} |
| | | hasBin: true |
| | | |
| | | adler-32@1.3.1: |
| | | resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} |
| | | engines: {node: '>=0.8'} |
| | | |
| | | ajv@6.12.6: |
| | | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} |
| | | |
| | |
| | | caniuse-lite@1.0.30001745: |
| | | resolution: {integrity: sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==} |
| | | |
| | | cfb@1.2.2: |
| | | resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} |
| | | engines: {node: '>=0.8'} |
| | | |
| | | chalk@4.1.2: |
| | | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} |
| | | engines: {node: '>=10'} |
| | |
| | | cliui@8.0.1: |
| | | resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} |
| | | engines: {node: '>=12'} |
| | | |
| | | codepage@1.15.0: |
| | | resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} |
| | | engines: {node: '>=0.8'} |
| | | |
| | | color-convert@2.0.1: |
| | | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} |
| | |
| | | peerDependenciesMeta: |
| | | typescript: |
| | | optional: true |
| | | |
| | | crc-32@1.2.2: |
| | | resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} |
| | | engines: {node: '>=0.8'} |
| | | hasBin: true |
| | | |
| | | cross-spawn@7.0.6: |
| | | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} |
| | |
| | | resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} |
| | | engines: {node: '>= 6'} |
| | | |
| | | frac@1.1.2: |
| | | resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} |
| | | engines: {node: '>=0.8'} |
| | | |
| | | fs-extra@10.1.0: |
| | | resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} |
| | | engines: {node: '>=12'} |
| | |
| | | js-yaml@4.1.0: |
| | | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} |
| | | hasBin: true |
| | | |
| | | jsbarcode@3.12.3: |
| | | resolution: {integrity: sha512-CuHU9hC6dPsHF5oVFMo8NW76uQVjH4L22CsP4hW+dNnGywJHC/B0ThA1CTDVLnxKLrrpYdicBLnd2xsgTfRnvg==} |
| | | |
| | | jsesc@3.1.0: |
| | | resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} |
| | |
| | | resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} |
| | | engines: {node: '>=0.10.0'} |
| | | |
| | | ssf@0.11.2: |
| | | resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} |
| | | engines: {node: '>=0.8'} |
| | | |
| | | ssr-window@3.0.0: |
| | | resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==} |
| | | |
| | |
| | | wildcard@1.1.2: |
| | | resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==} |
| | | |
| | | wmf@1.0.2: |
| | | resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} |
| | | engines: {node: '>=0.8'} |
| | | |
| | | word-wrap@1.2.5: |
| | | resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} |
| | | engines: {node: '>=0.10.0'} |
| | | |
| | | word@0.3.0: |
| | | resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} |
| | | engines: {node: '>=0.8'} |
| | | |
| | | wrap-ansi@7.0.0: |
| | | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} |
| | |
| | | peerDependencies: |
| | | core-js: '>=3.12.1' |
| | | |
| | | xlsx@0.18.5: |
| | | resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} |
| | | xlsx@https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz: |
| | | resolution: {integrity: sha512-+nKZ39+nvK7Qq6i0PvWWRA4j/EkfWOtkP/YhMtupm+lJIiHxUrgTr1CcKv1nBk1rHtkRRQ3O2+Ih/q/sA+FXZA==, tarball: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz} |
| | | version: 0.20.2 |
| | | engines: {node: '>=0.8'} |
| | | hasBin: true |
| | | |
| | |
| | | |
| | | acorn@8.15.0: {} |
| | | |
| | | adler-32@1.3.1: {} |
| | | |
| | | ajv@6.12.6: |
| | | dependencies: |
| | | fast-deep-equal: 3.1.3 |
| | |
| | | |
| | | caniuse-lite@1.0.30001745: {} |
| | | |
| | | cfb@1.2.2: |
| | | dependencies: |
| | | adler-32: 1.3.1 |
| | | crc-32: 1.2.2 |
| | | |
| | | chalk@4.1.2: |
| | | dependencies: |
| | | ansi-styles: 4.3.0 |
| | |
| | | string-width: 4.2.3 |
| | | strip-ansi: 6.0.1 |
| | | wrap-ansi: 7.0.0 |
| | | |
| | | codepage@1.15.0: {} |
| | | |
| | | color-convert@2.0.1: |
| | | dependencies: |
| | |
| | | parse-json: 5.2.0 |
| | | optionalDependencies: |
| | | typescript: 5.6.3 |
| | | |
| | | crc-32@1.2.2: {} |
| | | |
| | | cross-spawn@7.0.6: |
| | | dependencies: |
| | |
| | | hasown: 2.0.2 |
| | | mime-types: 2.1.35 |
| | | |
| | | frac@1.1.2: {} |
| | | |
| | | fs-extra@10.1.0: |
| | | dependencies: |
| | | graceful-fs: 4.2.11 |
| | |
| | | js-yaml@4.1.0: |
| | | dependencies: |
| | | argparse: 2.0.1 |
| | | |
| | | jsbarcode@3.12.3: {} |
| | | |
| | | jsesc@3.1.0: {} |
| | | |
| | |
| | | |
| | | speakingurl@14.0.1: {} |
| | | |
| | | ssf@0.11.2: |
| | | dependencies: |
| | | frac: 1.1.2 |
| | | |
| | | ssr-window@3.0.0: {} |
| | | |
| | | string-width@4.2.3: |
| | |
| | | |
| | | wildcard@1.1.2: {} |
| | | |
| | | wmf@1.0.2: {} |
| | | |
| | | word-wrap@1.2.5: {} |
| | | |
| | | word@0.3.0: {} |
| | | |
| | | wrap-ansi@7.0.0: |
| | | dependencies: |
| | |
| | | eventemitter3: 4.0.7 |
| | | xgplayer-subtitles: 3.0.23(core-js@3.45.1) |
| | | |
| | | xlsx@0.18.5: |
| | | dependencies: |
| | | adler-32: 1.3.1 |
| | | cfb: 1.2.2 |
| | | codepage: 1.15.0 |
| | | crc-32: 1.2.2 |
| | | ssf: 0.11.2 |
| | | wmf: 1.0.2 |
| | | word: 0.3.0 |
| | | xlsx@https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz: {} |
| | | |
| | | xml-name-validator@4.0.0: {} |
| | | |
| | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | export function fetchMatnrPrintTemplateList(params = {}) { |
| | | return request.post({ |
| | | url: '/matnrPrintTemplate/list', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | export function fetchGetMatnrPrintTemplateDetail(id) { |
| | | return request.get({ |
| | | url: `/matnrPrintTemplate/${id}` |
| | | }) |
| | | } |
| | | |
| | | export function fetchGetDefaultMatnrPrintTemplate() { |
| | | return request.get({ |
| | | url: '/matnrPrintTemplate/default' |
| | | }) |
| | | } |
| | | |
| | | export function fetchSaveMatnrPrintTemplate(params = {}) { |
| | | return request.post({ |
| | | url: '/matnrPrintTemplate/save', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | export function fetchUpdateMatnrPrintTemplate(params = {}) { |
| | | return request.post({ |
| | | url: '/matnrPrintTemplate/update', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | export function fetchRemoveMatnrPrintTemplate(ids) { |
| | | return request.post({ |
| | | url: `/matnrPrintTemplate/remove/${normalizeIds(ids)}` |
| | | }) |
| | | } |
| | | |
| | | export function fetchSetDefaultMatnrPrintTemplate(id) { |
| | | return request.post({ |
| | | url: `/matnrPrintTemplate/default/${id}` |
| | | }) |
| | | } |
| | |
| | | "customer": "Customer", |
| | | "shipper": "shipper", |
| | | "matnr": "Matnr", |
| | | "matnrPrintTemplate": "Material Print Templates", |
| | | "matnrGroup": "MatnrGroup", |
| | | "warehouse": "Warehouse", |
| | | "warehouseAreas": "WarehouseAreas", |
| | |
| | | "groupId": "Please select material group" |
| | | } |
| | | }, |
| | | "printTemplate": { |
| | | "page": { |
| | | "messages": { |
| | | "enabledFieldsTimeout": "Dynamic fields loading timed out", |
| | | "previewTimeout": "Sample material loading timed out", |
| | | "initFailed": "Failed to initialize the print template page" |
| | | } |
| | | }, |
| | | "workspace": { |
| | | "sidebarTitle": "Template List", |
| | | "sidebarSubtitle": "Tenant-shared multi-template management", |
| | | "defaultTag": "Default", |
| | | "unnamedTemplate": "Untitled Template", |
| | | "previewRecord": "Preview Record: {name}", |
| | | "noPreviewRecord": "No sample data", |
| | | "emptyTemplate": "No templates yet. Please create one first.", |
| | | "actions": { |
| | | "copy": "Copy", |
| | | "setDefault": "Set Default", |
| | | "saveTemplate": "Save Template", |
| | | "delete": "Delete" |
| | | }, |
| | | "messages": { |
| | | "loadFailed": "Failed to load templates", |
| | | "deleteConfirm": "Delete template \"{name}\"?", |
| | | "deleteSuccess": "Template deleted", |
| | | "deleteFailed": "Failed to delete template", |
| | | "defaultSuccess": "Default template updated", |
| | | "defaultFailed": "Failed to set default template", |
| | | "saveSuccess": "Template saved", |
| | | "saveFailed": "Failed to save template" |
| | | } |
| | | }, |
| | | "toolbar": { |
| | | "zoomOut": "Zoom Out", |
| | | "zoomIn": "Zoom In", |
| | | "zoomFit": "Auto Fit", |
| | | "elements": { |
| | | "text": "Text", |
| | | "barcode": "Barcode", |
| | | "qrcode": "QR Code", |
| | | "line": "Line", |
| | | "rect": "Rectangle", |
| | | "table": "Field Table" |
| | | } |
| | | }, |
| | | "fieldPanel": { |
| | | "title": "Available Fields", |
| | | "fixedCategory": "Fixed Fields", |
| | | "extendCategory": "Extended Fields" |
| | | }, |
| | | "propertyPanel": { |
| | | "sections": { |
| | | "templateInfo": "Template Info", |
| | | "canvas": "Canvas Settings", |
| | | "element": "Element Properties", |
| | | "fieldList": "Field List" |
| | | }, |
| | | "fields": { |
| | | "templateName": "Template Name", |
| | | "templateCode": "Template Code", |
| | | "defaultTemplate": "Default Template", |
| | | "widthMm": "Width (mm)", |
| | | "heightMm": "Height (mm)", |
| | | "backgroundColor": "Background Color", |
| | | "gridSizeMm": "Grid (mm)", |
| | | "elementType": "Element Type", |
| | | "visible": "Visible", |
| | | "zIndex": "Z-Index", |
| | | "contentMode": "Content Mode", |
| | | "textContent": "Text Content", |
| | | "fontSizeMm": "Font Size (mm)", |
| | | "fontWeight": "Font Weight", |
| | | "textAlign": "Alignment", |
| | | "textColor": "Text Color", |
| | | "valueTemplate": "Value Template", |
| | | "symbology": "Symbology", |
| | | "showText": "Show Text", |
| | | "direction": "Direction", |
| | | "lineWidthMm": "Line Width (mm)", |
| | | "lineColor": "Line Color", |
| | | "borderWidthMm": "Border Width (mm)", |
| | | "radiusMm": "Radius (mm)", |
| | | "borderColor": "Border Color", |
| | | "fillColor": "Fill Color", |
| | | "leftMode": "Left Column Mode", |
| | | "rightMode": "Right Column Mode", |
| | | "rowHeightMm": "Row Height (mm)", |
| | | "leftContent": "Left Content", |
| | | "rightContent": "Right Content" |
| | | }, |
| | | "options": { |
| | | "staticText": "Static Text", |
| | | "templateText": "Template Placeholder", |
| | | "template": "Template", |
| | | "alignLeft": "Left", |
| | | "alignCenter": "Center", |
| | | "alignRight": "Right", |
| | | "horizontal": "Horizontal", |
| | | "vertical": "Vertical" |
| | | }, |
| | | "actions": { |
| | | "removeElement": "Delete Element", |
| | | "addRow": "Add Row" |
| | | }, |
| | | "empty": "Select an element on the canvas to edit its position, size, and content here.", |
| | | "rowTitle": "Row {index}", |
| | | "defaultFieldLabel": "Field Label" |
| | | }, |
| | | "dialog": { |
| | | "title": "Print", |
| | | "templatePlaceholder": "Please select a template", |
| | | "copyCount": "Copies", |
| | | "labelCount": "{count} labels", |
| | | "noTemplate": "No print templates are available for the current tenant. Please create one from the left-side menu \"Material Print Templates\" first.", |
| | | "noData": "No printable data", |
| | | "canvasOverflow": "Some elements extend beyond the canvas bounds", |
| | | "canvasOverflowDescription": "Printing still uses the template canvas size as the source of truth. Anything outside the canvas will be clipped, so please adjust the layout in the template editor first.", |
| | | "confirmPrint": "Print", |
| | | "printDocumentTitle": "Material Print", |
| | | "messages": { |
| | | "loadFailed": "Failed to load templates", |
| | | "detailFailed": "Failed to load template details", |
| | | "popupBlocked": "The browser blocked the print window. Please check the popup settings." |
| | | } |
| | | }, |
| | | "helpers": { |
| | | "defaultTemplateName": "Material Label Template", |
| | | "copySuffix": " - Copy", |
| | | "defaultText": { |
| | | "productCode": "Product Code", |
| | | "productName": "Product", |
| | | "staticText": "Static Text" |
| | | }, |
| | | "tableLabels": { |
| | | "code": "Material Code", |
| | | "name": "Material Name", |
| | | "spec": "Specification", |
| | | "model": "Model" |
| | | }, |
| | | "fixedFields": { |
| | | "id": "Material ID", |
| | | "code": "Material Code", |
| | | "name": "Material Name", |
| | | "groupName": "Material Group", |
| | | "barcode": "Barcode", |
| | | "spec": "Specification", |
| | | "model": "Model", |
| | | "color": "Color", |
| | | "size": "Size", |
| | | "unit": "Unit", |
| | | "purUnit": "Purchase Unit", |
| | | "stockUnit": "Stock Unit", |
| | | "stockLevelText": "Stock Level", |
| | | "flagLabelManageText": "Label Management", |
| | | "flagCheckText": "Exempt Inspection", |
| | | "statusText": "Status", |
| | | "describle": "Description", |
| | | "memo": "Memo", |
| | | "createByText": "Created By", |
| | | "createTimeText": "Created At", |
| | | "updateByText": "Updated By", |
| | | "updateTimeText": "Updated At" |
| | | } |
| | | } |
| | | }, |
| | | "batchDialog": { |
| | | "titles": { |
| | | "status": "Batch Update Status", |
| | |
| | | "customer": "客户表", |
| | | "shipper": "货主信息", |
| | | "matnr": "物料", |
| | | "matnrPrintTemplate": "物料打印模板", |
| | | "matnrGroup": "物料分组", |
| | | "warehouse": "仓库", |
| | | "warehouseAreas": "库区", |
| | |
| | | "groupId": "请选择物料分组" |
| | | } |
| | | }, |
| | | "printTemplate": { |
| | | "page": { |
| | | "messages": { |
| | | "enabledFieldsTimeout": "扩展字段加载超时", |
| | | "previewTimeout": "物料示例数据加载超时", |
| | | "initFailed": "打印模板页面初始化失败" |
| | | } |
| | | }, |
| | | "workspace": { |
| | | "sidebarTitle": "模板列表", |
| | | "sidebarSubtitle": "租户共享,多模板管理", |
| | | "defaultTag": "默认", |
| | | "unnamedTemplate": "未命名模板", |
| | | "previewRecord": "预览记录:{name}", |
| | | "noPreviewRecord": "暂无示例数据", |
| | | "emptyTemplate": "暂无模板,请先新增模板", |
| | | "actions": { |
| | | "copy": "复制", |
| | | "setDefault": "设为默认", |
| | | "saveTemplate": "保存模板", |
| | | "delete": "删除" |
| | | }, |
| | | "messages": { |
| | | "loadFailed": "模板加载失败", |
| | | "deleteConfirm": "确认删除模板「{name}」吗?", |
| | | "deleteSuccess": "模板已删除", |
| | | "deleteFailed": "模板删除失败", |
| | | "defaultSuccess": "默认模板已更新", |
| | | "defaultFailed": "默认模板设置失败", |
| | | "saveSuccess": "模板保存成功", |
| | | "saveFailed": "模板保存失败" |
| | | } |
| | | }, |
| | | "toolbar": { |
| | | "zoomOut": "缩小", |
| | | "zoomIn": "放大", |
| | | "zoomFit": "自适应", |
| | | "elements": { |
| | | "text": "文本", |
| | | "barcode": "一维码", |
| | | "qrcode": "二维码", |
| | | "line": "直线", |
| | | "rect": "矩形", |
| | | "table": "字段表格" |
| | | } |
| | | }, |
| | | "fieldPanel": { |
| | | "title": "可用字段", |
| | | "fixedCategory": "固定字段", |
| | | "extendCategory": "扩展字段" |
| | | }, |
| | | "propertyPanel": { |
| | | "sections": { |
| | | "templateInfo": "模板信息", |
| | | "canvas": "画布设置", |
| | | "element": "元素属性", |
| | | "fieldList": "字段清单" |
| | | }, |
| | | "fields": { |
| | | "templateName": "模板名称", |
| | | "templateCode": "模板编码", |
| | | "defaultTemplate": "默认模板", |
| | | "widthMm": "宽度(mm)", |
| | | "heightMm": "高度(mm)", |
| | | "backgroundColor": "背景色", |
| | | "gridSizeMm": "网格(mm)", |
| | | "elementType": "元素类型", |
| | | "visible": "显示", |
| | | "zIndex": "层级", |
| | | "contentMode": "内容模式", |
| | | "textContent": "文本内容", |
| | | "fontSizeMm": "字号(mm)", |
| | | "fontWeight": "字重", |
| | | "textAlign": "对齐", |
| | | "textColor": "文字颜色", |
| | | "valueTemplate": "码值模板", |
| | | "symbology": "编码制式", |
| | | "showText": "显示文字", |
| | | "direction": "方向", |
| | | "lineWidthMm": "粗细(mm)", |
| | | "lineColor": "线条颜色", |
| | | "borderWidthMm": "边框宽度(mm)", |
| | | "radiusMm": "圆角(mm)", |
| | | "borderColor": "边框颜色", |
| | | "fillColor": "填充颜色", |
| | | "leftMode": "左列模式", |
| | | "rightMode": "右列模式", |
| | | "rowHeightMm": "行高(mm)", |
| | | "leftContent": "左列内容", |
| | | "rightContent": "右列内容" |
| | | }, |
| | | "options": { |
| | | "staticText": "静态文本", |
| | | "templateText": "占位符模板", |
| | | "template": "模板", |
| | | "alignLeft": "左对齐", |
| | | "alignCenter": "居中", |
| | | "alignRight": "右对齐", |
| | | "horizontal": "横线", |
| | | "vertical": "竖线" |
| | | }, |
| | | "actions": { |
| | | "removeElement": "删除元素", |
| | | "addRow": "新增一行" |
| | | }, |
| | | "empty": "选择画布中的元素后,在这里编辑位置、尺寸和字段内容。", |
| | | "rowTitle": "第 {index} 行", |
| | | "defaultFieldLabel": "字段名" |
| | | }, |
| | | "dialog": { |
| | | "title": "打印", |
| | | "templatePlaceholder": "请选择模板", |
| | | "copyCount": "打印份数", |
| | | "labelCount": "共 {count} 条标签", |
| | | "noTemplate": "当前租户还没有打印模板,请先到左侧菜单“物料打印模板”里创建。", |
| | | "noData": "暂无可打印数据", |
| | | "canvasOverflow": "当前模板有元素超出了画布边界", |
| | | "canvasOverflowDescription": "打印仍会严格按模板画布尺寸输出,超出部分会被裁切,请先回到模板页调整布局。", |
| | | "confirmPrint": "确认打印", |
| | | "printDocumentTitle": "物料打印", |
| | | "messages": { |
| | | "loadFailed": "模板加载失败", |
| | | "detailFailed": "模板详情加载失败", |
| | | "popupBlocked": "浏览器阻止了打印窗口,请检查弹窗设置" |
| | | } |
| | | }, |
| | | "helpers": { |
| | | "defaultTemplateName": "物料标签模板", |
| | | "copySuffix": "-副本", |
| | | "defaultText": { |
| | | "productCode": "商品编码", |
| | | "productName": "商品", |
| | | "staticText": "静态文本" |
| | | }, |
| | | "tableLabels": { |
| | | "code": "物料编码", |
| | | "name": "物料名称", |
| | | "spec": "规格", |
| | | "model": "型号" |
| | | }, |
| | | "fixedFields": { |
| | | "id": "物料ID", |
| | | "code": "物料编码", |
| | | "name": "物料名称", |
| | | "groupName": "物料分组", |
| | | "barcode": "条码", |
| | | "spec": "规格", |
| | | "model": "型号", |
| | | "color": "颜色", |
| | | "size": "尺寸", |
| | | "unit": "单位", |
| | | "purUnit": "采购单位", |
| | | "stockUnit": "库位单位", |
| | | "stockLevelText": "库存等级", |
| | | "flagLabelManageText": "标签管理", |
| | | "flagCheckText": "免检", |
| | | "statusText": "状态", |
| | | "describle": "描述", |
| | | "memo": "备注", |
| | | "createByText": "创建人", |
| | | "createTimeText": "创建时间", |
| | | "updateByText": "更新人", |
| | | "updateTimeText": "更新时间" |
| | | } |
| | | } |
| | | }, |
| | | "batchDialog": { |
| | | "titles": { |
| | | "status": "批量修改状态", |
| | |
| | | fieldsItem: '/system/fields-item', |
| | | whMat: '/basic-info/wh-mat', |
| | | matnr: '/basic-info/wh-mat', |
| | | matnrPrintTemplate: '/basic-info/matnr-print-template', |
| | | matnrGroup: '/basic-info/matnr-group', |
| | | locType: '/basic-info/loc-type', |
| | | taskPathTemplate: '/basic-info/task-path-template', |
| | |
| | | return null |
| | | } |
| | | |
| | | const path = resolvePath(node, { component, isFirstLevel, hasChildren, rawRoute }) |
| | | const path = resolvePath(node, { component, isFirstLevel, rawRoute }) |
| | | const meta = buildMeta(node) |
| | | const adapted = { |
| | | id: normalizeId(node.id), |
| | |
| | | return PHASE_1_COMPONENTS[normalizedKey] || normalizeComponentPath(fullRoutePath) |
| | | } |
| | | |
| | | function resolvePath(node, { component, isFirstLevel, hasChildren, rawRoute }) { |
| | | function resolvePath(node, { component, isFirstLevel, rawRoute }) { |
| | | if (rawRoute) { |
| | | if (!isFirstLevel && rawRoute.startsWith('/')) { |
| | | return normalizeComponentPath(rawRoute) |
| | |
| | | 'menu.locType': '库位类型(废)', |
| | | 'menu.logs': '日志', |
| | | 'menu.matnr': '物料', |
| | | 'menu.matnrPrintTemplate': '物料打印模板', |
| | | 'menu.matnrGroup': '物料分组', |
| | | 'menu.matnrRoleMenu': '物料权限', |
| | | 'menu.menu': '菜单管理', |
| | |
| | | return accumulator |
| | | }, |
| | | { |
| | | '仪表盘': 'menus.dashboard.title', |
| | | '工作台': 'menus.dashboard.console', |
| | | 仪表盘: 'menus.dashboard.title', |
| | | 工作台: 'menus.dashboard.console', |
| | | 'menus.dashboard.title': 'menus.dashboard.title', |
| | | 'menus.dashboard.console': 'menus.dashboard.console' |
| | | } |
| New file |
| | |
| | | <template> |
| | | <div class="matnr-print-template-page art-full-height"> |
| | | <WhMatPrintTemplateWorkspace |
| | | :enabled-fields="enabledFields" |
| | | :preview-record="previewRecord" |
| | | :auto-load="true" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref } from 'vue' |
| | | import { useI18n } from 'vue-i18n' |
| | | import { ElMessage } from 'element-plus' |
| | | import { fetchEnabledFields, fetchMatnrPage } from '@/api/wh-mat' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { |
| | | buildMatnrPageQueryParams, |
| | | normalizeMatnrRow, |
| | | normalizeWhMatEnabledFields |
| | | } from '../wh-mat/whMatPage.helpers' |
| | | import WhMatPrintTemplateWorkspace from '../wh-mat/modules/wh-mat-print-template-workspace.vue' |
| | | |
| | | defineOptions({ name: 'MatnrPrintTemplate' }) |
| | | |
| | | const { t } = useI18n() |
| | | const enabledFields = ref([]) |
| | | const previewRecord = ref({}) |
| | | |
| | | async function loadEnabledFields() { |
| | | const fields = await guardRequestWithMessage(fetchEnabledFields(), [], { |
| | | timeoutMessage: t('pages.basicInfo.whMat.printTemplate.page.messages.enabledFieldsTimeout') |
| | | }) |
| | | enabledFields.value = normalizeWhMatEnabledFields(fields) |
| | | } |
| | | |
| | | async function loadPreviewRecord() { |
| | | const response = await guardRequestWithMessage( |
| | | fetchMatnrPage( |
| | | buildMatnrPageQueryParams({ |
| | | current: 1, |
| | | pageSize: 1, |
| | | orderBy: 'update_time desc' |
| | | }) |
| | | ), |
| | | { |
| | | records: [] |
| | | }, |
| | | { |
| | | timeoutMessage: t('pages.basicInfo.whMat.printTemplate.page.messages.previewTimeout') |
| | | } |
| | | ) |
| | | |
| | | const firstRecord = Array.isArray(response?.records) ? response.records[0] : null |
| | | previewRecord.value = firstRecord |
| | | ? normalizeMatnrRow(firstRecord, () => '', enabledFields.value) |
| | | : {} |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | await loadEnabledFields() |
| | | await loadPreviewRecord() |
| | | } catch (error) { |
| | | ElMessage.error( |
| | | error?.message || t('pages.basicInfo.whMat.printTemplate.page.messages.initFailed') |
| | | ) |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .matnr-print-template-page { |
| | | height: calc(var(--art-full-height) - 40px); |
| | | max-height: calc(var(--art-full-height) - 40px); |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | @media (max-width: 640px) { |
| | | .matnr-print-template-page { |
| | | height: auto; |
| | | max-height: none; |
| | | overflow: auto; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <ElButton :loading="templateDownloading" @click="handleDownloadTemplate" v-ripple> |
| | | {{ t('pages.basicInfo.whMat.actions.downloadTemplate') }} |
| | | </ElButton> |
| | | <ListExportPrint |
| | | class="inline-flex" |
| | | :preview-visible="previewVisible" |
| | | @update:previewVisible="handlePreviewVisibleChange" |
| | | :report-title="reportTitle" |
| | | :selected-rows="selectedRows" |
| | | :query-params="reportQueryParams" |
| | | :columns="columns" |
| | | :preview-rows="previewRows" |
| | | :preview-meta="resolvedPreviewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading" |
| | | @export="handleExport" |
| | | @print="handlePrint" |
| | | /> |
| | | <ElButton :loading="exporting" :disabled="loading" @click="handleExport" v-ripple> |
| | | {{ t('common.actions.export') }} |
| | | </ElButton> |
| | | <ElButton |
| | | v-auth="'manager:matnrPrintTemplate:list'" |
| | | :loading="printLoading" |
| | | :disabled="loading || selectedRows.length === 0" |
| | | @click="handlePrint()" |
| | | v-ripple |
| | | > |
| | | {{ t('common.actions.print') }} |
| | | </ElButton> |
| | | </ElSpace> |
| | | </template> |
| | | </ArtTableHeader> |
| | |
| | | :loading="detailLoading" |
| | | :detail="detailData" |
| | | /> |
| | | |
| | | <WhMatPrintDialog v-model:visible="printDialogVisible" :rows="printRows" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useI18n } from 'vue-i18n' |
| | | import ListExportPrint from '@/components/biz/list-export-print/index.vue' |
| | | import { buildListExportPayload } from '@/components/biz/list-export-print/list-export-print.helpers.js' |
| | | import { useAuth } from '@/hooks/core/useAuth' |
| | | import { useTableColumns } from '@/hooks/core/useTableColumns' |
| | | import { useUserStore } from '@/store/modules/user' |
| | |
| | | import { defaultResponseAdapter } from '@/utils/table/tableUtils' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { useCrudPage } from '@/views/system/common/useCrudPage' |
| | | import { usePrintExportPage } from '@/views/system/common/usePrintExportPage' |
| | | import { |
| | | fetchBatchUpdateMatnr, |
| | | fetchBindMatnrGroup, |
| | |
| | | import WhMatBindLocDialog from './modules/wh-mat-bind-loc-dialog.vue' |
| | | import WhMatDialog from './modules/wh-mat-dialog.vue' |
| | | import WhMatDetailDrawer from './modules/wh-mat-detail-drawer.vue' |
| | | import WhMatPrintDialog from './modules/wh-mat-print-dialog.vue' |
| | | import { createWhMatTableColumns } from './whMatTable.columns' |
| | | import { |
| | | WH_MAT_REPORT_STYLE, |
| | |
| | | buildMatnrGroupTreeQueryParams, |
| | | buildMatnrPageQueryParams, |
| | | buildWhMatDialogModel, |
| | | buildWhMatPrintRows, |
| | | buildWhMatReportMeta, |
| | | buildWhMatSavePayload, |
| | | createWhMatSearchState, |
| | | getWhMatDynamicFieldKey, |
| | |
| | | const { t } = useI18n() |
| | | const { hasAuth } = useAuth() |
| | | const userStore = useUserStore() |
| | | const canPrintMatnr = hasAuth('manager:matnrPrintTemplate:list') |
| | | |
| | | const showBatchActionButtons = false |
| | | const loading = ref(false) |
| | |
| | | const bindLocDialogVisible = ref(false) |
| | | const bindLocOptionsLoading = ref(false) |
| | | const importing = ref(false) |
| | | const exporting = ref(false) |
| | | const printLoading = ref(false) |
| | | const printDialogVisible = ref(false) |
| | | const templateDownloading = ref(false) |
| | | const tableData = ref([]) |
| | | const groupTreeData = ref([]) |
| | | const detailData = ref({}) |
| | | const printRows = ref([]) |
| | | const enabledFields = ref([]) |
| | | const serialRuleOptions = ref([]) |
| | | const areaOptions = ref([]) |
| | |
| | | handleViewDetail: openDetailDrawer, |
| | | handleEdit: hasAuth('update') ? openEditDialog : null, |
| | | handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null, |
| | | handlePrint: (row) => handlePrint({ ids: [row.id] }), |
| | | handlePrint: canPrintMatnr ? (row) => handlePrint({ ids: [row.id] }) : null, |
| | | canEdit: hasAuth('update'), |
| | | canDelete: hasAuth('delete'), |
| | | t |
| | |
| | | } |
| | | } |
| | | |
| | | const resolvePrintRecords = async (payload) => { |
| | | if (Array.isArray(payload?.ids) && payload.ids.length > 0) { |
| | | return defaultResponseAdapter(await fetchGetMatnrMany(payload.ids)).records |
| | | } |
| | | return tableData.value |
| | | } |
| | | |
| | | const { |
| | | previewVisible, |
| | | previewRows, |
| | | previewMeta, |
| | | handlePreviewVisibleChange, |
| | | handleExport, |
| | | handlePrint |
| | | } = usePrintExportPage({ |
| | | downloadFileName: 'matnr.xlsx', |
| | | requestExport: (payload) => |
| | | fetchExportMatnrReport(payload, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }), |
| | | resolvePrintRecords, |
| | | buildPreviewRows: (records) => buildWhMatPrintRows(records, t), |
| | | buildPreviewMeta |
| | | }) |
| | | |
| | | const resolvedPreviewMeta = computed(() => |
| | | buildWhMatReportMeta({ |
| | | previewMeta: previewMeta.value, |
| | | count: previewRows.value.length, |
| | | orientation: previewMeta.value?.reportStyle?.orientation || WH_MAT_REPORT_STYLE.orientation |
| | | }) |
| | | ) |
| | | |
| | | async function downloadFile(response, fallbackName) { |
| | | if (!response?.ok) { |
| | | throw new Error( |
| | |
| | | } |
| | | } |
| | | |
| | | async function handleExport() { |
| | | exporting.value = true |
| | | try { |
| | | const payload = buildListExportPayload({ |
| | | reportTitle, |
| | | selectedRows: selectedRows.value, |
| | | queryParams: reportQueryParams.value, |
| | | columns, |
| | | meta: buildPreviewMeta(tableData.value) |
| | | }) |
| | | const response = await guardRequestWithMessage( |
| | | fetchExportMatnrReport(payload, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }), |
| | | null, |
| | | { |
| | | timeoutMessage: t('message.exportTimeoutStopped') |
| | | } |
| | | ) |
| | | if (!response) { |
| | | return |
| | | } |
| | | await downloadFile(response, 'matnr.xlsx') |
| | | ElMessage.success(t('crud.messages.exportSuccess')) |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('crud.messages.exportFailed')) |
| | | } finally { |
| | | exporting.value = false |
| | | } |
| | | } |
| | | |
| | | async function handlePrint(payload = {}) { |
| | | const ids = |
| | | Array.isArray(payload?.ids) && payload.ids.length > 0 ? payload.ids : getSelectedIds() |
| | | |
| | | if (!ids.length) { |
| | | ElMessage.warning(t('pages.basicInfo.whMat.messages.selectAtLeastOne')) |
| | | return |
| | | } |
| | | |
| | | printLoading.value = true |
| | | try { |
| | | const records = await guardRequestWithMessage(fetchGetMatnrMany(ids), [], { |
| | | timeoutMessage: t('message.printTimeoutStopped') |
| | | }) |
| | | const normalizedRecords = defaultResponseAdapter(records).records |
| | | if (!normalizedRecords.length) { |
| | | ElMessage.warning(t('print.noData')) |
| | | return |
| | | } |
| | | printRows.value = normalizedRecords.map((record) => |
| | | normalizeMatnrRow(record, t, enabledFields.value) |
| | | ) |
| | | printDialogVisible.value = true |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('crud.messages.printFailed')) |
| | | } finally { |
| | | printLoading.value = false |
| | | } |
| | | } |
| | | |
| | | async function handleDownloadTemplate() { |
| | | templateDownloading.value = true |
| | | try { |
| New file |
| | |
| | | import JsBarcode from 'jsbarcode' |
| | | import { $t } from '@/locales' |
| | | |
| | | const MM_UNIT = 'mm' |
| | | const DEFAULT_CANVAS = { |
| | | width: 70, |
| | | height: 40, |
| | | unit: MM_UNIT, |
| | | backgroundColor: '#FFFFFF', |
| | | gridSize: 1 |
| | | } |
| | | |
| | | const TEXT_STYLE_DEFAULTS = { |
| | | fontSize: 3.2, |
| | | fontWeight: 400, |
| | | textAlign: 'left', |
| | | color: '#111111', |
| | | lineHeight: 1.25 |
| | | } |
| | | |
| | | const TABLE_STYLE_DEFAULTS = { |
| | | fontSize: 2.8, |
| | | fontWeight: 400, |
| | | textAlign: 'left', |
| | | color: '#111111', |
| | | borderColor: '#111111', |
| | | borderWidth: 0.2, |
| | | backgroundColor: '#FFFFFF' |
| | | } |
| | | |
| | | const FIXED_FIELD_OPTIONS = [ |
| | | 'id', |
| | | 'code', |
| | | 'name', |
| | | 'groupName', |
| | | 'barcode', |
| | | 'spec', |
| | | 'model', |
| | | 'color', |
| | | 'size', |
| | | 'unit', |
| | | 'purUnit', |
| | | 'stockUnit', |
| | | 'stockLevelText', |
| | | 'flagLabelManageText', |
| | | 'flagCheckText', |
| | | 'statusText', |
| | | 'describle', |
| | | 'memo', |
| | | 'createByText', |
| | | 'createTimeText', |
| | | 'updateByText', |
| | | 'updateTimeText' |
| | | ] |
| | | |
| | | function cloneDeep(value) { |
| | | return JSON.parse(JSON.stringify(value ?? null)) |
| | | } |
| | | |
| | | function normalizeText(value) { |
| | | return String(value ?? '').trim() |
| | | } |
| | | |
| | | function normalizeNumber(value, fallback = 0) { |
| | | const nextValue = Number(value) |
| | | return Number.isFinite(nextValue) ? nextValue : fallback |
| | | } |
| | | |
| | | function normalizeInteger(value, fallback = 0) { |
| | | return Math.round(normalizeNumber(value, fallback)) |
| | | } |
| | | |
| | | function normalizeColor(value, fallback = '#111111') { |
| | | const color = normalizeText(value) |
| | | return color || fallback |
| | | } |
| | | |
| | | function createElementId(prefix = 'el') { |
| | | return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}` |
| | | } |
| | | |
| | | export function mmToPx(mm, scale = 4) { |
| | | return `${normalizeNumber(mm) * scale}px` |
| | | } |
| | | |
| | | export function buildMatnrPrintFieldOptions(enabledFields = []) { |
| | | const fixedFields = FIXED_FIELD_OPTIONS.map((field) => ({ |
| | | label: $t(`pages.basicInfo.whMat.printTemplate.helpers.fixedFields.${field}`), |
| | | path: field, |
| | | placeholder: `{${field}}`, |
| | | category: $t('pages.basicInfo.whMat.printTemplate.fieldPanel.fixedCategory') |
| | | })) |
| | | |
| | | const extendFields = (Array.isArray(enabledFields) ? enabledFields : []) |
| | | .map((field) => { |
| | | const key = normalizeText(field?.fields) |
| | | if (!key) { |
| | | return null |
| | | } |
| | | return { |
| | | label: normalizeText(field?.fieldsAlise || field?.fieldsAlias || key), |
| | | path: `extendFields.${key}`, |
| | | placeholder: `{extendFields.${key}}`, |
| | | category: $t('pages.basicInfo.whMat.printTemplate.fieldPanel.extendCategory') |
| | | } |
| | | }) |
| | | .filter(Boolean) |
| | | |
| | | return [...fixedFields, ...extendFields] |
| | | } |
| | | |
| | | export function resolveTemplatePlaceholders(template, record = {}) { |
| | | const source = typeof template === 'string' ? template : String(template ?? '') |
| | | return source.replace(/\{([^{}]+)\}/g, (_, rawPath) => { |
| | | const value = getValueByPath(record, normalizeText(rawPath)) |
| | | if (value === null || value === undefined) { |
| | | return '' |
| | | } |
| | | if (typeof value === 'object') { |
| | | return '' |
| | | } |
| | | return String(value) |
| | | }) |
| | | } |
| | | |
| | | export function renderMatnrPrintTemplate(template = {}, record = {}) { |
| | | const normalizedTemplate = getNormalizedTemplate(template) |
| | | const elements = normalizedTemplate.elements |
| | | .filter((element) => element.visible !== false) |
| | | .map((element) => resolveElement(element, record)) |
| | | .sort((left, right) => normalizeNumber(left?.zIndex) - normalizeNumber(right?.zIndex)) |
| | | |
| | | return { |
| | | version: normalizedTemplate.version, |
| | | canvas: normalizedTemplate.canvas, |
| | | elements |
| | | } |
| | | } |
| | | |
| | | export function measureMatnrPrintTemplateBounds(template = {}) { |
| | | return measureNormalizedTemplateBounds(getNormalizedTemplate(template)) |
| | | } |
| | | |
| | | export function detectMatnrPrintTemplateOverflow(template = {}) { |
| | | const normalizedTemplate = getNormalizedTemplate(template) |
| | | const bounds = measureNormalizedTemplateBounds(normalizedTemplate) |
| | | const canvasWidth = Math.max(10, normalizeNumber(normalizedTemplate?.canvas?.width, 70)) |
| | | const canvasHeight = Math.max(10, normalizeNumber(normalizedTemplate?.canvas?.height, 40)) |
| | | return { |
| | | hasOverflow: bounds.width > canvasWidth || bounds.height > canvasHeight, |
| | | overflowX: bounds.width > canvasWidth, |
| | | overflowY: bounds.height > canvasHeight, |
| | | bounds, |
| | | canvas: { |
| | | width: canvasWidth, |
| | | height: canvasHeight |
| | | } |
| | | } |
| | | } |
| | | |
| | | function resolveElement(element, record) { |
| | | const base = { |
| | | ...cloneDeep(element), |
| | | x: normalizeNumber(element?.x), |
| | | y: normalizeNumber(element?.y), |
| | | w: Math.max(0.5, normalizeNumber(element?.w, 10)), |
| | | h: Math.max(0.5, normalizeNumber(element?.h, 6)), |
| | | zIndex: normalizeInteger(element?.zIndex, 1), |
| | | visible: element?.visible !== false |
| | | } |
| | | |
| | | if (base.type === 'text') { |
| | | const contentTemplate = normalizeText(base.contentTemplate) |
| | | base.resolvedText = |
| | | base.contentMode === 'template' |
| | | ? resolveTemplatePlaceholders(contentTemplate, record) |
| | | : contentTemplate |
| | | base.style = { |
| | | ...TEXT_STYLE_DEFAULTS, |
| | | ...(base.style || {}) |
| | | } |
| | | return base |
| | | } |
| | | |
| | | if (base.type === 'barcode') { |
| | | const value = resolveTemplatePlaceholders(base.valueTemplate, record) |
| | | base.resolvedValue = value |
| | | base.showText = base.showText !== false |
| | | base.symbology = 'CODE128' |
| | | base.svgMarkup = buildBarcodeSvgMarkup(value, { |
| | | showText: base.showText, |
| | | widthMm: base.w, |
| | | heightMm: base.h |
| | | }) |
| | | return base |
| | | } |
| | | |
| | | if (base.type === 'qrcode') { |
| | | base.resolvedValue = resolveTemplatePlaceholders(base.valueTemplate, record) |
| | | return base |
| | | } |
| | | |
| | | if (base.type === 'line') { |
| | | base.direction = base.direction === 'vertical' ? 'vertical' : 'horizontal' |
| | | base.borderWidth = Math.max(0.2, normalizeNumber(base.borderWidth, 0.4)) |
| | | base.color = normalizeColor(base.color, '#111111') |
| | | return base |
| | | } |
| | | |
| | | if (base.type === 'rect') { |
| | | base.borderWidth = Math.max(0.2, normalizeNumber(base.borderWidth, 0.4)) |
| | | base.borderColor = normalizeColor(base.borderColor, '#111111') |
| | | base.backgroundColor = normalizeColor(base.backgroundColor, '#FFFFFF') |
| | | base.radius = Math.max(0, normalizeNumber(base.radius, 0)) |
| | | return base |
| | | } |
| | | |
| | | if (base.type === 'table') { |
| | | base.style = { |
| | | ...TABLE_STYLE_DEFAULTS, |
| | | ...(base.style || {}) |
| | | } |
| | | base.columns = Array.isArray(base.columns) ? base.columns : [] |
| | | base.rows = Array.isArray(base.rows) ? base.rows : [] |
| | | base.cells = Array.isArray(base.cells) ? base.cells : [] |
| | | base.resolvedCells = base.cells.map((cell) => ({ |
| | | ...cell, |
| | | row: normalizeInteger(cell?.row), |
| | | col: normalizeInteger(cell?.col), |
| | | rowspan: Math.max(1, normalizeInteger(cell?.rowspan, 1)), |
| | | colspan: Math.max(1, normalizeInteger(cell?.colspan, 1)), |
| | | resolvedText: |
| | | cell?.contentMode === 'template' |
| | | ? resolveTemplatePlaceholders(cell?.contentTemplate, record) |
| | | : normalizeText(cell?.contentTemplate), |
| | | style: { |
| | | ...TABLE_STYLE_DEFAULTS, |
| | | ...(base.style || {}), |
| | | ...(cell?.style || {}) |
| | | } |
| | | })) |
| | | return base |
| | | } |
| | | |
| | | return base |
| | | } |
| | | |
| | | function getNormalizedTemplate(template = {}) { |
| | | if ( |
| | | template && |
| | | typeof template === 'object' && |
| | | template.canvas && |
| | | template.canvas.unit === MM_UNIT && |
| | | Array.isArray(template.elements) |
| | | ) { |
| | | return template |
| | | } |
| | | return normalizeMatnrPrintTemplate(template) |
| | | } |
| | | |
| | | function measureNormalizedTemplateBounds(template = {}) { |
| | | const canvasWidth = Math.max(10, normalizeNumber(template?.canvas?.width, 70)) |
| | | const canvasHeight = Math.max(10, normalizeNumber(template?.canvas?.height, 40)) |
| | | const bounds = (Array.isArray(template?.elements) ? template.elements : []).reduce( |
| | | (result, element) => { |
| | | if (element?.visible === false) { |
| | | return result |
| | | } |
| | | const right = normalizeNumber(element?.x) + normalizeNumber(element?.w) |
| | | const bottom = normalizeNumber(element?.y) + normalizeNumber(element?.h) |
| | | return { |
| | | width: Math.max(result.width, right), |
| | | height: Math.max(result.height, bottom) |
| | | } |
| | | }, |
| | | { |
| | | width: canvasWidth, |
| | | height: canvasHeight |
| | | } |
| | | ) |
| | | return { |
| | | width: Number(bounds.width.toFixed(2)), |
| | | height: Number(bounds.height.toFixed(2)) |
| | | } |
| | | } |
| | | |
| | | export function normalizeMatnrPrintTemplate(template = {}) { |
| | | const canvasSource = |
| | | template?.canvas || template?.canvasJson?.canvas |
| | | ? template?.canvas || template?.canvasJson?.canvas |
| | | : {} |
| | | const rawCanvasJson = |
| | | template?.canvasJson && |
| | | typeof template.canvasJson === 'object' && |
| | | !Array.isArray(template.canvasJson) |
| | | ? template.canvasJson |
| | | : null |
| | | const source = |
| | | rawCanvasJson && Array.isArray(rawCanvasJson.elements) |
| | | ? rawCanvasJson |
| | | : { |
| | | version: template?.version, |
| | | canvas: canvasSource, |
| | | elements: Array.isArray(template?.elements) ? template.elements : [] |
| | | } |
| | | |
| | | return { |
| | | id: template?.id, |
| | | tenantId: template?.tenantId, |
| | | name: |
| | | normalizeText(template?.name) || |
| | | $t('pages.basicInfo.whMat.printTemplate.workspace.unnamedTemplate'), |
| | | code: normalizeText(template?.code) || `MATNR_${Date.now()}`, |
| | | isDefault: normalizeInteger(template?.isDefault, 0), |
| | | status: normalizeInteger(template?.status, 1), |
| | | memo: normalizeText(template?.memo), |
| | | version: normalizeInteger(source?.version, 1), |
| | | canvas: { |
| | | ...DEFAULT_CANVAS, |
| | | ...(source?.canvas || {}), |
| | | width: Math.max(10, normalizeNumber(source?.canvas?.width, DEFAULT_CANVAS.width)), |
| | | height: Math.max(10, normalizeNumber(source?.canvas?.height, DEFAULT_CANVAS.height)), |
| | | unit: MM_UNIT, |
| | | backgroundColor: normalizeColor( |
| | | source?.canvas?.backgroundColor, |
| | | DEFAULT_CANVAS.backgroundColor |
| | | ), |
| | | gridSize: Math.max(1, normalizeNumber(source?.canvas?.gridSize, DEFAULT_CANVAS.gridSize)) |
| | | }, |
| | | elements: (Array.isArray(source?.elements) ? source.elements : []).map((element, index) => |
| | | normalizeElement(element, index) |
| | | ) |
| | | } |
| | | } |
| | | |
| | | function normalizeElement(element = {}, index = 0) { |
| | | const normalized = { |
| | | id: normalizeText(element?.id) || createElementId(), |
| | | type: normalizeText(element?.type) || 'text', |
| | | x: normalizeNumber(element?.x, 2 + index), |
| | | y: normalizeNumber(element?.y, 2 + index), |
| | | w: Math.max(1, normalizeNumber(element?.w, 20)), |
| | | h: Math.max(1, normalizeNumber(element?.h, 8)), |
| | | zIndex: normalizeInteger(element?.zIndex, index + 1), |
| | | visible: element?.visible !== false |
| | | } |
| | | |
| | | if (normalized.type === 'text') { |
| | | normalized.contentMode = element?.contentMode === 'template' ? 'template' : 'static' |
| | | normalized.contentTemplate = normalizeText(element?.contentTemplate) |
| | | normalized.style = { |
| | | ...TEXT_STYLE_DEFAULTS, |
| | | ...(element?.style || {}), |
| | | fontSize: Math.max( |
| | | 1.8, |
| | | normalizeNumber(element?.style?.fontSize, TEXT_STYLE_DEFAULTS.fontSize) |
| | | ), |
| | | fontWeight: normalizeInteger(element?.style?.fontWeight, TEXT_STYLE_DEFAULTS.fontWeight), |
| | | textAlign: ['left', 'center', 'right'].includes(element?.style?.textAlign) |
| | | ? element.style.textAlign |
| | | : TEXT_STYLE_DEFAULTS.textAlign, |
| | | color: normalizeColor(element?.style?.color, TEXT_STYLE_DEFAULTS.color), |
| | | lineHeight: Math.max( |
| | | 1, |
| | | normalizeNumber(element?.style?.lineHeight, TEXT_STYLE_DEFAULTS.lineHeight) |
| | | ) |
| | | } |
| | | return normalized |
| | | } |
| | | |
| | | if (normalized.type === 'barcode') { |
| | | normalized.valueTemplate = normalizeText(element?.valueTemplate) |
| | | normalized.showText = element?.showText !== false |
| | | normalized.symbology = 'CODE128' |
| | | normalized.h = Math.max(8, normalizeNumber(element?.h, 14)) |
| | | return normalized |
| | | } |
| | | |
| | | if (normalized.type === 'qrcode') { |
| | | normalized.valueTemplate = normalizeText(element?.valueTemplate) |
| | | normalized.w = Math.max(8, normalizeNumber(element?.w, 18)) |
| | | normalized.h = Math.max(8, normalizeNumber(element?.h, 18)) |
| | | return normalized |
| | | } |
| | | |
| | | if (normalized.type === 'line') { |
| | | normalized.direction = element?.direction === 'vertical' ? 'vertical' : 'horizontal' |
| | | normalized.borderWidth = Math.max(0.2, normalizeNumber(element?.borderWidth, 0.4)) |
| | | normalized.color = normalizeColor(element?.color, '#111111') |
| | | normalized.w = |
| | | normalized.direction === 'horizontal' |
| | | ? Math.max(6, normalizeNumber(element?.w, 20)) |
| | | : Math.max(0.4, normalizeNumber(element?.w, 0.4)) |
| | | normalized.h = |
| | | normalized.direction === 'vertical' |
| | | ? Math.max(6, normalizeNumber(element?.h, 20)) |
| | | : Math.max(0.4, normalizeNumber(element?.h, 0.4)) |
| | | return normalized |
| | | } |
| | | |
| | | if (normalized.type === 'rect') { |
| | | normalized.borderWidth = Math.max(0.2, normalizeNumber(element?.borderWidth, 0.4)) |
| | | normalized.borderColor = normalizeColor(element?.borderColor, '#111111') |
| | | normalized.backgroundColor = normalizeColor(element?.backgroundColor, '#FFFFFF') |
| | | normalized.radius = Math.max(0, normalizeNumber(element?.radius, 0)) |
| | | return normalized |
| | | } |
| | | |
| | | if (normalized.type === 'table') { |
| | | normalized.style = { |
| | | ...TABLE_STYLE_DEFAULTS, |
| | | ...(element?.style || {}), |
| | | fontSize: Math.max( |
| | | 1.8, |
| | | normalizeNumber(element?.style?.fontSize, TABLE_STYLE_DEFAULTS.fontSize) |
| | | ), |
| | | fontWeight: normalizeInteger(element?.style?.fontWeight, TABLE_STYLE_DEFAULTS.fontWeight), |
| | | textAlign: ['left', 'center', 'right'].includes(element?.style?.textAlign) |
| | | ? element.style.textAlign |
| | | : TABLE_STYLE_DEFAULTS.textAlign, |
| | | color: normalizeColor(element?.style?.color, TABLE_STYLE_DEFAULTS.color), |
| | | borderColor: normalizeColor(element?.style?.borderColor, TABLE_STYLE_DEFAULTS.borderColor), |
| | | borderWidth: Math.max( |
| | | 0.2, |
| | | normalizeNumber(element?.style?.borderWidth, TABLE_STYLE_DEFAULTS.borderWidth) |
| | | ), |
| | | backgroundColor: normalizeColor( |
| | | element?.style?.backgroundColor, |
| | | TABLE_STYLE_DEFAULTS.backgroundColor |
| | | ) |
| | | } |
| | | normalized.columns = normalizeTableColumns(element?.columns, normalized.w) |
| | | normalized.rows = normalizeTableRows(element?.rows) |
| | | normalized.h = Math.max( |
| | | normalizeNumber(normalized.h, 0), |
| | | normalized.rows.reduce((total, row) => total + normalizeNumber(row?.height, 0), 0) |
| | | ) |
| | | normalized.cells = normalizeTableCells(element?.cells) |
| | | return normalized |
| | | } |
| | | |
| | | return normalized |
| | | } |
| | | |
| | | function normalizeTableColumns(columns = [], totalWidth = 40) { |
| | | const source = |
| | | Array.isArray(columns) && columns.length |
| | | ? columns |
| | | : [{ width: totalWidth * 0.36 }, { width: totalWidth * 0.64 }] |
| | | return source.map((column) => ({ |
| | | width: Math.max(6, normalizeNumber(column?.width, 18)) |
| | | })) |
| | | } |
| | | |
| | | function normalizeTableRows(rows = []) { |
| | | const source = Array.isArray(rows) && rows.length ? rows : [{ height: 6 }] |
| | | return source.map((row) => ({ |
| | | height: Math.max(4, normalizeNumber(row?.height, 6)) |
| | | })) |
| | | } |
| | | |
| | | function normalizeTableCells(cells = []) { |
| | | return (Array.isArray(cells) ? cells : []).map((cell) => ({ |
| | | row: normalizeInteger(cell?.row), |
| | | col: normalizeInteger(cell?.col), |
| | | rowspan: Math.max(1, normalizeInteger(cell?.rowspan, 1)), |
| | | colspan: Math.max(1, normalizeInteger(cell?.colspan, 1)), |
| | | contentMode: cell?.contentMode === 'template' ? 'template' : 'static', |
| | | contentTemplate: normalizeText(cell?.contentTemplate), |
| | | style: { |
| | | ...TABLE_STYLE_DEFAULTS, |
| | | ...(cell?.style || {}) |
| | | } |
| | | })) |
| | | } |
| | | |
| | | export function buildMatnrPrintTemplatePayload(template = {}) { |
| | | const normalized = normalizeMatnrPrintTemplate(template) |
| | | return { |
| | | ...(normalized.id ? { id: normalized.id } : {}), |
| | | name: normalized.name, |
| | | code: normalized.code, |
| | | isDefault: normalized.isDefault, |
| | | status: normalized.status, |
| | | memo: normalized.memo, |
| | | canvasJson: { |
| | | version: normalized.version, |
| | | canvas: normalized.canvas, |
| | | elements: normalized.elements |
| | | } |
| | | } |
| | | } |
| | | |
| | | export function createDefaultMatnrPrintTemplate(seed = {}) { |
| | | const name = |
| | | normalizeText(seed?.name) || |
| | | $t('pages.basicInfo.whMat.printTemplate.helpers.defaultTemplateName') |
| | | const code = normalizeText(seed?.code) || `MATNR_${Date.now()}` |
| | | return normalizeMatnrPrintTemplate({ |
| | | name, |
| | | code, |
| | | status: 1, |
| | | isDefault: seed?.isDefault ? 1 : 0, |
| | | memo: '', |
| | | canvasJson: { |
| | | version: 1, |
| | | canvas: { |
| | | ...DEFAULT_CANVAS |
| | | }, |
| | | elements: [ |
| | | { |
| | | id: createElementId(), |
| | | type: 'text', |
| | | x: 4, |
| | | y: 3, |
| | | w: 18, |
| | | h: 5, |
| | | zIndex: 1, |
| | | visible: true, |
| | | contentMode: 'static', |
| | | contentTemplate: $t( |
| | | 'pages.basicInfo.whMat.printTemplate.helpers.defaultText.productCode' |
| | | ), |
| | | style: { |
| | | ...TEXT_STYLE_DEFAULTS, |
| | | fontSize: 3.1, |
| | | fontWeight: 600, |
| | | textAlign: 'center' |
| | | } |
| | | }, |
| | | { |
| | | id: createElementId(), |
| | | type: 'barcode', |
| | | x: 24, |
| | | y: 3, |
| | | w: 40, |
| | | h: 14, |
| | | zIndex: 2, |
| | | visible: true, |
| | | valueTemplate: '{code}', |
| | | showText: true, |
| | | symbology: 'CODE128' |
| | | }, |
| | | { |
| | | id: createElementId(), |
| | | type: 'text', |
| | | x: 4, |
| | | y: 20, |
| | | w: 12, |
| | | h: 5, |
| | | zIndex: 3, |
| | | visible: true, |
| | | contentMode: 'static', |
| | | contentTemplate: $t( |
| | | 'pages.basicInfo.whMat.printTemplate.helpers.defaultText.productName' |
| | | ), |
| | | style: { |
| | | ...TEXT_STYLE_DEFAULTS, |
| | | fontSize: 3, |
| | | fontWeight: 600 |
| | | } |
| | | }, |
| | | { |
| | | id: createElementId(), |
| | | type: 'text', |
| | | x: 17, |
| | | y: 20, |
| | | w: 47, |
| | | h: 5, |
| | | zIndex: 4, |
| | | visible: true, |
| | | contentMode: 'template', |
| | | contentTemplate: '{name}', |
| | | style: { |
| | | ...TEXT_STYLE_DEFAULTS, |
| | | fontSize: 3, |
| | | fontWeight: 500 |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | }) |
| | | } |
| | | |
| | | export function duplicateMatnrPrintTemplate(template = {}) { |
| | | const normalized = normalizeMatnrPrintTemplate(template) |
| | | return normalizeMatnrPrintTemplate({ |
| | | ...cloneDeep(normalized), |
| | | id: null, |
| | | name: `${normalized.name}${$t('pages.basicInfo.whMat.printTemplate.helpers.copySuffix')}`, |
| | | code: `${normalized.code}_COPY_${Date.now()}`, |
| | | isDefault: 0, |
| | | canvasJson: { |
| | | version: normalized.version, |
| | | canvas: normalized.canvas, |
| | | elements: normalized.elements.map((element) => ({ |
| | | ...cloneDeep(element), |
| | | id: createElementId() |
| | | })) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | export function createElementByType(type, offsetIndex = 0) { |
| | | const offset = offsetIndex * 2 |
| | | const common = { |
| | | id: createElementId(), |
| | | type, |
| | | x: 4 + offset, |
| | | y: 4 + offset, |
| | | zIndex: offsetIndex + 1, |
| | | visible: true |
| | | } |
| | | |
| | | if (type === 'text') { |
| | | return normalizeElement({ |
| | | ...common, |
| | | w: 24, |
| | | h: 6, |
| | | contentMode: 'static', |
| | | contentTemplate: $t('pages.basicInfo.whMat.printTemplate.helpers.defaultText.staticText'), |
| | | style: { |
| | | ...TEXT_STYLE_DEFAULTS |
| | | } |
| | | }) |
| | | } |
| | | |
| | | if (type === 'barcode') { |
| | | return normalizeElement({ |
| | | ...common, |
| | | w: 34, |
| | | h: 14, |
| | | valueTemplate: '{code}', |
| | | showText: true, |
| | | symbology: 'CODE128' |
| | | }) |
| | | } |
| | | |
| | | if (type === 'qrcode') { |
| | | return normalizeElement({ |
| | | ...common, |
| | | w: 20, |
| | | h: 20, |
| | | valueTemplate: '{barcode}' |
| | | }) |
| | | } |
| | | |
| | | if (type === 'line') { |
| | | return normalizeElement({ |
| | | ...common, |
| | | w: 24, |
| | | h: 0.4, |
| | | direction: 'horizontal', |
| | | borderWidth: 0.4, |
| | | color: '#111111' |
| | | }) |
| | | } |
| | | |
| | | if (type === 'rect') { |
| | | return normalizeElement({ |
| | | ...common, |
| | | w: 24, |
| | | h: 12, |
| | | borderWidth: 0.4, |
| | | borderColor: '#111111', |
| | | backgroundColor: '#FFFFFF', |
| | | radius: 0 |
| | | }) |
| | | } |
| | | |
| | | if (type === 'table') { |
| | | return createFieldListTableElement(offsetIndex) |
| | | } |
| | | |
| | | return normalizeElement(common) |
| | | } |
| | | |
| | | export function createFieldListTableElement(offsetIndex = 0) { |
| | | const x = 4 + offsetIndex * 2 |
| | | const y = 4 + offsetIndex * 2 |
| | | return normalizeElement({ |
| | | id: createElementId(), |
| | | type: 'table', |
| | | x, |
| | | y, |
| | | w: 58, |
| | | h: 24, |
| | | zIndex: offsetIndex + 1, |
| | | visible: true, |
| | | style: { |
| | | ...TABLE_STYLE_DEFAULTS, |
| | | fontSize: 2.8 |
| | | }, |
| | | columns: [{ width: 18 }, { width: 40 }], |
| | | rows: [{ height: 6 }, { height: 6 }, { height: 6 }, { height: 6 }], |
| | | cells: [ |
| | | { |
| | | row: 0, |
| | | col: 0, |
| | | contentMode: 'static', |
| | | contentTemplate: $t('pages.basicInfo.whMat.printTemplate.helpers.tableLabels.code') |
| | | }, |
| | | { row: 0, col: 1, contentMode: 'template', contentTemplate: '{code}' }, |
| | | { |
| | | row: 1, |
| | | col: 0, |
| | | contentMode: 'static', |
| | | contentTemplate: $t('pages.basicInfo.whMat.printTemplate.helpers.tableLabels.name') |
| | | }, |
| | | { row: 1, col: 1, contentMode: 'template', contentTemplate: '{name}' }, |
| | | { |
| | | row: 2, |
| | | col: 0, |
| | | contentMode: 'static', |
| | | contentTemplate: $t('pages.basicInfo.whMat.printTemplate.helpers.tableLabels.spec') |
| | | }, |
| | | { row: 2, col: 1, contentMode: 'template', contentTemplate: '{spec}' }, |
| | | { |
| | | row: 3, |
| | | col: 0, |
| | | contentMode: 'static', |
| | | contentTemplate: $t('pages.basicInfo.whMat.printTemplate.helpers.tableLabels.model') |
| | | }, |
| | | { row: 3, col: 1, contentMode: 'template', contentTemplate: '{model}' } |
| | | ] |
| | | }) |
| | | } |
| | | |
| | | export function getFieldListTableRows(element = {}) { |
| | | if (element?.type !== 'table') { |
| | | return [] |
| | | } |
| | | const rows = Array.isArray(element?.rows) ? element.rows : [] |
| | | const cells = Array.isArray(element?.cells) ? element.cells : [] |
| | | return rows.map((row, rowIndex) => { |
| | | const labelCell = cells.find( |
| | | (cell) => normalizeInteger(cell?.row) === rowIndex && normalizeInteger(cell?.col) === 0 |
| | | ) |
| | | const valueCell = cells.find( |
| | | (cell) => normalizeInteger(cell?.row) === rowIndex && normalizeInteger(cell?.col) === 1 |
| | | ) |
| | | return { |
| | | id: `${element.id}_row_${rowIndex}`, |
| | | height: Math.max(4, normalizeNumber(row?.height, 6)), |
| | | labelTemplate: normalizeText(labelCell?.contentTemplate), |
| | | labelMode: labelCell?.contentMode === 'template' ? 'template' : 'static', |
| | | valueTemplate: normalizeText(valueCell?.contentTemplate), |
| | | valueMode: valueCell?.contentMode === 'static' ? 'static' : 'template' |
| | | } |
| | | }) |
| | | } |
| | | |
| | | export function updateFieldListTableRows(element = {}, rowConfigs = []) { |
| | | const sourceRows = Array.isArray(rowConfigs) && rowConfigs.length ? rowConfigs : [] |
| | | return normalizeElement({ |
| | | ...cloneDeep(element), |
| | | rows: sourceRows.map((row) => ({ |
| | | height: Math.max(4, normalizeNumber(row?.height, 6)) |
| | | })), |
| | | columns: normalizeTableColumns(element?.columns, element?.w), |
| | | cells: sourceRows.flatMap((row, index) => [ |
| | | { |
| | | row: index, |
| | | col: 0, |
| | | contentMode: row?.labelMode === 'template' ? 'template' : 'static', |
| | | contentTemplate: normalizeText(row?.labelTemplate) |
| | | }, |
| | | { |
| | | row: index, |
| | | col: 1, |
| | | contentMode: row?.valueMode === 'static' ? 'static' : 'template', |
| | | contentTemplate: normalizeText(row?.valueTemplate) |
| | | } |
| | | ]) |
| | | }) |
| | | } |
| | | |
| | | export function appendFieldPlaceholder(originValue, placeholder) { |
| | | const currentValue = String(originValue ?? '') |
| | | return `${currentValue}${placeholder}` |
| | | } |
| | | |
| | | function getValueByPath(source, path) { |
| | | if (!path) { |
| | | return '' |
| | | } |
| | | return path.split('.').reduce((current, key) => { |
| | | if (current === null || current === undefined) { |
| | | return '' |
| | | } |
| | | return current[key] |
| | | }, source) |
| | | } |
| | | |
| | | export function buildBarcodeSvgMarkup(value, options = {}) { |
| | | const codeValue = normalizeText(value) |
| | | if (!codeValue || typeof document === 'undefined') { |
| | | return '' |
| | | } |
| | | try { |
| | | const svgNode = document.createElementNS('http://www.w3.org/2000/svg', 'svg') |
| | | const widthMm = Math.max(8, normalizeNumber(options?.widthMm, 20)) |
| | | const heightMm = Math.max(8, normalizeNumber(options?.heightMm, 12)) |
| | | const fontSize = Math.max(12, normalizeNumber(options?.fontSize, 12)) |
| | | const targetHeightPx = Math.max(32, heightMm * 4) |
| | | const textReservePx = options?.showText !== false ? Math.max(fontSize + 8, 18) : 0 |
| | | JsBarcode(svgNode, codeValue, { |
| | | format: 'CODE128', |
| | | displayValue: options?.showText !== false, |
| | | width: Math.max(1, normalizeNumber(options?.lineWidth, 1.4)), |
| | | height: Math.max(12, targetHeightPx - textReservePx), |
| | | margin: 0, |
| | | background: '#FFFFFF', |
| | | lineColor: '#111111', |
| | | font: 'Arial', |
| | | fontSize, |
| | | textMargin: 2 |
| | | }) |
| | | svgNode.setAttribute('width', `${widthMm}mm`) |
| | | svgNode.setAttribute('height', `${heightMm}mm`) |
| | | svgNode.setAttribute('preserveAspectRatio', 'none') |
| | | return svgNode.outerHTML |
| | | } catch (error) { |
| | | console.warn('[matnr-print] barcode render failed', error) |
| | | return '' |
| | | } |
| | | } |
| New file |
| | |
| | | <template> |
| | | <div class="matnr-print-canvas-shell"> |
| | | <div |
| | | ref="canvasRef" |
| | | class="matnr-print-canvas" |
| | | :class="{ |
| | | 'matnr-print-canvas--editor': editorMode, |
| | | 'matnr-print-canvas--interactive': interactive, |
| | | 'matnr-print-canvas--grid': showGrid && editorMode |
| | | }" |
| | | :style="canvasStyle" |
| | | @mousedown.self="handleCanvasMouseDown" |
| | | > |
| | | <div |
| | | v-for="element in renderedTemplate.elements" |
| | | :key="element.id" |
| | | class="matnr-print-element" |
| | | :class="{ |
| | | 'is-selected': interactive && selectedElementId === element.id, |
| | | 'is-hidden': element.visible === false |
| | | }" |
| | | :style="getElementBoxStyle(element)" |
| | | @mousedown.stop="handleElementMouseDown($event, element)" |
| | | > |
| | | <div |
| | | v-if="element.type === 'text'" |
| | | class="matnr-print-element__text" |
| | | :style="getTextStyle(element)" |
| | | > |
| | | {{ element.resolvedText }} |
| | | </div> |
| | | |
| | | <div |
| | | v-else-if="element.type === 'barcode'" |
| | | class="matnr-print-element__barcode" |
| | | v-html="element.svgMarkup" |
| | | ></div> |
| | | |
| | | <div v-else-if="element.type === 'qrcode'" class="matnr-print-element__qrcode"> |
| | | <QrcodeVue |
| | | :value="element.resolvedValue || ' '" |
| | | render-as="svg" |
| | | level="M" |
| | | :size="getQrcodeSize(element)" |
| | | :margin="0" |
| | | /> |
| | | </div> |
| | | |
| | | <div |
| | | v-else-if="element.type === 'line'" |
| | | class="matnr-print-element__line" |
| | | :style="getLineStyle(element)" |
| | | ></div> |
| | | |
| | | <div |
| | | v-else-if="element.type === 'rect'" |
| | | class="matnr-print-element__rect" |
| | | :style="getRectStyle(element)" |
| | | ></div> |
| | | |
| | | <table |
| | | v-else-if="element.type === 'table'" |
| | | class="matnr-print-element__table" |
| | | :style="getTableStyle(element)" |
| | | > |
| | | <colgroup> |
| | | <col |
| | | v-for="(column, columnIndex) in element.columns" |
| | | :key="`${element.id}_col_${columnIndex}`" |
| | | :style="{ width: getUnitValue(column.width || 10) }" |
| | | /> |
| | | </colgroup> |
| | | <tbody> |
| | | <tr |
| | | v-for="(row, rowIndex) in element.rows" |
| | | :key="`${element.id}_row_${rowIndex}`" |
| | | :style="{ height: getUnitValue(row.height || 6) }" |
| | | > |
| | | <template |
| | | v-for="cell in getTableCellsForRow(element, rowIndex)" |
| | | :key="`${element.id}_${rowIndex}_${cell.col}`" |
| | | > |
| | | <td |
| | | :colspan="cell.colspan || 1" |
| | | :rowspan="cell.rowspan || 1" |
| | | :style="getTableCellStyle(cell, element)" |
| | | > |
| | | <div |
| | | class="matnr-print-element__table-cell-content" |
| | | :style="getTableCellContentStyle(cell, element)" |
| | | > |
| | | {{ cell.resolvedText }} |
| | | </div> |
| | | </td> |
| | | </template> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | |
| | | <template v-if="interactive && selectedElementId === element.id && element.type !== 'line'"> |
| | | <span |
| | | v-for="handle in resizeHandles" |
| | | :key="handle.direction" |
| | | class="matnr-print-element__handle" |
| | | :class="`is-${handle.direction}`" |
| | | @mousedown.stop="handleResizeMouseDown($event, element, handle.direction)" |
| | | ></span> |
| | | </template> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, onBeforeUnmount, ref } from 'vue' |
| | | import QrcodeVue from 'qrcode.vue' |
| | | import { |
| | | mmToPx, |
| | | normalizeMatnrPrintTemplate, |
| | | renderMatnrPrintTemplate |
| | | } from '../matnrPrintTemplate.helpers' |
| | | |
| | | defineOptions({ name: 'MatnrPrintCanvas' }) |
| | | |
| | | const props = defineProps({ |
| | | template: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | activeRecord: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | selectedElementId: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | mode: { |
| | | type: String, |
| | | default: 'editor' |
| | | }, |
| | | scale: { |
| | | type: Number, |
| | | default: 4 |
| | | }, |
| | | interactive: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | showGrid: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }) |
| | | |
| | | const emit = defineEmits(['select-element', 'update-element']) |
| | | |
| | | const canvasRef = ref(null) |
| | | const interactionState = ref(null) |
| | | const resizeHandles = [ |
| | | { direction: 'top-left' }, |
| | | { direction: 'top' }, |
| | | { direction: 'top-right' }, |
| | | { direction: 'right' }, |
| | | { direction: 'bottom-right' }, |
| | | { direction: 'bottom' }, |
| | | { direction: 'bottom-left' }, |
| | | { direction: 'left' } |
| | | ] |
| | | |
| | | const editorMode = computed(() => props.mode === 'editor') |
| | | const normalizedTemplate = computed(() => normalizeMatnrPrintTemplate(props.template)) |
| | | const renderedTemplate = computed(() => |
| | | renderMatnrPrintTemplate(normalizedTemplate.value, props.activeRecord || {}) |
| | | ) |
| | | |
| | | const canvasStyle = computed(() => { |
| | | const canvas = renderedTemplate.value.canvas |
| | | const width = editorMode.value ? mmToPx(canvas.width, props.scale) : `${canvas.width}mm` |
| | | const height = editorMode.value ? mmToPx(canvas.height, props.scale) : `${canvas.height}mm` |
| | | const gridSize = `${canvas.gridSize * props.scale}px` |
| | | return { |
| | | width, |
| | | height, |
| | | backgroundColor: canvas.backgroundColor || '#FFFFFF', |
| | | '--matnr-print-grid-size': gridSize |
| | | } |
| | | }) |
| | | |
| | | function getUnitValue(value) { |
| | | return editorMode.value ? mmToPx(value, props.scale) : `${value}mm` |
| | | } |
| | | |
| | | function getElementBoxStyle(element) { |
| | | return { |
| | | left: getUnitValue(element.x), |
| | | top: getUnitValue(element.y), |
| | | width: getUnitValue(element.w), |
| | | height: getUnitValue(element.h), |
| | | zIndex: element.zIndex |
| | | } |
| | | } |
| | | |
| | | function getTextStyle(element) { |
| | | const style = element.style || {} |
| | | return { |
| | | fontSize: editorMode.value ? mmToPx(style.fontSize, props.scale) : `${style.fontSize}mm`, |
| | | fontWeight: style.fontWeight, |
| | | color: style.color, |
| | | textAlign: style.textAlign, |
| | | lineHeight: style.lineHeight |
| | | } |
| | | } |
| | | |
| | | function getLineStyle(element) { |
| | | const borderSize = editorMode.value |
| | | ? mmToPx(element.borderWidth || 0.4, props.scale) |
| | | : `${element.borderWidth || 0.4}mm` |
| | | return { |
| | | backgroundColor: element.color || '#111111', |
| | | height: element.direction === 'horizontal' ? borderSize : editorMode.value ? '100%' : '100%', |
| | | width: element.direction === 'vertical' ? borderSize : editorMode.value ? '100%' : '100%' |
| | | } |
| | | } |
| | | |
| | | function getRectStyle(element) { |
| | | return { |
| | | borderWidth: editorMode.value |
| | | ? mmToPx(element.borderWidth || 0.4, props.scale) |
| | | : `${element.borderWidth || 0.4}mm`, |
| | | borderStyle: 'solid', |
| | | borderColor: element.borderColor || '#111111', |
| | | backgroundColor: element.backgroundColor || '#FFFFFF', |
| | | borderRadius: editorMode.value |
| | | ? mmToPx(element.radius || 0, props.scale) |
| | | : `${element.radius || 0}mm` |
| | | } |
| | | } |
| | | |
| | | function getTableStyle(element) { |
| | | const style = element.style || {} |
| | | return { |
| | | fontSize: editorMode.value ? mmToPx(style.fontSize, props.scale) : `${style.fontSize}mm`, |
| | | color: style.color, |
| | | tableLayout: 'fixed' |
| | | } |
| | | } |
| | | |
| | | function getTableCellSpanHeight(element, cell) { |
| | | const rows = Array.isArray(element?.rows) ? element.rows : [] |
| | | const startRow = Number(cell?.row) || 0 |
| | | const rowspan = Math.max(1, Number(cell?.rowspan) || 1) |
| | | return rows |
| | | .slice(startRow, startRow + rowspan) |
| | | .reduce((total, row) => total + (Number(row?.height) || 6), 0) |
| | | } |
| | | |
| | | function getTableCellStyle(cell, element) { |
| | | const style = cell.style || {} |
| | | return { |
| | | border: `${editorMode.value ? mmToPx(style.borderWidth || 0.2, props.scale) : `${style.borderWidth || 0.2}mm`} solid ${style.borderColor || '#111111'}`, |
| | | backgroundColor: style.backgroundColor || '#FFFFFF', |
| | | textAlign: style.textAlign || 'left', |
| | | fontWeight: style.fontWeight || 400, |
| | | height: getUnitValue(getTableCellSpanHeight(element, cell)), |
| | | padding: 0, |
| | | overflow: 'hidden' |
| | | } |
| | | } |
| | | |
| | | function getTableCellContentStyle(cell, element) { |
| | | const paddingY = editorMode.value ? `${0.4 * props.scale}px` : '0.4mm' |
| | | const paddingX = editorMode.value ? `${0.8 * props.scale}px` : '0.8mm' |
| | | return { |
| | | width: '100%', |
| | | height: getUnitValue(getTableCellSpanHeight(element, cell)), |
| | | boxSizing: 'border-box', |
| | | padding: `${paddingY} ${paddingX}`, |
| | | overflow: 'hidden', |
| | | display: 'flex', |
| | | alignItems: 'center', |
| | | whiteSpace: 'pre-wrap', |
| | | wordBreak: 'break-word' |
| | | } |
| | | } |
| | | |
| | | function getTableCellsForRow(element, rowIndex) { |
| | | return (Array.isArray(element?.resolvedCells) ? element.resolvedCells : []).filter( |
| | | (cell) => Number(cell?.row) === rowIndex |
| | | ) |
| | | } |
| | | |
| | | function getQrcodeSize(element) { |
| | | const sizeMm = Math.max(element.w || 12, element.h || 12) |
| | | return Math.max(48, sizeMm * (editorMode.value ? props.scale : 4)) |
| | | } |
| | | |
| | | function handleCanvasMouseDown() { |
| | | if (!props.interactive) { |
| | | return |
| | | } |
| | | emit('select-element', '') |
| | | } |
| | | |
| | | function handleElementMouseDown(event, element) { |
| | | if (!props.interactive) { |
| | | return |
| | | } |
| | | emit('select-element', element.id) |
| | | if (element.type === 'line') { |
| | | return startInteraction(event, element, 'drag') |
| | | } |
| | | if (event.target?.classList?.contains('matnr-print-element__handle')) { |
| | | return |
| | | } |
| | | startInteraction(event, element, 'drag') |
| | | } |
| | | |
| | | function handleResizeMouseDown(event, element, direction) { |
| | | if (!props.interactive) { |
| | | return |
| | | } |
| | | emit('select-element', element.id) |
| | | startInteraction(event, element, 'resize', direction) |
| | | } |
| | | |
| | | function startInteraction(event, element, type, direction = '') { |
| | | interactionState.value = { |
| | | type, |
| | | direction, |
| | | elementId: element.id, |
| | | startX: event.clientX, |
| | | startY: event.clientY, |
| | | origin: { |
| | | x: Number(element.x) || 0, |
| | | y: Number(element.y) || 0, |
| | | w: Number(element.w) || 0, |
| | | h: Number(element.h) || 0 |
| | | } |
| | | } |
| | | window.addEventListener('mousemove', handleWindowMouseMove) |
| | | window.addEventListener('mouseup', stopInteraction) |
| | | } |
| | | |
| | | function handleWindowMouseMove(event) { |
| | | if (!interactionState.value) { |
| | | return |
| | | } |
| | | const dx = snapMm((event.clientX - interactionState.value.startX) / props.scale) |
| | | const dy = snapMm((event.clientY - interactionState.value.startY) / props.scale) |
| | | const origin = interactionState.value.origin |
| | | let patch = {} |
| | | |
| | | if (interactionState.value.type === 'drag') { |
| | | patch = { |
| | | x: Math.max(0, origin.x + dx), |
| | | y: Math.max(0, origin.y + dy) |
| | | } |
| | | } else if (interactionState.value.type === 'resize') { |
| | | patch = buildResizePatch(origin, dx, dy, interactionState.value.direction) |
| | | } |
| | | |
| | | emit('update-element', { |
| | | id: interactionState.value.elementId, |
| | | patch |
| | | }) |
| | | } |
| | | |
| | | function buildResizePatch(origin, dx, dy, direction) { |
| | | const next = { |
| | | x: origin.x, |
| | | y: origin.y, |
| | | w: origin.w, |
| | | h: origin.h |
| | | } |
| | | |
| | | if (direction.includes('left')) { |
| | | next.x = Math.max(0, origin.x + dx) |
| | | next.w = Math.max(2, origin.w - dx) |
| | | } |
| | | if (direction.includes('right')) { |
| | | next.w = Math.max(2, origin.w + dx) |
| | | } |
| | | if (direction.includes('top')) { |
| | | next.y = Math.max(0, origin.y + dy) |
| | | next.h = Math.max(2, origin.h - dy) |
| | | } |
| | | if (direction.includes('bottom')) { |
| | | next.h = Math.max(2, origin.h + dy) |
| | | } |
| | | |
| | | if (next.w === 2 && direction.includes('left')) { |
| | | next.x = origin.x + origin.w - 2 |
| | | } |
| | | if (next.h === 2 && direction.includes('top')) { |
| | | next.y = origin.y + origin.h - 2 |
| | | } |
| | | |
| | | return next |
| | | } |
| | | |
| | | function stopInteraction() { |
| | | interactionState.value = null |
| | | window.removeEventListener('mousemove', handleWindowMouseMove) |
| | | window.removeEventListener('mouseup', stopInteraction) |
| | | } |
| | | |
| | | function snapMm(value) { |
| | | return Math.round(Number(value || 0)) |
| | | } |
| | | |
| | | onBeforeUnmount(() => { |
| | | stopInteraction() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .matnr-print-canvas-shell { |
| | | width: 100%; |
| | | min-width: 100%; |
| | | min-height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | overflow: auto; |
| | | } |
| | | |
| | | .matnr-print-canvas { |
| | | position: relative; |
| | | box-sizing: border-box; |
| | | flex: none; |
| | | margin: 0 auto; |
| | | box-shadow: 0 18px 36px rgba(15, 23, 42, 0.08); |
| | | } |
| | | |
| | | .matnr-print-canvas--editor { |
| | | border: 1px solid rgba(148, 163, 184, 0.45); |
| | | } |
| | | |
| | | .matnr-print-canvas--grid { |
| | | background-image: |
| | | linear-gradient(to right, rgba(148, 163, 184, 0.2) 1px, transparent 1px), |
| | | linear-gradient(to bottom, rgba(148, 163, 184, 0.2) 1px, transparent 1px); |
| | | background-size: var(--matnr-print-grid-size) var(--matnr-print-grid-size); |
| | | } |
| | | |
| | | .matnr-print-element { |
| | | position: absolute; |
| | | box-sizing: border-box; |
| | | overflow: hidden; |
| | | user-select: none; |
| | | } |
| | | |
| | | .matnr-print-element.is-selected { |
| | | outline: 1px solid #2563eb; |
| | | outline-offset: 0; |
| | | } |
| | | |
| | | .matnr-print-element__text, |
| | | .matnr-print-element__barcode, |
| | | .matnr-print-element__qrcode, |
| | | .matnr-print-element__rect, |
| | | .matnr-print-element__table { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .matnr-print-element__text { |
| | | display: flex; |
| | | align-items: center; |
| | | word-break: break-word; |
| | | white-space: pre-wrap; |
| | | } |
| | | |
| | | .matnr-print-element__barcode :deep(svg) { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: block; |
| | | } |
| | | |
| | | .matnr-print-element__qrcode { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .matnr-print-element__qrcode :deep(svg) { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .matnr-print-element__line { |
| | | position: absolute; |
| | | inset: 0; |
| | | } |
| | | |
| | | .matnr-print-element__rect { |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .matnr-print-element__table { |
| | | border-collapse: collapse; |
| | | } |
| | | |
| | | .matnr-print-element__table td { |
| | | box-sizing: border-box; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .matnr-print-element__table-cell-content { |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .matnr-print-element__handle { |
| | | position: absolute; |
| | | width: 10px; |
| | | height: 10px; |
| | | border: 1px solid #2563eb; |
| | | background: #ffffff; |
| | | border-radius: 50%; |
| | | z-index: 3; |
| | | } |
| | | |
| | | .matnr-print-element__handle.is-top-left { |
| | | top: -5px; |
| | | left: -5px; |
| | | cursor: nwse-resize; |
| | | } |
| | | |
| | | .matnr-print-element__handle.is-top { |
| | | top: -5px; |
| | | left: calc(50% - 5px); |
| | | cursor: ns-resize; |
| | | } |
| | | |
| | | .matnr-print-element__handle.is-top-right { |
| | | top: -5px; |
| | | right: -5px; |
| | | cursor: nesw-resize; |
| | | } |
| | | |
| | | .matnr-print-element__handle.is-right { |
| | | top: calc(50% - 5px); |
| | | right: -5px; |
| | | cursor: ew-resize; |
| | | } |
| | | |
| | | .matnr-print-element__handle.is-bottom-right { |
| | | right: -5px; |
| | | bottom: -5px; |
| | | cursor: nwse-resize; |
| | | } |
| | | |
| | | .matnr-print-element__handle.is-bottom { |
| | | left: calc(50% - 5px); |
| | | bottom: -5px; |
| | | cursor: ns-resize; |
| | | } |
| | | |
| | | .matnr-print-element__handle.is-bottom-left { |
| | | left: -5px; |
| | | bottom: -5px; |
| | | cursor: nesw-resize; |
| | | } |
| | | |
| | | .matnr-print-element__handle.is-left { |
| | | top: calc(50% - 5px); |
| | | left: -5px; |
| | | cursor: ew-resize; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <div class="matnr-print-field-panel"> |
| | | <div class="matnr-print-field-panel__title">{{ |
| | | t('pages.basicInfo.whMat.printTemplate.fieldPanel.title') |
| | | }}</div> |
| | | <ElScrollbar max-height="220px"> |
| | | <div class="matnr-print-field-panel__list"> |
| | | <button |
| | | v-for="field in fields" |
| | | :key="field.path" |
| | | type="button" |
| | | class="matnr-print-field-panel__item" |
| | | @click="$emit('insert-field', field.placeholder)" |
| | | > |
| | | <span>{{ field.label }}</span> |
| | | <small>{{ field.placeholder }}</small> |
| | | </button> |
| | | </div> |
| | | </ElScrollbar> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { useI18n } from 'vue-i18n' |
| | | |
| | | defineOptions({ name: 'MatnrPrintFieldPanel' }) |
| | | |
| | | const { t } = useI18n() |
| | | |
| | | defineProps({ |
| | | fields: { |
| | | type: Array, |
| | | default: () => [] |
| | | } |
| | | }) |
| | | |
| | | defineEmits(['insert-field']) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .matnr-print-field-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex-shrink: 0; |
| | | gap: 10px; |
| | | padding: 12px; |
| | | border-top: 1px solid rgba(148, 163, 184, 0.18); |
| | | } |
| | | |
| | | .matnr-print-field-panel__title { |
| | | font-size: 13px; |
| | | font-weight: 600; |
| | | color: var(--art-text-primary); |
| | | } |
| | | |
| | | .matnr-print-field-panel__list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .matnr-print-field-panel__item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | gap: 4px; |
| | | width: 100%; |
| | | padding: 8px 10px; |
| | | border: 1px solid rgba(148, 163, 184, 0.24); |
| | | border-radius: 10px; |
| | | background: #ffffff; |
| | | color: var(--art-text-primary); |
| | | cursor: pointer; |
| | | transition: |
| | | border-color 0.2s ease, |
| | | transform 0.2s ease; |
| | | } |
| | | |
| | | .matnr-print-field-panel__item:hover { |
| | | border-color: rgba(37, 99, 235, 0.45); |
| | | transform: translateY(-1px); |
| | | } |
| | | |
| | | .matnr-print-field-panel__item small { |
| | | color: var(--art-text-secondary); |
| | | font-size: 12px; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <div class="matnr-print-property-panel"> |
| | | <ElScrollbar height="100%"> |
| | | <div class="matnr-print-property-panel__content"> |
| | | <section class="matnr-print-property-panel__section"> |
| | | <div class="matnr-print-property-panel__section-title"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.propertyPanel.sections.templateInfo') }} |
| | | </div> |
| | | <div class="matnr-print-property-panel__grid"> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.templateName') |
| | | }}</span> |
| | | <ElInput |
| | | :model-value="template.name" |
| | | @update:model-value="updateTemplateMeta('name', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.templateCode') |
| | | }}</span> |
| | | <ElInput |
| | | :model-value="template.code" |
| | | @update:model-value="updateTemplateMeta('code', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ t('table.status') }}</span> |
| | | <ElSelect |
| | | :model-value="template.status" |
| | | @update:model-value="updateTemplateMeta('status', Number($event))" |
| | | > |
| | | <ElOption :value="1" :label="t('common.status.normal')" /> |
| | | <ElOption :value="0" :label="t('common.status.frozen')" /> |
| | | </ElSelect> |
| | | </label> |
| | | <label class="matnr-print-property-panel__switch"> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.defaultTemplate') |
| | | }}</span> |
| | | <ElSwitch |
| | | :model-value="template.isDefault === 1" |
| | | @update:model-value="updateTemplateMeta('isDefault', $event ? 1 : 0)" |
| | | /> |
| | | </label> |
| | | <label class="matnr-print-property-panel__span-2"> |
| | | <span>{{ t('table.memo') }}</span> |
| | | <ElInput |
| | | type="textarea" |
| | | :rows="2" |
| | | :model-value="template.memo" |
| | | @update:model-value="updateTemplateMeta('memo', $event)" |
| | | /> |
| | | </label> |
| | | </div> |
| | | </section> |
| | | |
| | | <section class="matnr-print-property-panel__section"> |
| | | <div class="matnr-print-property-panel__section-title"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.propertyPanel.sections.canvas') }} |
| | | </div> |
| | | <div class="matnr-print-property-panel__grid"> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.widthMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="template.canvas?.width" |
| | | :min="10" |
| | | :step="1" |
| | | controls-position="right" |
| | | @update:model-value="updateCanvas('width', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.heightMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="template.canvas?.height" |
| | | :min="10" |
| | | :step="1" |
| | | controls-position="right" |
| | | @update:model-value="updateCanvas('height', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.backgroundColor') |
| | | }}</span> |
| | | <ElColorPicker |
| | | :model-value="template.canvas?.backgroundColor" |
| | | @change="updateCanvas('backgroundColor', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.gridSizeMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="template.canvas?.gridSize" |
| | | :min="1" |
| | | :step="1" |
| | | controls-position="right" |
| | | @update:model-value="updateCanvas('gridSize', $event)" |
| | | /> |
| | | </label> |
| | | </div> |
| | | </section> |
| | | |
| | | <section class="matnr-print-property-panel__section"> |
| | | <div class="matnr-print-property-panel__section-header"> |
| | | <div class="matnr-print-property-panel__section-title"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.propertyPanel.sections.element') }} |
| | | </div> |
| | | <ElButton |
| | | v-if="selectedElement" |
| | | type="danger" |
| | | text |
| | | @click="$emit('remove-element', selectedElement.id)" |
| | | > |
| | | {{ t('pages.basicInfo.whMat.printTemplate.propertyPanel.actions.removeElement') }} |
| | | </ElButton> |
| | | </div> |
| | | |
| | | <div v-if="!selectedElement" class="matnr-print-property-panel__empty"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.propertyPanel.empty') }} |
| | | </div> |
| | | |
| | | <template v-else> |
| | | <div class="matnr-print-property-panel__grid"> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.elementType') |
| | | }}</span> |
| | | <ElInput :model-value="selectedElement.type" disabled /> |
| | | </label> |
| | | <label class="matnr-print-property-panel__switch"> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.visible') |
| | | }}</span> |
| | | <ElSwitch |
| | | :model-value="selectedElement.visible !== false" |
| | | @update:model-value="updateElement('visible', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>X(mm)</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.x" |
| | | :min="0" |
| | | controls-position="right" |
| | | @update:model-value="updateElement('x', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>Y(mm)</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.y" |
| | | :min="0" |
| | | controls-position="right" |
| | | @update:model-value="updateElement('y', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.widthMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.w" |
| | | :min="1" |
| | | controls-position="right" |
| | | @update:model-value="updateElement('w', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.heightMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.h" |
| | | :min="0.4" |
| | | :step="0.2" |
| | | controls-position="right" |
| | | @update:model-value="updateElement('h', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.zIndex') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.zIndex" |
| | | :min="1" |
| | | controls-position="right" |
| | | @update:model-value="updateElement('zIndex', $event)" |
| | | /> |
| | | </label> |
| | | </div> |
| | | |
| | | <template v-if="selectedElement.type === 'text'"> |
| | | <div class="matnr-print-property-panel__grid"> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.contentMode') |
| | | }}</span> |
| | | <ElSelect |
| | | :model-value="selectedElement.contentMode" |
| | | @update:model-value="updateElement('contentMode', $event)" |
| | | > |
| | | <ElOption |
| | | value="static" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.staticText') |
| | | " |
| | | /> |
| | | <ElOption |
| | | value="template" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.templateText') |
| | | " |
| | | /> |
| | | </ElSelect> |
| | | </label> |
| | | <label class="matnr-print-property-panel__span-2"> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.textContent') |
| | | }}</span> |
| | | <ElInput |
| | | type="textarea" |
| | | :rows="3" |
| | | :model-value="selectedElement.contentTemplate" |
| | | @focus="setPlaceholderTarget('contentTemplate')" |
| | | @update:model-value="updateElement('contentTemplate', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.fontSizeMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.style?.fontSize" |
| | | :min="1.8" |
| | | :step="0.2" |
| | | controls-position="right" |
| | | @update:model-value="updateElementStyle('fontSize', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.fontWeight') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.style?.fontWeight" |
| | | :min="100" |
| | | :max="900" |
| | | :step="100" |
| | | controls-position="right" |
| | | @update:model-value="updateElementStyle('fontWeight', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.textAlign') |
| | | }}</span> |
| | | <ElSelect |
| | | :model-value="selectedElement.style?.textAlign" |
| | | @update:model-value="updateElementStyle('textAlign', $event)" |
| | | > |
| | | <ElOption |
| | | value="left" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.alignLeft') |
| | | " |
| | | /> |
| | | <ElOption |
| | | value="center" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.alignCenter') |
| | | " |
| | | /> |
| | | <ElOption |
| | | value="right" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.alignRight') |
| | | " |
| | | /> |
| | | </ElSelect> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.textColor') |
| | | }}</span> |
| | | <ElColorPicker |
| | | :model-value="selectedElement.style?.color" |
| | | @change="updateElementStyle('color', $event)" |
| | | /> |
| | | </label> |
| | | </div> |
| | | </template> |
| | | |
| | | <template v-else-if="selectedElement.type === 'barcode'"> |
| | | <div class="matnr-print-property-panel__grid"> |
| | | <label class="matnr-print-property-panel__span-2"> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.valueTemplate') |
| | | }}</span> |
| | | <ElInput |
| | | :model-value="selectedElement.valueTemplate" |
| | | @focus="setPlaceholderTarget('valueTemplate')" |
| | | @update:model-value="updateElement('valueTemplate', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.symbology') |
| | | }}</span> |
| | | <ElInput model-value="CODE128" disabled /> |
| | | </label> |
| | | <label class="matnr-print-property-panel__switch"> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.showText') |
| | | }}</span> |
| | | <ElSwitch |
| | | :model-value="selectedElement.showText !== false" |
| | | @update:model-value="updateElement('showText', $event)" |
| | | /> |
| | | </label> |
| | | </div> |
| | | </template> |
| | | |
| | | <template v-else-if="selectedElement.type === 'qrcode'"> |
| | | <div class="matnr-print-property-panel__grid"> |
| | | <label class="matnr-print-property-panel__span-2"> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.valueTemplate') |
| | | }}</span> |
| | | <ElInput |
| | | :model-value="selectedElement.valueTemplate" |
| | | @focus="setPlaceholderTarget('valueTemplate')" |
| | | @update:model-value="updateElement('valueTemplate', $event)" |
| | | /> |
| | | </label> |
| | | </div> |
| | | </template> |
| | | |
| | | <template v-else-if="selectedElement.type === 'line'"> |
| | | <div class="matnr-print-property-panel__grid"> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.direction') |
| | | }}</span> |
| | | <ElSelect |
| | | :model-value="selectedElement.direction" |
| | | @update:model-value="updateLineDirection($event)" |
| | | > |
| | | <ElOption |
| | | value="horizontal" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.horizontal') |
| | | " |
| | | /> |
| | | <ElOption |
| | | value="vertical" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.vertical') |
| | | " |
| | | /> |
| | | </ElSelect> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.lineWidthMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.borderWidth" |
| | | :min="0.2" |
| | | :step="0.2" |
| | | controls-position="right" |
| | | @update:model-value="updateElement('borderWidth', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.lineColor') |
| | | }}</span> |
| | | <ElColorPicker |
| | | :model-value="selectedElement.color" |
| | | @change="updateElement('color', $event)" |
| | | /> |
| | | </label> |
| | | </div> |
| | | </template> |
| | | |
| | | <template v-else-if="selectedElement.type === 'rect'"> |
| | | <div class="matnr-print-property-panel__grid"> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.borderWidthMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.borderWidth" |
| | | :min="0.2" |
| | | :step="0.2" |
| | | controls-position="right" |
| | | @update:model-value="updateElement('borderWidth', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.radiusMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.radius" |
| | | :min="0" |
| | | :step="0.5" |
| | | controls-position="right" |
| | | @update:model-value="updateElement('radius', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.borderColor') |
| | | }}</span> |
| | | <ElColorPicker |
| | | :model-value="selectedElement.borderColor" |
| | | @change="updateElement('borderColor', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.fillColor') |
| | | }}</span> |
| | | <ElColorPicker |
| | | :model-value="selectedElement.backgroundColor" |
| | | @change="updateElement('backgroundColor', $event)" |
| | | /> |
| | | </label> |
| | | </div> |
| | | </template> |
| | | |
| | | <template v-else-if="selectedElement.type === 'table'"> |
| | | <div class="matnr-print-property-panel__grid"> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.fontSizeMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.style?.fontSize" |
| | | :min="1.8" |
| | | :step="0.2" |
| | | controls-position="right" |
| | | @update:model-value="updateElementStyle('fontSize', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.borderWidthMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="selectedElement.style?.borderWidth" |
| | | :min="0.2" |
| | | :step="0.2" |
| | | controls-position="right" |
| | | @update:model-value="updateElementStyle('borderWidth', $event)" |
| | | /> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.textAlign') |
| | | }}</span> |
| | | <ElSelect |
| | | :model-value="selectedElement.style?.textAlign" |
| | | @update:model-value="updateElementStyle('textAlign', $event)" |
| | | > |
| | | <ElOption |
| | | value="left" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.alignLeft') |
| | | " |
| | | /> |
| | | <ElOption |
| | | value="center" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.alignCenter') |
| | | " |
| | | /> |
| | | <ElOption |
| | | value="right" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.alignRight') |
| | | " |
| | | /> |
| | | </ElSelect> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.borderColor') |
| | | }}</span> |
| | | <ElColorPicker |
| | | :model-value="selectedElement.style?.borderColor" |
| | | @change="updateElementStyle('borderColor', $event)" |
| | | /> |
| | | </label> |
| | | </div> |
| | | |
| | | <div class="matnr-print-property-panel__section-subtitle"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.propertyPanel.sections.fieldList') }} |
| | | </div> |
| | | <div class="matnr-print-property-panel__table-rows"> |
| | | <div |
| | | v-for="(row, rowIndex) in tableRows" |
| | | :key="row.id" |
| | | class="matnr-print-property-panel__table-row" |
| | | > |
| | | <div class="matnr-print-property-panel__table-row-header"> |
| | | <span> |
| | | {{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.rowTitle', { |
| | | index: rowIndex + 1 |
| | | }) |
| | | }} |
| | | </span> |
| | | <ElButton type="danger" text @click="removeTableRow(rowIndex)"> |
| | | {{ t('common.actions.delete') }} |
| | | </ElButton> |
| | | </div> |
| | | <div class="matnr-print-property-panel__grid"> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.leftMode') |
| | | }}</span> |
| | | <ElSelect |
| | | :model-value="row.labelMode" |
| | | @update:model-value="updateTableRow(rowIndex, 'labelMode', $event)" |
| | | > |
| | | <ElOption |
| | | value="static" |
| | | :label=" |
| | | t( |
| | | 'pages.basicInfo.whMat.printTemplate.propertyPanel.options.staticText' |
| | | ) |
| | | " |
| | | /> |
| | | <ElOption |
| | | value="template" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.template') |
| | | " |
| | | /> |
| | | </ElSelect> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.rightMode') |
| | | }}</span> |
| | | <ElSelect |
| | | :model-value="row.valueMode" |
| | | @update:model-value="updateTableRow(rowIndex, 'valueMode', $event)" |
| | | > |
| | | <ElOption |
| | | value="template" |
| | | :label=" |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.options.template') |
| | | " |
| | | /> |
| | | <ElOption |
| | | value="static" |
| | | :label=" |
| | | t( |
| | | 'pages.basicInfo.whMat.printTemplate.propertyPanel.options.staticText' |
| | | ) |
| | | " |
| | | /> |
| | | </ElSelect> |
| | | </label> |
| | | <label> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.rowHeightMm') |
| | | }}</span> |
| | | <ElInputNumber |
| | | :model-value="row.height" |
| | | :min="4" |
| | | :step="1" |
| | | controls-position="right" |
| | | @update:model-value="updateTableRow(rowIndex, 'height', $event)" |
| | | /> |
| | | </label> |
| | | <label class="matnr-print-property-panel__span-2"> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.leftContent') |
| | | }}</span> |
| | | <ElInput |
| | | :model-value="row.labelTemplate" |
| | | @focus="setPlaceholderTarget('table', rowIndex, 'labelTemplate')" |
| | | @update:model-value="updateTableRow(rowIndex, 'labelTemplate', $event)" |
| | | /> |
| | | </label> |
| | | <label class="matnr-print-property-panel__span-2"> |
| | | <span>{{ |
| | | t('pages.basicInfo.whMat.printTemplate.propertyPanel.fields.rightContent') |
| | | }}</span> |
| | | <ElInput |
| | | :model-value="row.valueTemplate" |
| | | @focus="setPlaceholderTarget('table', rowIndex, 'valueTemplate')" |
| | | @update:model-value="updateTableRow(rowIndex, 'valueTemplate', $event)" |
| | | /> |
| | | </label> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <ElButton size="small" @click="appendTableRow"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.propertyPanel.actions.addRow') }} |
| | | </ElButton> |
| | | </template> |
| | | </template> |
| | | </section> |
| | | </div> |
| | | </ElScrollbar> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed } from 'vue' |
| | | import { useI18n } from 'vue-i18n' |
| | | import { getFieldListTableRows, updateFieldListTableRows } from '../matnrPrintTemplate.helpers' |
| | | |
| | | defineOptions({ name: 'MatnrPrintPropertyPanel' }) |
| | | |
| | | const { t } = useI18n() |
| | | |
| | | const props = defineProps({ |
| | | template: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | selectedElement: { |
| | | type: Object, |
| | | default: null |
| | | } |
| | | }) |
| | | |
| | | const emit = defineEmits([ |
| | | 'update-template-meta', |
| | | 'update-canvas', |
| | | 'update-element', |
| | | 'remove-element', |
| | | 'set-placeholder-target' |
| | | ]) |
| | | |
| | | const tableRows = computed(() => getFieldListTableRows(props.selectedElement)) |
| | | |
| | | function updateTemplateMeta(field, value) { |
| | | emit('update-template-meta', { field, value }) |
| | | } |
| | | |
| | | function updateCanvas(field, value) { |
| | | emit('update-canvas', { field, value }) |
| | | } |
| | | |
| | | function updateElement(field, value) { |
| | | emit('update-element', { |
| | | id: props.selectedElement?.id, |
| | | patch: { |
| | | [field]: value |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function updateElementStyle(field, value) { |
| | | emit('update-element', { |
| | | id: props.selectedElement?.id, |
| | | patch: { |
| | | style: { |
| | | ...(props.selectedElement?.style || {}), |
| | | [field]: value |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function updateLineDirection(direction) { |
| | | const patch = |
| | | direction === 'vertical' |
| | | ? { |
| | | direction, |
| | | w: Math.max(0.4, Number(props.selectedElement?.borderWidth || 0.4)) |
| | | } |
| | | : { |
| | | direction, |
| | | h: Math.max(0.4, Number(props.selectedElement?.borderWidth || 0.4)) |
| | | } |
| | | emit('update-element', { |
| | | id: props.selectedElement?.id, |
| | | patch |
| | | }) |
| | | } |
| | | |
| | | function setPlaceholderTarget(field, rowIndex, rowField) { |
| | | emit('set-placeholder-target', { |
| | | elementId: props.selectedElement?.id, |
| | | field, |
| | | rowIndex, |
| | | rowField |
| | | }) |
| | | } |
| | | |
| | | function appendTableRow() { |
| | | const nextRows = [ |
| | | ...tableRows.value, |
| | | { |
| | | height: 6, |
| | | labelTemplate: t('pages.basicInfo.whMat.printTemplate.propertyPanel.defaultFieldLabel'), |
| | | labelMode: 'static', |
| | | valueTemplate: '{code}', |
| | | valueMode: 'template' |
| | | } |
| | | ] |
| | | syncTableRows(nextRows) |
| | | } |
| | | |
| | | function removeTableRow(index) { |
| | | const nextRows = tableRows.value.filter((_, rowIndex) => rowIndex !== index) |
| | | syncTableRows(nextRows) |
| | | } |
| | | |
| | | function updateTableRow(index, field, value) { |
| | | const nextRows = tableRows.value.map((row, rowIndex) => |
| | | rowIndex === index |
| | | ? { |
| | | ...row, |
| | | [field]: value |
| | | } |
| | | : row |
| | | ) |
| | | syncTableRows(nextRows) |
| | | } |
| | | |
| | | function syncTableRows(rows) { |
| | | emit('update-element', { |
| | | id: props.selectedElement?.id, |
| | | patch: updateFieldListTableRows(props.selectedElement, rows) |
| | | }) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .matnr-print-property-panel { |
| | | flex: 1; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | background: #ffffff; |
| | | } |
| | | |
| | | .matnr-print-property-panel__content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16px; |
| | | padding: 16px; |
| | | } |
| | | |
| | | .matnr-print-property-panel__section { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | padding: 14px; |
| | | border: 1px solid rgba(148, 163, 184, 0.18); |
| | | border-radius: 16px; |
| | | background: linear-gradient(180deg, #ffffff, #f8fafc); |
| | | } |
| | | |
| | | .matnr-print-property-panel__section-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .matnr-print-property-panel__section-title { |
| | | font-size: 14px; |
| | | font-weight: 700; |
| | | color: var(--art-text-primary); |
| | | } |
| | | |
| | | .matnr-print-property-panel__section-subtitle { |
| | | font-size: 13px; |
| | | font-weight: 600; |
| | | color: var(--art-text-primary); |
| | | } |
| | | |
| | | .matnr-print-property-panel__grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | |
| | | .matnr-print-property-panel__grid label { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 6px; |
| | | min-width: 0; |
| | | font-size: 12px; |
| | | color: var(--art-text-secondary); |
| | | } |
| | | |
| | | .matnr-print-property-panel__span-2 { |
| | | grid-column: span 2; |
| | | } |
| | | |
| | | .matnr-print-property-panel__switch { |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .matnr-print-property-panel__empty { |
| | | padding: 18px 14px; |
| | | border-radius: 12px; |
| | | background: rgba(241, 245, 249, 0.85); |
| | | color: var(--art-text-secondary); |
| | | font-size: 13px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .matnr-print-property-panel__table-rows { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .matnr-print-property-panel__table-row { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | padding: 12px; |
| | | border: 1px solid rgba(148, 163, 184, 0.2); |
| | | border-radius: 14px; |
| | | background: rgba(255, 255, 255, 0.9); |
| | | } |
| | | |
| | | .matnr-print-property-panel__table-row-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | font-size: 13px; |
| | | font-weight: 600; |
| | | color: var(--art-text-primary); |
| | | } |
| | | |
| | | @media (max-width: 1280px) { |
| | | .matnr-print-property-panel__grid { |
| | | grid-template-columns: minmax(0, 1fr); |
| | | } |
| | | |
| | | .matnr-print-property-panel__span-2 { |
| | | grid-column: span 1; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <div class="matnr-print-toolbar"> |
| | | <ElSpace wrap> |
| | | <ElButton |
| | | v-for="item in items" |
| | | :key="item.type" |
| | | size="small" |
| | | @click="$emit('add-element', item.type)" |
| | | > |
| | | {{ item.label }} |
| | | </ElButton> |
| | | </ElSpace> |
| | | |
| | | <ElSpace wrap> |
| | | <ElButton size="small" :disabled="!canZoomOut" @click="$emit('zoom-out')"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.toolbar.zoomOut') }} |
| | | </ElButton> |
| | | <ElButton size="small" @click="$emit('zoom-reset')">{{ zoomPercent }}%</ElButton> |
| | | <ElButton size="small" :disabled="!canZoomIn" @click="$emit('zoom-in')"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.toolbar.zoomIn') }} |
| | | </ElButton> |
| | | <ElButton |
| | | size="small" |
| | | :type="autoFitActive ? 'primary' : 'default'" |
| | | @click="$emit('zoom-fit')" |
| | | > |
| | | {{ t('pages.basicInfo.whMat.printTemplate.toolbar.zoomFit') }} |
| | | </ElButton> |
| | | </ElSpace> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed } from 'vue' |
| | | import { useI18n } from 'vue-i18n' |
| | | |
| | | defineOptions({ name: 'MatnrPrintToolbar' }) |
| | | |
| | | defineProps({ |
| | | zoomPercent: { |
| | | type: Number, |
| | | default: 100 |
| | | }, |
| | | canZoomIn: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | canZoomOut: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | autoFitActive: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }) |
| | | |
| | | defineEmits(['add-element', 'zoom-in', 'zoom-out', 'zoom-reset', 'zoom-fit']) |
| | | |
| | | const { t, locale } = useI18n() |
| | | |
| | | const items = computed(() => { |
| | | locale.value |
| | | return [ |
| | | { type: 'text', label: t('pages.basicInfo.whMat.printTemplate.toolbar.elements.text') }, |
| | | { type: 'barcode', label: t('pages.basicInfo.whMat.printTemplate.toolbar.elements.barcode') }, |
| | | { type: 'qrcode', label: t('pages.basicInfo.whMat.printTemplate.toolbar.elements.qrcode') }, |
| | | { type: 'line', label: t('pages.basicInfo.whMat.printTemplate.toolbar.elements.line') }, |
| | | { type: 'rect', label: t('pages.basicInfo.whMat.printTemplate.toolbar.elements.rect') }, |
| | | { type: 'table', label: t('pages.basicInfo.whMat.printTemplate.toolbar.elements.table') } |
| | | ] |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .matnr-print-toolbar { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | flex-wrap: wrap; |
| | | padding: 12px 16px; |
| | | border-bottom: 1px solid rgba(148, 163, 184, 0.18); |
| | | background: linear-gradient(180deg, rgba(248, 250, 252, 0.96), rgba(241, 245, 249, 0.9)); |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <ElDialog |
| | | v-model="visibleModel" |
| | | :title="t('pages.basicInfo.whMat.printTemplate.dialog.title')" |
| | | width="min(96vw, 1180px)" |
| | | top="4vh" |
| | | class="wh-mat-print-preview-dialog" |
| | | destroy-on-close |
| | | > |
| | | <div class="wh-mat-print-dialog"> |
| | | <div class="wh-mat-print-dialog__toolbar"> |
| | | <ElSelect |
| | | v-model="selectedTemplateId" |
| | | class="wh-mat-print-dialog__template-select" |
| | | :placeholder="t('pages.basicInfo.whMat.printTemplate.dialog.templatePlaceholder')" |
| | | :disabled="loading || !templateOptions.length" |
| | | @change="handleTemplateChange" |
| | | > |
| | | <ElOption |
| | | v-for="item in templateOptions" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | /> |
| | | </ElSelect> |
| | | <div class="wh-mat-print-dialog__toolbar-right"> |
| | | <div class="wh-mat-print-dialog__copies"> |
| | | <span class="wh-mat-print-dialog__copies-label"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.dialog.copyCount') }} |
| | | </span> |
| | | <ElInputNumber |
| | | v-model="copyCount" |
| | | :min="1" |
| | | :max="99" |
| | | :step="1" |
| | | :precision="0" |
| | | size="small" |
| | | class="wh-mat-print-dialog__copies-input" |
| | | /> |
| | | </div> |
| | | <span class="wh-mat-print-dialog__summary"> |
| | | {{ |
| | | t('pages.basicInfo.whMat.printTemplate.dialog.labelCount', { |
| | | count: previewRows.length |
| | | }) |
| | | }} |
| | | </span> |
| | | <div class="wh-mat-print-dialog__zoom"> |
| | | <ElButton size="small" :disabled="!canZoomOut" @click="handleZoomOut"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.toolbar.zoomOut') }} |
| | | </ElButton> |
| | | <ElButton size="small" @click="handleZoomReset"> {{ previewZoomPercent }}% </ElButton> |
| | | <ElButton size="small" :disabled="!canZoomIn" @click="handleZoomIn"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.toolbar.zoomIn') }} |
| | | </ElButton> |
| | | <ElButton |
| | | size="small" |
| | | :type="autoFitEnabled ? 'primary' : 'default'" |
| | | @click="handleZoomFit" |
| | | > |
| | | {{ t('pages.basicInfo.whMat.printTemplate.toolbar.zoomFit') }} |
| | | </ElButton> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <ElEmpty |
| | | v-if="!templateOptions.length" |
| | | :description="t('pages.basicInfo.whMat.printTemplate.dialog.noTemplate')" |
| | | /> |
| | | |
| | | <ElEmpty |
| | | v-else-if="!normalizedRows.length" |
| | | :description="t('pages.basicInfo.whMat.printTemplate.dialog.noData')" |
| | | /> |
| | | |
| | | <template v-else> |
| | | <ElAlert |
| | | v-if="templateOverflow.hasOverflow" |
| | | class="wh-mat-print-dialog__overflow-alert" |
| | | type="warning" |
| | | :closable="false" |
| | | show-icon |
| | | :title="t('pages.basicInfo.whMat.printTemplate.dialog.canvasOverflow')" |
| | | :description="t('pages.basicInfo.whMat.printTemplate.dialog.canvasOverflowDescription')" |
| | | /> |
| | | |
| | | <div ref="previewViewportRef" class="wh-mat-print-dialog__preview-list"> |
| | | <div |
| | | v-for="item in previewItems" |
| | | :key="item.key" |
| | | class="wh-mat-print-dialog__preview-item" |
| | | :style="item.previewStyle" |
| | | > |
| | | <div class="wh-mat-print-dialog__preview-canvas" :style="item.previewCanvasStyle"> |
| | | <MatnrPrintCanvas |
| | | v-if="activeTemplate" |
| | | :template="activeTemplate" |
| | | :active-record="item.row" |
| | | mode="preview" |
| | | :interactive="false" |
| | | :show-grid="false" |
| | | :scale="4" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <div |
| | | v-if="printSourceVisible && activeTemplate && normalizedRows.length" |
| | | ref="printBodyRef" |
| | | class="wh-mat-print-dialog__print-source" |
| | | > |
| | | <div |
| | | v-for="item in previewItems" |
| | | :key="`${item.key}_print`" |
| | | class="wh-mat-print-dialog__preview-item wh-mat-print-dialog__preview-item--print" |
| | | :style="item.printStyle" |
| | | > |
| | | <MatnrPrintCanvas |
| | | :template="activeTemplate" |
| | | :active-record="item.row" |
| | | mode="preview" |
| | | :interactive="false" |
| | | :show-grid="false" |
| | | :scale="4" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="wh-mat-print-dialog__footer"> |
| | | <ElButton @click="visibleModel = false">{{ t('common.actions.close') }}</ElButton> |
| | | <ElButton |
| | | type="primary" |
| | | :disabled="!activeTemplate || !normalizedRows.length" |
| | | @click="handlePrint" |
| | | > |
| | | {{ t('pages.basicInfo.whMat.printTemplate.dialog.confirmPrint') }} |
| | | </ElButton> |
| | | </div> |
| | | </template> |
| | | </ElDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' |
| | | import { useI18n } from 'vue-i18n' |
| | | import { ElMessage } from 'element-plus' |
| | | import { |
| | | fetchGetDefaultMatnrPrintTemplate, |
| | | fetchGetMatnrPrintTemplateDetail, |
| | | fetchMatnrPrintTemplateList |
| | | } from '@/api/wh-mat' |
| | | import { |
| | | detectMatnrPrintTemplateOverflow, |
| | | normalizeMatnrPrintTemplate |
| | | } from '../matnrPrintTemplate.helpers' |
| | | import MatnrPrintCanvas from './matnr-print-canvas.vue' |
| | | |
| | | defineOptions({ name: 'WhMatPrintDialog' }) |
| | | |
| | | const { t, locale } = useI18n() |
| | | |
| | | const visibleModel = defineModel('visible', { |
| | | type: Boolean, |
| | | default: false |
| | | }) |
| | | |
| | | const props = defineProps({ |
| | | rows: { |
| | | type: Array, |
| | | default: () => [] |
| | | } |
| | | }) |
| | | |
| | | const previewViewportRef = ref(null) |
| | | const printBodyRef = ref(null) |
| | | const loading = ref(false) |
| | | const templateOptions = ref([]) |
| | | const selectedTemplateId = ref(null) |
| | | const activeTemplate = ref(null) |
| | | const printSourceVisible = ref(false) |
| | | const copyCount = ref(1) |
| | | const SCREEN_MM_TO_PX = 96 / 25.4 |
| | | const DEFAULT_PREVIEW_SCALE = 4 |
| | | const MIN_PREVIEW_SCALE = 0.5 |
| | | const MAX_PREVIEW_SCALE = 24 |
| | | const PREVIEW_SCALE_STEPS = [0.5, 0.75, 1, 1.5, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 20, 24] |
| | | const PREVIEW_PANEL_GAP = 24 |
| | | const previewScale = ref(DEFAULT_PREVIEW_SCALE) |
| | | const autoFitEnabled = ref(true) |
| | | let resizeObserver = null |
| | | |
| | | const normalizedRows = computed(() => (Array.isArray(props.rows) ? props.rows : [])) |
| | | const previewRows = computed(() => |
| | | normalizedRows.value.flatMap((row, rowIndex) => |
| | | Array.from({ length: copyCount.value }, (_, copyIndex) => ({ |
| | | key: `${row.id || row.code || rowIndex}_copy_${copyIndex + 1}`, |
| | | row |
| | | })) |
| | | ) |
| | | ) |
| | | const activeCanvas = computed(() => ({ |
| | | width: Number(activeTemplate.value?.canvas?.width) || 70, |
| | | height: Number(activeTemplate.value?.canvas?.height) || 40 |
| | | })) |
| | | const previewZoomRatio = computed(() => |
| | | Number((previewScale.value / DEFAULT_PREVIEW_SCALE).toFixed(4)) |
| | | ) |
| | | const previewZoomPercent = computed(() => |
| | | Math.round((previewScale.value / DEFAULT_PREVIEW_SCALE) * 100) |
| | | ) |
| | | const canZoomIn = computed(() => previewScale.value < MAX_PREVIEW_SCALE) |
| | | const canZoomOut = computed(() => previewScale.value > MIN_PREVIEW_SCALE) |
| | | const templateOverflow = computed(() => detectMatnrPrintTemplateOverflow(activeTemplate.value)) |
| | | const previewItems = computed(() => |
| | | previewRows.value.map((item) => { |
| | | const canvas = activeCanvas.value |
| | | const zoomRatio = previewZoomRatio.value |
| | | return { |
| | | key: item.key, |
| | | row: item.row, |
| | | previewStyle: { |
| | | width: `${canvas.width * SCREEN_MM_TO_PX * zoomRatio}px`, |
| | | height: `${canvas.height * SCREEN_MM_TO_PX * zoomRatio}px` |
| | | }, |
| | | previewCanvasStyle: { |
| | | width: `${canvas.width}mm`, |
| | | height: `${canvas.height}mm`, |
| | | transform: `scale(${zoomRatio})` |
| | | }, |
| | | printStyle: { |
| | | width: `${canvas.width}mm`, |
| | | height: `${canvas.height}mm` |
| | | } |
| | | } |
| | | }) |
| | | ) |
| | | |
| | | watch( |
| | | () => visibleModel.value, |
| | | async (visible) => { |
| | | if (visible) { |
| | | copyCount.value = 1 |
| | | await loadTemplates() |
| | | await nextTick() |
| | | observePreviewViewport() |
| | | fitPreviewToViewport() |
| | | return |
| | | } |
| | | printSourceVisible.value = false |
| | | copyCount.value = 1 |
| | | } |
| | | ) |
| | | |
| | | watch( |
| | | () => [activeTemplate.value?.id, normalizedRows.value.length], |
| | | async () => { |
| | | if (!visibleModel.value) { |
| | | return |
| | | } |
| | | await nextTick() |
| | | if (autoFitEnabled.value) { |
| | | fitPreviewToViewport() |
| | | } |
| | | } |
| | | ) |
| | | |
| | | async function loadTemplates() { |
| | | loading.value = true |
| | | try { |
| | | const [templates, defaultTemplate] = await Promise.all([ |
| | | fetchMatnrPrintTemplateList({}), |
| | | fetchGetDefaultMatnrPrintTemplate() |
| | | ]) |
| | | const nextTemplates = (Array.isArray(templates) ? templates : []) |
| | | .map((item) => normalizeMatnrPrintTemplate(item)) |
| | | .filter((item) => item.status === 1) |
| | | templateOptions.value = nextTemplates |
| | | if (!nextTemplates.length) { |
| | | selectedTemplateId.value = null |
| | | activeTemplate.value = null |
| | | return |
| | | } |
| | | |
| | | const defaultId = |
| | | defaultTemplate && typeof defaultTemplate === 'object' |
| | | ? Number(defaultTemplate.id) || null |
| | | : null |
| | | const nextTemplateId = |
| | | nextTemplates.find((item) => item.id === selectedTemplateId.value)?.id || |
| | | nextTemplates.find((item) => item.id === defaultId)?.id || |
| | | nextTemplates[0]?.id |
| | | selectedTemplateId.value = nextTemplateId |
| | | await handleTemplateChange(nextTemplateId) |
| | | } catch (error) { |
| | | ElMessage.error( |
| | | error?.message || t('pages.basicInfo.whMat.printTemplate.dialog.messages.loadFailed') |
| | | ) |
| | | templateOptions.value = [] |
| | | activeTemplate.value = null |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | async function handleTemplateChange(templateId) { |
| | | if (!templateId) { |
| | | activeTemplate.value = null |
| | | return |
| | | } |
| | | try { |
| | | const detail = await fetchGetMatnrPrintTemplateDetail(templateId) |
| | | activeTemplate.value = normalizeMatnrPrintTemplate(detail) |
| | | } catch (error) { |
| | | ElMessage.error( |
| | | error?.message || t('pages.basicInfo.whMat.printTemplate.dialog.messages.detailFailed') |
| | | ) |
| | | activeTemplate.value = null |
| | | } |
| | | } |
| | | |
| | | async function handlePrint() { |
| | | if (!activeTemplate.value) { |
| | | return |
| | | } |
| | | |
| | | if (!printSourceVisible.value || !printBodyRef.value) { |
| | | printSourceVisible.value = true |
| | | await nextTick() |
| | | await waitForPaint() |
| | | } |
| | | |
| | | if (!printBodyRef.value) { |
| | | printSourceVisible.value = false |
| | | return |
| | | } |
| | | |
| | | const printWindow = window.open('', '_blank') |
| | | if (!printWindow) { |
| | | printSourceVisible.value = false |
| | | ElMessage.warning(t('pages.basicInfo.whMat.printTemplate.dialog.messages.popupBlocked')) |
| | | return |
| | | } |
| | | |
| | | const content = printBodyRef.value.innerHTML |
| | | printSourceVisible.value = false |
| | | printWindow.document.open() |
| | | printWindow.document.write(buildPrintHtml(content, activeCanvas.value)) |
| | | printWindow.document.close() |
| | | } |
| | | |
| | | function buildPrintHtml(content, canvas) { |
| | | const printCanvas = canvas || { width: 70, height: 40 } |
| | | return ( |
| | | `<!DOCTYPE html> |
| | | <html lang="${locale.value === 'en' ? 'en' : 'zh-CN'}"> |
| | | <head> |
| | | <meta charset="UTF-8" /> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| | | <title>${t('pages.basicInfo.whMat.printTemplate.dialog.printDocumentTitle')}</title> |
| | | <style> |
| | | :root { |
| | | color-scheme: light; |
| | | } |
| | | * { |
| | | box-sizing: border-box; |
| | | -webkit-print-color-adjust: exact; |
| | | print-color-adjust: exact; |
| | | } |
| | | @page { |
| | | size: ${printCanvas.width}mm ${printCanvas.height}mm; |
| | | margin: 0; |
| | | } |
| | | html, |
| | | body { |
| | | margin: 0; |
| | | padding: 0; |
| | | background: #ffffff; |
| | | color: #111827; |
| | | font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif; |
| | | } |
| | | body { |
| | | display: block; |
| | | } |
| | | .wh-mat-print-dialog__print-pages { |
| | | display: block; |
| | | } |
| | | .wh-mat-print-dialog__preview-list { |
| | | display: block; |
| | | } |
| | | .wh-mat-print-dialog__preview-item { |
| | | display: block; |
| | | width: ${printCanvas.width}mm; |
| | | height: ${printCanvas.height}mm; |
| | | margin: 0; |
| | | overflow: hidden; |
| | | } |
| | | .wh-mat-print-dialog__preview-item + .wh-mat-print-dialog__preview-item { |
| | | page-break-before: always; |
| | | break-before: page; |
| | | } |
| | | .matnr-print-canvas-shell { |
| | | width: ${printCanvas.width}mm; |
| | | height: ${printCanvas.height}mm; |
| | | display: block; |
| | | overflow: hidden; |
| | | } |
| | | .matnr-print-canvas { |
| | | position: relative; |
| | | box-sizing: border-box; |
| | | display: block; |
| | | margin: 0; |
| | | box-shadow: none; |
| | | overflow: hidden; |
| | | } |
| | | .matnr-print-element { |
| | | position: absolute; |
| | | overflow: hidden; |
| | | } |
| | | .matnr-print-element__text, |
| | | .matnr-print-element__barcode, |
| | | .matnr-print-element__qrcode, |
| | | .matnr-print-element__rect, |
| | | .matnr-print-element__table { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | .matnr-print-element__text { |
| | | display: flex; |
| | | align-items: center; |
| | | white-space: pre-wrap; |
| | | word-break: break-word; |
| | | } |
| | | .matnr-print-element__barcode svg, |
| | | .matnr-print-element__qrcode svg { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: block; |
| | | } |
| | | .matnr-print-element__qrcode { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .matnr-print-element__table { |
| | | border-collapse: collapse; |
| | | table-layout: fixed; |
| | | } |
| | | .matnr-print-element__table td { |
| | | padding: 0.4mm 0.8mm; |
| | | vertical-align: middle; |
| | | word-break: break-word; |
| | | } |
| | | </style> |
| | | <scr` + |
| | | `ipt> |
| | | window.addEventListener('load', () => { |
| | | window.focus(); |
| | | setTimeout(() => window.print(), 60); |
| | | }); |
| | | window.addEventListener('afterprint', () => { |
| | | window.close(); |
| | | }); |
| | | </scr` + |
| | | `ipt> |
| | | </head> |
| | | <body><div class="wh-mat-print-dialog__print-pages">${content}</div></body> |
| | | </html>` |
| | | ) |
| | | } |
| | | |
| | | function waitForPaint() { |
| | | return new Promise((resolve) => { |
| | | requestAnimationFrame(() => resolve()) |
| | | }) |
| | | } |
| | | |
| | | function resolveNextScale(direction) { |
| | | if (direction > 0) { |
| | | return ( |
| | | PREVIEW_SCALE_STEPS.find((step) => step > previewScale.value + 0.001) || previewScale.value |
| | | ) |
| | | } |
| | | return ( |
| | | [...PREVIEW_SCALE_STEPS].reverse().find((step) => step < previewScale.value - 0.001) || |
| | | previewScale.value |
| | | ) |
| | | } |
| | | |
| | | function getPreviewViewportAvailableSize() { |
| | | const viewportElement = previewViewportRef.value |
| | | if (!viewportElement) { |
| | | return null |
| | | } |
| | | const viewportStyle = window.getComputedStyle(viewportElement) |
| | | const width = |
| | | viewportElement.clientWidth - |
| | | Number.parseFloat(viewportStyle.paddingLeft || '0') - |
| | | Number.parseFloat(viewportStyle.paddingRight || '0') - |
| | | PREVIEW_PANEL_GAP |
| | | const height = |
| | | viewportElement.clientHeight - |
| | | Number.parseFloat(viewportStyle.paddingTop || '0') - |
| | | Number.parseFloat(viewportStyle.paddingBottom || '0') - |
| | | PREVIEW_PANEL_GAP |
| | | if (width <= 0 || height <= 0) { |
| | | return null |
| | | } |
| | | return { width, height } |
| | | } |
| | | |
| | | function fitPreviewToViewport() { |
| | | const viewportSize = getPreviewViewportAvailableSize() |
| | | if (!viewportSize || !activeCanvas.value.width || !activeCanvas.value.height) { |
| | | return |
| | | } |
| | | const widthScale = |
| | | viewportSize.width / Math.max(Number(activeCanvas.value.width) * SCREEN_MM_TO_PX || 1, 1) |
| | | const heightScale = |
| | | viewportSize.height / Math.max(Number(activeCanvas.value.height) * SCREEN_MM_TO_PX || 1, 1) |
| | | const nextZoomRatio = Math.min( |
| | | widthScale, |
| | | heightScale, |
| | | MAX_PREVIEW_SCALE / DEFAULT_PREVIEW_SCALE |
| | | ) |
| | | const nextScale = DEFAULT_PREVIEW_SCALE * nextZoomRatio |
| | | previewScale.value = Number(Math.max(MIN_PREVIEW_SCALE, nextScale).toFixed(2)) |
| | | } |
| | | |
| | | function observePreviewViewport() { |
| | | if (!resizeObserver || !previewViewportRef.value) { |
| | | return |
| | | } |
| | | resizeObserver.disconnect() |
| | | resizeObserver.observe(previewViewportRef.value) |
| | | } |
| | | |
| | | function handleZoomIn() { |
| | | autoFitEnabled.value = false |
| | | previewScale.value = resolveNextScale(1) |
| | | } |
| | | |
| | | function handleZoomOut() { |
| | | autoFitEnabled.value = false |
| | | previewScale.value = resolveNextScale(-1) |
| | | } |
| | | |
| | | function handleZoomReset() { |
| | | autoFitEnabled.value = false |
| | | previewScale.value = DEFAULT_PREVIEW_SCALE |
| | | } |
| | | |
| | | async function handleZoomFit() { |
| | | autoFitEnabled.value = true |
| | | await nextTick() |
| | | fitPreviewToViewport() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | resizeObserver = new ResizeObserver(() => { |
| | | if (autoFitEnabled.value) { |
| | | fitPreviewToViewport() |
| | | } |
| | | }) |
| | | observePreviewViewport() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | resizeObserver?.disconnect() |
| | | resizeObserver = null |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .wh-mat-print-dialog { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16px; |
| | | height: min(70vh, 760px); |
| | | min-height: 420px; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__toolbar { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__toolbar-right { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-left: auto; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__zoom { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__copies { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__copies-label { |
| | | font-size: 13px; |
| | | color: var(--art-text-secondary); |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__copies-input { |
| | | width: 110px; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__template-select { |
| | | width: 320px; |
| | | max-width: 100%; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__summary { |
| | | font-size: 13px; |
| | | color: var(--art-text-secondary); |
| | | } |
| | | |
| | | .wh-mat-print-dialog__overflow-alert { |
| | | margin-bottom: -4px; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__preview-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | flex: 1; |
| | | min-height: 0; |
| | | gap: 16px; |
| | | overflow: auto; |
| | | padding: 12px; |
| | | background: |
| | | radial-gradient(circle at top left, rgba(59, 130, 246, 0.08), transparent 28%), |
| | | linear-gradient(180deg, #eef2f7 0%, #f8fafc 100%); |
| | | border-radius: 16px; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__preview-item { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | justify-content: center; |
| | | flex: none; |
| | | padding: 0; |
| | | border-radius: 0; |
| | | background: transparent; |
| | | box-shadow: none; |
| | | overflow: visible; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__preview-item :deep(.matnr-print-canvas-shell) { |
| | | width: auto; |
| | | min-width: 0; |
| | | min-height: 0; |
| | | display: block; |
| | | overflow: visible; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__preview-canvas { |
| | | transform-origin: center top; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__preview-canvas :deep(.matnr-print-canvas) { |
| | | box-shadow: 0 18px 36px rgba(15, 23, 42, 0.08); |
| | | } |
| | | |
| | | .wh-mat-print-dialog__print-source { |
| | | position: fixed; |
| | | left: -99999px; |
| | | top: 0; |
| | | visibility: hidden; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .wh-mat-print-dialog__footer { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-end; |
| | | gap: 12px; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <div class="wh-mat-print-template-manager__layout"> |
| | | <aside class="wh-mat-print-template-manager__sidebar"> |
| | | <div class="wh-mat-print-template-manager__sidebar-header"> |
| | | <div> |
| | | <div class="wh-mat-print-template-manager__title"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.workspace.sidebarTitle') }} |
| | | </div> |
| | | <div class="wh-mat-print-template-manager__subtitle"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.workspace.sidebarSubtitle') }} |
| | | </div> |
| | | </div> |
| | | <ElButton size="small" type="primary" @click="handleCreateTemplate"> |
| | | {{ t('common.actions.add') }} |
| | | </ElButton> |
| | | </div> |
| | | |
| | | <ElScrollbar class="wh-mat-print-template-manager__sidebar-scroll"> |
| | | <div class="wh-mat-print-template-manager__template-list"> |
| | | <button |
| | | v-for="template in templates" |
| | | :key="template.id || template.code" |
| | | type="button" |
| | | class="wh-mat-print-template-manager__template-card" |
| | | :class="{ 'is-active': template.code === activeTemplateCode }" |
| | | @click="selectTemplate(template.code)" |
| | | > |
| | | <div class="wh-mat-print-template-manager__template-card-header"> |
| | | <span>{{ template.name }}</span> |
| | | <ElTag v-if="template.isDefault === 1" size="small" type="success"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.workspace.defaultTag') }} |
| | | </ElTag> |
| | | </div> |
| | | <div class="wh-mat-print-template-manager__template-code">{{ template.code }}</div> |
| | | <div class="wh-mat-print-template-manager__template-actions"> |
| | | <ElButton size="small" text @click.stop="handleCopyTemplate(template.code)">{{ |
| | | t('pages.basicInfo.whMat.printTemplate.workspace.actions.copy') |
| | | }}</ElButton> |
| | | <ElButton size="small" text @click.stop="handleMarkDefault(template.code)"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.workspace.actions.setDefault') }} |
| | | </ElButton> |
| | | <ElButton |
| | | size="small" |
| | | text |
| | | type="danger" |
| | | @click.stop="handleDeleteTemplate(template.code)" |
| | | > |
| | | {{ t('common.actions.delete') }} |
| | | </ElButton> |
| | | </div> |
| | | </button> |
| | | </div> |
| | | </ElScrollbar> |
| | | </aside> |
| | | |
| | | <main class="wh-mat-print-template-manager__main"> |
| | | <div class="wh-mat-print-template-manager__main-header"> |
| | | <div> |
| | | <div class="wh-mat-print-template-manager__title"> |
| | | {{ |
| | | activeTemplate?.name || |
| | | t('pages.basicInfo.whMat.printTemplate.workspace.unnamedTemplate') |
| | | }} |
| | | </div> |
| | | <div class="wh-mat-print-template-manager__subtitle"> |
| | | {{ |
| | | t('pages.basicInfo.whMat.printTemplate.workspace.previewRecord', { |
| | | name: |
| | | previewRecord?.code || |
| | | previewRecord?.name || |
| | | t('pages.basicInfo.whMat.printTemplate.workspace.noPreviewRecord') |
| | | }) |
| | | }} |
| | | </div> |
| | | </div> |
| | | <ElSpace wrap> |
| | | <ElButton :loading="loading" @click="handleReload"> |
| | | {{ t('common.actions.refresh') }} |
| | | </ElButton> |
| | | <ElButton type="primary" :loading="saving" @click="handleSaveTemplate"> |
| | | {{ t('pages.basicInfo.whMat.printTemplate.workspace.actions.saveTemplate') }} |
| | | </ElButton> |
| | | </ElSpace> |
| | | </div> |
| | | |
| | | <MatnrPrintToolbar |
| | | :zoom-percent="zoomPercent" |
| | | :can-zoom-in="canZoomIn" |
| | | :can-zoom-out="canZoomOut" |
| | | :auto-fit-active="autoFitEnabled" |
| | | @add-element="handleAddElement" |
| | | @zoom-in="handleZoomIn" |
| | | @zoom-out="handleZoomOut" |
| | | @zoom-reset="handleZoomReset" |
| | | @zoom-fit="handleZoomFit" |
| | | /> |
| | | |
| | | <div ref="canvasPanelRef" class="wh-mat-print-template-manager__canvas-panel"> |
| | | <ElEmpty |
| | | v-if="!activeTemplate" |
| | | :description="t('pages.basicInfo.whMat.printTemplate.workspace.emptyTemplate')" |
| | | /> |
| | | <MatnrPrintCanvas |
| | | v-else |
| | | :template="activeTemplate" |
| | | :active-record="previewRecord" |
| | | :selected-element-id="selectedElementId" |
| | | mode="editor" |
| | | :scale="canvasScale" |
| | | :interactive="true" |
| | | :show-grid="true" |
| | | @select-element="selectedElementId = $event" |
| | | @update-element="handleElementPatch" |
| | | /> |
| | | </div> |
| | | </main> |
| | | |
| | | <aside class="wh-mat-print-template-manager__property"> |
| | | <MatnrPrintPropertyPanel |
| | | v-if="activeTemplate" |
| | | :template="activeTemplate" |
| | | :selected-element="selectedElement" |
| | | @update-template-meta="handleTemplateMetaPatch" |
| | | @update-canvas="handleCanvasPatch" |
| | | @update-element="handleElementPatch" |
| | | @remove-element="handleRemoveElement" |
| | | @set-placeholder-target="placeholderTarget = $event" |
| | | /> |
| | | <MatnrPrintFieldPanel |
| | | v-if="activeTemplate" |
| | | :fields="fieldOptions" |
| | | @insert-field="handleInsertPlaceholder" |
| | | /> |
| | | </aside> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' |
| | | import { useI18n } from 'vue-i18n' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { |
| | | appendFieldPlaceholder, |
| | | buildMatnrPrintFieldOptions, |
| | | buildMatnrPrintTemplatePayload, |
| | | createDefaultMatnrPrintTemplate, |
| | | createElementByType, |
| | | duplicateMatnrPrintTemplate, |
| | | getFieldListTableRows, |
| | | normalizeMatnrPrintTemplate, |
| | | updateFieldListTableRows |
| | | } from '../matnrPrintTemplate.helpers' |
| | | import { |
| | | fetchGetDefaultMatnrPrintTemplate, |
| | | fetchGetMatnrPrintTemplateDetail, |
| | | fetchMatnrPrintTemplateList, |
| | | fetchRemoveMatnrPrintTemplate, |
| | | fetchSaveMatnrPrintTemplate, |
| | | fetchSetDefaultMatnrPrintTemplate, |
| | | fetchUpdateMatnrPrintTemplate |
| | | } from '@/api/wh-mat' |
| | | import MatnrPrintCanvas from './matnr-print-canvas.vue' |
| | | import MatnrPrintFieldPanel from './matnr-print-field-panel.vue' |
| | | import MatnrPrintPropertyPanel from './matnr-print-property-panel.vue' |
| | | import MatnrPrintToolbar from './matnr-print-toolbar.vue' |
| | | |
| | | defineOptions({ name: 'WhMatPrintTemplateWorkspace' }) |
| | | |
| | | const { t, locale } = useI18n() |
| | | |
| | | const props = defineProps({ |
| | | enabledFields: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | previewRecord: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | autoLoad: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | }) |
| | | |
| | | const DEFAULT_CANVAS_SCALE = 4 |
| | | const MIN_CANVAS_SCALE = 0.5 |
| | | const MAX_CANVAS_SCALE = 24 |
| | | const CANVAS_SCALE_STEPS = [0.5, 0.75, 1, 1.5, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 20, 24] |
| | | const FIT_PANEL_GAP = 16 |
| | | |
| | | const templates = ref([]) |
| | | const activeTemplateCode = ref('') |
| | | const selectedElementId = ref('') |
| | | const placeholderTarget = ref(null) |
| | | const loading = ref(false) |
| | | const saving = ref(false) |
| | | const canvasScale = ref(DEFAULT_CANVAS_SCALE) |
| | | const autoFitEnabled = ref(true) |
| | | const canvasPanelRef = ref(null) |
| | | let resizeObserver = null |
| | | |
| | | const fieldOptions = computed(() => { |
| | | locale.value |
| | | return buildMatnrPrintFieldOptions(props.enabledFields) |
| | | }) |
| | | const zoomPercent = computed(() => Math.round((canvasScale.value / DEFAULT_CANVAS_SCALE) * 100)) |
| | | const canZoomIn = computed(() => canvasScale.value < MAX_CANVAS_SCALE) |
| | | const canZoomOut = computed(() => canvasScale.value > MIN_CANVAS_SCALE) |
| | | const activeTemplate = computed( |
| | | () => templates.value.find((template) => template.code === activeTemplateCode.value) || null |
| | | ) |
| | | const selectedElement = computed( |
| | | () => |
| | | activeTemplate.value?.elements?.find((element) => element.id === selectedElementId.value) || |
| | | null |
| | | ) |
| | | |
| | | watch( |
| | | () => props.autoLoad, |
| | | async (autoLoad, previousValue) => { |
| | | if (autoLoad && !previousValue) { |
| | | await loadTemplates() |
| | | } |
| | | }, |
| | | { |
| | | immediate: true |
| | | } |
| | | ) |
| | | |
| | | watch( |
| | | () => |
| | | `${activeTemplate.value?.canvas?.width || 0}_${activeTemplate.value?.canvas?.height || 0}`, |
| | | async () => { |
| | | if (!autoFitEnabled.value || !activeTemplate.value) { |
| | | return |
| | | } |
| | | await nextTick() |
| | | fitCanvasToViewport() |
| | | } |
| | | ) |
| | | |
| | | async function loadTemplates() { |
| | | loading.value = true |
| | | try { |
| | | const [list, defaultTemplate] = await Promise.all([ |
| | | fetchMatnrPrintTemplateList({}), |
| | | fetchGetDefaultMatnrPrintTemplate() |
| | | ]) |
| | | const nextTemplates = (Array.isArray(list) ? list : []) |
| | | .map((item) => normalizeMatnrPrintTemplate(item)) |
| | | .sort((left, right) => Number(right.isDefault) - Number(left.isDefault)) |
| | | |
| | | templates.value = nextTemplates |
| | | if (!templates.value.length) { |
| | | handleCreateTemplate() |
| | | return |
| | | } |
| | | |
| | | const defaultCode = normalizeMatnrPrintTemplate(defaultTemplate || {}).code |
| | | const nextActiveCode = |
| | | templates.value.find((item) => item.code === activeTemplateCode.value)?.code || |
| | | templates.value.find((item) => item.code === defaultCode)?.code || |
| | | templates.value[0]?.code || |
| | | '' |
| | | selectTemplate(nextActiveCode) |
| | | } catch (error) { |
| | | ElMessage.error( |
| | | error?.message || t('pages.basicInfo.whMat.printTemplate.workspace.messages.loadFailed') |
| | | ) |
| | | if (!templates.value.length) { |
| | | handleCreateTemplate() |
| | | } |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | function handleReload() { |
| | | loadTemplates() |
| | | } |
| | | |
| | | function resolveNextCanvasScale(direction) { |
| | | if (direction > 0) { |
| | | return ( |
| | | CANVAS_SCALE_STEPS.find((step) => step > canvasScale.value + 0.001) || canvasScale.value |
| | | ) |
| | | } |
| | | return ( |
| | | [...CANVAS_SCALE_STEPS].reverse().find((step) => step < canvasScale.value - 0.001) || |
| | | canvasScale.value |
| | | ) |
| | | } |
| | | |
| | | function handleZoomIn() { |
| | | autoFitEnabled.value = false |
| | | canvasScale.value = resolveNextCanvasScale(1) |
| | | } |
| | | |
| | | function handleZoomOut() { |
| | | autoFitEnabled.value = false |
| | | canvasScale.value = resolveNextCanvasScale(-1) |
| | | } |
| | | |
| | | function handleZoomReset() { |
| | | autoFitEnabled.value = false |
| | | canvasScale.value = DEFAULT_CANVAS_SCALE |
| | | } |
| | | |
| | | function getCanvasPanelAvailableSize() { |
| | | const panelElement = canvasPanelRef.value |
| | | if (!panelElement) { |
| | | return null |
| | | } |
| | | const panelStyle = window.getComputedStyle(panelElement) |
| | | const width = |
| | | panelElement.clientWidth - |
| | | Number.parseFloat(panelStyle.paddingLeft || '0') - |
| | | Number.parseFloat(panelStyle.paddingRight || '0') - |
| | | FIT_PANEL_GAP |
| | | const height = |
| | | panelElement.clientHeight - |
| | | Number.parseFloat(panelStyle.paddingTop || '0') - |
| | | Number.parseFloat(panelStyle.paddingBottom || '0') - |
| | | FIT_PANEL_GAP |
| | | if (width <= 0 || height <= 0) { |
| | | return null |
| | | } |
| | | return { width, height } |
| | | } |
| | | |
| | | function fitCanvasToViewport() { |
| | | if (!activeTemplate.value) { |
| | | return |
| | | } |
| | | const panelSize = getCanvasPanelAvailableSize() |
| | | if (!panelSize) { |
| | | return |
| | | } |
| | | const canvas = activeTemplate.value.canvas || {} |
| | | const widthScale = panelSize.width / Math.max(Number(canvas.width) || 1, 1) |
| | | const heightScale = panelSize.height / Math.max(Number(canvas.height) || 1, 1) |
| | | const nextScale = Math.min(widthScale, heightScale, MAX_CANVAS_SCALE) |
| | | canvasScale.value = Number(Math.max(MIN_CANVAS_SCALE, nextScale).toFixed(2)) |
| | | } |
| | | |
| | | async function handleZoomFit() { |
| | | autoFitEnabled.value = true |
| | | await nextTick() |
| | | fitCanvasToViewport() |
| | | } |
| | | |
| | | function selectTemplate(code) { |
| | | activeTemplateCode.value = code |
| | | selectedElementId.value = '' |
| | | placeholderTarget.value = null |
| | | if (autoFitEnabled.value) { |
| | | nextTick(() => { |
| | | fitCanvasToViewport() |
| | | }) |
| | | } |
| | | } |
| | | |
| | | function replaceActiveTemplate(patch) { |
| | | templates.value = templates.value.map((template) => |
| | | template.code === activeTemplateCode.value |
| | | ? normalizeMatnrPrintTemplate({ ...template, ...patch }) |
| | | : template |
| | | ) |
| | | } |
| | | |
| | | function handleTemplateMetaPatch({ field, value }) { |
| | | if (!activeTemplate.value) { |
| | | return |
| | | } |
| | | if (field === 'isDefault' && value === 1) { |
| | | templates.value = templates.value.map((template) => ({ |
| | | ...template, |
| | | isDefault: template.code === activeTemplateCode.value ? 1 : 0 |
| | | })) |
| | | return |
| | | } |
| | | replaceActiveTemplate({ |
| | | [field]: value |
| | | }) |
| | | } |
| | | |
| | | function handleCanvasPatch({ field, value }) { |
| | | if (!activeTemplate.value) { |
| | | return |
| | | } |
| | | replaceActiveTemplate({ |
| | | canvas: { |
| | | ...(activeTemplate.value.canvas || {}), |
| | | [field]: value |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function handleElementPatch({ id, patch }) { |
| | | if (!activeTemplate.value || !id) { |
| | | return |
| | | } |
| | | const nextElements = activeTemplate.value.elements.map((element) => |
| | | element.id === id |
| | | ? normalizeMatnrPrintTemplate({ elements: [{ ...element, ...patch }] }).elements[0] |
| | | : element |
| | | ) |
| | | replaceActiveTemplate({ |
| | | elements: nextElements |
| | | }) |
| | | } |
| | | |
| | | function handleAddElement(type) { |
| | | if (!activeTemplate.value) { |
| | | return |
| | | } |
| | | const newElement = createElementByType(type, activeTemplate.value.elements.length) |
| | | replaceActiveTemplate({ |
| | | elements: [...activeTemplate.value.elements, newElement] |
| | | }) |
| | | selectedElementId.value = newElement.id |
| | | } |
| | | |
| | | function handleRemoveElement(id) { |
| | | if (!activeTemplate.value) { |
| | | return |
| | | } |
| | | replaceActiveTemplate({ |
| | | elements: activeTemplate.value.elements.filter((element) => element.id !== id) |
| | | }) |
| | | if (selectedElementId.value === id) { |
| | | selectedElementId.value = '' |
| | | } |
| | | } |
| | | |
| | | function handleCreateTemplate() { |
| | | const newTemplate = createDefaultMatnrPrintTemplate({ |
| | | name: `${t('pages.basicInfo.whMat.printTemplate.helpers.defaultTemplateName')}${templates.value.length + 1}`, |
| | | code: `MATNR_${Date.now()}_${templates.value.length + 1}` |
| | | }) |
| | | templates.value = [ |
| | | ...templates.value.map((item) => ({ ...item, isDefault: item.isDefault })), |
| | | newTemplate |
| | | ] |
| | | selectTemplate(newTemplate.code) |
| | | } |
| | | |
| | | function handleCopyTemplate(code) { |
| | | const template = templates.value.find((item) => item.code === code) |
| | | if (!template) { |
| | | return |
| | | } |
| | | const copyTemplate = duplicateMatnrPrintTemplate(template) |
| | | templates.value = [...templates.value, copyTemplate] |
| | | selectTemplate(copyTemplate.code) |
| | | } |
| | | |
| | | async function handleDeleteTemplate(code) { |
| | | const template = templates.value.find((item) => item.code === code) |
| | | if (!template) { |
| | | return |
| | | } |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | t('pages.basicInfo.whMat.printTemplate.workspace.messages.deleteConfirm', { |
| | | name: template.name |
| | | }), |
| | | t('pages.basicInfo.whMat.printTemplate.workspace.actions.delete'), |
| | | { |
| | | type: 'warning' |
| | | } |
| | | ) |
| | | if (template.id) { |
| | | await fetchRemoveMatnrPrintTemplate(template.id) |
| | | } |
| | | templates.value = templates.value.filter((item) => item.code !== code) |
| | | if (!templates.value.length) { |
| | | handleCreateTemplate() |
| | | } else if (activeTemplateCode.value === code) { |
| | | selectTemplate(templates.value[0].code) |
| | | } |
| | | ElMessage.success(t('pages.basicInfo.whMat.printTemplate.workspace.messages.deleteSuccess')) |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | ElMessage.error( |
| | | error?.message || t('pages.basicInfo.whMat.printTemplate.workspace.messages.deleteFailed') |
| | | ) |
| | | } |
| | | } |
| | | } |
| | | |
| | | async function handleMarkDefault(code) { |
| | | const template = templates.value.find((item) => item.code === code) |
| | | if (!template) { |
| | | return |
| | | } |
| | | templates.value = templates.value.map((item) => ({ |
| | | ...item, |
| | | isDefault: item.code === code ? 1 : 0 |
| | | })) |
| | | if (template.id) { |
| | | try { |
| | | await fetchSetDefaultMatnrPrintTemplate(template.id) |
| | | ElMessage.success( |
| | | t('pages.basicInfo.whMat.printTemplate.workspace.messages.defaultSuccess') |
| | | ) |
| | | } catch (error) { |
| | | ElMessage.error( |
| | | error?.message || |
| | | t('pages.basicInfo.whMat.printTemplate.workspace.messages.defaultFailed') |
| | | ) |
| | | } |
| | | } |
| | | } |
| | | |
| | | async function handleSaveTemplate() { |
| | | if (!activeTemplate.value) { |
| | | return |
| | | } |
| | | saving.value = true |
| | | try { |
| | | const payload = buildMatnrPrintTemplatePayload(activeTemplate.value) |
| | | const saved = activeTemplate.value.id |
| | | ? await fetchUpdateMatnrPrintTemplate(payload) |
| | | : await fetchSaveMatnrPrintTemplate(payload) |
| | | const normalized = normalizeMatnrPrintTemplate(saved) |
| | | templates.value = templates.value.map((template) => |
| | | template.code === activeTemplateCode.value |
| | | ? normalized |
| | | : { ...template, isDefault: normalized.isDefault === 1 ? 0 : template.isDefault } |
| | | ) |
| | | if (!templates.value.find((template) => template.code === normalized.code)) { |
| | | templates.value = [...templates.value, normalized] |
| | | } |
| | | if (normalized.isDefault === 1) { |
| | | templates.value = templates.value.map((template) => ({ |
| | | ...template, |
| | | isDefault: template.id === normalized.id ? 1 : 0 |
| | | })) |
| | | } |
| | | activeTemplateCode.value = normalized.code |
| | | if (normalized.id) { |
| | | const detail = await fetchGetMatnrPrintTemplateDetail(normalized.id) |
| | | const nextTemplate = normalizeMatnrPrintTemplate(detail) |
| | | templates.value = templates.value.map((template) => |
| | | template.code === normalized.code ? nextTemplate : template |
| | | ) |
| | | } |
| | | ElMessage.success(t('pages.basicInfo.whMat.printTemplate.workspace.messages.saveSuccess')) |
| | | } catch (error) { |
| | | ElMessage.error( |
| | | error?.message || t('pages.basicInfo.whMat.printTemplate.workspace.messages.saveFailed') |
| | | ) |
| | | } finally { |
| | | saving.value = false |
| | | } |
| | | } |
| | | |
| | | function handleInsertPlaceholder(placeholder) { |
| | | if (!activeTemplate.value || !placeholderTarget.value || !selectedElement.value) { |
| | | return |
| | | } |
| | | const target = placeholderTarget.value |
| | | if (target.field === 'contentTemplate' || target.field === 'valueTemplate') { |
| | | handleElementPatch({ |
| | | id: selectedElement.value.id, |
| | | patch: { |
| | | [target.field]: appendFieldPlaceholder(selectedElement.value[target.field], placeholder) |
| | | } |
| | | }) |
| | | return |
| | | } |
| | | if (target.field === 'table') { |
| | | const rows = getFieldListTableRows(selectedElement.value).map((row, rowIndex) => |
| | | rowIndex === target.rowIndex |
| | | ? { |
| | | ...row, |
| | | [target.rowField]: appendFieldPlaceholder(row[target.rowField], placeholder) |
| | | } |
| | | : row |
| | | ) |
| | | handleElementPatch({ |
| | | id: selectedElement.value.id, |
| | | patch: updateFieldListTableRows(selectedElement.value, rows) |
| | | }) |
| | | } |
| | | } |
| | | |
| | | defineExpose({ |
| | | loadTemplates |
| | | }) |
| | | |
| | | onMounted(() => { |
| | | resizeObserver = new ResizeObserver(() => { |
| | | if (autoFitEnabled.value) { |
| | | fitCanvasToViewport() |
| | | } |
| | | }) |
| | | if (canvasPanelRef.value) { |
| | | resizeObserver.observe(canvasPanelRef.value) |
| | | } |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | resizeObserver?.disconnect() |
| | | resizeObserver = null |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .wh-mat-print-template-manager__layout { |
| | | display: grid; |
| | | grid-template-columns: 280px minmax(0, 1fr) 360px; |
| | | height: 100%; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | background: |
| | | radial-gradient(circle at top left, rgba(59, 130, 246, 0.08), transparent 26%), |
| | | linear-gradient(180deg, #f8fafc 0%, #eef2f7 100%); |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__sidebar, |
| | | .wh-mat-print-template-manager__property { |
| | | display: flex; |
| | | flex-direction: column; |
| | | min-width: 0; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | background: rgba(255, 255, 255, 0.94); |
| | | border-right: 1px solid rgba(148, 163, 184, 0.18); |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__property { |
| | | border-right: none; |
| | | border-left: 1px solid rgba(148, 163, 184, 0.18); |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__sidebar-header, |
| | | .wh-mat-print-template-manager__main-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 16px; |
| | | padding: 16px; |
| | | border-bottom: 1px solid rgba(148, 163, 184, 0.18); |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__sidebar-scroll { |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__template-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | padding: 16px; |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__template-card { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | padding: 14px; |
| | | border: 1px solid rgba(148, 163, 184, 0.18); |
| | | border-radius: 16px; |
| | | background: #ffffff; |
| | | text-align: left; |
| | | cursor: pointer; |
| | | transition: |
| | | transform 0.2s ease, |
| | | border-color 0.2s ease, |
| | | box-shadow 0.2s ease; |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__template-card:hover, |
| | | .wh-mat-print-template-manager__template-card.is-active { |
| | | border-color: rgba(37, 99, 235, 0.45); |
| | | transform: translateY(-1px); |
| | | box-shadow: 0 12px 28px rgba(37, 99, 235, 0.08); |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__template-card-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 8px; |
| | | font-size: 14px; |
| | | font-weight: 700; |
| | | color: var(--art-text-primary); |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__template-code, |
| | | .wh-mat-print-template-manager__subtitle { |
| | | font-size: 12px; |
| | | color: var(--art-text-secondary); |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__template-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__main { |
| | | display: flex; |
| | | flex-direction: column; |
| | | min-width: 0; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__canvas-panel { |
| | | flex: 1; |
| | | min-width: 0; |
| | | min-height: 0; |
| | | display: flex; |
| | | padding: 16px; |
| | | overflow: auto; |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__title { |
| | | font-size: 16px; |
| | | font-weight: 700; |
| | | color: var(--art-text-primary); |
| | | } |
| | | |
| | | @media (max-width: 1440px) { |
| | | .wh-mat-print-template-manager__layout { |
| | | grid-template-columns: 240px minmax(0, 1fr) 320px; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 1180px) { |
| | | .wh-mat-print-template-manager__layout { |
| | | grid-template-columns: minmax(0, 1fr); |
| | | grid-template-rows: minmax(0, 0.82fr) minmax(0, 1.45fr) minmax(0, 1fr); |
| | | } |
| | | |
| | | .wh-mat-print-template-manager__sidebar, |
| | | .wh-mat-print-template-manager__property { |
| | | border-left: none; |
| | | border-right: none; |
| | | border-bottom: 1px solid rgba(148, 163, 184, 0.18); |
| | | } |
| | | } |
| | | </style> |
| | |
| | | @Autowired |
| | | private CompanysService companysService; |
| | | |
| | | @PreAuthorize("hasAuthority('manager:locItem:list')") |
| | | @PreAuthorize("hasAnyAuthority('manager:locItem:list','manager:locPreview:list')") |
| | | @PostMapping("/locItem/page") |
| | | public R page(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| New file |
| | |
| | | package com.vincent.rsf.server.manager.controller; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.common.annotation.OperationLog; |
| | | import com.vincent.rsf.server.manager.entity.MatnrPrintTemplate; |
| | | import com.vincent.rsf.server.manager.service.MatnrPrintTemplateService; |
| | | import com.vincent.rsf.server.manager.utils.buildPageRowsUtils; |
| | | import com.vincent.rsf.server.system.controller.BaseController; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @RestController |
| | | @Api(tags = "物料打印模板接口") |
| | | public class MatnrPrintTemplateController extends BaseController { |
| | | |
| | | private final MatnrPrintTemplateService matnrPrintTemplateService; |
| | | |
| | | public MatnrPrintTemplateController(MatnrPrintTemplateService matnrPrintTemplateService) { |
| | | this.matnrPrintTemplateService = matnrPrintTemplateService; |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:matnrPrintTemplate:list')") |
| | | @ApiOperation("查询物料打印模板列表") |
| | | @PostMapping("/matnrPrintTemplate/list") |
| | | public R list(@RequestBody(required = false) Map<String, Object> params) { |
| | | List<MatnrPrintTemplate> templates = matnrPrintTemplateService.listCurrentTenantTemplates(); |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(templates)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:matnrPrintTemplate:list')") |
| | | @ApiOperation("获取物料打印模板详情") |
| | | @GetMapping("/matnrPrintTemplate/{id}") |
| | | public R get(@PathVariable("id") Long id) { |
| | | return R.ok(buildPageRowsUtils.rowsMap(matnrPrintTemplateService.getCurrentTenantTemplate(id))); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:matnrPrintTemplate:list')") |
| | | @ApiOperation("获取默认物料打印模板") |
| | | @GetMapping("/matnrPrintTemplate/default") |
| | | public R getDefaultTemplate() { |
| | | MatnrPrintTemplate template = matnrPrintTemplateService.getCurrentTenantDefaultTemplate(); |
| | | if (template == null) { |
| | | return R.ok(); |
| | | } |
| | | return R.ok(buildPageRowsUtils.rowsMap(template)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:matnrPrintTemplate:save')") |
| | | @OperationLog("Create 物料打印模板") |
| | | @ApiOperation("保存物料打印模板") |
| | | @PostMapping("/matnrPrintTemplate/save") |
| | | public R save(@RequestBody Map<String, Object> params) { |
| | | MatnrPrintTemplate template = JSONObject.parseObject(JSONObject.toJSONString(params), MatnrPrintTemplate.class); |
| | | if (template == null) { |
| | | throw new CoolException("模板参数不能为空"); |
| | | } |
| | | MatnrPrintTemplate saved = matnrPrintTemplateService.saveTemplate(template); |
| | | return R.ok("Create Success").add(buildPageRowsUtils.rowsMap(saved)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:matnrPrintTemplate:update')") |
| | | @OperationLog("Update 物料打印模板") |
| | | @ApiOperation("更新物料打印模板") |
| | | @PostMapping("/matnrPrintTemplate/update") |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R update(@RequestBody Map<String, Object> params) { |
| | | MatnrPrintTemplate template = JSONObject.parseObject(JSONObject.toJSONString(params), MatnrPrintTemplate.class); |
| | | if (template == null || template.getId() == null) { |
| | | throw new CoolException("模板ID不能为空"); |
| | | } |
| | | MatnrPrintTemplate updated = matnrPrintTemplateService.updateTemplate(template); |
| | | return R.ok("Update Success").add(buildPageRowsUtils.rowsMap(updated)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:matnrPrintTemplate:remove')") |
| | | @OperationLog("Delete 物料打印模板") |
| | | @ApiOperation("删除物料打印模板") |
| | | @PostMapping("/matnrPrintTemplate/remove/{ids}") |
| | | public R remove(@PathVariable Long[] ids) { |
| | | matnrPrintTemplateService.removeTemplates(Arrays.asList(ids)); |
| | | return R.ok("Delete Success").add(buildPageRowsUtils.rowsMap(ids)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:matnrPrintTemplate:update')") |
| | | @OperationLog("Set Default 物料打印模板") |
| | | @ApiOperation("设置默认物料打印模板") |
| | | @PostMapping("/matnrPrintTemplate/default/{id}") |
| | | public R setDefault(@PathVariable("id") Long id) { |
| | | matnrPrintTemplateService.setDefaultTemplate(id); |
| | | return R.ok("Update Success"); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.manager.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableLogic; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serializable; |
| | | import java.util.Date; |
| | | import java.util.Map; |
| | | |
| | | @Data |
| | | @Accessors(chain = true) |
| | | @TableName(value = "man_matnr_print_template", autoResultMap = true) |
| | | @ApiModel(value = "MatnrPrintTemplate", description = "物料打印模板") |
| | | public class MatnrPrintTemplate implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @ApiModelProperty(value = "ID") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty(value = "租户") |
| | | private Long tenantId; |
| | | |
| | | @ApiModelProperty(value = "模板名称") |
| | | private String name; |
| | | |
| | | @ApiModelProperty(value = "模板编码") |
| | | private String code; |
| | | |
| | | @ApiModelProperty(value = "是否默认模板") |
| | | private Integer isDefault; |
| | | |
| | | @ApiModelProperty(value = "状态 1: 正常 0: 冻结") |
| | | private Integer status; |
| | | |
| | | @ApiModelProperty(value = "模板画布 JSON") |
| | | @TableField(value = "canvas_json", typeHandler = JacksonTypeHandler.class) |
| | | private Map<String, Object> canvasJson; |
| | | |
| | | @ApiModelProperty(value = "是否删除 1: 是 0: 否") |
| | | @TableLogic |
| | | private Integer deleted; |
| | | |
| | | @ApiModelProperty(value = "添加人员") |
| | | private Long createBy; |
| | | |
| | | @ApiModelProperty(value = "添加时间") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private Date createTime; |
| | | |
| | | @ApiModelProperty(value = "修改人员") |
| | | private Long updateBy; |
| | | |
| | | @ApiModelProperty(value = "修改时间") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private Date updateTime; |
| | | |
| | | @ApiModelProperty(value = "备注") |
| | | private String memo; |
| | | |
| | | @TableField(exist = false) |
| | | private String tenantId$; |
| | | |
| | | @TableField(exist = false) |
| | | private String createBy$; |
| | | |
| | | @TableField(exist = false) |
| | | private String updateBy$; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.manager.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.vincent.rsf.server.manager.entity.MatnrPrintTemplate; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | @Mapper |
| | | @Repository |
| | | public interface MatnrPrintTemplateMapper extends BaseMapper<MatnrPrintTemplate> { |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.manager.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.server.manager.entity.MatnrPrintTemplate; |
| | | |
| | | import java.util.List; |
| | | |
| | | public interface MatnrPrintTemplateService extends IService<MatnrPrintTemplate> { |
| | | |
| | | List<MatnrPrintTemplate> listCurrentTenantTemplates(); |
| | | |
| | | MatnrPrintTemplate getCurrentTenantTemplate(Long id); |
| | | |
| | | MatnrPrintTemplate getCurrentTenantDefaultTemplate(); |
| | | |
| | | MatnrPrintTemplate saveTemplate(MatnrPrintTemplate template); |
| | | |
| | | MatnrPrintTemplate updateTemplate(MatnrPrintTemplate template); |
| | | |
| | | boolean removeTemplates(List<Long> ids); |
| | | |
| | | boolean setDefaultTemplate(Long id); |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.manager.service.impl; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.manager.entity.MatnrPrintTemplate; |
| | | import com.vincent.rsf.server.manager.mapper.MatnrPrintTemplateMapper; |
| | | import com.vincent.rsf.server.manager.service.MatnrPrintTemplateService; |
| | | import com.vincent.rsf.server.system.entity.User; |
| | | import org.springframework.security.core.Authentication; |
| | | import org.springframework.security.core.context.SecurityContextHolder; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | |
| | | @Service("matnrPrintTemplateService") |
| | | public class MatnrPrintTemplateServiceImpl |
| | | extends ServiceImpl<MatnrPrintTemplateMapper, MatnrPrintTemplate> |
| | | implements MatnrPrintTemplateService { |
| | | |
| | | private static final Set<String> SUPPORTED_ELEMENT_TYPES = Collections.unmodifiableSet( |
| | | new LinkedHashSet<>(Arrays.asList("text", "barcode", "qrcode", "line", "rect", "table")) |
| | | ); |
| | | |
| | | @Override |
| | | public List<MatnrPrintTemplate> listCurrentTenantTemplates() { |
| | | List<MatnrPrintTemplate> templates = this.list(new LambdaQueryWrapper<MatnrPrintTemplate>() |
| | | .orderByDesc(MatnrPrintTemplate::getIsDefault) |
| | | .orderByDesc(MatnrPrintTemplate::getUpdateTime) |
| | | .orderByDesc(MatnrPrintTemplate::getCreateTime) |
| | | ); |
| | | return templates == null ? new ArrayList<>() : templates; |
| | | } |
| | | |
| | | @Override |
| | | public MatnrPrintTemplate getCurrentTenantTemplate(Long id) { |
| | | if (id == null) { |
| | | throw new CoolException("模板ID不能为空"); |
| | | } |
| | | MatnrPrintTemplate template = this.getById(id); |
| | | if (template == null) { |
| | | throw new CoolException("模板不存在或已被删除"); |
| | | } |
| | | return template; |
| | | } |
| | | |
| | | @Override |
| | | public MatnrPrintTemplate getCurrentTenantDefaultTemplate() { |
| | | MatnrPrintTemplate template = this.getOne(new LambdaQueryWrapper<MatnrPrintTemplate>() |
| | | .eq(MatnrPrintTemplate::getStatus, 1) |
| | | .eq(MatnrPrintTemplate::getIsDefault, 1) |
| | | .orderByDesc(MatnrPrintTemplate::getUpdateTime) |
| | | .last("limit 1") |
| | | ); |
| | | if (template != null) { |
| | | return template; |
| | | } |
| | | return this.getOne(new LambdaQueryWrapper<MatnrPrintTemplate>() |
| | | .eq(MatnrPrintTemplate::getStatus, 1) |
| | | .orderByDesc(MatnrPrintTemplate::getUpdateTime) |
| | | .orderByDesc(MatnrPrintTemplate::getCreateTime) |
| | | .last("limit 1") |
| | | ); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MatnrPrintTemplate saveTemplate(MatnrPrintTemplate template) { |
| | | MatnrPrintTemplate normalized = prepareTemplateForSave(template, false); |
| | | long currentCount = this.count(); |
| | | boolean shouldDefault = Objects.equals(normalized.getIsDefault(), 1) || currentCount == 0; |
| | | normalized.setIsDefault(shouldDefault ? 1 : 0); |
| | | if (shouldDefault) { |
| | | clearCurrentTenantDefaults(); |
| | | } |
| | | if (!this.save(normalized)) { |
| | | throw new CoolException("模板保存失败"); |
| | | } |
| | | return this.getCurrentTenantTemplate(normalized.getId()); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MatnrPrintTemplate updateTemplate(MatnrPrintTemplate template) { |
| | | if (template == null || template.getId() == null) { |
| | | throw new CoolException("模板ID不能为空"); |
| | | } |
| | | MatnrPrintTemplate existing = getCurrentTenantTemplate(template.getId()); |
| | | MatnrPrintTemplate normalized = prepareTemplateForSave(template, true); |
| | | normalized.setTenantId(existing.getTenantId()); |
| | | normalized.setCreateBy(existing.getCreateBy()); |
| | | normalized.setCreateTime(existing.getCreateTime()); |
| | | normalized.setDeleted(existing.getDeleted()); |
| | | boolean shouldDefault = Objects.equals(normalized.getIsDefault(), 1); |
| | | if (shouldDefault) { |
| | | clearCurrentTenantDefaults(); |
| | | } else if (Objects.equals(existing.getIsDefault(), 1)) { |
| | | normalized.setIsDefault(1); |
| | | } |
| | | if (!this.updateById(normalized)) { |
| | | throw new CoolException("模板更新失败"); |
| | | } |
| | | ensureOneDefaultTemplate(); |
| | | return this.getCurrentTenantTemplate(normalized.getId()); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean removeTemplates(List<Long> ids) { |
| | | if (ids == null || ids.isEmpty()) { |
| | | throw new CoolException("请选择要删除的模板"); |
| | | } |
| | | List<MatnrPrintTemplate> templates = this.listByIds(ids); |
| | | if (templates == null || templates.isEmpty()) { |
| | | return true; |
| | | } |
| | | boolean removedDefault = templates.stream().anyMatch(item -> Objects.equals(item.getIsDefault(), 1)); |
| | | if (!this.removeByIds(ids)) { |
| | | throw new CoolException("模板删除失败"); |
| | | } |
| | | if (removedDefault) { |
| | | ensureOneDefaultTemplate(); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean setDefaultTemplate(Long id) { |
| | | MatnrPrintTemplate template = getCurrentTenantTemplate(id); |
| | | clearCurrentTenantDefaults(); |
| | | boolean updated = this.update(new LambdaUpdateWrapper<MatnrPrintTemplate>() |
| | | .eq(MatnrPrintTemplate::getId, template.getId()) |
| | | .set(MatnrPrintTemplate::getIsDefault, 1) |
| | | .set(MatnrPrintTemplate::getUpdateBy, resolveCurrentUserId()) |
| | | .set(MatnrPrintTemplate::getUpdateTime, new Date()) |
| | | ); |
| | | if (!updated) { |
| | | throw new CoolException("默认模板设置失败"); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private MatnrPrintTemplate prepareTemplateForSave(MatnrPrintTemplate template, boolean updating) { |
| | | if (template == null) { |
| | | throw new CoolException("模板参数不能为空"); |
| | | } |
| | | Long currentTenantId = resolveCurrentTenantId(); |
| | | Long currentUserId = resolveCurrentUserId(); |
| | | if (currentTenantId == null) { |
| | | throw new CoolException("当前租户信息缺失"); |
| | | } |
| | | String name = normalizeText(template.getName()); |
| | | String code = normalizeText(template.getCode()); |
| | | if (name.isEmpty()) { |
| | | throw new CoolException("模板名称不能为空"); |
| | | } |
| | | if (code.isEmpty()) { |
| | | throw new CoolException("模板编码不能为空"); |
| | | } |
| | | Map<String, Object> canvasJson = template.getCanvasJson(); |
| | | if (canvasJson == null || canvasJson.isEmpty()) { |
| | | throw new CoolException("模板画布不能为空"); |
| | | } |
| | | validateCanvasJson(canvasJson); |
| | | ensureTemplateCodeUnique(code, updating ? template.getId() : null); |
| | | |
| | | Date now = new Date(); |
| | | template.setTenantId(currentTenantId) |
| | | .setName(name) |
| | | .setCode(code) |
| | | .setStatus(template.getStatus() == null ? 1 : template.getStatus()) |
| | | .setIsDefault(Objects.equals(template.getIsDefault(), 1) ? 1 : 0) |
| | | .setMemo(normalizeText(template.getMemo())) |
| | | .setUpdateBy(currentUserId) |
| | | .setUpdateTime(now); |
| | | if (!updating) { |
| | | template.setCreateBy(currentUserId); |
| | | template.setCreateTime(now); |
| | | } |
| | | return template; |
| | | } |
| | | |
| | | private void ensureTemplateCodeUnique(String code, Long excludeId) { |
| | | long duplicateCount = this.count(new LambdaQueryWrapper<MatnrPrintTemplate>() |
| | | .eq(MatnrPrintTemplate::getCode, code) |
| | | .ne(excludeId != null, MatnrPrintTemplate::getId, excludeId) |
| | | ); |
| | | if (duplicateCount > 0) { |
| | | throw new CoolException("模板编码已存在,请更换后重试"); |
| | | } |
| | | } |
| | | |
| | | private void clearCurrentTenantDefaults() { |
| | | this.update(new LambdaUpdateWrapper<MatnrPrintTemplate>() |
| | | .eq(MatnrPrintTemplate::getIsDefault, 1) |
| | | .set(MatnrPrintTemplate::getIsDefault, 0) |
| | | .set(MatnrPrintTemplate::getUpdateBy, resolveCurrentUserId()) |
| | | .set(MatnrPrintTemplate::getUpdateTime, new Date()) |
| | | ); |
| | | } |
| | | |
| | | private void ensureOneDefaultTemplate() { |
| | | long defaultCount = this.count(new LambdaQueryWrapper<MatnrPrintTemplate>() |
| | | .eq(MatnrPrintTemplate::getIsDefault, 1) |
| | | ); |
| | | if (defaultCount > 0) { |
| | | return; |
| | | } |
| | | MatnrPrintTemplate newest = this.getOne(new LambdaQueryWrapper<MatnrPrintTemplate>() |
| | | .orderByDesc(MatnrPrintTemplate::getUpdateTime) |
| | | .orderByDesc(MatnrPrintTemplate::getCreateTime) |
| | | .last("limit 1") |
| | | ); |
| | | if (newest == null) { |
| | | return; |
| | | } |
| | | this.update(new LambdaUpdateWrapper<MatnrPrintTemplate>() |
| | | .eq(MatnrPrintTemplate::getId, newest.getId()) |
| | | .set(MatnrPrintTemplate::getIsDefault, 1) |
| | | .set(MatnrPrintTemplate::getUpdateBy, resolveCurrentUserId()) |
| | | .set(MatnrPrintTemplate::getUpdateTime, new Date()) |
| | | ); |
| | | } |
| | | |
| | | private void validateCanvasJson(Map<String, Object> canvasJson) { |
| | | JSONObject root = JSONObject.parseObject(JSON.toJSONString(canvasJson)); |
| | | if (root == null) { |
| | | throw new CoolException("模板画布格式不正确"); |
| | | } |
| | | if (root.getInteger("version") == null) { |
| | | throw new CoolException("模板版本不能为空"); |
| | | } |
| | | JSONObject canvas = root.getJSONObject("canvas"); |
| | | if (canvas == null) { |
| | | throw new CoolException("模板画布配置不能为空"); |
| | | } |
| | | double width = getPositiveNumber(canvas, "width", "画布宽度"); |
| | | double height = getPositiveNumber(canvas, "height", "画布高度"); |
| | | if (width <= 0 || height <= 0) { |
| | | throw new CoolException("画布尺寸必须大于0"); |
| | | } |
| | | String unit = normalizeText(canvas.getString("unit")); |
| | | if (!"mm".equals(unit)) { |
| | | throw new CoolException("画布单位仅支持 mm"); |
| | | } |
| | | JSONArray elements = root.getJSONArray("elements"); |
| | | if (elements == null) { |
| | | throw new CoolException("模板元素不能为空"); |
| | | } |
| | | for (int index = 0; index < elements.size(); index++) { |
| | | JSONObject element = elements.getJSONObject(index); |
| | | if (element == null) { |
| | | throw new CoolException("模板元素格式不正确"); |
| | | } |
| | | validateElement(element, index); |
| | | } |
| | | } |
| | | |
| | | private void validateElement(JSONObject element, int index) { |
| | | String type = normalizeText(element.getString("type")); |
| | | if (!SUPPORTED_ELEMENT_TYPES.contains(type)) { |
| | | throw new CoolException("第" + (index + 1) + "个元素类型不支持"); |
| | | } |
| | | if (normalizeText(element.getString("id")).isEmpty()) { |
| | | throw new CoolException("第" + (index + 1) + "个元素缺少 ID"); |
| | | } |
| | | ensureNumber(element, "x", "元素 X 坐标"); |
| | | ensureNumber(element, "y", "元素 Y 坐标"); |
| | | if (!"line".equals(type)) { |
| | | getPositiveNumber(element, "w", "元素宽度"); |
| | | getPositiveNumber(element, "h", "元素高度"); |
| | | } else { |
| | | String direction = normalizeText(element.getString("direction")); |
| | | if (!Arrays.asList("horizontal", "vertical").contains(direction)) { |
| | | throw new CoolException("线条元素方向仅支持 horizontal 或 vertical"); |
| | | } |
| | | getPositiveNumber(element, "w", "线条长度"); |
| | | getPositiveNumber(element, "h", "线条粗细"); |
| | | } |
| | | |
| | | switch (type) { |
| | | case "text": |
| | | String contentMode = normalizeText(element.getString("contentMode")); |
| | | if (!Arrays.asList("static", "template").contains(contentMode)) { |
| | | throw new CoolException("文本元素内容模式不支持"); |
| | | } |
| | | if (normalizeText(element.getString("contentTemplate")).isEmpty()) { |
| | | throw new CoolException("文本元素内容不能为空"); |
| | | } |
| | | break; |
| | | case "barcode": |
| | | if (normalizeText(element.getString("valueTemplate")).isEmpty()) { |
| | | throw new CoolException("条码元素值模板不能为空"); |
| | | } |
| | | String symbology = normalizeText(element.getString("symbology")); |
| | | if (!symbology.isEmpty() && !"CODE128".equals(symbology)) { |
| | | throw new CoolException("一维码仅支持 CODE128"); |
| | | } |
| | | break; |
| | | case "qrcode": |
| | | if (normalizeText(element.getString("valueTemplate")).isEmpty()) { |
| | | throw new CoolException("二维码元素值模板不能为空"); |
| | | } |
| | | break; |
| | | case "table": |
| | | if (element.getJSONArray("columns") == null) { |
| | | throw new CoolException("表格元素 columns 不能为空"); |
| | | } |
| | | if (element.getJSONArray("rows") == null) { |
| | | throw new CoolException("表格元素 rows 不能为空"); |
| | | } |
| | | if (element.getJSONArray("cells") == null) { |
| | | throw new CoolException("表格元素 cells 不能为空"); |
| | | } |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | |
| | | private void ensureNumber(JSONObject object, String key, String label) { |
| | | if (object.getBigDecimal(key) == null) { |
| | | throw new CoolException(label + "不能为空"); |
| | | } |
| | | } |
| | | |
| | | private double getPositiveNumber(JSONObject object, String key, String label) { |
| | | if (object.getBigDecimal(key) == null) { |
| | | throw new CoolException(label + "不能为空"); |
| | | } |
| | | double value = object.getBigDecimal(key).doubleValue(); |
| | | if (value <= 0) { |
| | | throw new CoolException(label + "必须大于0"); |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | private String normalizeText(String value) { |
| | | return value == null ? "" : value.trim(); |
| | | } |
| | | |
| | | private Long resolveCurrentTenantId() { |
| | | User loginUser = getCurrentUser(); |
| | | return loginUser == null ? null : loginUser.getTenantId(); |
| | | } |
| | | |
| | | private Long resolveCurrentUserId() { |
| | | User loginUser = getCurrentUser(); |
| | | return loginUser == null ? null : loginUser.getId(); |
| | | } |
| | | |
| | | private User getCurrentUser() { |
| | | try { |
| | | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
| | | if (authentication != null && authentication.getPrincipal() instanceof User) { |
| | | return (User) authentication.getPrincipal(); |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | return null; |
| | | } |
| | | } |
| | |
| | | if (menu.getParentId() == null || menu.getParentId() == 0L) { |
| | | return "/index/index"; |
| | | } |
| | | return "/"+menu.getComponent(); |
| | | return menu.getRoute(); |
| | | } |
| | | |
| | | private String resolveRoutePath(Menu menu) { |
| New file |
| | |
| | | CREATE TABLE IF NOT EXISTS `man_matnr_print_template` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', |
| | | `tenant_id` bigint NOT NULL COMMENT '租户ID', |
| | | `name` varchar(100) NOT NULL COMMENT '模板名称', |
| | | `code` varchar(100) NOT NULL COMMENT '模板编码', |
| | | `is_default` tinyint NOT NULL DEFAULT 0 COMMENT '是否默认模板 1是 0否', |
| | | `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态 1正常 0冻结', |
| | | `canvas_json` longtext NOT NULL COMMENT '画布JSON', |
| | | `deleted` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除 1是 0否', |
| | | `create_by` bigint DEFAULT NULL COMMENT '创建人', |
| | | `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
| | | `update_by` bigint DEFAULT NULL COMMENT '更新人', |
| | | `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
| | | `memo` varchar(500) DEFAULT NULL COMMENT '备注', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_matnr_print_template_tenant_code_deleted` (`tenant_id`, `code`, `deleted`), |
| | | KEY `idx_matnr_print_template_tenant_default` (`tenant_id`, `is_default`), |
| | | KEY `idx_matnr_print_template_tenant_status` (`tenant_id`, `status`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物料打印模板'; |
| New file |
| | |
| | | -- 物料打印模板左侧独立菜单 + 权限按钮 |
| | | -- 说明: |
| | | -- 1. 在“基础信息”下新增“物料打印模板”独立菜单 |
| | | -- 2. 菜单路由为 /basic-info/matnr-print-template,组件键为 matnrPrintTemplate |
| | | -- 3. 若此前已执行旧版“挂在物料页下的按钮权限”脚本,重复执行本脚本会自动迁移按钮到新菜单下 |
| | | |
| | | SET @tenant_id := 1; |
| | | |
| | | SET @basic_info_menu_id := COALESCE( |
| | | ( |
| | | SELECT parent_id |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND type = 0 |
| | | AND ( |
| | | route = '/basic-info/wh-mat' |
| | | OR component IN ('matnr', 'whMat') |
| | | OR name = 'menu.matnr' |
| | | ) |
| | | ORDER BY id |
| | | LIMIT 1 |
| | | ), |
| | | ( |
| | | SELECT id |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND type = 0 |
| | | AND ( |
| | | route = '/basic-info' |
| | | OR component = 'basicInfo' |
| | | OR name = 'menu.basicInfo' |
| | | ) |
| | | ORDER BY id |
| | | LIMIT 1 |
| | | ) |
| | | ); |
| | | |
| | | INSERT INTO sys_menu ( |
| | | name, |
| | | parent_id, |
| | | parent_name, |
| | | path, |
| | | path_name, |
| | | route, |
| | | component, |
| | | brief, |
| | | code, |
| | | type, |
| | | authority, |
| | | icon, |
| | | sort, |
| | | meta, |
| | | tenant_id, |
| | | status, |
| | | deleted, |
| | | create_time, |
| | | create_by, |
| | | update_time, |
| | | update_by, |
| | | memo |
| | | ) |
| | | SELECT |
| | | 'menu.matnrPrintTemplate', |
| | | @basic_info_menu_id, |
| | | 'menu.basicInfo', |
| | | 'matnr-print-template', |
| | | 'matnrPrintTemplate', |
| | | '/basic-info/matnr-print-template', |
| | | 'matnrPrintTemplate', |
| | | '物料打印模板独立菜单', |
| | | NULL, |
| | | 0, |
| | | NULL, |
| | | 'ri:price-tag-3-line', |
| | | 96, |
| | | NULL, |
| | | @tenant_id, |
| | | 1, |
| | | 0, |
| | | NOW(), |
| | | 1, |
| | | NOW(), |
| | | 1, |
| | | '物料打印模板左侧菜单' |
| | | FROM dual |
| | | WHERE @basic_info_menu_id IS NOT NULL |
| | | AND NOT EXISTS ( |
| | | SELECT 1 |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND type = 0 |
| | | AND ( |
| | | route = '/basic-info/matnr-print-template' |
| | | OR component = 'matnrPrintTemplate' |
| | | OR name = 'menu.matnrPrintTemplate' |
| | | ) |
| | | ); |
| | | |
| | | SET @template_menu_id := ( |
| | | SELECT id |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND type = 0 |
| | | AND ( |
| | | route = '/basic-info/matnr-print-template' |
| | | OR component = 'matnrPrintTemplate' |
| | | OR name = 'menu.matnrPrintTemplate' |
| | | ) |
| | | ORDER BY id |
| | | LIMIT 1 |
| | | ); |
| | | |
| | | UPDATE sys_menu |
| | | SET |
| | | name = 'menu.matnrPrintTemplate', |
| | | parent_id = @basic_info_menu_id, |
| | | parent_name = 'menu.basicInfo', |
| | | path = 'matnr-print-template', |
| | | path_name = 'matnrPrintTemplate', |
| | | route = '/basic-info/matnr-print-template', |
| | | component = 'matnrPrintTemplate', |
| | | brief = '物料打印模板独立菜单', |
| | | type = 0, |
| | | authority = NULL, |
| | | icon = 'ri:price-tag-3-line', |
| | | sort = 96, |
| | | status = 1, |
| | | update_time = NOW(), |
| | | update_by = 1, |
| | | memo = '物料打印模板左侧菜单' |
| | | WHERE @template_menu_id IS NOT NULL |
| | | AND id = @template_menu_id; |
| | | |
| | | UPDATE sys_menu |
| | | SET |
| | | parent_id = @template_menu_id, |
| | | parent_name = 'menu.matnrPrintTemplate', |
| | | update_time = NOW(), |
| | | update_by = 1 |
| | | WHERE @template_menu_id IS NOT NULL |
| | | AND deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND authority IN ( |
| | | 'manager:matnrPrintTemplate:list', |
| | | 'manager:matnrPrintTemplate:save', |
| | | 'manager:matnrPrintTemplate:update', |
| | | 'manager:matnrPrintTemplate:remove' |
| | | ); |
| | | |
| | | INSERT INTO sys_menu ( |
| | | name, |
| | | parent_id, |
| | | parent_name, |
| | | path, |
| | | path_name, |
| | | route, |
| | | component, |
| | | brief, |
| | | code, |
| | | type, |
| | | authority, |
| | | icon, |
| | | sort, |
| | | meta, |
| | | tenant_id, |
| | | status, |
| | | deleted, |
| | | create_time, |
| | | create_by, |
| | | update_time, |
| | | update_by, |
| | | memo |
| | | ) |
| | | SELECT |
| | | 'Query 物料打印模板', |
| | | @template_menu_id, |
| | | 'menu.matnrPrintTemplate', |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | '物料打印模板读取权限', |
| | | NULL, |
| | | 1, |
| | | 'manager:matnrPrintTemplate:list', |
| | | NULL, |
| | | 10, |
| | | NULL, |
| | | @tenant_id, |
| | | 1, |
| | | 0, |
| | | NOW(), |
| | | 1, |
| | | NOW(), |
| | | 1, |
| | | '物料打印模板列表、详情、默认模板、打印读取' |
| | | FROM dual |
| | | WHERE @template_menu_id IS NOT NULL |
| | | AND NOT EXISTS ( |
| | | SELECT 1 |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND authority = 'manager:matnrPrintTemplate:list' |
| | | ); |
| | | |
| | | INSERT INTO sys_menu ( |
| | | name, |
| | | parent_id, |
| | | parent_name, |
| | | path, |
| | | path_name, |
| | | route, |
| | | component, |
| | | brief, |
| | | code, |
| | | type, |
| | | authority, |
| | | icon, |
| | | sort, |
| | | meta, |
| | | tenant_id, |
| | | status, |
| | | deleted, |
| | | create_time, |
| | | create_by, |
| | | update_time, |
| | | update_by, |
| | | memo |
| | | ) |
| | | SELECT |
| | | 'Create 物料打印模板', |
| | | @template_menu_id, |
| | | 'menu.matnrPrintTemplate', |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | '物料打印模板新增权限', |
| | | NULL, |
| | | 1, |
| | | 'manager:matnrPrintTemplate:save', |
| | | NULL, |
| | | 11, |
| | | NULL, |
| | | @tenant_id, |
| | | 1, |
| | | 0, |
| | | NOW(), |
| | | 1, |
| | | NOW(), |
| | | 1, |
| | | '物料打印模板新增' |
| | | FROM dual |
| | | WHERE @template_menu_id IS NOT NULL |
| | | AND NOT EXISTS ( |
| | | SELECT 1 |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND authority = 'manager:matnrPrintTemplate:save' |
| | | ); |
| | | |
| | | INSERT INTO sys_menu ( |
| | | name, |
| | | parent_id, |
| | | parent_name, |
| | | path, |
| | | path_name, |
| | | route, |
| | | component, |
| | | brief, |
| | | code, |
| | | type, |
| | | authority, |
| | | icon, |
| | | sort, |
| | | meta, |
| | | tenant_id, |
| | | status, |
| | | deleted, |
| | | create_time, |
| | | create_by, |
| | | update_time, |
| | | update_by, |
| | | memo |
| | | ) |
| | | SELECT |
| | | 'Update 物料打印模板', |
| | | @template_menu_id, |
| | | 'menu.matnrPrintTemplate', |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | '物料打印模板修改权限', |
| | | NULL, |
| | | 1, |
| | | 'manager:matnrPrintTemplate:update', |
| | | NULL, |
| | | 12, |
| | | NULL, |
| | | @tenant_id, |
| | | 1, |
| | | 0, |
| | | NOW(), |
| | | 1, |
| | | NOW(), |
| | | 1, |
| | | '物料打印模板编辑、设默认' |
| | | FROM dual |
| | | WHERE @template_menu_id IS NOT NULL |
| | | AND NOT EXISTS ( |
| | | SELECT 1 |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND authority = 'manager:matnrPrintTemplate:update' |
| | | ); |
| | | |
| | | INSERT INTO sys_menu ( |
| | | name, |
| | | parent_id, |
| | | parent_name, |
| | | path, |
| | | path_name, |
| | | route, |
| | | component, |
| | | brief, |
| | | code, |
| | | type, |
| | | authority, |
| | | icon, |
| | | sort, |
| | | meta, |
| | | tenant_id, |
| | | status, |
| | | deleted, |
| | | create_time, |
| | | create_by, |
| | | update_time, |
| | | update_by, |
| | | memo |
| | | ) |
| | | SELECT |
| | | 'Delete 物料打印模板', |
| | | @template_menu_id, |
| | | 'menu.matnrPrintTemplate', |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | '物料打印模板删除权限', |
| | | NULL, |
| | | 1, |
| | | 'manager:matnrPrintTemplate:remove', |
| | | NULL, |
| | | 13, |
| | | NULL, |
| | | @tenant_id, |
| | | 1, |
| | | 0, |
| | | NOW(), |
| | | 1, |
| | | NOW(), |
| | | 1, |
| | | '物料打印模板删除' |
| | | FROM dual |
| | | WHERE @template_menu_id IS NOT NULL |
| | | AND NOT EXISTS ( |
| | | SELECT 1 |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND authority = 'manager:matnrPrintTemplate:remove' |
| | | ); |