Merge branch 'dev_NEW_pro' into dev_天津_宝东
# Conflicts:
# multiple/config.json
# src/api/productionManagement/productionOrder.js
# src/api/salesManagement/deliveryLedger.js
# src/views/basicData/customerFile/index.vue
# src/views/basicData/customerFileOpenSea/index.vue
# src/views/basicData/product/index.vue
# src/views/collaborativeApproval/approvalManagement/index.vue
# src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
# src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
# src/views/collaborativeApproval/approvalProcess/index.vue
# src/views/collaborativeApproval/purchaseApproval/index.vue
# src/views/customerService/feedbackRegistration/components/formDia.vue
# src/views/equipmentManagement/upkeep/Form/formDia.vue
# src/views/equipmentManagement/upkeep/index.vue
# src/views/inventoryManagement/dispatchLog/Record.vue
# src/views/inventoryManagement/stockManagement/Qualified.vue
# src/views/inventoryManagement/stockManagement/Record.vue
# src/views/inventoryManagement/stockReport/index.vue
# src/views/procurementManagement/procurementLedger/index.vue
# src/views/procurementManagement/purchaseReturnOrder/index.vue
# src/views/productionManagement/productStructure/index.vue
# src/views/productionManagement/productionCosting/index.vue
# src/views/productionManagement/productionOrder/New.vue
# src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
# src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
# src/views/productionManagement/productionOrder/index.vue
# src/views/qualityManagement/processInspection/components/formDia.vue
# src/views/salesManagement/deliveryLedger/index.vue
# src/views/salesManagement/returnOrder/components/detailDia.vue
# src/views/salesManagement/salesLedger/index.vue
# src/views/salesManagement/salesQuotation/index.vue
已添加83个文件
已修改190个文件
已删除9个文件
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # æ¬å°æä»¶ä¸ä¼ README |
| | | |
| | | æ¬ææ¡£åºäºä»¥ä¸å®ç°æ´çï¼ |
| | | |
| | | - `src/components/AttachmentUpload/file/index.vue` |
| | | - `src/components/AttachmentUpload/image/index.vue` |
| | | - `src/components/AttachmentPreview/image/index.vue` |
| | | - `src/components/Dialog/FileList.vue` |
| | | - `src/api/basicData/common.js` |
| | | - `src/api/publicApi/commonFile.js` |
| | | |
| | | ç¸å
³ç»ä»¶å·²å¨ `src/main.js` 䏿³¨å为å
¨å±ç»ä»¶ï¼å¯ç´æ¥å¨é¡µé¢ä¸ä½¿ç¨ï¼ |
| | | |
| | | - `FileUpload` |
| | | - `ImageUpload` |
| | | - `ImagePreview` |
| | | - `FileListDialog` |
| | | |
| | | ## 1. åè½æ¦è§ |
| | | |
| | | å½åè¿å¥ä¸ä¼ è½å主è¦å为 4 é¨åï¼ |
| | | |
| | | 1. `FileUpload`ï¼æ®éæä»¶ä¸ä¼ ï¼æ¯æææ½ãæ¹éä¸ä¼ ãé¢è§ãå é¤ |
| | | 2. `ImageUpload`ï¼å¾çä¸ä¼ ï¼æ¯æå¾çå¢å±ç¤ºãé¢è§ãå é¤ |
| | | 3. `ImagePreview`ï¼å¾çå表é¢è§å±ç¤º |
| | | 4. `FileListDialog`ï¼ä¸å¡éä»¶å¼¹çªï¼æ¯ææ¥è¯¢ãä¸ä¼ ãå é¤ãä¸è½½ |
| | | |
| | | ä¸ä¼ åºå±ç»ä¸èµ°æ¥å£ï¼ |
| | | |
| | | - `POST /common/upload` |
| | | |
| | | å¯¹åºæ¹æ³å¨ `src/api/basicData/common.js`ï¼ |
| | | |
| | | ```js |
| | | uploadFile(data) |
| | | ``` |
| | | |
| | | ## 2. ä¸ä¼ æ¥å£è¯´æ |
| | | |
| | | ### 2.1 éç¨ä¸ä¼ æ¥å£ |
| | | |
| | | æä»¶ä¸ä¼ ç»ä»¶åå¾çä¸ä¼ ç»ä»¶é½è°ç¨äºï¼ |
| | | |
| | | ```js |
| | | import { uploadFile } from '@/api/basicData/common' |
| | | ``` |
| | | |
| | | æ¥å£ç¹å¾ï¼ |
| | | |
| | | - è¯·æ±æ¹å¼ï¼`POST` |
| | | - å°åï¼`/common/upload` |
| | | - 请æ±ç±»åï¼`multipart/form-data` |
| | | - æ¯æ `FormData` æ¹éä¸ä¼ |
| | | - é»è®¤å段åï¼`files` |
| | | |
| | | ç»ä»¶å
é¨ä¼è¿æ ·ç»è£
åæ°ï¼ |
| | | |
| | | ```js |
| | | const formData = new FormData() |
| | | validFiles.forEach((file) => { |
| | | formData.append(props.uploadFieldName, file.raw) |
| | | }) |
| | | ``` |
| | | |
| | | ### 2.2 ä¸ä¼ è¿åå¼è¦æ± |
| | | |
| | | ä¸ä¼ æååï¼ç»ä»¶ä¼å°è¯ä»ä»¥ä¸ç»æä¸æåæ°ç»ï¼ |
| | | |
| | | - `response` |
| | | - `response.data` |
| | | - `response.data.data` |
| | | - `response.payload` |
| | | - `response.payload.data` |
| | | - `response.rows` |
| | | - `response.result` |
| | | |
| | | å æ¤å端è¿åæ°ç»æ¶ï¼ä¸é¢ä»»æä¸ç§ç»æé½å¯ä»¥è¢«è¯å«ã |
| | | |
| | | ç»ä»¶å±ç¤ºæ¶å¸¸ç¨å°çåæ®µæï¼ |
| | | |
| | | - æä»¶åï¼`name` / `originalFilename` / `fileName` / `uidFilename` |
| | | - æä»¶å°åï¼`url` / `downloadURL` |
| | | - å¾çå°åï¼`url` / `previewURL` / `previewUrl` |
| | | - 主é®ï¼`id` |
| | | |
| | | 建议ä¸ä¼ æ¥å£è¿åçå项对象尽éå
å«ï¼ |
| | | |
| | | ```js |
| | | { |
| | | id: 1, |
| | | originalFilename: 'demo.pdf', |
| | | downloadURL: 'https://xxx/demo.pdf', |
| | | previewURL: 'https://xxx/demo.png' |
| | | } |
| | | ``` |
| | | |
| | | ## 3. FileUpload æä»¶ä¸ä¼ ç»ä»¶ |
| | | |
| | | ç»ä»¶è·¯å¾ï¼ |
| | | |
| | | `src/components/AttachmentUpload/file/index.vue` |
| | | |
| | | ### 3.1 åºç¡ç¨æ³ |
| | | |
| | | ```vue |
| | | <template> |
| | | <FileUpload v-model:file-list="fileList" /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue' |
| | | |
| | | const fileList = ref([]) |
| | | </script> |
| | | ``` |
| | | |
| | | ### 3.2 常ç¨å±æ§ |
| | | |
| | | | 屿§ | 说æ | ç±»å | é»è®¤å¼ | |
| | | | --- | --- | --- | --- | |
| | | | `fileList` | ç»å®æä»¶å表 | `Array` | `[]` | |
| | | | `limit` | æå¤§ä¸ä¼ æ°é | `Number` | `10` | |
| | | | `fileSize` | å个æä»¶å¤§å°éå¶ï¼åä½ MB | `Number` | `50` | |
| | | | `fileType` | å
许ä¸ä¼ çæä»¶ç±»åï¼å¦ `['pdf', 'docx']` | `Array` | `[]` | |
| | | | `buttonText` | ä¸ä¼ æç¤ºææ¡ | `String` | `åå»éæ©æä»¶` | |
| | | | `disabled` | æ¯å¦ç¦ç¨ | `Boolean` | `false` | |
| | | | `uploadFieldName` | `FormData` åæ®µå | `String` | `files` | |
| | | | `index` | è¡¨æ ¼/åè¡¨è¡æ¨¡å¼ä¸çå½åè¡ç´¢å¼ | `Number` | `-1` | |
| | | | `childrenKey` | è¡å
æè½½å段å | `String` | `files` | |
| | | |
| | | ### 3.3 äºä»¶ |
| | | |
| | | | äºä»¶ | 说æ | |
| | | | --- | --- | |
| | | | `update:fileList` | æä»¶å表ååæ¶è§¦å | |
| | | | `change` | æä»¶å表ååæ¶è§¦åï¼è¿åææ°å表 | |
| | | |
| | | ### 3.4 éå¶è§å |
| | | |
| | | ç»ä»¶å
å·²å®ç°ï¼ |
| | | |
| | | - æä»¶æ°ééå¶ |
| | | - æä»¶å¤§å°éå¶ |
| | | - æä»¶ç±»åæ ¡éª |
| | | - ä¸ä¼ ä¸ç¶æéå® |
| | | - 失败åèªå¨æ¸
空å½åéæ©éå |
| | | |
| | | ä¾å¦éå¶ PDF/Wordï¼ |
| | | |
| | | ```vue |
| | | <FileUpload |
| | | v-model:file-list="fileList" |
| | | :limit="5" |
| | | :file-size="20" |
| | | :file-type="['pdf', 'doc', 'docx']" |
| | | /> |
| | | ``` |
| | | |
| | | ### 3.5 è¿åæ°æ®æ ¼å¼å»ºè®® |
| | | |
| | | `FileUpload` æ´é忥æ¶è¿æ ·çåè¡¨ï¼ |
| | | |
| | | ```js |
| | | [ |
| | | { |
| | | id: 1, |
| | | originalFilename: 'åå.pdf', |
| | | downloadURL: 'https://xxx/contract.pdf' |
| | | } |
| | | ] |
| | | ``` |
| | | |
| | | å 为ç»ä»¶æå¼æä»¶æ¶ä¼ä¼å
读åï¼ |
| | | |
| | | ```js |
| | | url || downloadURL || previewURL || previewUrl |
| | | ``` |
| | | |
| | | ### 3.6 è¡å
åµå¥æ¨¡å¼ |
| | | |
| | | 妿ä¸ä¼ ç»ä»¶æ¾å¨è¡¨æ ¼æä¸è¡ä¸ï¼å¯é
å `index` å `childrenKey` 使ç¨ï¼ |
| | | |
| | | ```vue |
| | | <FileUpload |
| | | v-model:file-list="tableData" |
| | | :index="scope.$index" |
| | | children-key="files" |
| | | /> |
| | | ``` |
| | | |
| | | æ¤æ¶ç»ä»¶ä¼èªå¨è¯»åï¼ |
| | | |
| | | ```js |
| | | tableData[scope.$index].files |
| | | ``` |
| | | |
| | | ## 4. ImageUpload å¾çä¸ä¼ ç»ä»¶ |
| | | |
| | | ç»ä»¶è·¯å¾ï¼ |
| | | |
| | | `src/components/AttachmentUpload/image/index.vue` |
| | | |
| | | ### 4.1 åºç¡ç¨æ³ |
| | | |
| | | ```vue |
| | | <template> |
| | | <ImageUpload v-model:file-list="imageList" /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue' |
| | | |
| | | const imageList = ref([]) |
| | | </script> |
| | | ``` |
| | | |
| | | ### 4.2 é»è®¤è¡ä¸º |
| | | |
| | | å¾çä¸ä¼ ç»ä»¶é»è®¤ï¼ |
| | | |
| | | - æå¤ä¸ä¼ `10` å¼ |
| | | - åå¼ ä¸è¶
è¿ `10MB` |
| | | - é»è®¤æ¯ææ ¼å¼ï¼`png / jpg / jpeg / webp` |
| | | - ä½¿ç¨ `picture-card` 飿 ¼å±ç¤º |
| | | - ç¹å»ç¼©ç¥å¾å¯é¢è§å¤§å¾ |
| | | |
| | | ### 4.3 常ç¨ç¤ºä¾ |
| | | |
| | | ```vue |
| | | <ImageUpload |
| | | v-model:file-list="imageList" |
| | | :limit="9" |
| | | :file-size="5" |
| | | :file-type="['png', 'jpg', 'jpeg']" |
| | | button-text="ä¸ä¼ å¾ç" |
| | | /> |
| | | ``` |
| | | |
| | | ### 4.4 è¿åæ°æ®æ ¼å¼å»ºè®® |
| | | |
| | | `ImageUpload` å±ç¤ºå¾çæ¶ä¼å
读åï¼ |
| | | |
| | | ```js |
| | | url || previewURL || previewUrl |
| | | ``` |
| | | |
| | | 建议å端è¿åï¼ |
| | | |
| | | ```js |
| | | [ |
| | | { |
| | | id: 1, |
| | | originalFilename: 'ç°åºå¾ç.jpg', |
| | | previewURL: 'https://xxx/image.jpg' |
| | | } |
| | | ] |
| | | ``` |
| | | |
| | | ### 4.5 è¡å
åµå¥æ¨¡å¼ |
| | | |
| | | å¾çç»ä»¶åæ ·æ¯æè¡å
åæ®µååï¼ |
| | | |
| | | ```vue |
| | | <ImageUpload |
| | | v-model:file-list="tableData" |
| | | :index="scope.$index" |
| | | children-key="images" |
| | | /> |
| | | ``` |
| | | |
| | | é»è®¤åååæ®µä¸º `images`ã |
| | | |
| | | ## 5. ImagePreview å¾çé¢è§ç»ä»¶ |
| | | |
| | | ç»ä»¶è·¯å¾ï¼ |
| | | |
| | | `src/components/AttachmentPreview/image/index.vue` |
| | | |
| | | ### 5.1 åºç¡ç¨æ³ |
| | | |
| | | ```vue |
| | | <ImagePreview :file-list="imageList" /> |
| | | ``` |
| | | |
| | | ### 5.2 å¯é
置项 |
| | | |
| | | | 屿§ | 说æ | ç±»å | é»è®¤å¼ | |
| | | | --- | --- | --- | --- | |
| | | | `fileList` | å¾çå表 | `Array` | `[]` | |
| | | | `thumbSize` | 缩ç¥å¾å¤§å° | `Number` | `72` | |
| | | | `gap` | 缩ç¥å¾é´è· | `Number` | `10` | |
| | | |
| | | ### 5.3 æ°æ®è¦æ± |
| | | |
| | | ç»ä»¶ä¼è¿æ»¤æ²¡æ `previewURL` ç项ï¼å æ¤å¦æè¦æ£å¸¸æ¾ç¤ºï¼å»ºè®®è³å°å
å«ï¼ |
| | | |
| | | ```js |
| | | [ |
| | | { |
| | | previewURL: 'https://xxx/image.jpg', |
| | | originalFilename: 'å¾ç1.jpg' |
| | | } |
| | | ] |
| | | ``` |
| | | |
| | | 妿å表为空ï¼ç»ä»¶æ¾ç¤ºâææ å¾çâã |
| | | |
| | | ## 6. FileListDialog éä»¶å¼¹çªç»ä»¶ |
| | | |
| | | ç»ä»¶è·¯å¾ï¼ |
| | | |
| | | `src/components/Dialog/FileList.vue` |
| | | |
| | | è¿ä¸ªç»ä»¶éåä¸å¡è¡¨åæè¯¦æ
页éçâé件管çâåºæ¯ï¼è½åå
æ¬ï¼ |
| | | |
| | | - æ ¹æ®ä¸å¡ä¸»é®æ¥è¯¢éä»¶å表 |
| | | - æå¼å¼¹çªæ¥çéä»¶ |
| | | - å¨å¼¹çªä¸ç»§ç»ä¸ä¼ éä»¶ |
| | | - å é¤éä»¶ |
| | | - ä¸è½½éä»¶ |
| | | |
| | | ### 6.1 ç»ä»¶å±æ§ |
| | | |
| | | | 屿§ | 说æ | ç±»å | é»è®¤å¼ | |
| | | | --- | --- | --- | --- | |
| | | | `visible` | æ¯å¦æ¾ç¤ºå¼¹çª | `Boolean` | å¿
ä¼ | |
| | | | `recordType` | ä¸å¡ç±»å | `String` | `''` | |
| | | | `recordId` | ä¸å¡ä¸»é® | `Number` | `0` | |
| | | | `title` | å¼¹çªæ é¢ | `String` | `éä»¶` | |
| | | | `width` | å¼¹çªå®½åº¦ | `String` | `50%` | |
| | | | `showActions` | æ¯å¦æ¾ç¤ºä¸è½½/å 餿ä½å | `Boolean` | `true` | |
| | | |
| | | ### 6.2 åºç¡ç¨æ³ |
| | | |
| | | ```vue |
| | | <template> |
| | | <el-button @click="visible = true">æ¥çéä»¶</el-button> |
| | | |
| | | <FileListDialog |
| | | v-model:visible="visible" |
| | | record-type="salesLedger" |
| | | :record-id="rowId" |
| | | title="éä»¶å表" |
| | | /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue' |
| | | |
| | | const visible = ref(false) |
| | | const rowId = ref(1001) |
| | | </script> |
| | | ``` |
| | | |
| | | ### 6.3 ç»ä»¶å
é¨ä¾èµçæ¥å£ |
| | | |
| | | `FileListDialog` æ¬èº«ä¸ç´æ¥è°ç¨ `commonFile.js`ï¼èæ¯ä¾èµï¼ |
| | | |
| | | - `attachmentList` |
| | | - `createAttachment` |
| | | - `deleteAttachment` |
| | | |
| | | å¤çé»è¾ä¸ºï¼ |
| | | |
| | | 1. æå¼å¼¹çªåæ ¹æ® `recordType + recordId` æ¥è¯¢éä»¶ |
| | | 2. ç¹å»âä¸ä¼ éä»¶âåï¼å
é¨ä½¿ç¨ `AttachmentUpload/file` å
ä¸ä¼ å° `/common/upload` |
| | | 3. ä¸ä¼ æååï¼å°è¿åçæä»¶å¯¹è±¡åå·²æå表ä¸èµ·æäº¤ç» `createAttachment` |
| | | 4. å 餿¶è°ç¨ `deleteAttachment` |
| | | 5. ä¸è½½æ¶ç´æ¥ `window.open(downloadURL, '_blank')` |
| | | |
| | | å æ¤è¿éè¦ç¹å«æ³¨æï¼ |
| | | |
| | | - `recordType` å `recordId` å¿
é¡»æ¯ææä¸å¡æ è¯ |
| | | - ä¸ä¼ æåè¿åçæ°æ®ï¼éè¦è½è¢« `createAttachment` ç´æ¥æ¥æ¶ |
| | | - å表ä¸çä¸è½½å°ååæ®µåºä¸º `downloadURL` |
| | | |
| | | ## 7. commonFile.js 说æ |
| | | |
| | | æä»¶è·¯å¾ï¼ |
| | | |
| | | `src/api/publicApi/commonFile.js` |
| | | |
| | | å½åæä»¶æä¾çæ¯å
Œ
±æä»¶å 餿¥å£ï¼ |
| | | |
| | | ```js |
| | | delCommonFile(ids) |
| | | delCommonFileInvoiceLedger(ids) |
| | | ``` |
| | | |
| | | å¯¹åºæ¥å£ï¼ |
| | | |
| | | - `/commonFile/delCommonFile` |
| | | - `/invoiceLedger/delFile` |
| | | |
| | | è¿ä¸¤ä¸ªæ¹æ³æ´éåå·²ç»åå
·ä½ä¸å¡ç»å®åçâå é¤å·²ä¿åéä»¶âåºæ¯ï¼ä¸è´è´£ä¸ä¼ æä»¶æ¬èº«ã |
| | | |
| | | 示ä¾ï¼ |
| | | |
| | | ```js |
| | | import { delCommonFile } from '@/api/publicApi/commonFile' |
| | | |
| | | await delCommonFile([1, 2, 3]) |
| | | ``` |
| | | |
| | | ## 8. æ¨èä½¿ç¨æ¹å¼ |
| | | |
| | | ### 8.1 æ®éä¸å¡è¡¨åä¸ä¼ éä»¶ |
| | | |
| | | ```vue |
| | | <FileUpload v-model:file-list="form.storageBlobDTOs" /> |
| | | ``` |
| | | |
| | | æäº¤è¡¨åæ¶ç´æ¥å¸¦ä¸ï¼ |
| | | |
| | | ```js |
| | | { |
| | | ...form, |
| | | storageBlobDTOs: form.storageBlobDTOs |
| | | } |
| | | ``` |
| | | |
| | | ### 8.2 å¾çç±»ä¸å¡ |
| | | |
| | | ```vue |
| | | <ImageUpload v-model:file-list="form.images" /> |
| | | <ImagePreview :file-list="form.images" /> |
| | | ``` |
| | | |
| | | ### 8.3 å·²è½åºé件管ç |
| | | |
| | | ```vue |
| | | <FileListDialog |
| | | v-model:visible="dialogVisible" |
| | | :record-type="recordType" |
| | | :record-id="recordId" |
| | | /> |
| | | ``` |
| | | |
| | | éå详æ
页ã审æ¹é¡µãå°è´¦é¡µè¿ç±»âæ¥çå¹¶ç»´æ¤å½åä¸å¡éä»¶âçåºæ¯ã |
| | | |
| | | ## 9. 注æäºé¡¹ |
| | | |
| | | 1. `FileUpload` å `ImageUpload` åªæ¯è´è´£ææä»¶å
ä¼ å° `/common/upload`ï¼ä¸çäºå·²ç»åä¸å¡æ°æ®ç»å®ã |
| | | 2. 妿ä¸å¡éè¦æä¹
åéä»¶å
³ç³»ï¼ä»éè¦å¨ä¿åè¡¨åæ¶æè¿åçæä»¶å¯¹è±¡æäº¤ç»ä¸å¡æ¥å£ã |
| | | 3. `ImagePreview` å½ååªè¯å« `previewURL`ï¼å¦æå端åªè¿å `url`ï¼é¢è§ç»ä»¶å°ä¸ä¼å±ç¤ºï¼æå¥½ç»ä¸è¡¥é½ `previewURL`ã |
| | | 4. `FileListDialog` ä¾èµ `recordType`ã`recordId` æ¥è¯¢åä¿åéä»¶å
³ç³»ï¼æ°å¢ä¸å¡æ¶è¦å
确认å端å
³èæ¥å£å¯ç¨ã |
| | | 5. å 餿¬å°å表项åå é¤å·²ä¿åéä»¶æ¯ä¸¤ä»¶äºï¼ |
| | | - ä¸ä¼ ç»ä»¶éçå é¤ï¼åªä¼ä»å½åå端ç»å®æ°ç»ä¸ç§»é¤ |
| | | - `commonFile.js` / `deleteAttachment`ï¼ææ¯çæ£è°ç¨å端å é¤ |
| | | |
| | | ## 10. ä¸å¥æå¸¸è§ç页é¢åæ³ |
| | | |
| | | ```vue |
| | | <template> |
| | | <el-form :model="form"> |
| | | <el-form-item label="éä»¶"> |
| | | <FileUpload v-model:file-list="form.storageBlobDTOs" :limit="5" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç°åºå¾ç"> |
| | | <ImageUpload v-model:file-list="form.images" :limit="9" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å¾çé¢è§"> |
| | | <ImagePreview :file-list="form.images" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue' |
| | | |
| | | const form = ref({ |
| | | storageBlobDTOs: [], |
| | | images: [], |
| | | }) |
| | | </script> |
| | | ``` |
| | | |
| | | å¦æä½ çç®æ æ¯âå
ä¸ä¼ ï¼åè·ä¸å¡ä¸èµ·ä¿åâï¼è¿å¥åæ³å¯ä»¥ç´æ¥ä½ä¸ºåºç¡æ¨¡æ¿ä½¿ç¨ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # è´¢å¡ç®¡çåç«¯ææ¡£ï¼ä»
è´è´£æ¨¡åï¼ |
| | | |
| | | æ´æ°æ¶é´ï¼2026-05-12 |
| | | éç¨èå´ï¼ä»
ä»¥ä¸ 6 个模åï¼ï¼ |
| | | 1. åºå®èµäº§ï¼`/financial/fixed-assets`ï¼ |
| | | 2. æ å½¢èµäº§ï¼`/financial/intangible-assets`ï¼ |
| | | 3. æ»è´¦ç§ç®ï¼`/financial/general-ledger`ï¼ |
| | | 4. åè¯ï¼`/financial/voucher`ï¼ |
| | | 5. ç§ç®æ»è´¦ï¼`/financial/voucher-general-ledger`ï¼ |
| | | 6. ç§ç®æç»è´¦ï¼`/financial/voucher-detail-ledger`ï¼ |
| | | |
| | | --- |
| | | |
| | | ## 1. ç»ä¸çº¦å® |
| | | |
| | | ### 1.1 ååºç»æ |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "success", |
| | | "data": {} |
| | | } |
| | | ``` |
| | | |
| | | ### 1.2 åé¡µç»æï¼å¦ææ¯å页æ¥å£ï¼ |
| | | 请æ±åæ°å»ºè®®ï¼ |
| | | - `current`ï¼é¡µç ï¼ |
| | | - `size`ï¼æ¯é¡µæ¡æ°ï¼ |
| | | |
| | | ååºå»ºè®®ï¼ |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "data": { |
| | | "records": [], |
| | | "total": 0 |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 1.3 éé¢ä¸ç²¾åº¦ |
| | | - éé¢å段建议 `decimal(18,2)`ã |
| | | - åå端ç»ä¸ä¿ç两ä½å°æ°ã |
| | | |
| | | --- |
| | | |
| | | ## 2. 模åä¸ï¼æ»è´¦ç§ç®ï¼å·²æ¥çå® APIï¼ |
| | | |
| | | å端æä»¶ï¼`src/views/financialManagement/generalLedger/index.vue` |
| | | API æä»¶ï¼`src/api/financialManagement/accountSubject.js` |
| | | |
| | | ### 2.1 æ¥å£ç°ç¶ |
| | | - `GET /accountSubject/list` |
| | | - `POST /accountSubject/add` |
| | | - `PUT /accountSubject/edit` |
| | | - `DELETE /accountSubject/remove/{ids}` |
| | | - `POST /accountSubject/export` |
| | | |
| | | ### 2.2 åæ®µæ¨¡å |
| | | - `id` |
| | | - `subjectCode`ï¼ç§ç®ç¼ç ï¼ |
| | | - `subjectName`ï¼ç§ç®åç§°ï¼ |
| | | - `subjectType`ï¼ç§ç®ç±»åï¼ |
| | | - `balanceDirection`ï¼ä½é¢æ¹åï¼åæ¹/è´·æ¹ï¼ |
| | | - `status`ï¼0 å¯ç¨ï¼1 ç¦ç¨ï¼ |
| | | - `remark` |
| | | |
| | | ### 2.3 ä¸å¡è§å |
| | | - `subjectCode`ã`subjectName`ã`subjectType` å¿
å¡«ã |
| | | - å é¤éè¦åå¼ç¨æ ¡éªï¼è¥å·²è¢«åè¯åå½å¼ç¨ï¼ä¸å
许å é¤ï¼ã |
| | | |
| | | --- |
| | | |
| | | ## 3. 模åäºï¼åºå®èµäº§ï¼å½åå端为 mockï¼å¾
å端å®ç°ï¼ |
| | | |
| | | å端æä»¶ï¼`src/views/financialManagement/assets/fixedAssets.vue` |
| | | |
| | | ### 3.1 建议æ¥å£ |
| | | - `GET /financial/fixedAsset/page` |
| | | - `POST /financial/fixedAsset/add` |
| | | - `PUT /financial/fixedAsset/update` |
| | | - `DELETE /financial/fixedAsset/delete` |
| | | - `POST /financial/fixedAsset/depreciate`ï¼ææè®¡æï¼ |
| | | |
| | | ### 3.2 åæ®µæ¨¡å |
| | | - `id, assetCode, assetName, category, specification` |
| | | - `purchaseDate, originalValue, usefulLife, residualRate` |
| | | - `accumulatedDepreciation, netValue` |
| | | - `location, department, keeper, status, remark` |
| | | |
| | | ### 3.3 æ ¸å¿å
¬å¼ï¼å¿
é¡»ä¸è´ï¼ |
| | | - `monthlyDepreciation = originalValue * (1 - residualRate/100) / (usefulLife*12)` |
| | | - `accumulatedDepreciation += monthlyDepreciation` |
| | | - `netValue = originalValue - accumulatedDepreciation` |
| | | |
| | | ### 3.4 ç¶æå»ºè®® |
| | | - `in_use`ï¼å¨ç¨ï¼ |
| | | - `idle`ï¼é²ç½®ï¼ |
| | | - `repair`ï¼ç»´ä¿®ä¸ï¼ |
| | | - `scrapped`ï¼æ¥åºï¼ |
| | | |
| | | --- |
| | | |
| | | ## 4. 模åä¸ï¼æ å½¢èµäº§ï¼å½åå端为 mockï¼å¾
å端å®ç°ï¼ |
| | | |
| | | å端æä»¶ï¼`src/views/financialManagement/assets/intangibleAssets.vue` |
| | | |
| | | ### 4.1 建议æ¥å£ |
| | | - `GET /financial/intangibleAsset/page` |
| | | - `POST /financial/intangibleAsset/add` |
| | | - `PUT /financial/intangibleAsset/update` |
| | | - `DELETE /financial/intangibleAsset/delete` |
| | | - `POST /financial/intangibleAsset/amortize`ï¼æææéï¼ |
| | | |
| | | ### 4.2 åæ®µæ¨¡å |
| | | - `id, assetCode, assetName, category, certificateNo` |
| | | - `acquisitionDate, originalValue, amortizationPeriod, residualRate` |
| | | - `accumulatedAmortization, netValue` |
| | | - `validityDate, status, description, remark` |
| | | |
| | | ### 4.3 æ ¸å¿å
¬å¼ï¼å¿
é¡»ä¸è´ï¼ |
| | | - `monthlyAmortization = originalValue * (1 - residualRate/100) / (amortizationPeriod*12)` |
| | | - `accumulatedAmortization += monthlyAmortization` |
| | | - `netValue = originalValue - accumulatedAmortization` |
| | | - å½ `netValue <= 0`ï¼ |
| | | - `netValue = 0` |
| | | - `status = amortized` |
| | | |
| | | ### 4.4 ç¶æå»ºè®® |
| | | - `in_use`ï¼å¨ç¨ï¼ |
| | | - `expired`ï¼å°æï¼ |
| | | - `amortized`ï¼å·²æéå®ï¼ |
| | | |
| | | --- |
| | | |
| | | ## 5. 模ååï¼åè¯ï¼å½åå端为 mockï¼å¾
å端å®ç°ï¼ |
| | | |
| | | å端æä»¶ï¼`src/views/financialManagement/voucher/index.vue` |
| | | |
| | | ### 5.1 建议æ¥å£ |
| | | - `GET /financial/voucher/page` |
| | | - `POST /financial/voucher/add` |
| | | - `PUT /financial/voucher/update` |
| | | - `POST /financial/voucher/post`ï¼è¿è´¦ï¼ |
| | | - `POST /financial/voucher/cancel`ï¼ä½åºï¼ |
| | | - `GET /financial/voucher/detail/{id}` |
| | | |
| | | ### 5.2 ä¸»è¡¨åæ®µ |
| | | - `id, voucherNo, voucherDate, summary` |
| | | - `debit, credit, creator, status, attachmentCount, remark` |
| | | |
| | | ### 5.3 åå½å段 |
| | | - `subjectCode, subjectName, summary, debit, credit` |
| | | |
| | | ### 5.4 å
³é®æ ¡éª |
| | | - åå½è³å°ä¸æ¡ææè¡ï¼ç§ç®ä¸ç©ºï¼ä¸åæ¹æè´·æ¹ > 0ï¼ã |
| | | - å贷平衡ï¼`sum(debit) == sum(credit)` ä¸ > 0ï¼ä¸æ»¡è¶³ç¦æ¢ä¿åã |
| | | |
| | | ### 5.5 ç¶ææµè½¬ |
| | | - `unposted -> posted` |
| | | - `unposted -> cancelled` |
| | | |
| | | --- |
| | | |
| | | ## 6. 模åäºï¼ç§ç®æ»è´¦ï¼å½åå端为 mockï¼å¾
å端å®ç°ï¼ |
| | | |
| | | å端æä»¶ï¼`src/views/financialManagement/voucher/generalLedger.vue` |
| | | |
| | | ### 6.1 建议æ¥å£ |
| | | - `GET /financial/ledger/general` |
| | | |
| | | ### 6.2 请æ±åæ° |
| | | - `subjectCode`ï¼æ«çº§ææå®ç§ç®ï¼ |
| | | - `startMonth`ï¼YYYY-MMï¼ |
| | | - `endMonth`ï¼YYYY-MMï¼ |
| | | |
| | | ### 6.3 ååºå段 |
| | | - `date, voucherNo, summary` |
| | | - `debit, credit, direction, balance` |
| | | |
| | | ### 6.4 è§å |
| | | - ä»
å¨éæ©ç§ç®åè¿åæ°æ®ã |
| | | - æ¯æâæåä½é¢ / æ¬æå计 / æ¬å¹´ç´¯è®¡âè¡ï¼å¯éè¿ `rowType` åæ®µåºåï¼ã |
| | | |
| | | --- |
| | | |
| | | ## 7. 模åå
ï¼ç§ç®æç»è´¦ï¼å½åå端为 mockï¼å¾
å端å®ç°ï¼ |
| | | |
| | | å端æä»¶ï¼`src/views/financialManagement/voucher/detailLedger.vue` |
| | | |
| | | ### 7.1 建议æ¥å£ |
| | | - `GET /financial/ledger/detail` |
| | | |
| | | ### 7.2 请æ±åæ° |
| | | - `subjectCode` |
| | | - `auxiliaryType`ï¼customer/supplier/department/employee/projectï¼ |
| | | - `auxiliaryId` |
| | | - `startMonth`ï¼YYYY-MMï¼ |
| | | - `endMonth`ï¼YYYY-MMï¼ |
| | | |
| | | ### 7.3 ååºå段 |
| | | - `date, voucherNo, summary` |
| | | - `debit, credit, direction, balance` |
| | | |
| | | ### 7.4 è§å |
| | | - å
éç§ç®ï¼åæ¥æç»ã |
| | | - è¾
婿 ¸ç®æ¡ä»¶ä¸ºå¯éï¼ä½å»ºè®®åç«¯æ¯æç»´åº¦è¿æ»¤ã |
| | | |
| | | --- |
| | | |
| | | ## 8. æ¨èæå°è¡¨è®¾è®¡ï¼ä»
æ¬èå´ï¼ |
| | | |
| | | - `fin_account_subject` |
| | | - `fin_fixed_asset` |
| | | - `fin_intangible_asset` |
| | | - `fin_voucher` |
| | | - `fin_voucher_entry` |
| | | - `fin_ledger_snapshot_general`ï¼å¯éï¼åæ§è½ä¼åï¼ |
| | | - `fin_ledger_snapshot_detail`ï¼å¯éï¼åæ§è½ä¼åï¼ |
| | | |
| | | --- |
| | | |
| | | ## 9. AI çæå端任å¡é¡ºåºï¼å»ºè®®ï¼ |
| | | |
| | | 1. å
宿 **æ»è´¦ç§ç®**ï¼å·²æ APIï¼æç¨³å®ï¼ã |
| | | 2. 宿 **åè¯ + åå½ + åè´·å¹³è¡¡æ ¡éª + ç¶ææµè½¬**ã |
| | | 3. å®ç° **ç§ç®æ»è´¦ / ç§ç®æç»è´¦** æ¥è¯¢ã |
| | | 4. å®ç° **åºå®èµäº§ææ§** ä¸ **æ å½¢èµäº§æé**ã |
| | | 5. è¡¥æµè¯ï¼ |
| | | - åè´·å¹³è¡¡æ ¡éª |
| | | - ææ§/æéå
¬å¼ |
| | | - ç§ç®è¢«å¼ç¨ç¦æ¢å é¤ |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | { |
| | | "compilerOptions": { |
| | | "baseUrl": ".", |
| | | "paths": { |
| | | "@/*": ["src/*"], |
| | | "~/*": ["./*"] |
| | | } |
| | | }, |
| | | "include": ["src/**/*.js", "src/**/*.vue", "vite.config.js"] |
| | | } |
| | |
| | | |
| | | const envFilePath = path.join(process.cwd(), '.env.production.local'); |
| | | |
| | | async function copyFileWithOverwrite(src, dest) { |
| | | await fs.mkdir(path.dirname(dest), { recursive: true }); |
| | | if (fsSync.existsSync(dest)) { |
| | | try { |
| | | await fs.chmod(dest, 0o666); |
| | | } catch { |
| | | // Ignore chmod failure and try delete directly. |
| | | } |
| | | await fs.rm(dest, { force: true }); |
| | | } |
| | | await fs.copyFile(src, dest); |
| | | } |
| | | |
| | | try { |
| | | // 1ï¸â£ çæ .env |
| | | console.log("=======çæ.env======="); |
| | |
| | | const backupFile = path.join(replacePath, config[key]); |
| | | const replaceFile = path.join(resourcePath, companyMap[key]); |
| | | |
| | | await fs.mkdir(path.dirname(backupFile), { recursive: true }); |
| | | await fs.copyFile(originFile, backupFile); |
| | | await fs.copyFile(replaceFile, originFile); |
| | | await copyFileWithOverwrite(originFile, backupFile); |
| | | await copyFileWithOverwrite(replaceFile, originFile); |
| | | } |
| | | |
| | | console.log("=====å¼å§æå
======"); |
| | |
| | | const originFile = path.join(rootPath, config[key]); |
| | | const backupFile = path.join(replacePath, config[key]); |
| | | |
| | | await fs.copyFile(backupFile, originFile); |
| | | await copyFileWithOverwrite(backupFile, originFile); |
| | | } |
| | | await fs.rm(replacePath, { recursive: true, force: true }); |
| | | console.log(`ðï¸ å·²å é¤ ${replacePath}`); |
| | |
| | | }, |
| | | "overrides": { |
| | | "quill": "2.0.2" |
| | | } |
| | | }, |
| | | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // éç¨ä¸ä¼ æ¥å£ï¼æ¯æ FormData æ¹éä¼ æä»¶ |
| | | export function uploadFile(data) { |
| | | return request({ |
| | | url: '/common/upload', |
| | | method: 'post', |
| | | data, |
| | | headers: { |
| | | 'Content-Type': 'multipart/form-data', |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | // éç¨ä¸ä¼ æ¥å£ï¼æ¯æ FormData æ¹éä¼ æä»¶,æ°¸ä¸è¿æï¼æ
ç¨ |
| | | export function uploadPublicFile(data) { |
| | | return request({ |
| | | url: '/common/public/upload', |
| | | method: 'post', |
| | | data, |
| | | headers: { |
| | | 'Content-Type': 'multipart/form-data', |
| | | }, |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | export function listCustomer(query) { |
| | | return request({ |
| | | url: '/basic/customer/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // åé
å®¢æ· |
| | | export function assignCustomer(data) { |
| | | return request({ |
| | | url: '/basic/customer/assignCustomer', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | // åæ¶å®¢æ· |
| | | export function recycleCustomer(data) { |
| | | return request({ |
| | | url: '/basic/customer/recycleCustomer', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | // æµå
¥å
¬æµ· |
| | | export function backCustomer(id) { |
| | | return request({ |
| | | url: '/basic/customer/back/' + id, |
| | | method: 'post' |
| | | }) |
| | | } |
| | | |
| | | export function shareCustomer(data) { |
| | | return request({ |
| | | url: '/basic/customer/together', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | export function getCustomer(id) { |
| | | return request({ |
| | | url: '/basic/customer/' + id, |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | export function addCustomer(data) { |
| | | return request({ |
| | | url: '/basic/customer/addCustomer', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | export function updateCustomer(data) { |
| | | return request({ |
| | | url: '/basic/customer/updateCustomer', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | export function exportCustomer(query) { |
| | | return request({ |
| | | url: '/basic/customer/export', |
| | | method: 'get', |
| | | params: query, |
| | | responseType: 'blob' |
| | | }) |
| | | } |
| | | |
| | | export function delCustomer(ids) { |
| | | return request({ |
| | | url: '/basic/customer/delCustomer', |
| | | method: 'delete', |
| | | data: ids |
| | | }) |
| | | } |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | export function listCustomer(query) { |
| | | return request({ |
| | | url: '/basic/customer/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // å®¢æ·æ¡£æ¡ç§æµ·æ¥è¯¢ |
| | | export function listCustomerPrivatePool(query) { |
| | | return request({ |
| | | url: '/customerPrivatePool/listPage', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | export function addCustomerPrivatePool(data) { |
| | | return request({ |
| | | url: '/customerPrivatePool/add', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | export function addCustomerPrivate(data) { |
| | | return request({ |
| | | url: '/customerPrivate/add', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | export function delCustomerPrivate(ids) { |
| | | return request({ |
| | | url: '/customerPrivate/delete', |
| | | method: 'delete', |
| | | data: ids |
| | | }) |
| | | } |
| | | |
| | | export function delCustomerPrivatePool(id) { |
| | | return request({ |
| | | url: '/customerPrivatePool/delete/' + id, |
| | | method: 'delete', |
| | | }) |
| | | } |
| | | |
| | | export function shareCustomer(data) { |
| | | return request({ |
| | | url: '/customerPrivatePool/together', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | export function getCustomer(id) { |
| | | return request({ |
| | | url: '/basic/customer/' + id, |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | export function getCustomerPrivatePoolById(id) { |
| | | return request({ |
| | | url: '/customerPrivatePool/getbyId/' + id, |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | export function getCustomerPrivatePoolInfo(id) { |
| | | return request({ |
| | | url: '/customerPrivatePool/info/' + id, |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | export function addCustomer(data) { |
| | | return request({ |
| | | url: '/basic/customer/addCustomer', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | export function updateCustomer(data) { |
| | | return request({ |
| | | url: '/basic/customer/updateCustomer', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | export function updateCustomerPrivatePool(data) { |
| | | return request({ |
| | | url: '/customerPrivatePool/update', |
| | | method: 'put', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | export function exportCustomer(query) { |
| | | return request({ |
| | | url: '/basic/customer/export', |
| | | method: 'get', |
| | | params: query, |
| | | responseType: 'blob' |
| | | }) |
| | | } |
| | | |
| | | export function delCustomer(ids) { |
| | | return request({ |
| | | url: '/basic/customer/delCustomer', |
| | | method: 'delete', |
| | | data: ids |
| | | }) |
| | | } |
| | | |
| | | export function addCustomerFollow(data) { |
| | | return request({ |
| | | url: '/basic/customer-follow/add', |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // åæ°ç»´æ¤é¡µé¢æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢åæ°å表 |
| | | export function parameterListPage(query) { |
| | | return request({ |
| | | url: "/basic/parameter/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢åæ° |
| | | export function addParameter(data) { |
| | | return request({ |
| | | url: "/basic/parameter/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ç¼è¾åæ° |
| | | export function updateParameter(data) { |
| | | return request({ |
| | | url: "/basic/parameter/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤åæ° |
| | | export function delParameter(ids) { |
| | | return request({ |
| | | url: "/basic/parameter/del", |
| | | method: "delete", |
| | | data: Array.isArray(ids) ? ids : [ids], |
| | | }); |
| | | } |
| | | |
| | | // è·å产åç±»åå表 |
| | | export function getProductTypes() { |
| | | return request({ |
| | | url: "/basic/product/typeList", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢åºç¡åæ° |
| | | export function addBaseParam(data) { |
| | | return request({ |
| | | url: "/technologyParam/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ç¼è¾åºç¡åæ° |
| | | export function editBaseParam(data) { |
| | | return request({ |
| | | url: "/technologyParam/edit", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢åºç¡åæ°å表 |
| | | export function getBaseParamList(query) { |
| | | return request({ |
| | | url: "/technologyParam/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å é¤åºç¡åæ° |
| | | export function removeBaseParam(ids) { |
| | | return request({ |
| | | url: `/technologyParam/remove/` + ids, |
| | | method: "delete", |
| | | }); |
| | | } |
| | |
| | | import request from '@/utils/request' |
| | | import request from "@/utils/request"; |
| | | |
| | | // å·¥åºå表å页æ¥è¯¢ |
| | | export function productProcessListPage(query) { |
| | | return request({ |
| | | url: '/productProcess/listPage', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | url: "/technologyOperation/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // é件页颿¥å£ |
| | | import request from '@/utils/request' |
| | | |
| | | // éä»¶æ¥è¯¢ |
| | | export function attachmentList(query) { |
| | | return request({ |
| | | url: '/storageAttachment/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // éä»¶æ°å¢ |
| | | export function createAttachment(data) { |
| | | return request({ |
| | | url: '/storageAttachment/add', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | // éä»¶å é¤ |
| | | export function deleteAttachment(data) { |
| | | return request({ |
| | | url: '/storageAttachment/delete', |
| | | method: 'delete', |
| | | data |
| | | }) |
| | | } |
| | |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc éªæ¶å®¡æ¹ |
| | | * @param {éªæ¶åæ°} data |
| | | * @returns |
| | | */ |
| | | export const repairAcceptance = (data) => { |
| | | return request({ |
| | | url: `/device/repair/acceptance`, |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** éè´å
¥åºå页å表 */ |
| | | export const listPageAccountPurchase = (params) => { |
| | | return request({ |
| | | url: "/accountPurchase/listPageAccountPurchase", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | /** éè´éè´§å页å表 */ |
| | | export const listPageAccountPurchaseReturn = (params) => { |
| | | return request({ |
| | | url: "/accountPurchase/listPageAccountPurchaseReturn", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** éå®åºåºå页å表 */ |
| | | export const listPageAccountSales = (params) => { |
| | | return request({ |
| | | url: "/accountSales/listPageAccountSales", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | /** éå®éè´§å页å表 */ |
| | | export const listPageAccountSalesReturn = (params) => { |
| | | return request({ |
| | | url: "/accountSales/listPageAccountSalesReturn", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢æ»å¸ç§ç®å表 |
| | | export function listAccountSubject(query) { |
| | | return request({ |
| | | url: "/accountSubject/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢æ»å¸ç§ç® |
| | | export function addAccountSubject(data) { |
| | | return request({ |
| | | url: "/accountSubject/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹æ»å¸ç§ç® |
| | | export function updateAccountSubject(data) { |
| | | return request({ |
| | | url: "/accountSubject/edit", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å 餿»å¸ç§ç® |
| | | export function delAccountSubject(ids) { |
| | | return request({ |
| | | url: "/accountSubject/remove/" + ids, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | // å¯¼åºæ»å¸ç§ç® |
| | | export function exportAccountSubject(data) { |
| | | return request({ |
| | | url: "/accountSubject/export", |
| | | method: "post", |
| | | data: data, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // åºå®èµäº§å页æ¥è¯¢ï¼current/sizeï¼ |
| | | export function listFixedAssetPage(params) { |
| | | return request({ |
| | | url: "/financial/fixedAsset/page", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢åºå®èµäº§ |
| | | export function addFixedAsset(data) { |
| | | return request({ |
| | | url: "/financial/fixedAsset/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹åºå®èµäº§ |
| | | export function updateFixedAsset(data) { |
| | | return request({ |
| | | url: "/financial/fixedAsset/update", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤åºå®èµäº§ï¼åç«¯è¦æ± ids=1&ids=2 å½¢å¼ï¼ |
| | | export function deleteFixedAsset(ids) { |
| | | const idList = Array.isArray(ids) ? ids : [ids]; |
| | | const query = idList |
| | | .filter(id => id !== undefined && id !== null && id !== "") |
| | | .map(id => `ids=${encodeURIComponent(id)}`) |
| | | .join("&"); |
| | | return request({ |
| | | url: `/financial/fixedAsset/delete?${query}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | // ææ§è®¡æï¼{} 表示å
¨é¨å¨ç¨èµäº§ï¼ |
| | | export function depreciateFixedAsset(data = {}) { |
| | | return request({ |
| | | url: "/financial/fixedAsset/depreciate", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ å½¢èµäº§å页æ¥è¯¢ï¼current/sizeï¼ |
| | | export function listIntangibleAssetPage(params) { |
| | | return request({ |
| | | url: "/financial/intangibleAsset/page", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢æ å½¢èµäº§ |
| | | export function addIntangibleAsset(data) { |
| | | return request({ |
| | | url: "/financial/intangibleAsset/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹æ å½¢èµäº§ |
| | | export function updateIntangibleAsset(data) { |
| | | return request({ |
| | | url: "/financial/intangibleAsset/update", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // å 餿 å½¢èµäº§ï¼åç«¯è¦æ± ids=1&ids=2 å½¢å¼ï¼ |
| | | export function deleteIntangibleAsset(ids) { |
| | | const idList = Array.isArray(ids) ? ids : [ids]; |
| | | const query = idList |
| | | .filter(id => id !== undefined && id !== null && id !== "") |
| | | .map(id => `ids=${encodeURIComponent(id)}`) |
| | | .join("&"); |
| | | return request({ |
| | | url: `/financial/intangibleAsset/delete?${query}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | // æé计æï¼{} 表示å
¨é¨å¨ç¨èµäº§ï¼ |
| | | export function amortizeIntangibleAsset(data = {}) { |
| | | return request({ |
| | | url: "/financial/intangibleAsset/amortize", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // ç§ç®æ»è´¦ |
| | | export function getGeneralLedger(params) { |
| | | return request({ |
| | | url: "/financial/ledger/general", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | // ç§ç®æç»è´¦ |
| | | export function getDetailLedger(params) { |
| | | return request({ |
| | | url: "/financial/ledger/detail", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // åè¯å页æ¥è¯¢ï¼current/size + è¿æ»¤æ¡ä»¶ï¼ |
| | | export function listVoucherPage(params) { |
| | | return request({ |
| | | url: "/financial/voucher/page", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢åè¯ |
| | | export function addVoucher(data) { |
| | | return request({ |
| | | url: "/financial/voucher/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹åè¯ï¼ä»
æªè¿è´¦ï¼ |
| | | export function updateVoucher(data) { |
| | | return request({ |
| | | url: "/financial/voucher/update", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // è¿è´¦ |
| | | export function postVoucher(data) { |
| | | return request({ |
| | | url: "/financial/voucher/post", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // ä½åº |
| | | export function cancelVoucher(data) { |
| | | return request({ |
| | | url: "/financial/voucher/cancel", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // 详æ
|
| | | export function getVoucherDetail(id) { |
| | | return request({ |
| | | url: `/financial/voucher/detail/${id}`, |
| | | method: "get", |
| | | }); |
| | | } |
| | |
| | | data: ids, |
| | | }); |
| | | }; |
| | | |
| | | export const batchDeletePendingStockInRecords = (ids) => { |
| | | return request({ |
| | | url: "/stockInRecord/pending", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | }; |
| | | |
| | | // æ¹é审æ¹å
¥åºè®°å½ï¼approvalStatus: approved/rejectedï¼ |
| | | export const batchApproveStockInRecords = (data) => { |
| | | return request({ |
| | | url: "/stockInRecord/approve", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryBatchNoQty = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/getBatchNoQty", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å建åºåè®°å½ |
| | | export const createStockInventory = (params) => { |
| | | return request({ |
| | |
| | | export const subtractStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/subtractStockInventory", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // æ°å¢å
¥åºè®°å½ï¼ä»
å建记å½ï¼ä¸è°æ´åºåï¼ |
| | | export const addStockInRecordOnly = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/addStockInRecordOnly", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // æ°å¢åºåºè®°å½ï¼ä»
å建记å½ï¼ä¸è°æ´åºåï¼ |
| | | export const addStockOutRecordOnly = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/addStockOutRecordOnly", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryByModelId = (productModelId) => { |
| | | return request({ |
| | | url: "/stockInventory/getByModelId", |
| | | method: "get", |
| | | params: { productModelId }, |
| | | }); |
| | | }; |
| | | |
| | |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | //å é¤å¾
审æ¹åºåºä¿¡æ¯ |
| | | export const delPendingStockOut = (ids) => { |
| | | return request({ |
| | | url: "/stockOutRecord/pending", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | // æ¹é审æ¹åºåºè®°å½ï¼approvalStatus: approved/rejectedï¼ |
| | | export const batchApproveStockOutRecords = (data) => { |
| | | return request({ |
| | | url: "/stockOutRecord/approve", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | |
| | | }); |
| | | }; |
| | | |
| | | // æ°å¢å
¥åºè®°å½ï¼ä»
å建记å½ï¼ä¸è°æ´åºåï¼ |
| | | export const addUnqualifiedStockInRecordOnly = (params) => { |
| | | return request({ |
| | | url: "/stockUninventory/addStockInRecordOnly", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // æ°å¢åºåºè®°å½ï¼ä»
å建记å½ï¼ä¸è°æ´åºåï¼ |
| | | export const addUnqualifiedStockOutRecordOnly = (params) => { |
| | | return request({ |
| | | url: "/stockUninventory/addStockOutRecordOnly", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // å»ç»åºåè®°å½ |
| | | export const frozenStockUninventory = (params) => { |
| | | return request({ |
| | |
| | | }); |
| | | } |
| | | |
| | | export function productUploadFile(data) { |
| | | return request({ |
| | | url: "/file/uploadFile", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // export function getProductRecordById(params) { |
| | | // return request({ |
| | | // url: "/purchase/registration/getProductRecordById", |
| | |
| | | }); |
| | | } |
| | | |
| | | // æ ¹æ®éè´å°è´¦ ID æ¥è¯¢å¯é产åçä¿¡æ¯ |
| | | export function getPurchaseReturnOrderByPurchaseLedgerId(query) { |
| | | return request({ |
| | | url: "/purchaseReturnOrders/getByPurchaseLedgerId", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | // purchaseReturnOrders/selectById/xxx |
| | | export function getPurchaseReturnOrderDetail(id) { |
| | |
| | | // å页æ¥è¯¢ |
| | | export function listPage(query) { |
| | | return request({ |
| | | url: "/processRoute/page", |
| | | url: "/technologyRouting/page", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/processRoute", |
| | | url: "/technologyRouting/addTechRoute", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // export function del(ids) { |
| | | // return request({ |
| | | // url: "/processRoute/" + ids, |
| | | // method: "delete", |
| | | // }); |
| | | // } |
| | | export function del(ids) { |
| | | return request({ |
| | | url: '/processRoute/' + ids, |
| | | method: 'delete', |
| | | }) |
| | | url: "/technologyRouting/delete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | export function update(data) { |
| | | return request({ |
| | | url: '/processRoute', |
| | | method: 'put', |
| | | url: "/technologyRouting/editTechRoute", |
| | | method: "put", |
| | | data: data, |
| | | }) |
| | | }); |
| | | } |
| | | |
| | | // è·å详æ
|
| | | export function getById(id) { |
| | | return request({ |
| | | url: `/processRoute/${id}`, |
| | | method: 'get', |
| | | }) |
| | | method: "get", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // éä»¶å表 |
| | | export function listProcessRouteFiles(query) { |
| | | return request({ |
| | | url: "/technologyRoutingFile/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢éä»¶ |
| | | export function addProcessRouteFile(data) { |
| | | return request({ |
| | | url: "/technologyRoutingFile/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤éä»¶ |
| | | export function delProcessRouteFile(ids) { |
| | | return request({ |
| | | url: "/technologyRoutingFile/del", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | |
| | | // å表æ¥è¯¢ |
| | | export function findProcessRouteItemList(query) { |
| | | return request({ |
| | | url: "/processRouteItem/list", |
| | | url: "/technologyRoutingOperation/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | |
| | | export function addOrUpdateProcessRouteItem(data) { |
| | | return request({ |
| | | url: "/processRouteItem", |
| | | url: "/technologyRoutingOperation/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | export function addOrUpdateProcessRouteItem1(data) { |
| | | return request({ |
| | | url: "/technologyRoutingOperation", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | |
| | | // æåºæ¥å£ |
| | | export function sortProcessRouteItem(data) { |
| | | return request({ |
| | | url: "/processRouteItem/sort", |
| | | url: "/technologyRoutingOperation/sort", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | |
| | | // å°idæ°ç»è½¬æ¢ä¸ºéå·åéçåç¬¦ä¸²ï¼æ¼æ¥å°URLåé¢ |
| | | const idsStr = Array.isArray(ids) ? ids.join(",") : ids; |
| | | return request({ |
| | | url: `/processRouteItem/batchDelete/${idsStr}`, |
| | | url: `/technologyRoutingOperation/${idsStr}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | // è·åå·¥åºåæ°å表 |
| | | export function getProcessParamList(query) { |
| | | return request({ |
| | | url: `/technologyRoutingOperationParam/list`, |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // å·¥èºè·¯çº¿åæ°æ°å¢ |
| | | export function addProcessRouteItemParam(data) { |
| | | return request({ |
| | | url: "/technologyRoutingOperationParam/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | // å·¥èºè·¯çº¿åæ°ä¿®æ¹ |
| | | export function editProcessRouteItemParam(data) { |
| | | return request({ |
| | | url: "/technologyRoutingOperationParam", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | // å·¥èºè·¯çº¿åæ°å é¤ |
| | | export function delProcessRouteItemParam(id) { |
| | | return request({ |
| | | url: `/technologyRoutingOperationParam/${id}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | // æå·¥èºè·¯çº¿å·¥åºåæ¥å·¥åºåæ° |
| | | export function syncProcessParamItem(data) { |
| | | return request({ |
| | | url: "/technologyRoutingOperationParam/sync", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | // æå·¥èºè·¯çº¿å·¥åºåæ¥å·¥åºåæ°-ç产订å |
| | | export function syncProcessParamItemOrder(data) { |
| | | return request({ |
| | | url: "/productionOrderRoutingOperationParam/sync", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | |
| | | // å页æ¥è¯¢ |
| | | export function listPage(query) { |
| | | return request({ |
| | | url: "/productBom/listPage", |
| | | url: "/technologyBom/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | // æ°å¢ |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/productBom/add", |
| | | url: "/technologyBom/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å¤å¶ |
| | | export function copy(data) { |
| | | return request({ |
| | | url: "/technologyBom/copy", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | // ä¿®æ¹ |
| | | export function update(data) { |
| | | return request({ |
| | | url: "/productBom/update", |
| | | url: "/technologyBom/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | |
| | | // æ¹éå é¤ |
| | | export function batchDelete(ids) { |
| | | return request({ |
| | | url: "/productBom/batchDelete", |
| | | url: "/technologyBom/batchDelete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | |
| | | // æ ¹æ®äº§ååå·IDæ¥è¯¢BOM |
| | | export function getByModel(productModelId) { |
| | | return request({ |
| | | url: "/productBom/getByModel", |
| | | url: "/technologyBom/getByModel", |
| | | method: "get", |
| | | params: { productModelId }, |
| | | }); |
| | |
| | | // 导åºBOM |
| | | export function exportBom(bomId) { |
| | | return request({ |
| | | url: "/productBom/exportBom", |
| | | url: "/technologyBom/exportBom", |
| | | method: "post", |
| | | params: { bomId }, |
| | | responseType: "blob", |
| | |
| | | // ä¸è½½æ¨¡æ¿ |
| | | export function downloadTemplate() { |
| | | return request({ |
| | | url: "/productBom/downloadTemplate", |
| | | url: "/technologyBom/downloadTemplate", |
| | | method: "get", |
| | | responseType: "blob", |
| | | }); |
| | |
| | | // å表æ¥è¯¢ |
| | | export function findProductProcessRouteItemList(query) { |
| | | return request({ |
| | | url: "/productProcessRoute/list", |
| | | url: "/productionOrderRouting/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | |
| | | export function addOrUpdateProductProcessRouteItem(data) { |
| | | return request({ |
| | | url: "/productProcessRoute/updateRouteItem", |
| | | url: "/productionOrderRouting/updateRouteItem", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | |
| | | // ç产订åä¸ï¼æ°å¢å·¥èºè·¯çº¿é¡¹ç® |
| | | export function addRouteItem(data) { |
| | | return request({ |
| | | url: "/productProcessRoute/addRouteItem", |
| | | url: "/productionOrderRouting/addRouteItem", |
| | | method: "post", |
| | | data, |
| | | }); |
| | |
| | | // è·åç产订åå
³èçå·¥èºè·¯çº¿ä¸»ä¿¡æ¯ |
| | | export function listMain(orderId) { |
| | | return request({ |
| | | url: "/productProcessRoute/listMain", |
| | | url: "/productionOrderRouting/listMain", |
| | | method: "get", |
| | | params: { orderId }, |
| | | }); |
| | |
| | | // å é¤å·¥èºè·¯çº¿é¡¹ç®ï¼è·¯ç±åæ¼æ¥ idï¼ |
| | | export function deleteRouteItem(id) { |
| | | return request({ |
| | | url: `/productProcessRoute/deleteRouteItem/${id}`, |
| | | url: `/productionOrderRouting/deleteRouteItem/${id}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | |
| | | // ç产订åä¸ï¼æåºå·¥èºè·¯çº¿é¡¹ç® |
| | | export function sortRouteItem(data) { |
| | | return request({ |
| | | url: "/productProcessRoute/sortRouteItem", |
| | | url: "/productionOrderRouting/sortRouteItem", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | // è·åå·¥åºåæ°å表-ç产订å |
| | | export function findProcessParamListOrder(query) { |
| | | return request({ |
| | | url: `/productionOrderRoutingOperationParam/list`, |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // å·¥èºè·¯çº¿åæ°æ°å¢-ç产订å |
| | | export function addProcessRouteItemParamOrder(data) { |
| | | return request({ |
| | | url: "/productionOrderRoutingOperationParam", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | // å·¥èºè·¯çº¿åæ°ä¿®æ¹-ç产订å |
| | | export function editProcessRouteItemParamOrder(data) { |
| | | return request({ |
| | | url: "/productionOrderRoutingOperationParam", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | // å·¥èºè·¯çº¿åæ°å é¤-ç产订å |
| | | export function delProcessRouteItemParamOrder(id) { |
| | | return request({ |
| | | url: `/productionOrderRoutingOperationParam/${id}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | |
| | | // å页æ¥è¯¢ |
| | | export function queryList(id) { |
| | | return request({ |
| | | url: "/productStructure/listBybomId/" + id, |
| | | url: "/technologyBomStructure/listByBomId/" + id, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // å页æ¥è¯¢-产å订å |
| | | export function queryList2(id) { |
| | | return request({ |
| | | url: "/productionBomStructure/listByBomId/" + id, |
| | | method: "get", |
| | | }); |
| | | } |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/productStructure", |
| | | url: "/productStructure/" + data.bomId, |
| | | method: "post", |
| | | data: data.children, |
| | | }); |
| | | } |
| | | |
| | | export function addBomDetail(data) { |
| | | return request({ |
| | | url: "/technologyBomStructure", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | // å页æ¥è¯¢-产å订å |
| | | // export function queryList2(id) { |
| | | // return request({ |
| | | // url: "/productionOrderStructure/getBomStructs/" + id, |
| | | // method: "get", |
| | | // }); |
| | | // } |
| | | |
| | | export function add2(data) { |
| | | return request({ |
| | | url: "/productionBomStructure/addOrUpdateBomStructs", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | |
| | | // salesLedger/productionAccounting/page |
| | | export function salesLedgerProductionAccountingList(query) { |
| | | return request({ |
| | | url: "/salesLedger/productionAccounting/page", |
| | | url: "/productionAccount/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | // |
| | | export function salesLedgerProductionAccountingListProductionDetails(query) { |
| | | return request({ |
| | | url: "/salesLedger/productionAccounting/listProductionDetails", |
| | | url: "/productionAccount/listProductionDetails", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | |
| | | export function productOrderListPage(query) { |
| | | return request({ |
| | | url: "/productOrder/page", |
| | | url: "/productionOrder/page", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | // ç产订å-ç»å®å·¥èºè·¯çº¿ |
| | | export function bindingRoute(data) { |
| | | return request({ |
| | | url: "/productOrder/bindingRoute", |
| | | url: "/productionOrder/bindingRoute", |
| | | method: "post", |
| | | data, |
| | | }); |
| | |
| | | // ç产订å-æ°å¢ |
| | | export function addProductOrder(data) { |
| | | return request({ |
| | | url: "/productOrder/addProductOrder", |
| | | url: "/productionOrder/addOrder", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ç产订å-ä¿®æ¹ |
| | | export function updateProductOrder(data) { |
| | | return request({ |
| | | url: "/productionOrder/updateOrder", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | |
| | | |
| | | export function delProductOrder(ids) { |
| | | return request({ |
| | | url: `/productOrder/${ids}`, |
| | | url: `/productionOrder/delete`, |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // ç产订å-ä¿å颿å°è´¦ |
| | | // export function saveMaterialPickingLedger(data) { |
| | | // return request({ |
| | | // url: "/productOrderMaterial/save", |
| | | // method: "post", |
| | | // data, |
| | | // }); |
| | | // } |
| | | export function saveMaterialPickingLedger(data) { |
| | | return request({ |
| | | url: "/productOrderMaterial/update", |
| | | url: "/productionOrderPick/savePick", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | export function updateMaterialPickingLedger(data) { |
| | | return request({ |
| | | url: "/productionOrderPick/updatePick", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // ç产订å-é¢æè¯¦æ
å表 |
| | | export function listMaterialPickingDetail(query) { |
| | | // çäº§è®¢åæº¯æºè¯¦æ
|
| | | export function getOrderDetail(npsNo) { |
| | | return request({ |
| | | url: "/productOrderMaterial/detailList", |
| | | url: "/productionOrder/ordeDetail", |
| | | method: "get", |
| | | params: { npsNo }, |
| | | }); |
| | | } |
| | | // ç产订å-é¢æè¯¦æ
å表 |
| | | // export function listMaterialPickingDetail(query) { |
| | | // return request({ |
| | | // url: "/productOrderMaterial/detailList", |
| | | // method: "get", |
| | | // params: query, |
| | | // }); |
| | | // } |
| | | export function listMaterialPickingBom(productionOrderId) { |
| | | return request({ |
| | | url: "/productionOrder/pick/" + productionOrderId, |
| | | method: "get", |
| | | }); |
| | | } |
| | | export function listMaterialPickingDetail(productionOrderId) { |
| | | return request({ |
| | | url: "/productionOrderPick/detail/" + productionOrderId, |
| | | method: "get", |
| | | }); |
| | | } |
| | | // ç产订å-è¡¥æè®°å½å表 |
| | | export function listMaterialSupplementRecord(query) { |
| | | return request({ |
| | | url: "/productionOrderPickRecord/feeding", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // ç产订å-è¡¥æè®°å½å表 |
| | | export function listMaterialSupplementRecord(query) { |
| | | // ç产订å-è·åæ¥æºæ°æ® |
| | | export function getProductOrderSource(id) { |
| | | return request({ |
| | | url: "/productOrderMaterial/supplementRecord", |
| | | url: `/productionOrder/source/${id}`, |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | |
| | | // å页æ¥è¯¢ |
| | | export function listPage(query) { |
| | | return request({ |
| | | url: "/productProcess/listPage", |
| | | url: "/technologyOperation/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | |
| | | export function processList(query) { |
| | | return request({ |
| | | url: "/productProcess/list", |
| | | url: "/technologyOperation/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å·¥åºæ¥è¯¢ |
| | | export function list(query) { |
| | | return request({ |
| | | url: "/technologyOperation/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/productProcess", |
| | | url: "/technologyOperation/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | |
| | | |
| | | export function del(data) { |
| | | return request({ |
| | | url: '/productProcess/batchDelete', |
| | | method: 'delete', |
| | | url: "/technologyOperation/batchDelete", |
| | | method: "delete", |
| | | data: data, |
| | | }) |
| | | }); |
| | | } |
| | | |
| | | export function update(data) { |
| | | return request({ |
| | | url: '/productProcess/update', |
| | | method: 'put', |
| | | url: "/technologyOperation/update", |
| | | method: "put", |
| | | data: data, |
| | | }) |
| | | } |
| | | |
| | | // å·¥åºæ¥è¯¢ |
| | | export function list() { |
| | | return request({ |
| | | url: "/productProcess/list", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // 导å
¥æ°æ® |
| | | export function importData(data) { |
| | | return request({ |
| | | url: "/productProcess/importData", |
| | | url: "/technologyOperation/importData", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | |
| | | // ä¸è½½æ¨¡æ¿ |
| | | export function downloadTemplate() { |
| | | return request({ |
| | | url: "/productProcess/downloadTemplate", |
| | | url: "/technologyOperation/downloadTemplate", |
| | | method: "post", |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // è·åå·¥åºåæ°å表 |
| | | export function getProcessParamList(params) { |
| | | return request({ |
| | | url: `/technologyOperationParam/list`, |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | // æ·»å å·¥åºåæ° |
| | | export function addProcessParam(data) { |
| | | return request({ |
| | | url: "/technologyOperationParam/", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ç¼è¾å·¥åºåæ° |
| | | export function editProcessParam(data) { |
| | | return request({ |
| | | url: "/technologyOperationParam/", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤å·¥åºåæ° |
| | | export function deleteProcessParam(id) { |
| | | return request({ |
| | | url: `/technologyOperationParam/batchDelete/${id}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | |
| | | |
| | | export function productWorkOrderPage(query) { |
| | | return request({ |
| | | url: "/productWorkOrder/page", |
| | | url: "/productionOperationTask/page", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | |
| | | export function updateProductWorkOrder(data) { |
| | | return request({ |
| | | url: "/productWorkOrder/updateProductWorkOrder", |
| | | url: "/productionOperationTask/updateProductWorkOrder", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | |
| | | }); |
| | | } |
| | | |
| | | export function assignProductWorkOrder(data) { |
| | | return request({ |
| | | url: "/productionOperationTask/assign", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¸è½½å·¥åæµè½¬å¡ï¼è¿åæä»¶æµï¼ |
| | | export function downProductWorkOrder(id) { |
| | | return request({ |
| | | url: "/productWorkOrder/down", |
| | | url: "/productionOperationTask/down", |
| | | method: "post", |
| | | data: { id }, |
| | | responseType: "blob", |
| | |
| | | // å·¥å-è¡¥æ |
| | | export function addWorkOrderMaterialSupplement(data) { |
| | | return request({ |
| | | url: "/productWorkOrder/material/supplement", |
| | | url: "/productionOperationTask/material/supplement", |
| | | method: "post", |
| | | data, |
| | | }); |
| | |
| | | // å·¥å-éæ |
| | | export function addWorkOrderMaterialReturn(data) { |
| | | return request({ |
| | | url: "/productWorkOrder/material/return", |
| | | url: "/productionOperationTask/material/return", |
| | | method: "post", |
| | | data, |
| | | }); |
| | |
| | | // å·¥å-è¡¥æè®°å½ |
| | | export function listWorkOrderMaterialSupplementRecord(query) { |
| | | return request({ |
| | | url: "/productWorkOrder/material/supplementRecord", |
| | | url: "/productionOperationTask/material/supplementRecord", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | // å·¥å-é¢ç¨ï¼æäº¤å®é
é¢ç¨æ°éï¼ |
| | | export function pickWorkOrderMaterial(data) { |
| | | return request({ |
| | | url: "/productWorkOrder/material/pick", |
| | | url: "/productionOperationTask/material/pick", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // è·åå·¥åºç»è®¡æ°æ® |
| | | export function getOperationStatistics(query) { |
| | | return request({ |
| | | url: "/productionOperationTask/getOperation", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // ç产订å页颿¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | export function productionPlanListPage(query) { |
| | | return request({ |
| | | url: "/productionPlan/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æåæ°æ® |
| | | export function loadProdData(query) { |
| | | return request({ |
| | | url: "/productionPlan/loadProdData", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | export function summaryByProductType(query) { |
| | | return request({ |
| | | url: "/productionPlan/summaryByProductType", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 导åºç产计å |
| | | export function exportProductionPlan(bomId) { |
| | | return request({ |
| | | url: "/productionPlan/export", |
| | | method: "post", |
| | | params: { bomId }, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // ç产计å-æ°å¢ä¿®æ¹ |
| | | export function productionPlanAdd(query) { |
| | | return request({ |
| | | url: "/productionPlan/addProductionPlan", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | export function productionPlanUpdate(query) { |
| | | return request({ |
| | | url: "/productionPlan/updateProductionPlan", |
| | | method: "put", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // ç产计å-å é¤ |
| | | export function productionPlanDelete(data) { |
| | | return request({ |
| | | url: "/productionPlan/deleteProductionPlan", |
| | | method: "delete", |
| | | data, |
| | | }); |
| | | } |
| | | // åå¹¶ä¸å |
| | | export function productionPlanCombine(query) { |
| | | return request({ |
| | | url: "/productionPlan/combine", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // 追踪è¿åº¦ |
| | | export function trackProgressByNo(query) { |
| | | return request({ |
| | | url: "/track/trackProgressByNo", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | |
| | | } |
| | | |
| | | // ä¿®æ¹åè´§å°è´¦ |
| | | export function getDeliveryDetail(id) { |
| | | return request({ |
| | | url: `/shippingInfo/getDateil/${id}`, |
| | | method: "get", |
| | | }); |
| | | } |
| | | // ä¿®æ¹åè´§å°è´¦ |
| | | export function getDeliveryDetailByShippingNo(query) { |
| | | return request({ |
| | | url: "/shippingInfo/getDateilByShippingNo", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | export function addOrUpdateDeliveryLedger(query) { |
| | | return request({ |
| | | url: "/shippingInfo/update", |
| | |
| | | } |
| | | |
| | | // ä¸ä¼ APK |
| | | export function uploadApk(data) { |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/app/uploadApk", |
| | | url: "/app/add", |
| | | method: "post", |
| | | data, |
| | | headers: { |
| | | "Content-Type": "multipart/form-data", |
| | | }, |
| | | data |
| | | }); |
| | | } |
| | |
| | | }
|
| | | }
|
| | | .table_list {
|
| | | margin-top: 20px;
|
| | | background: rgba(255, 255, 255, 0.88);
|
| | | border: 1px solid var(--surface-border);
|
| | | border-radius: var(--radius-md);
|
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { Cpu } from '@element-plus/icons-vue' |
| | | |
| | | export const generalAssistant = { |
| | | key: 'general', |
| | | label: 'å¾
åå©ç', |
| | | title: 'å¾
åæºè½å©ç', |
| | | tooltip: 'å¾
å婿', |
| | | icon: Cpu, |
| | | apiBase: '/xiaozhi', |
| | | storageKey: 'ai_chat_uuid', |
| | | placeholder: '请è¾å
¥æ¨çé®é¢... (Enter åé, Shift+Enter æ¢è¡)', |
| | | welcomeMessage: 'ä½ å¥½', |
| | | description: 'æå¯ä»¥åçä½ çé®é¢ï¼ä¸ºä½ æä¾ä¸å¡æ°æ®è§£è¯»ä¿¡æ¯ãå¤ç建议åè¾
å©å³çæ¯æã', |
| | | allowFileUpload: true, |
| | | emptySessionText: 'ææ åå²ä¼è¯', |
| | | quickPrompts: [ |
| | | 'æå½åæåªäºå®¡æ¹å¾
åéè¦å¤çï¼', |
| | | '帮æååºä»å¤©æ°å¢ç审æ¹å¾
åã', |
| | | 'å½åå¾
æå®¡æ¹çåæ®ï¼ææ¶é´ååºååºæ¥ã', |
| | | 'æåèµ·ç审æ¹éï¼åªäºè¿å¨å¤çä¸ï¼', |
| | | 'æ¥è¯¢æµç¨ç¼å· XXX ç审æ¹è¯¦æ
ã', |
| | | 'æµç¨ç¼å· XXX ç°å¨å¡å¨åªä¸ªå®¡æ¹èç¹ï¼å½å审æ¹äººæ¯è°ï¼', |
| | | 'å¸®ææ¥çæµç¨ç¼å· XXX çå®¡æ¹æµè½¬è®°å½ã', |
| | | 'è¿7天æç审æ¹å¾
åç»è®¡æ
嵿乿 ·ï¼', |
| | | 'æ¬ææç审æ¹ä¸ï¼éè¿ã驳åãå¤çä¸åæå¤å°ï¼', |
| | | 'è¿30天åç±»åå®¡æ¹æ°éå叿¯ä»ä¹ï¼', |
| | | '帮æå®¡æ¹éè¿æµç¨ç¼å· XXXï¼å¤æ³¨âåæâã', |
| | | '帮æé©³åæµç¨ç¼å· XXXï¼å¤æ³¨â请补å
说æâã', |
| | | 'æ¤éæåå对æµç¨ç¼å· XXX çå®¡æ¹æä½ã', |
| | | '帮æä¿®æ¹æµç¨ç¼å· XXX ç夿³¨ä¸ºâ已补å
éä»¶âã', |
| | | 'å 餿åèµ·çæµç¨ç¼å· XXXã' |
| | | ] |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { generalAssistant } from './generalAssistant' |
| | | import { purchaseAssistant } from './purchaseAssistant' |
| | | |
| | | export { generalAssistant, purchaseAssistant } |
| | | |
| | | export const builtInAssistants = [generalAssistant, purchaseAssistant] |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { ShoppingCart } from '@element-plus/icons-vue' |
| | | |
| | | export const purchaseAssistant = { |
| | | key: 'purchase', |
| | | label: 'éè´å©ç', |
| | | title: 'éè´æºè½å©ç', |
| | | tooltip: 'éè´æºè½å©ç', |
| | | icon: ShoppingCart, |
| | | apiBase: '/purchase-ai', |
| | | storageKey: 'purchase_ai_chat_uuid', |
| | | placeholder: '请è¾å
¥éè´é®é¢... (Enter åé, Shift+Enter æ¢è¡)', |
| | | welcomeMessage: 'ä½ å¥½', |
| | | description: 'æå¯ä»¥åå©ä½ åæéè´è®¢åãå°è´§è¿åº¦ãä¾åºå表ç°å仿¬¾æ
åµï¼å¸®å©ä½ å¿«éå®ä½éè´å¼å¸¸ã', |
| | | allowFileUpload: true, |
| | | allowMultipleFileUpload: true, |
| | | fileAnalyzeUrl: '/purchase-ai/analyze-files', |
| | | emptySessionText: 'ææ éè´ä¼è¯', |
| | | quickPrompts: [ |
| | | 'æ¬æéè´é颿åååçç©ææåªäºï¼', |
| | | 'åªäºéè´è®¢åè¿æªå
¥åºï¼', |
| | | 'æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäºï¼', |
| | | '帮æç»è®¡å¾
仿¬¾éè´å', |
| | | 'ååºæ¬æéè´éè´§æ
åµ' |
| | | ] |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup> |
| | | const props = defineProps({ |
| | | fileList: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | thumbSize: { |
| | | type: Number, |
| | | default: 72, |
| | | }, |
| | | gap: { |
| | | type: Number, |
| | | default: 10, |
| | | }, |
| | | }) |
| | | |
| | | const normalizedList = computed(() => { |
| | | return (props.fileList || []) |
| | | .filter((item) => item && item.previewURL) |
| | | .map((item, index) => ({ |
| | | id: item.id ?? index, |
| | | name: item.originalFilename || `image-${index + 1}`, |
| | | url: item.previewURL, |
| | | })) |
| | | }) |
| | | const previewUrls = computed(() => normalizedList.value.map((item) => item.url)) |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="attachment-image-preview"> |
| | | <div v-if="!normalizedList.length" class="empty">ææ å¾ç</div> |
| | | |
| | | <div v-else class="thumbs" :style="{ gap: `${gap}px` }"> |
| | | <el-image |
| | | v-for="(item, index) in normalizedList" |
| | | :key="item.id" |
| | | class="thumb" |
| | | :style="{ width: `${thumbSize}px`, height: `${thumbSize}px` }" |
| | | :src="item.url" |
| | | :preview-src-list="previewUrls" |
| | | :initial-index="index" |
| | | fit="cover" |
| | | preview-teleported |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .attachment-image-preview { |
| | | width: 100%; |
| | | } |
| | | |
| | | .empty { |
| | | height: 120px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--el-text-color-secondary); |
| | | border: 1px dashed var(--el-border-color); |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .thumbs { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .thumb { |
| | | border: 1px solid var(--el-border-color); |
| | | border-radius: 6px; |
| | | overflow: hidden; |
| | | cursor: pointer; |
| | | background: #fff; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup> |
| | | import { UploadFilled } from '@element-plus/icons-vue' |
| | | import { uploadFile } from '@/api/basicData/common' |
| | | |
| | | const props = defineProps({ |
| | | fileList: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | index: { |
| | | type: Number, |
| | | default: -1, |
| | | }, |
| | | childrenKey: { |
| | | type: String, |
| | | default: 'files', |
| | | }, |
| | | limit: { |
| | | type: Number, |
| | | default: 10, |
| | | }, |
| | | fileSize: { |
| | | type: Number, |
| | | default: 50, |
| | | }, |
| | | fileType: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | buttonText: { |
| | | type: String, |
| | | default: 'åå»éæ©æä»¶', |
| | | }, |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | uploadFieldName: { |
| | | type: String, |
| | | default: 'files', |
| | | }, |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:fileList', 'change']) |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const uploadRef = ref() |
| | | const uploadQueueTimer = ref(null) |
| | | const uploading = ref(false) |
| | | const queuedUidSet = ref(new Set()) |
| | | const innerList = ref([]) |
| | | |
| | | function readListFromProps() { |
| | | if (props.index > -1) { |
| | | const row = props.fileList?.[props.index] |
| | | return Array.isArray(row?.[props.childrenKey]) ? row[props.childrenKey] : [] |
| | | } |
| | | return Array.isArray(props.fileList) ? props.fileList : [] |
| | | } |
| | | |
| | | watch( |
| | | () => props.fileList, |
| | | () => { |
| | | innerList.value = [...readListFromProps()] |
| | | }, |
| | | { deep: true, immediate: true }, |
| | | ) |
| | | |
| | | const currentList = computed({ |
| | | get() { |
| | | return innerList.value |
| | | }, |
| | | set(value) { |
| | | const nextList = Array.isArray(value) ? value : [] |
| | | innerList.value = nextList |
| | | |
| | | if (props.index > -1) { |
| | | const nextModelValue = Array.isArray(props.fileList) ? [...props.fileList] : [] |
| | | const currentRow = nextModelValue[props.index] || {} |
| | | nextModelValue[props.index] = { |
| | | ...currentRow, |
| | | [props.childrenKey]: nextList, |
| | | } |
| | | emit('update:fileList', nextModelValue) |
| | | emit('change', nextList, nextModelValue) |
| | | return |
| | | } |
| | | |
| | | emit('update:fileList', nextList) |
| | | emit('change', nextList, nextList) |
| | | }, |
| | | }) |
| | | |
| | | const displayFileList = computed(() => { |
| | | return currentList.value.map((item, index) => ({ |
| | | uid: getItemUid(item, index), |
| | | name: getItemName(item, index), |
| | | url: getItemUrl(item), |
| | | status: 'success', |
| | | rawData: item, |
| | | })) |
| | | }) |
| | | |
| | | const uploadTip = computed(() => { |
| | | if (!props.fileType.length) return `å个æä»¶ä¸è¶
è¿ ${props.fileSize}MB` |
| | | return `æ¯æ ${props.fileType.join('/')}ï¼å个æä»¶ä¸è¶
è¿ ${props.fileSize}MB` |
| | | }) |
| | | |
| | | function getItemUid(item, index) { |
| | | if (item?.id !== undefined && item?.id !== null) return `${item.id}` |
| | | return `${getItemName(item, index)}-${getItemUrl(item) || index}` |
| | | } |
| | | |
| | | function getItemUrl(item) { |
| | | if (!item) return '' |
| | | if (typeof item === 'string') return item |
| | | return item.url || item.downloadURL || item.previewURL || item.previewUrl || '' |
| | | } |
| | | |
| | | function getItemName(item, index = 0) { |
| | | if (!item) return `file-${index + 1}` |
| | | if (typeof item === 'string') return `file-${index + 1}` |
| | | return item.name || item.originalFilename || item.fileName || item.uidFilename || `file-${index + 1}` |
| | | } |
| | | |
| | | function normalizeResponseItem(item, index) { |
| | | if (typeof item === 'string') { |
| | | return { |
| | | name: `file-${currentList.value.length + index + 1}`, |
| | | url: item, |
| | | } |
| | | } |
| | | return Object.assign({}, item, { |
| | | url: item.url || item.downloadURL || item.previewURL || item.previewUrl || '', |
| | | name: item.name || item.originalFilename || item.fileName || item.uidFilename || `file-${currentList.value.length + index + 1}`, |
| | | }) |
| | | } |
| | | |
| | | function extractResponseArray(response) { |
| | | if (Array.isArray(response)) return response |
| | | if (Array.isArray(response?.data)) return response.data |
| | | if (Array.isArray(response?.data?.data)) return response.data.data |
| | | if (Array.isArray(response?.payload)) return response.payload |
| | | if (Array.isArray(response?.payload?.data)) return response.payload.data |
| | | if (Array.isArray(response?.rows)) return response.rows |
| | | if (Array.isArray(response?.result)) return response.result |
| | | return [] |
| | | } |
| | | |
| | | function validateFile(rawFile) { |
| | | const extension = rawFile.name.includes('.') |
| | | ? rawFile.name.slice(rawFile.name.lastIndexOf('.') + 1).toLowerCase() |
| | | : '' |
| | | |
| | | if (props.fileType.length) { |
| | | const isValidType = props.fileType.some((type) => { |
| | | const normalizedType = String(type).toLowerCase() |
| | | return rawFile.type.toLowerCase().includes(normalizedType) || extension === normalizedType |
| | | }) |
| | | if (!isValidType) { |
| | | proxy.$modal.msgError(`请ä¸ä¼ ${props.fileType.join('/')} æ ¼å¼çæä»¶`) |
| | | return false |
| | | } |
| | | } |
| | | |
| | | const isWithinSize = rawFile.size / 1024 / 1024 <= props.fileSize |
| | | if (!isWithinSize) { |
| | | proxy.$modal.msgError(`æä»¶å¤§å°ä¸è½è¶
è¿ ${props.fileSize}MB`) |
| | | return false |
| | | } |
| | | |
| | | return true |
| | | } |
| | | |
| | | function scheduleUpload(uploadFiles) { |
| | | clearTimeout(uploadQueueTimer.value) |
| | | uploadQueueTimer.value = setTimeout(() => { |
| | | const readyFiles = uploadFiles.filter((file) => file.raw && !queuedUidSet.value.has(file.uid)) |
| | | if (!readyFiles.length) return |
| | | |
| | | const remainCount = props.limit - currentList.value.length |
| | | if (remainCount <= 0) { |
| | | proxy.$modal.msgError(`æå¤ä¸ä¼ ${props.limit} 个æä»¶`) |
| | | uploadRef.value?.clearFiles() |
| | | return |
| | | } |
| | | |
| | | const selectedFiles = readyFiles.slice(0, remainCount) |
| | | if (selectedFiles.length < readyFiles.length) { |
| | | proxy.$modal.msgWarning(`æå¤ä¸ä¼ ${props.limit} 个æä»¶ï¼è¶
åºé¨å已忽ç¥`) |
| | | } |
| | | |
| | | selectedFiles.forEach((file) => queuedUidSet.value.add(file.uid)) |
| | | uploadSelectedFiles(selectedFiles) |
| | | }, 0) |
| | | } |
| | | |
| | | async function uploadSelectedFiles(files) { |
| | | const validFiles = files.filter((file) => validateFile(file.raw)) |
| | | const invalidFiles = files.filter((file) => !validFiles.includes(file)) |
| | | |
| | | invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | |
| | | if (!validFiles.length) { |
| | | uploadRef.value?.clearFiles() |
| | | return |
| | | } |
| | | |
| | | const formData = new FormData() |
| | | validFiles.forEach((file) => { |
| | | formData.append(props.uploadFieldName, file.raw) |
| | | }) |
| | | |
| | | uploading.value = true |
| | | proxy.$modal.loading('æä»¶ä¸ä¼ ä¸ï¼è¯·ç¨å...') |
| | | |
| | | try { |
| | | const response = await uploadFile(formData) |
| | | const responseList = extractResponseArray(response).map((item, index) => normalizeResponseItem(item, index)) |
| | | |
| | | if (!responseList.length) { |
| | | proxy.$modal.msgError('ä¸ä¼ æ¥å£æªè¿åæ°ç»æ°æ®') |
| | | return |
| | | } |
| | | |
| | | currentList.value = [...currentList.value, ...responseList] |
| | | proxy.$modal.msgSuccess('ä¸ä¼ æå') |
| | | } catch (error) { |
| | | proxy.$modal.msgError(error?.message || 'ä¸ä¼ 失败') |
| | | } finally { |
| | | validFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | uploadRef.value?.clearFiles() |
| | | uploading.value = false |
| | | proxy.$modal.closeLoading() |
| | | } |
| | | } |
| | | |
| | | function handleChange(file, uploadFiles) { |
| | | if (props.disabled || uploading.value) return |
| | | scheduleUpload(uploadFiles) |
| | | } |
| | | |
| | | function handleRemove(file) { |
| | | const targetUrl = file.url || getItemUrl(file.rawData) |
| | | const nextList = currentList.value.filter((item, index) => { |
| | | const itemUrl = getItemUrl(item) |
| | | const itemName = getItemName(item, index) |
| | | return !(itemUrl === targetUrl && itemName === file.name) |
| | | }) |
| | | currentList.value = nextList |
| | | } |
| | | |
| | | function handleExceed() { |
| | | proxy.$modal.msgError(`æå¤ä¸ä¼ ${props.limit} 个æä»¶`) |
| | | } |
| | | |
| | | function openFile(file) { |
| | | const fileUrl = file.url || getItemUrl(file.rawData) |
| | | if (!fileUrl) return |
| | | window.open(fileUrl, '_blank') |
| | | } |
| | | |
| | | onBeforeUnmount(() => { |
| | | clearTimeout(uploadQueueTimer.value) |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="attachment-upload-file"> |
| | | <el-upload |
| | | ref="uploadRef" |
| | | drag |
| | | :auto-upload="false" |
| | | :multiple="true" |
| | | :show-file-list="true" |
| | | :file-list="displayFileList" |
| | | :disabled="disabled || uploading" |
| | | :limit="limit" |
| | | :on-change="handleChange" |
| | | :on-remove="handleRemove" |
| | | :on-exceed="handleExceed" |
| | | :on-preview="openFile" |
| | | > |
| | | <el-icon class="upload-drag-icon"><UploadFilled /></el-icon> |
| | | <div class="el-upload__text"> |
| | | å°æä»¶æå°æ¤å¤ï¼æ <em>{{ buttonText }}</em> |
| | | </div> |
| | | <div class="upload-tip">{{ uploadTip }}</div> |
| | | </el-upload> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .attachment-upload-file { |
| | | width: 100%; |
| | | } |
| | | |
| | | .upload-drag-icon { |
| | | font-size: 40px; |
| | | color: var(--el-text-color-secondary); |
| | | } |
| | | |
| | | .upload-tip { |
| | | margin-top: 8px; |
| | | color: var(--el-text-color-secondary); |
| | | font-size: 12px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup> |
| | | import {Plus} from '@element-plus/icons-vue' |
| | | import {uploadFile} from '@/api/basicData/common' |
| | | |
| | | const props = defineProps({ |
| | | fileList: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | index: { |
| | | type: Number, |
| | | default: -1, |
| | | }, |
| | | childrenKey: { |
| | | type: String, |
| | | default: 'images', |
| | | }, |
| | | limit: { |
| | | type: Number, |
| | | default: 10, |
| | | }, |
| | | fileSize: { |
| | | type: Number, |
| | | default: 10, |
| | | }, |
| | | fileType: { |
| | | type: Array, |
| | | default: () => ['png', 'jpg', 'jpeg', 'webp'], |
| | | }, |
| | | buttonText: { |
| | | type: String, |
| | | default: 'ä¸ä¼ å¾ç', |
| | | }, |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | uploadFieldName: { |
| | | type: String, |
| | | default: 'files', |
| | | }, |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:fileList', 'change']) |
| | | const {proxy} = getCurrentInstance() |
| | | |
| | | const uploadRef = ref() |
| | | const previewVisible = ref(false) |
| | | const previewUrl = ref('') |
| | | const uploadQueueTimer = ref(null) |
| | | const uploading = ref(false) |
| | | const queuedUidSet = ref(new Set()) |
| | | |
| | | const currentList = computed({ |
| | | get() { |
| | | if (props.index > -1) { |
| | | const row = props.fileList?.[props.index] |
| | | return Array.isArray(row?.[props.childrenKey]) ? row[props.childrenKey] : [] |
| | | } |
| | | return Array.isArray(props.fileList) ? props.fileList : [] |
| | | }, |
| | | set(value) { |
| | | const nextList = Array.isArray(value) ? value : [] |
| | | if (props.index > -1) { |
| | | const nextModelValue = Array.isArray(props.fileList) ? [...props.fileList] : [] |
| | | const currentRow = nextModelValue[props.index] || {} |
| | | nextModelValue[props.index] = { |
| | | ...currentRow, |
| | | [props.childrenKey]: nextList, |
| | | } |
| | | emit('update:fileList', nextModelValue) |
| | | emit('change', nextList, nextModelValue) |
| | | return |
| | | } |
| | | emit('update:fileList', nextList) |
| | | emit('change', nextList, nextList) |
| | | }, |
| | | }) |
| | | |
| | | const displayFileList = computed(() => { |
| | | return currentList.value.map((item, index) => ({ |
| | | uid: getItemUid(item, index), |
| | | name: getItemName(item, index), |
| | | url: getItemUrl(item), |
| | | status: 'success', |
| | | rawData: item, |
| | | })) |
| | | }) |
| | | |
| | | const uploadTip = computed(() => { |
| | | return `æ¯æ ${props.fileType.join('/')}ï¼åå¼ ä¸è¶
è¿ ${props.fileSize}MBï¼æå¤ä¸ä¼ ${props.limit} å¼ å¾ç` |
| | | }) |
| | | |
| | | function getItemUid(item, index) { |
| | | if (item?.id !== undefined && item?.id !== null) return `${item.id}` |
| | | return `${getItemName(item, index)}-${getItemUrl(item) || index}` |
| | | } |
| | | |
| | | function getItemUrl(item) { |
| | | if (!item) return '' |
| | | if (typeof item === 'string') return item |
| | | return item.url || item.previewURL || '' |
| | | } |
| | | |
| | | function getItemName(item, index = 0) { |
| | | if (!item) return `image-${index + 1}` |
| | | if (typeof item === 'string') return `image-${index + 1}` |
| | | return item.name || item.fileName || item.originalFilename || `image-${index + 1}` |
| | | } |
| | | |
| | | function normalizeResponseItem(item, index) { |
| | | if (typeof item === 'string') { |
| | | return { |
| | | name: `image-${currentList.value.length + index + 1}`, |
| | | url: item, |
| | | } |
| | | } |
| | | return Object.assign({}, item, { |
| | | url: item.url || item.previewURL || item.previewUrl || '', |
| | | name: item.name || item.originalFilename || item.fileName || `image-${currentList.value.length + index + 1}`, |
| | | }) |
| | | } |
| | | |
| | | function extractResponseArray(response) { |
| | | if (Array.isArray(response)) return response |
| | | if (Array.isArray(response?.data)) return response.data |
| | | if (Array.isArray(response?.data?.data)) return response.data.data |
| | | if (Array.isArray(response?.payload)) return response.payload |
| | | if (Array.isArray(response?.payload?.data)) return response.payload.data |
| | | if (Array.isArray(response?.rows)) return response.rows |
| | | if (Array.isArray(response?.result)) return response.result |
| | | return [] |
| | | } |
| | | |
| | | function validateFile(rawFile) { |
| | | let isValidType = false |
| | | const extension = rawFile.name.includes('.') |
| | | ? rawFile.name.slice(rawFile.name.lastIndexOf('.') + 1).toLowerCase() |
| | | : '' |
| | | |
| | | if (props.fileType.length) { |
| | | isValidType = props.fileType.some((type) => { |
| | | const normalizedType = String(type).toLowerCase() |
| | | return rawFile.type.toLowerCase().includes(normalizedType) || extension === normalizedType |
| | | }) |
| | | } else { |
| | | isValidType = rawFile.type.includes('image') |
| | | } |
| | | |
| | | if (!isValidType) { |
| | | proxy.$modal.msgError(`请ä¸ä¼ ${props.fileType.join('/')} æ ¼å¼çå¾ç`) |
| | | return false |
| | | } |
| | | |
| | | const isWithinSize = rawFile.size / 1024 / 1024 <= props.fileSize |
| | | if (!isWithinSize) { |
| | | proxy.$modal.msgError(`å¾ç大å°ä¸è½è¶
è¿ ${props.fileSize}MB`) |
| | | return false |
| | | } |
| | | |
| | | return true |
| | | } |
| | | |
| | | function scheduleUpload(uploadFiles) { |
| | | clearTimeout(uploadQueueTimer.value) |
| | | uploadQueueTimer.value = setTimeout(() => { |
| | | const readyFiles = uploadFiles.filter((file) => file.raw && !queuedUidSet.value.has(file.uid)) |
| | | if (!readyFiles.length) return |
| | | |
| | | const remainCount = props.limit - currentList.value.length |
| | | if (remainCount <= 0) { |
| | | proxy.$modal.msgError(`æå¤ä¸ä¼ ${props.limit} å¼ å¾ç`) |
| | | uploadRef.value?.clearFiles() |
| | | return |
| | | } |
| | | |
| | | const selectedFiles = readyFiles.slice(0, remainCount) |
| | | if (selectedFiles.length < readyFiles.length) { |
| | | proxy.$modal.msgWarning(`æå¤ä¸ä¼ ${props.limit} å¼ å¾çï¼è¶
åºé¨å已忽ç¥`) |
| | | } |
| | | |
| | | selectedFiles.forEach((file) => queuedUidSet.value.add(file.uid)) |
| | | uploadSelectedFiles(selectedFiles) |
| | | }, 0) |
| | | } |
| | | |
| | | async function uploadSelectedFiles(files) { |
| | | const validFiles = files.filter((file) => validateFile(file.raw)) |
| | | const invalidFiles = files.filter((file) => !validFiles.includes(file)) |
| | | |
| | | invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | |
| | | if (!validFiles.length) { |
| | | uploadRef.value?.clearFiles() |
| | | return |
| | | } |
| | | |
| | | const formData = new FormData() |
| | | validFiles.forEach((file) => { |
| | | formData.append(props.uploadFieldName, file.raw) |
| | | }) |
| | | |
| | | uploading.value = true |
| | | proxy.$modal.loading('å¾çä¸ä¼ ä¸ï¼è¯·ç¨å...') |
| | | |
| | | try { |
| | | const response = await uploadFile(formData) |
| | | const responseList = extractResponseArray(response).map((item, index) => normalizeResponseItem(item, index)) |
| | | |
| | | if (!responseList.length) { |
| | | proxy.$modal.msgError('ä¸ä¼ æ¥å£æªè¿åæ°ç»æ°æ®') |
| | | return |
| | | } |
| | | console.log('responseList', responseList) |
| | | |
| | | currentList.value = [...currentList.value, ...responseList] |
| | | console.log('currentList.value', currentList.value) |
| | | proxy.$modal.msgSuccess('ä¸ä¼ æå') |
| | | } catch (error) { |
| | | proxy.$modal.msgError(error?.message || 'ä¸ä¼ 失败') |
| | | } finally { |
| | | validFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid)) |
| | | uploadRef.value?.clearFiles() |
| | | uploading.value = false |
| | | proxy.$modal.closeLoading() |
| | | } |
| | | } |
| | | |
| | | function handleChange(file, uploadFiles) { |
| | | if (props.disabled || uploading.value) return |
| | | scheduleUpload(uploadFiles) |
| | | } |
| | | |
| | | function handleRemove(file) { |
| | | const targetUrl = file.url || getItemUrl(file.rawData) |
| | | const nextList = currentList.value.filter((item, index) => { |
| | | const itemUrl = getItemUrl(item) |
| | | const itemName = getItemName(item, index) |
| | | return !(itemUrl === targetUrl && itemName === file.name) |
| | | }) |
| | | currentList.value = nextList |
| | | } |
| | | |
| | | function handlePreview(file) { |
| | | previewUrl.value = file.url || getItemUrl(file.rawData) |
| | | previewVisible.value = true |
| | | } |
| | | |
| | | function handleExceed() { |
| | | proxy.$modal.msgError(`æå¤ä¸ä¼ ${props.limit} å¼ å¾ç`) |
| | | } |
| | | |
| | | onBeforeUnmount(() => { |
| | | clearTimeout(uploadQueueTimer.value) |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="attachment-upload-image"> |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :auto-upload="false" |
| | | :multiple="true" |
| | | :show-file-list="true" |
| | | :file-list="displayFileList" |
| | | list-type="picture-card" |
| | | accept="image/*" |
| | | :disabled="disabled || uploading" |
| | | :limit="limit" |
| | | :on-change="handleChange" |
| | | :on-remove="handleRemove" |
| | | :on-preview="handlePreview" |
| | | :on-exceed="handleExceed" |
| | | > |
| | | <div class="upload-trigger"> |
| | | <el-icon> |
| | | <Plus/> |
| | | </el-icon> |
| | | <span>{{ buttonText }}</span> |
| | | </div> |
| | | </el-upload> |
| | | |
| | | <div class="upload-tip"> |
| | | {{ uploadTip }} |
| | | </div> |
| | | |
| | | <el-dialog v-model="previewVisible" title="å¾çé¢è§" width="720px" append-to-body> |
| | | <img class="preview-image" :src="previewUrl" alt="preview"/> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .attachment-upload-image { |
| | | width: 100%; |
| | | } |
| | | |
| | | .upload-trigger { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 6px; |
| | | color: var(--el-text-color-secondary); |
| | | font-size: 12px; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .upload-tip { |
| | | margin-top: 8px; |
| | | color: var(--el-text-color-secondary); |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .preview-image { |
| | | display: block; |
| | | max-width: 100%; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | :deep(.el-upload-list--picture-card) { |
| | | margin: 0; |
| | | } |
| | | |
| | | :deep(.el-upload--picture-card) { |
| | | width: 132px; |
| | | height: 132px; |
| | | } |
| | | |
| | | :deep(.el-upload-list--picture-card .el-upload-list__item) { |
| | | width: 132px; |
| | | height: 132px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="isShow" |
| | | :title="title" |
| | | :width="width" |
| | | @close="handleClose" |
| | | class="attachment-dialog"> |
| | | <!-- å·¥å
·æ --> |
| | | <div v-if="editable" |
| | | class="toolbar"> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="handleUpload"> |
| | | ä¸ä¼ éä»¶ |
| | | </el-button> |
| | | </div> |
| | | <!-- ä¸ä¼ ç»ä»¶å¼¹çª --> |
| | | <el-dialog v-model="uploadDialogVisible" |
| | | title="ä¸ä¼ éä»¶" |
| | | width="50%" |
| | | @close="closeUpload"> |
| | | <AttachmentUpload v-model:file-list="newFileList" /> |
| | | <template #footer> |
| | | <el-button @click="saveUpload">ä¿å</el-button> |
| | | <el-button @click="closeUpload">å
³é</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- æä»¶åè¡¨è¡¨æ ¼ --> |
| | | <div class="table-container"> |
| | | <el-table :data="tableData" |
| | | border |
| | | class="attachment-table" |
| | | :height="tableData.length > 0 ? 'auto' : '120px'"> |
| | | <el-table-column label="éä»¶åç§°" |
| | | prop="originalFilename" |
| | | show-overflow-tooltip /> |
| | | <el-table-column v-if="showActions" |
| | | fixed="right" |
| | | label="æä½" |
| | | :width="150" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | class="download-link" |
| | | @click="previewFile(scope.row.previewURL)"> |
| | | é¢è§ |
| | | </el-button> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | class="download-link" |
| | | @click="downloadFile(scope.row.downloadURL)"> |
| | | ä¸è½½ |
| | | </el-button> |
| | | <el-button v-if="editable" |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | @click="handleDelete(scope.row)"> |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-dialog> |
| | | <filePreview ref="filePreviewRef" /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ElMessage } from 'element-plus' |
| | | import { ref, computed, getCurrentInstance, onMounted, watch } from "vue"; |
| | | import AttachmentUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | import { |
| | | attachmentList, |
| | | deleteAttachment, |
| | | createAttachment, |
| | | } from "@/api/basicData/storageAttachment.js"; |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | const filePreviewRef = ref() |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | | required: true, |
| | | }, |
| | | recordType: { |
| | | type: String, |
| | | default: "", |
| | | required: true, |
| | | }, |
| | | recordId: { |
| | | type: Number, |
| | | default: 0, |
| | | required: true, |
| | | }, |
| | | title: { |
| | | type: String, |
| | | default: "éä»¶", |
| | | }, |
| | | width: { |
| | | type: String, |
| | | default: "50%", |
| | | }, |
| | | showActions: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | editable: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | }); |
| | | |
| | | const emit = defineEmits(["close", "download", "upload", "delete"]); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const uploadDialogVisible = ref(false); |
| | | const newFileList = ref([]); |
| | | |
| | | const isShow = computed({ |
| | | get() { |
| | | return props.visible; |
| | | }, |
| | | set(val) { |
| | | emit("update:visible", val); |
| | | }, |
| | | }); |
| | | |
| | | const handleClose = () => { |
| | | isShow.value = false; |
| | | }; |
| | | |
| | | // é¢è§æä»¶ |
| | | const previewFile = (url) => { |
| | | if (url) { |
| | | filePreviewRef.value.open(url) |
| | | } else { |
| | | ElMessage.warning('æä»¶å°åæ æï¼æ æ³é¢è§') |
| | | } |
| | | } |
| | | |
| | | const handleUpload = () => { |
| | | uploadDialogVisible.value = true; |
| | | }; |
| | | |
| | | const saveUpload = async () => { |
| | | // æ£æ¥æ¯å¦ææ°ä¸ä¼ çæä»¶ |
| | | if (newFileList.value.length > 0) { |
| | | createAttachment({ |
| | | application: "file", |
| | | recordType: props.recordType, |
| | | recordId: props.recordId, |
| | | storageBlobDTOs: [...newFileList.value, ...tableData.value], |
| | | }).then((res) => { |
| | | if (res && res.code === 200) { |
| | | proxy?.$modal?.msgSuccess("ä¸ä¼ æå"); |
| | | newFileList.value = []; |
| | | // å·æ°å表 |
| | | setList(); |
| | | } |
| | | }).finally(() => { |
| | | uploadDialogVisible.value = false; |
| | | }) |
| | | } |
| | | } |
| | | |
| | | const closeUpload = () => { |
| | | newFileList.value = []; |
| | | uploadDialogVisible.value = false; |
| | | }; |
| | | |
| | | const handleDelete = async (row, index) => { |
| | | deleteAttachment([row.storageAttachmentId]).then((res) => { |
| | | if (res && res.code === 200) { |
| | | proxy?.$modal?.msgSuccess("å 餿å"); |
| | | setList(); |
| | | } |
| | | }) |
| | | }; |
| | | |
| | | const setList = () => { |
| | | attachmentList({ |
| | | recordType: props.recordType, |
| | | recordId: props.recordId, |
| | | }).then(res => { |
| | | tableData.value = (res && res.data) || []; |
| | | }); |
| | | }; |
| | | |
| | | const downloadFile = url => { |
| | | window.open(url, "_blank"); |
| | | }; |
| | | onMounted(() => { |
| | | setList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .attachment-dialog { |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | .toolbar { |
| | | margin-bottom: 16px; |
| | | text-align: right; |
| | | } |
| | | |
| | | .table-container { |
| | | max-height: 40vh; |
| | | overflow-y: auto; |
| | | min-height: 120px; |
| | | padding-bottom: 16px; |
| | | box-sizing: border-box; |
| | | will-change: scroll-position; |
| | | transform: translateZ(0); |
| | | -webkit-overflow-scrolling: touch; |
| | | } |
| | | |
| | | :deep(.el-table) { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | :deep(.el-table__body-wrapper) { |
| | | overflow-y: auto; |
| | | will-change: transform; |
| | | transform: translateZ(0); |
| | | } |
| | | |
| | | :deep(.el-table__body tr) { |
| | | transition: none; |
| | | } |
| | | |
| | | :deep(.el-dialog__footer) { |
| | | padding-top: 12px; |
| | | border-top: 1px solid #e9ecef; |
| | | } |
| | | |
| | | .attachment-table { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | :deep(.el-dialog__header) { |
| | | background-color: #f8f9fa; |
| | | border-bottom: 1px solid #e9ecef; |
| | | padding: 16px 20px; |
| | | } |
| | | |
| | | :deep(.el-dialog__title) { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | :deep(.el-dialog__body) { |
| | | padding: 16px 20px; |
| | | } |
| | | |
| | | :deep(.el-table__empty-text) { |
| | | color: #999; |
| | | } |
| | | </style> |
| | |
| | | @pagination="paginationSearch" |
| | | @change="handleChange" /> |
| | | </el-dialog> |
| | | <!-- // todo éä»¶é¢è§ç¸å
³ --> |
| | | <filePreview v-if="showPreview" |
| | | ref="filePreviewRef" /> |
| | | </template> |
| | |
| | | :before-upload="handleBeforeUpload" |
| | | :on-success="handleUploadSuccess" |
| | | :on-error="handleUploadError" |
| | | name="file" |
| | | name="files" |
| | | :show-file-list="false" |
| | | :headers="headers" |
| | | class="editor-img-uploader" |
| | | v-if="type == 'url'" |
| | | v-if="type === 'url'" |
| | | > |
| | | <i ref="uploadRef" class="editor-img-uploader"></i> |
| | | </el-upload> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import axios from "axios"; |
| | | import { QuillEditor } from "@vueup/vue-quill"; |
| | | import "@vueup/vue-quill/dist/vue-quill.snow.css"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import {uploadPublicFile} from "@/api/basicData/common.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const quillEditorRef = ref(); |
| | | const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // ä¸ä¼ çå¾çæå¡å¨å°å |
| | | const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/public/upload"); // ä¸ä¼ çå¾çæå¡å¨å°å |
| | | const headers = ref({ |
| | | Authorization: "Bearer " + getToken(), |
| | | }); |
| | |
| | | function handleUploadSuccess(res, file) { |
| | | // 妿ä¸ä¼ æå |
| | | if (res.code == 200) { |
| | | const imageUrl = resolveImageUrl(res); |
| | | if (!imageUrl) { |
| | | proxy.$modal.msgError("æªè·åå°å¾çå°å"); |
| | | return; |
| | | } |
| | | // è·å坿æ¬å®ä¾ |
| | | let quill = toRaw(quillEditorRef.value).getQuill(); |
| | | // è·åå
æ ä½ç½® |
| | | let length = quill.selection.savedRange.index; |
| | | const selection = quill.getSelection(true); |
| | | const length = selection ? selection.index : quill.getLength(); |
| | | // æå
¥å¾çï¼res.url为æå¡å¨è¿åçå¾ç龿¥å°å |
| | | quill.insertEmbed( |
| | | length, |
| | | "image", |
| | | import.meta.env.VITE_APP_BASE_API + res.fileName |
| | | ); |
| | | quill.insertEmbed(length, "image", imageUrl); |
| | | // è°æ´å
æ å°æå |
| | | quill.setSelection(length + 1); |
| | | } else { |
| | | proxy.$modal.msgError("å¾çæå
¥å¤±è´¥"); |
| | | } |
| | | } |
| | | |
| | | function resolveImageUrl(res) { |
| | | if (!res) return ""; |
| | | // å
¼å®¹æ°æ¥å£: data[0].previewURL |
| | | const previewURL = res?.data?.[0]?.previewURL; |
| | | if (previewURL) { |
| | | return previewURL; |
| | | } |
| | | // å
¼å®¹æ§æ¥å£ |
| | | if (res.url) { |
| | | return res.url; |
| | | } |
| | | if (res.fileName) { |
| | | return `${import.meta.env.VITE_APP_BASE_API}${res.fileName}`; |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | // ä¸ä¼ 失败å¤ç |
| | |
| | | |
| | | function insertImage(file) { |
| | | const formData = new FormData(); |
| | | formData.append("file", file); |
| | | axios |
| | | .post(uploadUrl.value, formData, { |
| | | headers: { |
| | | "Content-Type": "multipart/form-data", |
| | | Authorization: headers.value.Authorization, |
| | | }, |
| | | formData.append("files", file); |
| | | uploadPublicFile(formData).then((res) => { |
| | | handleUploadSuccess(res) |
| | | }) |
| | | .then((res) => { |
| | | handleUploadSuccess(res.data); |
| | | }); |
| | | } |
| | | </script> |
| | | |
| | |
| | | <template> |
| | | <el-table |
| | | ref="multipleTable" |
| | | <el-table ref="multipleTable" |
| | | v-loading="tableLoading" |
| | | :border="border" |
| | | :data="tableData" |
| | |
| | | @current-change="currentChange" |
| | | @selection-change="handleSelectionChange" |
| | | @expand-change="expandChange" |
| | | class="lims-table" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | class="lims-table"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | :selectable="selectable" |
| | | width="55" |
| | | v-if="isSelection" |
| | | /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | |
| | | <el-table-column |
| | | v-for="(item, index) in column" |
| | | v-if="isSelection" /> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column v-for="(item, index) in column" |
| | | :key="index" |
| | | :column-key="item.columnKey" |
| | | :filter-method="item.filterHandler" |
| | |
| | | :sortable="!!item.sortable" |
| | | :type="item.type" |
| | | :width="item.width" |
| | | :minWidth="item.minWidth" |
| | | > |
| | | :minWidth="item.minWidth"> |
| | | <template #header="scope"> |
| | | <div class="pim-table-header-cell" :class="{ 'has-extra': item.headerSlot }"> |
| | | <div class="pim-table-header-cell" |
| | | :class="{ 'has-extra': item.headerSlot }"> |
| | | <div class="pim-table-header-title"> |
| | | {{ item.label }} |
| | | </div> |
| | | <div v-if="item.headerSlot" class="pim-table-header-extra"> |
| | | <slot :name="item.headerSlot" :column="scope.column" /> |
| | | <div v-if="item.headerSlot" |
| | | class="pim-table-header-extra"> |
| | | <slot :name="item.headerSlot" |
| | | :column="scope.column" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <template |
| | | v-if="item.hasOwnProperty('colunmTemplate')" |
| | | #[item.colunmTemplate]="scope" |
| | | > |
| | | <slot |
| | | v-if="item.theadSlot" |
| | | <template v-if="item.hasOwnProperty('colunmTemplate')" |
| | | #[item.colunmTemplate]="scope"> |
| | | <slot v-if="item.theadSlot" |
| | | :name="item.theadSlot" |
| | | :index="scope.$index" |
| | | :row="scope.row" |
| | | /> |
| | | :row="scope.row" /> |
| | | </template> |
| | | |
| | | <template #default="scope"> |
| | | <!-- ææ§½ --> |
| | | <div v-if="item.dataType == 'slot'"> |
| | | <slot |
| | | v-if="item.slot" |
| | | <slot v-if="item.slot" |
| | | :index="scope.$index" |
| | | :name="item.slot" |
| | | :row="scope.row" |
| | | /> |
| | | :row="scope.row" /> |
| | | </div> |
| | | <!-- è¿åº¦æ¡ --> |
| | | <div v-else-if="item.dataType == 'progress'"> |
| | |
| | | </div> |
| | | <!-- å¾ç --> |
| | | <div v-else-if="item.dataType == 'image'"> |
| | | <img |
| | | :src="javaApi + '/img/' + scope.row[item.prop]" |
| | | <img :src="javaApi + '/img/' + scope.row[item.prop]" |
| | | alt="" |
| | | style="width: 40px; height: 40px; margin-top: 10px" |
| | | /> |
| | | style="width: 40px; height: 40px; margin-top: 10px" /> |
| | | </div> |
| | | |
| | | <!-- tag --> |
| | | <div v-else-if="item.dataType == 'tag'"> |
| | | <el-tag |
| | | v-if=" |
| | | <el-tag v-if=" |
| | | typeof dataTypeFn(scope.row[item.prop], item.formatData) === |
| | | 'string' |
| | | " |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :type="formatType(scope.row[item.prop], item.formatType)" |
| | | > |
| | | :type="formatType(scope.row[item.prop], item.formatType)"> |
| | | {{ formatters(scope.row[item.prop], item.formatData) }} |
| | | </el-tag> |
| | | |
| | | <el-tag |
| | | v-for="(tag, index) in dataTypeFn( |
| | | <el-tag v-for="(tag, index) in dataTypeFn( |
| | | scope.row[item.prop], |
| | | item.formatData |
| | | )" |
| | |
| | | " |
| | | :key="index" |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :type="formatType(tag, item.formatType)" |
| | | > |
| | | :type="formatType(tag, item.formatType)"> |
| | | {{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }} |
| | | </el-tag> |
| | | |
| | | <el-tag |
| | | v-else |
| | | <el-tag v-else |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :type="formatType(scope.row[item.prop], item.formatType)" |
| | | > |
| | | :type="formatType(scope.row[item.prop], item.formatType)"> |
| | | {{ formatters(scope.row[item.prop], item.formatData) }} |
| | | </el-tag> |
| | | </div> |
| | | |
| | | <!-- æé® --> |
| | | <div v-else-if="item.dataType == 'action'" @click.stop> |
| | | <template v-for="(o, key) in item.operation" :key="key"> |
| | | <el-button |
| | | v-show="o.type != 'upload'" |
| | | <div v-else-if="item.dataType == 'action'" |
| | | @click.stop> |
| | | <template v-for="(o, key) in item.operation" |
| | | :key="key"> |
| | | <el-button v-show="o.type != 'upload'" |
| | | v-if="o.showHide ? o.showHide(scope.row) : true" |
| | | :disabled="isOperationDisabled(o, scope.row)" |
| | | :plain="o.plain" |
| | |
| | | }" |
| | | link |
| | | @click.stop="o.clickFun(scope.row)" |
| | | :key="key" |
| | | > |
| | | :key="key"> |
| | | {{ o.name }} |
| | | </el-button> |
| | | <el-upload |
| | | :action=" |
| | | <el-upload :action=" |
| | | javaApi + |
| | | o.url + |
| | | '?id=' + |
| | |
| | | handleSuccessUp(response, file, fileList, scope.$index) |
| | | " |
| | | :on-exceed="onExceed" |
| | | :show-file-list="false" |
| | | > |
| | | <el-button |
| | | link |
| | | :show-file-list="false"> |
| | | <el-button link |
| | | type="primary" |
| | | :disabled="isOperationDisabled(o, scope.row)" |
| | | :style="{ |
| | | color: getOperationColor(o, scope.row), |
| | | }" |
| | | >{{ o.name }}</el-button |
| | | > |
| | | }">{{ o.name }}</el-button> |
| | | </el-upload> |
| | | </template> |
| | | </div> |
| | | <!-- å¯ç¹å»çæå --> |
| | | <div |
| | | v-else-if="item.dataType == 'link'" |
| | | <div v-else-if="item.dataType == 'link'" |
| | | class="cell link" |
| | | style="width: 100%" |
| | | @click="goLink(scope.row, item.linkMethod)" |
| | | > |
| | | @click="goLink(scope.row, item.linkMethod)"> |
| | | <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span> |
| | | </div> |
| | | <!-- é»è®¤çº¯å±ç¤ºæ°æ® --> |
| | | <div v-else class="cell" style="width: 100%"> |
| | | <div v-else |
| | | class="cell" |
| | | style="width: 100%"> |
| | | <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span> |
| | | <span v-else>{{ |
| | | formatters(scope.row[item.prop], item.formatData) |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination |
| | | v-if="isShowPagination" |
| | | <pagination v-if="isShowPagination" |
| | | :total="page.total" |
| | | :layout="page.layout" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationSearch" |
| | | /> |
| | | @pagination="paginationSearch" /> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | const uploadHeader = proxy.uploadHeader; |
| | | const javaApi = proxy.javaApi; |
| | | |
| | | const emit = defineEmits(["pagination", "expand-change", "selection-change", "row-click"]); |
| | | const emit = defineEmits([ |
| | | "pagination", |
| | | "expand-change", |
| | | "selection-change", |
| | | "row-click", |
| | | ]); |
| | | |
| | | // Filters |
| | | const typeFn = (val, row) => { |
| | |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | selectable: { |
| | | type: Function, |
| | | default: () => true, |
| | | }, |
| | | isShowPagination: { |
| | | type: Boolean, |
| | | default: true, |
| | |
| | | }, |
| | | rowKey: { |
| | | type: String, |
| | | default: 'id', |
| | | default: "id", |
| | | }, |
| | | page: { |
| | | type: Object, |
| | |
| | | const currentFiles = ref({}); |
| | | const uploadKeys = ref({}); |
| | | |
| | | const indexMethod = (index) => { |
| | | const indexMethod = index => { |
| | | return (props.page.current - 1) * props.page.size + index + 1; |
| | | }; |
| | | |
| | |
| | | }; |
| | | |
| | | // è·åç¶ç»ä»¶æ¹æ³ï¼ç¤ºä¾å®ç°ï¼ |
| | | const getParentMethod = (methodName) => { |
| | | const getParentMethod = methodName => { |
| | | const parentMethods = inject("parentMethods", {}); |
| | | return parentMethods[methodName]; |
| | | }; |
| | |
| | | : !!operation.disabled; |
| | | }; |
| | | |
| | | const parseHexToRgb = (hex) => { |
| | | const normalized = String(hex || "").trim().replace("#", ""); |
| | | const parseHexToRgb = hex => { |
| | | const normalized = String(hex || "") |
| | | .trim() |
| | | .replace("#", ""); |
| | | if (normalized.length === 3) { |
| | | const r = parseInt(normalized[0] + normalized[0], 16); |
| | | const g = parseInt(normalized[1] + normalized[1], 16); |
| | | const b = parseInt(normalized[2] + normalized[2], 16); |
| | | if ([r, g, b].some((n) => Number.isNaN(n))) return null; |
| | | if ([r, g, b].some(n => Number.isNaN(n))) return null; |
| | | return { r, g, b }; |
| | | } |
| | | if (normalized.length === 6 || normalized.length === 8) { |
| | | const r = parseInt(normalized.slice(0, 2), 16); |
| | | const g = parseInt(normalized.slice(2, 4), 16); |
| | | const b = parseInt(normalized.slice(4, 6), 16); |
| | | if ([r, g, b].some((n) => Number.isNaN(n))) return null; |
| | | if ([r, g, b].some(n => Number.isNaN(n))) return null; |
| | | return { r, g, b }; |
| | | } |
| | | return null; |
| | |
| | | if (!rgb) return c; |
| | | return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`; |
| | | } |
| | | const rgbMatch = c.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*[\d.]+\s*)?\)$/i); |
| | | const rgbMatch = c.match( |
| | | /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*[\d.]+\s*)?\)$/i |
| | | ); |
| | | if (rgbMatch) { |
| | | const r = Number(rgbMatch[1]); |
| | | const g = Number(rgbMatch[2]); |
| | | const b = Number(rgbMatch[3]); |
| | | if ([r, g, b].some((n) => Number.isNaN(n))) return c; |
| | | if ([r, g, b].some(n => Number.isNaN(n))) return c; |
| | | return `rgba(${r}, ${g}, ${b}, ${alpha})`; |
| | | } |
| | | if (c.includes("--el-color-primary")) { |
| | |
| | | } |
| | | }; |
| | | |
| | | const resetUploadComponent = (index) => { |
| | | const resetUploadComponent = index => { |
| | | uploadKeys[index] = Date.now(); |
| | | }; |
| | | |
| | |
| | | emit("pagination", { page: page, limit: limit }); |
| | | }; |
| | | |
| | | const rowClick = (row) => { |
| | | const rowClick = row => { |
| | | emit("row-click", row); |
| | | }; |
| | | |
| | |
| | | emit("expand-change", row, expandedRows); |
| | | }; |
| | | |
| | | const handleSelectionChange = (newSelection) => { |
| | | const handleSelectionChange = newSelection => { |
| | | emit("selection-change", newSelection); |
| | | }; |
| | | </script> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="visible" |
| | | :title="title" |
| | | width="800px" |
| | | destroy-on-close> |
| | | <div class="param-list-container"> |
| | | <div class="params-header"> |
| | | <span>åæ°å表</span> |
| | | <div> |
| | | <el-button v-if="editable" |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="handleAddParam"> |
| | | <el-icon> |
| | | <Plus /> |
| | | </el-icon>æ°å¢ |
| | | </el-button> |
| | | <!-- <el-button v-if="editable" |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="getsyncProcessParamItem"> |
| | | <el-icon> |
| | | <Refresh /> |
| | | </el-icon>忥工åºåæ° |
| | | </el-button> --> |
| | | </div> |
| | | </div> |
| | | <div class="params-list"> |
| | | <div v-for="param in paramList" |
| | | :key="param.id" |
| | | class="param-item"> |
| | | <div class="param-info"> |
| | | <span class="param-code">{{ param.paramName }}</span> |
| | | <span class="param-value"> |
| | | æ åå¼ï¼{{ param.standardValue || "-" }} {{ param.unit }} |
| | | </span> |
| | | </div> |
| | | <div class="param-actions"> |
| | | <el-button v-if="editable" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleEditParam(param)"> |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button v-if="editable" |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | @click="handleDeleteParam(param)"> |
| | | å é¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <el-empty v-if="!paramList || paramList.length === 0" |
| | | description="ææ åæ°" |
| | | :image-size="50" /> |
| | | </div> |
| | | </div> |
| | | <!-- éæ©åæ°å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="selectParamDialogVisible" |
| | | title="鿩忰" |
| | | width="1000px"> |
| | | <div class="param-select-container"> |
| | | <!-- 左侧忰å表 --> |
| | | <div class="param-list-area"> |
| | | <div class="area-title">å¯éåæ°</div> |
| | | <div class="search-box"> |
| | | <el-input v-model="paramSearchKeyword" |
| | | placeholder="请è¾å
¥åæ°åç§°æç´¢" |
| | | clearable |
| | | size="small" |
| | | @input="getBaseParamListData"> |
| | | <template #prefix> |
| | | <el-icon> |
| | | <Search /> |
| | | </el-icon> |
| | | </template> |
| | | </el-input> |
| | | </div> |
| | | <el-table :data="filteredParamList" |
| | | height="400" |
| | | border |
| | | highlight-current-row |
| | | @current-change="handleSelectParam"> |
| | | <el-table-column prop="paramName" |
| | | label="åæ°åç§°" /> |
| | | <el-table-column prop="paramType" |
| | | label="åæ°ç±»å"> |
| | | <template #default="scope"> |
| | | <el-tag size="small" |
| | | :type="getParamTypeTag(scope.row.paramType)">{{ getParamTypeText(scope.row.paramType) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <!-- å页æ§ä»¶ --> |
| | | <div class="pagination-container" |
| | | style="margin-top: 10px;"> |
| | | <el-pagination :current-page="paramPage.current" |
| | | :page-size="paramPage.size" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :total="paramPage.total" |
| | | @size-change="getBaseParamListData" |
| | | @current-change="getBaseParamListData" |
| | | size="small" /> |
| | | </div> |
| | | </div> |
| | | <!-- å³ä¾§åæ°è¯¦æ
--> |
| | | <div class="param-detail-area"> |
| | | <div class="area-title">åæ°è¯¦æ
</div> |
| | | <el-form v-if="selectedParam" |
| | | :model="selectedParam" |
| | | label-width="100px" |
| | | class="param-detail-form"> |
| | | <el-form-item label="åæ°åç§°"> |
| | | <span class="detail-text">{{ selectedParam.paramName }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="åæ°ç±»å"> |
| | | <el-tag size="small" |
| | | :type="getParamTypeTag(selectedParam.paramType)">{{ getParamTypeText(selectedParam.paramType) }}</el-tag> |
| | | </el-form-item> |
| | | <el-form-item label="åæ°æ ¼å¼"> |
| | | <span class="detail-text">{{ selectedParam.paramFormat || '-' }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="åä½"> |
| | | <span class="detail-text">{{ selectedParam.unit || '-' }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æ åå¼"> |
| | | <el-input v-model="selectedParam.standardValue" |
| | | @input="val => onStandardValueInput(val, selectedParam)" |
| | | placeholder="请è¾å
¥é»è®¤å¼" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯å¦å¿
å¡«"> |
| | | <el-switch :active-value="1" |
| | | :inactive-value="0" |
| | | v-model="selectedParam.isRequired" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <el-empty v-else |
| | | description="请ä»å·¦ä¾§éæ©åæ°" |
| | | :image-size="100" /> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <el-button type="primary" |
| | | @click="handleParamSelectSubmit">ç¡®å®</el-button> |
| | | <el-button @click="selectParamDialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- ç¼è¾åæ°å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="editParamDialogVisible" |
| | | title="ç¼è¾åæ°" |
| | | width="600px"> |
| | | <el-form :model="editParamForm" |
| | | :rules="editParamRules" |
| | | ref="editParamFormRef" |
| | | label-width="120px"> |
| | | <el-form-item label="åæ°åç§°"> |
| | | <span class="detail-text">{{ editParamForm.paramName }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="åæ°ç±»å"> |
| | | <el-tag size="small" |
| | | :type="getParamTypeTag(editParamForm.paramType)"> |
| | | {{ getParamTypeText(editParamForm.paramType) }} |
| | | </el-tag> |
| | | </el-form-item> |
| | | <el-form-item label="åæ°æ ¼å¼"> |
| | | <span class="detail-text">{{ editParamForm.paramFormat || '-' }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="åä½"> |
| | | <span class="detail-text">{{ editParamForm.unit || '-' }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æ åå¼" |
| | | prop="standardValue"> |
| | | <el-input v-model="editParamForm.standardValue" |
| | | @input="val => onStandardValueInput(val, editParamForm)" |
| | | placeholder="请è¾å
¥æ åå¼" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" |
| | | @click="handleEditParamSubmit">ç¡®å®</el-button> |
| | | <el-button @click="editParamDialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, watch } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { Plus, Search } from "@element-plus/icons-vue"; |
| | | import { |
| | | delProcessRouteItemParam, |
| | | editProcessRouteItemParam, |
| | | addProcessRouteItemParam, |
| | | } from "@/api/productionManagement/processRouteItem.js"; |
| | | import { |
| | | addProcessRouteItemParamOrder, |
| | | delProcessRouteItemParamOrder, |
| | | editProcessRouteItemParamOrder, |
| | | } from "@/api/productionManagement/productProcessRoute.js"; |
| | | |
| | | import { getBaseParamList } from "@/api/basicData/parameterMaintenance.js"; |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | title: { |
| | | type: String, |
| | | default: "åæ°å表", |
| | | }, |
| | | routeId: { |
| | | type: Number, |
| | | default: 0, |
| | | }, |
| | | process: { |
| | | type: Object, |
| | | default: () => ({}), |
| | | }, |
| | | paramList: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | editable: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | orderId: { |
| | | type: Number, |
| | | default: 0, |
| | | }, |
| | | pageType: { |
| | | type: String, |
| | | default: "route", |
| | | }, |
| | | }); |
| | | |
| | | const emit = defineEmits(["update:modelValue", "refresh"]); |
| | | |
| | | const visible = computed({ |
| | | get: () => props.modelValue, |
| | | set: value => emit("update:modelValue", value), |
| | | }); |
| | | |
| | | // ååºå¼æ°æ® |
| | | const selectParamDialogVisible = ref(false); |
| | | const editParamDialogVisible = ref(false); |
| | | const paramSearchKeyword = ref(""); |
| | | const selectedParam = ref(null); |
| | | const filteredParamList = ref([]); |
| | | const paramPage = ref({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const editParamForm = ref({ |
| | | id: null, |
| | | processId: null, |
| | | paramId: null, |
| | | paramName: "", |
| | | standardValue: null, |
| | | isRequired: 0, |
| | | paramType: null, |
| | | paramFormat: "", |
| | | unit: "", |
| | | }); |
| | | |
| | | const onStandardValueInput = (val, target) => { |
| | | const data = target.value || target; |
| | | const type = data.paramType || data.parameterType; |
| | | if (type === 1) { |
| | | // æ°å¼æ ¼å¼ï¼ä¸è½è¾å
¥ä¸ææè±æå符 |
| | | data.standardValue = val.replace(/[a-zA-Z\u4e00-\u9fa5]/g, ""); |
| | | } |
| | | }; |
| | | |
| | | const editParamRules = ref({ |
| | | standardValue: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | const type = |
| | | editParamForm.value.paramType || editParamForm.value.parameterType; |
| | | if (type === 1 && value) { |
| | | if (/[a-zA-Z\u4e00-\u9fa5]/.test(value)) { |
| | | return callback(new Error("æ°å¼æ ¼å¼ä¸è½å
å«ä¸è±æå符")); |
| | | } |
| | | } |
| | | callback(); |
| | | }, |
| | | trigger: "blur", |
| | | }, |
| | | ], |
| | | }); |
| | | const editParamFormRef = ref(null); |
| | | |
| | | // æ°å¢åæ° |
| | | const handleAddParam = () => { |
| | | selectedParam.value = null; |
| | | paramSearchKeyword.value = ""; |
| | | paramPage.value.current = 1; |
| | | // è·åå¯éåæ°å表 |
| | | getBaseParamListData(); |
| | | selectParamDialogVisible.value = true; |
| | | }; |
| | | |
| | | // ç¼è¾åæ° |
| | | const handleEditParam = param => { |
| | | editParamForm.value = { |
| | | id: param.id, |
| | | processId: props.process.id, |
| | | paramId: param.paramId, |
| | | paramName: param.parameterName || param.paramName, |
| | | standardValue: param.standardValue, |
| | | isRequired: param.isRequired || 0, |
| | | paramType: param.parameterType || param.paramType, |
| | | paramFormat: param.parameterFormat || param.paramFormat, |
| | | unit: param.unit || param.unit, |
| | | }; |
| | | editParamDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å é¤åæ° |
| | | const handleDeleteParam = param => { |
| | | ElMessageBox.confirm("ç¡®å®è¦å é¤è¯¥åæ°åï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // è°ç¨APIå é¤åæ° |
| | | if (props.pageType === "order") { |
| | | delProcessRouteItemParamOrder(param.id) |
| | | .then(res => { |
| | | ElMessage.success("å 餿å"); |
| | | emit("refresh"); |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error("å é¤åæ°å¤±è´¥"); |
| | | console.error("å é¤åæ°å¤±è´¥ï¼", err); |
| | | }); |
| | | } else { |
| | | delProcessRouteItemParam(param.id) |
| | | .then(res => { |
| | | ElMessage.success("å 餿å"); |
| | | emit("refresh"); |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error("å é¤åæ°å¤±è´¥"); |
| | | console.error("å é¤åæ°å¤±è´¥ï¼", err); |
| | | }); |
| | | } |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | const getsyncProcessParamItem = () => { |
| | | emit("getsyncProcessParamItem"); |
| | | }; |
| | | |
| | | // è·åå¯éåæ°å表 |
| | | const getBaseParamListData = () => { |
| | | console.log(paramPage, "paramPage.size"); |
| | | |
| | | getBaseParamList({ |
| | | paramName: paramSearchKeyword.value, |
| | | current: paramPage.value.current, |
| | | size: paramPage.value.size, |
| | | }).then(res => { |
| | | if (res.code === 200) { |
| | | filteredParamList.value = res.data?.records || []; |
| | | paramPage.value.total = res.data.total || 0; |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // 鿩忰 |
| | | const handleSelectParam = param => { |
| | | selectedParam.value = param; |
| | | }; |
| | | |
| | | // æäº¤éæ©åæ° |
| | | const handleParamSelectSubmit = () => { |
| | | if (!selectedParam.value) { |
| | | ElMessage.warning("请å
éæ©ä¸ä¸ªåæ°"); |
| | | return; |
| | | } |
| | | |
| | | if (!props.process || !props.process.id) { |
| | | ElMessage.error("å·¥èºè·¯çº¿é¡¹ç®ä¿¡æ¯ä¸å®æ´"); |
| | | return; |
| | | } |
| | | |
| | | // è°ç¨APIæ°å¢åæ° |
| | | if (props.pageType === "order") { |
| | | addProcessRouteItemParamOrder({ |
| | | productionOrderId: Number(props.orderId), |
| | | productionOrderRoutingOperationId: props.process.id, |
| | | technologyRoutingOperationParamId: props.process.id, |
| | | paramId: selectedParam.value.id, |
| | | standardValue: selectedParam.value.standardValue || "", |
| | | isRequired: selectedParam.value.isRequired || 0, |
| | | }) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("æ·»å åæ°æå"); |
| | | selectParamDialogVisible.value = false; |
| | | emit("refresh"); |
| | | } else { |
| | | ElMessage.error(res.msg || "æ·»å åæ°å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error("æ·»å åæ°å¤±è´¥"); |
| | | console.error("æ·»å åæ°å¤±è´¥ï¼", err); |
| | | }); |
| | | } else { |
| | | console.log(selectedParam.value, "selectedParam"); |
| | | |
| | | addProcessRouteItemParam({ |
| | | technologyRoutingOperationId: props.process.id, |
| | | paramId: selectedParam.value.id, |
| | | standardValue: selectedParam.value.standardValue || "", |
| | | isRequired: selectedParam.value.isRequired || 0, |
| | | }) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("æ·»å åæ°æå"); |
| | | selectParamDialogVisible.value = false; |
| | | emit("refresh"); |
| | | } else { |
| | | ElMessage.error(res.msg || "æ·»å åæ°å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error("æ·»å åæ°å¤±è´¥"); |
| | | console.error("æ·»å åæ°å¤±è´¥ï¼", err); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // æäº¤ç¼è¾åæ° |
| | | const handleEditParamSubmit = () => { |
| | | if (!editParamFormRef.value) return; |
| | | editParamFormRef.value.validate(valid => { |
| | | if (valid) { |
| | | if (props.pageType === "order") { |
| | | editProcessRouteItemParamOrder({ |
| | | id: editParamForm.value.id, |
| | | standardValue: editParamForm.value.standardValue || "", |
| | | isRequired: editParamForm.value.isRequired || 0, |
| | | // productionOrderRoutingOperationId: props.process.id, |
| | | }) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("ç¼è¾æå"); |
| | | editParamDialogVisible.value = false; |
| | | emit("refresh"); |
| | | } else { |
| | | ElMessage.error(res.msg || "ç¼è¾å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error("ç¼è¾åæ°å¤±è´¥"); |
| | | console.error("ç¼è¾åæ°å¤±è´¥ï¼", err); |
| | | }); |
| | | } else { |
| | | // è°ç¨APIä¿®æ¹åæ° |
| | | editProcessRouteItemParam({ |
| | | id: editParamForm.value.id, |
| | | technologyRoutingOperationId: props.process.id, |
| | | paramId: editParamForm.value.paramId, |
| | | standardValue: editParamForm.value.standardValue || "", |
| | | isRequired: editParamForm.value.isRequired || 0, |
| | | }) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("ç¼è¾æå"); |
| | | editParamDialogVisible.value = false; |
| | | emit("refresh"); |
| | | } else { |
| | | ElMessage.error(res.msg || "ç¼è¾å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error("ç¼è¾åæ°å¤±è´¥"); |
| | | console.error("ç¼è¾åæ°å¤±è´¥ï¼", err); |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // è·ååæ°ç±»åæ ç¾ |
| | | const getParamTypeTag = type => { |
| | | const typeMap = { |
| | | 1: "primary", |
| | | 2: "info", |
| | | 3: "warning", |
| | | 4: "success", |
| | | }; |
| | | return typeMap[type] || "default"; |
| | | }; |
| | | |
| | | // è·ååæ°ç±»åææ¬ |
| | | const getParamTypeText = type => { |
| | | const typeMap = { |
| | | 1: "æ°å¼æ ¼å¼", |
| | | 2: "ææ¬æ ¼å¼", |
| | | 3: "䏿é项", |
| | | 4: "æ¶é´æ ¼å¼", |
| | | }; |
| | | return typeMap[type] || type; |
| | | }; |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | newVal => { |
| | | if (!newVal) { |
| | | // å¼¹çªå
³éæ¶éç½®æ°æ® |
| | | selectParamDialogVisible.value = false; |
| | | editParamDialogVisible.value = false; |
| | | selectedParam.value = null; |
| | | paramSearchKeyword.value = ""; |
| | | paramPage.value.current = 1; |
| | | filteredParamList.value = []; |
| | | editParamForm.value = { |
| | | id: null, |
| | | processId: null, |
| | | paramId: null, |
| | | paramName: "", |
| | | standardValue: null, |
| | | isRequired: 0, |
| | | paramType: null, |
| | | paramFormat: "", |
| | | unit: "", |
| | | }; |
| | | } |
| | | } |
| | | ); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .param-list-container { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .params-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | padding-bottom: 10px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | } |
| | | |
| | | .params-header span { |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | } |
| | | |
| | | .params-list { |
| | | max-height: 400px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .param-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 12px 16px; |
| | | margin-bottom: 8px; |
| | | background-color: #f9f9f9; |
| | | border-radius: 4px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .param-item:hover { |
| | | background-color: #ecf5ff; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .param-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 20px; |
| | | flex: 1; |
| | | } |
| | | |
| | | .param-code { |
| | | font-weight: 500; |
| | | color: #303133; |
| | | min-width: 120px; |
| | | } |
| | | |
| | | .param-value { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .param-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | /* æ»å¨æ¡æ ·å¼ */ |
| | | .params-list::-webkit-scrollbar { |
| | | width: 6px; |
| | | } |
| | | |
| | | .params-list::-webkit-scrollbar-track { |
| | | background: #f1f1f1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .params-list::-webkit-scrollbar-thumb { |
| | | background: #c1c1c1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .params-list::-webkit-scrollbar-thumb:hover { |
| | | background: #a8a8a8; |
| | | } |
| | | |
| | | /* éæ©åæ°å¯¹è¯æ¡æ ·å¼ */ |
| | | .param-select-container { |
| | | display: flex; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .param-list-area { |
| | | flex: 1; |
| | | min-width: 400px; |
| | | } |
| | | |
| | | .param-detail-area { |
| | | flex: 1; |
| | | min-width: 300px; |
| | | } |
| | | |
| | | .area-title { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | margin-bottom: 10px; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-box { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .param-detail-form { |
| | | background: #f9f9f9; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .detail-text { |
| | | font-weight: 500; |
| | | } |
| | | </style> |
| | |
| | | </el-row> |
| | | |
| | | <el-form-item label="éä»¶" prop="attachmentIds"> |
| | | <el-upload |
| | | v-model:file-list="fileList" |
| | | :action="upload.url" |
| | | :headers="upload.headers" |
| | | multiple |
| | | name="files" |
| | | :on-success="handleUploadSuccess" |
| | | :on-error="handleUploadError" |
| | | :on-remove="handleRemove" |
| | | > |
| | | <el-button type="primary">ä¸ä¼ æä»¶</el-button> |
| | | </el-upload> |
| | | <FileUpload v-model:file-list="form.storageBlobDTOs" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="visible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submit">ç¡®å®</el-button> |
| | | <el-button @click="visible = false">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | import { computed, reactive, ref, watch } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { getToken } from '@/utils/auth' |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { type: Boolean, default: false }, |
| | |
| | | }) |
| | | |
| | | const formRef = ref() |
| | | const fileList = ref([]) |
| | | const upload = reactive({ |
| | | url: import.meta.env.VITE_APP_BASE_API + '/basic/customer-follow/upload', |
| | | headers: { Authorization: 'Bearer ' + getToken() } |
| | | }) |
| | | |
| | | const form = ref({ |
| | | planNodeId: undefined, |
| | |
| | | managerName: '', |
| | | departmentName: '', |
| | | remark: '', |
| | | attachmentIds: [] |
| | | storageBlobDTOs: [] |
| | | }) |
| | | |
| | | const rules = { |
| | |
| | | managerName: info.managerName || '', |
| | | departmentName: info.departmentName || '', |
| | | remark: '', |
| | | attachmentIds: [] |
| | | storageBlobDTOs: [] |
| | | } |
| | | fileList.value = [] |
| | | } |
| | | |
| | | watch( |
| | |
| | | form.value.completionProgress = 100 |
| | | form.value.totalProgress = 100 |
| | | if (!form.value.actualEndTime) form.value.actualEndTime = form.value.reportDate || '' |
| | | } |
| | | |
| | | function handleUploadError() { |
| | | ElMessage.error('ä¸ä¼ æä»¶å¤±è´¥') |
| | | } |
| | | |
| | | function handleUploadSuccess(res, file) { |
| | | if (res?.code !== 200) { |
| | | ElMessage.error(res?.msg || 'ä¸ä¼ 失败') |
| | | return |
| | | } |
| | | const attachmentId = res?.data?.id ?? res?.data?.tempId ?? '' |
| | | if (!attachmentId) return |
| | | form.value.attachmentIds.push(attachmentId) |
| | | try { |
| | | file.attachmentId = attachmentId |
| | | } catch (e) {} |
| | | ElMessage.success('ä¸ä¼ æå') |
| | | } |
| | | |
| | | function handleRemove(file) { |
| | | const attachmentId = file?.attachmentId |
| | | if (!attachmentId) return |
| | | form.value.attachmentIds = (form.value.attachmentIds || []).filter(id => id !== attachmentId) |
| | | } |
| | | |
| | | async function submit() { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <AIChatSidebar :assistants="assistants" default-assistant="purchase" /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import AIChatSidebar from '@/components/AIChatSidebar/index.vue' |
| | | import { purchaseAssistant } from '@/components/AIChatSidebar/assistants' |
| | | |
| | | const assistants = [purchaseAssistant] |
| | | </script> |
| | |
| | | props: {
|
| | | iconClass: {
|
| | | type: String,
|
| | | required: true
|
| | | default: ''
|
| | | },
|
| | | className: {
|
| | | type: String,
|
| | |
| | | transformData: (workbookData) => workbookData, |
| | | }); |
| | | |
| | | // 计ç®å±æ§ - 夿æä»¶ç±»å |
| | | // 计ç®å±æ§ - 夿æä»¶ç±»åï¼æ¯æURL带æ¥è¯¢åæ°ï¼ |
| | | const isImage = computed(() => { |
| | | const state = /\.(jpg|jpeg|png|gif)$/i.test(fileUrl.value); |
| | | const state = /\.(jpg|jpeg|png|gif)(\?.*)?$/i.test(fileUrl.value); |
| | | if (state) { |
| | | imgUrl.value = fileUrl.value.replaceAll('word', 'img'); |
| | | } |
| | |
| | | |
| | | const isPdf = computed(() => { |
| | | console.log(fileUrl.value) |
| | | return /\.pdf$/i.test(fileUrl.value); |
| | | return /\.pdf(\?.*)?$/i.test(fileUrl.value); |
| | | }); |
| | | |
| | | const isDoc = computed(() => { |
| | | return /\.(doc|docx)$/i.test(fileUrl.value); |
| | | return /\.(doc|docx)(\?.*)?$/i.test(fileUrl.value); |
| | | }); |
| | | |
| | | const isXls = computed(() => { |
| | | const state = /\.(xls|xlsx)$/i.test(fileUrl.value); |
| | | const state = /\.(xls|xlsx)(\?.*)?$/i.test(fileUrl.value); |
| | | if (state) { |
| | | options.value.xls = /\.(xls)$/i.test(fileUrl.value); |
| | | options.value.xls = /\.(xls)(\?.*)?$/i.test(fileUrl.value); |
| | | } |
| | | return state; |
| | | }); |
| | | |
| | | const isZipOrRar = computed(() => { |
| | | return /\.(zip|rar)$/i.test(fileUrl.value); |
| | | return /\.(zip|rar)(\?.*)?$/i.test(fileUrl.value); |
| | | }); |
| | | |
| | | const isSupported = computed(() => { |
| | |
| | | }; |
| | | |
| | | const open = (url) => { |
| | | fileUrl.value = window.location.protocol+'//'+window.location.host+ url; |
| | | fileUrl.value = url; |
| | | dialogVisible.value = true; |
| | | }; |
| | | const handleClose = () => { |
| | |
| | | <template> |
| | | <div :class="{ 'has-logo': showLogo }" class="sidebar-container"> |
| | | <logo v-if="showLogo" :collapse="isCollapse" /> |
| | | <div :class="{ 'has-logo': showLogo }" |
| | | class="sidebar-container"> |
| | | <logo v-if="showLogo" |
| | | :collapse="isCollapse" /> |
| | | <el-scrollbar wrap-class="scrollbar-wrapper"> |
| | | <el-menu |
| | | :default-active="activeMenu" |
| | | <el-menu :default-active="activeMenu" |
| | | :collapse="isCollapse" |
| | | :background-color="getMenuBackground" |
| | | :text-color="getMenuTextColor" |
| | |
| | | :active-text-color="theme" |
| | | :collapse-transition="false" |
| | | mode="vertical" |
| | | :class="sideTheme" |
| | | > |
| | | <sidebar-item |
| | | v-for="(route, index) in sidebarRouters" |
| | | :class="sideTheme"> |
| | | <sidebar-item v-for="(route, index) in sidebarRouters" |
| | | :key="route.path + index" |
| | | :item="route" |
| | | :base-path="route.path" |
| | | /> |
| | | :base-path="route.path" /> |
| | | </el-menu> |
| | | </el-scrollbar> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import Logo from './Logo' |
| | | import SidebarItem from './SidebarItem' |
| | | import variables from '@/assets/styles/variables.module.scss' |
| | | import useAppStore from '@/store/modules/app' |
| | | import useSettingsStore from '@/store/modules/settings' |
| | | import usePermissionStore from '@/store/modules/permission' |
| | | import Logo from "./Logo"; |
| | | import SidebarItem from "./SidebarItem"; |
| | | import variables from "@/assets/styles/variables.module.scss"; |
| | | import useAppStore from "@/store/modules/app"; |
| | | import useSettingsStore from "@/store/modules/settings"; |
| | | import usePermissionStore from "@/store/modules/permission"; |
| | | |
| | | const route = useRoute() |
| | | const appStore = useAppStore() |
| | | const settingsStore = useSettingsStore() |
| | | const permissionStore = usePermissionStore() |
| | | const route = useRoute(); |
| | | const appStore = useAppStore(); |
| | | const settingsStore = useSettingsStore(); |
| | | const permissionStore = usePermissionStore(); |
| | | |
| | | const sidebarRouters = computed(() => permissionStore.sidebarRouters) |
| | | const showLogo = computed(() => settingsStore.sidebarLogo) |
| | | const sideTheme = computed(() => settingsStore.sideTheme) |
| | | const theme = computed(() => settingsStore.theme) |
| | | const isCollapse = computed(() => !appStore.sidebar.opened) |
| | | const sidebarRouters = computed(() => permissionStore.sidebarRouters); |
| | | const showLogo = computed(() => settingsStore.sidebarLogo); |
| | | const sideTheme = computed(() => settingsStore.sideTheme); |
| | | const theme = computed(() => settingsStore.theme); |
| | | const isCollapse = computed(() => !appStore.sidebar.opened); |
| | | |
| | | const getMenuBackground = computed(() => 'var(--sidebar-bg)') |
| | | const getMenuBackground = computed(() => "var(--sidebar-bg)"); |
| | | |
| | | const getMenuTextColor = computed(() => { |
| | | if (settingsStore.isDark) { |
| | | return 'var(--sidebar-text)' |
| | | return "var(--sidebar-text)"; |
| | | } |
| | | return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText |
| | | }) |
| | | return sideTheme.value === "theme-dark" |
| | | ? variables.menuText |
| | | : variables.menuLightText; |
| | | }); |
| | | |
| | | const activeMenu = computed(() => { |
| | | const { meta, path } = route |
| | | const { meta, path } = route; |
| | | if (meta.activeMenu) { |
| | | return meta.activeMenu |
| | | return meta.activeMenu; |
| | | } |
| | | return path |
| | | }) |
| | | return path; |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | |
| | | background-color: var(--menu-active-bg, rgba(0, 0, 0, 0.06)) !important; |
| | | border-radius: 14px; |
| | | margin: 0 10px 6px !important; |
| | | width: calc(100% - 20px) !important; |
| | | // width: calc(100% - 20px) !important; |
| | | padding-left: 10px !important; |
| | | padding-right: 10px !important; |
| | | box-sizing: border-box; |
| | |
| | | <app-main />
|
| | | <settings ref="settingRef" />
|
| | | </div>
|
| | | <AIChatSidebar v-if="showGlobalAiChat" /> |
| | | </div>
|
| | | </template>
|
| | |
|
| | | <script setup>
|
| | | import { useWindowSize } from "@vueuse/core";
|
| | | import { useRoute } from "vue-router"; |
| | | import Sidebar from "./components/Sidebar/index.vue";
|
| | | import { AppMain, Navbar, Settings, TagsView } from "./components";
|
| | | import AIChatSidebar from "@/components/AIChatSidebar/index.vue"; |
| | | import defaultSettings from "@/settings";
|
| | |
|
| | | import useAppStore from "@/store/modules/app";
|
| | | import useUserStore from "@/store/modules/user"; |
| | | import useSettingsStore from "@/store/modules/settings";
|
| | |
|
| | | const settingsStore = useSettingsStore();
|
| | | const userStore = useUserStore(); |
| | | const route = useRoute(); |
| | | const theme = computed(() => settingsStore.theme);
|
| | | const sideTheme = computed(() => settingsStore.sideTheme);
|
| | | const sidebar = computed(() => useAppStore().sidebar);
|
| | | const device = computed(() => useAppStore().device);
|
| | | const needTagsView = computed(() => settingsStore.tagsView);
|
| | | const fixedHeader = computed(() => settingsStore.fixedHeader);
|
| | | const aiEnabled = computed(() => Number(userStore.aiEnabled) === 1); |
| | | const showGlobalAiChat = computed(() => { |
| | | const isIndustrialBrainRoute = String(route.path || "").startsWith("/ai-industrial-brain"); |
| | | return !isIndustrialBrainRoute && aiEnabled.value; |
| | | }); |
| | |
|
| | | const classObj = computed(() => ({
|
| | | hideSidebar: !sidebar.value.opened,
|
| | |
| | | // 坿æ¬ç»ä»¶ |
| | | import Editor from "@/components/Editor"; |
| | | // æä»¶ä¸ä¼ ç»ä»¶ |
| | | import FileUpload from "@/components/FileUpload"; |
| | | import FileUpload from "@/components/AttachmentUpload/file"; |
| | | // å¾çä¸ä¼ ç»ä»¶ |
| | | import ImageUpload from "@/components/ImageUpload"; |
| | | import ImageUpload from "@/components/AttachmentUpload/image"; |
| | | // å¾çé¢è§ç»ä»¶ |
| | | import ImagePreview from "@/components/ImagePreview"; |
| | | import ImagePreview from "@/components/AttachmentPreview/image"; |
| | | // éä»¶å¼¹çªç»ä»¶ |
| | | import FileListDialog from "@/components/Dialog/FileList.vue"; |
| | | // åå
¸æ ç¾ç»ä»¶ |
| | | import DictTag from "@/components/DictTag"; |
| | | // è¡¨æ ¼ç»ä»¶ |
| | |
| | | app.component("FileUpload", FileUpload); |
| | | app.component("ImageUpload", ImageUpload); |
| | | app.component("ImagePreview", ImagePreview); |
| | | app.component("FileListDialog", FileListDialog); |
| | | app.component("RightToolbar", RightToolbar); |
| | | app.component("Editor", Editor); |
| | | app.component("PIMTable", PIMTable); |
| | |
| | | saveAs(text, name, opts) {
|
| | | saveAs(text, name, opts);
|
| | | },
|
| | | byUrl(url, filename) {
|
| | | // å°URLä¸çpreviewæ¿æ¢ædownload
|
| | | const downloadUrl = url.replace(/preview/g, 'download')
|
| | | const link = document.createElement('a')
|
| | | link.href = downloadUrl
|
| | | link.download = filename || ''
|
| | | document.body.appendChild(link)
|
| | | link.click()
|
| | | document.body.removeChild(link)
|
| | | },
|
| | | async printErrMsg(data) {
|
| | | const resText = await data.text();
|
| | | const rspObj = JSON.parse(resText);
|
| | |
| | | component: () => import("@/views/register"), |
| | | hidden: true, |
| | | }, |
| | | // ç³»ç»æ¶æå¾ |
| | | // { |
| | | // path: "/system-architecture", |
| | | // component: Layout, |
| | | // redirect: "/system-architecture/index", |
| | | // children: [ |
| | | // { |
| | | // path: "index", |
| | | // component: () => import("@/views/systemArchitecture/index.vue"), |
| | | // name: "SystemArchitecture", |
| | | // meta: { title: "ç³»ç»æ¶æå¾", icon: "tree" }, |
| | | // }, |
| | | // ], |
| | | // }, |
| | | { |
| | | path: "/:pathMatch(.*)*", |
| | | component: () => import("@/views/error/404"), |
| | |
| | | ], |
| | | }, |
| | | { |
| | | path: "/ai-industrial-brain", |
| | | component: Layout, |
| | | children: [ |
| | | { |
| | | path: "index", |
| | | component: () => import("@/views/aiIndustrialBrain/index.vue"), |
| | | name: "AiIndustrialBrain", |
| | | meta: { title: "AIå·¥ä¸å¤§è", icon: "skill" }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/user", |
| | | component: Layout, |
| | | hidden: true, |
| | |
| | | name: "DeviceInfo", |
| | | meta: { title: "设å¤ä¿¡æ¯", icon: "monitor" }, |
| | | }, |
| | | // æ·»å 项ç®è¯¦æ
页é¢è·¯ç±é
ç½® |
| | | { |
| | | path: "/oaSystem/projectManagement/projectDetail", |
| | | component: Layout, |
| | | hidden: true, |
| | | children: [ |
| | | { |
| | | path: ":projectId", |
| | | component: () => import("@/views/oaSystem/projectManagement/projectDetail.vue"), |
| | | name: "ProjectDetail", |
| | | meta: { title: "项ç®è¯¦æ
", activeMenu: "/oaSystem/projectManagement" }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/projectManagement/Management/detail", |
| | | component: Layout, |
| | |
| | | }, |
| | | ], |
| | | }, |
| | | // è´¢å¡ç®¡ç模åè·¯ç± |
| | | { |
| | | path: "/financial", |
| | | component: Layout, |
| | | hidden: false, |
| | | redirect: "/financial/general-ledger", |
| | | alwaysShow: true, |
| | | meta: { title: "è´¢å¡ç®¡ç", icon: "money" }, |
| | | children: [ |
| | | { |
| | | path: "sales-out", |
| | | component: () => import("@/views/financialManagement/receivable/salesOut.vue"), |
| | | name: "SalesOut", |
| | | meta: { title: "éå®åºåº" }, |
| | | }, |
| | | { |
| | | path: "sales-return", |
| | | component: () => import("@/views/financialManagement/receivable/salesReturn.vue"), |
| | | name: "SalesReturn", |
| | | meta: { title: "éå®éè´§" }, |
| | | }, |
| | | |
| | | { |
| | | path: "invoice-apply", |
| | | component: () => import("@/views/financialManagement/receivable/invoiceApply.vue"), |
| | | name: "InvoiceApply", |
| | | meta: { title: "å¼ç¥¨ç³è¯·" }, |
| | | }, |
| | | { |
| | | path: "output-invoice", |
| | | component: () => import("@/views/financialManagement/receivable/outputInvoice.vue"), |
| | | name: "OutputInvoice", |
| | | meta: { title: "é项å票" }, |
| | | }, |
| | | { |
| | | path: "receipt", |
| | | component: () => import("@/views/financialManagement/receivable/receipt.vue"), |
| | | name: "Receipt", |
| | | meta: { title: "æ¶æ¬¾å" }, |
| | | }, |
| | | { |
| | | path: "receivable-reconciliation", |
| | | component: () => import("@/views/financialManagement/receivable/reconciliation.vue"), |
| | | name: "ReceivableReconciliation", |
| | | meta: { title: "åºæ¶å¯¹è´¦" }, |
| | | }, |
| | | { |
| | | path: "purchase-in", |
| | | component: () => import("@/views/financialManagement/payable/purchaseIn.vue"), |
| | | name: "PurchaseIn", |
| | | meta: { title: "éè´å
¥åº" }, |
| | | }, |
| | | { |
| | | path: "purchase-return", |
| | | component: () => import("@/views/financialManagement/payable/purchaseReturn.vue"), |
| | | name: "PurchaseReturn", |
| | | meta: { title: "éè´éè´§" }, |
| | | }, |
| | | { |
| | | path: "input-invoice", |
| | | component: () => import("@/views/financialManagement/payable/input-invoice.vue"), |
| | | name: "InputInvoice", |
| | | meta: { title: "è¿é¡¹å票" }, |
| | | }, |
| | | { |
| | | path: "payment-apply", |
| | | component: () => import("@/views/financialManagement/payable/paymentApply.vue"), |
| | | name: "PaymentApply", |
| | | meta: { title: "仿¬¾ç³è¯·" }, |
| | | }, |
| | | |
| | | { |
| | | path: "payment", |
| | | component: () => import("@/views/financialManagement/payable/payment.vue"), |
| | | name: "Payment", |
| | | meta: { title: "仿¬¾å" }, |
| | | }, |
| | | { |
| | | path: "payable-reconciliation", |
| | | component: () => import("@/views/financialManagement/payable/reconciliation.vue"), |
| | | name: "PayableReconciliation", |
| | | meta: { title: "åºä»å¯¹è´¦" }, |
| | | }, |
| | | { |
| | | path: "fixed-assets", |
| | | component: () => import("@/views/financialManagement/assets/fixedAssets.vue"), |
| | | name: "FixedAssets", |
| | | meta: { title: "åºå®èµäº§" }, |
| | | }, |
| | | { |
| | | path: "intangible-assets", |
| | | component: () => import("@/views/financialManagement/assets/intangibleAssets.vue"), |
| | | name: "IntangibleAssets", |
| | | meta: { title: "æ å½¢èµäº§" }, |
| | | }, |
| | | { |
| | | path: "general-ledger", |
| | | component: () => import("@/views/financialManagement/generalLedger/index.vue"), |
| | | name: "GeneralLedger", |
| | | meta: { title: "æ»å¸ç§ç®" }, |
| | | }, |
| | | { |
| | | path: "voucher", |
| | | component: () => import("@/views/financialManagement/voucher/index.vue"), |
| | | name: "Voucher", |
| | | meta: { title: "åè¯" }, |
| | | }, |
| | | { |
| | | path: "voucher-general-ledger", |
| | | component: () => import("@/views/financialManagement/voucher/generalLedger.vue"), |
| | | name: "VoucherGeneralLedger", |
| | | meta: { title: "ç§ç®æ»å¸" }, |
| | | }, |
| | | { |
| | | path: "voucher-detail-ledger", |
| | | component: () => import("@/views/financialManagement/voucher/detailLedger.vue"), |
| | | name: "VoucherDetailLedger", |
| | | meta: { title: "ç§ç®æç»å¸" }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | // å¨æè·¯ç±ï¼åºäºç¨æ·æé卿å»å è½½ |
| | |
| | | import Layout from '@/layout/index'
|
| | | import ParentView from '@/components/ParentView'
|
| | | import InnerLink from '@/layout/components/InnerLink'
|
| | | import useUserStore from '@/store/modules/user' |
| | |
|
| | | // å¹é
viewsé颿æç.vueæä»¶
|
| | | const modules = import.meta.glob('./../../views/**/*.vue')
|
| | |
| | | return new Promise(resolve => {
|
| | | // åå端请æ±è·¯ç±æ°æ®
|
| | | getRouters().then(res => {
|
| | | const sdata = JSON.parse(JSON.stringify(res.data))
|
| | | const rdata = JSON.parse(JSON.stringify(res.data))
|
| | | const defaultData = JSON.parse(JSON.stringify(res.data))
|
| | | const aiEnabled = Number(useUserStore().aiEnabled) === 1 |
| | | const rawRoutes = filterAiFeatureRoutes(res.data, aiEnabled) |
| | | const sdata = JSON.parse(JSON.stringify(rawRoutes)) |
| | | const rdata = JSON.parse(JSON.stringify(rawRoutes)) |
| | | const defaultData = JSON.parse(JSON.stringify(rawRoutes)) |
| | | const sidebarRoutes = filterAsyncRouter(sdata)
|
| | | const rewriteRoutes = filterAsyncRouter(rdata, false, true)
|
| | | const defaultRoutes = filterAsyncRouter(defaultData)
|
| | | const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
|
| | | asyncRoutes.forEach(route => { router.addRoute(route) })
|
| | | this.setRoutes(rewriteRoutes)
|
| | | const constantSidebarRoutes = filterAiFeatureRoutes(constantRoutes, aiEnabled) |
| | | // å°è´¢å¡ç®¡çè·¯ç±åå¹¶å°ä¾§è¾¹æ
|
| | | this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
|
| | | this.setSidebarRouters(constantSidebarRoutes.concat(sidebarRoutes)) |
| | | this.setDefaultRoutes(sidebarRoutes)
|
| | | this.setTopbarRoutes(defaultRoutes)
|
| | | resolve(rewriteRoutes)
|
| | |
| | | })
|
| | |
|
| | | // éååå°ä¼ æ¥çè·¯ç±å符串ï¼è½¬æ¢ä¸ºç»ä»¶å¯¹è±¡
|
| | | function filterAiFeatureRoutes(routes = [], aiEnabled = false) { |
| | | if (aiEnabled) { |
| | | return routes |
| | | } |
| | | return routes.reduce((acc, route) => { |
| | | if (!route || isAiFeatureRoute(route)) { |
| | | return acc |
| | | } |
| | | const nextRoute = { ...route } |
| | | if (Array.isArray(nextRoute.children) && nextRoute.children.length > 0) { |
| | | nextRoute.children = filterAiFeatureRoutes(nextRoute.children, aiEnabled) |
| | | } |
| | | acc.push(nextRoute) |
| | | return acc |
| | | }, []) |
| | | } |
| | | |
| | | function isAiFeatureRoute(route = {}) { |
| | | const path = String(route.path || '').toLowerCase() |
| | | const component = String(route.component || '').toLowerCase() |
| | | const name = String(route.name || '').toLowerCase() |
| | | const title = String(route?.meta?.title ?? route?.title ?? '') |
| | | |
| | | return ( |
| | | path.includes('chathome') || |
| | | component.includes('chathome') || |
| | | name.includes('chathome') || |
| | | title.includes('AI') |
| | | ) |
| | | } |
| | | |
| | | function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
| | | return asyncRouterMap.filter(route => {
|
| | | if (type && route.children) {
|
| | |
| | | name: '',
|
| | | avatar: '',
|
| | | roles: [],
|
| | | permissions: []
|
| | | permissions: [], |
| | | aiEnabled: 0 |
| | | }),
|
| | | actions: {
|
| | | // ç»å½
|
| | |
| | | this.roleName = user.roles[0].roleName
|
| | | this.currentDeptId = user.tenantId
|
| | | this.currentLoginTime = this.getCurrentTime()
|
| | | this.aiEnabled = Number(res.aiEnabled) === 1 ? 1 : 0 |
| | | resolve(res)
|
| | | }).catch(error => {
|
| | | reject(error)
|
| | |
| | | this.token = ''
|
| | | this.roles = []
|
| | | this.permissions = []
|
| | | this.aiEnabled = 0 |
| | | removeToken()
|
| | | resolve()
|
| | | }).catch(error => {
|
| | |
| | | return `<el-dialog v-model="dialogVisible" @open="onOpen" @close="onClose" title="Dialog Titile"> |
| | | ${str} |
| | | <template #footer> |
| | | <el-button @click="close">åæ¶</el-button> |
| | | <el-button type="primary" @click="handelConfirm">ç¡®å®</el-button> |
| | | <el-button @click="close">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog>` |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <transition name="fade"> |
| | | <section v-if="visible" class="assistant-workspace"> |
| | | <div class="assistant-workspace__panel"> |
| | | <button |
| | | v-if="assistantMode === 'pending'" |
| | | type="button" |
| | | class="workspace-back-btn" |
| | | @click="$emit('close')" |
| | | > |
| | | <el-icon><ArrowLeftBold /></el-icon> |
| | | <span>è¿åå·¥ä¸å¤§å±</span> |
| | | </button> |
| | | |
| | | <div class="assistant-workspace__body"> |
| | | <AIChatSidebar |
| | | v-if="assistantMode !== 'pending'" |
| | | :key="assistantMode" |
| | | class="workspace-chat" |
| | | :assistants="assistantMode === 'purchase' ? [purchaseAssistant] : [generalAssistant]" |
| | | :default-assistant="assistantMode" |
| | | :hide-trigger="true" |
| | | :auto-open="true" |
| | | drawer-size="100%" |
| | | drawer-direction="ttb" |
| | | header-extra-action-text="è¿åå·¥ä¸å¤§å±" |
| | | @header-extra-action="$emit('close')" |
| | | /> |
| | | |
| | | <div v-else class="workspace-pending"> |
| | | <div class="workspace-pending__content"> |
| | | <h3>{{ agentTitle }}</h3> |
| | | <p>æ£å¨å¼åï¼æ¬è¯·æå¾
......</p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </section> |
| | | </transition> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed } from "vue"; |
| | | import { ArrowLeftBold } from "@element-plus/icons-vue"; |
| | | import AIChatSidebar from "@/components/AIChatSidebar/index.vue"; |
| | | import { generalAssistant, purchaseAssistant } from "@/components/AIChatSidebar/assistants"; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | agent: { |
| | | type: Object, |
| | | default: () => ({}), |
| | | }, |
| | | }); |
| | | |
| | | defineEmits(["close"]); |
| | | |
| | | const agentKey = computed(() => String(props.agent?.key || "")); |
| | | const agentTitle = computed(() => String(props.agent?.name || "AI婿")); |
| | | const assistantMode = computed(() => { |
| | | if (agentKey.value === "purchase") return "purchase"; |
| | | if (agentKey.value === "general") return "general"; |
| | | return "pending"; |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .assistant-workspace { |
| | | position: fixed; |
| | | inset: 0; |
| | | z-index: 2100; |
| | | padding: 12px; |
| | | background: rgba(33, 49, 63, 0.24); |
| | | backdrop-filter: blur(2px); |
| | | } |
| | | |
| | | .assistant-workspace__panel { |
| | | position: relative; |
| | | height: 100%; |
| | | border-radius: 22px; |
| | | border: 1px solid var(--surface-border); |
| | | background: linear-gradient(180deg, #f9fcfb 0%, #f0f5f2 100%); |
| | | box-shadow: var(--shadow-md); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .assistant-workspace__body { |
| | | height: 100%; |
| | | min-height: 100%; |
| | | } |
| | | |
| | | .workspace-back-btn { |
| | | position: absolute; |
| | | top: 16px; |
| | | right: 20px; |
| | | z-index: 5; |
| | | height: 36px; |
| | | padding: 0 14px; |
| | | border: 1px solid rgba(38, 112, 183, 0.3); |
| | | border-radius: 10px; |
| | | background: rgba(255, 255, 255, 0.92); |
| | | color: #25528f; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | } |
| | | |
| | | .workspace-back-btn:hover { |
| | | border-color: rgba(31, 122, 114, 0.45); |
| | | color: #1f5ddf; |
| | | box-shadow: 0 8px 16px rgba(31, 122, 114, 0.14); |
| | | transform: translateY(-1px); |
| | | } |
| | | |
| | | .workspace-chat { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .workspace-chat :deep(.ai-chat-sidebar-wrapper) { |
| | | height: 100%; |
| | | } |
| | | |
| | | .workspace-chat :deep(.ai-chat-drawer) { |
| | | height: 100%; |
| | | } |
| | | |
| | | .workspace-chat :deep(.el-drawer) { |
| | | height: 100% !important; |
| | | width: 100% !important; |
| | | } |
| | | |
| | | .workspace-pending { |
| | | height: 100%; |
| | | display: grid; |
| | | place-items: center; |
| | | padding: 20px; |
| | | color: var(--text-secondary); |
| | | } |
| | | |
| | | .workspace-pending__content { |
| | | display: grid; |
| | | gap: 12px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .workspace-pending__content h3 { |
| | | margin: 0; |
| | | font-size: 36px; |
| | | color: var(--text-primary); |
| | | } |
| | | |
| | | .workspace-pending__content p { |
| | | margin: 0; |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .fade-enter-active, |
| | | .fade-leave-active { |
| | | transition: opacity 0.2s ease; |
| | | } |
| | | |
| | | .fade-enter-from, |
| | | .fade-leave-to { |
| | | opacity: 0; |
| | | } |
| | | |
| | | @media (max-width: 1600px) { |
| | | .workspace-back-btn { |
| | | top: 12px; |
| | | right: 14px; |
| | | height: 32px; |
| | | padding: 0 12px; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .workspace-pending__content h3 { |
| | | font-size: 30px; |
| | | } |
| | | |
| | | .workspace-pending__content p { |
| | | font-size: 20px; |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div ref="screenRef" class="ai-brain-screen"> |
| | | <section class="brain-stage"> |
| | | <header class="brain-head"> |
| | | <div class="head-date"> |
| | | <p>{{ weekLabel }}</p> |
| | | <p>{{ dateLabel }}</p> |
| | | </div> |
| | | |
| | | <div class="head-title"> |
| | | <span>AIå·¥ä¸å¤§è</span> |
| | | </div> |
| | | |
| | | <div class="head-actions"> |
| | | <button type="button" class="head-back-btn" @click="goBack"> |
| | | <el-icon><ArrowLeftBold /></el-icon> |
| | | <span>è¿å</span> |
| | | </button> |
| | | </div> |
| | | </header> |
| | | |
| | | <section class="brain-intro"> |
| | | <h2>å·¥ä¸AIæ°ååå·¥ï¼èµè½æºé æ°çºªå
</h2> |
| | | <p>å
大AI婿ååä¼ä¸ç®¡çãéå®ãéè´ãç产ãè´¢å¡åæ°æ®å
¨é¾è·¯</p> |
| | | <div class="intro-sign">é¿éäº Ã åé®å¤§æ¨¡å à æºè½ä½AI</div> |
| | | </section> |
| | | |
| | | <section class="carousel-area"> |
| | | <button type="button" class="nav-btn nav-btn--left" @click="prevCard"> |
| | | <el-icon><ArrowLeftBold /></el-icon> |
| | | </button> |
| | | |
| | | <div class="carousel-track"> |
| | | <article |
| | | v-for="card in visibleCards" |
| | | :key="card.agent.key" |
| | | class="agent-card" |
| | | :class="{ 'agent-card--active': card.offset === 0 }" |
| | | :style="getCardStyle(card.offset)" |
| | | @click="openAssistant(card.realIndex)" |
| | | > |
| | | <div class="agent-card__head" :class="{ 'agent-card__head--active': card.offset === 0 }"> |
| | | {{ card.agent.name }} |
| | | </div> |
| | | |
| | | <div class="agent-card__body" :class="{ 'agent-card__body--active': card.offset === 0 }"> |
| | | <div class="avatar-shell" :class="{ 'avatar-shell--active': card.offset === 0 }"> |
| | | <div class="avatar-base"></div> |
| | | <div class="avatar-cut"> |
| | | <img v-if="card.agent.avatar" class="avatar-cut__img" :src="card.agent.avatar" :alt="card.agent.name" /> |
| | | </div> |
| | | </div> |
| | | <div v-if="card.offset === 0" class="highlight-list"> |
| | | <div |
| | | v-for="highlight in card.agent.highlights" |
| | | :key="highlight" |
| | | class="highlight-item" |
| | | > |
| | | {{ highlight }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </article> |
| | | </div> |
| | | |
| | | <button type="button" class="nav-btn nav-btn--right" @click="nextCard"> |
| | | <el-icon><ArrowRightBold /></el-icon> |
| | | </button> |
| | | </section> |
| | | |
| | | <section class="brain-footer"> |
| | | <div class="footer-grid-overlay"></div> |
| | | |
| | | <div class="footer-metrics"> |
| | | <article class="footer-metric"> |
| | | <span class="footer-metric__label">å¨çº¿æºè½ä½</span> |
| | | <strong class="footer-metric__value">{{ agents.length }}个</strong> |
| | | <small class="footer-metric__hint">å
¨é¾è·¯ååè¿è¡</small> |
| | | </article> |
| | | |
| | | <article class="footer-metric footer-metric--focus"> |
| | | <span class="footer-metric__label">å½åç¦ç¹</span> |
| | | <strong class="footer-metric__value">{{ getFooterAgentName(focusAgent.name) }}</strong> |
| | | <small class="footer-metric__hint">{{ focusAgent.highlights?.[0] || "æºè½åæèå¨" }}</small> |
| | | </article> |
| | | |
| | | <article class="footer-metric footer-metric--period"> |
| | | <span class="footer-metric__label">è½®æå¨æ</span> |
| | | <strong class="footer-metric__value">{{ carouselSecondsText }}</strong> |
| | | <div class="footer-period-control"> |
| | | <button type="button" class="period-btn" @click="adjustCarouselSeconds(-0.5)">-</button> |
| | | <input |
| | | v-model.number="carouselSeconds" |
| | | class="period-input" |
| | | type="number" |
| | | min="2" |
| | | max="12" |
| | | step="0.5" |
| | | /> |
| | | <span class="period-unit">s</span> |
| | | <button type="button" class="period-btn" @click="adjustCarouselSeconds(0.5)">+</button> |
| | | </div> |
| | | <input |
| | | v-model.number="carouselSeconds" |
| | | class="footer-period-slider" |
| | | type="range" |
| | | min="2" |
| | | max="12" |
| | | step="0.5" |
| | | /> |
| | | <small class="footer-metric__hint">坿å¨è®¾ç½® 2.0s - 12.0s</small> |
| | | </article> |
| | | </div> |
| | | |
| | | <div class="footer-rail"> |
| | | <div class="footer-rail__line"> |
| | | <span class="footer-rail__flow"></span> |
| | | </div> |
| | | <div class="footer-rail__nodes"> |
| | | <button |
| | | v-for="node in footerNodes" |
| | | :key="node.key" |
| | | type="button" |
| | | class="footer-node" |
| | | :class="{ 'footer-node--active': node.index === carouselIndex }" |
| | | @click="openAssistant(node.index)" |
| | | > |
| | | <span class="footer-node__dot"></span> |
| | | <span class="footer-node__name">{{ getFooterAgentName(node.name) }}</span> |
| | | </button> |
| | | </div> |
| | | </div> |
| | | </section> |
| | | </section> |
| | | |
| | | <AiAssistantWorkspace |
| | | :visible="fullscreenVisible" |
| | | :agent="currentAgent" |
| | | @close="closeFullscreen" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import { ArrowLeftBold, ArrowRightBold } from "@element-plus/icons-vue"; |
| | | import AiAssistantWorkspace from "./components/AiAssistantWorkspace.vue"; |
| | | import todoAvatar from "@/assets/AI/å¾
å婿.png"; |
| | | import salesAvatar from "@/assets/AI/éå®å©æ.png"; |
| | | import purchaseAvatar from "@/assets/AI/éè´å©æ.png"; |
| | | import productionAvatar from "@/assets/AI/çäº§å©æ.png"; |
| | | import financeAvatar from "@/assets/AI/è´¢å¡å©æ.png"; |
| | | |
| | | const router = useRouter(); |
| | | |
| | | const agents = [ |
| | | { |
| | | key: "general", |
| | | name: "AIå¾
å婿", |
| | | highlights: ["è·¨æ¨¡åæµç¨è¯æ", "ç»è¥é£é©æºè½æé"], |
| | | }, |
| | | { |
| | | key: "sales", |
| | | name: "AIéå®å©æ", |
| | | highlights: ["å®¢æ·æµå¤±é£é©åæ", "忬¾ä¸æ¥ä»·çç¥å»ºè®®"], |
| | | }, |
| | | { |
| | | key: "purchase", |
| | | name: "AIéè´å©æ", |
| | | highlights: ["ä¾åºé¾ææ åæ", "éè´è®¢åæºè½çæ"], |
| | | }, |
| | | { |
| | | key: "production", |
| | | name: "AIçäº§å©æ", |
| | | highlights: ["å·¥åºç¶é¢å®ä½", "产è½ä¸æ¥åºæºè½é¢è¦"], |
| | | }, |
| | | { |
| | | key: "finance", |
| | | name: "AIè´¢å¡å©æ", |
| | | highlights: ["ç°éæµååé¢å¤", "è´¹ç¨ç»ææºè½åæ"], |
| | | }, |
| | | ]; |
| | | |
| | | const avatarByAgentKey = { |
| | | general: todoAvatar, |
| | | sales: salesAvatar, |
| | | purchase: purchaseAvatar, |
| | | production: productionAvatar, |
| | | finance: financeAvatar, |
| | | }; |
| | | |
| | | for (let i = agents.length - 1; i >= 0; i -= 1) { |
| | | const agent = agents[i]; |
| | | const avatar = avatarByAgentKey[agent.key]; |
| | | if (!avatar) { |
| | | agents.splice(i, 1); |
| | | continue; |
| | | } |
| | | agent.avatar = avatar; |
| | | } |
| | | |
| | | const carouselIndex = ref(Math.min(2, Math.max(agents.length - 1, 0))); |
| | | const fullscreenVisible = ref(false); |
| | | const screenRef = ref(null); |
| | | const carouselIntervalMs = ref(4500); |
| | | |
| | | let carouselTimer = null; |
| | | |
| | | const fallbackAgent = { |
| | | key: "fallback", |
| | | name: "AI婿", |
| | | avatar: "", |
| | | highlights: [], |
| | | }; |
| | | |
| | | const currentAgent = computed(() => agents[carouselIndex.value] || agents[0] || fallbackAgent); |
| | | const focusAgent = computed(() => currentAgent.value || fallbackAgent); |
| | | const footerNodes = computed(() => |
| | | agents.map((agent, index) => ({ |
| | | key: agent.key, |
| | | name: agent.name, |
| | | index, |
| | | })) |
| | | ); |
| | | const carouselSeconds = computed({ |
| | | get: () => Number((carouselIntervalMs.value / 1000).toFixed(1)), |
| | | set: (value) => { |
| | | const next = Number(value); |
| | | if (!Number.isFinite(next)) return; |
| | | const clamped = Math.max(2, Math.min(12, Math.round(next * 2) / 2)); |
| | | carouselIntervalMs.value = Math.round(clamped * 1000); |
| | | }, |
| | | }); |
| | | const carouselSecondsText = computed(() => `${carouselSeconds.value.toFixed(1)}s`); |
| | | |
| | | const weekLabel = computed(() => { |
| | | const weekMap = ["æææ¥", "ææä¸", "ææäº", "ææä¸", "ææå", "ææäº", "ææå
"]; |
| | | return weekMap[new Date().getDay()]; |
| | | }); |
| | | |
| | | const dateLabel = computed(() => { |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); |
| | | const month = String(now.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(now.getDate()).padStart(2, "0"); |
| | | return `${year}å¹´${month}æ${day}æ¥`; |
| | | }); |
| | | |
| | | const visibleCards = computed(() => { |
| | | const total = agents.length; |
| | | return agents |
| | | .map((agent, index) => { |
| | | let offset = index - carouselIndex.value; |
| | | if (offset > total / 2) offset -= total; |
| | | if (offset < -total / 2) offset += total; |
| | | return { agent, offset, realIndex: index }; |
| | | }) |
| | | .filter((item) => Math.abs(item.offset) <= 2) |
| | | .sort((a, b) => a.offset - b.offset); |
| | | }); |
| | | |
| | | function getCardStyle(offset) { |
| | | const distance = Math.abs(offset); |
| | | const scale = distance === 0 ? 1 : distance === 1 ? 0.88 : 0.78; |
| | | const opacity = distance === 0 ? 1 : distance === 1 ? 0.92 : 0.76; |
| | | return { |
| | | transform: `translateX(${offset * 340}px) scale(${scale})`, |
| | | zIndex: String(50 - distance), |
| | | opacity, |
| | | }; |
| | | } |
| | | |
| | | function getFooterAgentName(name) { |
| | | return String(name || "AI婿").replace(/^AI/, ""); |
| | | } |
| | | |
| | | function adjustCarouselSeconds(delta) { |
| | | carouselSeconds.value = carouselSeconds.value + delta; |
| | | } |
| | | |
| | | function prevCard() { |
| | | const total = agents.length; |
| | | if (!total) return; |
| | | carouselIndex.value = (carouselIndex.value - 1 + total) % total; |
| | | } |
| | | |
| | | function nextCard() { |
| | | const total = agents.length; |
| | | if (!total) return; |
| | | carouselIndex.value = (carouselIndex.value + 1) % total; |
| | | } |
| | | |
| | | async function enterBrowserFullscreen() { |
| | | if (document.fullscreenElement) return; |
| | | const target = screenRef.value || document.documentElement; |
| | | if (!target || typeof target.requestFullscreen !== "function") return; |
| | | try { |
| | | await target.requestFullscreen(); |
| | | } catch (error) { |
| | | // Ignore: browser may block fullscreen when there is no direct user activation. |
| | | } |
| | | } |
| | | |
| | | async function exitBrowserFullscreen() { |
| | | if (!document.fullscreenElement || typeof document.exitFullscreen !== "function") return; |
| | | try { |
| | | await document.exitFullscreen(); |
| | | } catch (error) { |
| | | // Ignore fullscreen exit failures. |
| | | } |
| | | } |
| | | |
| | | function goBack() { |
| | | closeFullscreen(); |
| | | exitBrowserFullscreen(); |
| | | if (window.history.length > 1) { |
| | | router.back(); |
| | | return; |
| | | } |
| | | router.push("/index"); |
| | | } |
| | | |
| | | function openAssistant(index) { |
| | | if (!agents.length) return; |
| | | carouselIndex.value = index; |
| | | fullscreenVisible.value = true; |
| | | } |
| | | |
| | | function closeFullscreen() { |
| | | fullscreenVisible.value = false; |
| | | } |
| | | |
| | | function startCarousel() { |
| | | stopCarousel(); |
| | | if (fullscreenVisible.value) return; |
| | | carouselTimer = window.setInterval(() => { |
| | | nextCard(); |
| | | }, carouselIntervalMs.value); |
| | | } |
| | | |
| | | function stopCarousel() { |
| | | if (carouselTimer) { |
| | | window.clearInterval(carouselTimer); |
| | | carouselTimer = null; |
| | | } |
| | | } |
| | | |
| | | function handleEscClose(event) { |
| | | if (event.key === "Escape" && fullscreenVisible.value) { |
| | | closeFullscreen(); |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => fullscreenVisible.value, |
| | | (opened) => { |
| | | if (opened) { |
| | | stopCarousel(); |
| | | } else { |
| | | startCarousel(); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | watch( |
| | | () => carouselIntervalMs.value, |
| | | () => { |
| | | if (!fullscreenVisible.value) { |
| | | startCarousel(); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | onMounted(() => { |
| | | startCarousel(); |
| | | window.addEventListener("keydown", handleEscClose); |
| | | window.requestAnimationFrame(() => { |
| | | enterBrowserFullscreen(); |
| | | }); |
| | | }); |
| | | |
| | | onBeforeUnmount(() => { |
| | | stopCarousel(); |
| | | window.removeEventListener("keydown", handleEscClose); |
| | | exitBrowserFullscreen(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .ai-brain-screen { |
| | | position: fixed; |
| | | inset: 0; |
| | | z-index: 1900; |
| | | padding: 10px; |
| | | overflow: hidden; |
| | | background: var(--app-bg); |
| | | } |
| | | |
| | | .brain-stage { |
| | | position: relative; |
| | | height: 100%; |
| | | min-height: 100%; |
| | | border-radius: 22px; |
| | | border: 1px solid var(--surface-border); |
| | | background: |
| | | radial-gradient(circle at 14% 8%, rgba(31, 122, 114, 0.14), transparent 40%), |
| | | radial-gradient(circle at 86% 12%, rgba(30, 91, 255, 0.1), transparent 42%), |
| | | linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(245, 249, 247, 0.94)), |
| | | repeating-linear-gradient( |
| | | 135deg, |
| | | rgba(255, 255, 255, 0.05) 0, |
| | | rgba(255, 255, 255, 0.05) 14px, |
| | | rgba(31, 122, 114, 0.03) 14px, |
| | | rgba(31, 122, 114, 0.03) 28px |
| | | ); |
| | | box-shadow: var(--shadow-sm); |
| | | } |
| | | |
| | | .brain-head { |
| | | display: grid; |
| | | grid-template-columns: 220px minmax(0, 1fr) 180px; |
| | | align-items: center; |
| | | padding: 12px 18px 0; |
| | | } |
| | | |
| | | .head-date { |
| | | color: var(--text-secondary); |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .head-date p { |
| | | margin: 0; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .head-title { |
| | | justify-self: center; |
| | | width: min(760px, 95%); |
| | | height: 68px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 0 0 46px 46px; |
| | | color: #fff; |
| | | font-size: 42px; |
| | | font-style: italic; |
| | | font-weight: 700; |
| | | letter-spacing: 1px; |
| | | background: linear-gradient(135deg, #1f7a72 0%, #1e5bff 100%); |
| | | box-shadow: 0 16px 30px rgba(31, 122, 114, 0.24); |
| | | } |
| | | |
| | | .head-actions { |
| | | justify-self: end; |
| | | } |
| | | |
| | | .head-back-btn { |
| | | height: 40px; |
| | | padding: 0 14px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | border: none; |
| | | border-radius: 999px; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: var(--colorPrimary); |
| | | background: var(--surface-base); |
| | | box-shadow: 0 8px 18px rgba(31, 49, 38, 0.12); |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .brain-intro { |
| | | text-align: center; |
| | | margin-top: 34px; |
| | | } |
| | | |
| | | .brain-intro h2 { |
| | | margin: 0; |
| | | font-size: 44px; |
| | | font-style: italic; |
| | | font-weight: 700; |
| | | color: var(--text-primary); |
| | | } |
| | | |
| | | .brain-intro p { |
| | | margin: 12px 0 10px; |
| | | font-size: 28px; |
| | | color: var(--text-secondary); |
| | | } |
| | | |
| | | .intro-sign { |
| | | display: inline-block; |
| | | padding: 6px 18px; |
| | | border-radius: 999px; |
| | | font-size: 24px; |
| | | font-weight: 700; |
| | | color: #1e5bff; |
| | | background: rgba(255, 255, 255, 0.82); |
| | | border: 1px solid rgba(30, 91, 255, 0.18); |
| | | } |
| | | |
| | | .carousel-area { |
| | | position: relative; |
| | | margin-top: 34px; |
| | | padding: 0 72px 12px; |
| | | } |
| | | |
| | | .carousel-track { |
| | | position: relative; |
| | | height: 500px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .brain-footer { |
| | | position: relative; |
| | | margin: 0 72px; |
| | | height: clamp(226px, 25vh,0); |
| | | border-radius: 18px; |
| | | border: 1px solid rgba(31, 122, 114, 0.28); |
| | | background: |
| | | linear-gradient(120deg, rgba(31, 122, 114, 0.14), rgba(30, 91, 255, 0.14)), |
| | | linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(236, 244, 249, 0.9)); |
| | | box-shadow: |
| | | 0 16px 34px rgba(31, 81, 131, 0.12), |
| | | inset 0 1px 0 rgba(255, 255, 255, 0.72); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .brain-footer::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: -22%; |
| | | bottom: -120%; |
| | | width: 52%; |
| | | height: 260%; |
| | | background: radial-gradient(ellipse at center, rgba(30, 91, 255, 0.2) 0%, rgba(30, 91, 255, 0) 72%); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .brain-footer::after { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | background: linear-gradient(110deg, transparent 12%, rgba(255, 255, 255, 0.24) 38%, transparent 64%); |
| | | transform: translateX(-120%); |
| | | animation: footerSweep 5.8s linear infinite; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .footer-grid-overlay { |
| | | position: absolute; |
| | | inset: 0; |
| | | background: |
| | | repeating-linear-gradient( |
| | | 90deg, |
| | | rgba(31, 122, 114, 0.07) 0, |
| | | rgba(31, 122, 114, 0.07) 1px, |
| | | transparent 1px, |
| | | transparent 36px |
| | | ), |
| | | repeating-linear-gradient( |
| | | 0deg, |
| | | rgba(30, 91, 255, 0.06) 0, |
| | | rgba(30, 91, 255, 0.06) 1px, |
| | | transparent 1px, |
| | | transparent 28px |
| | | ); |
| | | opacity: 0.72; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .footer-metrics { |
| | | position: relative; |
| | | z-index: 2; |
| | | padding: 14px 20px 72px; |
| | | display: grid; |
| | | grid-template-columns: repeat(3, minmax(0, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | |
| | | .footer-metric { |
| | | min-height: 76px; |
| | | border-radius: 12px; |
| | | padding: 10px 14px; |
| | | border: 1px solid rgba(37, 124, 188, 0.2); |
| | | background: linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(245, 250, 255, 0.82)); |
| | | box-shadow: 0 10px 18px rgba(29, 83, 134, 0.08); |
| | | display: grid; |
| | | grid-template-rows: auto auto 1fr; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .footer-metric--focus { |
| | | border-color: rgba(38, 122, 194, 0.34); |
| | | box-shadow: |
| | | 0 12px 22px rgba(30, 91, 255, 0.12), |
| | | inset 0 0 0 1px rgba(85, 148, 232, 0.2); |
| | | } |
| | | |
| | | .footer-metric__label { |
| | | font-size: 14px; |
| | | color: rgba(38, 72, 108, 0.88); |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .footer-metric__value { |
| | | font-size: 30px; |
| | | line-height: 1; |
| | | font-style: italic; |
| | | font-weight: 700; |
| | | color: #1f5ddf; |
| | | text-shadow: 0 3px 10px rgba(30, 91, 255, 0.18); |
| | | } |
| | | |
| | | .footer-metric__hint { |
| | | margin-top: auto; |
| | | font-size: 13px; |
| | | color: rgba(52, 89, 128, 0.82); |
| | | } |
| | | |
| | | .footer-metric--period .footer-metric__hint { |
| | | margin-top: 0; |
| | | line-height: 1.25; |
| | | } |
| | | |
| | | .footer-metric--period { |
| | | min-height: 122px; |
| | | grid-template-rows: auto auto auto auto auto; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .footer-period-control { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .period-btn { |
| | | width: 22px; |
| | | height: 22px; |
| | | border-radius: 50%; |
| | | border: 1px solid rgba(38, 112, 183, 0.28); |
| | | background: rgba(255, 255, 255, 0.9); |
| | | color: #2054c9; |
| | | font-size: 14px; |
| | | font-weight: 700; |
| | | line-height: 1; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .period-input { |
| | | width: 56px; |
| | | height: 24px; |
| | | border-radius: 8px; |
| | | border: 1px solid rgba(38, 112, 183, 0.24); |
| | | background: rgba(255, 255, 255, 0.94); |
| | | color: #1f5ddf; |
| | | font-size: 13px; |
| | | font-weight: 600; |
| | | text-align: center; |
| | | padding: 0 4px; |
| | | } |
| | | |
| | | .period-unit { |
| | | font-size: 12px; |
| | | font-weight: 600; |
| | | color: rgba(40, 80, 117, 0.86); |
| | | } |
| | | |
| | | .footer-period-slider { |
| | | width: min(250px, 100%); |
| | | height: 3px; |
| | | accent-color: #2a6ded; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .footer-rail { |
| | | position: absolute; |
| | | left: 20px; |
| | | right: 20px; |
| | | bottom: 18px; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .footer-rail__line { |
| | | position: relative; |
| | | height: 2px; |
| | | border-radius: 999px; |
| | | background: linear-gradient(90deg, rgba(31, 122, 114, 0.12), rgba(30, 91, 255, 0.6), rgba(31, 122, 114, 0.12)); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .footer-rail__flow { |
| | | position: absolute; |
| | | top: -1px; |
| | | left: -18%; |
| | | width: 22%; |
| | | height: 4px; |
| | | border-radius: 999px; |
| | | background: linear-gradient(90deg, rgba(31, 122, 114, 0), rgba(59, 146, 244, 0.92), rgba(30, 91, 255, 0)); |
| | | filter: blur(0.2px); |
| | | animation: railFlow 3.1s ease-in-out infinite; |
| | | } |
| | | |
| | | .footer-rail__nodes { |
| | | margin-top: 12px; |
| | | display: grid; |
| | | grid-template-columns: repeat(5, minmax(0, 1fr)); |
| | | gap: 8px; |
| | | } |
| | | |
| | | .footer-node { |
| | | height: 34px; |
| | | border: 1px solid rgba(38, 112, 183, 0.18); |
| | | border-radius: 999px; |
| | | background: rgba(255, 255, 255, 0.76); |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | color: rgba(40, 80, 117, 0.92); |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | cursor: pointer; |
| | | transition: all 0.26s ease; |
| | | } |
| | | |
| | | .footer-node:hover { |
| | | transform: translateY(-1px); |
| | | border-color: rgba(31, 122, 114, 0.34); |
| | | box-shadow: 0 8px 14px rgba(31, 122, 114, 0.14); |
| | | } |
| | | |
| | | .footer-node--active { |
| | | color: #fff; |
| | | border-color: transparent; |
| | | background: linear-gradient(135deg, rgba(31, 122, 114, 0.94), rgba(30, 91, 255, 0.94)); |
| | | box-shadow: 0 10px 18px rgba(30, 91, 255, 0.28); |
| | | } |
| | | |
| | | .footer-node__dot { |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | background: rgba(30, 91, 255, 0.72); |
| | | box-shadow: 0 0 10px rgba(30, 91, 255, 0.52); |
| | | } |
| | | |
| | | .footer-node--active .footer-node__dot { |
| | | background: #fff; |
| | | box-shadow: 0 0 12px rgba(255, 255, 255, 0.72); |
| | | animation: nodePulse 1.4s ease-in-out infinite; |
| | | } |
| | | |
| | | .agent-card { |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 0; |
| | | width: 460px; |
| | | margin-left: -230px; |
| | | cursor: pointer; |
| | | transform-origin: center bottom; |
| | | transition: transform 0.35s ease, opacity 0.35s ease; |
| | | } |
| | | |
| | | .agent-card__head { |
| | | height: 56px; |
| | | border-radius: 12px 12px 0 0; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 28px; |
| | | color: #fff; |
| | | font-weight: 700; |
| | | background: linear-gradient(135deg, #1f7a72 0%, #1e5bff 100%); |
| | | } |
| | | |
| | | .agent-card__head--active { |
| | | box-shadow: |
| | | 0 12px 22px rgba(30, 91, 255, 0.26), |
| | | inset 0 0 0 1px rgba(255, 255, 255, 0.28); |
| | | position: relative; |
| | | } |
| | | |
| | | .agent-card__head--active::after { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 12px; |
| | | right: 12px; |
| | | bottom: 6px; |
| | | height: 3px; |
| | | border-radius: 999px; |
| | | background: linear-gradient(90deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.96), rgba(255, 255, 255, 0.22)); |
| | | } |
| | | |
| | | .agent-card__body { |
| | | position: relative; |
| | | height: 430px; |
| | | border: 1px solid var(--surface-border-strong); |
| | | border-top: none; |
| | | border-radius: 0 0 20px 20px; |
| | | background: rgba(255, 255, 255, 0.96); |
| | | overflow: hidden; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: flex-end; |
| | | isolation: isolate; |
| | | box-shadow: 0 12px 24px rgba(31, 49, 38, 0.1); |
| | | } |
| | | |
| | | .agent-card__body--active { |
| | | background: linear-gradient(180deg, rgba(248, 252, 251, 0.96), rgba(225, 241, 250, 0.9)); |
| | | border-color: rgba(31, 122, 114, 0.35); |
| | | } |
| | | |
| | | .agent-card__body--active::after { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | background: linear-gradient(108deg, transparent 28%, rgba(255, 255, 255, 0.34) 50%, transparent 72%); |
| | | transform: translateX(-125%); |
| | | animation: bodySweep 3.6s linear infinite; |
| | | pointer-events: none; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .avatar-shell { |
| | | position: relative; |
| | | width: 248px; |
| | | height: 430px; |
| | | display: flex; |
| | | align-items: flex-end; |
| | | justify-content: center; |
| | | --base-core: rgba(53, 143, 222, 0.4); |
| | | --base-ring: rgba(39, 122, 201, 0.62); |
| | | --base-glow: rgba(46, 133, 214, 0.28); |
| | | } |
| | | |
| | | .avatar-shell::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 50%; |
| | | bottom: -10px; |
| | | width: 268px; |
| | | height: 58px; |
| | | transform: translateX(-50%); |
| | | border-radius: 50%; |
| | | background: radial-gradient( |
| | | ellipse at center, |
| | | rgba(55, 140, 219, 0.22) 0%, |
| | | rgba(55, 140, 219, 0.11) 46%, |
| | | rgba(55, 140, 219, 0) 74% |
| | | ); |
| | | filter: blur(2.4px); |
| | | animation: baseGlow 4.6s ease-in-out infinite; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .avatar-shell::after { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 50%; |
| | | bottom: 0; |
| | | width: 248px; |
| | | height: 46px; |
| | | transform: translateX(-50%); |
| | | border-radius: 50%; |
| | | border: 2px solid var(--base-ring); |
| | | box-shadow: |
| | | inset 0 0 0 1px rgba(255, 255, 255, 0.64), |
| | | 0 0 24px var(--base-glow); |
| | | animation: basePulse 3.1s ease-in-out infinite; |
| | | z-index: 4; |
| | | } |
| | | |
| | | .avatar-base { |
| | | position: absolute; |
| | | left: 50%; |
| | | bottom: 2px; |
| | | width: 224px; |
| | | height: 38px; |
| | | transform: translateX(-50%); |
| | | z-index: 2; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .avatar-base::before { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | border-radius: 50%; |
| | | background: |
| | | radial-gradient( |
| | | ellipse at center, |
| | | rgba(255, 255, 255, 0.96) 0%, |
| | | rgba(255, 255, 255, 0.92) 36%, |
| | | var(--base-core) 68%, |
| | | rgba(38, 118, 195, 0.08) 100% |
| | | ); |
| | | box-shadow: |
| | | 0 0 30px var(--base-core), |
| | | 0 0 10px rgba(255, 255, 255, 0.34) inset; |
| | | z-index: 3; |
| | | } |
| | | |
| | | .avatar-base::after { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | width: 194px; |
| | | height: 194px; |
| | | transform: translate(-50%, -50%); |
| | | border-radius: 50%; |
| | | background: |
| | | conic-gradient( |
| | | from 180deg, |
| | | transparent 0deg, |
| | | var(--base-ring) 48deg, |
| | | transparent 112deg, |
| | | var(--base-ring) 208deg, |
| | | transparent 284deg, |
| | | rgba(33, 114, 191, 0.48) 332deg, |
| | | transparent 360deg |
| | | ); |
| | | -webkit-mask: radial-gradient(circle, transparent 61%, #000 62%, #000 68%, transparent 70%); |
| | | mask: radial-gradient(circle, transparent 61%, #000 62%, #000 68%, transparent 70%); |
| | | opacity: 0.62; |
| | | animation: baseRotate 10.5s linear infinite; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .avatar-shell--active { |
| | | --base-core: rgba(50, 141, 217, 0.52); |
| | | --base-ring: rgba(42, 127, 205, 0.76); |
| | | --base-glow: rgba(38, 130, 211, 0.38); |
| | | } |
| | | |
| | | .avatar-cut { |
| | | position: relative; |
| | | width: 220px; |
| | | height: 430px; |
| | | z-index: 6; |
| | | display: flex; |
| | | align-items: flex-end; |
| | | justify-content: center; |
| | | filter: saturate(1.04) drop-shadow(0 14px 18px rgba(24, 44, 66, 0.14)); |
| | | transform-origin: center 82%; |
| | | animation: avatarFloat 3.2s ease-in-out infinite; |
| | | } |
| | | |
| | | .avatar-cut__img { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: contain; |
| | | object-position: center bottom; |
| | | display: block; |
| | | } |
| | | |
| | | .agent-card--active .avatar-cut { |
| | | animation-duration: 2.6s; |
| | | } |
| | | |
| | | .highlight-list { |
| | | position: absolute; |
| | | right: 10px; |
| | | top: 14px; |
| | | display: grid; |
| | | gap: 8px; |
| | | width: 220px; |
| | | z-index: 16; |
| | | } |
| | | |
| | | .highlight-item { |
| | | border-radius: 10px; |
| | | padding: 8px 10px; |
| | | font-size: 18px; |
| | | line-height: 1.4; |
| | | color: #fff; |
| | | background: rgba(33, 49, 63, 0.92); |
| | | box-shadow: 0 8px 16px rgba(21, 30, 40, 0.22); |
| | | } |
| | | |
| | | .agent-card--active .highlight-item { |
| | | background: rgba(31, 122, 114, 0.9); |
| | | } |
| | | |
| | | .nav-btn { |
| | | position: absolute; |
| | | top: 212px; |
| | | z-index: 80; |
| | | width: 50px; |
| | | height: 50px; |
| | | border-radius: 50%; |
| | | border: none; |
| | | font-size: 30px; |
| | | color: var(--colorPrimary); |
| | | background: var(--surface-base); |
| | | box-shadow: 0 10px 20px rgba(31, 49, 38, 0.16); |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .nav-btn--left { |
| | | left: 14px; |
| | | } |
| | | |
| | | .nav-btn--right { |
| | | right: 14px; |
| | | } |
| | | |
| | | .ai-fullscreen { |
| | | position: fixed; |
| | | inset: 0; |
| | | z-index: 2100; |
| | | padding: 12px; |
| | | background: rgba(33, 49, 63, 0.24); |
| | | backdrop-filter: blur(2px); |
| | | } |
| | | |
| | | .ai-panel { |
| | | height: 100%; |
| | | border-radius: 22px; |
| | | border: 1px solid var(--surface-border); |
| | | background: linear-gradient(180deg, #f9fcfb 0%, #f0f5f2 100%); |
| | | display: grid; |
| | | grid-template-rows: 62px minmax(0, 1fr) 110px; |
| | | box-shadow: var(--shadow-md); |
| | | } |
| | | |
| | | .ai-panel__top { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 0 24px; |
| | | } |
| | | |
| | | .ai-brand { |
| | | font-size: 34px; |
| | | color: var(--text-primary); |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .ai-close { |
| | | width: 40px; |
| | | height: 40px; |
| | | border: none; |
| | | border-radius: 50%; |
| | | background: transparent; |
| | | font-size: 30px; |
| | | color: var(--text-secondary); |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .ai-panel__center { |
| | | padding: 8px 20px 10px; |
| | | display: grid; |
| | | grid-template-rows: 120px 290px minmax(0, 1fr); |
| | | gap: 10px; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .welcome-card { |
| | | border-radius: 14px; |
| | | background: linear-gradient(135deg, rgba(232, 244, 242, 0.95), rgba(230, 237, 250, 0.9)); |
| | | padding: 16px 18px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | border: 1px solid var(--surface-border); |
| | | } |
| | | |
| | | .welcome-card__text h3 { |
| | | margin: 0; |
| | | font-size: 28px; |
| | | color: var(--text-primary); |
| | | } |
| | | |
| | | .welcome-card__text p { |
| | | margin: 8px 0 0; |
| | | font-size: 20px; |
| | | color: var(--text-secondary); |
| | | } |
| | | |
| | | .mini-avatar { |
| | | width: 120px; |
| | | height: 120px; |
| | | border-radius: 14px; |
| | | border: 1px solid var(--surface-border); |
| | | background-color: #fff; |
| | | background-clip: border-box; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .mini-avatar__img { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: contain; |
| | | object-position: center bottom; |
| | | display: block; |
| | | } |
| | | |
| | | .recommend-card { |
| | | border-radius: 14px; |
| | | border: 1px solid var(--surface-border); |
| | | background: rgba(255, 255, 255, 0.86); |
| | | padding: 12px 14px; |
| | | } |
| | | |
| | | .recommend-card__head { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | color: var(--text-primary); |
| | | font-size: 24px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .refresh-btn { |
| | | border: none; |
| | | background: transparent; |
| | | color: var(--text-secondary); |
| | | font-size: 18px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .recommend-grid { |
| | | display: grid; |
| | | grid-template-columns: 1fr 1fr; |
| | | gap: 8px 18px; |
| | | } |
| | | |
| | | .recommend-item { |
| | | border: 1px solid var(--surface-border); |
| | | border-radius: 8px; |
| | | text-align: left; |
| | | padding: 8px 10px; |
| | | font-size: 18px; |
| | | color: var(--text-secondary); |
| | | background: #fff; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .recommend-item:hover { |
| | | background: rgba(31, 122, 114, 0.08); |
| | | color: var(--colorPrimary); |
| | | } |
| | | |
| | | .chat-card { |
| | | border-radius: 14px; |
| | | border: 1px solid var(--surface-border); |
| | | background: #fff; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .chat-empty { |
| | | height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--text-tertiary); |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .chat-messages { |
| | | height: 100%; |
| | | overflow-y: auto; |
| | | padding: 14px; |
| | | display: grid; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .chat-row { |
| | | display: flex; |
| | | } |
| | | |
| | | .chat-row--assistant { |
| | | justify-content: flex-start; |
| | | } |
| | | |
| | | .chat-row--user { |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .chat-bubble { |
| | | max-width: 72%; |
| | | border-radius: 12px; |
| | | padding: 10px 12px; |
| | | font-size: 18px; |
| | | line-height: 1.5; |
| | | white-space: pre-wrap; |
| | | color: var(--text-primary); |
| | | background: var(--surface-soft); |
| | | border: 1px solid var(--surface-border); |
| | | } |
| | | |
| | | .chat-row--user .chat-bubble { |
| | | color: #fff; |
| | | background: linear-gradient(135deg, #1f7a72 0%, #1e5bff 100%); |
| | | border: none; |
| | | } |
| | | |
| | | .ai-panel__input { |
| | | display: grid; |
| | | grid-template-columns: minmax(0, 1fr) 130px; |
| | | gap: 12px; |
| | | padding: 14px 20px 18px; |
| | | } |
| | | |
| | | .ask-input :deep(.el-input__wrapper) { |
| | | height: 74px; |
| | | border-radius: 18px; |
| | | box-shadow: 0 0 0 1px var(--surface-border) inset; |
| | | background: #fff; |
| | | } |
| | | |
| | | .ask-input :deep(.el-input__inner) { |
| | | font-size: 20px; |
| | | } |
| | | |
| | | .send-btn { |
| | | align-self: center; |
| | | height: 56px; |
| | | font-size: 20px; |
| | | min-width: 98px; |
| | | } |
| | | |
| | | .fade-enter-active, |
| | | .fade-leave-active { |
| | | transition: opacity 0.2s ease; |
| | | } |
| | | |
| | | .fade-enter-from, |
| | | .fade-leave-to { |
| | | opacity: 0; |
| | | } |
| | | |
| | | @keyframes avatarFloat { |
| | | 0%, |
| | | 100% { |
| | | transform: translateY(0); |
| | | } |
| | | 50% { |
| | | transform: translateY(-8px); |
| | | } |
| | | } |
| | | |
| | | @keyframes basePulse { |
| | | 0%, |
| | | 100% { |
| | | transform: translateX(-50%) scale(1); |
| | | opacity: 0.88; |
| | | } |
| | | 50% { |
| | | transform: translateX(-50%) scale(1.045); |
| | | opacity: 0.95; |
| | | } |
| | | } |
| | | |
| | | @keyframes baseRotate { |
| | | from { |
| | | transform: translate(-50%, -50%) rotate(0deg); |
| | | } |
| | | to { |
| | | transform: translate(-50%, -50%) rotate(360deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes baseGlow { |
| | | 0%, |
| | | 100% { |
| | | transform: translateX(-50%) scaleX(1); |
| | | opacity: 0.84; |
| | | } |
| | | 50% { |
| | | transform: translateX(-50%) scaleX(1.06); |
| | | opacity: 0.96; |
| | | } |
| | | } |
| | | |
| | | @keyframes bodySweep { |
| | | 0% { |
| | | transform: translateX(-125%); |
| | | } |
| | | 100% { |
| | | transform: translateX(135%); |
| | | } |
| | | } |
| | | |
| | | @keyframes footerSweep { |
| | | 0% { |
| | | transform: translateX(-120%); |
| | | } |
| | | 100% { |
| | | transform: translateX(140%); |
| | | } |
| | | } |
| | | |
| | | @keyframes railFlow { |
| | | 0% { |
| | | transform: translateX(0); |
| | | opacity: 0; |
| | | } |
| | | 20% { |
| | | opacity: 1; |
| | | } |
| | | 80% { |
| | | opacity: 1; |
| | | } |
| | | 100% { |
| | | transform: translateX(520%); |
| | | opacity: 0; |
| | | } |
| | | } |
| | | |
| | | @keyframes nodePulse { |
| | | 0%, |
| | | 100% { |
| | | transform: scale(1); |
| | | } |
| | | 50% { |
| | | transform: scale(1.25); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 1600px) { |
| | | .head-title { |
| | | font-size: 34px; |
| | | height: 60px; |
| | | } |
| | | |
| | | .brain-intro h2 { |
| | | font-size: 36px; |
| | | } |
| | | |
| | | .brain-intro p { |
| | | font-size: 22px; |
| | | } |
| | | |
| | | .intro-sign { |
| | | font-size: 20px; |
| | | } |
| | | |
| | | .agent-card { |
| | | width: 380px; |
| | | margin-left: -190px; |
| | | } |
| | | |
| | | .agent-card__head { |
| | | font-size: 24px; |
| | | height: 54px; |
| | | } |
| | | |
| | | .agent-card__body { |
| | | height: 390px; |
| | | } |
| | | |
| | | .highlight-list { |
| | | width: 184px; |
| | | } |
| | | |
| | | .highlight-item { |
| | | font-size: 15px; |
| | | } |
| | | |
| | | .avatar-shell { |
| | | width: 220px; |
| | | height: 390px; |
| | | } |
| | | |
| | | .avatar-cut { |
| | | width: 202px; |
| | | height: 390px; |
| | | } |
| | | |
| | | .avatar-base { |
| | | width: 194px; |
| | | height: 34px; |
| | | } |
| | | |
| | | .avatar-base::after { |
| | | width: 164px; |
| | | height: 164px; |
| | | } |
| | | |
| | | .avatar-shell::before { |
| | | width: 236px; |
| | | height: 48px; |
| | | bottom: -9px; |
| | | } |
| | | |
| | | .avatar-shell::after { |
| | | width: 220px; |
| | | height: 40px; |
| | | bottom: 0; |
| | | } |
| | | |
| | | .brain-footer { |
| | | margin: 0 52px; |
| | | height: clamp(210px, 23vh, 264px); |
| | | } |
| | | |
| | | .footer-metrics { |
| | | padding: 12px 14px 66px; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .footer-metric { |
| | | min-height: 66px; |
| | | padding: 8px 10px; |
| | | } |
| | | |
| | | .footer-metric--period { |
| | | min-height: 108px; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .footer-metric__label { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .footer-metric__value { |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .footer-metric__hint { |
| | | font-size: 11px; |
| | | } |
| | | |
| | | .footer-period-control { |
| | | gap: 6px; |
| | | } |
| | | |
| | | .period-btn { |
| | | width: 20px; |
| | | height: 20px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .period-input { |
| | | width: 50px; |
| | | height: 22px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .footer-period-slider { |
| | | width: 100%; |
| | | } |
| | | |
| | | .footer-rail { |
| | | left: 14px; |
| | | right: 14px; |
| | | bottom: 14px; |
| | | } |
| | | |
| | | .footer-rail__nodes { |
| | | margin-top: 10px; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .footer-node { |
| | | height: 30px; |
| | | font-size: 12px; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .footer-node__dot { |
| | | width: 7px; |
| | | height: 7px; |
| | | } |
| | | |
| | | .ai-brand { |
| | | font-size: 28px; |
| | | } |
| | | |
| | | .welcome-card__text h3, |
| | | .recommend-card__head { |
| | | font-size: 22px; |
| | | } |
| | | |
| | | .welcome-card__text p, |
| | | .recommend-item, |
| | | .chat-bubble, |
| | | .refresh-btn, |
| | | .chat-empty, |
| | | .ask-input :deep(.el-input__inner), |
| | | .send-btn { |
| | | font-size: 16px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div class="search_form" style="margin-bottom: 20px;"> |
| | | <div> |
| | | <span class="search_title">客æ·åç§°ï¼</span> |
| | | <el-input v-model="searchForm.customerName" |
| | |
| | | <div> |
| | | <el-button type="primary" |
| | | @click="openForm('add')">æ°å¢å®¢æ·</el-button> |
| | | <el-button type="primary" |
| | | plain |
| | | @click="back">æµå
¥å
¬æµ·</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="info" |
| | | plain |
| | |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :action="upload.url" |
| | | :data="upload.data" |
| | | :disabled="upload.isUploading" |
| | | :before-upload="upload.beforeUpload" |
| | | :on-progress="upload.onProgress" |
| | |
| | | import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue"; |
| | | import { Search, Paperclip, Upload } from "@element-plus/icons-vue"; |
| | | import { |
| | | addCustomerPrivate, |
| | | delCustomerPrivate, |
| | | getCustomer, |
| | | getCustomerPrivatePoolById, |
| | | getCustomerPrivatePoolInfo, |
| | | listCustomerPrivatePool, |
| | | updateCustomerPrivatePool, |
| | | addCustomerFollow, |
| | | updateCustomerFollow, |
| | | delCustomerFollow, |
| | | addReturnVisit, |
| | | getReturnVisit, |
| | | } from "@/api/basicData/customerFile.js"; |
| | | import {listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer, backCustomer} from "@/api/basicData/customer.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | |
| | | const negotiationFormRef = ref(); |
| | | const negotiationForm = reactive({ |
| | | customerName: "", |
| | | customerPrivatePoolId: "", |
| | | customerId: "", |
| | | followUpMethod: "", |
| | | followUpLevel: "", |
| | | followUpTime: "", |
| | |
| | | searchForm: { |
| | | customerName: "", |
| | | customerType: "", |
| | | type: 0 |
| | | }, |
| | | form: { |
| | | customerName: "", |
| | |
| | | contactPhone: "", |
| | | contactPosition: "", |
| | | customerType: "", |
| | | type: 0 |
| | | }, |
| | | rules: { |
| | | customerName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/customerPrivate/importData", |
| | | url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData", |
| | | data: { |
| | | type: 0 |
| | | }, |
| | | // æä»¶ä¸ä¼ åçåè° |
| | | beforeUpload: file => { |
| | | console.log("æä»¶å³å°ä¸ä¼ ", file); |
| | |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listCustomerPrivatePool({ ...searchForm.value, ...page }).then(res => { |
| | | listCustomer({ ...searchForm.value, ...page }).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | |
| | | } |
| | | /** ä¸è½½æ¨¡æ¿ */ |
| | | function importTemplate() { |
| | | proxy.download("/customerPrivate/downloadTemplate", {}, "客æ·å¯¼å
¥æ¨¡æ¿.xlsx"); |
| | | proxy.download("/basic/customer/downloadTemplate", {}, "客æ·å¯¼å
¥æ¨¡æ¿.xlsx"); |
| | | } |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | |
| | | contactPosition: "", |
| | | }, |
| | | ]; |
| | | form.value.type = 0; |
| | | form.value.maintenanceTime = getCurrentDate(); |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | if (type === "edit") { |
| | | getCustomerPrivatePoolById(row.id).then(res => { |
| | | getCustomer(row.id).then(res => { |
| | | form.value = { ...res.data }; |
| | | const persons = String(res.data.contactPerson || "").split(","); |
| | | const phones = String(res.data.contactPhone || "").split(","); |
| | |
| | | form.value.contactPhone = formYYs.value.contactList |
| | | .map(item => item.contactPhone) |
| | | .join(","); |
| | | addCustomer(form.value).then(res => { |
| | | form.value.contactPosition = formYYs.value.contactList |
| | | .map(item => item.contactPosition || "") |
| | | .join(","); |
| | |
| | | form.value.contactPosition = formYYs.value.contactList |
| | | .map(item => item.contactPosition || "") |
| | | .join(","); |
| | | updateCustomerPrivatePool(form.value).then(res => { |
| | | updateCustomer(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/customerPrivate/export", {}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | proxy.download("/basic/customer/export", {type: 0}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delCustomerPrivate(ids) |
| | | delCustomer(ids) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | const back = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | ElMessageBox.confirm("éä¸ç客æ·å°æµå
¥å
¬æµ·ï¼æ¯å¦ç¡®è®¤ï¼", "æµå
¥å
¬æµ·æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | return Promise.all(ids.map(id => backCustomer(id))) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("æµå
¥å
¬æµ·æå"); |
| | | selectedRows.value = []; |
| | | getList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }) |
| | | .catch(error => { |
| | | if (error === "cancel" || error === "close") { |
| | | proxy.$modal.msg("已忶"); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | |
| | | if (reminderForm.id) { |
| | | submitvalue.value = { |
| | | id: reminderForm.id, |
| | | customerPrivatePoolId: reminderForm.id, |
| | | customerPrivatePoolId: currentCustomerId.value, |
| | | customerId: currentCustomerId.value, |
| | | isEnabled: reminderForm.reminderSwitch ? 1 : 0, |
| | | content: reminderForm.reminderContent, |
| | | reminderTime: reminderForm.reminderTime, |
| | |
| | | }; |
| | | } else { |
| | | submitvalue.value = { |
| | | customerPrivatePoolId: currentCustomerId.value, |
| | | customerId: currentCustomerId.value, |
| | | isEnabled: reminderForm.reminderSwitch ? 1 : 0, |
| | | content: reminderForm.reminderContent, |
| | | reminderTime: reminderForm.reminderTime, |
| | |
| | | // æå¼æ´½è°è¿åº¦å¼¹çª |
| | | const openNegotiationDialog = row => { |
| | | negotiationForm.customerName = row.customerName; |
| | | negotiationForm.customerPrivatePoolId = row.id; |
| | | negotiationForm.customerId = row.id; |
| | | negotiationForm.followUpMethod = ""; |
| | | negotiationForm.followUpLevel = ""; |
| | | negotiationForm.followUpTime = ""; |
| | |
| | | // ä¿®æ¹æä½ |
| | | updateCustomerFollow(negotiationForm).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | getCustomer(negotiationForm.customerPrivatePoolId).then(res => { |
| | | getCustomer(negotiationForm.customerId).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | negotiationRecords.value = res.data.followUpList || []; |
| | | }); |
| | |
| | | |
| | | // æå¼è¯¦æ
å¼¹çª |
| | | const openDetailDialog = row => { |
| | | getCustomerPrivatePoolInfo(row.id).then(res => { |
| | | getCustomer(row.id).then(res => { |
| | | // å¡«å
客æ·åºæ¬ä¿¡æ¯ |
| | | Object.assign(detailForm, res.data); |
| | | |
| | |
| | | // å°å½åè®°å½æ°æ®å¡«å
å°è¡¨å |
| | | Object.assign(negotiationForm, { |
| | | customerName: row.customerName, |
| | | customerPrivatePoolId: row.customerPrivatePoolId, |
| | | customerId: row.customerId, |
| | | followUpMethod: row.followUpMethod, |
| | | followUpLevel: row.followUpLevel, |
| | | followUpTime: row.followUpTime, |
| | |
| | | // }); |
| | | delCustomerFollow(row.id).then(() => { |
| | | // å 餿ååæ´æ°æ¬å°æ°æ® |
| | | getCustomer(row.customerPrivatePoolId).then(res => { |
| | | getCustomer(row.customerId).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | negotiationRecords.value = res.data.followUpList || []; |
| | | }); |
| | |
| | | const downloadAttachment = row => { |
| | | if (row.url) { |
| | | // proxy.download(row.url, {}, row.name); |
| | | proxy.$download.name(row.url); |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | } else { |
| | | proxy.$modal.msgError("ä¸è½½é¾æ¥ä¸åå¨"); |
| | | } |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div class="search_form" style="margin-bottom: 20px;"> |
| | | <div> |
| | | <span class="search_title">客æ·åç§°ï¼</span> |
| | | <el-input v-model="searchForm.customerName" |
| | |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :action="upload.url" |
| | | :data="upload.data" |
| | | :disabled="upload.isUploading" |
| | | :before-upload="upload.beforeUpload" |
| | | :on-progress="upload.onProgress" |
| | |
| | | import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue"; |
| | | import { Search, Paperclip, Upload } from "@element-plus/icons-vue"; |
| | | import { |
| | | addCustomer, |
| | | addCustomerPrivatePool, |
| | | delCustomerPrivatePool, |
| | | delCustomer, |
| | | getCustomer, |
| | | shareCustomer, |
| | | listCustomer, |
| | | updateCustomer, |
| | | addCustomerFollow, |
| | | updateCustomerFollow, |
| | | delCustomerFollow, |
| | | addReturnVisit, |
| | | getReturnVisit, |
| | | } from "@/api/basicData/customerFile.js"; |
| | | import { |
| | | listCustomer, |
| | | addCustomer, |
| | | delCustomer, |
| | | updateCustomer, |
| | | getCustomer, |
| | | assignCustomer, |
| | | recycleCustomer, |
| | | shareCustomer, |
| | | } from "@/api/basicData/customer.js"; |
| | | |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | |
| | | type: "text", |
| | | showHide: row => row.usageStatus == 1, |
| | | clickFun: row => { |
| | | recycleCustomer(row); |
| | | recycle(row); |
| | | }, |
| | | }, |
| | | { |
| | |
| | | searchForm: { |
| | | customerName: "", |
| | | customerType: "", |
| | | type: 1 |
| | | }, |
| | | form: { |
| | | customerName: "", |
| | |
| | | contactPhone: "", |
| | | contactPosition: "", |
| | | customerType: "", |
| | | type: 1 |
| | | }, |
| | | rules: { |
| | | customerName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData", |
| | | data: { |
| | | type: 1 |
| | | }, |
| | | // æä»¶ä¸ä¼ åçåè° |
| | | beforeUpload: file => { |
| | | console.log("æä»¶å³å°ä¸ä¼ ", file); |
| | |
| | | const openShareDialog = row => { |
| | | shareForm.id = row.id; |
| | | shareForm.customerName = row.customerName; |
| | | shareForm.boundIds = []; |
| | | shareForm.boundIds = row.userIds || []; |
| | | ensureUserList().then(() => { |
| | | shareDialogVisible.value = true; |
| | | }); |
| | |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | addCustomerPrivatePool({ |
| | | customerId: assignForm.id, |
| | | boundId: assignForm.boundId, |
| | | assignCustomer({ |
| | | id: assignForm.id, |
| | | usageUser: assignForm.boundId, |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("åé
æå"); |
| | | closeAssignDialog(); |
| | |
| | | return; |
| | | } |
| | | shareCustomer({ |
| | | customerId: shareForm.id, |
| | | boundIds: shareForm.boundIds, |
| | | id: shareForm.id, |
| | | userIds: shareForm.boundIds, |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("å
±äº«æå"); |
| | | closeShareDialog(); |
| | |
| | | }); |
| | | }); |
| | | }; |
| | | const recycleCustomer = row => { |
| | | const recycle = row => { |
| | | ElMessageBox.confirm("ç¡®è®¤åæ¶å®¢æ·â" + row.customerName + "âåï¼", "åæ¶æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | return delCustomerPrivatePool(row.id); |
| | | }) |
| | | .then(() => { |
| | | return recycleCustomer({id: row.id}).then(() => { |
| | | proxy.$modal.msgSuccess("åæ¶æå"); |
| | | getList(); |
| | | }) |
| | | }) |
| | | .catch(error => { |
| | | if (error === "cancel" || error === "close") { |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/basic/customer/export", {}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | proxy.download("/basic/customer/export", {type: 1}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | |
| | | const downloadAttachment = row => { |
| | | if (row.url) { |
| | | // proxy.download(row.url, {}, row.name); |
| | | proxy.$download.name(row.url); |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | } else { |
| | | proxy.$modal.msgError("ä¸è½½é¾æ¥ä¸åå¨"); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title ml10">åæ°åç§°ï¼</span> |
| | | <el-input v-model="searchForm.paramName" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥åæ°åç§°" |
| | | clearable /> |
| | | <!-- å
³è产åç±»åæç´¢ --> |
| | | <!-- <span class="search_title ml10">å
³è产åç±»åï¼</span> |
| | | <el-input v-model="searchForm.productName" |
| | | style="width: 200px"d |
| | | placeholder="请è¾å
¥å
³è产åç±»å" |
| | | clearable /> --> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | | <el-button type="primary" |
| | | @click="handleAdd" |
| | | style="margin-left: 10px">æ°å¢åæ°</el-button> |
| | | <!-- 产åç±»åç»´æ¤æé® --> |
| | | <!-- <el-button type="primary" |
| | | @click="handleProductTypeMaintenance" |
| | | style="margin-left: 10px">产åç±»åç»´æ¤</el-button> --> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="paramName" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | height="calc(100vh - 320px)" |
| | | :tableLoading="tableLoading" |
| | | :isSelection="false" |
| | | :isShowPagination="true" |
| | | @pagination="pagination"> |
| | | </PIMTable> |
| | | </div> |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="500px"> |
| | | <el-form :model="formData" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | label-width="120px"> |
| | | <el-form-item label="åæ°ç¼ç " |
| | | prop="paramCode"> |
| | | <el-input v-model="formData.paramCode" |
| | | disabled |
| | | placeholder="èªå¨çæ" /> |
| | | </el-form-item> |
| | | <el-form-item label="åæ°åç§°" |
| | | prop="paramName"> |
| | | <el-input v-model="formData.paramName" |
| | | placeholder="请è¾å
¥åæ°åç§°" /> |
| | | </el-form-item> |
| | | <el-form-item label="åæ°ç±»å" |
| | | prop="paramType"> |
| | | <el-select v-model="formData.paramType" |
| | | @change="handleParamTypeChange" |
| | | placeholder="è¯·éæ©åæ°ç±»å"> |
| | | <el-option label="æ°å¼æ ¼å¼" |
| | | :value="1" /> |
| | | <el-option label="ææ¬æ ¼å¼" |
| | | :value="2" /> |
| | | <el-option label="䏿é项" |
| | | :value="3" /> |
| | | <el-option label="æ¶é´æ ¼å¼" |
| | | :value="4" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <!-- <el-form-item label="å弿¨¡å¼" |
| | | prop="valueMode"> |
| | | <el-select v-model="formData.valueMode" |
| | | placeholder="è¯·éæ©å弿¨¡å¼"> |
| | | <el-option label="åå¼" |
| | | value="1" /> |
| | | <el-option label="åºé´" |
| | | value="2" /> |
| | | </el-select> |
| | | </el-form-item> --> |
| | | <el-form-item label="åä½" |
| | | prop="unit"> |
| | | <el-input v-model="formData.unit" |
| | | placeholder="请è¾å
¥åä½" /> |
| | | </el-form-item> |
| | | <el-form-item label="å弿 ¼å¼" |
| | | v-if="formData.paramType == 1 || formData.paramType == 2" |
| | | prop="paramFormat"> |
| | | <el-input v-model="formData.paramFormat" |
| | | placeholder="请è¾å
¥å弿 ¼å¼" /> |
| | | <!-- <el-select v-model="formData.paramFormat" |
| | | placeholder="è¯·éæ©å弿¨¡å¼"> |
| | | <el-option label="#.00000" |
| | | value="#.00000" /> |
| | | <el-option label="#.0000" |
| | | value="#.0000" /> |
| | | <el-option label="#.000" |
| | | value="#.000" /> |
| | | <el-option label="#.00" |
| | | value="#.00" /> |
| | | </el-select> --> |
| | | </el-form-item> |
| | | <el-form-item label="䏿åå
¸" |
| | | v-else-if="formData.paramType == 3" |
| | | prop="paramFormat"> |
| | | <el-select v-model="formData.paramFormat" |
| | | placeholder="è¯·éæ©å弿¨¡å¼"> |
| | | <el-option v-for="item in dictTypes" |
| | | :key="item.dictType" |
| | | :label="item.dictName" |
| | | :value="item.dictType" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¶é´æ ¼å¼" |
| | | v-else-if="formData.paramType == 4" |
| | | prop="paramFormat"> |
| | | <el-select v-model="formData.paramFormat" |
| | | placeholder="è¯·éæ©å弿¨¡å¼"> |
| | | <el-option label="YYYY-MM-DD" |
| | | value="YYYY-MM-DD" /> |
| | | <el-option label="YYYY-MM-DD HH:mm:ss" |
| | | value="YYYY-MM-DD HH:mm:ss" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯å¦å¿
å¡«" |
| | | prop="isRequired"> |
| | | <el-switch v-model="formData.isRequired" |
| | | :active-value="1" |
| | | :inactive-value="0" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" |
| | | prop="remark"> |
| | | <el-input v-model="formData.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 产åç±»åç»´æ¤å¯¹è¯æ¡ --> |
| | | <!-- <el-dialog v-model="productTypeDialogVisible" |
| | | title="产åç±»åç»´æ¤" |
| | | width="600px"> |
| | | <div class="product-type-header"> |
| | | <el-button type="primary" |
| | | @click="handleAddProductType">æ°å¢äº§åç±»å</el-button> |
| | | </div> |
| | | <el-table :data="productTypeList" |
| | | border |
| | | style="width: 100%; margin-top: 10px; margin-bottom: 20px"> |
| | | <el-table-column prop="typeCode" |
| | | label="ç±»åç¼ç " |
| | | width="150" /> |
| | | <el-table-column prop="typeName" |
| | | label="ç±»ååç§°" /> |
| | | <el-table-column label="æä½" |
| | | width="150"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | type="primary" |
| | | @click="handleEditProductType(scope.row)">ç¼è¾</el-button> |
| | | <el-button link |
| | | type="danger" |
| | | @click="handleDeleteProductType(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> --> |
| | | <!-- æ°å¢/ç¼è¾äº§åç±»åå¯¹è¯æ¡ --> |
| | | <!-- <el-dialog v-model="productTypeFormVisible" |
| | | :title="productTypeDialogTitle" |
| | | width="400px"> |
| | | <el-form :model="productTypeForm" |
| | | :rules="productTypeRules" |
| | | ref="productTypeFormRef" |
| | | label-width="100px"> |
| | | <el-form-item label="ç±»åç¼ç " |
| | | prop="typeCode"> |
| | | <el-input v-model="productTypeForm.typeCode" |
| | | placeholder="请è¾å
¥ç±»åç¼ç " /> |
| | | </el-form-item> |
| | | <el-form-item label="ç±»ååç§°" |
| | | prop="typeName"> |
| | | <el-input v-model="productTypeForm.typeName" |
| | | placeholder="请è¾å
¥ç±»ååç§°" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="productTypeFormVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="handleProductTypeSubmit">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> --> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref, reactive } from "vue"; |
| | | import { |
| | | parameterListPage, |
| | | addParameter, |
| | | updateParameter, |
| | | delParameter, |
| | | addBaseParam, |
| | | editBaseParam, |
| | | getBaseParamList, |
| | | removeBaseParam, |
| | | // getProductTypes as getProductTypesApi, |
| | | } from "@/api/basicData/parameterMaintenance.js"; |
| | | import { listType } from "@/api/system/dict/type"; |
| | | import { deptTreeSelect } from "@/api/system/user.js"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "åæ°ç¼ç ", |
| | | prop: "paramCode", |
| | | }, |
| | | { |
| | | label: "åæ°åç§°", |
| | | prop: "paramName", |
| | | }, |
| | | { |
| | | label: "åæ°ç±»å", |
| | | prop: "paramType", |
| | | dataType: "tag", |
| | | formatType: params => { |
| | | const typeMap = { |
| | | 1: "primary", |
| | | 2: "info", |
| | | 3: "warning", |
| | | 4: "success", |
| | | }; |
| | | return typeMap[params] || "default"; |
| | | }, |
| | | formatData: val => { |
| | | const labelMap = { |
| | | 1: "æ°å¼æ ¼å¼", |
| | | 2: "ææ¬æ ¼å¼", |
| | | 3: "䏿é项", |
| | | 4: "æ¶é´æ ¼å¼", |
| | | }; |
| | | return labelMap[val] || val; |
| | | }, |
| | | }, |
| | | // { |
| | | // label: "å弿¨¡å¼", |
| | | // prop: "valueMode", |
| | | // dataType: "tag", |
| | | // formatType: params => { |
| | | // return params === 2 ? "warning" : "success"; |
| | | // }, |
| | | // formatData: val => { |
| | | // return val === 2 ? "åºé´" : "åå¼"; |
| | | // }, |
| | | // }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "å弿 ¼å¼", |
| | | prop: "paramFormat", |
| | | }, |
| | | { |
| | | label: "æ¯å¦å¿
å¡«", |
| | | prop: "isRequired", |
| | | dataType: "tag", |
| | | formatType: val => { |
| | | return val === 1 ? "success" : "info"; |
| | | }, |
| | | formatData: val => { |
| | | return val === 1 ? "æ¯" : "å¦"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "夿³¨", |
| | | prop: "remark", |
| | | }, |
| | | { |
| | | label: "å建æ¶é´", |
| | | prop: "createTime", |
| | | }, |
| | | { |
| | | label: "æä½", |
| | | dataType: "action", |
| | | width: "150", |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | clickFun: row => { |
| | | handleEdit(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å é¤", |
| | | clickFun: row => { |
| | | handleDelete(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | paramName: "", |
| | | productName: "", |
| | | }); |
| | | |
| | | // å¯¹è¯æ¡ç¸å
³ |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const formData = reactive({ |
| | | id: null, |
| | | paramCode: "", |
| | | paramName: "", |
| | | paramType: "", |
| | | // valueMode: "1", |
| | | unit: "", |
| | | remark: "", |
| | | isRequired: 0, |
| | | paramFormat: "", |
| | | }); |
| | | const rules = reactive({ |
| | | paramName: [{ required: true, message: "请è¾å
¥åæ°åç§°", trigger: "blur" }], |
| | | paramType: [{ required: true, message: "è¯·éæ©åæ°ç±»å", trigger: "change" }], |
| | | // valueMode: [{ required: true, message: "è¯·éæ©å弿¨¡å¼", trigger: "change" }], |
| | | unit: [ |
| | | { |
| | | required: false, |
| | | message: "请è¾å
¥åä½", |
| | | trigger: "blur", |
| | | validator: (rule, value, callback) => { |
| | | if (formData.paramType === 1 && !value) { |
| | | callback(new Error("æ°å¼ç±»åå¿
须填ååä½")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | }, |
| | | ], |
| | | }); |
| | | // const productTypes = ref([]); |
| | | const isEdit = ref(false); |
| | | |
| | | // 产åç±»åç»´æ¤ç¸å
³ - 已注é |
| | | // const productTypeDialogVisible = ref(false); |
| | | // const productTypeFormVisible = ref(false); |
| | | // const productTypeDialogTitle = ref(""); |
| | | // const productTypeFormRef = ref(null); |
| | | // const productTypeList = ref([]); |
| | | // const productTypeForm = reactive({ |
| | | // id: null, |
| | | // typeCode: "", |
| | | // typeName: "", |
| | | // }); |
| | | // const productTypeRules = reactive({ |
| | | // typeCode: [{ required: true, message: "请è¾å
¥ç±»åç¼ç ", trigger: "blur" }], |
| | | // typeName: [{ required: true, message: "请è¾å
¥ç±»ååç§°", trigger: "blur" }], |
| | | // }); |
| | | // const isProductTypeEdit = ref(false); |
| | | const handleParamTypeChange = () => { |
| | | if (formData.paramType === 1) { |
| | | formData.paramFormat = "#.00000"; |
| | | } else if (formData.paramType === 4) { |
| | | formData.paramFormat = "YYYY-MM-DD HH:mm:ss"; |
| | | } else { |
| | | formData.paramFormat = ""; |
| | | } |
| | | // 触ååä½å段éªè¯ |
| | | if (formRef.value) { |
| | | formRef.value.validateField("unit"); |
| | | } |
| | | }; |
| | | // 产åç±»åç»´æ¤æé®ç¹å»äºä»¶ - 已注é |
| | | // const handleProductTypeMaintenance = () => { |
| | | // productTypeDialogVisible.value = true; |
| | | // getProductTypeList(); |
| | | // }; |
| | | |
| | | // è·å产åç±»åå表 - 已注é |
| | | // const getProductTypeList = () => { |
| | | // productTypeList.value = [ |
| | | // { id: 1, typeCode: "TYPE001", typeName: "3.5ç å" }, |
| | | // { id: 2, typeCode: "TYPE002", typeName: "5.0ç å" }, |
| | | // { id: 3, typeCode: "TYPE003", typeName: "æ¿æ" }, |
| | | // ]; |
| | | // }; |
| | | |
| | | // æ°å¢äº§åç±»å - 已注é |
| | | // const handleAddProductType = () => { |
| | | // isProductTypeEdit.value = false; |
| | | // productTypeDialogTitle.value = "æ°å¢äº§åç±»å"; |
| | | // productTypeForm.id = null; |
| | | // productTypeForm.typeCode = ""; |
| | | // productTypeForm.typeName = ""; |
| | | // productTypeFormVisible.value = true; |
| | | // }; |
| | | |
| | | // ç¼è¾äº§åç±»å - 已注é |
| | | // const handleEditProductType = row => { |
| | | // isProductTypeEdit.value = true; |
| | | // productTypeDialogTitle.value = "ç¼è¾äº§åç±»å"; |
| | | // productTypeForm.id = row.id; |
| | | // productTypeForm.typeCode = row.typeCode; |
| | | // productTypeForm.typeName = row.typeName; |
| | | // productTypeFormVisible.value = true; |
| | | // }; |
| | | |
| | | // å é¤äº§åç±»å - 已注é |
| | | // const handleDeleteProductType = row => { |
| | | // ElMessageBox.confirm("ç¡®å®è¦å é¤è¯¥äº§åç±»ååï¼", "æç¤º", { |
| | | // confirmButtonText: "ç¡®å®", |
| | | // cancelButtonText: "åæ¶", |
| | | // type: "warning", |
| | | // }) |
| | | // .then(() => { |
| | | // ElMessage.success("å 餿å"); |
| | | // getProductTypeList(); |
| | | // }) |
| | | // .catch(() => {}); |
| | | // }; |
| | | |
| | | // æäº¤äº§åç±»å表å - 已注é |
| | | // const handleProductTypeSubmit = () => { |
| | | // productTypeFormRef.value.validate(valid => { |
| | | // if (valid) { |
| | | // ElMessage.success(isProductTypeEdit.value ? "ç¼è¾æå" : "æ°å¢æå"); |
| | | // productTypeFormVisible.value = false; |
| | | // getProductTypeList(); |
| | | // } |
| | | // }); |
| | | // }; |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | /** éç½®æé®æä½ */ |
| | | const handleReset = () => { |
| | | searchForm.paramName = ""; |
| | | searchForm.productName = ""; |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | // è°ç¨æ°æ¥å£ /baseParam/list |
| | | getBaseParamList({ |
| | | paramName: searchForm.paramName, |
| | | current: page.current, |
| | | size: page.size, |
| | | }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | if (res.code === 200) { |
| | | tableData.value = res.data.records || []; |
| | | page.total = res.data.total || 0; |
| | | console.log(tableData.value, "tableData.value"); |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | ElMessage.error("æ¥è¯¢å¤±è´¥"); |
| | | }); |
| | | }; |
| | | |
| | | // è·å产åç±»åå表 - 已注é |
| | | // const getProductTypes = () => { |
| | | // productTypes.value = [ |
| | | // { label: "3.5ç å", value: "type1" }, |
| | | // { label: "5.0ç å", value: "type2" }, |
| | | // { label: "æ¿æ", value: "type3" }, |
| | | // ]; |
| | | // }; |
| | | |
| | | // æ°å¢æé®ç¹å»äºä»¶ |
| | | const handleAdd = () => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ°å¢åæ°"; |
| | | // é置表å |
| | | formData.id = null; |
| | | formData.paramCode = ""; |
| | | formData.paramName = ""; |
| | | formData.paramType = ""; |
| | | // formData.valueMode = "1"; |
| | | formData.unit = ""; |
| | | formData.remark = ""; |
| | | formData.isRequired = 0; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ç¼è¾æé®ç¹å»äºä»¶ |
| | | const handleEdit = row => { |
| | | isEdit.value = true; |
| | | dialogTitle.value = "ç¼è¾åæ°"; |
| | | // å¡«å
è¡¨åæ°æ® |
| | | formData.id = row.id; |
| | | formData.paramCode = row.paramCode || ""; |
| | | formData.paramName = row.paramName || ""; |
| | | formData.paramType = row.paramType !== undefined ? row.paramType : null; |
| | | // formData.valueMode = |
| | | // row.valueMode !== undefined ? String(row.valueMode) : "1"; |
| | | formData.unit = row.unit || ""; |
| | | formData.remark = row.remark || ""; |
| | | formData.paramFormat = row.paramFormat || ""; |
| | | formData.isRequired = row.isRequired || 0; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // å 餿é®ç¹å»äºä»¶ |
| | | const handleDelete = row => { |
| | | ElMessageBox.confirm("ç¡®å®è¦å é¤è¿æ¡æ°æ®åï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // è°ç¨æ°æ¥å£ /baseParam/remove/{id} |
| | | removeBaseParam([row.id]) |
| | | .then(res => { |
| | | ElMessage.success("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å é¤å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | // åæ¶å é¤ |
| | | }); |
| | | }; |
| | | |
| | | // æäº¤è¡¨å |
| | | const handleSubmit = () => { |
| | | if (formData.paramType == 3 && !formData.paramFormat) { |
| | | ElMessage.warning("䏿åå
¸ä¸è½ä¸ºç©ºï¼"); |
| | | return; |
| | | } |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | if (formData.id) { |
| | | // ç¼è¾ä½¿ç¨æ°æ¥å£ /technologyParam/edit |
| | | editBaseParam(formData) |
| | | .then(res => { |
| | | ElMessage.success("ç¼è¾æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | // ElMessage.error("ç¼è¾å¤±è´¥"); |
| | | }); |
| | | } else { |
| | | // æ°å¢ä½¿ç¨æ°æ¥å£ /technologyParam/add |
| | | addBaseParam(formData) |
| | | .then(res => { |
| | | ElMessage.success("æ°å¢æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("æ°å¢å¤±è´¥"); |
| | | }); |
| | | } |
| | | } else { |
| | | return false; |
| | | } |
| | | }); |
| | | }; |
| | | const dictTypes = ref([]); |
| | | const getDictTypes = () => { |
| | | listType({ pageNum: 1, pageSize: 1000 }).then(res => { |
| | | dictTypes.value = res.rows || []; |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getDictTypes(); |
| | | getList(); |
| | | // getProductTypes(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .app-container { |
| | | padding: 24px; |
| | | background-color: #f0f2f5; |
| | | min-height: calc(100vh - 48px); |
| | | } |
| | | |
| | | .search_form { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 24px; |
| | | padding: 20px; |
| | | background-color: #ffffff; |
| | | border-radius: 6px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08); |
| | | } |
| | | |
| | | .search_title { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .ml10 { |
| | | margin-left: 10px; |
| | | } |
| | | } |
| | | |
| | | .table_list { |
| | | background-color: #ffffff; |
| | | border-radius: 6px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); |
| | | overflow: hidden; |
| | | height: calc(100vh - 230px); |
| | | } |
| | | |
| | | :deep(.el-table) { |
| | | border: none; |
| | | border-radius: 6px; |
| | | overflow: hidden; |
| | | box-shadow: 0 4px 16px rgba(102, 126, 234, 0.1); |
| | | |
| | | .el-table__header-wrapper { |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | |
| | | th { |
| | | background: transparent; |
| | | font-weight: 600; |
| | | // color: #ffffff; |
| | | border-bottom: none; |
| | | padding: 16px 0; |
| | | letter-spacing: 0.5px; |
| | | } |
| | | } |
| | | |
| | | .el-table__body-wrapper { |
| | | tr { |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | background: linear-gradient( |
| | | 90deg, |
| | | rgba(102, 126, 234, 0.05) 0%, |
| | | rgba(118, 75, 162, 0.05) 100% |
| | | ); |
| | | transform: scale(1.002); |
| | | box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1); |
| | | } |
| | | |
| | | td { |
| | | border-bottom: 1px solid #f0f0f0; |
| | | padding: 14px 0; |
| | | color: #303133; |
| | | } |
| | | } |
| | | |
| | | tr.current-row { |
| | | background: linear-gradient( |
| | | 90deg, |
| | | rgba(102, 126, 234, 0.08) 0%, |
| | | rgba(118, 75, 162, 0.08) 100% |
| | | ); |
| | | } |
| | | |
| | | // æ°å¼åæ®µæ ·å¼ |
| | | .quantity-cell, |
| | | .volume-cell, |
| | | .dimension-cell { |
| | | font-weight: 600; |
| | | color: #409eff; |
| | | font-family: "Courier New", monospace; |
| | | text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2); |
| | | } |
| | | |
| | | // è§æ ¼åæ®µæ ·å¼ |
| | | .spec-cell { |
| | | color: #67c23a; |
| | | font-weight: 500; |
| | | padding: 4px 8px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | // ç¼ç åæ®µæ ·å¼ |
| | | .code-cell { |
| | | color: #e6a23c; |
| | | font-family: "Courier New", monospace; |
| | | font-weight: 500; |
| | | padding: 4px 8px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | // æ¥æåæ®µæ ·å¼ |
| | | .date-cell { |
| | | color: #909399; |
| | | font-style: italic; |
| | | } |
| | | } |
| | | |
| | | .el-table__empty-block { |
| | | padding: 60px 0; |
| | | background-color: #fafafa; |
| | | } |
| | | } |
| | | |
| | | .pagination-container { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | padding: 16px 20px; |
| | | background-color: #ffffff; |
| | | border-top: 1px solid #ebeef5; |
| | | border-radius: 0 0 12px 12px; |
| | | } |
| | | |
| | | :deep(.el-button) { |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | transform: translateY(-1px); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .app-container { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .search_form { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | gap: 12px; |
| | | |
| | | .el-form { |
| | | width: 100%; |
| | | |
| | | .el-form-item { |
| | | width: 100%; |
| | | } |
| | | } |
| | | |
| | | .el-button { |
| | | margin-right: 12px; |
| | | } |
| | | } |
| | | |
| | | :deep(.el-table) { |
| | | th, |
| | | td { |
| | | padding: 10px 0; |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <div class="app-container product-view"> |
| | | <div class="left"> |
| | | <div> |
| | | <el-input |
| | | v-model="search" |
| | | <el-input v-model="search" |
| | | style="width: 210px" |
| | | placeholder="è¾å
¥å
³é®åè¿è¡æç´¢" |
| | | @change="searchFilter" |
| | | @clear="searchFilter" |
| | | clearable |
| | | prefix-icon="Search" |
| | | /> |
| | | <el-button |
| | | v-if="false" |
| | | prefix-icon="Search" /> |
| | | <el-button v-if="false" |
| | | type="primary" |
| | | @click="openProDia('addOne')" |
| | | style="margin-left: 10px" |
| | | >æ°å¢äº§å大类</el-button |
| | | > |
| | | style="margin-left: 10px">æ°å¢äº§å大类</el-button> |
| | | </div> |
| | | <div ref="containerRef"> |
| | | <el-tree |
| | | ref="tree" |
| | | <el-tree ref="tree" |
| | | v-loading="treeLoad" |
| | | :data="list" |
| | | @node-click="handleNodeClick" |
| | |
| | | highlight-current |
| | | node-key="id" |
| | | class="product-tree-scroll" |
| | | style="height: calc(100vh - 190px); overflow-y: auto" |
| | | > |
| | | style="height: calc(100vh - 190px); overflow-y: auto"> |
| | | <template #default="{ node, data }"> |
| | | <div class="custom-tree-node"> |
| | | <span class="tree-node-content"> |
| | |
| | | <span class="tree-node-label">{{ data.label }}</span> |
| | | </span> |
| | | <div> |
| | | <el-button |
| | | type="primary" |
| | | <el-button type="primary" |
| | | link |
| | | :disabled="isTopLevelNode(data, node)" |
| | | @click="openProDia('edit', data)" |
| | | > |
| | | @click="openProDia('edit', data, node)"> |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button type="primary" link @click="openProDia('add', data)"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="openProDia('add', data, node)"> |
| | | æ·»å 产å |
| | | </el-button> |
| | | <el-button |
| | | v-if="!node.childNodes.length" |
| | | <el-button v-if="!node.childNodes.length" |
| | | style="margin-left: 4px" |
| | | type="danger" |
| | | link |
| | | :disabled="isTopLevelNode(data, node)" |
| | | @click="remove(node, data)" |
| | | > |
| | | @click="remove(node, data)"> |
| | | å é¤ |
| | | </el-button> |
| | | </div> |
| | |
| | | <el-button type="primary" @click="openModelDia('add')"> |
| | | æ°å¢å°ºå¯¸ |
| | | </el-button> |
| | | <ImportExcel :product-id="currentId" @uploadSuccess="getModelList" /> |
| | | <el-button |
| | | type="danger" |
| | | <ImportExcel :product-id="currentId" |
| | | @uploadSuccess="getModelList" /> |
| | | <el-button type="danger" |
| | | @click="handleDelete" |
| | | style="margin-left: 10px" |
| | | plain |
| | | > |
| | | plain> |
| | | å é¤ |
| | | </el-button> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | ></PIMTable> |
| | | @pagination="pagination"></PIMTable> |
| | | </div> |
| | | <el-dialog v-model="productDia" title="产å" width="400px" @keydown.enter.prevent> |
| | | <el-form |
| | | :model="form" |
| | | <el-dialog v-model="productDia" |
| | | title="产å" |
| | | width="400px" |
| | | @keydown.enter.prevent> |
| | | <el-form :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | > |
| | | ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="产ååç§°ï¼" prop="productName"> |
| | | <el-input |
| | | v-model="form.productName" |
| | | <el-form-item label="产ååç§°ï¼" |
| | | prop="productName"> |
| | | <el-input v-model="form.productName" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | maxlength="20" |
| | | show-word-limit |
| | | clearable |
| | | @keydown.enter.prevent |
| | | /> |
| | | @keydown.enter.prevent /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">确认</el-button> |
| | | <el-button @click="closeProDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog |
| | | v-model="modelDia" |
| | | <el-dialog v-model="modelDia" |
| | | title="尺寸" |
| | | width="400px" |
| | | @close="closeModelDia" |
| | | @keydown.enter.prevent |
| | | > |
| | | <el-form |
| | | :model="modelForm" |
| | | @keydown.enter.prevent> |
| | | <el-form :model="modelForm" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="modelRules" |
| | | ref="modelFormRef" |
| | | > |
| | | ref="modelFormRef"> |
| | | <el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="尺寸ï¼" prop="model"> |
| | | <el-input |
| | | v-model="modelForm.model" |
| | | <el-form-item label="产åç¼å·ï¼" |
| | | prop="productCode"> |
| | | <el-input v-model="modelForm.productCode" |
| | | placeholder="请è¾å
¥äº§åç¼å·" |
| | | clearable |
| | | @keydown.enter.prevent /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="尺寸ï¼" |
| | | prop="model"> |
| | | <el-input v-model="modelForm.model" |
| | | placeholder="请è¾å
¥å°ºå¯¸" |
| | | clearable |
| | | @keydown.enter.prevent |
| | | /> |
| | | @keydown.enter.prevent /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="åä½ï¼" prop="unit"> |
| | | <el-input |
| | | v-model="modelForm.unit" |
| | | <el-form-item label="åä½ï¼" |
| | | prop="unit"> |
| | | <el-input v-model="modelForm.unit" |
| | | placeholder="请è¾å
¥åä½" |
| | | clearable |
| | | @keydown.enter.prevent |
| | | /> |
| | | @keydown.enter.prevent /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitModelForm">确认</el-button> |
| | | <el-button type="primary" |
| | | @click="submitModelForm">确认</el-button> |
| | | <el-button @click="closeModelDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | ); |
| | | }; |
| | | |
| | | loadExpandedKeys().forEach((key) => expandedKeySet.add(key)); |
| | | loadExpandedKeys().forEach(key => expandedKeySet.add(key)); |
| | | |
| | | const syncExpandedKeysFromTree = () => { |
| | | const keys = []; |
| | | const walk = (nodes) => { |
| | | (nodes || []).forEach((item) => { |
| | | const walk = nodes => { |
| | | (nodes || []).forEach(item => { |
| | | if (item.expanded && item.data?.id !== undefined) { |
| | | keys.push(item.data.id); |
| | | } |
| | |
| | | |
| | | walk(tree.value?.root?.childNodes); |
| | | expandedKeySet.clear(); |
| | | keys.forEach((key) => expandedKeySet.add(key)); |
| | | keys.forEach(key => expandedKeySet.add(key)); |
| | | expandedKeys.value = keys; |
| | | saveExpandedKeys(); |
| | | }; |
| | | |
| | | const normalizeExpandedKeys = (treeData) => { |
| | | const normalizeExpandedKeys = treeData => { |
| | | const parentMap = new Map(); |
| | | const walk = (nodes, parentId = null) => { |
| | | (nodes || []).forEach((item) => { |
| | | (nodes || []).forEach(item => { |
| | | parentMap.set(item.id, parentId); |
| | | if (item.children && item.children.length) { |
| | | walk(item.children, item.id); |
| | |
| | | |
| | | walk(treeData); |
| | | |
| | | const normalizedKeys = Array.from(expandedKeySet).filter((key) => { |
| | | const normalizedKeys = Array.from(expandedKeySet).filter(key => { |
| | | if (!parentMap.has(key)) { |
| | | return false; |
| | | } |
| | |
| | | |
| | | if (normalizedKeys.length !== expandedKeySet.size) { |
| | | expandedKeySet.clear(); |
| | | normalizedKeys.forEach((key) => expandedKeySet.add(key)); |
| | | normalizedKeys.forEach(key => expandedKeySet.add(key)); |
| | | saveExpandedKeys(); |
| | | } |
| | | }; |
| | |
| | | const search = ref(""); |
| | | const currentId = ref(""); |
| | | const currentParentId = ref(""); |
| | | /** 产åå¼¹çªï¼add åç¶èç¹ idï¼edit åå½åèç¹ id ä¸ parentIdï¼ä¸ä¾èµæ éä¸é¡¹ï¼ */ |
| | | const productDialogTarget = ref(null); |
| | | const operationType = ref(""); |
| | | const treeLoad = ref(false); |
| | | const list = ref([]); |
| | | const expandedKeys = ref([]); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "产åç¼å·", |
| | | prop: "productCode", |
| | | }, |
| | | { |
| | | label: "尺寸", |
| | | prop: "model", |
| | |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | clickFun: row => { |
| | | openModelDia("edit", row); |
| | | }, |
| | | }, |
| | |
| | | modelForm: { |
| | | model: "", |
| | | unit: "", |
| | | productCode: "", |
| | | }, |
| | | modelRules: { |
| | | model: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | unit: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | productCode: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const { form, rules, modelForm, modelRules } = toRefs(data); |
| | |
| | | const getProductTreeList = () => { |
| | | treeLoad.value = true; |
| | | productTreeList() |
| | | .then((res) => { |
| | | .then(res => { |
| | | list.value = res || []; |
| | | normalizeExpandedKeys(list.value); |
| | | expandedKeys.value = Array.from(expandedKeySet); |
| | |
| | | tree.value?.setDefaultExpandedKeys?.(expandedKeys.value); |
| | | }); |
| | | }) |
| | | .catch((err) => { |
| | | .catch(err => { |
| | | console.error(err); |
| | | }) |
| | | .finally(() => { |
| | | treeLoad.value = false; |
| | | }); |
| | | }; |
| | | const handleNodeExpand = (data) => { |
| | | const handleNodeExpand = data => { |
| | | nextTick(syncExpandedKeysFromTree); |
| | | }; |
| | | const handleNodeCollapse = (data, node) => { |
| | | node?.eachNode?.((item) => { |
| | | node?.eachNode?.(item => { |
| | | item.collapse(); |
| | | }); |
| | | nextTick(syncExpandedKeysFromTree); |
| | |
| | | return [null, undefined, "", 0, "0"].includes(data?.parentId); |
| | | }; |
| | | // æå¼äº§åå¼¹æ¡ |
| | | const openProDia = (type, data) => { |
| | | if (data && type === "edit" && isTopLevelNode(data)) { |
| | | const openProDia = (type, data, node) => { |
| | | if (data && type === "edit" && isTopLevelNode(data, node)) { |
| | | proxy.$modal.msgWarning("ä¸çº§èç¹ä¸è½ç¼è¾æå é¤"); |
| | | return; |
| | | } |
| | | operationType.value = type; |
| | | productDia.value = true; |
| | | form.value.productName = ""; |
| | | if (type === "edit") { |
| | | form.value.productName = data.productName; |
| | | productDialogTarget.value = null; |
| | | if (type === "add" && data) { |
| | | productDialogTarget.value = { parentId: data.id }; |
| | | } else if (type === "edit" && data) { |
| | | let parentId = data.parentId; |
| | | if ( |
| | | [null, undefined, ""].includes(parentId) && |
| | | node?.parent?.data?.id != null |
| | | ) { |
| | | parentId = node.parent.data.id; |
| | | } |
| | | productDialogTarget.value = { id: data.id, parentId }; |
| | | } |
| | | productDia.value = true; |
| | | form.value.productName = |
| | | type === "edit" && data ? data.productName : ""; |
| | | }; |
| | | // æå¼å°ºå¯¸å¼¹æ¡ |
| | | // æå¼è§æ ¼åå·å¼¹æ¡ |
| | | const openModelDia = (type, data) => { |
| | | modelOperationType.value = type; |
| | | modelDia.value = true; |
| | | modelForm.value.model = ""; |
| | | modelForm.value.model = ""; |
| | | modelForm.value.unit = ""; |
| | | modelForm.value.productCode = ""; |
| | | modelForm.value.id = ""; |
| | | if (type === "edit") { |
| | | modelForm.value = { ...data }; |
| | |
| | | }; |
| | | // æäº¤äº§ååç§°ä¿®æ¹ |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate((valid) => { |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "add") { |
| | | form.value.parentId = currentId.value; |
| | | form.value.parentId = |
| | | productDialogTarget.value?.parentId ?? currentId.value; |
| | | form.value.id = ""; |
| | | } else if (operationType.value === "addOne") { |
| | | form.value.id = ""; |
| | | form.value.parentId = ""; |
| | | } else { |
| | | form.value.id = currentId.value; |
| | | form.value.parentId = ""; |
| | | form.value.id = |
| | | productDialogTarget.value?.id ?? currentId.value; |
| | | form.value.parentId = productDialogTarget.value?.parentId ?? ""; |
| | | } |
| | | addOrEditProduct(form.value).then((res) => { |
| | | addOrEditProduct(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeProDia(); |
| | | getProductTreeList(); |
| | |
| | | // å
³é产åå¼¹æ¡ |
| | | const closeProDia = () => { |
| | | proxy.$refs.formRef.resetFields(); |
| | | productDialogTarget.value = null; |
| | | productDia.value = false; |
| | | }; |
| | | |
| | |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delProduct(ids) |
| | | .then((res) => { |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getProductTreeList(); |
| | | }) |
| | |
| | | |
| | | // æäº¤å°ºå¯¸ä¿®æ¹ |
| | | const submitModelForm = () => { |
| | | proxy.$refs.modelFormRef.validate((valid) => { |
| | | proxy.$refs.modelFormRef.validate(valid => { |
| | | if (valid) { |
| | | modelForm.value.productId = currentId.value; |
| | | addOrEditProductModel(modelForm.value).then((res) => { |
| | | addOrEditProductModel(modelForm.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeModelDia(); |
| | | getModelList(); |
| | |
| | | modelDia.value = false; |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æ¥è¯¢å°ºå¯¸ |
| | | const pagination = (obj) => { |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getModelList(); |
| | |
| | | id: currentId.value, |
| | | current: page.current, |
| | | size: page.size, |
| | | }).then((res) => { |
| | | }).then(res => { |
| | | console.log("res", res); |
| | | tableData.value = res.records; |
| | | page.total = res.total; |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // å é¤å°ºå¯¸ |
| | | // å é¤è§æ ¼åå· |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delProductModel(ids) |
| | | .then((res) => { |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getModelList(); |
| | | }) |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <div class="search_form"> |
| | | <div style="margin-bottom: 10px;"> |
| | | <span class="search_title">ä¾åºåæ¡£æ¡ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.supplierName" |
| | |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div> |
| | | <div style="margin-bottom: 10px;"> |
| | | <el-button type="primary" @click="openForm('add')" |
| | | >æ°å¢ä¾åºå</el-button |
| | | > |
| | |
| | | } |
| | | // ä¸è½½éä»¶ |
| | | const downLoadFile = (row) => { |
| | | proxy.$download.name(row.url); |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | } |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | <div class="header-actions"> |
| | | <div class="header-actions" v-if="approverList.length > 0"> |
| | | <el-button @click="handleReset" size="default"> |
| | | <el-icon><RefreshLeft /></el-icon> |
| | | éç½® |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | <el-dialog v-model="dialogFormVisible" |
| | | :title="operationType === 'approval' ? '审æ¹' : '详æ
'" |
| | | width="700px" |
| | | @close="closeDia" |
| | | > |
| | | <el-form :model="form" label-width="140px" label-position="top" ref="formRef"> |
| | | @close="closeDia"> |
| | | <el-form :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | ref="formRef"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æµç¨ç¼å·ï¼" prop="approveId"> |
| | | <el-input v-model="form.approveId" placeholder="èªå¨ç¼å·" clearable disabled/> |
| | | <el-form-item label="æµç¨ç¼å·ï¼" |
| | | prop="approveId"> |
| | | <el-input v-model="form.approveId" |
| | | placeholder="èªå¨ç¼å·" |
| | | clearable |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="ç³è¯·é¨é¨ï¼"> |
| | | <el-select |
| | | disabled |
| | | <el-select disabled |
| | | v-model="form.approveDeptId" |
| | | placeholder="éæ©é¨é¨" |
| | | > |
| | | <el-option |
| | | v-for="user in productOptions" |
| | | placeholder="éæ©é¨é¨"> |
| | | <el-option v-for="user in productOptions" |
| | | :key="user.deptId" |
| | | :label="user.deptName" |
| | | :value="user.deptId" |
| | | /> |
| | | :value="user.deptId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="!isQuotationApproval && !isPurchaseApproval"> |
| | | <el-col :span="24"> |
| | | <el-form-item :label="props.approveType == 5 ? 'éè´ååå·ï¼' : '审æ¹äºç±ï¼'" prop="approveReason"> |
| | | <el-input v-model="form.approveReason" placeholder="请è¾å
¥" clearable type="textarea" disabled/> |
| | | <el-form-item :label="props.approveType == 5 ? 'éè´ååå·ï¼' : '审æ¹äºç±ï¼'" |
| | | prop="approveReason"> |
| | | <el-input v-model="form.approveReason" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | type="textarea" |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <!-- æ¥ä»·å®¡æ¹ï¼å±ç¤ºæ¥ä»·è¯¦æ
ï¼å¤ç¨é宿¥ä»·"æ¥ç详æ
å¯¹è¯æ¡"å
å®¹ç»æï¼ --> |
| | | <div v-if="isQuotationApproval" style="margin: 10px 0 18px;"> |
| | | <div v-if="isQuotationApproval" |
| | | style="margin: 10px 0 18px;"> |
| | | <el-divider content-position="left">æ¥ä»·è¯¦æ
</el-divider> |
| | | <el-skeleton :loading="quotationLoading" animated> |
| | | <el-skeleton :loading="quotationLoading" |
| | | animated> |
| | | <template #template> |
| | | <el-skeleton-item variant="h3" style="width: 30%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | <el-skeleton-item variant="h3" |
| | | style="width: 30%" /> |
| | | <el-skeleton-item variant="text" |
| | | style="width: 100%" /> |
| | | <el-skeleton-item variant="text" |
| | | style="width: 100%" /> |
| | | </template> |
| | | <template #default> |
| | | <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo" description="æªæ¥è¯¢å°å¯¹åºæ¥ä»·è¯¦æ
" /> |
| | | <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo" |
| | | description="æªæ¥è¯¢å°å¯¹åºæ¥ä»·è¯¦æ
" /> |
| | | <template v-else> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="æ¥ä»·åå·">{{ currentQuotation.quotationNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="客æ·åç§°">{{ currentQuotation.customer }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸å¡å">{{ currentQuotation.salesperson }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥ä»·æ¥æ">{{ currentQuotation.quotationDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="æææè³">{{ currentQuotation.validDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="仿¬¾æ¹å¼">{{ currentQuotation.paymentMethod }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥ä»·æ»é¢" :span="2"> |
| | | <el-descriptions-item label="æ¥ä»·æ»é¢" |
| | | :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <h4>产åæç»</h4> |
| | | <el-table :data="currentQuotation.products || []" border style="width: 100%"> |
| | | <el-table-column prop="product" label="产ååç§°" /> |
| | | <el-table-column prop="specification" label="尺寸" /> |
| | | <el-table-column prop="unit" label="åä½" /> |
| | | <el-table-column prop="unitPrice" label="åä»·"> |
| | | <el-table :data="currentQuotation.products || []" |
| | | border |
| | | style="width: 100%"> |
| | | <el-table-column prop="product" |
| | | label="产ååç§°" /> |
| | | <el-table-column prop="specification" |
| | | label="è§æ ¼åå·" /> |
| | | <el-table-column prop="unit" |
| | | label="åä½" /> |
| | | <el-table-column prop="unitPrice" |
| | | label="åä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.unitPrice ?? 0).toFixed(2) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div v-if="currentQuotation.remark" style="margin-top: 20px;"> |
| | | <div v-if="currentQuotation.remark" |
| | | style="margin-top: 20px;"> |
| | | <h4>夿³¨</h4> |
| | | <p>{{ currentQuotation.remark }}</p> |
| | | </div> |
| | |
| | | </template> |
| | | </el-skeleton> |
| | | </div> |
| | | |
| | | <!-- éè´å®¡æ¹ï¼å±ç¤ºéè´è¯¦æ
--> |
| | | <div v-if="isPurchaseApproval" style="margin: 10px 0 18px;"> |
| | | <div v-if="isPurchaseApproval" |
| | | style="margin: 10px 0 18px;"> |
| | | <el-divider content-position="left">éè´è¯¦æ
</el-divider> |
| | | <el-skeleton :loading="purchaseLoading" animated> |
| | | <el-skeleton :loading="purchaseLoading" |
| | | animated> |
| | | <template #template> |
| | | <el-skeleton-item variant="h3" style="width: 30%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | <el-skeleton-item variant="h3" |
| | | style="width: 30%" /> |
| | | <el-skeleton-item variant="text" |
| | | style="width: 100%" /> |
| | | <el-skeleton-item variant="text" |
| | | style="width: 100%" /> |
| | | </template> |
| | | <template #default> |
| | | <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" description="æªæ¥è¯¢å°å¯¹åºéè´è¯¦æ
" /> |
| | | <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" |
| | | description="æªæ¥è¯¢å°å¯¹åºéè´è¯¦æ
" /> |
| | | <template v-else> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="éè´ååå·">{{ currentPurchase.purchaseContractNumber }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¾åºååç§°">{{ currentPurchase.supplierName }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®åç§°">{{ currentPurchase.projectName }}</el-descriptions-item> |
| | |
| | | <el-descriptions-item label="ç¾è®¢æ¥æ">{{ currentPurchase.executionDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="å½å
¥æ¥æ">{{ currentPurchase.entryDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="仿¬¾æ¹å¼">{{ currentPurchase.paymentMethod }}</el-descriptions-item> |
| | | <el-descriptions-item label="ååéé¢" :span="2"> |
| | | <el-descriptions-item label="ååéé¢" |
| | | :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <h4>产åæç»</h4> |
| | | <el-table :data="currentPurchase.productData || []" border style="width: 100%"> |
| | | <el-table-column prop="productCategory" label="产ååç§°" /> |
| | | <el-table-column prop="specificationModel" label="尺寸" /> |
| | | <el-table-column prop="unit" label="åä½" /> |
| | | <el-table-column prop="quantity" label="æ°é" /> |
| | | <el-table-column prop="taxInclusiveUnitPrice" label="å«ç¨åä»·"> |
| | | <el-table :data="currentPurchase.productData || []" |
| | | border |
| | | style="width: 100%"> |
| | | <el-table-column prop="productCategory" |
| | | label="产ååç§°" /> |
| | | <el-table-column prop="specificationModel" |
| | | label="è§æ ¼åå·" /> |
| | | <el-table-column prop="unit" |
| | | label="åä½" /> |
| | | <el-table-column prop="quantity" |
| | | label="æ°é" /> |
| | | <el-table-column prop="taxInclusiveUnitPrice" |
| | | label="å«ç¨åä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="taxInclusiveTotalPrice" label="å«ç¨æ»ä»·"> |
| | | <el-table-column prop="taxInclusiveTotalPrice" |
| | | label="å«ç¨æ»ä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | </template> |
| | | </el-skeleton> |
| | | </div> |
| | | |
| | | <el-form :model="{ activities }" ref="formRef" label-position="top"> |
| | | <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical"> |
| | | <el-step |
| | | v-for="(activity, index) in activities" |
| | | <!-- å货审æ¹ï¼å±ç¤ºå货详æ
--> |
| | | <div v-if="isDeliveryApproval" |
| | | style="margin: 10px 0 18px;"> |
| | | <el-divider content-position="left">å货详æ
</el-divider> |
| | | <el-skeleton :loading="deliveryLoading" |
| | | animated> |
| | | <template #template> |
| | | <el-skeleton-item variant="h3" |
| | | style="width: 30%" /> |
| | | <el-skeleton-item variant="text" |
| | | style="width: 100%" /> |
| | | <el-skeleton-item variant="text" |
| | | style="width: 100%" /> |
| | | </template> |
| | | <template #default> |
| | | <el-empty v-if="!currentDelivery || !currentDelivery.shippingInfo" |
| | | description="æªæ¥è¯¢å°å¯¹åºå货详æ
" /> |
| | | <template v-else> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="éå®è®¢å">{{ currentDelivery.shippingInfo.salesContractNo || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å货订åå·">{{ currentDelivery.shippingInfo.shippingNo || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="客æ·åç§°">{{ currentDelivery.shippingInfo.customerName || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="åè´§ç±»å">{{ currentDelivery.shippingInfo.type || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="åè´§æ¥æ">{{ currentDelivery.shippingInfo.shippingDate || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å®¡æ ¸ç¶æ">{{ currentDelivery.shippingInfo.status || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å货车çå·">{{ currentDelivery.shippingInfo.shippingCarNumber || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¿«éå
¬å¸">{{ currentDelivery.shippingInfo.expressCompany || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¿«éåå·" |
| | | :span="2">{{ currentDelivery.shippingInfo.expressNumber || '--' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <div style="margin-top: 20px;"> |
| | | <h4>产åæç»</h4> |
| | | <el-table :data="deliveryProductList" |
| | | border |
| | | size="small" |
| | | style="width: 100%"> |
| | | <el-table-column prop="batchNo" |
| | | label="æ¹å·" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="productName" |
| | | label="产ååç§°" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="specificationModel" |
| | | label="è§æ ¼åå·" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="deliveryQuantity" |
| | | label="åè´§æ°é" |
| | | align="center" /> |
| | | </el-table> |
| | | </div> |
| | | <div v-if="currentDelivery.shippingInfo.storageBlobVOs && currentDelivery.shippingInfo.storageBlobVOs.length" |
| | | style="margin-top: 20px;"> |
| | | <h4>åè´§å¾ç</h4> |
| | | <ImagePreview :file-list="currentDelivery.shippingInfo.storageBlobVOs" /> |
| | | </div> |
| | | </template> |
| | | </template> |
| | | </el-skeleton> |
| | | </div> |
| | | <el-form :model="{ activities }" |
| | | ref="formRef" |
| | | label-position="top"> |
| | | <el-steps :active="getActiveStep()" |
| | | finish-status="success" |
| | | process-status="process" |
| | | align-center |
| | | direction="vertical"> |
| | | <el-step v-for="(activity, index) in activities" |
| | | :key="index" |
| | | finish-status="success" |
| | | :title="getNodeTitle(index, activities.length)" |
| | | :description="activity.approveNodeUser" |
| | | :icon="getNodeIcon(activity, index)" |
| | | > |
| | | :icon="getNodeIcon(activity, index)"> |
| | | <template #icon> |
| | | <el-icon v-if="activity.approveNodeStatus === 2" color="red" :size="22"><WarningFilled /></el-icon> |
| | | <el-icon v-else-if="activity.isShen" color="#1890ff" :size="22"><Edit /></el-icon> |
| | | <el-icon v-else-if="activity.approveNodeStatus === 1" color="#67C23A" :size="26"><Check /></el-icon> |
| | | <el-icon v-else color="#C0C4CC" :size="22"><MoreFilled /></el-icon> |
| | | <el-icon v-if="activity.approveNodeStatus === 2" |
| | | color="red" |
| | | :size="22"> |
| | | <WarningFilled /> |
| | | </el-icon> |
| | | <el-icon v-else-if="activity.isShen" |
| | | color="#1890ff" |
| | | :size="22"> |
| | | <Edit /> |
| | | </el-icon> |
| | | <el-icon v-else-if="activity.approveNodeStatus === 1" |
| | | color="#67C23A" |
| | | :size="26"> |
| | | <Check /> |
| | | </el-icon> |
| | | <el-icon v-else |
| | | color="#C0C4CC" |
| | | :size="22"> |
| | | <MoreFilled /> |
| | | </el-icon> |
| | | </template> |
| | | <template #title> |
| | | <span style="color: #000000">{{ getNodeTitle(index, activities.length) }}</span> |
| | |
| | | <template #description> |
| | | <div class="node-user"> |
| | | <div class="avatar-wrapper"> |
| | | <img :src="userStore.avatar" class="user-avatar" alt=""/> |
| | | <img :src="userStore.avatar" |
| | | class="user-avatar" |
| | | alt="" /> |
| | | </div> |
| | | <span style="color: #000000">{{ activity.approveNodeUser }}-{{activity.isApproval}}</span> |
| | | </div> |
| | | <div v-if="!activity.isShen" class="node-reason"> |
| | | <div v-if="!activity.isShen" |
| | | class="node-reason"> |
| | | <span>å®¡æ¹æè§ï¼</span>{{ activity.approveNodeReason }} |
| | | </div> |
| | | <div v-else-if="activity.isShen"> |
| | | <el-form-item |
| | | :prop="'activities.' + index + '.approveNodeReason'" |
| | | :rules="[{ required: true, message: 'å®¡æ¹æè§ä¸è½ä¸ºç©º', trigger: 'blur' }]" |
| | | > |
| | | <el-input v-model="activity.approveNodeReason" clearable type="textarea" :disabled="operationType === 'view'"></el-input> |
| | | <el-form-item :prop="'activities.' + index + '.approveNodeReason'" |
| | | :rules="[{ required: true, message: 'å®¡æ¹æè§ä¸è½ä¸ºç©º', trigger: 'blur' }]"> |
| | | <el-input v-model="activity.approveNodeReason" |
| | | clearable |
| | | type="textarea" |
| | | :disabled="operationType === 'view'"></el-input> |
| | | </el-form-item> |
| | | </div> |
| | | </template> |
| | | </el-step> |
| | | </el-steps> |
| | | </el-form> |
| | | <template #footer v-if="operationType === 'approval'"> |
| | | <template #footer |
| | | v-if="operationType === 'approval'"> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm(2)">ä¸éè¿</el-button> |
| | | <el-button type="primary" @click="submitForm(1)">éè¿</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm(2)">ä¸éè¿</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm(1)">éè¿</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs } from "vue"; |
| | | import { |
| | | computed, |
| | | getCurrentInstance, |
| | | nextTick, |
| | | reactive, |
| | | ref, |
| | | toRefs, |
| | | } from "vue"; |
| | | import { |
| | | approveProcessDetails, |
| | | getDept, |
| | | updateApproveNode |
| | | updateApproveNode, |
| | | } from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue' |
| | | import { |
| | | WarningFilled, |
| | | Edit, |
| | | Check, |
| | | MoreFilled, |
| | | } from "@element-plus/icons-vue"; |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js"; |
| | | const emit = defineEmits(['close']) |
| | | const { proxy } = getCurrentInstance() |
| | | import { getDeliveryDetailByShippingNo } from "@/api/salesManagement/deliveryLedger.js"; |
| | | import ImagePreview from "@/components/AttachmentPreview/image/index.vue"; |
| | | const emit = defineEmits(["close"]); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const props = defineProps({ |
| | | approveType: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | } |
| | | }) |
| | | default: 0, |
| | | }, |
| | | }); |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const activities = ref([]) |
| | | const operationType = ref(""); |
| | | const activities = ref([]); |
| | | const formRef = ref(null); |
| | | const userStore = useUserStore() |
| | | const userStore = useUserStore(); |
| | | const productOptions = ref([]); |
| | | const quotationLoading = ref(false) |
| | | const currentQuotation = ref({}) |
| | | const purchaseLoading = ref(false) |
| | | const currentPurchase = ref({}) |
| | | const isQuotationApproval = computed(() => Number(props.approveType) === 6) |
| | | const isPurchaseApproval = computed(() => Number(props.approveType) === 5) |
| | | const quotationLoading = ref(false); |
| | | const currentQuotation = ref({}); |
| | | const purchaseLoading = ref(false); |
| | | const currentPurchase = ref({}); |
| | | const deliveryLoading = ref(false); |
| | | const currentDelivery = ref({}); |
| | | const deliveryProductList = ref([]); |
| | | const isQuotationApproval = computed(() => Number(props.approveType) === 6); |
| | | const isPurchaseApproval = computed(() => Number(props.approveType) === 5); |
| | | const isDeliveryApproval = computed(() => Number(props.approveType) === 7); |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | |
| | | |
| | | // èç¹æ é¢ |
| | | const getNodeTitle = (index, len) => { |
| | | if (index === len - 1) return 'ç»æ'; |
| | | return '审æ¹'; |
| | | if (index === len - 1) return "ç»æ"; |
| | | return "审æ¹"; |
| | | }; |
| | | |
| | | // è·åå½åæ¿æ´»æ¥éª¤ |
| | |
| | | }; |
| | | // æ¥éª¤icon |
| | | const getNodeIcon = (activity, index) => { |
| | | if (activity.approveNodeStatus === 2) return 'el-icon-warning'; // ä¸éè¿ |
| | | if (activity.isShen) return 'Edit'; |
| | | return ''; |
| | | if (activity.approveNodeStatus === 2) return "el-icon-warning"; // ä¸éè¿ |
| | | if (activity.isShen) return "Edit"; |
| | | return ""; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | currentQuotation.value = {} |
| | | currentPurchase.value = {} |
| | | form.value = {...row} |
| | | currentQuotation.value = {}; |
| | | currentPurchase.value = {}; |
| | | form.value = { ...row }; |
| | | // ç«å³æ¸
é¤è¡¨åéªè¯ç¶æï¼å ä¸ºåæ®µæ¯disabledçï¼ä¸éè¦éªè¯ï¼ |
| | | nextTick(() => { |
| | | if (formRef.value) { |
| | |
| | | getProductOptions().then(() => { |
| | | // ç¡®ä¿å¼ç±»åå¹é
ï¼å¦æé项已å è½½ï¼ |
| | | if (productOptions.value.length > 0 && form.value.approveDeptId) { |
| | | const matchedOption = productOptions.value.find(opt => |
| | | const matchedOption = productOptions.value.find( |
| | | opt => |
| | | opt.deptId == form.value.approveDeptId || |
| | | String(opt.deptId) === String(form.value.approveDeptId) |
| | | ); |
| | |
| | | if (isQuotationApproval.value) { |
| | | const quotationNo = row?.approveReason; |
| | | if (quotationNo) { |
| | | quotationLoading.value = true |
| | | getQuotationList({ quotationNo }).then((res) => { |
| | | const records = res?.data?.records || [] |
| | | currentQuotation.value = records[0] || {} |
| | | }).finally(() => { |
| | | quotationLoading.value = false |
| | | quotationLoading.value = true; |
| | | getQuotationList({ quotationNo }) |
| | | .then(res => { |
| | | const records = res?.data?.records || []; |
| | | currentQuotation.value = records[0] || {}; |
| | | }) |
| | | .finally(() => { |
| | | quotationLoading.value = false; |
| | | }); |
| | | } |
| | | } |
| | | |
| | |
| | | if (isPurchaseApproval.value) { |
| | | const purchaseContractNumber = row?.approveReason; |
| | | if (purchaseContractNumber) { |
| | | purchaseLoading.value = true |
| | | getPurchaseByCode({ purchaseContractNumber }).then((res) => { |
| | | currentPurchase.value = res |
| | | }).catch((err) => { |
| | | console.error('æ¥è¯¢éè´è¯¦æ
失败:', err) |
| | | proxy.$modal.msgError('æ¥è¯¢éè´è¯¦æ
失败') |
| | | }).finally(() => { |
| | | purchaseLoading.value = false |
| | | purchaseLoading.value = true; |
| | | getPurchaseByCode({ purchaseContractNumber }) |
| | | .then(res => { |
| | | currentPurchase.value = res; |
| | | }) |
| | | .catch(err => { |
| | | console.error("æ¥è¯¢éè´è¯¦æ
失败:", err); |
| | | proxy.$modal.msgError("æ¥è¯¢éè´è¯¦æ
失败"); |
| | | }) |
| | | .finally(() => { |
| | | purchaseLoading.value = false; |
| | | }); |
| | | } |
| | | } |
| | | // å货审æ¹ï¼ç¨å®¡æ¹äºç±å段æ¿è½½ç"åè´§åå·"廿¥å货详æ
|
| | | if (isDeliveryApproval.value) { |
| | | const deliveryNo = row?.approveReason; |
| | | if (deliveryNo) { |
| | | deliveryLoading.value = true; |
| | | currentDelivery.value = {}; |
| | | deliveryProductList.value = []; |
| | | getDeliveryDetailByShippingNo({ shippingNo: deliveryNo }) |
| | | .then(res => { |
| | | const detailData = res?.data || res || {}; |
| | | currentDelivery.value = detailData; |
| | | deliveryProductList.value = |
| | | detailData.shippingProductDetailDtoList || []; |
| | | }) |
| | | .catch(err => { |
| | | console.error("æ¥è¯¢å货详æ
失败:", err); |
| | | proxy.$modal.msgError("æ¥è¯¢å货详æ
失败"); |
| | | }) |
| | | .finally(() => { |
| | | deliveryLoading.value = false; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | approveProcessDetails(row.approveId).then((res) => { |
| | | activities.value = res.data |
| | | approveProcessDetails(row.approveId).then(res => { |
| | | activities.value = res.data; |
| | | // å¢å isApprovalåæ®µ |
| | | activities.value.forEach(item => { |
| | | if (item.url && item.url.includes('word')) { |
| | | item.urlTem = item.url.replaceAll('word', 'img') |
| | | if (item.url && item.url.includes("word")) { |
| | | item.urlTem = item.url.replaceAll("word", "img"); |
| | | } else { |
| | | item.urlTem = item.url |
| | | item.urlTem = item.url; |
| | | } |
| | | if (item.approveNodeStatus === 2) { |
| | | item.isApproval = '已驳å'; |
| | | item.isApproval = "已驳å"; |
| | | } else if (item.approveNodeStatus === 1) { |
| | | item.isApproval = 'å·²åæ'; |
| | | item.isApproval = "å·²åæ"; |
| | | } else { |
| | | item.isApproval = 'æªå®¡æ¹'; |
| | | item.isApproval = "æªå®¡æ¹"; |
| | | } |
| | | }) |
| | | }) |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const getDeliveryProductInfoList = () => { |
| | | const row = currentDelivery.value; |
| | | if (!row) return []; |
| | | const normalizeBatchNoList = value => { |
| | | if (Array.isArray(value)) return value; |
| | | if (typeof value === "string" && value.includes(",")) { |
| | | return value |
| | | .split(",") |
| | | .map(item => item.trim()) |
| | | .filter(Boolean); |
| | | } |
| | | return value ? [value] : []; |
| | | }; |
| | | const detailList = deliveryProductList.value.length |
| | | ? deliveryProductList.value |
| | | : [ |
| | | row.batchNoDetailList, |
| | | row.batchNoList, |
| | | row.shippingBatchList, |
| | | row.shippingInfoDetailList, |
| | | row.detailList, |
| | | row.batchDetailList, |
| | | ].find(value => Array.isArray(value) && value.length); |
| | | const batchNoList = normalizeBatchNoList(row.batchNo); |
| | | const toTableRow = (item = {}) => ({ |
| | | batchNo: |
| | | typeof item === "string" || typeof item === "number" |
| | | ? item |
| | | : item.batchNo ?? item.batchNumber ?? row.batchNo ?? "--", |
| | | productName: item.productName ?? row.productName ?? "--", |
| | | specificationModel: |
| | | item.specificationModel ?? item.model ?? row.specificationModel ?? "--", |
| | | deliveryQuantity: |
| | | item.deliveryQuantity ?? |
| | | item.quantity ?? |
| | | item.shippingQuantity ?? |
| | | row.deliveryQuantity ?? |
| | | row.quantity ?? |
| | | "--", |
| | | }); |
| | | if (detailList?.length) { |
| | | return detailList.map(toTableRow); |
| | | } |
| | | if (batchNoList.length) { |
| | | return batchNoList.map(batchNo => toTableRow({ batchNo })); |
| | | } |
| | | return [toTableRow()]; |
| | | }; |
| | | const getApprovalStatusText = status => { |
| | | const statusMap = { |
| | | 0: "å¾
å®¡æ ¸", |
| | | 1: "å®¡æ ¸éè¿", |
| | | 2: "å®¡æ ¸æç»", |
| | | 3: "å®¡æ ¸ä¸", |
| | | }; |
| | | return statusMap[status] || "å¾
å®¡æ ¸"; |
| | | }; |
| | | |
| | | const getProductOptions = () => { |
| | | return getDept().then((res) => { |
| | | return getDept().then(res => { |
| | | productOptions.value = res.data; |
| | | }); |
| | | }; |
| | | // æäº¤å®¡æ¹ |
| | | const submitForm = (status) => { |
| | | const filteredActivities = activities.value.filter(activity => activity.isShen); |
| | | const submitForm = status => { |
| | | const filteredActivities = activities.value.filter( |
| | | activity => activity.isShen |
| | | ); |
| | | if (!filteredActivities || filteredActivities.length === 0) { |
| | | proxy.$modal.msgError("æªæ¾å°å¾
审æ¹çèç¹"); |
| | | return; |
| | |
| | | } |
| | | currentActivity.approveNodeStatus = status; |
| | | // 夿æ¯å¦ä¸ºæå䏿¥ |
| | | const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1; |
| | | const isLast = |
| | | activities.value.findIndex(a => a.isShen) === activities.value.length - 1; |
| | | updateApproveNode({ ...currentActivity, isLast }).then(() => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | quotationLoading.value = false |
| | | currentQuotation.value = {} |
| | | purchaseLoading.value = false |
| | | currentPurchase.value = {} |
| | | emit('close') |
| | | quotationLoading.value = false; |
| | | currentQuotation.value = {}; |
| | | purchaseLoading.value = false; |
| | | currentPurchase.value = {}; |
| | | emit("close"); |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | .node-user { |
| | | margin: 10px 0; |
| | | font-size: 16px; |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" prop="remark"> |
| | | <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload |
| | | :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" |
| | | :on-success="handleUploadSuccess" :on-remove="handleRemove"> |
| | | <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button> |
| | | <template #tip v-if="operationType !== 'view'"> |
| | | <div class="el-upload__tip"> |
| | | æä»¶æ ¼å¼æ¯æ |
| | | docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | <FileUpload v-model:file-list="fileList" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | import useUserStore from "@/store/modules/user"; |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | const userStore = useUserStore(); |
| | | |
| | | const dialogFormVisible = ref(false); |
| | |
| | | approveDeptName: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | startDate: "", // 请åå¼å§æ¶é´ |
| | | endDate: "", // 请åç»ææ¶é´ |
| | | price: null, // æ¥ééé¢ |
| | |
| | | currentApproveStatus.value = row.approveStatus |
| | | approveProcessGetInfo({id: row.approveId,approveReason: '1'}).then(res => { |
| | | form.value = {...res.data} |
| | | fileList.value = res.data.storageBlobVOS |
| | | }) |
| | | } |
| | | } |
| | |
| | | return |
| | | } |
| | | } |
| | | form.value.storageBlobDTOList = fileList.value |
| | | |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "add" || currentApproveStatus.value == 3) { |
| | |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | function handleBeforeUpload(file) { |
| | | // æ ¡æ£æä»¶å¤§å° |
| | | // if (file.size > 1024 * 1024 * 10) { |
| | | // proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿10MB!"); |
| | | // return false; |
| | | // } |
| | | proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); |
| | | return true; |
| | | } |
| | | // ä¸ä¼ 失败 |
| | | function handleUploadError(err) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | // ä¸ä¼ æååè° |
| | | function handleUploadSuccess(res, file, uploadFiles) { |
| | | proxy.$modal.closeLoading(); |
| | | if (res.code === 200) { |
| | | // ç¡®ä¿ tempFileIds åå¨ä¸ä¸ºæ°ç» |
| | | if (!form.value.tempFileIds) { |
| | | form.value.tempFileIds = []; |
| | | } |
| | | form.value.tempFileIds.push(res.data.tempId); |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | proxy.$refs.fileUpload.handleRemove(file); |
| | | } |
| | | } |
| | | // ç§»é¤æä»¶ |
| | | function handleRemove(file) { |
| | | if (operationType.value === "edit") { |
| | | let ids = []; |
| | | ids.push(file.id); |
| | | delLedgerFile(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | |
| | | tableData.value = list |
| | | } |
| | | const downLoadFile = (row) => { |
| | | proxy.$download.name(row.url); |
| | | |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | } |
| | | const lookFile = (row) => { |
| | | filePreviewRef.value.open(row.url) |
| | |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <div class="custom-table"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumnCopy" |
| | |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | class="custom-table" |
| | | ></PIMTable> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- å¼¹çªç»ä»¶ --> |
| | |
| | | |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="showAddContactDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="addContact">ç¡®å®</el-button> |
| | | <el-button @click="showAddContactDialog = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div class="search_form" style="margin-bottom: 20px;"> |
| | | <div> |
| | | <span class="search_title">ç¥è¯æ é¢ï¼</span> |
| | | <el-input |
| | |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { Clock, Location, User, UserFilled } from '@element-plus/icons-vue' |
| | | import Editor from "@/components/Editor/index.vue"; |
| | | import {getMeetSummaryItems,getMeetSummary} from '@/api/collaborativeApproval/meeting.js' |
| | | import dayjs from "dayjs"; |
| | | |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form :model="searchForm" |
| | | :inline="true"> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="searchForm.supplierName" placeholder="请è¾å
¥" clearable prefix-icon="Search" |
| | | <el-input v-model="searchForm.supplierName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="éè´ååå·ï¼"> |
| | | <el-input |
| | | v-model="searchForm.purchaseContractNumber" |
| | | <el-input v-model="searchForm.purchaseContractNumber" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | :prefix-icon="Search" /> |
| | | </el-form-item> |
| | | <el-form-item label="éå®ååå·ï¼"> |
| | | <el-input v-model="searchForm.salesContractNo" placeholder="请è¾å
¥" clearable prefix-icon="Search" |
| | | <el-input v-model="searchForm.salesContractNo" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="项ç®åç§°ï¼"> |
| | | <el-input v-model="searchForm.projectName" placeholder="请è¾å
¥" clearable prefix-icon="Search" |
| | | <el-input v-model="searchForm.projectName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery"> æç´¢ </el-button> |
| | | <el-button type="primary" |
| | | @click="handleQuery"> æç´¢ </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | </div> |
| | | <div class="table_list"> |
| | | <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;"> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | <el-table |
| | | :data="tableData" |
| | | <el-table :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | |
| | | :summary-method="summarizeMainTable" |
| | | @expand-change="expandChange" |
| | | height="calc(100vh - 18.5em)" |
| | | :row-class-name="tableRowClassName" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | :row-class-name="tableRowClassName"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" /> |
| | | <el-table-column type="expand"> |
| | | <template #default="props"> |
| | | <el-table |
| | | :data="props.row.children" |
| | | <el-table :data="props.row.children" |
| | | border |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | :summary-method="summarizeChildrenTable"> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column label="产å大类" prop="productCategory" /> |
| | | <el-table-column label="尺寸" prop="specificationModel" /> |
| | | <el-table-column label="åä½" prop="unit" /> |
| | | <el-table-column label="æ°é" prop="quantity" /> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" /> |
| | | <el-table-column |
| | | label="å«ç¨åä»·(å
)" |
| | | width="60" /> |
| | | <el-table-column label="产å大类" |
| | | prop="productCategory" /> |
| | | <el-table-column label="尺寸" |
| | | prop="specificationModel" /> |
| | | <el-table-column label="åä½" |
| | | prop="unit" /> |
| | | <el-table-column label="æ°é" |
| | | prop="quantity" /> |
| | | <el-table-column label="ç¨ç(%)" |
| | | prop="taxRate" /> |
| | | <el-table-column label="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨æ»ä»·(å
)" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="ä¸å«ç¨æ»ä»·(å
)" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="ä¸å«ç¨æ»ä»·(å
)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | :formatter="formattedNumber" /> |
| | | </el-table> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column |
| | | label="éè´ååå·" |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="éè´ååå·" |
| | | prop="purchaseContractNumber" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="éå®ååå·" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="éå®ååå·" |
| | | prop="salesContractNo" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¾åºååç§°" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="ä¾åºååç§°" |
| | | width="240" |
| | | prop="supplierName" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="订åç¶æ" width="100" align="center"> |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="订åç¶æ" |
| | | width="100" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.isInvalid" type="danger" size="small">失æ</el-tag> |
| | | <el-tag v-else type="success" size="small">æ£å¸¸</el-tag> |
| | | <el-tag v-if="scope.row.isInvalid" |
| | | type="danger" |
| | | size="small">失æ</el-tag> |
| | | <el-tag v-else |
| | | type="success" |
| | | size="small">æ£å¸¸</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="项ç®åç§°" |
| | | <el-table-column label="项ç®åç§°" |
| | | prop="projectName" |
| | | width="420" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="审æ¹ç¶æ" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="审æ¹ç¶æ" |
| | | prop="approvalStatus" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | > |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-tag |
| | | size="small" |
| | | > |
| | | <el-tag size="small"> |
| | | {{ approvalStatusText[scope.row.approvalStatus] || 'æªç¥ç¶æ' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="仿¬¾æ¹å¼" |
| | | <el-table-column label="仿¬¾æ¹å¼" |
| | | width="100" |
| | | prop="paymentMethod" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ååéé¢(å
)" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="ååéé¢(å
)" |
| | | prop="contractAmount" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="å½å
¥äºº" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="å½å
¥äºº" |
| | | prop="recorderName" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å½å
¥æ¥æ" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="å½å
¥æ¥æ" |
| | | prop="entryDate" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | fixed="right" |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="æä½" |
| | | min-width="150" |
| | | align="center" |
| | | > |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | @click="approvePurchase(scope.row)" |
| | | :disabled="scope.row.approvalStatus !== 0" |
| | | >审æ¹</el-button |
| | | > |
| | | <el-button |
| | | link |
| | | :disabled="scope.row.approvalStatus !== 0">审æ¹</el-button> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | @click="rejectPurchase(scope.row)" |
| | | :disabled="scope.row.approvalStatus !== 0" |
| | | >æç»å®¡æ¹</el-button |
| | | > |
| | | :disabled="scope.row.approvalStatus !== 0">æç»å®¡æ¹</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination |
| | | v-show="total > 0" |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | @pagination="paginationChange" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | <script setup> |
| | | import { getToken } from "@/utils/auth"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { ref, onMounted, reactive, toRefs, getCurrentInstance, nextTick } from "vue"; |
| | | import { |
| | | ref, |
| | | onMounted, |
| | | reactive, |
| | | toRefs, |
| | | getCurrentInstance, |
| | | nextTick, |
| | | } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | |
| | | productList, |
| | | getPurchaseById, |
| | | getOptions, |
| | | createPurchaseNo, updateApprovalStatus, |
| | | createPurchaseNo, |
| | | updateApprovalStatus, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | import QRCode from "qrcode"; |
| | | |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | |
| | | |
| | | // 订å审æ¹ç¶ææ¾ç¤ºææ¬ |
| | | const approvalStatusText = { |
| | | 0: 'å¾
审æ¹', |
| | | 1: '审æ¹éè¿', |
| | | 2: '审æ¹å¤±è´¥' |
| | | 0: "å¾
审æ¹", |
| | | 1: "审æ¹éè¿", |
| | | 2: "审æ¹å¤±è´¥", |
| | | }; |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | |
| | | }, |
| | | }); |
| | | const { productForm, productRules } = toRefs(productFormData); |
| | | const upload = reactive({ |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | // const upload = reactive({ |
| | | // // ä¸ä¼ çå°å |
| | | // url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | // headers: { Authorization: "Bearer " + getToken() }, |
| | | // }); |
| | | |
| | | const changeDaterange = (value) => { |
| | | const changeDaterange = value => { |
| | | if (value) { |
| | | searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | |
| | | getList(); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeChildrenTable = (param) => { |
| | | const summarizeChildrenTable = param => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | |
| | | } |
| | | ); |
| | | }; |
| | | const paginationChange = (obj) => { |
| | | const paginationChange = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | |
| | | tableLoading.value = true; |
| | | const { entryDate, ...rest } = searchForm; |
| | | purchaseListPage({ ...rest, ...page }) |
| | | .then((res) => { |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | // tableData.value = res.data.records; |
| | | // å¤çæ°æ®ï¼æ·»å 失æç¶ææ è®° |
| | | tableData.value = res.data.records.map(record => ({ |
| | | ...record, |
| | | isInvalid: record.isWhite === 1 |
| | | isInvalid: record.isWhite === 1, |
| | | })); |
| | | tableData.value.map((item) => { |
| | | tableData.value.map(item => { |
| | | item.children = []; |
| | | }); |
| | | total.value = res.data.total; |
| | |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const productSelected = (selectedRows) => { |
| | | const productSelected = selectedRows => { |
| | | productSelectedRows.value = selectedRows; |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | |
| | | if (expandedRows.length > 0) { |
| | | expandedRowKeys.value = []; |
| | | try { |
| | | productList({ salesLedgerId: row.id, type: 2 }).then((res) => { |
| | | const index = tableData.value.findIndex((item) => item.id === row.id); |
| | | productList({ salesLedgerId: row.id, type: 2 }).then(res => { |
| | | const index = tableData.value.findIndex(item => item.id === row.id); |
| | | if (index > -1) { |
| | | tableData.value[index].children = res.data; |
| | | } |
| | |
| | | } |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = (param) => { |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable(param, ["contractAmount"]); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeProTable = (param) => { |
| | | const summarizeProTable = param => { |
| | | return proxy.summarizeTable(param, [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | |
| | | productData.value = []; |
| | | fileList.value = []; |
| | | if (operationType.value == "add") { |
| | | createPurchaseNo().then((res) => { |
| | | createPurchaseNo().then(res => { |
| | | form.value.purchaseContractNumber = res.data; |
| | | }); |
| | | } |
| | | userListNoPage().then((res) => { |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | getSalesNo().then((res) => { |
| | | getSalesNo().then(res => { |
| | | salesContractList.value = res; |
| | | }); |
| | | getOptions().then((res) => { |
| | | getOptions().then(res => { |
| | | // ä¾åºåè¿æ»¤åºisWhite=0 çæ°æ® |
| | | supplierList.value = res.data.filter((item) => item.isWhite == 0); |
| | | supplierList.value = res.data.filter(item => item.isWhite == 0); |
| | | }); |
| | | form.value.recorderId = userStore.id; |
| | | form.value.entryDate = getCurrentDate(); |
| | | if (type === "edit") { |
| | | currentId.value = row.id; |
| | | getPurchaseById({ id: row.id, type: 2 }).then((res) => { |
| | | getPurchaseById({ id: row.id, type: 2 }).then(res => { |
| | | form.value = { ...res }; |
| | | productData.value = form.value.productData; |
| | | if (form.value.salesLedgerFiles) { |
| | |
| | | if (operationType.value === "edit") { |
| | | let ids = []; |
| | | ids.push(file.id); |
| | | delLedgerFile(ids).then((res) => { |
| | | delLedgerFile(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }); |
| | | } |
| | | } |
| | | // æäº¤è¡¨å |
| | | const submitForm = (n) => { |
| | | proxy.$refs["formRef"].validate((valid) => { |
| | | const submitForm = n => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | if (productData.value.length > 0) { |
| | | form.value.productData = proxy.HaveJson(productData.value); |
| | |
| | | } |
| | | let tempFileIds = []; |
| | | if (fileList.value.length > 0) { |
| | | tempFileIds = fileList.value.map((item) => item.tempId); |
| | | tempFileIds = fileList.value.map(item => item.tempId); |
| | | } |
| | | form.value.tempFileIds = tempFileIds; |
| | | form.value.type = 2; |
| | | form.value.approvalStatus = n; |
| | | addOrEditPurchase(form.value).then((res) => { |
| | | addOrEditPurchase(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | |
| | | getProductOptions(); |
| | | }; |
| | | const getProductOptions = () => { |
| | | productTreeList().then((res) => { |
| | | productTreeList().then(res => { |
| | | productOptions.value = convertIdToValue(res); |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | | const getModels = value => { |
| | | if (value) { |
| | | productForm.value.productCategory = findNodeById(productOptions.value, value) || ""; |
| | | modelList({ id: value }).then((res) => { |
| | | productForm.value.productCategory = |
| | | findNodeById(productOptions.value, value) || ""; |
| | | modelList({ id: value }).then(res => { |
| | | modelOptions.value = res; |
| | | }); |
| | | } else { |
| | |
| | | modelOptions.value = []; |
| | | } |
| | | }; |
| | | const getProductModel = (value) => { |
| | | const index = modelOptions.value.findIndex((item) => item.id === value); |
| | | const getProductModel = value => { |
| | | const index = modelOptions.value.findIndex(item => item.id === value); |
| | | if (index !== -1) { |
| | | productForm.value.specificationModel = modelOptions.value[index].model; |
| | | productForm.value.unit = modelOptions.value[index].unit; |
| | |
| | | return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull |
| | | }; |
| | | function convertIdToValue(data) { |
| | | return data.map((item) => { |
| | | return data.map(item => { |
| | | const { id, children, ...rest } = item; |
| | | const newItem = { |
| | | ...rest, |
| | |
| | | } |
| | | // æäº¤äº§å表å |
| | | const submitProduct = () => { |
| | | proxy.$refs["productFormRef"].validate((valid) => { |
| | | proxy.$refs["productFormRef"].validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "edit") { |
| | | submitProductEdit(); |
| | |
| | | const submitProductEdit = () => { |
| | | productForm.value.salesLedgerId = currentId.value; |
| | | productForm.value.type = 2; |
| | | addOrUpdateSalesLedgerProduct(productForm.value).then((res) => { |
| | | addOrUpdateSalesLedgerProduct(productForm.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeProductDia(); |
| | | getPurchaseById({ id: currentId.value, type: 2 }).then((res) => { |
| | | getPurchaseById({ id: currentId.value, type: 2 }).then(res => { |
| | | productData.value = res.productData; |
| | | }); |
| | | }); |
| | |
| | | return; |
| | | } |
| | | if (operationType.value === "add") { |
| | | productSelectedRows.value.forEach((selectedRow) => { |
| | | productSelectedRows.value.forEach(selectedRow => { |
| | | const index = productData.value.findIndex( |
| | | (product) => product.id === selectedRow.id |
| | | product => product.id === selectedRow.id |
| | | ); |
| | | if (index !== -1) { |
| | | productData.value.splice(index, 1); |
| | |
| | | } else { |
| | | let ids = []; |
| | | if (productSelectedRows.value.length > 0) { |
| | | ids = productSelectedRows.value.map((item) => item.id); |
| | | ids = productSelectedRows.value.map(item => item.id); |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delProduct(ids).then((res) => { |
| | | delProduct(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | closeProductDia(); |
| | | getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then( |
| | | (res) => { |
| | | res => { |
| | | productData.value = res.productData; |
| | | } |
| | | ); |
| | |
| | | productFormVisible.value = false; |
| | | }; |
| | | // 审æ¹éè¿æ¹æ³ |
| | | const approvePurchase = (row) => { |
| | | ElMessageBox.confirm(`确认éè¿éè´ååå·ä¸º ${row.purchaseContractNumber} ç审æ¹ï¼`, '审æ¹ç¡®è®¤', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }).then(() => { |
| | | updateApprovalStatus({ id: row.id, approvalStatus: 1}).then((res)=>{ |
| | | proxy.$modal.msgSuccess('å®¡æ¹æå'); |
| | | const approvePurchase = row => { |
| | | ElMessageBox.confirm( |
| | | `确认éè¿éè´ååå·ä¸º ${row.purchaseContractNumber} ç审æ¹ï¼`, |
| | | "审æ¹ç¡®è®¤", |
| | | { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | } |
| | | ) |
| | | .then(() => { |
| | | updateApprovalStatus({ id: row.id, approvalStatus: 1 }).then(res => { |
| | | proxy.$modal.msgSuccess("å®¡æ¹æå"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | }).catch(() => { |
| | | proxy.$modal.msg('已忶审æ¹'); |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶审æ¹"); |
| | | }); |
| | | }; |
| | | |
| | | // å®¡æ¹æç»æ¹æ³ |
| | | const rejectPurchase = (row) => { |
| | | ElMessageBox.confirm(`确认æç»éè´ååå·ä¸º ${row.purchaseContractNumber} ç审æ¹ï¼`, '审æ¹ç¡®è®¤', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }).then(() => { |
| | | updateApprovalStatus({ id: row.id, approvalStatus: 2}).then((res)=>{ |
| | | proxy.$modal.msgSuccess('å®¡æ¹æå'); |
| | | const rejectPurchase = row => { |
| | | ElMessageBox.confirm( |
| | | `确认æç»éè´ååå·ä¸º ${row.purchaseContractNumber} ç审æ¹ï¼`, |
| | | "审æ¹ç¡®è®¤", |
| | | { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | } |
| | | ) |
| | | .then(() => { |
| | | updateApprovalStatus({ id: row.id, approvalStatus: 2 }).then(res => { |
| | | proxy.$modal.msgSuccess("å®¡æ¹æå"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | }).catch(() => { |
| | | proxy.$modal.msg('已忶审æ¹'); |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶审æ¹"); |
| | | }); |
| | | }; |
| | | |
| | |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | const unauthorizedData = selectedRows.value.filter(item => item.recorderName !== userStore.nickName); |
| | | const unauthorizedData = selectedRows.value.filter( |
| | | item => item.recorderName !== userStore.nickName |
| | | ); |
| | | if (unauthorizedData.length > 0) { |
| | | proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); |
| | | return; |
| | | } |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delPurchase(ids).then((res) => { |
| | | delPurchase(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | |
| | | ); |
| | | } |
| | | }; |
| | | const reverseMathNum = (field) => { |
| | | const reverseMathNum = field => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请å
éæ©ç¨ç"); |
| | | return; |
| | | } |
| | | const taxRate = Number(productForm.value.taxRate); |
| | | if (!taxRate) return; |
| | | if (field === 'taxInclusiveTotalPrice') { |
| | | if (field === "taxInclusiveTotalPrice") { |
| | | // å·²ç¥å«ç¨æ»ä»·åæ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | productForm.value.taxInclusiveUnitPrice = |
| | | (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2); |
| | | productForm.value.taxInclusiveUnitPrice = ( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | Number(productForm.value.quantity) |
| | | ).toFixed(2); |
| | | } |
| | | // å·²ç¥å«ç¨æ»ä»·åå«ç¨åä»·ï¼åç®æ°é |
| | | else if (productForm.value.taxInclusiveUnitPrice) { |
| | | productForm.value.quantity = |
| | | (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2); |
| | | productForm.value.quantity = ( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | Number(productForm.value.taxInclusiveUnitPrice) |
| | | ).toFixed(2); |
| | | } |
| | | // åç®ä¸å«ç¨æ»ä»· |
| | | productForm.value.taxExclusiveTotalPrice = |
| | | (Number(productForm.value.taxInclusiveTotalPrice) / (1 + taxRate / 100)).toFixed(2); |
| | | } else if (field === 'taxExclusiveTotalPrice') { |
| | | productForm.value.taxExclusiveTotalPrice = ( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | (1 + taxRate / 100) |
| | | ).toFixed(2); |
| | | } else if (field === "taxExclusiveTotalPrice") { |
| | | // åç®å«ç¨æ»ä»· |
| | | productForm.value.taxInclusiveTotalPrice = |
| | | (Number(productForm.value.taxExclusiveTotalPrice) * (1 + taxRate / 100)).toFixed(2); |
| | | productForm.value.taxInclusiveTotalPrice = ( |
| | | Number(productForm.value.taxExclusiveTotalPrice) * |
| | | (1 + taxRate / 100) |
| | | ).toFixed(2); |
| | | // å·²ç¥æ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | productForm.value.taxInclusiveUnitPrice = |
| | | (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2); |
| | | productForm.value.taxInclusiveUnitPrice = ( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | Number(productForm.value.quantity) |
| | | ).toFixed(2); |
| | | } |
| | | // å·²ç¥å«ç¨åä»·ï¼åç®æ°é |
| | | else if (productForm.value.taxInclusiveUnitPrice) { |
| | | productForm.value.quantity = |
| | | (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2); |
| | | productForm.value.quantity = ( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | Number(productForm.value.taxInclusiveUnitPrice) |
| | | ).toFixed(2); |
| | | } |
| | | } |
| | | }; |
| | | // éå®ååéæ©æ¹åæ¹æ³ |
| | | const salesLedgerChange = async (row) => { |
| | | const salesLedgerChange = async row => { |
| | | console.log("row", row); |
| | | var index = salesContractList.value.findIndex((item) => item.id == row); |
| | | var index = salesContractList.value.findIndex(item => item.id == row); |
| | | console.log("index", index); |
| | | if (index > -1) { |
| | | form.value.projectName = salesContractList.value[index].projectName; |
| | |
| | | }; |
| | | |
| | | // æ¾ç¤ºäºç»´ç |
| | | const showQRCode = async (row) => { |
| | | const showQRCode = async row => { |
| | | try { |
| | | // æå»ºäºç»´ç å
容ï¼åªå
å«éè´ååå·ï¼çº¯ææ¬ï¼ |
| | | const qrContent = row.purchaseContractNumber || ''; |
| | | const qrContent = row.purchaseContractNumber || ""; |
| | | // æ£æ¥å
容æ¯å¦ä¸ºç©º |
| | | if (!qrContent || qrContent.trim() === '') { |
| | | if (!qrContent || qrContent.trim() === "") { |
| | | proxy.$modal.msgWarning("è¯¥è¡æ²¡æéè´ååå·ï¼æ æ³çæäºç»´ç "); |
| | | return; |
| | | } |
| | |
| | | width: 200, |
| | | margin: 2, |
| | | color: { |
| | | dark: '#000000', |
| | | light: '#FFFFFF' |
| | | } |
| | | dark: "#000000", |
| | | light: "#FFFFFF", |
| | | }, |
| | | }); |
| | | qrCodeDialogVisible.value = true; |
| | | } catch (error) { |
| | | console.error('çæäºç»´ç 失败:', error); |
| | | console.error("çæäºç»´ç 失败:", error); |
| | | proxy.$modal.msgError("çæäºç»´ç 失败ï¼" + error.message); |
| | | } |
| | | }; |
| | |
| | | return; |
| | | } |
| | | |
| | | const a = document.createElement('a'); |
| | | const a = document.createElement("a"); |
| | | a.href = qrCodeUrl.value; |
| | | a.download = `éè´ååå·äºç»´ç _${new Date().getTime()}.png`; |
| | | document.body.appendChild(a); |
| | |
| | | scanRemark: "", |
| | | }); |
| | | const scanAddRules = { |
| | | purchaseContractNumber: [{ required: true, message: "请è¾å
¥éè´ååå·", trigger: "blur" }], |
| | | supplierName: [{ required: true, message: "请è¾å
¥ä¾åºååç§°", trigger: "blur" }], |
| | | purchaseContractNumber: [ |
| | | { required: true, message: "请è¾å
¥éè´ååå·", trigger: "blur" }, |
| | | ], |
| | | supplierName: [ |
| | | { required: true, message: "请è¾å
¥ä¾åºååç§°", trigger: "blur" }, |
| | | ], |
| | | projectName: [{ required: true, message: "请è¾å
¥é¡¹ç®åç§°", trigger: "blur" }], |
| | | }; |
| | | |
| | |
| | | }; |
| | | |
| | | // è§£ææ«ç å
å®¹ï¼æ¨¡æè§£æäºç»´ç æ°æ®ï¼ |
| | | const parseScanContent = (content) => { |
| | | const parseScanContent = content => { |
| | | if (!content) return; |
| | | |
| | | // 模æè§£æäºç»´ç å
容ï¼è¿éå¯ä»¥æ ¹æ®å®é
éæ±è°æ´è§£æé»è¾ |
| | | // å设æ«ç å
å®¹æ ¼å¼ä¸ºï¼ååå·|ä¾åºå|项ç®|éé¢|仿¬¾æ¹å¼ |
| | | const parts = content.split('|'); |
| | | const parts = content.split("|"); |
| | | if (parts.length >= 3) { |
| | | scanAddForm.purchaseContractNumber = parts[0] || ""; |
| | | scanAddForm.supplierName = parts[1] || ""; |
| | |
| | | |
| | | // æäº¤æ«ç æ°å¢ |
| | | const submitScanAdd = () => { |
| | | proxy.$refs["scanAddFormRef"].validate((valid) => { |
| | | proxy.$refs["scanAddFormRef"].validate(valid => { |
| | | if (valid) { |
| | | // æå»ºæ°å¢æ°æ® |
| | | const newData = { |
| | |
| | | recorderName: scanAddForm.recorderName, |
| | | entryDate: getCurrentDate(), |
| | | remark: scanAddForm.scanRemark, |
| | | type: 2 |
| | | type: 2, |
| | | }; |
| | | |
| | | // æ¨¡ææ°å¢æå |
| | |
| | | }; |
| | | |
| | | // æå¼æ«ç ç»è®°å¯¹è¯æ¡ |
| | | const openScanDialog = (row) => { |
| | | const openScanDialog = row => { |
| | | scanForm.purchaseContractNumber = row.purchaseContractNumber; |
| | | scanForm.supplierName = row.supplierName; |
| | | scanForm.projectName = row.projectName; |
| | |
| | | |
| | | // æäº¤æ«ç ç»è®° |
| | | const submitScan = () => { |
| | | proxy.$refs["scanFormRef"].validate((valid) => { |
| | | proxy.$refs["scanFormRef"].validate(valid => { |
| | | if (valid) { |
| | | // æ·»å æ«ç è®°å½ |
| | | scanRecords.value.push({ |
| | |
| | | |
| | | // æ·»å è¡ç±»åæ¹æ³ |
| | | const tableRowClassName = ({ row }) => { |
| | | return row.isInvalid ? 'invalid-row' : ''; |
| | | return row.isInvalid ? "invalid-row" : ""; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | <FileListDialog ref="fileListDialogRef" |
| | | v-model="fileDialogVisible" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :delete-method="handleAttachmentDelete" |
| | | :rules-regulations-management-id="currentFileRuleId" |
| | | :name-column-label="'éä»¶åç§°'" |
| | | @upload="handleAttachmentUpload"/> |
| | | <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="rules_regulations_management" :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | addReadingStatus, |
| | | updateReadingStatus, |
| | | } from "@/api/collaborativeApproval/sealManagement.js"; |
| | | import FileListDialog from "@/components/Dialog/FileListDialog.vue"; |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | import { |
| | | listRuleFiles, |
| | | delRuleFile, |
| | |
| | | total: 0, |
| | | }); |
| | | // éä»¶å¼¹çª |
| | | const fileDialogVisible = ref(false); |
| | | const fileListDialogRef = ref(null); |
| | | const currentFileRuleId = ref(null); |
| | | const filePage = reactive({ |
| | | current: 1, |
| | | size: 1000, |
| | | total: 0, |
| | | }); |
| | | // è§ç« å¶åº¦ç¸å
³ |
| | | const showRegulationDialog = ref(false); |
| | | const showRegulationDetailDialog = ref(false); |
| | |
| | | ); |
| | | }; |
| | | |
| | | // éä»¶ï¼æ¥è¯¢ |
| | | const fetchRuleFiles = async rulesRegulationsManagementId => { |
| | | const params = { |
| | | current: filePage.current, |
| | | size: filePage.size, |
| | | rulesRegulationsManagementId, |
| | | }; |
| | | const res = await listRuleFiles(params); |
| | | const records = res?.data?.records || []; |
| | | filePage.total = res?.data?.total || records.length; |
| | | const mapped = records.map(item => ({ |
| | | id: item.id, |
| | | name: item.fileName || item.name, |
| | | url: item.fileUrl || item.url, |
| | | raw: item, |
| | | })); |
| | | fileListDialogRef.value?.setList(mapped); |
| | | }; |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const openFileDialog = async row => { |
| | | currentFileRuleId.value = row.id; |
| | | fileDialogVisible.value = true; |
| | | await fetchRuleFiles(row.id); |
| | | }; |
| | | const recordId =ref(0) |
| | | const fileDialogVisible = ref(false) |
| | | |
| | | // å·æ°éä»¶å表 |
| | | const refreshFileList = async () => { |
| | | if (!currentFileRuleId.value) return; |
| | | await fetchRuleFiles(currentFileRuleId.value); |
| | | }; |
| | | |
| | | // ä¸ä¼ éä»¶ï¼ç±åç»ä»¶è§¦åï¼ |
| | | const handleAttachmentUpload = async filePayload => { |
| | | if (!currentFileRuleId.value) return; |
| | | const payload = { |
| | | name: filePayload?.fileName || filePayload?.name, |
| | | url: filePayload?.fileUrl || filePayload?.url, |
| | | rulesRegulationsManagementId: currentFileRuleId.value, |
| | | }; |
| | | await addRuleFile(payload); |
| | | ElMessage.success("æä»¶ä¸ä¼ æå"); |
| | | await refreshFileList(); |
| | | }; |
| | | |
| | | // å é¤éä»¶ |
| | | const handleAttachmentDelete = async row => { |
| | | if (!row?.id) return false; |
| | | try { |
| | | await ElMessageBox.confirm("确认å é¤è¯¥éä»¶ï¼", "æç¤º", { type: "warning" }); |
| | | } catch { |
| | | return false; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFileDialog = async (row) => { |
| | | recordId.value = row.id |
| | | fileDialogVisible.value = true |
| | | } |
| | | await delRuleFile([row.id]); |
| | | ElMessage.success("å 餿å"); |
| | | await refreshFileList(); |
| | | }; |
| | | |
| | | // è·åè§ç« å¶åº¦åè¡¨æ°æ® |
| | | const getRegulationList = async () => { |
| | |
| | | </el-form-item> |
| | | <el-form-item label="ç´§æ¥ç¨åº¦" prop="urgency"> |
| | | <el-radio-group v-model="sealForm.urgency"> |
| | | <el-radio label="normal">æ®é</el-radio> |
| | | <el-radio label="urgent">ç´§æ¥</el-radio> |
| | | <el-radio label="very-urgent">ç¹æ¥</el-radio> |
| | | <el-radio value="normal">æ®é</el-radio> |
| | | <el-radio value="urgent">ç´§æ¥</el-radio> |
| | | <el-radio value="very-urgent">ç¹æ¥</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="éä»¶ä¸ä¼ "> |
| | | <AttachmentUploadFile |
| | | v-model:fileList="sealForm.storageBlobDTOs" |
| | | :limit="10" |
| | | :fileSize="50" |
| | | buttonText="ç¹å»ä¸ä¼ éä»¶" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </FormDialog> |
| | |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·åå " :span="2">{{ currentSealDetail.reason }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <!-- éä»¶å表 --> |
| | | <div v-if="currentSealDetail.storageBlobVOList?.length || currentSealDetail.storageBlobDTOs?.length" class="attachment-section"> |
| | | <div class="attachment-title">éä»¶å表ï¼</div> |
| | | <el-table :data="currentSealDetail.storageBlobVOList || currentSealDetail.storageBlobDTOs" border class="attachment-table"> |
| | | <el-table-column label="éä»¶åç§°" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ scope.row.originalFilename || scope.row.name || scope.row.fileName || 'æªå½åæä»¶' }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="æä½" width="150" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="previewFile(scope.row)">é¢è§</el-button> |
| | | <el-button link type="primary" size="small" @click="downloadFile(scope.row)">ä¸è½½</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </FormDialog> |
| | | <!-- æä»¶é¢è§ç»ä»¶ --> |
| | | <FilePreview ref="filePreviewRef" /> |
| | | |
| | | </div> |
| | | </template> |
| | |
| | | import useUserStore from '@/store/modules/user' |
| | | import FormDialog from '@/components/Dialog/FormDialog.vue' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import AttachmentUploadFile from '@/components/AttachmentUpload/file/index.vue' |
| | | import FilePreview from '@/components/filePreview/index.vue' |
| | | import download from '@/plugins/download.js' |
| | | |
| | | // ååºå¼æ°æ® |
| | | // ç¨å°ç³è¯·ç¸å
³ |
| | |
| | | const tableLoading = ref(false) |
| | | const showSealDetailDialog = ref(false) |
| | | const currentSealDetail = ref(null) |
| | | const filePreviewRef = ref(null) |
| | | const sealFormRef = ref() |
| | | const userList = ref([]) |
| | | const sealForm = reactive({ |
| | |
| | | reason: '', |
| | | approveUserId: '', |
| | | urgency: 'normal', |
| | | status: 'pending' |
| | | status: 'pending', |
| | | storageBlobDTOs: [] |
| | | }) |
| | | |
| | | const sealRules = { |
| | |
| | | reason: '', |
| | | approveUserId: '', |
| | | urgency: 'normal', |
| | | status: 'pending' |
| | | status: 'pending', |
| | | storageBlobDTOs: [] |
| | | }) |
| | | } |
| | | }).catch(err => { |
| | |
| | | reason: '', |
| | | approveUserId: '', |
| | | urgency: 'normal', |
| | | status: 'pending' |
| | | status: 'pending', |
| | | storageBlobDTOs: [] |
| | | }) |
| | | // æ¸
é¤è¡¨åéªè¯ç¶æ |
| | | if (sealFormRef.value) { |
| | |
| | | const viewSealDetail = (row) => { |
| | | currentSealDetail.value = row |
| | | showSealDetailDialog.value = true |
| | | } |
| | | |
| | | // é¢è§æä»¶ |
| | | const previewFile = (row) => { |
| | | const url = row.previewURL || row.previewUrl || row.url |
| | | if (url && filePreviewRef.value) { |
| | | filePreviewRef.value.open(url) |
| | | } else { |
| | | ElMessage.warning('æä»¶å°åæ æï¼æ æ³é¢è§') |
| | | } |
| | | } |
| | | |
| | | // ä¸è½½æä»¶ |
| | | const downloadFile = (row) => { |
| | | const url = row.downloadURL || row.downloadUrl || row.url |
| | | if (url) { |
| | | const filename = row.originalFilename || row.name || row.fileName || 'download' |
| | | download.byUrl(url, filename) |
| | | } else { |
| | | ElMessage.warning('æä»¶å°åæ æï¼æ æ³ä¸è½½') |
| | | } |
| | | } |
| | | // 审æ¹ç¨å°ç³è¯· |
| | | const approveSeal = (row) => { |
| | |
| | | .ml-10 { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .attachment-section { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .attachment-title { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 10px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .attachment-table { |
| | | border-radius: 4px; |
| | | } |
| | | </style> |
| | |
| | | tableData.value = list |
| | | } |
| | | const downLoadFile = (row) => { |
| | | proxy.$download.name(row.url); |
| | | |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | } |
| | | const lookFile = (row) => { |
| | | filePreviewRef.value.open(row.url) |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search-wrapper"> |
| | | <div class="search-wrapper mb20"> |
| | | <el-form |
| | | :model="searchForm" |
| | | class="demo-form-inline" |
| | |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | <FileListDialog |
| | | ref="fileListRef" |
| | | v-model="fileListDialogVisible" |
| | | title="å®åéä»¶" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :upload-method="handleFileUpload" |
| | | :delete-method="handleFileDelete" |
| | | /> |
| | | <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="after_sales_service" :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue"; |
| | | import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick, defineAsyncComponent} from "vue"; |
| | | import FormDia from "@/views/customerService/afterSalesHandling/components/formDia.vue"; |
| | | import FileListDialog from "@/components/Dialog/FileListDialog.vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import request from "@/utils/request"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import { |
| | | afterSalesServiceListPage, |
| | | afterSalesServiceFileListPage, |
| | | afterSalesServiceFileDel, |
| | | } from "@/api/customerService/index.js"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore() |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | |
| | | }) |
| | | } |
| | | |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const recordId =ref(0) |
| | | const fileDialogVisible = ref(false) |
| | | |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = async (row) => { |
| | | currentFileRow.value = row |
| | | try { |
| | | const res = await afterSalesServiceFileListPage({ |
| | | afterSalesServiceId: row.id, |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | if (res.code === 200 && fileListRef.value) { |
| | | const fileList = (res.data?.records || []).map((item) => ({ |
| | | name: item.name || item.fileName, |
| | | url: item.url || item.fileUrl, |
| | | id: item.id, |
| | | ...item, |
| | | })) |
| | | fileListRef.value.open(fileList) |
| | | fileListDialogVisible.value = true |
| | | } else { |
| | | fileListRef.value?.open([]) |
| | | fileListDialogVisible.value = true |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("è·åéä»¶å表失败") |
| | | fileListRef.value?.open([]) |
| | | fileListDialogVisible.value = true |
| | | } |
| | | } |
| | | |
| | | // ä¸ä¼ éä»¶ |
| | | const handleFileUpload = async () => { |
| | | if (!currentFileRow.value) { |
| | | proxy.$modal.msgWarning("请å
éæ©æ°æ®") |
| | | return |
| | | } |
| | | return new Promise((resolve) => { |
| | | const input = document.createElement("input") |
| | | input.type = "file" |
| | | input.style.display = "none" |
| | | input.onchange = async (e) => { |
| | | const file = e.target.files[0] |
| | | if (!file) { |
| | | resolve(null) |
| | | return |
| | | } |
| | | try { |
| | | const formData = new FormData() |
| | | formData.append("file", file) |
| | | formData.append("id", currentFileRow.value.id) |
| | | const uploadRes = await request({ |
| | | url: "/afterSalesService/file/upload", |
| | | method: "post", |
| | | data: formData, |
| | | headers: { |
| | | "Content-Type": "multipart/form-data", |
| | | Authorization: `Bearer ${getToken()}`, |
| | | }, |
| | | }) |
| | | if (uploadRes.code === 200) { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå") |
| | | // éæ°è·åæä»¶å表 |
| | | const listRes = await afterSalesServiceFileListPage({ |
| | | afterSalesServiceId: currentFileRow.value.id, |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | if (listRes.code === 200 && fileListRef.value) { |
| | | const fileList = (listRes.data?.records || []).map((item) => ({ |
| | | name: item.fileName, |
| | | url: item.fileUrl, |
| | | id: item.id, |
| | | ...item, |
| | | })) |
| | | fileListRef.value.setList(fileList) |
| | | } |
| | | resolve({ name: file.name, url: "", id: null }) |
| | | } else { |
| | | proxy.$modal.msgError(uploadRes.msg || "æä»¶ä¸ä¼ 失败") |
| | | resolve(null) |
| | | } |
| | | } catch (err) { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败") |
| | | resolve(null) |
| | | } finally { |
| | | document.body.removeChild(input) |
| | | } |
| | | } |
| | | document.body.appendChild(input) |
| | | input.click() |
| | | }) |
| | | } |
| | | |
| | | // å é¤éä»¶ |
| | | const handleFileDelete = async (row) => { |
| | | try { |
| | | // æ·»å ç¡®è®¤å¯¹è¯æ¡ |
| | | const confirmResult = await ElMessageBox.confirm( |
| | | 'ç¡®å®è¦å é¤è¿ä¸ªéä»¶åï¼', |
| | | 'å é¤ç¡®è®¤', |
| | | { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ) |
| | | |
| | | if (confirmResult === 'confirm') { |
| | | const res = await afterSalesServiceFileDel(row.id) |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("å 餿å") |
| | | if (currentFileRow.value && fileListRef.value) { |
| | | const listRes = await afterSalesServiceFileListPage({ |
| | | afterSalesServiceId: currentFileRow.value.id, |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | if (listRes.code === 200) { |
| | | const fileList = (listRes.data?.records || []).map((item) => ({ |
| | | name: item.fileName, |
| | | url: item.fileUrl, |
| | | id: item.id, |
| | | ...item, |
| | | })) |
| | | fileListRef.value.setList(fileList) |
| | | } |
| | | } |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "å é¤å¤±è´¥") |
| | | return false |
| | | } |
| | | } |
| | | } catch (error) { |
| | | // å¦æç¨æ·åæ¶å é¤ï¼ä¸æ¾ç¤ºéè¯¯ä¿¡æ¯ |
| | | if (error !== 'cancel') { |
| | | proxy.$modal.msgError("å é¤å¤±è´¥") |
| | | } |
| | | return false |
| | | } |
| | | recordId.value = row.id |
| | | fileDialogVisible.value = true |
| | | } |
| | | |
| | | // æ¥è¯¢å表 |
| | |
| | | v-model="form.productName" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | :disabled="isFieldDisabled('productName')" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | v-model="form.batchNumber" |
| | | placeholder="请è¾å
¥äº§åæ¹å·" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | :disabled="isFieldDisabled('batchNumber')" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | type="date" |
| | | placeholder="è¯·éæ©ä¸´ææ¥æ" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | :disabled="isFieldDisabled('expiryDate')" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | :min="0" |
| | | placeholder="请è¾å
¥åºåæ°é" |
| | | style="width: 100%" |
| | | :disabled="operationType === 'view'" |
| | | :disabled="isFieldDisabled('stockQuantity')" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | v-model="form.customerName" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | :disabled="isFieldDisabled('customerName')" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | v-model="form.contactPhone" |
| | | placeholder="请è¾å
¥èç³»çµè¯" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | :disabled="isFieldDisabled('contactPhone')" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | v-model="form.problemDesc" |
| | | placeholder="请è¾å
¥é®é¢æè¿°" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | :disabled="isFieldDisabled('problemDesc')" |
| | | type="textarea" |
| | | :rows="3" |
| | | /> |
| | |
| | | v-model="form.handlerId" |
| | | placeholder="è¯·éæ©å¤ç人" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | :disabled="isFieldDisabled('handlerId')" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | |
| | | type="date" |
| | | placeholder="è¯·éæ©å¤çæ¥æ" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | :disabled="isFieldDisabled('handleDate')" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | v-model="form.handleResult" |
| | | placeholder="请è¾å
¥å¤çç»æ" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | :disabled="isFieldDisabled('handleResult')" |
| | | type="textarea" |
| | | :rows="3" |
| | | /> |
| | |
| | | return 'æ°å¢ä¸´æå®å'; |
| | | case 'edit': |
| | | return 'ç¼è¾ä¸´æå®å'; |
| | | case 'handle': |
| | | return 'å¤ç临æå®å'; |
| | | case 'view': |
| | | return 'æ¥ç临æå®å'; |
| | | default: |
| | |
| | | }) |
| | | const { form, rules } = toRefs(data); |
| | | const userList = ref([]) |
| | | const handleEditableFields = ["handlerId", "handleDate", "handleResult"]; |
| | | |
| | | const isFieldDisabled = (field) => { |
| | | if (operationType.value === "view") return true; |
| | | if (operationType.value === "handle") return !handleEditableFields.includes(field); |
| | | return false; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | |
| | | } else { |
| | | // ç¼è¾ææ¥çæ¶å¡«å
æ°æ® |
| | | form.value = { ...row }; |
| | | if (type === 'edit' && !form.value.handlerId) { |
| | | if (type === 'handle' && !form.value.handlerId) { |
| | | form.value.handlerId = userStore.id; |
| | | form.value.handleDate = getCurrentDate(); |
| | | } |
| | |
| | | } |
| | | |
| | | const submitForm = () => { |
| | | if (operationType.value === "handle") { |
| | | if (!form.value.handlerId || !form.value.handleDate || !form.value.handleResult) { |
| | | proxy.$modal.msgWarning("请填åå¤ç人ãå¤çæ¥æåå¤çç»æ"); |
| | | return; |
| | | } |
| | | handleSubmit(); |
| | | return; |
| | | } |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | handleSubmit(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | const submitData = { |
| | | id: form.value.id, |
| | | productName: form.value.productName, |
| | |
| | | customerName: form.value.customerName, |
| | | contactPhone: form.value.contactPhone, |
| | | disRes: form.value.problemDesc, |
| | | status: form.value.status, |
| | | status: operationType.value === "handle" ? 2 : form.value.status, |
| | | disposeUserId: form.value.handlerId, |
| | | disposeNickName: userList.value.find(item => item.userId === form.value.handlerId)?.nickName, |
| | | disposeResult: form.value.handleResult, |
| | |
| | | |
| | | const apiCall = operationType.value === 'add' ? expiryAfterSalesAdd : expiryAfterSalesUpdate; |
| | | apiCall(submitData).then(() => { |
| | | proxy.$modal.msgSuccess(operationType.value === 'add' ? "æ°å¢æå" : "æ´æ°æå"); |
| | | const successText = operationType.value === "add" ? "æ°å¢æå" : operationType.value === "handle" ? "å¤çæå" : "æ´æ°æå"; |
| | | proxy.$modal.msgSuccess(successText); |
| | | closeDia(); |
| | | }).catch(error => { |
| | | console.error('æäº¤æ°æ®å¤±è´¥:', error); |
| | | proxy.$modal.msgError('æäº¤æ°æ®å¤±è´¥ï¼è¯·ç¨åéè¯'); |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div class="search_form mb20"> |
| | | <div> |
| | | <span class="search_title">ä¸´ææ¥æï¼</span> |
| | | <el-date-picker |
| | |
| | | |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="openForm('view', row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="openForm('edit', row)" v-if="row.status === 1">ç¼è¾</el-button> |
| | | <el-button type="primary" link @click="openForm('handle', row)" v-if="row.status === 1">å¤ç</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | <el-dialog v-model="dialogFormVisible" |
| | | title="æ°å¢å®åå" |
| | | width="90%" |
| | | @close="closeDia" |
| | | > |
| | | @close="closeDia"> |
| | | <div> |
| | | <span class="descriptions">åºç¡èµæ</span> |
| | | <el-form |
| | | :model="form" |
| | | <el-form :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | > |
| | | ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="4"> |
| | | <el-form-item label="客æ·åç§°ï¼" prop="customerName"> |
| | | <el-select |
| | | v-model="form.customerName" |
| | | <el-form-item label="客æ·åç§°ï¼" |
| | | prop="customerName"> |
| | | <el-select v-model="form.customerName" |
| | | filterable |
| | | @change="customerNameChange" |
| | | > |
| | | <el-option |
| | | v-for="item in customerNameOptions" |
| | | @change="customerNameChange"> |
| | | <el-option v-for="item in customerNameOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="å®åç±»åï¼" prop="serviceType"> |
| | | <el-select |
| | | v-model="form.serviceType" |
| | | filterable |
| | | > |
| | | <el-option |
| | | v-for="dict in serviceTypeOptions" |
| | | <el-form-item label="å®åç±»åï¼" |
| | | prop="serviceType"> |
| | | <el-select v-model="form.serviceType" |
| | | filterable> |
| | | <el-option v-for="dict in serviceTypeOptions" |
| | | :key="dict.value" |
| | | :label="dict.label" |
| | | :value="dict.value" |
| | | /> |
| | | :value="dict.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="å
³èéå®åå·ï¼" prop="salesContractNo"> |
| | | <el-select |
| | | v-model="form.salesContractNo" |
| | | <el-form-item label="å
³èéå®åå·ï¼" |
| | | prop="salesContractNo"> |
| | | <el-select v-model="form.salesContractNo" |
| | | @change="associatedSalesOrderNumberChange" |
| | | filterable |
| | | > |
| | | <el-option |
| | | v-for="item in associatedSalesOrderNumberOptions" |
| | | filterable> |
| | | <el-option v-for="item in associatedSalesOrderNumberOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="ç´§æ¥ç¨åº¦ï¼" prop="urgency"> |
| | | <el-select |
| | | v-model="form.urgency" |
| | | filterable |
| | | > |
| | | <el-option |
| | | v-for="dict in urgencyOptions" |
| | | <el-form-item label="ç´§æ¥ç¨åº¦ï¼" |
| | | prop="urgency"> |
| | | <el-select v-model="form.urgency" |
| | | filterable> |
| | | <el-option v-for="dict in urgencyOptions" |
| | | :key="dict.value" |
| | | :label="dict.label" |
| | | :value="dict.value" |
| | | /> |
| | | :value="dict.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="é®é¢æè¿°ï¼" prop="proDesc"> |
| | | <el-input |
| | | v-model="form.proDesc" |
| | | placeholder="请è¾å
¥é®é¢æè¿°" |
| | | /> |
| | | <el-form-item label="é®é¢æè¿°ï¼" |
| | | prop="proDesc"> |
| | | <el-input v-model="form.proDesc" |
| | | placeholder="请è¾å
¥é®é¢æè¿°" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <div style="padding-top: 20px"> |
| | | <div style="display: flex; justify-content: space-between"> |
| | | <span class="descriptions">å
³è产å</span> |
| | | <el-button |
| | | type="primary" |
| | | <el-button type="primary" |
| | | style="margin-right: 12px; margin-bottom: 10px" |
| | | @click="isShowProductSelectDialog = true" |
| | | > |
| | | @click="isShowProductSelectDialog = true"> |
| | | éæ©äº§å |
| | | </el-button> |
| | | </div> |
| | | <PIMTable |
| | | :isShowPagination="false" |
| | | <PIMTable :isShowPagination="false" |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | > |
| | | :tableData="tableData"> |
| | | <template #approveStatus="{ row }"> |
| | | <el-tag :type="getApproveStatusType(row)" size="small"> |
| | | <el-tag :type="getApproveStatusType(row)" |
| | | size="small"> |
| | | {{ getApproveStatusText(row) }} |
| | | </el-tag> |
| | | </template> |
| | | <template #shippingStatus="{ row }"> |
| | | <el-tag :type="getShippingStatusType(row)" size="small"> |
| | | <el-tag :type="getShippingStatusType(row)" |
| | | size="small"> |
| | | {{ getShippingStatusText(row) }} |
| | | </el-tag> |
| | | </template> |
| | |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- éæ©äº§åå¼¹çª --> |
| | | <ProductSelectDialog |
| | | v-model="isShowProductSelectDialog" |
| | | <ProductSelectDialog v-model="isShowProductSelectDialog" |
| | | :products="currentSalesOrderProducts" |
| | | :selected-ids="currentSelectedProductIds" |
| | | @confirm="handleSelectProducts" |
| | | /> |
| | | @confirm="handleSelectProducts" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import ProductSelectDialog from "./ProductSelectDialog.vue"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import {afterSalesServiceAdd, afterSalesServiceUpdate, getAllCustomerList, getSalesLedger } from "@/api/customerService/index.js"; |
| | | import { |
| | | afterSalesServiceAdd, |
| | | afterSalesServiceUpdate, |
| | | getAllCustomerList, |
| | | getSalesLedger, |
| | | } from "@/api/customerService/index.js"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["close"]); |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const formRef = ref(null) |
| | | const customerNameOptions = ref([]) |
| | | const operationType = ref(""); |
| | | const formRef = ref(null); |
| | | const customerNameOptions = ref([]); |
| | | const userStore = useUserStore(); |
| | | |
| | | const data = reactive({ |
| | |
| | | customerId: null, |
| | | salesContractNo: "", |
| | | proDesc: "", |
| | | customerName: "" |
| | | customerName: "", |
| | | }, |
| | | rules: { |
| | | customerName: [{required: true, message: "è¯·éæ©å®¢æ·åç§°", trigger: "change"}], |
| | | serviceType: [{required: true, message: "è¯·éæ©å®åç±»å", trigger: "change"}], |
| | | customerName: [ |
| | | { required: true, message: "è¯·éæ©å®¢æ·åç§°", trigger: "change" }, |
| | | ], |
| | | serviceType: [ |
| | | { required: true, message: "è¯·éæ©å®åç±»å", trigger: "change" }, |
| | | ], |
| | | urgency: [{required: true, message: "è¯·éæ©ç´§æ¥ç¨åº¦", trigger: "change"}], |
| | | feedbackDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | } |
| | | }) |
| | | }, |
| | | }); |
| | | |
| | | // èªå®ä¹æ ¡éªå½æ°ï¼å¤ææ¯å¦éè¦æ ¡éªå®åç¼å· |
| | | |
| | | const { form, rules } = toRefs(data); |
| | | const userList = ref([]) |
| | | const userList = ref([]); |
| | | |
| | | const formatCurrency = (val) => { |
| | | if (val === null || val === undefined || val === '') return '-' |
| | | const num = Number(val) |
| | | return Number.isFinite(num) ? num.toFixed(2) : '-' |
| | | } |
| | | const formatCurrency = val => { |
| | | if (val === null || val === undefined || val === "") return "-"; |
| | | const num = Number(val); |
| | | return Number.isFinite(num) ? num.toFixed(2) : "-"; |
| | | }; |
| | | |
| | | const { post_sale_waiting_list, degree_of_urgency } = proxy.useDict( |
| | | "post_sale_waiting_list", |
| | |
| | | const serviceTypeOptions = computed(() => post_sale_waiting_list?.value || []); |
| | | const urgencyOptions = computed(() => degree_of_urgency?.value || []); |
| | | |
| | | const getProductRowId = (row) => { |
| | | return row?.id ?? row?.productModelId ?? row?.modelId ?? `${row?.productCategory || row?.productName || ""}-${row?.specificationModel || row?.model || ""}-${row?.unit || ""}` |
| | | } |
| | | const getProductRowId = row => { |
| | | return ( |
| | | row?.id ?? |
| | | row?.productModelId ?? |
| | | row?.modelId ?? |
| | | `${row?.productCategory || row?.productName || ""}-${ |
| | | row?.specificationModel || row?.model || "" |
| | | }-${row?.unit || ""}` |
| | | ); |
| | | }; |
| | | |
| | | const normalizeProductRow = (row) => { |
| | | const normalizeProductRow = row => { |
| | | return { |
| | | ...row, |
| | | id: getProductRowId(row), |
| | | productCategory: row?.productCategory ?? row?.productName ?? '', |
| | | specificationModel: row?.specificationModel ?? row?.model ?? '', |
| | | unit: row?.unit ?? '', |
| | | productCategory: row?.productCategory ?? row?.productName ?? "", |
| | | specificationModel: row?.specificationModel ?? row?.model ?? "", |
| | | unit: row?.unit ?? "", |
| | | approveStatus: row?.approveStatus ?? null, |
| | | shippingStatus: row?.shippingStatus ?? '', |
| | | expressCompany: row?.expressCompany ?? '', |
| | | expressNumber: row?.expressNumber ?? '', |
| | | shippingCarNumber: row?.shippingCarNumber ?? '', |
| | | shippingDate: row?.shippingDate ?? '', |
| | | shippingStatus: row?.shippingStatus ?? "", |
| | | expressCompany: row?.expressCompany ?? "", |
| | | expressNumber: row?.expressNumber ?? "", |
| | | shippingCarNumber: row?.shippingCarNumber ?? "", |
| | | shippingDate: row?.shippingDate ?? "", |
| | | quantity: row?.quantity ?? 0, |
| | | taxRate: row?.taxRate ?? 0, |
| | | taxInclusiveUnitPrice: row?.taxInclusiveUnitPrice ?? 0, |
| | | taxInclusiveTotalPrice: row?.taxInclusiveTotalPrice ?? 0, |
| | | taxExclusiveTotalPrice: row?.taxExclusiveTotalPrice ?? 0, |
| | | } |
| | | } |
| | | noQuantity: row?.noQuantity ?? 0, |
| | | }; |
| | | }; |
| | | |
| | | const tableColumn = ref([ |
| | | { label: "产å大类", prop: "productCategory" }, |
| | | { label: "尺寸", prop: "specificationModel" }, |
| | | { label: "è§æ ¼åå·", prop: "specificationModel" }, |
| | | { label: "åä½", prop: "unit" }, |
| | | { |
| | | label: "产åç¶æ", |
| | |
| | | }, |
| | | { label: "å¿«éå
¬å¸", prop: "expressCompany", width: 140 }, |
| | | { label: "å¿«éåå·", prop: "expressNumber", width: 160 }, |
| | | { label: "å货车ç", prop: "shippingCarNumber", minWidth: 100, align: "center" }, |
| | | { |
| | | label: "å货车ç", |
| | | prop: "shippingCarNumber", |
| | | minWidth: 100, |
| | | align: "center", |
| | | }, |
| | | { label: "åè´§æ¥æ", prop: "shippingDate", minWidth: 100, align: "center" }, |
| | | { label: "æ°é", prop: "quantity", width: 100 }, |
| | | { label: "ç¨ç(%)", prop: "taxRate", width: 100 }, |
| | |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | fixed: "right", |
| | | operation: [ |
| | | { |
| | | name: "å é¤", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | tableData.value = tableData.value.filter(i => getProductRowId(i) !== getProductRowId(row)) |
| | | clickFun: row => { |
| | | tableData.value = tableData.value.filter( |
| | | i => getProductRowId(i) !== getProductRowId(row) |
| | | ); |
| | | }, |
| | | |
| | | }, |
| | | ], |
| | | }, |
| | | ]) |
| | | const tableData = ref([]) |
| | | ]); |
| | | const tableData = ref([]); |
| | | // éæ©äº§åå¼¹çª |
| | | const isShowProductSelectDialog = ref(false) |
| | | const handleSelectProducts = (rows) => { |
| | | if (!Array.isArray(rows)) return |
| | | const existingIds = new Set(tableData.value.map(i => String(getProductRowId(i)))) |
| | | const isShowProductSelectDialog = ref(false); |
| | | const handleSelectProducts = rows => { |
| | | if (!Array.isArray(rows)) return; |
| | | const existingIds = new Set( |
| | | tableData.value.map(i => String(getProductRowId(i))) |
| | | ); |
| | | const mapped = rows |
| | | .map(normalizeProductRow) |
| | | .filter(r => !existingIds.has(String(getProductRowId(r)))) |
| | | tableData.value = tableData.value.concat(mapped) |
| | | } |
| | | .filter(r => !existingIds.has(String(getProductRowId(r)))); |
| | | tableData.value = tableData.value.concat(mapped); |
| | | }; |
| | | const currentSelectedProductIds = computed(() => { |
| | | return tableData.value.map(item => getProductRowId(item)).filter(item => item !== undefined && item !== null && item !== '') |
| | | }) |
| | | return tableData.value |
| | | .map(item => getProductRowId(item)) |
| | | .filter(item => item !== undefined && item !== null && item !== ""); |
| | | }); |
| | | |
| | | const associatedSalesOrderNumberChange = () => { |
| | | const opt = associatedSalesOrderNumberOptions.value.find( |
| | | (item) => item.value === form.value.salesContractNo |
| | | ) |
| | | tableData.value = (opt?.productData || []).map(normalizeProductRow) |
| | | form.value.salesLedgerId = opt?.id || null |
| | | } |
| | | item => item.value === form.value.salesContractNo |
| | | ); |
| | | tableData.value = (opt?.productData || []).map(normalizeProductRow); |
| | | form.value.salesLedgerId = opt?.id || null; |
| | | }; |
| | | |
| | | const associatedSalesOrderNumberOptions = ref([]) |
| | | const associatedSalesOrderNumberOptions = ref([]); |
| | | |
| | | const currentSalesOrderProducts = computed(() => { |
| | | const opt = associatedSalesOrderNumberOptions.value.find( |
| | | (item) => item.value === form.value.salesContractNo |
| | | ) |
| | | return (opt?.productData || []).map(normalizeProductRow) |
| | | }) |
| | | item => item.value === form.value.salesContractNo |
| | | ); |
| | | return (opt?.productData || []).map(normalizeProductRow); |
| | | }); |
| | | |
| | | const customerNameChange = (val) => { |
| | | const customerNameChange = val => { |
| | | form.value.salesContractNo = ""; |
| | | form.value.salesLedgerId = null; |
| | | tableData.value = []; |
| | |
| | | form.value.customerId = null; |
| | | } |
| | | getSalesLedger({ |
| | | customerName: form.value.customerName |
| | | customerName: form.value.customerName, |
| | | }).then(res => { |
| | | if(res.code === 200){ |
| | | associatedSalesOrderNumberOptions.value = res.data.records.map(item => ({ |
| | | label: item.salesContractNo, |
| | | value: item.salesContractNo, |
| | | productData:item.productData, |
| | | id: item.id |
| | | })) |
| | | id: item.id, |
| | | })); |
| | | } |
| | | }) |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const getApproveStatusText = (row) => { |
| | | if (!row) return 'ä¸è¶³' |
| | | if (row.approveStatus === 1 && (!row.shippingDate || !row.shippingCarNumber)) { |
| | | return 'å
è¶³' |
| | | const getApproveStatusText = row => { |
| | | if (!row) return "ä¸è¶³"; |
| | | if ( |
| | | row.approveStatus === 1 && |
| | | (!row.shippingDate || !row.shippingCarNumber) |
| | | ) { |
| | | return "å
è¶³"; |
| | | } |
| | | if (row.approveStatus === 0 && (row.shippingDate || row.shippingCarNumber)) { |
| | | return 'å·²åºåº' |
| | | return "å·²åºåº"; |
| | | } |
| | | return 'ä¸è¶³' |
| | | } |
| | | return "ä¸è¶³"; |
| | | }; |
| | | |
| | | const getApproveStatusType = (row) => { |
| | | const statusText = getApproveStatusText(row) |
| | | return statusText === 'ä¸è¶³' ? 'danger' : 'success' |
| | | } |
| | | const getApproveStatusType = row => { |
| | | const statusText = getApproveStatusText(row); |
| | | return statusText === "ä¸è¶³" ? "danger" : "success"; |
| | | }; |
| | | |
| | | const getShippingStatusText = (row) => { |
| | | if (!row) return 'å¾
åè´§' |
| | | const getShippingStatusText = row => { |
| | | if (!row) return "å¾
åè´§"; |
| | | if (row.shippingDate || row.shippingCarNumber) { |
| | | return 'å·²åè´§' |
| | | return "å·²åè´§"; |
| | | } |
| | | const status = row.shippingStatus |
| | | if (status === null || status === undefined || status === '') { |
| | | return 'å¾
åè´§' |
| | | const status = row.shippingStatus; |
| | | if (status === null || status === undefined || status === "") { |
| | | return "å¾
åè´§"; |
| | | } |
| | | const map = { |
| | | 'å¾
åè´§': 'å¾
åè´§', |
| | | 'å¾
å®¡æ ¸': 'å¾
å®¡æ ¸', |
| | | 'å®¡æ ¸ä¸': 'å®¡æ ¸ä¸', |
| | | 'å®¡æ ¸æç»': 'å®¡æ ¸æç»', |
| | | 'å®¡æ ¸éè¿': 'å®¡æ ¸éè¿', |
| | | 'å·²åè´§': 'å·²åè´§' |
| | | } |
| | | return map[String(status).trim()] || 'å¾
åè´§' |
| | | } |
| | | å¾
åè´§: "å¾
åè´§", |
| | | å¾
å®¡æ ¸: "å¾
å®¡æ ¸", |
| | | å®¡æ ¸ä¸: "å®¡æ ¸ä¸", |
| | | å®¡æ ¸æç»: "å®¡æ ¸æç»", |
| | | å®¡æ ¸éè¿: "å®¡æ ¸éè¿", |
| | | å·²åè´§: "å·²åè´§", |
| | | }; |
| | | return map[String(status).trim()] || "å¾
åè´§"; |
| | | }; |
| | | |
| | | const getShippingStatusType = (row) => { |
| | | if (!row) return 'info' |
| | | const getShippingStatusType = row => { |
| | | if (!row) return "info"; |
| | | if (row.shippingDate || row.shippingCarNumber) { |
| | | return 'success' |
| | | return "success"; |
| | | } |
| | | const status = row.shippingStatus |
| | | if (status === null || status === undefined || status === '') { |
| | | return 'info' |
| | | const status = row.shippingStatus; |
| | | if (status === null || status === undefined || status === "") { |
| | | return "info"; |
| | | } |
| | | const map = { |
| | | 'å¾
åè´§': 'info', |
| | | 'å¾
å®¡æ ¸': 'warning', |
| | | 'å®¡æ ¸ä¸': 'warning', |
| | | 'å®¡æ ¸æç»': 'danger', |
| | | 'å®¡æ ¸éè¿': 'success', |
| | | 'å·²åè´§': 'success' |
| | | } |
| | | return map[String(status).trim()] || 'info' |
| | | } |
| | | å¾
åè´§: "info", |
| | | å¾
å®¡æ ¸: "warning", |
| | | å®¡æ ¸ä¸: "warning", |
| | | å®¡æ ¸æç»: "danger", |
| | | å®¡æ ¸éè¿: "success", |
| | | å·²åè´§: "success", |
| | | }; |
| | | return map[String(status).trim()] || "info"; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog =async (type, row) => { |
| | |
| | | size: 1000, |
| | | total: 0, |
| | | }); |
| | | if(res.records){ |
| | | customerNameOptions.value = res.records.map(item => ({ |
| | | console.log(res, "res"); |
| | | |
| | | if (res.data.records) { |
| | | customerNameOptions.value = res.data.records.map(item => ({ |
| | | label: item.customerName, |
| | | value: item.customerName, |
| | | id: item.id |
| | | id: item.id, |
| | | })); |
| | | } else { |
| | | } |
| | | |
| | | |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | form.value = {} |
| | | form.value = {}; |
| | | proxy.resetForm("formRef"); |
| | | form.value.checkUserId = userStore.id; |
| | | form.value.feedbackDate = getCurrentDate(); |
| | | // æ°å¢æ¶æ¸
空已éå
³è产å |
| | | if (type === "add") { |
| | | tableData.value = [] |
| | | tableData.value = []; |
| | | } |
| | | userListNoPageByTenantId().then((res) => { |
| | | userListNoPageByTenantId().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | if (type === "edit") { |
| | | form.value = {...row} |
| | | form.value = { ...row }; |
| | | if (form.value.customerName) { |
| | | const res = await getSalesLedger({ customerName: form.value.customerName }) |
| | | const res = await getSalesLedger({ |
| | | customerName: form.value.customerName, |
| | | }); |
| | | if (res?.code === 200) { |
| | | console.log(res) |
| | | associatedSalesOrderNumberOptions.value = (res.data?.records || []).map(item => ({ |
| | | console.log(res); |
| | | associatedSalesOrderNumberOptions.value = (res.data?.records || []).map( |
| | | item => ({ |
| | | label: item.salesContractNo, |
| | | value: item.salesContractNo, |
| | | productData: item.productData, |
| | | id: item.id |
| | | })) |
| | | id: item.id, |
| | | }) |
| | | ); |
| | | } |
| | | } |
| | | console.log(form.value) |
| | | console.log(form.value); |
| | | } |
| | | } |
| | | }; |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | // å¹é
产ååå·IDs |
| | | form.value.productModelIds = tableData.value.map(item => item.id).join(",") |
| | | form.value.productModelIds = tableData.value |
| | | .map(item => item.id) |
| | | .join(","); |
| | | if (operationType.value === "add") { |
| | | afterSalesServiceAdd(form.value).then(response => { |
| | | proxy.$modal.msgSuccess("æ°å¢æå") |
| | | closeDia() |
| | | }) |
| | | proxy.$modal.msgSuccess("æ°å¢æå"); |
| | | closeDia(); |
| | | }); |
| | | } else { |
| | | afterSalesServiceUpdate(form.value).then(response => { |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå") |
| | | closeDia() |
| | | }) |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | closeDia(); |
| | | }); |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | emit("close"); |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 1rem; |
| | | background-color: #002FA7; /* Element é»è®¤çº¢è² */ |
| | | background-color: #002fa7; /* Element é»è®¤çº¢è² */ |
| | | border-radius: 2px; |
| | | } |
| | | </style> |
| | |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="visible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | <el-button @click="visible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div class="search_form mb20"> |
| | | <div> |
| | | <span class="search_title">æ£å®æ¥æï¼</span> |
| | | <el-date-picker |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="showRegisterDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitDefectForm">ç¡®å®</el-button> |
| | | <el-button @click="showRegisterDialog = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å·¡æ£é¡¹ç®" prop="inspectionProject"> |
| | | <el-input v-model="form.inspectionProject" placeholder="请è¾å
¥å·¡æ£é¡¹ç®" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦å¯ç¨" prop="isEnabled"> |
| | | <el-radio-group v-model="form.isEnabled"> |
| | | <el-radio :value="1">æ¯</el-radio> |
| | | <el-radio :value="0">å¦</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="夿³¨" prop="remarks"> |
| | | <el-input v-model="form.remarks" placeholder="请è¾å
¥å¤æ³¨" type="textarea" /> |
| | | </el-form-item> |
| | |
| | | taskName: undefined, |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | inspectionProject: '', |
| | | isEnabled: 1, |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | |
| | | taskName: undefined, |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | inspectionProject: '', |
| | | isEnabled: 1, |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | |
| | | const currentMediaIndex = ref(0); |
| | | const mediaList = ref([]); // åå¨å½åè¦æ¥ççåªä½å表ï¼å«å¾çåè§é¢å¯¹è±¡ï¼ |
| | | const mediaType = ref('image'); // image | video |
| | | const javaApi = proxy.javaApi; |
| | | |
| | | // å¤ç URLï¼å° Windows è·¯å¾è½¬æ¢ä¸ºå¯è®¿é®ç URL |
| | | function processFileUrl(fileUrl) { |
| | | if (!fileUrl) return ''; |
| | | |
| | | // 妿 URL æ¯ Windows è·¯å¾æ ¼å¼ï¼å
å«åææ ï¼ï¼éè¦è½¬æ¢ |
| | | if (fileUrl && fileUrl.indexOf('\\') > -1) { |
| | | // æ¥æ¾ uploads å
³é®åçä½ç½®ï¼ä»é£éå¼å§æåç¸å¯¹è·¯å¾ |
| | | const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads'); |
| | | if (uploadsIndex > -1) { |
| | | // ä» uploads å¼å§æåè·¯å¾ï¼å¹¶å°åææ æ¿æ¢ä¸ºæ£ææ |
| | | const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/'); |
| | | fileUrl = '/' + relativePath; |
| | | } else { |
| | | // å¦ææ²¡ææ¾å° uploadsï¼æåæåä¸ä¸ªç®å½åæä»¶å |
| | | const parts = fileUrl.split('\\'); |
| | | const fileName = parts[parts.length - 1]; |
| | | fileUrl = '/uploads/' + fileName; |
| | | } |
| | | } |
| | | |
| | | // ç¡®ä¿ææé http å¼å¤´ç URL 齿¼æ¥ baseUrl |
| | | if (fileUrl && !fileUrl.startsWith('http')) { |
| | | // ç¡®ä¿è·¯å¾ä»¥ / å¼å¤´ |
| | | if (!fileUrl.startsWith('/')) { |
| | | fileUrl = '/' + fileUrl; |
| | | } |
| | | // æ¼æ¥ baseUrl |
| | | fileUrl = javaApi + fileUrl; |
| | | } |
| | | |
| | | return fileUrl; |
| | | } |
| | | |
| | | // å¤çæ¯ä¸ç±»æ°æ®ï¼å离å¾çåè§é¢ |
| | | function processItems(items) { |
| | |
| | | } |
| | | |
| | | items.forEach(item => { |
| | | if (!item || !item.url) return; |
| | | if (!item || !item.previewURL || !item.contentType) return; |
| | | |
| | | |
| | | // å¤çæä»¶ URL |
| | | const fileUrl = processFileUrl(item.url); |
| | | const fileUrl = item.previewURL; |
| | | const contentType = String(item.contentType).toLowerCase(); |
| | | |
| | | // æ ¹æ®æä»¶æ©å±å夿æ¯å¾çè¿æ¯è§é¢ |
| | | const urlLower = fileUrl.toLowerCase(); |
| | | if (urlLower.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/)) { |
| | | // æ ¹æ® contentType 夿æ¯å¾çè¿æ¯è§é¢ |
| | | if (contentType.startsWith('image/')) { |
| | | images.push(fileUrl); |
| | | } else if (urlLower.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/)) { |
| | | } else if (contentType.startsWith('video/')) { |
| | | videos.push(fileUrl); |
| | | } else if (item.contentType) { |
| | | // 妿æ contentTypeï¼ä½¿ç¨ contentType 夿 |
| | | if (item.contentType.startsWith('image/')) { |
| | | images.push(fileUrl); |
| | | } else if (item.contentType.startsWith('video/')) { |
| | | videos.push(fileUrl); |
| | | } |
| | | } |
| | | }); |
| | | |
| | |
| | | // æå¼å¼¹çªå¹¶å è½½æ°æ® |
| | | const openDialog = async (row) => { |
| | | // ä½¿ç¨æ£ç¡®çåæ®µåï¼commonFileListBefore, commonFileListAfter |
| | | // productionIssues å¯è½ä¸åå¨ï¼ä½¿ç¨ç©ºæ°ç» |
| | | const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBefore || []); |
| | | const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfter || []); |
| | | const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues || []); |
| | | const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBeforeVO || []); |
| | | const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfterVO || []); |
| | | const { images: issueImgs, videos: issueVids } = processItems(row.commonFileListVO || []); |
| | | |
| | | beforeProductionImgs.value = beforeImgs; |
| | | beforeProductionVideos.value = beforeVids; |
| | |
| | | class="no-data">--</span> |
| | | </div> |
| | | </template> |
| | | <template #isEnabled="{ row }"> |
| | | <el-tag :type="row.isEnabled === 1 ? 'success' : 'danger'" size="small"> |
| | | {{ row.isEnabled == 1 ? 'æ¯' : 'å¦' }} |
| | | </el-tag> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </el-card> |
| | |
| | | // åé
ç½® |
| | | const columns = ref([ |
| | | { prop: "taskName", label: "å·¡æ£ä»»å¡åç§°", minWidth: 160 }, |
| | | { prop: "inspectionProject", label: "å·¡æ£é¡¹ç®", minWidth: 150 }, |
| | | { prop: "remarks", label: "夿³¨", minWidth: 150 }, |
| | | { prop: "inspector", label: "æ§è¡å·¡æ£äºº", minWidth: 150, slot: "inspector" }, |
| | | { |
| | | prop: "isEnabled", |
| | | label: "æ¯å¦å¯ç¨", |
| | | minWidth: 100, |
| | | dataType: "slot", |
| | | slot: "isEnabled" |
| | | }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | |
| | | operationsArr.value = ["edit"]; |
| | | } else if (value === "task") { |
| | | const operationColumn = getOperationColumn(["viewFile"]); |
| | | // 宿¶ä»»å¡è®°å½ä¸å±ç¤º"æ¯å¦å¯ç¨"å |
| | | const taskColumns = columns.value.filter(col => col.prop !== "isEnabled"); |
| | | tableColumns.value = [ |
| | | ...columns.value, |
| | | ...taskColumns, |
| | | ...(operationColumn ? [operationColumn] : []), |
| | | ]; |
| | | operationsArr.value = ["viewFile"]; |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¨ç(%)" prop="taxRate"> |
| | | <!-- <el-input |
| | | v-model="form.taxRate" |
| | | placeholder="请è¾å
¥ç¨ç" |
| | | type="number" |
| | | > |
| | | <template #append> % </template> |
| | | </el-input> --> |
| | | <el-select |
| | | v-model="form.taxRate" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="mathNum" |
| | | > |
| | | <el-option label="1" :value="1" /> |
| | | <el-option label="6" :value="6" /> |
| | | <el-option label="13" :value="13" /> |
| | | <el-option |
| | | v-for="dict in tax_rate" |
| | | :key="dict.value" |
| | | :label="dict.label" |
| | | :value="Number(dict.value)" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | calculateTaxExclusiveTotalPrice, |
| | | } from "@/utils/summarizeTable"; |
| | | import { ElMessage } from "element-plus"; |
| | | import {ref} from "vue"; |
| | | import {ref, getCurrentInstance} from "vue"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const { tax_rate } = proxy.useDict("tax_rate"); |
| | | |
| | | defineOptions({ |
| | | name: "设å¤å°è´¦è¡¨å", |
| | |
| | | const showQRCode = async (row) => { |
| | | // ç´æ¥ä½¿ç¨URLï¼ä¸è¦ç¨JSON.stringifyå
è£
|
| | | const qrContent = proxy.javaApi + '/device-info?deviceId=' + row.id; |
| | | qrCodeUrl.value = await QRCode.toDataURL(qrContent); |
| | | const qrDataUrl = await QRCode.toDataURL(qrContent, { width: 200, margin: 2 }); |
| | | |
| | | // å建canvasåæå¸¦åç§°çäºç»´ç å¾ç |
| | | const canvas = document.createElement('canvas'); |
| | | const ctx = canvas.getContext('2d'); |
| | | const qrSize = 200; |
| | | const textHeight = 30; |
| | | const padding = 10; |
| | | canvas.width = qrSize + padding * 2; |
| | | canvas.height = qrSize + textHeight + padding * 2; |
| | | |
| | | // å¡«å
ç½è²èæ¯ |
| | | ctx.fillStyle = '#ffffff'; |
| | | ctx.fillRect(0, 0, canvas.width, canvas.height); |
| | | |
| | | // ç»å¶äºç»´ç |
| | | const qrImg = new Image(); |
| | | qrImg.src = qrDataUrl; |
| | | await new Promise((resolve) => { qrImg.onload = resolve; }); |
| | | ctx.drawImage(qrImg, padding, padding, qrSize, qrSize); |
| | | |
| | | // ç»å¶è®¾å¤åç§° |
| | | ctx.fillStyle = '#333333'; |
| | | ctx.font = 'bold 14px Arial'; |
| | | ctx.textAlign = 'center'; |
| | | ctx.fillText(row.deviceName || '', canvas.width / 2, qrSize + padding + 20); |
| | | |
| | | qrCodeUrl.value = canvas.toDataURL('image/png'); |
| | | qrRowData.value = row; |
| | | qrDialogVisible.value = true; |
| | | }; |
| | |
| | | form.value.entryDate = getCurrentDate(); |
| | | } |
| | | |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | function handleBeforeUpload(file) { |
| | | proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); |
| | | return true; |
| | | } |
| | | // ä¸ä¼ 失败 |
| | | function handleUploadError(err) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | // ä¸ä¼ æååè° |
| | | function handleUploadSuccess(res, file, uploadFiles) { |
| | | proxy.$modal.closeLoading(); |
| | | if (res.code === 200) { |
| | | file.tempId = res.data.tempId; |
| | | form.value.tempFileIds.push(file.tempId); |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | proxy.$refs.fileUpload.handleRemove(file); |
| | | } |
| | | } |
| | | // ç§»é¤æä»¶ |
| | | function handleRemove(file) { |
| | | if (operationType.value === "edit") { |
| | | let ids = []; |
| | | ids.push(file.id); |
| | | delLedgerFile(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // å¤çæææ¥æè¾å
¥ï¼åªå
è®¸æ£æ´æ° |
| | | const handleValidInput = (value) => { |
| | | if (value === '' || value === null || value === undefined) { |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å®è£
ä½ç½®ï¼" prop="instationLocation"> |
| | | <el-input |
| | | v-model="form.instationLocation" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£å®åä½ï¼" prop="unit"> |
| | | <el-input |
| | | v-model="form.unit" |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¯ä¹¦ç¼å·ï¼" prop="model"> |
| | | <el-input |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ææ°é´å®æ¥æï¼" prop="mostDate"> |
| | | <el-date-picker |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææ¥æ(天)ï¼" prop="valid"> |
| | | <el-input |
| | |
| | | > |
| | | <template #append>æ¥</template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£å®å¨æï¼" prop="cycle"> |
| | | <el-input |
| | | v-model="form.cycle" |
| | | placeholder="请è¾å
¥æ£å®å¨æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | form: { |
| | | code: "", |
| | | name: "", |
| | | instationLocation: "", |
| | | mostDate:"", |
| | | model: "", |
| | | cycle:"", |
| | | validDate: "", |
| | | nextDate: "", |
| | | userId: "", |
| | |
| | | nextDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | userId: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | recordDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | instationLocation: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | mostDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | cycle: [{required: true, message: "è¯·éæ©", trigger: "blur"}], |
| | | valid: [ |
| | | {required: true, message: "请è¾å
¥", trigger: "blur"}, |
| | | { |
| | |
| | | } |
| | | // ä¸è½½éä»¶ |
| | | const downLoadFile = (row) => { |
| | | proxy.$download.name(row.url); |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | } |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div class="search_form mb20"> |
| | | <div> |
| | | <span class="search_title">å½å
¥æ¥æï¼</span> |
| | | <el-date-picker |
| | |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :dbRowClick="dbRowClick" |
| | | :rowClassName="rowClassName" |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | |
| | | align: "center", |
| | | }, |
| | | { |
| | | label: "å®è£
ä½ç½®", |
| | | prop: "instationLocation", |
| | | width: 150, |
| | | align:"center" |
| | | }, |
| | | { |
| | | label: "æ£å®åä½", |
| | | prop: "unit", |
| | | width: 200, |
| | |
| | | { |
| | | label: "æææ¥æ", |
| | | prop: "valid", |
| | | width: 130, |
| | | align:"center" |
| | | }, |
| | | { |
| | | label: "æ£å®å¨æ(天)", |
| | | prop: "cycle", |
| | | width: 130, |
| | | align:"center" |
| | | }, |
| | |
| | | |
| | | const dbRowClick = (row)=>{ |
| | | rowClickData.value?.openDialog(row) |
| | | } |
| | | |
| | | // è¡æ ·å¼ï¼å¿«å°æï¼7天å
ï¼æé¾ææ 红 |
| | | const rowClassName = ({ row }) => { |
| | | console.log('rowClassName called:', row); |
| | | // valid æ¯ææå¤©æ°ï¼mostDate æ¯ææ°æ£å®æ¥æ |
| | | if (row.valid && row.mostDate) { |
| | | const mostDate = new Date(row.mostDate); |
| | | // 计ç®å°ææ¥æ = æ£å®æ¥æ + ææå¤©æ° |
| | | const validDays = parseInt(row.valid) || 0; |
| | | const expireDate = new Date(mostDate); |
| | | expireDate.setDate(expireDate.getDate() + validDays); |
| | | |
| | | const now = new Date(); |
| | | const diffDays = Math.ceil((expireDate - now) / (1000 * 60 * 60 * 24)); |
| | | console.log('row:', row.code, 'validDays:', validDays, 'expireDate:', expireDate, 'diffDays:', diffDays); |
| | | // 7天å
å°ææå·²é¾æé½æ 红 |
| | | if (diffDays <= 7) { |
| | | console.log('return warning-row'); |
| | | return 'warning-row'; |
| | | } |
| | | } else { |
| | | console.log('row missing valid or mostDate:', row.valid, row.mostDate); |
| | | } |
| | | return ''; |
| | | } |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | :deep(.el-table .warning-row) { |
| | | background-color: #fef0f0 !important; |
| | | } |
| | | :deep(.el-table .warning-row:hover > td) { |
| | | background-color: #f9d5d5 !important; |
| | | } |
| | | :deep(.el-table .el-table__body tr.warning-row td) { |
| | | background-color: #fef0f0 !important; |
| | | } |
| | | </style> |
| | |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | {{ scope.row.runtimeDuration || '-' }} |
| | | {{ getRuntimeDurationDisplay(scope.row) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { ref, onMounted, onUnmounted, computed } from 'vue' |
| | | import dayjs from 'dayjs' |
| | | import { ElMessage } from 'element-plus' |
| | | import { |
| | | VideoPlay, |
| | |
| | | |
| | | return filtered |
| | | }) |
| | | |
| | | // è¿è¡ä¸æ ç»ææ¶é´æ¶ï¼è¿è¡æ¶é¿ééå½åæ¶é´ååï¼ç¨ tick è§¦åæ¨¡æ¿éç® |
| | | const runtimeDisplayTick = ref(0) |
| | | |
| | | /** åå端å¯è½ä½¿ç¨çå¼å§/ç»ææ¶é´å段 */ |
| | | const pickStartTime = (row) => row?.startRuntimeTime ?? row?.startTime ?? row?.start_time |
| | | const pickEndTime = (row) => row?.endRuntimeTime ?? row?.endTime ?? row?.end_time |
| | | |
| | | /** |
| | | * è§£ææ¥å£/å端åå
¥çåç±»æ¶é´ï¼æ¶é´æ³ãISO å符串ãyyyy-MM-dd HH:mm:ssãJackson æ°ç» [y,M,d,h,m,s]ãå«ä¸æç toLocaleString ç |
| | | */ |
| | | const parseDeviceTime = (input) => { |
| | | if (input === null || input === undefined || input === '') return null |
| | | if (typeof input === 'number' && !Number.isNaN(input)) { |
| | | const d = dayjs(input) |
| | | return d.isValid() ? d.toDate() : null |
| | | } |
| | | if (Array.isArray(input)) { |
| | | const [y, mo, day, h = 0, mi = 0, se = 0] = input |
| | | if (y == null || y === '') return null |
| | | const d = dayjs() |
| | | .year(Number(y)) |
| | | .month(Number(mo || 1) - 1) |
| | | .date(Number(day || 1)) |
| | | .hour(Number(h) || 0) |
| | | .minute(Number(mi) || 0) |
| | | .second(Number(se) || 0) |
| | | return d.isValid() ? d.toDate() : null |
| | | } |
| | | const s = String(input).trim() |
| | | if (!s || s === '-') return null |
| | | let d = dayjs(s) |
| | | if (d.isValid()) return d.toDate() |
| | | d = dayjs(s.replace(/-/g, '/')) |
| | | if (d.isValid()) return d.toDate() |
| | | d = dayjs(s.replace(/\//g, '-')) |
| | | if (d.isValid()) return d.toDate() |
| | | return null |
| | | } |
| | | |
| | | const formatDurationMs = (durationMs) => { |
| | | if (durationMs == null || Number.isNaN(durationMs) || durationMs < 0) return '-' |
| | | const hours = Math.floor(durationMs / (1000 * 60 * 60)) |
| | | const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60)) |
| | | if (hours === 0 && minutes === 0) return 'ä¸è¶³1åé' |
| | | return `${hours}å°æ¶${minutes}åé` |
| | | } |
| | | |
| | | const hasMeaningfulEnd = (endRaw) => |
| | | endRaw !== null && |
| | | endRaw !== undefined && |
| | | String(endRaw).trim() !== '' && |
| | | String(endRaw).trim() !== '-' |
| | | |
| | | const formatStoredDuration = (row) => { |
| | | const rd = row?.runtimeDuration |
| | | if (rd === null || rd === undefined) return '' |
| | | const t = String(rd).trim() |
| | | return t === '' || t === '-' ? '' : String(rd) |
| | | } |
| | | |
| | | /** è¿è¡ä¸ï¼å§ç»ç¨ãå½åæ¶é´ - å¼å§æ¶é´ãï¼å·²åæ¢ï¼ä¼å
æ¥å£ runtimeDurationï¼å¦åç¨ç»æ-å¼å§ï¼æ ç»æå¯çå·²åæ¶é¿æå¨ææ¨ç® */ |
| | | const getRuntimeDurationDisplay = (row) => { |
| | | void runtimeDisplayTick.value |
| | | const start = parseDeviceTime(pickStartTime(row)) |
| | | if (!start) { |
| | | return formatStoredDuration(row) || '-' |
| | | } |
| | | |
| | | const statusStr = String(row?.status ?? '').trim() |
| | | const isRunning = statusStr === 'è¿è¡ä¸' || statusStr === '1' |
| | | const endRaw = pickEndTime(row) |
| | | const hasEnd = hasMeaningfulEnd(endRaw) |
| | | |
| | | // æ ç»ææ¶é´ï¼è¿è¡ä¸ä¸å®å¨æç®ï¼å·²åæ¢åä¼å
å±ç¤ºåç«¯å·²åæ¶é¿ï¼æ²¡æåæå½åæ¶é´æ¨ç® |
| | | if (!hasEnd) { |
| | | if (isRunning) return formatDurationMs(Date.now() - start.getTime()) |
| | | const stored = formatStoredDuration(row) |
| | | if (stored) return stored |
| | | return formatDurationMs(Date.now() - start.getTime()) |
| | | } |
| | | |
| | | if (isRunning) { |
| | | return formatDurationMs(Date.now() - start.getTime()) |
| | | } |
| | | |
| | | const end = parseDeviceTime(endRaw) |
| | | const stored = formatStoredDuration(row) |
| | | if (stored) return stored |
| | | if (end) return formatDurationMs(end.getTime() - start.getTime()) |
| | | return '-' |
| | | } |
| | | |
| | | // æ£æ¥è®¾å¤æ¯å¦è¶
æ¶æªå¯å¨ |
| | | const isOverdue = (device) => { |
| | |
| | | device.endRuntimeTime = currentTime |
| | | // 计ç®è¿è¡æ¶é¿ |
| | | if (device.startRuntimeTime) { |
| | | const startTime = new Date(device.startRuntimeTime) |
| | | const endTime = new Date(currentTime) |
| | | const duration = endTime - startTime |
| | | const hours = Math.floor(duration / (1000 * 60 * 60)) |
| | | const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60)) |
| | | device.runtimeDuration = `${hours}å°æ¶${minutes}åé` |
| | | const startTime = parseDeviceTime(device.startRuntimeTime) |
| | | const endTime = parseDeviceTime(currentTime) |
| | | if (startTime && endTime) { |
| | | device.runtimeDuration = formatDurationMs(endTime.getTime() - startTime.getTime()) |
| | | } |
| | | } |
| | | } |
| | | const params = { |
| | |
| | | |
| | | |
| | | |
| | | // ç»ä»¶æè½½æ¶åå§åæ°æ® |
| | | const POLL_MS = 60 * 1000 |
| | | const RUNTIME_TICK_MS = 30 * 1000 |
| | | let listPollTimer = null |
| | | let runtimeTickTimer = null |
| | | |
| | | // ç»ä»¶æè½½æ¶æåæ°æ®ï¼å¹¶æ¯åéå·æ°ä¸æ¬¡å表ï¼è¿è¡ä¸æ¶é¿æ¯ 30 ç§å·æ°æ¾ç¤º |
| | | onMounted(() => { |
| | | getList() |
| | | listPollTimer = setInterval(() => { |
| | | getList() |
| | | }, POLL_MS) |
| | | runtimeTickTimer = setInterval(() => { |
| | | runtimeDisplayTick.value++ |
| | | }, RUNTIME_TICK_MS) |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | if (listPollTimer != null) { |
| | | clearInterval(listPollTimer) |
| | | listPollTimer = null |
| | | } |
| | | if (runtimeTickTimer != null) { |
| | | clearInterval(runtimeTickTimer) |
| | | runtimeTickTimer = null |
| | | } |
| | | }) |
| | | </script> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <FormDialog |
| | | v-model="visible" |
| | | title="éªæ¶å®¡æ¹" |
| | | width="500px" |
| | | @confirm="submitForm" |
| | | @cancel="handleCancel" |
| | | @close="handleCancel" |
| | | > |
| | | <el-form :model="form" :rules="rules" label-width="100px"> |
| | | <el-form-item label="éªæ¶äºº" prop="acceptanceName"> |
| | | <el-select |
| | | v-model="form.acceptanceName" |
| | | placeholder="è¯·éæ©éªæ¶äºº" |
| | | filterable |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in userList" |
| | | :key="item.userId" |
| | | :label="item.nickName" |
| | | :value="item.nickName" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="éªæ¶æ¶é´" prop="acceptanceTime"> |
| | | <el-date-picker |
| | | v-model="form.acceptanceTime" |
| | | type="datetime" |
| | | placeholder="è¯·éæ©éªæ¶æ¶é´" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="éªæ¶å¤æ³¨" prop="acceptanceRemark"> |
| | | <el-input |
| | | v-model="form.acceptanceRemark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥éªæ¶å¤æ³¨" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { ref, reactive } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { repairAcceptance } from "@/api/equipmentManagement/repair"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | defineOptions({ |
| | | name: "éªæ¶å®¡æ¹å¼¹çª", |
| | | }); |
| | | |
| | | const emits = defineEmits(["ok"]); |
| | | |
| | | const visible = ref(false); |
| | | const loading = ref(false); |
| | | const repairId = ref(null); |
| | | const userList = ref([]); |
| | | |
| | | const form = reactive({ |
| | | acceptanceName: undefined, |
| | | acceptanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | acceptanceRemark: undefined, |
| | | }); |
| | | |
| | | const rules = { |
| | | acceptanceName: [ |
| | | { required: true, message: "è¯·éæ©éªæ¶äºº", trigger: "change" }, |
| | | ], |
| | | acceptanceTime: [ |
| | | { required: true, message: "è¯·éæ©éªæ¶æ¶é´", trigger: "change" }, |
| | | ], |
| | | acceptanceRemark: [ |
| | | { required: true, message: "请è¾å
¥éªæ¶å¤æ³¨", trigger: "blur" }, |
| | | ], |
| | | }; |
| | | |
| | | // å è½½ç¨æ·å表 |
| | | const loadUserList = async () => { |
| | | const { data } = await userListNoPageByTenantId(); |
| | | userList.value = data; |
| | | }; |
| | | |
| | | // æå¼å¼¹çª |
| | | const open = async (row) => { |
| | | repairId.value = row.id; |
| | | visible.value = true; |
| | | // é置表å |
| | | form.acceptanceName = undefined; |
| | | form.acceptanceTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | form.acceptanceRemark = undefined; |
| | | await loadUserList(); |
| | | }; |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = async () => { |
| | | if (!form.acceptanceName) { |
| | | ElMessage.warning("è¯·éæ©éªæ¶äºº"); |
| | | return; |
| | | } |
| | | if (!form.acceptanceTime) { |
| | | ElMessage.warning("è¯·éæ©éªæ¶æ¶é´"); |
| | | return; |
| | | } |
| | | if (!form.acceptanceRemark) { |
| | | ElMessage.warning("请è¾å
¥éªæ¶å¤æ³¨"); |
| | | return; |
| | | } |
| | | |
| | | loading.value = true; |
| | | try { |
| | | const { code } = await repairAcceptance({ |
| | | id: repairId.value, |
| | | acceptanceName: form.acceptanceName, |
| | | acceptanceTime: form.acceptanceTime, |
| | | acceptanceRemark: form.acceptanceRemark, |
| | | }); |
| | | if (code === 200) { |
| | | ElMessage.success("éªæ¶éè¿"); |
| | | visible.value = false; |
| | | emits("ok"); |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | visible.value = false; |
| | | }; |
| | | |
| | | defineExpose({ |
| | | open, |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped></style> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç±»ç®"> |
| | | <el-input v-model="form.machineryCategory" placeholder="请è¾å
¥ç±»ç®" /> |
| | | <el-form-item label="æ¥ä¿®æ¥ä¿®é¡¹ç®"> |
| | | <el-input v-model="form.machineryCategory" placeholder="请è¾å
¥æ¥ä¿®æ¥ä¿®é¡¹ç®" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="维修人"> |
| | | <el-input v-model="form.maintenanceName" placeholder="请è¾å
¥ç»´ä¿®äººå§å" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="id"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥ä¿®ç¶æ"> |
| | | <el-select v-model="form.status"> |
| | | <el-select v-model="form.status" disabled> |
| | | <el-option label="å¾
ç»´ä¿®" :value="0"></el-option> |
| | | <el-option label="å®ç»" :value="1"></el-option> |
| | | <el-option label="å·²éªæ¶" :value="1"></el-option> |
| | | <el-option label="失败" :value="2"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- éªæ¶ä¿¡æ¯å±ç¤º --> |
| | | <el-row v-if="id && form.status === 1"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éªæ¶äºº"> |
| | | <el-input v-model="form.acceptanceName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éªæ¶æ¶é´"> |
| | | <el-input v-model="form.acceptanceTime" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éªæ¶å¤æ³¨"> |
| | | <el-input v-model="form.acceptanceRemark" type="textarea" :rows="2" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶" prop="attachmentIds"> |
| | | <FileUpload v-model:file-list="form.storageBlobDTOs" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | import { |
| | | addRepair, |
| | | editRepair, |
| | |
| | | |
| | | const userStore = useUserStore(); |
| | | const deviceOptions = ref([]); |
| | | const fileList = ref([]); |
| | | |
| | | const loadDeviceName = async () => { |
| | | const { data } = await getDeviceLedger(); |
| | |
| | | remark: undefined, // æ
éç°è±¡ |
| | | status: 0, // æ¥ä¿®ç¶æ |
| | | machineryCategory: undefined, |
| | | storageBlobDTOs: [], |
| | | maintenanceName: undefined, // 维修人 |
| | | }); |
| | | |
| | | const setDeviceModel = (deviceId) => { |
| | |
| | | form.remark = data.remark; |
| | | form.status = data.status; |
| | | form.machineryCategory = data.machineryCategory; |
| | | form.storageBlobDTOs = data.storageBlobVOs || []; |
| | | form.maintenanceName = data.maintenanceName; |
| | | form.acceptanceName = data.acceptanceName; |
| | | form.acceptanceTime = data.acceptanceTime; |
| | | form.acceptanceRemark = data.acceptanceRemark; |
| | | }; |
| | | |
| | | const sendForm = async () => { |
| | |
| | | const openAdd = async () => { |
| | | id.value = undefined; |
| | | visible.value = true; |
| | | fileList.value = []; |
| | | await nextTick(); |
| | | await loadDeviceName(); |
| | | }; |
| | |
| | | <template #statusRef="{ row }"> |
| | | <el-tag v-if="row.status === 2" type="danger">失败</el-tag> |
| | | <el-tag v-if="row.status === 1" type="success">å®ç»</el-tag> |
| | | <el-tag v-if="row.status === 3" type="info">å¾
éªæ¶</el-tag> |
| | | <el-tag v-if="row.status === 0" type="warning">å¾
ç»´ä¿®</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | :disabled="row.status === 1" |
| | | :disabled="row.status === 1 || row.status === 3" |
| | | @click="editRepair(row.id)" |
| | | > |
| | | ç¼è¾ |
| | |
| | | <el-button |
| | | type="success" |
| | | link |
| | | :disabled="row.status === 1" |
| | | :disabled="row.status !== 0" |
| | | @click="addMaintain(row)" |
| | | > |
| | | ç»´ä¿® |
| | | </el-button> |
| | | <el-button |
| | | type="warning" |
| | | link |
| | | :disabled="row.status !== 3" |
| | | @click="openAcceptance(row)" |
| | | > |
| | | éªæ¶ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | :disabled="row.status === 1" |
| | | :disabled="row.status === 1 || row.status === 3" |
| | | @click="delRepairByIds(row.id)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @click="openFileDialog(row)" |
| | | > |
| | | éä»¶ |
| | | </el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <RepairModal ref="repairModalRef" @ok="getTableData"/> |
| | | <MaintainModal ref="maintainModalRef" @ok="getTableData"/> |
| | | <AcceptanceModal ref="acceptanceModalRef" @ok="getTableData"/> |
| | | <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" :record-type="'device_repair'" :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, getCurrentInstance, computed } from "vue"; |
| | | import {onMounted, getCurrentInstance, computed, ref, defineAsyncComponent} from "vue"; |
| | | import {usePaginationApi} from "@/hooks/usePaginationApi"; |
| | | import {getRepairPage, delRepair} from "@/api/equipmentManagement/repair"; |
| | | import RepairModal from "./Modal/RepairModal.vue"; |
| | | import {ElMessageBox, ElMessage} from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import MaintainModal from "./Modal/MaintainModal.vue"; |
| | | import AcceptanceModal from "./Modal/AcceptanceModal.vue"; |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | |
| | | defineOptions({ |
| | | name: "è®¾å¤æ¥ä¿®", |
| | |
| | | // æ¨¡ææ¡å®ä¾ |
| | | const repairModalRef = ref(); |
| | | const maintainModalRef = ref(); |
| | | const acceptanceModalRef = ref(); |
| | | |
| | | // è¡¨æ ¼å¤éæ¡éä¸é¡¹ |
| | | const multipleList = ref([]); |
| | |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "ç±»ç®", |
| | | label: "æ¥ä¿®é¡¹ç®", |
| | | align: "center", |
| | | prop: "machineryCategory", |
| | | }, |
| | |
| | | formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""), |
| | | }, |
| | | { |
| | | label: "éªæ¶äºº", |
| | | align: "center", |
| | | prop: "acceptanceName", |
| | | }, |
| | | { |
| | | label: "éªæ¶æ¶é´", |
| | | align: "center", |
| | | prop: "acceptanceTime", |
| | | formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : ""), |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | align: "center", |
| | | prop: "status", |
| | |
| | | getTableData(); |
| | | }; |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const recordId =ref(0) |
| | | const fileDialogVisible = ref(false) |
| | | |
| | | const openFileDialog = async (row) => { |
| | | recordId.value = row.id |
| | | fileDialogVisible.value = true |
| | | } |
| | | |
| | | // å¤éååä»ä¹ |
| | | const handleSelectionChange = (selectionList) => { |
| | | multipleList.value = selectionList; |
| | |
| | | maintainModalRef.value.open(row.id, row); |
| | | }; |
| | | |
| | | // æå¼éªæ¶å¼¹çª |
| | | const openAcceptance = (row) => { |
| | | acceptanceModalRef.value.open(row); |
| | | }; |
| | | |
| | | const changePage = ({page, limit}) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false" :disabled="formLoading">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm" :loading="formLoading">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false" :disabled="formLoading">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ç±»ç®"> |
| | | <el-form-item label="ä¿å
»é¡¹ç®"> |
| | | <el-input |
| | | v-model="form.machineryCategory" |
| | | placeholder="请è¾å
¥ç±»ç®" |
| | | placeholder="请è¾å
¥ä¿å
»é¡¹ç®" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="å½å
¥äºº"> |
| | |
| | | <el-option label="失败" :value="2"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¿å
»äºº"> |
| | | <el-input |
| | | v-model="form.maintenancePerson" |
| | | placeholder="请è¾å
¥ä¿å
»äººå§å" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="计åä¿å
»æ¥æ"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶" prop="attachmentIds"> |
| | | <FileUpload v-model:file-list="form.storageBlobDTOs" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </FormDialog> |
| | | </template> |
| | |
| | | import { onMounted } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "设å¤ä¿å
»æ°å¢è®¡å", |
| | |
| | | createUser: undefined, // å½å
¥äºº |
| | | status: 0, //ä¿ä¿®ç¶æ |
| | | machineryCategory: undefined, |
| | | storageBlobDTOs: [], |
| | | maintenancePerson: undefined, // ä¿å
»äºº |
| | | }); |
| | | |
| | | const setDeviceModel = (deviceId) => { |
| | |
| | | form.createUser = Number(data.createUser); |
| | | form.status = data.status; |
| | | form.machineryCategory = data.machineryCategory; |
| | | form.maintenancePerson = data.maintenancePerson; |
| | | if (data.maintenancePlanTime) { |
| | | form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format( |
| | | "YYYY-MM-DD HH:mm:ss" |
| | | ); |
| | | } |
| | | form.storageBlobDTOs = data.storageBlobVOs || []; |
| | | }; |
| | | |
| | | // ç¨æ·å表 |
| | |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¿å
»é¡¹ç®" prop="machineryCategory"> |
| | | <el-input |
| | | v-model.trim="form.machineryCategory" |
| | | placeholder="请è¾å
¥ä¿å
»é¡¹ç®" |
| | | maxlength="100" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¿å
»äºº" prop="maintenancePerson"> |
| | | <el-input |
| | | v-model.trim="form.maintenancePerson" |
| | | placeholder="请è¾å
¥ä¿å
»äººå§å" |
| | | maxlength="100" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä»»å¡é¢ç" prop="frequencyType"> |
| | | <el-select v-model="form.frequencyType" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="æ¯æ¥" value="DAILY"/> |
| | |
| | | taskName: undefined, |
| | | // å½å
¥äººï¼åéä¸ä¸ªç¨æ· id |
| | | inspector: undefined, |
| | | machineryCategory: "", |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | | week: '', |
| | | time: '', |
| | | deviceModel: undefined, // 尺寸 |
| | | registrationDate: '' |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | registrationDate: '', |
| | | maintenancePerson: '' // ä¿å
»äºº |
| | | }, |
| | | rules: { |
| | | taskId: [{ required: true, message: "è¯·éæ©è®¾å¤", trigger: "change" },], |
| | | inspector: [{ required: true, message: "è¯·éæ©å½å
¥äºº", trigger: "blur" },], |
| | | registrationDate: [{ required: true, message: "è¯·éæ©ç»è®°æ¶é´", trigger: "change" }] |
| | | registrationDate: [{ required: true, message: "è¯·éæ©ç»è®°æ¶é´", trigger: "change" }], |
| | | machineryCategory: [{ required: true, message: "请è¾å
¥ä¿å
»é¡¹ç®", trigger: "blur" }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | |
| | | taskId: undefined, |
| | | taskName: undefined, |
| | | inspector: undefined, |
| | | inspector: undefined, |
| | | machineryCategory: "", |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | | week: '', |
| | | time: '', |
| | | deviceModel: undefined, |
| | | registrationDate: '' |
| | | registrationDate: '', |
| | | maintenancePerson: '' |
| | | } |
| | | } |
| | | |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-tabs v-model="activeTab" @tab-change="handleTabChange"> |
| | | <el-tabs v-model="activeTab" |
| | | @tab-change="handleTabChange"> |
| | | <!-- 宿¶ä»»å¡ç®¡çtab --> |
| | | <el-tab-pane label="宿¶ä»»å¡ç®¡ç" name="scheduled"> |
| | | <el-tab-pane label="宿¶ä»»å¡ç®¡ç" |
| | | name="scheduled"> |
| | | <div class="search_form"> |
| | | <el-form :model="scheduledFilters" :inline="true"> |
| | | <el-form :model="scheduledFilters" |
| | | :inline="true"> |
| | | <el-form-item label="ä»»å¡åç§°"> |
| | | <el-input |
| | | v-model="scheduledFilters.taskName" |
| | | <el-input v-model="scheduledFilters.taskName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥ä»»å¡åç§°" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getScheduledTableData" |
| | | /> |
| | | @change="getScheduledTableData" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä»»å¡ç¶æ"> |
| | | <el-select v-model="scheduledFilters.status" placeholder="è¯·éæ©ä»»å¡ç¶æ" clearable style="width: 200px"> |
| | | <el-option label="å¯ç¨" value="1" /> |
| | | <el-option label="åç¨" value="0" /> |
| | | <el-select v-model="scheduledFilters.status" |
| | | placeholder="è¯·éæ©ä»»å¡ç¶æ" |
| | | clearable |
| | | style="width: 200px"> |
| | | <el-option label="å¯ç¨" |
| | | value="1" /> |
| | | <el-option label="åç¨" |
| | | value="0" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getScheduledTableData">æç´¢</el-button> |
| | | <el-button type="primary" |
| | | @click="getScheduledTableData">æç´¢</el-button> |
| | | <el-button @click="resetScheduledFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <el-text class="mx-1" size="large">宿¶ä»»å¡ç®¡ç</el-text> |
| | | <el-text class="mx-1" |
| | | size="large">宿¶ä»»å¡ç®¡ç</el-text> |
| | | <div> |
| | | <el-button type="primary" icon="Plus" @click="addScheduledTask"> |
| | | <el-button type="primary" |
| | | icon="Plus" |
| | | @click="addScheduledTask"> |
| | | æ°å¢ä»»å¡ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | <el-button type="danger" |
| | | icon="Delete" |
| | | :disabled="scheduledMultipleList.length <= 0" |
| | | @click="delScheduledTaskByIds(scheduledMultipleList.map((item) => item.id))" |
| | | > |
| | | @click="delScheduledTaskByIds(scheduledMultipleList.map((item) => item.id))"> |
| | | æ¹éå é¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | <PIMTable rowKey="id" |
| | | isSelection |
| | | :column="scheduledColumns" |
| | | :tableData="scheduledDataList" |
| | |
| | | total: scheduledPagination.total, |
| | | }" |
| | | @selection-change="handleScheduledSelectionChange" |
| | | @pagination="changeScheduledPage" |
| | | > |
| | | @pagination="changeScheduledPage"> |
| | | <template #statusRef="{ row }"> |
| | | <el-tag v-if="row.status === 1" type="success">å¯ç¨</el-tag> |
| | | <el-tag v-if="row.status === 0" type="danger">åç¨</el-tag> |
| | | <el-tag v-if="row.status === 1" |
| | | type="success">å¯ç¨</el-tag> |
| | | <el-tag v-if="row.status === 0" |
| | | type="danger">åç¨</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button |
| | | type="primary" |
| | | <el-button type="primary" |
| | | link |
| | | @click="editScheduledTask(row)" |
| | | > |
| | | @click="editScheduledTask(row)"> |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | <el-button type="danger" |
| | | link |
| | | @click="delScheduledTaskByIds(row.id)" |
| | | > |
| | | @click="delScheduledTaskByIds(row.id)"> |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- ä»»å¡è®°å½tabï¼å设å¤ä¿å
»é¡µé¢ï¼ --> |
| | | <el-tab-pane label="ä»»å¡è®°å½" name="record"> |
| | | <el-tab-pane label="ä»»å¡è®°å½" |
| | | name="record"> |
| | | <div class="search_form"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form :model="filters" |
| | | :inline="true"> |
| | | <el-form-item label="设å¤åç§°"> |
| | | <el-input |
| | | v-model="filters.deviceName" |
| | | <el-input v-model="filters.deviceName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥è®¾å¤åç§°" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | @change="getTableData" /> |
| | | </el-form-item> |
| | | <el-form-item label="计åä¿å
»æ¥æ"> |
| | | <el-date-picker |
| | | v-model="filters.maintenancePlanTime" |
| | | <el-date-picker v-model="filters.maintenancePlanTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©è®¡åä¿å
»æ¥æ" |
| | | size="default" |
| | | @change="(date) => handleDateChange(date,2)" |
| | | /> |
| | | @change="(date) => handleDateChange(date,2)" /> |
| | | </el-form-item> |
| | | <el-form-item label="å®é
ä¿å
»æ¥æ"> |
| | | <el-date-picker |
| | | v-model="filters.maintenanceActuallyTime" |
| | | <el-date-picker v-model="filters.maintenanceActuallyTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©å®é
ä¿å
»æ¥æ" |
| | | size="default" |
| | | @change="(date) => handleDateChange(date,1)" |
| | | /> |
| | | @change="(date) => handleDateChange(date,1)" /> |
| | | </el-form-item> |
| | | <el-form-item label="å®é
ä¿å
»äºº"> |
| | | <el-input |
| | | v-model="filters.maintenanceActuallyName" |
| | | <el-input v-model="filters.maintenanceActuallyName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å®é
ä¿å
»äºº" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | @change="getTableData" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button type="primary" |
| | | @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <el-text class="mx-1" size="large">ä»»å¡è®°å½</el-text> |
| | | <el-text class="mx-1" |
| | | size="large">ä»»å¡è®°å½</el-text> |
| | | <div> |
| | | <el-button type="success" icon="Van" @click="addPlan"> |
| | | <el-button type="success" |
| | | icon="Van" |
| | | @click="addPlan"> |
| | | æ°å¢è®¡å |
| | | </el-button> |
| | | <el-button @click="handleOut"> |
| | | å¯¼åº |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | <el-button type="danger" |
| | | icon="Delete" |
| | | :disabled="multipleList.length <= 0 || hasFinishedStatus" |
| | | @click="delRepairByIds(multipleList.map((item) => item.id))" |
| | | > |
| | | @click="delRepairByIds(multipleList.map((item) => item.id))"> |
| | | æ¹éå é¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | <PIMTable rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | @pagination="changePage"> |
| | | <template #maintenanceResultRef="{ row }"> |
| | | <div>{{ row.maintenanceResult || '-' }}</div> |
| | | </template> |
| | | <template #statusRef="{ row }"> |
| | | <el-tag v-if="row.status === 2" type="danger">失败</el-tag> |
| | | <el-tag v-if="row.status === 1" type="success">å®ç»</el-tag> |
| | | <el-tag v-if="row.status === 0" type="warning">å¾
ä¿å
»</el-tag> |
| | | <el-tag v-if="row.status === 2" |
| | | type="danger">失败</el-tag> |
| | | <el-tag v-if="row.status === 1" |
| | | type="success">å®ç»</el-tag> |
| | | <el-tag v-if="row.status === 0" |
| | | type="warning">å¾
ä¿å
»</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <!-- è¿ä¸ªåè½è·æ°å¢ä¿å
»åè½ä¸æ¨¡ä¸æ ·ï¼æå¥æä¹ï¼ --> |
| | |
| | | > |
| | | æ°å¢ä¿å
» |
| | | </el-button> --> |
| | | <el-button |
| | | type="primary" |
| | | <el-button type="primary" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="editPlan(row.id)" |
| | | > |
| | | @click="editPlan(row.id)"> |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="success" |
| | | <el-button type="success" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="addMaintain(row)" |
| | | > |
| | | @click="addMaintain(row)"> |
| | | ä¿å
» |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | <el-button type="danger" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="delRepairByIds(row.id)" |
| | | > |
| | | @click="delRepairByIds(row.id)"> |
| | | å é¤ |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | <el-button type="primary" |
| | | link |
| | | @click="openFileDialog(row)" |
| | | > |
| | | @click="openFileDialog(row)"> |
| | | éä»¶ |
| | | </el-button> |
| | | </template> |
| | |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | <PlanModal ref="planModalRef" @ok="getTableData" /> |
| | | <MaintenanceModal ref="maintainModalRef" @ok="getTableData" /> |
| | | <FormDia ref="formDiaRef" @closeDia="getScheduledTableData" /> |
| | | <FileListDialog |
| | | ref="fileListDialogRef" |
| | | v-model="fileDialogVisible" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :delete-method="handleAttachmentDelete" |
| | | :name-column-label="'éä»¶åç§°'" |
| | | :rulesRegulationsManagementId="currentMaintenanceTaskId" |
| | | @upload="handleAttachmentUpload" /> |
| | | <PlanModal ref="planModalRef" |
| | | @ok="getTableData" /> |
| | | <MaintenanceModal ref="maintainModalRef" |
| | | @ok="getTableData" /> |
| | | <FormDia ref="formDiaRef" |
| | | @closeDia="getScheduledTableData" /> |
| | | <FileList v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | :record-type="'device_maintenance'" |
| | | :record-id="currentMaintenanceTaskId" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, reactive, getCurrentInstance, nextTick, computed } from 'vue' |
| | | import { Search } from '@element-plus/icons-vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import PlanModal from './Form/PlanModal.vue' |
| | | import MaintenanceModal from './Form/MaintenanceModal.vue' |
| | | import FormDia from './Form/formDia.vue' |
| | | import FileListDialog from '@/components/Dialog/FileListDialog.vue' |
| | | import { |
| | | ref, |
| | | onMounted, |
| | | reactive, |
| | | getCurrentInstance, |
| | | nextTick, |
| | | computed, |
| | | defineAsyncComponent, |
| | | } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PlanModal from "./Form/PlanModal.vue"; |
| | | import MaintenanceModal from "./Form/MaintenanceModal.vue"; |
| | | import FormDia from "./Form/formDia.vue"; |
| | | import { |
| | | getUpkeepPage, |
| | | delUpkeep, |
| | | deviceMaintenanceTaskList, |
| | | deviceMaintenanceTaskDel, |
| | | } from '@/api/equipmentManagement/upkeep' |
| | | import { |
| | | listMaintenanceTaskFiles, |
| | | addMaintenanceTaskFile, |
| | | delMaintenanceTaskFile, |
| | | } from '@/api/equipmentManagement/maintenanceTaskFile' |
| | | import dayjs from 'dayjs' |
| | | } from "@/api/equipmentManagement/upkeep"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const { proxy } = getCurrentInstance(); |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | |
| | | // Tabç¸å
³ |
| | | const activeTab = ref('scheduled') |
| | | const activeTab = ref("scheduled"); |
| | | |
| | | // 计åå¼¹çªæ§å¶å¨ |
| | | const planModalRef = ref() |
| | | const planModalRef = ref(); |
| | | // ä¿å
»å¼¹çªæ§å¶å¨ |
| | | const maintainModalRef = ref() |
| | | const maintainModalRef = ref(); |
| | | // 宿¶ä»»å¡å¼¹çªæ§å¶å¨ |
| | | const formDiaRef = ref() |
| | | const formDiaRef = ref(); |
| | | // éä»¶å¼¹çª |
| | | const fileListDialogRef = ref(null) |
| | | const fileDialogVisible = ref(false) |
| | | const currentMaintenanceTaskId = ref(null) |
| | | const fileListDialogRef = ref(null); |
| | | const fileDialogVisible = ref(false); |
| | | const currentMaintenanceTaskId = ref(null); |
| | | |
| | | // ä»»å¡è®°å½tabï¼å设å¤ä¿å
»é¡µé¢ï¼ç¸å
³åé |
| | | const filters = reactive({ |
| | | deviceName: '', |
| | | maintenancePlanTime: '', |
| | | maintenanceActuallyTime: '', |
| | | maintenanceActuallyName: '', |
| | | }) |
| | | deviceName: "", |
| | | maintenancePlanTime: "", |
| | | maintenanceActuallyTime: "", |
| | | maintenanceActuallyName: "", |
| | | }); |
| | | |
| | | const dataList = ref([]) |
| | | const dataList = ref([]); |
| | | const pagination = ref({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }) |
| | | const multipleList = ref([]) |
| | | }); |
| | | const multipleList = ref([]); |
| | | |
| | | // 宿¶ä»»å¡ç®¡çtabç¸å
³åé |
| | | const scheduledFilters = reactive({ |
| | | taskName: '', |
| | | status: '', |
| | | }) |
| | | taskName: "", |
| | | status: "", |
| | | }); |
| | | |
| | | const scheduledDataList = ref([]) |
| | | const scheduledDataList = ref([]); |
| | | const scheduledPagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }) |
| | | const scheduledMultipleList = ref([]) |
| | | }); |
| | | const scheduledMultipleList = ref([]); |
| | | |
| | | // 宿¶ä»»å¡ç®¡çè¡¨æ ¼åé
ç½® |
| | | const scheduledColumns = ref([ |
| | | { prop: "taskName", label: "设å¤åç§°"}, |
| | | { |
| | | label: "尺寸", |
| | | label: "è§æ ¼åå·", |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "ä¿å
»é¡¹ç®", |
| | | prop: "machineryCategory", |
| | | minWidth: 120, |
| | | formatData: cell => cell || "--", |
| | | }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | | minWidth: 150, |
| | | // PIMTable 使ç¨çæ¯ formatDataï¼è䏿¯ Element-Plus ç formatter |
| | | formatData: (cell) => ({ |
| | | formatData: cell => |
| | | ({ |
| | | DAILY: "æ¯æ¥", |
| | | WEEKLY: "æ¯å¨", |
| | | MONTHLY: "æ¯æ", |
| | | QUARTERLY: "å£åº¦" |
| | | }[cell] || "") |
| | | QUARTERLY: "å£åº¦", |
| | | }[cell] || ""), |
| | | }, |
| | | { |
| | | prop: "frequencyDetail", |
| | | label: "å¼å§æ¥æä¸æ¶é´", |
| | | minWidth: 150, |
| | | // åæ ·æ¹ç¨ formatDataï¼PIMTable å
é¨ä¼æåå
æ ¼å¼ä¼ è¿æ¥ |
| | | formatData: (cell) => { |
| | | if (typeof cell !== 'string') return ''; |
| | | formatData: cell => { |
| | | if (typeof cell !== "string") return ""; |
| | | let val = cell; |
| | | const replacements = { |
| | | MON: 'å¨ä¸', |
| | | TUE: 'å¨äº', |
| | | WED: 'å¨ä¸', |
| | | THU: 'å¨å', |
| | | FRI: 'å¨äº', |
| | | SAT: 'å¨å
', |
| | | SUN: '卿¥' |
| | | MON: "å¨ä¸", |
| | | TUE: "å¨äº", |
| | | WED: "å¨ä¸", |
| | | THU: "å¨å", |
| | | FRI: "å¨äº", |
| | | SAT: "å¨å
", |
| | | SUN: "卿¥", |
| | | }; |
| | | // ä½¿ç¨æ£å䏿¬¡æ§æ¿æ¢ææå¹é
项 |
| | | return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]); |
| | | } |
| | | return val.replace( |
| | | /MON|TUE|WED|THU|FRI|SAT|SUN/g, |
| | | match => replacements[match] |
| | | ); |
| | | }, |
| | | }, |
| | | { prop: "maintenancePerson", label: "ä¿å
»äºº", minWidth: 100 }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "registrationDate", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | { |
| | |
| | | align: "center", |
| | | width: "200px", |
| | | }, |
| | | ]) |
| | | ]); |
| | | |
| | | // ä»»å¡è®°å½è¡¨æ ¼åé
ç½®ï¼å设å¤ä¿å
»è¡¨æ ¼åï¼ |
| | | const columns = ref([ |
| | |
| | | prop: "deviceName", |
| | | }, |
| | | { |
| | | label: "尺寸", |
| | | label: "è§æ ¼åå·", |
| | | align: "center", |
| | | prop: "deviceModel", |
| | | }, |
| | |
| | | label: "计åä¿å
»æ¥æ", |
| | | align: "center", |
| | | prop: "maintenancePlanTime", |
| | | formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"), |
| | | formatData: cell => dayjs(cell).format("YYYY-MM-DD"), |
| | | }, |
| | | { |
| | | label: "å½å
¥äºº", |
| | |
| | | prop: "createUserName", |
| | | }, |
| | | { |
| | | label: "ç±»ç®", |
| | | label: "ä¿å
»é¡¹ç®", |
| | | align: "center", |
| | | prop: "machineryCategory", |
| | | formatData: cell => cell || "--", |
| | | }, |
| | | // { |
| | | // label: "å½å
¥æ¥æ", |
| | |
| | | label: "å®é
ä¿å
»æ¥æ", |
| | | align: "center", |
| | | prop: "maintenanceActuallyTime", |
| | | formatData: (cell) => |
| | | formatData: cell => |
| | | cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-", |
| | | }, |
| | | { |
| | |
| | | align: "center", |
| | | width: "350px", |
| | | }, |
| | | ]) |
| | | ]); |
| | | |
| | | // Tab忢å¤ç |
| | | const handleTabChange = (tabName) => { |
| | | if (tabName === 'record') { |
| | | getTableData() |
| | | } else if (tabName === 'scheduled') { |
| | | getScheduledTableData() |
| | | const handleTabChange = tabName => { |
| | | if (tabName === "record") { |
| | | getTableData(); |
| | | } else if (tabName === "scheduled") { |
| | | getScheduledTableData(); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 宿¶ä»»å¡ç®¡çç¸å
³æ¹æ³ |
| | | const getScheduledTableData = async () => { |
| | |
| | | size: scheduledPagination.pageSize, |
| | | taskName: scheduledFilters.taskName || undefined, |
| | | status: scheduledFilters.status || undefined, |
| | | } |
| | | const { code, data } = await deviceMaintenanceTaskList(params) |
| | | }; |
| | | const { code, data } = await deviceMaintenanceTaskList(params); |
| | | if (code === 200) { |
| | | scheduledDataList.value = data?.records || [] |
| | | scheduledPagination.total = data?.total || 0 |
| | | scheduledDataList.value = data?.records || []; |
| | | scheduledPagination.total = data?.total || 0; |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('è·å宿¶ä»»å¡å表失败') |
| | | ElMessage.error("è·å宿¶ä»»å¡å表失败"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const resetScheduledFilters = () => { |
| | | scheduledFilters.taskName = '' |
| | | scheduledFilters.status = '' |
| | | getScheduledTableData() |
| | | } |
| | | scheduledFilters.taskName = ""; |
| | | scheduledFilters.status = ""; |
| | | getScheduledTableData(); |
| | | }; |
| | | |
| | | const handleScheduledSelectionChange = (selection) => { |
| | | scheduledMultipleList.value = selection |
| | | } |
| | | const handleScheduledSelectionChange = selection => { |
| | | scheduledMultipleList.value = selection; |
| | | }; |
| | | |
| | | const changeScheduledPage = (page) => { |
| | | scheduledPagination.currentPage = page.page |
| | | scheduledPagination.pageSize = page.limit |
| | | getScheduledTableData() |
| | | } |
| | | const changeScheduledPage = page => { |
| | | scheduledPagination.currentPage = page.page; |
| | | scheduledPagination.pageSize = page.limit; |
| | | getScheduledTableData(); |
| | | }; |
| | | |
| | | const addScheduledTask = () => { |
| | | nextTick(() => { |
| | | formDiaRef.value?.openDialog('add'); |
| | | formDiaRef.value?.openDialog("add"); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const editScheduledTask = (row) => { |
| | | const editScheduledTask = row => { |
| | | if (row) { |
| | | nextTick(() => { |
| | | formDiaRef.value?.openDialog('edit', row); |
| | | formDiaRef.value?.openDialog("edit", row); |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const delScheduledTaskByIds = async (ids) => { |
| | | const delScheduledTaskByIds = async ids => { |
| | | try { |
| | | await ElMessageBox.confirm('ç¡®å®å é¤éä¸ç宿¶ä»»å¡åï¼', 'æç¤º', { |
| | | type: 'warning', |
| | | }) |
| | | const payload = Array.isArray(ids) ? ids : [ids] |
| | | await deviceMaintenanceTaskDel(payload) |
| | | ElMessage.success('å é¤å®æ¶ä»»å¡æå') |
| | | getScheduledTableData() |
| | | await ElMessageBox.confirm("ç¡®å®å é¤éä¸ç宿¶ä»»å¡åï¼", "æç¤º", { |
| | | type: "warning", |
| | | }); |
| | | const payload = Array.isArray(ids) ? ids : [ids]; |
| | | await deviceMaintenanceTaskDel(payload); |
| | | ElMessage.success("å é¤å®æ¶ä»»å¡æå"); |
| | | getScheduledTableData(); |
| | | } catch (error) { |
| | | // ç¨æ·åæ¶å é¤ |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const handleScheduledOut = () => { |
| | | ElMessage.info('导åºå®æ¶ä»»å¡åè½å¾
å®ç°') |
| | | } |
| | | ElMessage.info("导åºå®æ¶ä»»å¡åè½å¾
å®ç°"); |
| | | }; |
| | | |
| | | // ä»»å¡è®°å½ç¸å
³æ¹æ³ï¼å设å¤ä¿å
»é¡µé¢æ¹æ³ï¼ |
| | | const getTableData = async () => { |
| | |
| | | current: pagination.value.currentPage, |
| | | size: pagination.value.pageSize, |
| | | deviceName: filters.deviceName || undefined, |
| | | maintenancePlanTime: filters.maintenancePlanTime ? dayjs(filters.maintenancePlanTime).format('YYYY-MM-DD') : undefined, |
| | | maintenanceActuallyTime: filters.maintenanceActuallyTime ? dayjs(filters.maintenanceActuallyTime).format('YYYY-MM-DD') : undefined, |
| | | maintenancePlanTime: filters.maintenancePlanTime |
| | | ? dayjs(filters.maintenancePlanTime).format("YYYY-MM-DD") |
| | | : undefined, |
| | | maintenanceActuallyTime: filters.maintenanceActuallyTime |
| | | ? dayjs(filters.maintenanceActuallyTime).format("YYYY-MM-DD") |
| | | : undefined, |
| | | maintenanceActuallyName: filters.maintenanceActuallyName || undefined, |
| | | } |
| | | }; |
| | | |
| | | const { code, data } = await getUpkeepPage(params) |
| | | const { code, data } = await getUpkeepPage(params); |
| | | if (code === 200) { |
| | | dataList.value = data.records |
| | | pagination.value.total = data.total |
| | | dataList.value = data.records; |
| | | pagination.value.total = data.total; |
| | | } |
| | | } catch (error) { |
| | | console.log(error); |
| | | |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.deviceName = '' |
| | | filters.maintenancePlanTime = '' |
| | | filters.maintenanceActuallyTime = '' |
| | | filters.maintenanceActuallyName = '' |
| | | getTableData() |
| | | } |
| | | filters.deviceName = ""; |
| | | filters.maintenancePlanTime = ""; |
| | | filters.maintenanceActuallyTime = ""; |
| | | filters.maintenanceActuallyName = ""; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | multipleList.value = selection |
| | | } |
| | | const handleSelectionChange = selection => { |
| | | multipleList.value = selection; |
| | | }; |
| | | |
| | | // æ£æ¥éä¸çè®°å½ä¸æ¯å¦æå®ç»ç¶æç |
| | | const hasFinishedStatus = computed(() => { |
| | | return multipleList.value.some(item => item.status === 1) |
| | | }) |
| | | return multipleList.value.some(item => item.status === 1); |
| | | }); |
| | | |
| | | const changePage = (page) => { |
| | | pagination.value.currentPage = page.page |
| | | pagination.value.pageSize = page.limit |
| | | getTableData() |
| | | } |
| | | const changePage = page => { |
| | | pagination.value.currentPage = page.page; |
| | | pagination.value.pageSize = page.limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const addMaintain = (row) => { |
| | | maintainModalRef.value.open(row.id, row) |
| | | } |
| | | const addMaintain = row => { |
| | | maintainModalRef.value.open(row.id, row); |
| | | }; |
| | | |
| | | const addPlan = () => { |
| | | planModalRef.value.openModal() |
| | | } |
| | | planModalRef.value.openModal(); |
| | | }; |
| | | |
| | | const editPlan = (id) => { |
| | | planModalRef.value.openEdit(id) |
| | | } |
| | | const editPlan = id => { |
| | | planModalRef.value.openEdit(id); |
| | | }; |
| | | |
| | | const delRepairByIds = async (ids) => { |
| | | const delRepairByIds = async ids => { |
| | | // æ£æ¥æ¯å¦æå®ç»ç¶æçè®°å½ |
| | | const hasFinished = multipleList.value.some(item => item.status === 1) |
| | | const hasFinished = multipleList.value.some(item => item.status === 1); |
| | | if (hasFinished) { |
| | | ElMessage.warning('ä¸è½å é¤ç¶æä¸ºå®ç»çè®°å½') |
| | | return |
| | | ElMessage.warning("ä¸è½å é¤ç¶æä¸ºå®ç»çè®°å½"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | await ElMessageBox.confirm('确认å é¤ä¿å
»æ°æ®, æ¤æä½ä¸å¯é?', 'è¦å', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }) |
| | | await ElMessageBox.confirm("确认å é¤ä¿å
»æ°æ®, æ¤æä½ä¸å¯é?", "è¦å", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }); |
| | | |
| | | const { code } = await delUpkeep(ids) |
| | | const { code } = await delUpkeep(ids); |
| | | if (code === 200) { |
| | | ElMessage.success('å 餿å') |
| | | getTableData() |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | } |
| | | } catch (error) { |
| | | // ç¨æ·åæ¶å é¤ |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm('éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼', '导åº', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download('/device/maintenance/export', {}, '设å¤ä¿å
».xlsx') |
| | | proxy.download("/device/maintenance/export", {}, "设å¤ä¿å
».xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.info('已忶') |
| | | }) |
| | | } |
| | | ElMessage.info("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | const handleDateChange = (date, type) => { |
| | | if (type === 1) { |
| | | filters.maintenanceActuallyTime = date ? dayjs(date).format('YYYY-MM-DD') : '' |
| | | filters.maintenanceActuallyTime = date |
| | | ? dayjs(date).format("YYYY-MM-DD") |
| | | : ""; |
| | | } else { |
| | | filters.maintenancePlanTime = date ? dayjs(date).format('YYYY-MM-DD') : '' |
| | | filters.maintenancePlanTime = date ? dayjs(date).format("YYYY-MM-DD") : ""; |
| | | } |
| | | getTableData() |
| | | } |
| | | |
| | | // éä»¶ç¸å
³æ¹æ³ |
| | | // æ¥è¯¢éä»¶å表 |
| | | const fetchMaintenanceTaskFiles = async (deviceMaintenanceId) => { |
| | | try { |
| | | const params = { |
| | | current: 1, |
| | | size: 100, |
| | | deviceMaintenanceId, |
| | | rulesRegulationsManagementId:deviceMaintenanceId |
| | | } |
| | | const res = await listMaintenanceTaskFiles(params) |
| | | const records = res?.data?.records || [] |
| | | const mapped = records.map(item => ({ |
| | | id: item.id, |
| | | name: item.fileName || item.name, |
| | | url: item.fileUrl || item.url, |
| | | raw: item, |
| | | })) |
| | | fileListDialogRef.value?.setList(mapped) |
| | | } catch (error) { |
| | | ElMessage.error('è·åéä»¶å表失败') |
| | | } |
| | | } |
| | | getTableData(); |
| | | }; |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const openFileDialog = async (row) => { |
| | | currentMaintenanceTaskId.value = row.id |
| | | fileDialogVisible.value = true |
| | | await fetchMaintenanceTaskFiles(row.id) |
| | | } |
| | | |
| | | // å·æ°éä»¶å表 |
| | | const refreshFileList = async () => { |
| | | if (!currentMaintenanceTaskId.value) return |
| | | await fetchMaintenanceTaskFiles(currentMaintenanceTaskId.value) |
| | | } |
| | | |
| | | // ä¸ä¼ éä»¶ |
| | | const handleAttachmentUpload = async (filePayload) => { |
| | | if (!currentMaintenanceTaskId.value) return |
| | | try { |
| | | const payload = { |
| | | name: filePayload?.fileName || filePayload?.name, |
| | | url: filePayload?.fileUrl || filePayload?.url, |
| | | deviceMaintenanceId: currentMaintenanceTaskId.value, |
| | | } |
| | | await addMaintenanceTaskFile(payload) |
| | | ElMessage.success('æä»¶ä¸ä¼ æå') |
| | | await refreshFileList() |
| | | } catch (error) { |
| | | ElMessage.error('æä»¶ä¸ä¼ 失败') |
| | | } |
| | | } |
| | | |
| | | // å é¤éä»¶ |
| | | const handleAttachmentDelete = async (row) => { |
| | | if (!row?.id) return false |
| | | try { |
| | | await ElMessageBox.confirm('确认å é¤è¯¥éä»¶ï¼', 'æç¤º', { type: 'warning' }) |
| | | } catch { |
| | | return false |
| | | } |
| | | try { |
| | | await delMaintenanceTaskFile(row.id) |
| | | ElMessage.success('å 餿å') |
| | | await refreshFileList() |
| | | return true |
| | | } catch (error) { |
| | | ElMessage.error('å é¤å¤±è´¥') |
| | | return false |
| | | } |
| | | } |
| | | const openFileDialog = async row => { |
| | | currentMaintenanceTaskId.value = row.id; |
| | | fileDialogVisible.value = true; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // æ ¹æ®é»è®¤æ¿æ´»ç Tab è°ç¨å¯¹åºçæ¥è¯¢æ¥å£ |
| | | if (activeTab.value === 'scheduled') { |
| | | getScheduledTableData() |
| | | if (activeTab.value === "scheduled") { |
| | | getScheduledTableData(); |
| | | } else { |
| | | getTableData() |
| | | getTableData(); |
| | | } |
| | | }) |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åé
书ç±ï¼" prop="documentationId"> |
| | | <!-- <el-select v-model="borrowForm.documentationId" placeholder="è¯·éæ©åé
书ç±" style="width: 100%" @change="handleScanContent"> |
| | | <el-option |
| | | v-for="item in documentList" |
| | | :key="item.id" |
| | | :label="item.docName || item.name" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> --> |
| | | <div style="display: flex; gap: 10px;"> |
| | | <el-select v-model="borrowForm.documentationId" placeholder="è¯·éæ©åé
书ç±" style="flex: 1;width: 100px;" @change="handleSelectChange"> |
| | | <el-select |
| | | v-if="borrowOperationType !== 'edit'" |
| | | v-model="borrowForm.documentationId" |
| | | placeholder="è¯·éæ©åé
书ç±" |
| | | style="flex: 1;width: 100px;" |
| | | @change="handleSelectChange" |
| | | > |
| | | <el-option |
| | | v-for="item in documentList" |
| | | :key="item.id" |
| | |
| | | /> |
| | | </el-select> |
| | | <el-input |
| | | v-else |
| | | v-model="currentEditDocName" |
| | | style="flex: 1;width: 100px;" |
| | | disabled |
| | | /> |
| | | <el-input |
| | | v-if="borrowOperationType !== 'edit'" |
| | | v-model="scanContent" |
| | | placeholder="æ«ç è¾å
¥" |
| | | style="width: 100px;" |
| | |
| | | const selectedRows = ref([]); |
| | | const documentList = ref([]); // ææ¡£å表ï¼ç¨äºåé
书ç±éæ© |
| | | const scanContent = ref() // æ«ç å
容 |
| | | const currentEditDocName = ref(''); // ç¼è¾æ¶åå¨çææ¡£åç§° |
| | | // å页ç¸å
³ |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | disabled: (row) => row.borrowStatus === 'å½è¿', |
| | | clickFun: (row) => { |
| | | openBorrowDia('edit', row) |
| | | }, |
| | |
| | | if (type === "edit") { |
| | | // ç¼è¾æ¨¡å¼ï¼å è½½ç°ææ°æ® |
| | | Object.assign(borrowForm, data); |
| | | // åå¨ææ¡£åç§°ç¨äºæ¾ç¤º |
| | | currentEditDocName.value = data.docName || ''; |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ï¼æ¸
空表å |
| | | Object.keys(borrowForm).forEach(key => { |
| | | borrowForm[key] = ""; |
| | | }); |
| | | currentEditDocName.value = ''; // æ¸
空ç¼è¾æ¶çææ¡£åç§° |
| | | // 设置é»è®¤ç¶æ |
| | | borrowForm.borrowStatus = "åé
"; |
| | | // 设置å½åæ¥æä¸ºåé
æ¥æ |
| | |
| | | proxy.$refs.borrowFormRef.resetFields(); |
| | | borrowDia.value = false; |
| | | scanContent.value = ''; // æ¸
空æ«ç å
容 |
| | | currentEditDocName.value = ''; // æ¸
空ç¼è¾æ¶çææ¡£åç§° |
| | | }; |
| | | |
| | | // æäº¤åé
表å |
| | |
| | | documentForm[key] = ""; |
| | | }); |
| | | documentForm.attachments = []; // æ°å¢æ¨¡å¼ä¸ä¹æ¸
空éä»¶ |
| | | // 设置é»è®¤å¼ - 使ç¨åå
¸æ°æ®ç第ä¸ä¸ªé项ä½ä¸ºé»è®¤å¼ |
| | | // 设置é»è®¤å¼ - ææ¡£ç¶æé»è®¤è®¾ç½®ä¸º"æ£å¸¸" |
| | | if (document_status.value && document_status.value.length > 0) { |
| | | documentForm.docStatus = document_status.value[0].value; |
| | | const normalStatus = document_status.value.find(item => item.label === 'æ£å¸¸'); |
| | | documentForm.docStatus = normalStatus ? normalStatus.value : document_status.value[0].value; |
| | | } |
| | | if (document_urgency.value && document_urgency.value.length > 0) { |
| | | documentForm.urgencyLevel = document_urgency.value[0].value; |
| | | const normalUrgency = document_urgency.value.find(item => item.label === 'æ®é'); |
| | | documentForm.urgencyLevel = normalUrgency ? normalUrgency.value : document_urgency.value[0].value; |
| | | } |
| | | } |
| | | }; |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ææ¡£ï¼" prop="borrowId"> |
| | | <!-- <el-select v-model="returnForm.borrowId" placeholder="è¯·éæ©ææ¡£" style="flex: 1;" @change="handleDocumentChange"> |
| | | <el-option |
| | | v-for="item in documentList" |
| | | :key="item.id" |
| | | :label="item.docName || item.name" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> --> |
| | | <div style="display: flex; gap: 10px;"> |
| | | <el-select v-model="returnForm.borrowId" placeholder="è¯·éæ©ææ¡£" style="width: 120px;" @change="handleDocumentChange"> |
| | | <el-select |
| | | v-if="returnOperationType !== 'edit'" |
| | | v-model="returnForm.borrowId" |
| | | placeholder="è¯·éæ©ææ¡£" |
| | | style="width: 120px;" |
| | | @change="handleDocumentChange" |
| | | > |
| | | <el-option |
| | | v-for="item in documentList" |
| | | :key="item.id" |
| | |
| | | /> |
| | | </el-select> |
| | | <el-input |
| | | v-else |
| | | v-model="currentEditDocName" |
| | | style="width: 120px;" |
| | | disabled |
| | | /> |
| | | <el-input |
| | | v-if="returnOperationType !== 'edit'" |
| | | v-model="scanContent" |
| | | placeholder="æ«ç è¾å
¥" |
| | | style="flex: 1;" |
| | |
| | | const documentList = ref([]); // ææ¡£å表 |
| | | const borrowInfoList = ref([]); // åé
ä¿¡æ¯å表 |
| | | const scanContent = ref(); // æ«ç å
容 |
| | | const currentEditDocName = ref(''); // ç¼è¾æ¶åå¨çææ¡£åç§° |
| | | |
| | | // å页ç¸å
³ |
| | | const pagination = reactive({ |
| | |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | disabled: (row) => row.borrowStatus === 'å½è¿', |
| | | clickFun: (row) => { |
| | | openReturnDia('edit', row) |
| | | }, |
| | |
| | | if (type === "edit") { |
| | | // ç¼è¾æ¨¡å¼ï¼å è½½ç°ææ°æ® |
| | | Object.assign(returnForm, data); |
| | | // ç¼è¾æ¨¡å¼ä¸ï¼ææ¡£éæ©åèªå¨å¡«å
åé
人ååºå½è¿æ¥æ |
| | | if (returnForm.borrowId) { |
| | | handleDocumentChange(returnForm.borrowId); |
| | | } |
| | | // åå¨ææ¡£åç§°ç¨äºæ¾ç¤º |
| | | currentEditDocName.value = data.docName || ''; |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ï¼æ¸
空表å |
| | | Object.keys(returnForm).forEach(key => { |
| | | returnForm[key] = ""; |
| | | }); |
| | | currentEditDocName.value = ''; // æ¸
空ç¼è¾æ¶çææ¡£åç§° |
| | | // 设置é»è®¤ç¶æ |
| | | returnForm.borrowStatus = "å½è¿"; |
| | | // 设置å½åæ¥æä¸ºå½è¿æ¥æ |
| | |
| | | returnDia.value = false; |
| | | scanContent.value = ''; // æ¸
空æ«ç å
容 |
| | | borrowInfoList.value = []; // æ¸
空åé
ä¿¡æ¯å表 |
| | | currentEditDocName.value = ''; // æ¸
空ç¼è¾æ¶çææ¡£åç§° |
| | | }; |
| | | |
| | | // æäº¤å½è¿è¡¨å |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="èµäº§ç¼å·:"> |
| | | <el-input v-model="filters.assetCode" placeholder="请è¾å
¥èµäº§ç¼å·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="èµäº§åç§°:"> |
| | | <el-input v-model="filters.assetName" placeholder="请è¾å
¥èµäº§åç§°" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="èµäº§ç±»å«:"> |
| | | <el-select v-model="filters.category" placeholder="è¯·éæ©ç±»å«" clearable style="width: 150px;"> |
| | | <el-option label="æ¿å±å»ºç" value="building" /> |
| | | <el-option label="æºå¨è®¾å¤" value="machine" /> |
| | | <el-option label="è¿è¾å·¥å
·" value="vehicle" /> |
| | | <el-option label="çµå设å¤" value="electronic" /> |
| | | <el-option label="åå
¬å®¶å
·" value="furniture" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="å¨ç¨" value="in_use" /> |
| | | <el-option label="é²ç½®" value="idle" /> |
| | | <el-option label="æ¥åº" value="scrapped" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-statistic title="èµäº§åå¼å计" :value="totalOriginalValue" precision="2" prefix="Â¥" /> |
| | | <el-statistic title="ç´¯è®¡ææ§å计" :value="totalDepreciation" precision="2" prefix="Â¥" style="margin-left: 30px;" /> |
| | | <el-statistic title="åå¼å计" :value="totalNetValue" precision="2" prefix="Â¥" style="margin-left: 30px;" /> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢èµäº§</el-button> |
| | | <el-button type="warning" @click="handleDepreciation" icon="Money">ææ§è®¡æ</el-button> |
| | | <!-- <el-button @click="handleOut" icon="Download">导åº</el-button> --> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #originalValue="{ row }"> |
| | | <span class="text-primary">Â¥{{ formatMoney(row.originalValue) }}</span> |
| | | </template> |
| | | <template #accumulatedDepreciation="{ row }"> |
| | | <span class="text-warning">Â¥{{ formatMoney(row.accumulatedDepreciation) }}</span> |
| | | </template> |
| | | <template #netValue="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.netValue) }}</span> |
| | | </template> |
| | | <template #category="{ row }"> |
| | | <el-tag>{{ getCategoryLabel(row.category) }}</el-tag> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)">ç¼è¾</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" :disabled="isView" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§ç¼å·" prop="assetCode"> |
| | | <el-input v-model="form.assetCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§åç§°" prop="assetName"> |
| | | <el-input v-model="form.assetName" placeholder="请è¾å
¥èµäº§åç§°" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§ç±»å«" prop="category"> |
| | | <el-select v-model="form.category" placeholder="è¯·éæ©èµäº§ç±»å«" style="width: 100%;"> |
| | | <el-option label="æ¿å±å»ºç" value="building" /> |
| | | <el-option label="æºå¨è®¾å¤" value="machine" /> |
| | | <el-option label="è¿è¾å·¥å
·" value="vehicle" /> |
| | | <el-option label="çµå设å¤" value="electronic" /> |
| | | <el-option label="åå
¬å®¶å
·" value="furniture" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·" prop="specification"> |
| | | <el-input v-model="form.specification" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è´ç½®æ¥æ" prop="purchaseDate"> |
| | | <el-date-picker v-model="form.purchaseDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§åå¼" prop="originalValue"> |
| | | <el-input-number v-model="form.originalValue" :min="0" :precision="2" style="width: 100%;" @change="calculateNetValue" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="使ç¨å¹´é" prop="usefulLife"> |
| | | <el-input-number v-model="form.usefulLife" :min="1" :max="50" style="width: 100%;" /> |
| | | <span style="margin-left: 10px;">å¹´</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ®å¼ç" prop="residualRate"> |
| | | <el-input-number v-model="form.residualRate" :min="0" :max="10" :precision="2" style="width: 100%;" /> |
| | | <span style="margin-left: 10px;">%</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç´¯è®¡ææ§"> |
| | | <el-input v-model="form.accumulatedDepreciation" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§åå¼"> |
| | | <el-input v-model="form.netValue" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åæ¾å°ç¹" prop="location"> |
| | | <el-input v-model="form.location" placeholder="请è¾å
¥åæ¾å°ç¹" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="使ç¨é¨é¨" prop="department"> |
| | | <el-input v-model="form.department" placeholder="请è¾å
¥ä½¿ç¨é¨é¨" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¿ç®¡äºº" prop="keeper"> |
| | | <el-input v-model="form.keeper" placeholder="请è¾å
¥ä¿ç®¡äºº" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%;"> |
| | | <el-option label="å¨ç¨" value="in_use" /> |
| | | <el-option label="é²ç½®" value="idle" /> |
| | | <el-option label="æ¥åº" value="scrapped" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button v-if="!isView" type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { |
| | | listFixedAssetPage, |
| | | addFixedAsset, |
| | | updateFixedAsset, |
| | | deleteFixedAsset, |
| | | depreciateFixedAsset, |
| | | } from "@/api/financialManagement/fixedAsset"; |
| | | |
| | | defineOptions({ |
| | | name: "åºå®èµäº§", |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | assetCode: "", |
| | | assetName: "", |
| | | category: "", |
| | | status: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "èµäº§ç¼å·", prop: "assetCode", width: "130" }, |
| | | { label: "èµäº§åç§°", prop: "assetName", width: "150" }, |
| | | { label: "èµäº§ç±»å«", prop: "category", dataType: "slot", slot: "category" }, |
| | | { label: "è§æ ¼åå·", prop: "specification", width: "120" }, |
| | | { label: "èµäº§åå¼", prop: "originalValue", dataType: "slot", slot: "originalValue" }, |
| | | { label: "ç´¯è®¡ææ§", prop: "accumulatedDepreciation", dataType: "slot", slot: "accumulatedDepreciation" }, |
| | | { label: "èµäº§åå¼", prop: "netValue", dataType: "slot", slot: "netValue" }, |
| | | { label: "ç¶æ", prop: "status", dataType: "slot", slot: "status" }, |
| | | { label: "æä½", prop: "operation", dataType: "slot", slot: "operation", width: "180", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const multipleList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const isView = ref(false); |
| | | const currentId = ref(null); |
| | | const selectedIds = computed(() => |
| | | multipleList.value |
| | | .map(item => item?.id) |
| | | .filter(id => id !== undefined && id !== null && id !== "") |
| | | ); |
| | | |
| | | const createDefaultForm = () => ({ |
| | | assetCode: "", |
| | | assetName: "", |
| | | category: "", |
| | | specification: "", |
| | | purchaseDate: "", |
| | | originalValue: 0, |
| | | usefulLife: 5, |
| | | residualRate: 5, |
| | | accumulatedDepreciation: 0, |
| | | netValue: 0, |
| | | location: "", |
| | | department: "", |
| | | keeper: "", |
| | | status: "in_use", |
| | | remark: "", |
| | | }); |
| | | |
| | | const form = reactive({ |
| | | ...createDefaultForm(), |
| | | }); |
| | | |
| | | const rules = { |
| | | assetName: [{ required: true, message: "请è¾å
¥èµäº§åç§°", trigger: "blur" }], |
| | | category: [{ required: true, message: "è¯·éæ©èµäº§ç±»å«", trigger: "change" }], |
| | | purchaseDate: [{ required: true, message: "è¯·éæ©è´ç½®æ¥æ", trigger: "change" }], |
| | | originalValue: [{ required: true, message: "请è¾å
¥èµäº§åå¼", trigger: "blur" }], |
| | | usefulLife: [{ required: true, message: "请è¾å
¥ä½¿ç¨å¹´é", trigger: "blur" }], |
| | | }; |
| | | |
| | | const totalOriginalValue = computed(() => { |
| | | return dataList.value.reduce((sum, item) => sum + Number(item.originalValue), 0); |
| | | }); |
| | | |
| | | const totalDepreciation = computed(() => { |
| | | return dataList.value.reduce((sum, item) => sum + Number(item.accumulatedDepreciation), 0); |
| | | }); |
| | | |
| | | const totalNetValue = computed(() => { |
| | | return dataList.value.reduce((sum, item) => sum + Number(item.netValue), 0); |
| | | }); |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getCategoryLabel = (category) => { |
| | | const map = { |
| | | building: "æ¿å±å»ºç", |
| | | machine: "æºå¨è®¾å¤", |
| | | vehicle: "è¿è¾å·¥å
·", |
| | | electronic: "çµå设å¤", |
| | | furniture: "åå
¬å®¶å
·", |
| | | }; |
| | | return map[category] || category; |
| | | }; |
| | | |
| | | const getStatusLabel = (status) => { |
| | | const key = String(status || "").toLowerCase(); |
| | | const map = { in_use: "å¨ç¨", idle: "é²ç½®", repair: "ç»´ä¿®ä¸", scrapped: "æ¥åº" }; |
| | | return map[key] || status; |
| | | }; |
| | | |
| | | const getStatusType = (status) => { |
| | | const key = String(status || "").toLowerCase(); |
| | | const map = { in_use: "success", idle: "warning", repair: "warning", scrapped: "info" }; |
| | | return map[key] || ""; |
| | | }; |
| | | |
| | | const calculateNetValue = () => { |
| | | const originalValue = Number(form.originalValue || 0); |
| | | const accumulatedDepreciation = Number(form.accumulatedDepreciation || 0); |
| | | form.netValue = Number((originalValue - accumulatedDepreciation).toFixed(2)); |
| | | }; |
| | | |
| | | // èè°çº¦å®ï¼å页忰åºå®ä¸º current/sizeï¼è¿å data.records/data.total |
| | | const getTableData = async () => { |
| | | try { |
| | | const { data } = await listFixedAssetPage({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | assetCode: filters.assetCode, |
| | | assetName: filters.assetName, |
| | | category: filters.category, |
| | | status: filters.status, |
| | | }); |
| | | dataList.value = data?.records || []; |
| | | multipleList.value = []; |
| | | pagination.total = Number(data?.total || 0); |
| | | } catch (error) { |
| | | // æç¤ºç±å
¨å±è¯·æ±æ¦æªå¨å¤çï¼è¿éä»
鲿¢æªæè·å¼å¸¸ |
| | | } |
| | | }; |
| | | |
| | | const handleSelectionChange = (selectionList) => { |
| | | multipleList.value = selectionList; |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.assetCode = ""; |
| | | filters.assetName = ""; |
| | | filters.category = ""; |
| | | filters.status = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const buildAssetCode = () => `GD${Date.now().toString().slice(-10)}`; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | currentId.value = null; |
| | | dialogTitle.value = "æ°å¢åºå®èµäº§"; |
| | | Object.assign(form, createDefaultForm(), { |
| | | assetCode: buildAssetCode(), |
| | | purchaseDate: new Date().toISOString().split('T')[0], |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | isView.value = false; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾åºå®èµäº§"; |
| | | Object.assign(form, createDefaultForm(), row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | edit(row); |
| | | isView.value = true; |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm("确认å é¤è¯¥åºå®èµäº§åï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(async () => { |
| | | // èè°çº¦å®ï¼å 餿¥å£ä½¿ç¨ ids=1&ids=2 |
| | | await deleteFixedAsset([row.id]); |
| | | if (dataList.value.length === 1 && pagination.currentPage > 1) { |
| | | pagination.currentPage -= 1; |
| | | } |
| | | ElMessage.success("å 餿å"); |
| | | await getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleDepreciation = () => { |
| | | const ids = selectedIds.value; |
| | | const confirmText = ids.length |
| | | ? `确认对éä¸ç ${ids.length} æ¡èµäº§è¿è¡æ¬æææ§è®¡æåï¼` |
| | | : "确认è¿è¡æ¬æææ§è®¡æåï¼"; |
| | | ElMessageBox.confirm(confirmText, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(async () => { |
| | | await depreciateFixedAsset({ ids }); |
| | | ElMessage.success("ææ§è®¡æå®æ"); |
| | | await getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | if (isView.value) { |
| | | dialogVisible.value = false; |
| | | return; |
| | | } |
| | | formRef.value.validate(async valid => { |
| | | if (valid) { |
| | | try { |
| | | calculateNetValue(); |
| | | const payload = { ...form }; |
| | | if (isEdit.value) { |
| | | payload.id = currentId.value; |
| | | await updateFixedAsset(payload); |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | await addFixedAsset(payload); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | await getTableData(); |
| | | } catch (error) { |
| | | // æç¤ºç±å
¨å±è¯·æ±æ¦æªå¨å¤çï¼è¿éä»
鲿¢æªæè·å¼å¸¸ |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | |
| | | > div:first-child { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-warning { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="èµäº§ç¼å·:"> |
| | | <el-input v-model="filters.assetCode" placeholder="请è¾å
¥èµäº§ç¼å·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="èµäº§åç§°:"> |
| | | <el-input v-model="filters.assetName" placeholder="请è¾å
¥èµäº§åç§°" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="èµäº§ç±»å«:"> |
| | | <el-select v-model="filters.category" placeholder="è¯·éæ©ç±»å«" clearable style="width: 150px;"> |
| | | <el-option label="ä¸å©æ" value="patent" /> |
| | | <el-option label="åæ æ" value="trademark" /> |
| | | <el-option label="è使" value="copyright" /> |
| | | <el-option label="软件" value="software" /> |
| | | <el-option label="åå°ä½¿ç¨æ" value="land" /> |
| | | <el-option label="å
¶ä»" value="other" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="å¨ç¨" value="in_use" /> |
| | | <el-option label="é²ç½®" value="idle" /> |
| | | <el-option label="å·²æé宿¯" value="amortized" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-statistic title="èµäº§åå¼å计" :value="totalOriginalValue" precision="2" prefix="Â¥" /> |
| | | <el-statistic title="累计æéå计" :value="totalAmortization" precision="2" prefix="Â¥" style="margin-left: 30px;" /> |
| | | <el-statistic title="åå¼å计" :value="totalNetValue" precision="2" prefix="Â¥" style="margin-left: 30px;" /> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢èµäº§</el-button> |
| | | <el-button type="warning" @click="handleAmortization" icon="Money">æé计æ</el-button> |
| | | <!-- <el-button @click="handleOut" icon="Download">导åº</el-button> --> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #originalValue="{ row }"> |
| | | <span class="text-primary">Â¥{{ formatMoney(row.originalValue) }}</span> |
| | | </template> |
| | | <template #accumulatedAmortization="{ row }"> |
| | | <span class="text-warning">Â¥{{ formatMoney(row.accumulatedAmortization) }}</span> |
| | | </template> |
| | | <template #netValue="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.netValue) }}</span> |
| | | </template> |
| | | <template #category="{ row }"> |
| | | <el-tag>{{ getCategoryLabel(row.category) }}</el-tag> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)">ç¼è¾</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" :disabled="isView" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§ç¼å·" prop="assetCode"> |
| | | <el-input v-model="form.assetCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§åç§°" prop="assetName"> |
| | | <el-input v-model="form.assetName" placeholder="请è¾å
¥èµäº§åç§°" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§ç±»å«" prop="category"> |
| | | <el-select v-model="form.category" placeholder="è¯·éæ©èµäº§ç±»å«" style="width: 100%;"> |
| | | <el-option label="ä¸å©æ" value="patent" /> |
| | | <el-option label="åæ æ" value="trademark" /> |
| | | <el-option label="è使" value="copyright" /> |
| | | <el-option label="软件" value="software" /> |
| | | <el-option label="åå°ä½¿ç¨æ" value="land" /> |
| | | <el-option label="å
¶ä»" value="other" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¯ä¹¦ç¼å·" prop="certificateNo"> |
| | | <el-input v-model="form.certificateNo" placeholder="请è¾å
¥è¯ä¹¦ç¼å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å徿¥æ" prop="acquisitionDate"> |
| | | <el-date-picker v-model="form.acquisitionDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§åå¼" prop="originalValue"> |
| | | <el-input-number v-model="form.originalValue" :min="0" :precision="2" style="width: 100%;" @change="calculateNetValue" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æéå¹´é" prop="amortizationPeriod"> |
| | | <el-input-number v-model="form.amortizationPeriod" :min="1" :max="50" style="width: 100%;" /> |
| | | <span style="margin-left: 10px;">å¹´</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ®å¼ç" prop="residualRate"> |
| | | <el-input-number v-model="form.residualRate" :min="0" :max="10" :precision="2" style="width: 100%;" /> |
| | | <span style="margin-left: 10px;">%</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="累计æé"> |
| | | <el-input v-model="form.accumulatedAmortization" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§åå¼"> |
| | | <el-input v-model="form.netValue" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææè³" prop="validityDate"> |
| | | <el-date-picker v-model="form.validityDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%;"> |
| | | <el-option label="å¨ç¨" value="in_use" /> |
| | | <el-option label="é²ç½®" value="idle" /> |
| | | <el-option label="å·²æé宿¯" value="amortized" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="èµäº§æè¿°" prop="description"> |
| | | <el-input v-model="form.description" type="textarea" :rows="3" placeholder="请è¾å
¥èµäº§æè¿°" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button v-if="!isView" type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { |
| | | listIntangibleAssetPage, |
| | | addIntangibleAsset, |
| | | updateIntangibleAsset, |
| | | deleteIntangibleAsset, |
| | | amortizeIntangibleAsset, |
| | | } from "@/api/financialManagement/intangibleAsset"; |
| | | |
| | | defineOptions({ |
| | | name: "æ å½¢èµäº§", |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | assetCode: "", |
| | | assetName: "", |
| | | category: "", |
| | | status: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "èµäº§ç¼å·", prop: "assetCode", width: "130" }, |
| | | { label: "èµäº§åç§°", prop: "assetName", width: "150" }, |
| | | { label: "èµäº§ç±»å«", prop: "category", dataType: "slot", slot: "category" }, |
| | | { label: "è¯ä¹¦ç¼å·", prop: "certificateNo", width: "150" }, |
| | | { label: "èµäº§åå¼", prop: "originalValue", dataType: "slot", slot: "originalValue" }, |
| | | { label: "累计æé", prop: "accumulatedAmortization", dataType: "slot", slot: "accumulatedAmortization" }, |
| | | { label: "èµäº§åå¼", prop: "netValue", dataType: "slot", slot: "netValue" }, |
| | | { label: "ç¶æ", prop: "status", dataType: "slot", slot: "status" }, |
| | | { label: "æä½", prop: "operation", dataType: "slot", slot: "operation", width: "180", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const multipleList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const isView = ref(false); |
| | | const currentId = ref(null); |
| | | const selectedIds = computed(() => |
| | | multipleList.value |
| | | .map(item => item?.id) |
| | | .filter(id => id !== undefined && id !== null && id !== "") |
| | | ); |
| | | |
| | | const createDefaultForm = () => ({ |
| | | assetCode: "", |
| | | assetName: "", |
| | | category: "", |
| | | certificateNo: "", |
| | | acquisitionDate: "", |
| | | originalValue: 0, |
| | | amortizationPeriod: 10, |
| | | residualRate: 0, |
| | | accumulatedAmortization: 0, |
| | | netValue: 0, |
| | | validityDate: "", |
| | | status: "in_use", |
| | | description: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | const form = reactive({ |
| | | ...createDefaultForm(), |
| | | }); |
| | | |
| | | const rules = { |
| | | assetName: [{ required: true, message: "请è¾å
¥èµäº§åç§°", trigger: "blur" }], |
| | | category: [{ required: true, message: "è¯·éæ©èµäº§ç±»å«", trigger: "change" }], |
| | | acquisitionDate: [{ required: true, message: "è¯·éæ©å徿¥æ", trigger: "change" }], |
| | | originalValue: [{ required: true, message: "请è¾å
¥èµäº§åå¼", trigger: "blur" }], |
| | | amortizationPeriod: [{ required: true, message: "请è¾å
¥æéå¹´é", trigger: "blur" }], |
| | | }; |
| | | |
| | | const totalOriginalValue = computed(() => { |
| | | return dataList.value.reduce((sum, item) => sum + Number(item.originalValue), 0); |
| | | }); |
| | | |
| | | const totalAmortization = computed(() => { |
| | | return dataList.value.reduce((sum, item) => sum + Number(item.accumulatedAmortization), 0); |
| | | }); |
| | | |
| | | const totalNetValue = computed(() => { |
| | | return dataList.value.reduce((sum, item) => sum + Number(item.netValue), 0); |
| | | }); |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getCategoryLabel = (category) => { |
| | | const map = { |
| | | patent: "ä¸å©æ", |
| | | trademark: "åæ æ", |
| | | copyright: "è使", |
| | | software: "软件", |
| | | land: "åå°ä½¿ç¨æ", |
| | | other: "å
¶ä»", |
| | | }; |
| | | return map[category] || category; |
| | | }; |
| | | |
| | | const getStatusLabel = (status) => { |
| | | const key = String(status || "").toLowerCase(); |
| | | const map = { |
| | | in_use: "å¨ç¨", |
| | | idle: "é²ç½®", |
| | | expired: "已尿", |
| | | amortized: "å·²æé宿¯", |
| | | }; |
| | | return map[key] || status; |
| | | }; |
| | | |
| | | const getStatusType = (status) => { |
| | | const key = String(status || "").toLowerCase(); |
| | | const map = { in_use: "success", idle: "warning", expired: "warning", amortized: "info" }; |
| | | return map[key] || ""; |
| | | }; |
| | | |
| | | const calculateNetValue = () => { |
| | | const originalValue = Number(form.originalValue || 0); |
| | | const accumulatedAmortization = Number(form.accumulatedAmortization || 0); |
| | | form.netValue = Number((originalValue - accumulatedAmortization).toFixed(2)); |
| | | }; |
| | | |
| | | // èè°çº¦å®ï¼å页忰åºå®ä¸º current/sizeï¼è¿å data.records/data.total |
| | | const getTableData = async () => { |
| | | try { |
| | | const { data } = await listIntangibleAssetPage({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | assetCode: filters.assetCode, |
| | | assetName: filters.assetName, |
| | | category: filters.category, |
| | | status: filters.status, |
| | | }); |
| | | dataList.value = data?.records || []; |
| | | multipleList.value = []; |
| | | pagination.total = Number(data?.total || 0); |
| | | } catch (error) { |
| | | // æç¤ºç±å
¨å±è¯·æ±æ¦æªå¨å¤çï¼è¿éä»
鲿¢æªæè·å¼å¸¸ |
| | | } |
| | | }; |
| | | |
| | | const handleSelectionChange = (selectionList) => { |
| | | multipleList.value = selectionList; |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.assetCode = ""; |
| | | filters.assetName = ""; |
| | | filters.category = ""; |
| | | filters.status = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const buildAssetCode = () => `WX${Date.now().toString().slice(-10)}`; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | currentId.value = null; |
| | | dialogTitle.value = "æ°å¢æ å½¢èµäº§"; |
| | | Object.assign(form, createDefaultForm(), { |
| | | assetCode: buildAssetCode(), |
| | | acquisitionDate: new Date().toISOString().split('T')[0], |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | isView.value = false; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾æ å½¢èµäº§"; |
| | | Object.assign(form, createDefaultForm(), row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | edit(row); |
| | | isView.value = true; |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm("确认å é¤è¯¥æ å½¢èµäº§åï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(async () => { |
| | | // èè°çº¦å®ï¼å 餿¥å£ä½¿ç¨ ids=1&ids=2 |
| | | await deleteIntangibleAsset([row.id]); |
| | | if (dataList.value.length === 1 && pagination.currentPage > 1) { |
| | | pagination.currentPage -= 1; |
| | | } |
| | | ElMessage.success("å 餿å"); |
| | | await getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleAmortization = () => { |
| | | const ids = selectedIds.value; |
| | | const confirmText = ids.length |
| | | ? `确认对éä¸ç ${ids.length} æ¡èµäº§è¿è¡æ¬ææé计æåï¼` |
| | | : "确认è¿è¡æ¬ææé计æåï¼"; |
| | | ElMessageBox.confirm(confirmText, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(async () => { |
| | | await amortizeIntangibleAsset({ ids }); |
| | | ElMessage.success("æé计æå®æ"); |
| | | await getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | if (isView.value) { |
| | | dialogVisible.value = false; |
| | | return; |
| | | } |
| | | formRef.value.validate(async valid => { |
| | | if (valid) { |
| | | try { |
| | | calculateNetValue(); |
| | | const payload = { ...form }; |
| | | if (isEdit.value) { |
| | | payload.id = currentId.value; |
| | | await updateIntangibleAsset(payload); |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | await addIntangibleAsset(payload); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | await getTableData(); |
| | | } catch (error) { |
| | | // æç¤ºç±å
¨å±è¯·æ±æ¦æªå¨å¤çï¼è¿éä»
鲿¢æªæè·å¼å¸¸ |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | |
| | | > div:first-child { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-warning { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| | |
| | | </PIMTable> |
| | | </div> |
| | | <Modal ref="modalRef" @success="getTableData"></Modal> |
| | | <FileListDialog |
| | | ref="fileListRef" |
| | | v-model="fileListDialogVisible" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :upload-method="handleUpload" |
| | | :delete-method="handleFileDelete" |
| | | /> |
| | | <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="account_expense" :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { usePaginationApi } from "@/hooks/usePaginationApi"; |
| | | import { listPage, delAccountExpense, fileListPage, fileAdd, fileDel } from "@/api/financialManagement/expenseManagement"; |
| | | import { onMounted, getCurrentInstance, ref, computed } from "vue"; |
| | | import { listPage, delAccountExpense } from "@/api/financialManagement/expenseManagement"; |
| | | import {onMounted, getCurrentInstance, ref, computed, defineAsyncComponent} from "vue"; |
| | | import Modal from "./Modal.vue"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import FileListDialog from "@/components/Dialog/FileListDialog.vue"; |
| | | import request from "@/utils/request"; |
| | | import { getToken } from "@/utils/auth"; |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | |
| | | defineOptions({ |
| | | name: "æ¯åºç®¡ç", |
| | |
| | | const modalRef = ref(); |
| | | const { checkout_payment } = proxy.useDict("checkout_payment"); |
| | | const { expense_types } = proxy.useDict("expense_types"); |
| | | const fileListRef = ref(null); |
| | | const fileListDialogVisible = ref(false); |
| | | const currentFileRow = ref(null); |
| | | const accountType = ref('æ¯åº'); |
| | | |
| | | const { |
| | |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const recordId =ref(0) |
| | | const fileDialogVisible = ref(false) |
| | | |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = async (row) => { |
| | | currentFileRow.value = row; |
| | | accountType.value = 'æ¯åº'; |
| | | try { |
| | | const res = await fileListPage({ |
| | | accountId: row.id, |
| | | accountType: accountType.value, |
| | | current: 1, |
| | | size: 100 |
| | | }); |
| | | if (res.code === 200 && fileListRef.value) { |
| | | // å°æ°æ®è½¬æ¢ä¸º FileListDialog éè¦çæ ¼å¼ |
| | | const fileList = (res.data?.records || []).map(item => ({ |
| | | name: item.name, |
| | | url: item.url, |
| | | id: item.id, |
| | | ...item |
| | | })); |
| | | fileListRef.value.open(fileList); |
| | | fileListDialogVisible.value = true; |
| | | recordId.value = row.id |
| | | fileDialogVisible.value = true |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("è·åéä»¶å表失败"); |
| | | } |
| | | }; |
| | | |
| | | // ä¸ä¼ éä»¶ |
| | | const handleUpload = async () => { |
| | | if (!currentFileRow.value) { |
| | | proxy.$modal.msgWarning("请å
éæ©æ°æ®"); |
| | | return null; |
| | | } |
| | | |
| | | return new Promise((resolve) => { |
| | | // å建ä¸ä¸ªéèçæä»¶è¾å
¥å
ç´ |
| | | const input = document.createElement('input'); |
| | | input.type = 'file'; |
| | | input.style.display = 'none'; |
| | | input.onchange = async (e) => { |
| | | const file = e.target.files[0]; |
| | | if (!file) { |
| | | resolve(null); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // ä½¿ç¨ FormData ä¸ä¼ æä»¶ |
| | | const formData = new FormData(); |
| | | formData.append('file', file); |
| | | |
| | | const uploadRes = await request({ |
| | | url: '/file/upload', |
| | | method: 'post', |
| | | data: formData, |
| | | headers: { |
| | | 'Content-Type': 'multipart/form-data', |
| | | Authorization: `Bearer ${getToken()}` |
| | | } |
| | | }); |
| | | |
| | | if (uploadRes.code === 200) { |
| | | // ä¿åéä»¶ä¿¡æ¯ |
| | | const fileData = { |
| | | accountId: currentFileRow.value.id, |
| | | accountType: accountType.value, |
| | | name: uploadRes.data.originalName || file.name, |
| | | url: uploadRes.data.tempPath || uploadRes.data.url |
| | | }; |
| | | |
| | | const saveRes = await fileAdd(fileData); |
| | | if (saveRes.code === 200) { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | // éæ°å è½½æä»¶å表 |
| | | const listRes = await fileListPage({ |
| | | accountId: currentFileRow.value.id, |
| | | accountType: accountType.value, |
| | | current: 1, |
| | | size: 100 |
| | | }); |
| | | if (listRes.code === 200 && fileListRef.value) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | | name: item.name, |
| | | url: item.url, |
| | | id: item.id, |
| | | ...item |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | } |
| | | // è¿åæ°æä»¶ä¿¡æ¯ |
| | | resolve({ |
| | | name: fileData.name, |
| | | url: fileData.url, |
| | | id: saveRes.data?.id |
| | | }); |
| | | } else { |
| | | proxy.$modal.msgError(saveRes.msg || "æä»¶ä¿å失败"); |
| | | resolve(null); |
| | | } |
| | | } else { |
| | | proxy.$modal.msgError(uploadRes.msg || "æä»¶ä¸ä¼ 失败"); |
| | | resolve(null); |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | resolve(null); |
| | | } finally { |
| | | document.body.removeChild(input); |
| | | } |
| | | }; |
| | | |
| | | document.body.appendChild(input); |
| | | input.click(); |
| | | }); |
| | | }; |
| | | |
| | | // å é¤éä»¶ |
| | | const handleFileDelete = async (row) => { |
| | | try { |
| | | const res = await fileDel([row.id]); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | // éæ°å è½½æä»¶å表 |
| | | if (currentFileRow.value && fileListRef.value) { |
| | | const listRes = await fileListPage({ |
| | | accountId: currentFileRow.value.id, |
| | | accountType: accountType.value, |
| | | current: 1, |
| | | size: 100 |
| | | }); |
| | | if (listRes.code === 200) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | | name: item.name, |
| | | url: item.url, |
| | | id: item.id, |
| | | ...item |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | } |
| | | } |
| | | return true; // è¿å true 表示å 餿åï¼ç»ä»¶ä¼æ´æ°å表 |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "å é¤å¤±è´¥"); |
| | | return false; |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("å é¤å¤±è´¥"); |
| | | return false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" |
| | | :inline="true"> |
| | | <el-form-item label="ç§ç®ç¼ç :"> |
| | | <el-input v-model="filters.subjectCode" |
| | | placeholder="请è¾å
¥ç§ç®ç¼ç " |
| | | clearable |
| | | style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç§ç®åç§°:"> |
| | | <el-input v-model="filters.subjectName" |
| | | placeholder="请è¾å
¥ç§ç®åç§°" |
| | | clearable |
| | | style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç§ç®ç±»å:"> |
| | | <el-select v-model="filters.subjectType" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | style="width: 200px;"> |
| | | <el-option label="èµäº§ç±»" |
| | | value="èµäº§ç±»" /> |
| | | <el-option label="è´åºç±»" |
| | | value="è´åºç±»" /> |
| | | <el-option label="æçç±»" |
| | | value="æçç±»" /> |
| | | <el-option label="ææ¬ç±»" |
| | | value="ææ¬ç±»" /> |
| | | <el-option label="æçç±»" |
| | | value="æçç±»" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" |
| | | @click="add" |
| | | icon="Plus">æ°å¢</el-button> |
| | | <!-- <el-button @click="handleOut" |
| | | icon="Download">导åº</el-button> --> |
| | | </div> |
| | | </div> |
| | | <el-table ref="tableRef" |
| | | v-loading="loading" |
| | | :data="dataList" |
| | | row-key="id" |
| | | :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" |
| | | height="calc(100vh - 280px)" |
| | | border |
| | | stripe |
| | | highlight-current-row |
| | | class="subject-table"> |
| | | <el-table-column label="ç§ç®ç¼ç " prop="subjectCode" width="140"> |
| | | <template #default="scope"> |
| | | <span class="subject-code">{{ scope.row.subjectCode }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç§ç®åç§°" prop="subjectName" min-width="180"> |
| | | <template #default="scope"> |
| | | <span class="subject-name" :class="{ 'is-parent': scope.row.children?.length > 0 }"> |
| | | {{ scope.row.subjectName }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç§ç®ç±»å" prop="subjectType" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag size="small" :type="getSubjectTypeType(scope.row.subjectType)"> |
| | | {{ scope.row.subjectType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä½é¢æ¹å" prop="balanceDirection" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag size="small" :type="scope.row.balanceDirection === 'åæ¹' ? 'primary' : 'danger'"> |
| | | {{ scope.row.balanceDirection }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç¶æ" prop="status" width="80" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag size="small" :type="scope.row.status === 0 || scope.row.status === '0' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 0 || scope.row.status === '0' ? 'å¯ç¨' : 'ç¦ç¨' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip min-width="150" /> |
| | | <el-table-column label="æä½" align="center" fixed="right" width="240"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" icon="Plus" @click="addChild(scope.row)">æ°å¢</el-button> |
| | | <el-button link type="primary" icon="Edit" @click="edit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <FormDialog :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="600px" |
| | | @confirm="submitForm" |
| | | @cancel="dialogVisible = false"> |
| | | <el-form :model="form" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | label-width="100px"> |
| | | <el-form-item label="ç¶çº§ç§ç®"> |
| | | <el-input :model-value="parentSubjectLabel" |
| | | disabled /> |
| | | </el-form-item> |
| | | <el-form-item label="ç§ç®ç¼ç " |
| | | prop="subjectCode"> |
| | | <el-input v-model="form.subjectCode" |
| | | placeholder="请è¾å
¥ç§ç®ç¼ç " /> |
| | | </el-form-item> |
| | | <el-form-item label="ç§ç®åç§°" |
| | | prop="subjectName"> |
| | | <el-input v-model="form.subjectName" |
| | | placeholder="请è¾å
¥ç§ç®åç§°" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç§ç®ç±»å" |
| | | prop="subjectType"> |
| | | <el-select v-model="form.subjectType" |
| | | placeholder="è¯·éæ©ç§ç®ç±»å" |
| | | style="width: 100%;"> |
| | | <el-option label="èµäº§ç±»" |
| | | value="èµäº§ç±»" /> |
| | | <el-option label="è´åºç±»" |
| | | value="è´åºç±»" /> |
| | | <el-option label="æçç±»" |
| | | value="æçç±»" /> |
| | | <el-option label="ææ¬ç±»" |
| | | value="ææ¬ç±»" /> |
| | | <el-option label="æçç±»" |
| | | value="æçç±»" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä½é¢æ¹å" |
| | | prop="balanceDirection"> |
| | | <el-radio-group v-model="form.balanceDirection"> |
| | | <el-radio label="åæ¹">åæ¹</el-radio> |
| | | <el-radio label="è´·æ¹">è´·æ¹</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" |
| | | prop="status"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio :label="0">å¯ç¨</el-radio> |
| | | <el-radio :label="1">ç¦ç¨</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" |
| | | prop="remark"> |
| | | <el-input v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" |
| | | @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance, nextTick } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { |
| | | listAccountSubject, |
| | | addAccountSubject, |
| | | updateAccountSubject, |
| | | delAccountSubject, |
| | | exportAccountSubject, |
| | | } from "@/api/financialManagement/accountSubject"; |
| | | |
| | | defineOptions({ |
| | | name: "æ»å¸ç§ç®", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const filters = reactive({ |
| | | subjectCode: "", |
| | | subjectName: "", |
| | | subjectType: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "ç§ç®ç¼ç ", prop: "subjectCode", width: "120" }, |
| | | { label: "ç§ç®åç§°", prop: "subjectName", width: "150" }, |
| | | { label: "ç§ç®ç±»å", prop: "subjectType" }, |
| | | { |
| | | label: "ä½é¢æ¹å", |
| | | prop: "balanceDirection", |
| | | dataType: "tag", |
| | | formatData: value => { |
| | | if (value === "åæ¹") { |
| | | return "åæ¹"; |
| | | } |
| | | return "è´·æ¹"; |
| | | }, |
| | | formatType: value => { |
| | | if (value === "åæ¹") { |
| | | return "primary"; |
| | | } |
| | | return "danger"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | formatData: value => { |
| | | if (value === 0 || value === "0") { |
| | | return "å¯ç¨"; |
| | | } |
| | | return "ç¦ç¨"; |
| | | }, |
| | | formatType: value => { |
| | | if (value === 0 || value === "0") { |
| | | return "success"; |
| | | } |
| | | return "info"; |
| | | }, |
| | | }, |
| | | |
| | | { label: "夿³¨", prop: "remark", showOverflowTooltip: true }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: "220", |
| | | operation: [ |
| | | { |
| | | name: "æ°å¢", |
| | | type: "primary", |
| | | clickFun: row => { |
| | | addChild(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "primary", |
| | | clickFun: row => { |
| | | edit(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å é¤", |
| | | type: "danger", |
| | | clickFun: row => { |
| | | handleDelete(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const parentSubjectLabel = ref("顶级ç§ç®"); |
| | | const formRef = ref(null); |
| | | const tableRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const loading = ref(false); |
| | | |
| | | const form = reactive({ |
| | | id: undefined, |
| | | parentId: null, |
| | | subjectCode: "", |
| | | subjectName: "", |
| | | subjectType: "", |
| | | balanceDirection: "åæ¹", |
| | | status: 0, |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | subjectCode: [{ required: true, message: "请è¾å
¥ç§ç®ç¼ç ", trigger: "blur" }], |
| | | subjectName: [{ required: true, message: "请è¾å
¥ç§ç®åç§°", trigger: "blur" }], |
| | | subjectType: [ |
| | | { required: true, message: "è¯·éæ©ç§ç®ç±»å", trigger: "change" }, |
| | | ], |
| | | }; |
| | | |
| | | const getSubjectTypeType = type => { |
| | | const map = { |
| | | èµäº§ç±»: "success", |
| | | è´åºç±»: "danger", |
| | | æçç±»: "warning", |
| | | ææ¬ç±»: "info", |
| | | æçç±»: "primary", |
| | | }; |
| | | return map[type] || ""; |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | loading.value = true; |
| | | const query = { |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | ...filters, |
| | | }; |
| | | listAccountSubject(query).then(response => { |
| | | dataList.value = response.data.records || []; |
| | | loading.value = false; |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.subjectCode = ""; |
| | | filters.subjectName = ""; |
| | | filters.subjectType = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = obj => { |
| | | pagination.currentPage = obj.page; |
| | | pagination.pageSize = obj.limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const buildParentSubjectLabel = parentRow => { |
| | | if (!parentRow) { |
| | | return "顶级ç§ç®"; |
| | | } |
| | | const code = parentRow.subjectCode || ""; |
| | | const name = parentRow.subjectName || ""; |
| | | return `${code} ${name}`.trim(); |
| | | }; |
| | | |
| | | const resetForm = ({ parentId = null, parentRow = null } = {}) => { |
| | | Object.assign(form, { |
| | | id: undefined, |
| | | parentId, |
| | | subjectCode: "", |
| | | subjectName: "", |
| | | subjectType: "", |
| | | balanceDirection: "åæ¹", |
| | | status: 0, |
| | | remark: "", |
| | | }); |
| | | parentSubjectLabel.value = buildParentSubjectLabel(parentRow); |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ°å¢ç§ç®"; |
| | | resetForm({ parentId: null, parentRow: null }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const addChild = row => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ°å¢åç§ç®"; |
| | | resetForm({ parentId: row.id, parentRow: row }); |
| | | form.subjectType = row.subjectType || ""; |
| | | form.balanceDirection = row.balanceDirection || "åæ¹"; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const findSubjectById = (nodes, id) => { |
| | | for (const item of nodes || []) { |
| | | if (item.id === id) { |
| | | return item; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | const found = findSubjectById(item.children, id); |
| | | if (found) { |
| | | return found; |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const edit = row => { |
| | | isEdit.value = true; |
| | | dialogTitle.value = "ç¼è¾ç§ç®"; |
| | | Object.assign(form, row); |
| | | form.parentId = row.parentId ?? null; |
| | | const parentRow = |
| | | row.parentId === null || row.parentId === undefined |
| | | ? null |
| | | : findSubjectById(dataList.value, row.parentId); |
| | | parentSubjectLabel.value = parentRow |
| | | ? buildParentSubjectLabel(parentRow) |
| | | : row.parentId |
| | | ? `ä¸çº§ID: ${row.parentId}` |
| | | : buildParentSubjectLabel(null); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | updateAccountSubject(form).then(() => { |
| | | ElMessage.success("ç¼è¾æå"); |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | }); |
| | | } else { |
| | | addAccountSubject(form).then(() => { |
| | | ElMessage.success("æ°å¢æå"); |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleDelete = row => { |
| | | const ids = row.id; |
| | | ElMessageBox.confirm("确认å é¤è¯¥ç§ç®åï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | return delAccountSubject(ids); |
| | | }) |
| | | .then(() => { |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "accountSubject/export", |
| | | { |
| | | ...filters, |
| | | }, |
| | | `account_subject_${new Date().getTime()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .subject-table { |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | |
| | | :deep(.el-table__row) { |
| | | transition: background-color 0.3s; |
| | | } |
| | | |
| | | :deep(.el-table__row:hover) { |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .subject-code { |
| | | color: #606266; |
| | | } |
| | | |
| | | .subject-name { |
| | | font-weight: 500; |
| | | |
| | | &.is-parent { |
| | | color: #409eff; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="å票代ç :"> |
| | | <el-input v-model="filters.invoiceCode" placeholder="请è¾å
¥å票代ç " clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="å票å·ç :"> |
| | | <el-input v-model="filters.invoiceNo" placeholder="请è¾å
¥å票å·ç " clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable style="width: 200px;"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="认è¯ç¶æ:"> |
| | | <el-select v-model="filters.certifyStatus" placeholder="è¯·éæ©è®¤è¯ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="æªè®¤è¯" value="uncertified" /> |
| | | <el-option label="已认è¯" value="certified" /> |
| | | <el-option label="认è¯å¤±è´¥" value="failed" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-button type="success" @click="handleBatchCertify" icon="Check" :disabled="selectedRows.length === 0">æ¹é认è¯</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">å½å
¥å票</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #amount="{ row }"> |
| | | <span class="text-primary">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | <template #taxAmount="{ row }"> |
| | | <span class="text-danger">Â¥{{ formatMoney(row.taxAmount) }}</span> |
| | | </template> |
| | | <template #totalAmount="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.totalAmount) }}</span> |
| | | </template> |
| | | <template #certifyStatus="{ row }"> |
| | | <el-tag :type="getCertifyStatusType(row.certifyStatus)">{{ getCertifyStatusLabel(row.certifyStatus) }}</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleCertify(row)" v-if="row.certifyStatus === 'uncertified'">认è¯</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票代ç " prop="invoiceCode"> |
| | | <el-input v-model="form.invoiceCode" placeholder="请è¾å
¥å票代ç " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票å·ç " prop="invoiceNo"> |
| | | <el-input v-model="form.invoiceNo" placeholder="请è¾å
¥å票å·ç " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºå" prop="supplierId"> |
| | | <el-select v-model="form.supplierId" placeholder="è¯·éæ©ä¾åºå" style="width: 100%;"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼ç¥¨æ¥æ" prop="invoiceDate"> |
| | | <el-date-picker v-model="form.invoiceDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="éé¢(ä¸å«ç¨)" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" @change="calculateTax" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ç¨ç" prop="taxRate"> |
| | | <el-select v-model="form.taxRate" placeholder="è¯·éæ©ç¨ç" style="width: 100%;" @change="calculateTax"> |
| | | <el-option |
| | | v-for="dict in tax_rate" |
| | | :key="dict.value" |
| | | :label="dict.label" |
| | | :value="Number(dict.value)" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ç¨é¢"> |
| | | <el-input v-model="form.taxAmount" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="认è¯ç¶æ" prop="certifyStatus"> |
| | | <el-select v-model="form.certifyStatus" placeholder="è¯·éæ©è®¤è¯ç¶æ" style="width: 100%;" disabled> |
| | | <el-option label="æªè®¤è¯" value="uncertified" /> |
| | | <el-option label="已认è¯" value="certified" /> |
| | | <el-option label="认è¯å¤±è´¥" value="failed" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è®¤è¯æ¥æ" prop="certifyDate"> |
| | | <el-date-picker v-model="form.certifyDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å票å
容" prop="content"> |
| | | <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请è¾å
¥å票å
容" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "è¿é¡¹å票", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const { tax_rate } = proxy.useDict("tax_rate"); |
| | | |
| | | const filters = reactive({ |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | supplierId: "", |
| | | certifyStatus: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "å票代ç ", prop: "invoiceCode", width: "130" }, |
| | | { label: "å票å·ç ", prop: "invoiceNo", width: "120" }, |
| | | { label: "ä¾åºå", prop: "supplierName", width: "180" }, |
| | | { label: "å¼ç¥¨æ¥æ", prop: "invoiceDate", width: "120" }, |
| | | { label: "éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "ç¨é¢", prop: "taxAmount", slot: "taxAmount" }, |
| | | { label: "ä»·ç¨å计", prop: "totalAmount", slot: "totalAmount" }, |
| | | { label: "认è¯ç¶æ", prop: "certifyStatus", slot: "certifyStatus" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "180", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const selectedRows = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | |
| | | const supplierList = [ |
| | | { id: 1, name: "åäº¬åææä¾åºå" }, |
| | | { id: 2, name: "䏿µ·çµåå
å¨ä»¶å
¬å¸" }, |
| | | { id: 3, name: "广å·å
è£
ææå" }, |
| | | { id: 4, name: "æ·±å³äºéé
ä»¶å
¬å¸" }, |
| | | ]; |
| | | |
| | | const form = reactive({ |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | supplierId: "", |
| | | invoiceDate: "", |
| | | amount: 0, |
| | | taxRate: 13, |
| | | taxAmount: 0, |
| | | totalAmount: 0, |
| | | certifyStatus: "uncertified", |
| | | certifyDate: "", |
| | | content: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | invoiceCode: [{ required: true, message: "请è¾å
¥å票代ç ", trigger: "blur" }], |
| | | invoiceNo: [{ required: true, message: "请è¾å
¥å票å·ç ", trigger: "blur" }], |
| | | supplierId: [{ required: true, message: "è¯·éæ©ä¾åºå", trigger: "change" }], |
| | | invoiceDate: [{ required: true, message: "è¯·éæ©å¼ç¥¨æ¥æ", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥éé¢", trigger: "blur" }], |
| | | taxRate: [{ required: true, message: "è¯·éæ©ç¨ç", trigger: "change" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, invoiceCode: "0440021001", invoiceNo: "12345678", supplierId: 1, supplierName: "åäº¬åææä¾åºå", invoiceDate: "2024-01-08", amount: 8000, taxRate: 13, taxAmount: 1040, totalAmount: 9040, certifyStatus: "certified", certifyDate: "2024-01-15", content: "åææéè´", remark: "" }, |
| | | { id: 2, invoiceCode: "0440021002", invoiceNo: "87654321", supplierId: 2, supplierName: "䏿µ·çµåå
å¨ä»¶å
¬å¸", invoiceDate: "2024-01-10", amount: 12000, taxRate: 13, taxAmount: 1560, totalAmount: 13560, certifyStatus: "uncertified", certifyDate: "", content: "çµåå
å¨ä»¶", remark: "" }, |
| | | { id: 3, invoiceCode: "0440021003", invoiceNo: "11112222", supplierId: 3, supplierName: "广å·å
è£
ææå", invoiceDate: "2024-01-12", amount: 3500, taxRate: 13, taxAmount: 455, totalAmount: 3955, certifyStatus: "certified", certifyDate: "2024-01-18", content: "å
è£
ææ", remark: "" }, |
| | | ]; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const calculateTax = () => { |
| | | form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2)); |
| | | form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2)); |
| | | }; |
| | | |
| | | const getCertifyStatusLabel = (status) => { |
| | | const map = { uncertified: "æªè®¤è¯", certified: "已认è¯", failed: "认è¯å¤±è´¥" }; |
| | | return map[status] || status; |
| | | }; |
| | | |
| | | const getCertifyStatusType = (status) => { |
| | | const map = { uncertified: "info", certified: "success", failed: "danger" }; |
| | | return map[status] || ""; |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.invoiceCode) { |
| | | result = result.filter(item => item.invoiceCode.includes(filters.invoiceCode)); |
| | | } |
| | | if (filters.invoiceNo) { |
| | | result = result.filter(item => item.invoiceNo.includes(filters.invoiceNo)); |
| | | } |
| | | if (filters.supplierId) { |
| | | result = result.filter(item => item.supplierId === filters.supplierId); |
| | | } |
| | | if (filters.certifyStatus) { |
| | | result = result.filter(item => item.certifyStatus === filters.certifyStatus); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.invoiceCode = ""; |
| | | filters.invoiceNo = ""; |
| | | filters.supplierId = ""; |
| | | filters.certifyStatus = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "å½å
¥å票"; |
| | | Object.assign(form, { |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | supplierId: "", |
| | | invoiceDate: new Date().toISOString().split('T')[0], |
| | | amount: 0, |
| | | taxRate: 13, |
| | | taxAmount: 0, |
| | | totalAmount: 0, |
| | | certifyStatus: "uncertified", |
| | | certifyDate: "", |
| | | content: "", |
| | | remark: "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾å票"; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥çå票: ${row.invoiceCode}-${row.invoiceNo}`); |
| | | }; |
| | | |
| | | const handleCertify = (row) => { |
| | | ElMessageBox.confirm("确认认è¯è¯¥å票åï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].certifyStatus = "certified"; |
| | | mockData[index].certifyDate = new Date().toISOString().split('T')[0]; |
| | | } |
| | | ElMessage.success("è®¤è¯æå"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleBatchCertify = () => { |
| | | ElMessageBox.confirm(`确认æ¹é认è¯éä¸ç ${selectedRows.value.length} å¼ å票åï¼`, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(() => { |
| | | selectedRows.value.forEach(row => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1 && mockData[index].certifyStatus === "uncertified") { |
| | | mockData[index].certifyStatus = "certified"; |
| | | mockData[index].certifyDate = new Date().toISOString().split('T')[0]; |
| | | } |
| | | }); |
| | | ElMessage.success("æ¹éè®¤è¯æå"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const supplier = supplierList.find(item => item.id === form.supplierId); |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, supplierName: supplier?.name }); |
| | | ElMessage.success("å½å
¥æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="仿¬¾åå·:"> |
| | | <el-input v-model="filters.paymentCode" placeholder="请è¾å
¥ä»æ¬¾åå·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable style="width: 200px;"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾æ¹å¼:"> |
| | | <el-select v-model="filters.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" clearable style="width: 150px;"> |
| | | <el-option label="é¶è¡è½¬è´¦" value="bank_transfer" /> |
| | | <el-option label="ç°é" value="cash" /> |
| | | <el-option label="æ¯ç¥¨" value="check" /> |
| | | <el-option label="æ±ç¥¨" value="draft" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="å¾
仿¬¾" value="pending" /> |
| | | <el-option label="已宿" value="completed" /> |
| | | <el-option label="已忶" value="cancelled" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-statistic title="æ¬æä»æ¬¾å计" :value="totalPaymentAmount" precision="2" prefix="Â¥" /> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢ä»æ¬¾</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | <template #amount="{ row }"> |
| | | <span class="text-danger">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | <template #paymentMethod="{ row }"> |
| | | <el-tag>{{ getPaymentMethodLabel(row.paymentMethod) }}</el-tag> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="row.status === 'completed' ? 'success' : row.status === 'pending' ? 'warning' : 'info'"> |
| | | {{ row.status === 'completed' ? '已宿' : row.status === 'pending' ? 'å¾
仿¬¾' : '已忶' }} |
| | | </el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleComplete(row)" v-if="row.status === 'pending'">宿</el-button> |
| | | <el-button type="danger" link @click="handleCancel(row)" v-if="row.status === 'pending'">åæ¶</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾åå·" prop="paymentCode"> |
| | | <el-input v-model="form.paymentCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
³èç³è¯·å" prop="applyCode"> |
| | | <el-select v-model="form.applyCode" placeholder="è¯·éæ©å
³èç³è¯·å" style="width: 100%;" :disabled="isEdit"> |
| | | <el-option v-for="item in applyList" :key="item.applyCode" :label="item.applyCode" :value="item.applyCode" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºå" prop="supplierId"> |
| | | <el-select v-model="form.supplierId" placeholder="è¯·éæ©ä¾åºå" style="width: 100%;" :disabled="isEdit"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¥æ" prop="paymentDate"> |
| | | <el-date-picker v-model="form.paymentDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾éé¢" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="form.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%;"> |
| | | <el-option label="é¶è¡è½¬è´¦" value="bank_transfer" /> |
| | | <el-option label="ç°é" value="cash" /> |
| | | <el-option label="æ¯ç¥¨" value="check" /> |
| | | <el-option label="æ±ç¥¨" value="draft" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¶è¡è´¦å·" prop="bankAccount" v-if="form.paymentMethod === 'bank_transfer'"> |
| | | <el-input v-model="form.bankAccount" placeholder="请è¾å
¥é¶è¡è´¦å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="弿·è¡" prop="bankName" v-if="form.paymentMethod === 'bank_transfer'"> |
| | | <el-input v-model="form.bankName" placeholder="请è¾å
¥å¼æ·è¡" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "仿¬¾å", |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | paymentCode: "", |
| | | supplierId: "", |
| | | paymentMethod: "", |
| | | status: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "仿¬¾åå·", prop: "paymentCode", width: "150" }, |
| | | { label: "å
³èç³è¯·å", prop: "applyCode", width: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", width: "180" }, |
| | | { label: "仿¬¾æ¥æ", prop: "paymentDate", width: "120" }, |
| | | { label: "仿¬¾éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "仿¬¾æ¹å¼", prop: "paymentMethod", slot: "paymentMethod" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { label: "夿³¨", prop: "remark", showOverflowTooltip: true }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "220", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | |
| | | const supplierList = [ |
| | | { id: 1, name: "åäº¬åææä¾åºå" }, |
| | | { id: 2, name: "䏿µ·çµåå
å¨ä»¶å
¬å¸" }, |
| | | { id: 3, name: "广å·å
è£
ææå" }, |
| | | { id: 4, name: "æ·±å³äºéé
ä»¶å
¬å¸" }, |
| | | ]; |
| | | |
| | | const applyList = [ |
| | | { applyCode: "FK2024001", supplierId: 1, amount: 5000 }, |
| | | { applyCode: "FK2024002", supplierId: 2, amount: 8000 }, |
| | | { applyCode: "FK2024003", supplierId: 3, amount: 3000 }, |
| | | ]; |
| | | |
| | | const form = reactive({ |
| | | paymentCode: "", |
| | | applyCode: "", |
| | | supplierId: "", |
| | | paymentDate: "", |
| | | amount: 0, |
| | | paymentMethod: "bank_transfer", |
| | | bankAccount: "", |
| | | bankName: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | applyCode: [{ required: true, message: "è¯·éæ©å
³èç³è¯·å", trigger: "change" }], |
| | | supplierId: [{ required: true, message: "è¯·éæ©ä¾åºå", trigger: "change" }], |
| | | paymentDate: [{ required: true, message: "è¯·éæ©ä»æ¬¾æ¥æ", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥ä»æ¬¾éé¢", trigger: "blur" }], |
| | | paymentMethod: [{ required: true, message: "è¯·éæ©ä»æ¬¾æ¹å¼", trigger: "change" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, paymentCode: "FKD2024001", applyCode: "FK2024001", supplierId: 1, supplierName: "åäº¬åææä¾åºå", paymentDate: "2024-01-15", amount: 5000, paymentMethod: "bank_transfer", status: "completed", bankAccount: "6222021234567890123", bankName: "å·¥åé¶è¡", remark: "" }, |
| | | { id: 2, paymentCode: "FKD2024002", applyCode: "FK2024002", supplierId: 2, supplierName: "䏿µ·çµåå
å¨ä»¶å
¬å¸", paymentDate: "2024-01-18", amount: 8000, paymentMethod: "bank_transfer", status: "pending", bankAccount: "6222029876543210987", bankName: "建设é¶è¡", remark: "" }, |
| | | { id: 3, paymentCode: "FKD2024003", applyCode: "FK2024003", supplierId: 3, supplierName: "广å·å
è£
ææå", paymentDate: "2024-01-20", amount: 3000, paymentMethod: "cash", status: "completed", remark: "" }, |
| | | ]; |
| | | |
| | | const totalPaymentAmount = computed(() => { |
| | | return dataList.value.reduce((sum, item) => sum + Number(item.amount), 0); |
| | | }); |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getPaymentMethodLabel = (method) => { |
| | | const map = { |
| | | bank_transfer: "é¶è¡è½¬è´¦", |
| | | cash: "ç°é", |
| | | check: "æ¯ç¥¨", |
| | | draft: "æ±ç¥¨", |
| | | }; |
| | | return map[method] || method; |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.paymentCode) { |
| | | result = result.filter(item => item.paymentCode.includes(filters.paymentCode)); |
| | | } |
| | | if (filters.supplierId) { |
| | | result = result.filter(item => item.supplierId === filters.supplierId); |
| | | } |
| | | if (filters.paymentMethod) { |
| | | result = result.filter(item => item.paymentMethod === filters.paymentMethod); |
| | | } |
| | | if (filters.status) { |
| | | result = result.filter(item => item.status === filters.status); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.paymentCode = ""; |
| | | filters.supplierId = ""; |
| | | filters.paymentMethod = ""; |
| | | filters.status = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ°å¢ä»æ¬¾"; |
| | | Object.assign(form, { |
| | | paymentCode: "FKD" + Date.now().toString().slice(-8), |
| | | applyCode: "", |
| | | supplierId: "", |
| | | paymentDate: new Date().toISOString().split('T')[0], |
| | | amount: 0, |
| | | paymentMethod: "bank_transfer", |
| | | bankAccount: "", |
| | | bankName: "", |
| | | remark: "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾ä»æ¬¾"; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥ç仿¬¾å: ${row.paymentCode}`); |
| | | }; |
| | | |
| | | const handleComplete = (row) => { |
| | | ElMessageBox.confirm("ç¡®è®¤è¯¥ä»æ¬¾å已宿åï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "completed"; |
| | | } |
| | | ElMessage.success("仿¬¾å®æ"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleCancel = (row) => { |
| | | ElMessageBox.confirm("ç¡®è®¤åæ¶è¯¥ä»æ¬¾ååï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "cancelled"; |
| | | } |
| | | ElMessage.success("已忶"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const supplier = supplierList.find(item => item.id === form.supplierId); |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" }); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="ç³è¯·åå·:"> |
| | | <el-input v-model="filters.applyCode" placeholder="请è¾å
¥ç³è¯·åå·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable style="width: 200px;"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="å¾
审æ¹" value="pending" /> |
| | | <el-option label="已审æ¹" value="approved" /> |
| | | <el-option label="已驳å" value="rejected" /> |
| | | <el-option label="已仿¬¾" value="paid" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢ç³è¯·</el-button> |
| | | <el-button @click="handleBatchApply" icon="Document" :disabled="selectedRows.length === 0">æ¹éç³è¯·</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #amount="{ row }"> |
| | | <span class="text-danger">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | <template #paymentMethod="{ row }"> |
| | | <el-tag>{{ getPaymentMethodLabel(row.paymentMethod) }}</el-tag> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">审æ¹</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·åå·" prop="applyCode"> |
| | | <el-input v-model="form.applyCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºå" prop="supplierId"> |
| | | <el-select v-model="form.supplierId" placeholder="è¯·éæ©ä¾åºå" style="width: 100%;" :disabled="isEdit"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾éé¢" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="form.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%;"> |
| | | <el-option label="é¶è¡è½¬è´¦" value="bank_transfer" /> |
| | | <el-option label="ç°é" value="cash" /> |
| | | <el-option label="æ¯ç¥¨" value="check" /> |
| | | <el-option label="æ±ç¥¨" value="draft" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·æ¥æ" prop="applyDate"> |
| | | <el-date-picker v-model="form.applyDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ææä»æ¬¾æ¥æ" prop="expectedDate"> |
| | | <el-date-picker v-model="form.expectedDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å
³èå
¥åºå" prop="relatedDocs"> |
| | | <el-select v-model="form.relatedDocs" multiple placeholder="è¯·éæ©å
³èå
¥åºå" style="width: 100%;"> |
| | | <el-option v-for="item in inList" :key="item.inCode" :label="item.inCode" :value="item.inCode" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾äºç±" prop="reason"> |
| | | <el-input v-model="form.reason" type="textarea" :rows="3" placeholder="请è¾å
¥ä»æ¬¾äºç±" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "仿¬¾ç³è¯·", |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | applyCode: "", |
| | | supplierId: "", |
| | | status: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "ç³è¯·åå·", prop: "applyCode", width: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", width: "180" }, |
| | | { label: "仿¬¾éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "仿¬¾æ¹å¼", prop: "paymentMethod", slot: "paymentMethod" }, |
| | | { label: "ç³è¯·æ¥æ", prop: "applyDate", width: "120" }, |
| | | { label: "ææä»æ¬¾æ¥", prop: "expectedDate", width: "120" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "200", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const selectedRows = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | |
| | | const supplierList = [ |
| | | { id: 1, name: "åäº¬åææä¾åºå" }, |
| | | { id: 2, name: "䏿µ·çµåå
å¨ä»¶å
¬å¸" }, |
| | | { id: 3, name: "广å·å
è£
ææå" }, |
| | | { id: 4, name: "æ·±å³äºéé
ä»¶å
¬å¸" }, |
| | | ]; |
| | | |
| | | const inList = [ |
| | | { inCode: "RK2024001", supplierId: 1 }, |
| | | { inCode: "RK2024002", supplierId: 2 }, |
| | | { inCode: "RK2024003", supplierId: 3 }, |
| | | ]; |
| | | |
| | | const form = reactive({ |
| | | applyCode: "", |
| | | supplierId: "", |
| | | amount: 0, |
| | | paymentMethod: "bank_transfer", |
| | | applyDate: "", |
| | | expectedDate: "", |
| | | relatedDocs: [], |
| | | reason: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | supplierId: [{ required: true, message: "è¯·éæ©ä¾åºå", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥ä»æ¬¾éé¢", trigger: "blur" }], |
| | | paymentMethod: [{ required: true, message: "è¯·éæ©ä»æ¬¾æ¹å¼", trigger: "change" }], |
| | | applyDate: [{ required: true, message: "è¯·éæ©ç³è¯·æ¥æ", trigger: "change" }], |
| | | expectedDate: [{ required: true, message: "è¯·éæ©ææä»æ¬¾æ¥æ", trigger: "change" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, applyCode: "FK2024001", supplierId: 1, supplierName: "åäº¬åææä¾åºå", amount: 5000, paymentMethod: "bank_transfer", applyDate: "2024-01-12", expectedDate: "2024-01-15", status: "pending", relatedDocs: ["RK2024001"], reason: "æ¯ä»åææè´§æ¬¾", remark: "" }, |
| | | { id: 2, applyCode: "FK2024002", supplierId: 2, supplierName: "䏿µ·çµåå
å¨ä»¶å
¬å¸", amount: 8000, paymentMethod: "bank_transfer", applyDate: "2024-01-14", expectedDate: "2024-01-18", status: "approved", relatedDocs: ["RK2024002"], reason: "æ¯ä»çµåå
å¨ä»¶è´§æ¬¾", remark: "" }, |
| | | { id: 3, applyCode: "FK2024003", supplierId: 3, supplierName: "广å·å
è£
ææå", amount: 3000, paymentMethod: "cash", applyDate: "2024-01-16", expectedDate: "2024-01-20", status: "paid", relatedDocs: ["RK2024003"], reason: "æ¯ä»å
è£
ææè´§æ¬¾", remark: "" }, |
| | | ]; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getPaymentMethodLabel = (method) => { |
| | | const map = { |
| | | bank_transfer: "é¶è¡è½¬è´¦", |
| | | cash: "ç°é", |
| | | check: "æ¯ç¥¨", |
| | | draft: "æ±ç¥¨", |
| | | }; |
| | | return map[method] || method; |
| | | }; |
| | | |
| | | const getStatusLabel = (status) => { |
| | | const map = { pending: "å¾
审æ¹", approved: "已审æ¹", rejected: "已驳å", paid: "已仿¬¾" }; |
| | | return map[status] || status; |
| | | }; |
| | | |
| | | const getStatusType = (status) => { |
| | | const map = { pending: "warning", approved: "success", rejected: "danger", paid: "primary" }; |
| | | return map[status] || ""; |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.applyCode) { |
| | | result = result.filter(item => item.applyCode.includes(filters.applyCode)); |
| | | } |
| | | if (filters.supplierId) { |
| | | result = result.filter(item => item.supplierId === filters.supplierId); |
| | | } |
| | | if (filters.status) { |
| | | result = result.filter(item => item.status === filters.status); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.applyCode = ""; |
| | | filters.supplierId = ""; |
| | | filters.status = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ°å¢ä»æ¬¾ç³è¯·"; |
| | | Object.assign(form, { |
| | | applyCode: "FK" + Date.now().toString().slice(-8), |
| | | supplierId: "", |
| | | amount: 0, |
| | | paymentMethod: "bank_transfer", |
| | | applyDate: new Date().toISOString().split('T')[0], |
| | | expectedDate: "", |
| | | relatedDocs: [], |
| | | reason: "", |
| | | remark: "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾ä»æ¬¾ç³è¯·"; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥çç³è¯·å: ${row.applyCode}`); |
| | | }; |
| | | |
| | | const handleAudit = (row) => { |
| | | ElMessageBox.confirm("确认审æ¹éè¿è¯¥ä»æ¬¾ç³è¯·åï¼", "æç¤º", { |
| | | confirmButtonText: "éè¿", |
| | | cancelButtonText: "驳å", |
| | | distinguishCancelAndClose: true, |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "approved"; |
| | | } |
| | | ElMessage.success("审æ¹éè¿"); |
| | | getTableData(); |
| | | }).catch((action) => { |
| | | if (action === "cancel") { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "rejected"; |
| | | } |
| | | ElMessage.warning("已驳å"); |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleBatchApply = () => { |
| | | ElMessage.success(`æ¹éç³è¯· ${selectedRows.value.length} æ¡è®°å½`); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const supplier = supplierList.find(item => item.id === form.supplierId); |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" }); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <!-- éè´å
¥åº --> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="å
¥åºåå·:"> |
| | | <el-input v-model="filters.inboundBatches" placeholder="请è¾å
¥å
¥åºåå·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-input v-model="filters.supplierName" placeholder="请è¾å
¥ä¾åºå" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="å
¥åºæ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | <template #inboundDate="{ row }"> |
| | | {{ row.InboundDate || row.inboundDate || "" }} |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listPageAccountPurchase } from "@/api/financialManagement/accountPurchase"; |
| | | |
| | | defineOptions({ |
| | | name: "éè´å
¥åº", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const filters = reactive({ |
| | | inboundBatches: "", |
| | | supplierName: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "å
¥åºåå·", prop: "inboundBatches", minWidth: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", minWidth: "180" }, |
| | | { |
| | | label: "å
¥åºæ¥æ", |
| | | prop: "InboundDate", |
| | | minWidth: "170", |
| | | dataType: "slot", |
| | | slot: "inboundDate", |
| | | }, |
| | | { label: "产ååç§°", prop: "productName", minWidth: "140" }, |
| | | { label: "产åè§æ ¼", prop: "specificationModel", minWidth: "140" }, |
| | | { label: "éè´è®¢åå·", prop: "purchaseContractNumber", minWidth: "150" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | function buildFilterParams() { |
| | | const params = { |
| | | inboundBatches: filters.inboundBatches || undefined, |
| | | supplierName: filters.supplierName || undefined, |
| | | }; |
| | | if (filters.dateRange && filters.dateRange.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | return params; |
| | | } |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | listPageAccountPurchase({ |
| | | ...buildFilterParams(), |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }) |
| | | .then((res) => { |
| | | const ok = res.code === 200 || res.code === 0; |
| | | if (ok && res.data) { |
| | | pagination.total = res.data.total ?? 0; |
| | | dataList.value = res.data.records ?? []; |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | dataList.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.inboundBatches = ""; |
| | | filters.supplierName = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "/accountPurchase/exportAccountPurchaseInbound", |
| | | buildFilterParams(), |
| | | `éè´å
¥åº_${new Date().getTime()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <!-- éè´éè´§ --> |
| | | |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="éè´§åå·:"> |
| | | <el-input |
| | | v-model="filters.returnNo" |
| | | placeholder="请è¾å
¥éè´§åå·" |
| | | clearable |
| | | style="width: 200px" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-input |
| | | v-model="filters.supplierName" |
| | | placeholder="请è¾å
¥ä¾åºå" |
| | | clearable |
| | | style="width: 200px" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="éè´§æ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | |
| | | <div> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | |
| | | size: pagination.pageSize, |
| | | |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | |
| | | import { ElMessage } from "element-plus"; |
| | | |
| | | import { listPageAccountPurchaseReturn } from "@/api/financialManagement/accountPurchase"; |
| | | |
| | | defineOptions({ |
| | | name: "éè´éè´§", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const filters = reactive({ |
| | | returnNo: "", |
| | | |
| | | supplierName: "", |
| | | |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | |
| | | pageSize: 10, |
| | | |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "éè´§åå·", prop: "returnNo", minWidth: "150" }, |
| | | |
| | | { label: "ä¾åºå", prop: "supplierName", minWidth: "180" }, |
| | | |
| | | { label: "å
³èå
¥åºåå·", prop: "inboundBatches", minWidth: "150" }, |
| | | |
| | | { label: "éè´§æ¥æ", prop: "preparedAt", minWidth: "170" }, |
| | | |
| | | { |
| | | label: "鿬¾æ»é¢", |
| | | |
| | | prop: "totalAmount", |
| | | |
| | | minWidth: "150", |
| | | |
| | | align: "right", |
| | | |
| | | formatData: (val) => |
| | | val === null || val === undefined || val === "" |
| | | ? "" |
| | | : Number(val).toLocaleString("zh-CN", { |
| | | minimumFractionDigits: 2, |
| | | maximumFractionDigits: 2, |
| | | }), |
| | | }, |
| | | |
| | | { label: "éè´§æ¹å¼", prop: "returnType", minWidth: "150" }, |
| | | |
| | | { label: "éè´è®¢åå·", prop: "purchaseContractNumber", minWidth: "150" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | |
| | | const tableLoading = ref(false); |
| | | |
| | | function buildFilterParams() { |
| | | const params = { |
| | | returnNo: filters.returnNo || undefined, |
| | | |
| | | supplierName: filters.supplierName || undefined, |
| | | }; |
| | | |
| | | if (filters.dateRange && filters.dateRange.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | |
| | | return params; |
| | | } |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | |
| | | listPageAccountPurchaseReturn({ |
| | | ...buildFilterParams(), |
| | | |
| | | current: pagination.currentPage, |
| | | |
| | | size: pagination.pageSize, |
| | | }) |
| | | .then((res) => { |
| | | const ok = res.code === 200 || res.code === 0; |
| | | |
| | | if (ok && res.data) { |
| | | pagination.total = res.data.total ?? 0; |
| | | |
| | | dataList.value = res.data.records ?? []; |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | |
| | | dataList.value = []; |
| | | } |
| | | }) |
| | | |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | }) |
| | | |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.returnNo = ""; |
| | | |
| | | filters.supplierName = ""; |
| | | |
| | | filters.dateRange = []; |
| | | |
| | | pagination.currentPage = 1; |
| | | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | |
| | | pagination.pageSize = limit; |
| | | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "/accountPurchase/exportAccountPurchaseReturn", |
| | | |
| | | buildFilterParams(), |
| | | |
| | | `éè´éè´§_${new Date().getTime()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | |
| | | justify-content: space-between; |
| | | |
| | | margin-bottom: 15px; |
| | | } |
| | | </style> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable style="width: 200px;"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="对账æé´:"> |
| | | <el-date-picker v-model="filters.startMonth" type="month" placeholder="å¼å§æä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <span style="margin: 0 10px;">è³</span> |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="ç»ææä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-button type="primary" @click="generateStatement" icon="Document">çæå¯¹è´¦å</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button @click="handleOut" icon="Download">导åºå¯¹è´¦å</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | <template #beginBalance="{ row }"> |
| | | <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.beginBalance) }}</span> |
| | | </template> |
| | | <template #currentPayable="{ row }"> |
| | | <span class="text-danger">Â¥{{ formatMoney(row.currentPayable) }}</span> |
| | | </template> |
| | | <template #currentPayment="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.currentPayment) }}</span> |
| | | </template> |
| | | <template #endBalance="{ row }"> |
| | | <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.endBalance) }}</span> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="viewDetail(row)">æ¥çæç»</el-button> |
| | | <el-button type="primary" link @click="printStatement(row)">æå°</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog title="对账æç»" v-model="detailDialogVisible" width="900px" @confirm="printDetail" @cancel="detailDialogVisible = false" operationType="detail"> |
| | | <div class="statement-header"> |
| | | <h3>{{ currentSupplier }} åºä»å¯¹è´¦å</h3> |
| | | <p>对账æé´: {{ currentPeriod }}</p> |
| | | </div> |
| | | <el-table :data="detailData" border style="width: 100%"> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="type" label="ç±»å" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.type === 'å
¥åº' ? 'success' : row.type === 'éè´§' ? 'danger' : 'primary'">{{ row.type }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="code" label="åæ®ç¼å·" width="150" /> |
| | | <el-table-column prop="debit" label="åæ¹(仿¬¾)" width="120"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.debit > 0" class="text-success">Â¥{{ formatMoney(row.debit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="credit" label="è´·æ¹(åºä»)" width="120"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.credit > 0" class="text-danger">Â¥{{ formatMoney(row.credit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="balance" label="ä½é¢" width="120"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.balance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.balance) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" show-overflow-tooltip /> |
| | | </el-table> |
| | | <template #footer> |
| | | <el-button type="primary" @click="printDetail">æå°</el-button> |
| | | <el-button @click="detailDialogVisible = false">å
³é</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <FormDialog title="çæå¯¹è´¦å" v-model="generateDialogVisible" width="1000px" @confirm="confirmGenerate" @cancel="generateDialogVisible = false"> |
| | | <el-form :model="generateForm" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éæ©ä¾åºå" prop="supplierId"> |
| | | <el-select v-model="generateForm.supplierId" placeholder="è¯·éæ©ä¾åºå" style="width: 100%;" @change="onSupplierChange"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="对账æä»½" prop="period"> |
| | | <el-date-picker v-model="generateForm.period" type="month" placeholder="éæ©æä»½" value-format="YYYY-MM" style="width: 100%;" @change="onPeriodChange" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <div v-if="purchaseData.length > 0" class="purchase-section"> |
| | | <div class="section-title">æ¬æéè´æ°æ®</div> |
| | | <el-table :data="purchaseData" border style="width: 100%; margin-bottom: 15px;" v-loading="purchaseLoading" @selection-change="handlePurchaseSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="code" label="åæ®ç¼å·" width="150" /> |
| | | <el-table-column prop="type" label="ç±»å" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.type === 'å
¥åº' ? 'success' : 'danger'">{{ row.type }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="amount" label="éé¢" width="120"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.type === 'å
¥åº' ? 'text-danger' : 'text-success'">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" /> |
| | | </el-table> |
| | | |
| | | <div class="summary-row"> |
| | | <span>æåä½é¢: <strong class="text-primary">Â¥{{ formatMoney(generateForm.beginBalance) }}</strong></span> |
| | | <span>æ¬æåºä»: <strong class="text-danger">Â¥{{ formatMoney(generateForm.currentPayable) }}</strong></span> |
| | | <span>æ¬æä»æ¬¾: <strong class="text-success">Â¥{{ formatMoney(generateForm.currentPayment) }}</strong></span> |
| | | <span>ææ«ä½é¢: <strong class="text-primary">Â¥{{ formatMoney(calculateEndBalance(generateForm.beginBalance, generateForm.currentPayable, generateForm.currentPayment)) }}</strong></span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else-if="generateForm.supplierId && !purchaseLoading" class="empty-tip"> |
| | | <el-empty description="该ä¾åºåæ¬æææ éè´æ°æ®" /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate">确认çæ</el-button> |
| | | <el-button @click="generateDialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "åºä»å¯¹è´¦", |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | supplierId: "", |
| | | startMonth: "", |
| | | endMonth: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "对账åå·", prop: "statementCode", width: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", width: "180" }, |
| | | { label: "对账æé´", prop: "period", width: "150" }, |
| | | { label: "æåä½é¢", prop: "beginBalance", slot: "beginBalance" }, |
| | | { label: "æ¬æåºä»", prop: "currentPayable", slot: "currentPayable" }, |
| | | { label: "æ¬æä»æ¬¾", prop: "currentPayment", slot: "currentPayment" }, |
| | | { label: "ææ«ä½é¢", prop: "endBalance", slot: "endBalance" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "150", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const detailDialogVisible = ref(false); |
| | | const currentSupplier = ref(""); |
| | | const currentPeriod = ref(""); |
| | | const detailData = ref([]); |
| | | |
| | | const generateDialogVisible = ref(false); |
| | | const purchaseLoading = ref(false); |
| | | const purchaseData = ref([]); |
| | | const selectedPurchases = ref([]); |
| | | |
| | | const generateForm = reactive({ |
| | | supplierId: "", |
| | | supplierName: "", |
| | | period: "", |
| | | beginBalance: 0, |
| | | currentPayable: 0, |
| | | currentPayment: 0, |
| | | }); |
| | | |
| | | const canGenerate = computed(() => { |
| | | return generateForm.supplierId && generateForm.period && selectedPurchases.value.length > 0; |
| | | }); |
| | | |
| | | const supplierList = [ |
| | | { id: 1, name: "åäº¬åææä¾åºå" }, |
| | | { id: 2, name: "䏿µ·çµåå
å¨ä»¶å
¬å¸" }, |
| | | { id: 3, name: "广å·å
è£
ææå" }, |
| | | { id: 4, name: "æ·±å³äºéé
ä»¶å
¬å¸" }, |
| | | ]; |
| | | |
| | | const mockData = [ |
| | | { id: 1, statementCode: "DZ202401001", supplierId: 1, supplierName: "åäº¬åææä¾åºå", period: "2024-01", beginBalance: 20000, currentPayable: 15000, currentPayment: 10000, endBalance: 25000 }, |
| | | { id: 2, statementCode: "DZ202401002", supplierId: 2, supplierName: "䏿µ·çµåå
å¨ä»¶å
¬å¸", period: "2024-01", beginBalance: 10000, currentPayable: 20000, currentPayment: 15000, endBalance: 15000 }, |
| | | { id: 3, statementCode: "DZ202402001", supplierId: 1, supplierName: "åäº¬åææä¾åºå", period: "2024-02", beginBalance: 25000, currentPayable: 18000, currentPayment: 20000, endBalance: 23000 }, |
| | | ]; |
| | | |
| | | const calculateEndBalance = (beginBalance, currentPayable, currentPayment) => { |
| | | return beginBalance + currentPayable - currentPayment; |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.supplierId) { |
| | | result = result.filter(item => item.supplierId === filters.supplierId); |
| | | } |
| | | if (filters.startMonth && filters.endMonth) { |
| | | result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.supplierId = ""; |
| | | filters.startMonth = ""; |
| | | filters.endMonth = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const generateStatement = () => { |
| | | generateForm.supplierId = ""; |
| | | generateForm.supplierName = ""; |
| | | generateForm.period = ""; |
| | | generateForm.beginBalance = 0; |
| | | generateForm.currentPayable = 0; |
| | | generateForm.currentPayment = 0; |
| | | purchaseData.value = []; |
| | | selectedPurchases.value = []; |
| | | generateDialogVisible.value = true; |
| | | }; |
| | | |
| | | const onSupplierChange = (supplierId) => { |
| | | const supplier = supplierList.find(item => item.id === supplierId); |
| | | if (supplier) { |
| | | generateForm.supplierName = supplier.name; |
| | | } |
| | | loadPurchaseData(); |
| | | }; |
| | | |
| | | const onPeriodChange = () => { |
| | | loadPurchaseData(); |
| | | }; |
| | | |
| | | const loadPurchaseData = () => { |
| | | if (!generateForm.supplierId || !generateForm.period) { |
| | | purchaseData.value = []; |
| | | return; |
| | | } |
| | | |
| | | purchaseLoading.value = true; |
| | | |
| | | setTimeout(() => { |
| | | const mockPurchaseData = [ |
| | | { id: 1, date: generateForm.period + "-05", code: "RK2024001", type: "å
¥åº", amount: 8000, remark: "åææéè´" }, |
| | | { id: 2, date: generateForm.period + "-10", code: "FK2024001", type: "仿¬¾", amount: 5000, remark: "æ¯ä»è´§æ¬¾" }, |
| | | { id: 3, date: generateForm.period + "-15", code: "RK2024002", type: "å
¥åº", amount: 12000, remark: "çµåå
å¨ä»¶" }, |
| | | { id: 4, date: generateForm.period + "-18", code: "TH2024001", type: "éè´§", amount: 2000, remark: "è´¨éé®é¢éè´§" }, |
| | | { id: 5, date: generateForm.period + "-22", code: "RK2024003", type: "å
¥åº", amount: 6000, remark: "å
è£
ææ" }, |
| | | { id: 6, date: generateForm.period + "-25", code: "FK2024002", type: "仿¬¾", amount: 8000, remark: "æ¯ä»è´§æ¬¾" }, |
| | | ]; |
| | | |
| | | purchaseData.value = mockPurchaseData; |
| | | |
| | | const lastPeriod = getLastPeriod(generateForm.period); |
| | | const lastStatement = mockData.find(item => |
| | | item.supplierId === generateForm.supplierId && item.period === lastPeriod |
| | | ); |
| | | generateForm.beginBalance = lastStatement ? lastStatement.endBalance : 0; |
| | | |
| | | calculateSummary(); |
| | | |
| | | purchaseLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | const getLastPeriod = (period) => { |
| | | const [year, month] = period.split("-").map(Number); |
| | | if (month === 1) { |
| | | return `${year - 1}-12`; |
| | | } |
| | | return `${year}-${String(month - 1).padStart(2, "0")}`; |
| | | }; |
| | | |
| | | const calculateSummary = () => { |
| | | let payable = 0; |
| | | let payment = 0; |
| | | |
| | | selectedPurchases.value.forEach(item => { |
| | | if (item.type === "å
¥åº") { |
| | | payable += item.amount; |
| | | } else if (item.type === "éè´§") { |
| | | payable -= item.amount; |
| | | } else if (item.type === "仿¬¾") { |
| | | payment += item.amount; |
| | | } |
| | | }); |
| | | |
| | | generateForm.currentPayable = payable; |
| | | generateForm.currentPayment = payment; |
| | | }; |
| | | |
| | | const handlePurchaseSelectionChange = (selection) => { |
| | | selectedPurchases.value = selection; |
| | | calculateSummary(); |
| | | }; |
| | | |
| | | const confirmGenerate = () => { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | const endBalance = calculateEndBalance(generateForm.beginBalance, generateForm.currentPayable, generateForm.currentPayment); |
| | | |
| | | mockData.unshift({ |
| | | id: newId, |
| | | statementCode: "DZ" + Date.now(), |
| | | supplierId: generateForm.supplierId, |
| | | supplierName: generateForm.supplierName, |
| | | period: generateForm.period, |
| | | beginBalance: generateForm.beginBalance, |
| | | currentPayable: generateForm.currentPayable, |
| | | currentPayment: generateForm.currentPayment, |
| | | endBalance, |
| | | }); |
| | | |
| | | generateDialogVisible.value = false; |
| | | ElMessage.success("对账åçææå"); |
| | | getTableData(); |
| | | }; |
| | | |
| | | const viewDetail = (row) => { |
| | | currentSupplier.value = row.supplierName; |
| | | currentPeriod.value = row.period; |
| | | |
| | | const purchaseInAmount = Math.floor(row.currentPayable * 0.7); |
| | | const returnAmount = Math.floor(row.currentPayable * 0.1); |
| | | const firstPayment = Math.floor(row.currentPayment * 0.5); |
| | | const secondPayment = row.currentPayment - firstPayment; |
| | | |
| | | let runningBalance = row.beginBalance; |
| | | |
| | | detailData.value = [ |
| | | { date: row.period + "-01", type: "æå", code: "-", debit: 0, credit: 0, balance: runningBalance, remark: "æåä½é¢" }, |
| | | { date: row.period + "-05", type: "å
¥åº", code: "RK2024001", debit: 0, credit: purchaseInAmount, balance: runningBalance += purchaseInAmount, remark: "éè´å
¥åº" }, |
| | | { date: row.period + "-10", type: "仿¬¾", code: "FK2024001", debit: firstPayment, credit: 0, balance: runningBalance -= firstPayment, remark: "æ¯ä»è´§æ¬¾" }, |
| | | { date: row.period + "-15", type: "å
¥åº", code: "RK2024002", debit: 0, credit: row.currentPayable - purchaseInAmount - returnAmount, balance: runningBalance += (row.currentPayable - purchaseInAmount - returnAmount), remark: "éè´å
¥åº" }, |
| | | { date: row.period + "-20", type: "éè´§", code: "TH2024001", debit: 0, credit: -returnAmount, balance: runningBalance -= returnAmount, remark: "éè´éè´§" }, |
| | | { date: row.period + "-25", type: "仿¬¾", code: "FK2024002", debit: secondPayment, credit: 0, balance: runningBalance -= secondPayment, remark: "æ¯ä»è´§æ¬¾" }, |
| | | ]; |
| | | |
| | | detailDialogVisible.value = true; |
| | | }; |
| | | |
| | | const printStatement = (row) => { |
| | | ElMessage.info(`æå°å¯¹è´¦å: ${row.statementCode}`); |
| | | }; |
| | | |
| | | const printDetail = () => { |
| | | ElMessage.info("æå°æç»"); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .statement-header { |
| | | text-align: center; |
| | | margin-bottom: 20px; |
| | | h3 { |
| | | margin: 0 0 10px 0; |
| | | } |
| | | p { |
| | | color: #909399; |
| | | margin: 0; |
| | | } |
| | | } |
| | | |
| | | .purchase-section { |
| | | margin-top: 20px; |
| | | |
| | | .section-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | margin-bottom: 15px; |
| | | padding-left: 10px; |
| | | border-left: 4px solid #409eff; |
| | | } |
| | | } |
| | | |
| | | .summary-row { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | padding: 15px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | margin-top: 15px; |
| | | |
| | | span { |
| | | font-size: 14px; |
| | | |
| | | strong { |
| | | font-size: 16px; |
| | | margin-left: 5px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .empty-tip { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="ç³è¯·åå·:"> |
| | | <el-input v-model="filters.applyCode" placeholder="请è¾å
¥ç³è¯·åå·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·:"> |
| | | <el-select v-model="filters.customerId" placeholder="è¯·éæ©å®¢æ·" clearable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="å¾
å®¡æ ¸" value="pending" /> |
| | | <el-option label="å·²å®¡æ ¸" value="approved" /> |
| | | <el-option label="已驳å" value="rejected" /> |
| | | <el-option label="å·²å¼ç¥¨" value="invoiced" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢ç³è¯·</el-button> |
| | | <el-button @click="handleBatchApply" icon="Document" :disabled="selectedRows.length === 0">æ¹éç³è¯·</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #amount="{ row }"> |
| | | <span class="text-primary">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | <template #taxRate="{ row }"> |
| | | <span>{{ row.taxRate }}%</span> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">å®¡æ ¸</el-button> |
| | | <el-button type="warning" link @click="handleInvoice(row)" v-if="row.status === 'approved'">å¼ç¥¨</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·åå·" prop="applyCode"> |
| | | <el-input v-model="form.applyCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·" prop="customerId"> |
| | | <el-select v-model="form.customerId" placeholder="è¯·éæ©å®¢æ·" style="width: 100%;" :disabled="isEdit"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼ç¥¨éé¢" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¨ç" prop="taxRate"> |
| | | <el-select v-model="form.taxRate" placeholder="è¯·éæ©ç¨ç" style="width: 100%;"> |
| | | <el-option |
| | | v-for="dict in tax_rate" |
| | | :key="dict.value" |
| | | :label="dict.label" |
| | | :value="Number(dict.value)" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票类å" prop="invoiceType"> |
| | | <el-select v-model="form.invoiceType" placeholder="è¯·éæ©å票类å" style="width: 100%;"> |
| | | <el-option label="å¢å¼ç¨ä¸ç¨å票" value="special" /> |
| | | <el-option label="å¢å¼ç¨æ®éå票" value="normal" /> |
| | | <el-option label="çµåå票" value="electronic" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·æ¥æ" prop="applyDate"> |
| | | <el-date-picker v-model="form.applyDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å票å
容" prop="content"> |
| | | <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请è¾å
¥å票å
容" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "å¼ç¥¨ç³è¯·", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const { tax_rate } = proxy.useDict("tax_rate"); |
| | | |
| | | const filters = reactive({ |
| | | applyCode: "", |
| | | customerId: "", |
| | | status: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "ç³è¯·åå·", prop: "applyCode", width: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { label: "å¼ç¥¨éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "ç¨ç", prop: "taxRate", slot: "taxRate" }, |
| | | { label: "å票类å", prop: "invoiceTypeLabel", width: "130" }, |
| | | { label: "ç³è¯·æ¥æ", prop: "applyDate", width: "120" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "200", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const selectedRows = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | |
| | | const customerList = [ |
| | | { id: 1, name: "åäº¬ç§ææéå
¬å¸" }, |
| | | { id: 2, name: "䏿µ·è´¸æå
¬å¸" }, |
| | | { id: 3, name: "广å·å®ä¸æéå
¬å¸" }, |
| | | { id: 4, name: "æ·±å³çµåå
¬å¸" }, |
| | | ]; |
| | | |
| | | const form = reactive({ |
| | | applyCode: "", |
| | | customerId: "", |
| | | amount: 0, |
| | | taxRate: 13, |
| | | invoiceType: "special", |
| | | applyDate: "", |
| | | content: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | customerId: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥å¼ç¥¨éé¢", trigger: "blur" }], |
| | | taxRate: [{ required: true, message: "è¯·éæ©ç¨ç", trigger: "change" }], |
| | | invoiceType: [{ required: true, message: "è¯·éæ©å票类å", trigger: "change" }], |
| | | applyDate: [{ required: true, message: "è¯·éæ©ç³è¯·æ¥æ", trigger: "change" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, applyCode: "KP2024001", customerId: 1, customerName: "åäº¬ç§ææéå
¬å¸", amount: 5000, taxRate: 13, invoiceType: "special", invoiceTypeLabel: "å¢å¼ç¨ä¸ç¨å票", applyDate: "2024-01-15", status: "pending", content: "软件æå¡è´¹", remark: "" }, |
| | | { id: 2, applyCode: "KP2024002", customerId: 2, customerName: "䏿µ·è´¸æå
¬å¸", amount: 8000, taxRate: 13, invoiceType: "normal", invoiceTypeLabel: "å¢å¼ç¨æ®éå票", applyDate: "2024-01-16", status: "approved", content: "ååéå®", remark: "" }, |
| | | { id: 3, applyCode: "KP2024003", customerId: 3, customerName: "广å·å®ä¸æéå
¬å¸", amount: 12000, taxRate: 6, invoiceType: "electronic", invoiceTypeLabel: "çµåå票", applyDate: "2024-01-18", status: "invoiced", content: "ææ¯æå¡è´¹", remark: "" }, |
| | | ]; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getStatusLabel = (status) => { |
| | | const map = { pending: "å¾
å®¡æ ¸", approved: "å·²å®¡æ ¸", rejected: "已驳å", invoiced: "å·²å¼ç¥¨" }; |
| | | return map[status] || status; |
| | | }; |
| | | |
| | | const getStatusType = (status) => { |
| | | const map = { pending: "warning", approved: "success", rejected: "danger", invoiced: "primary" }; |
| | | return map[status] || ""; |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.applyCode) { |
| | | result = result.filter(item => item.applyCode.includes(filters.applyCode)); |
| | | } |
| | | if (filters.customerId) { |
| | | result = result.filter(item => item.customerId === filters.customerId); |
| | | } |
| | | if (filters.status) { |
| | | result = result.filter(item => item.status === filters.status); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.applyCode = ""; |
| | | filters.customerId = ""; |
| | | filters.status = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ°å¢å¼ç¥¨ç³è¯·"; |
| | | Object.assign(form, { |
| | | applyCode: "KP" + Date.now().toString().slice(-8), |
| | | customerId: "", |
| | | amount: 0, |
| | | taxRate: 13, |
| | | invoiceType: "special", |
| | | applyDate: new Date().toISOString().split('T')[0], |
| | | content: "", |
| | | remark: "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾å¼ç¥¨ç³è¯·"; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥çç³è¯·å: ${row.applyCode}`); |
| | | }; |
| | | |
| | | const handleAudit = (row) => { |
| | | ElMessageBox.confirm("ç¡®è®¤å®¡æ ¸éè¿è¯¥å¼ç¥¨ç³è¯·åï¼", "æç¤º", { |
| | | confirmButtonText: "éè¿", |
| | | cancelButtonText: "驳å", |
| | | distinguishCancelAndClose: true, |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "approved"; |
| | | } |
| | | ElMessage.success("å®¡æ ¸éè¿"); |
| | | getTableData(); |
| | | }).catch((action) => { |
| | | if (action === "cancel") { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "rejected"; |
| | | } |
| | | ElMessage.warning("已驳å"); |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleInvoice = (row) => { |
| | | ElMessageBox.confirm("确认已å¼å
·å票ï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "invoiced"; |
| | | } |
| | | ElMessage.success("å¼ç¥¨å®æ"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleBatchApply = () => { |
| | | ElMessage.success(`æ¹éç³è¯· ${selectedRows.value.length} æ¡è®°å½`); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const customer = customerList.find(item => item.id === form.customerId); |
| | | const invoiceTypeMap = { special: "å¢å¼ç¨ä¸ç¨å票", normal: "å¢å¼ç¨æ®éå票", electronic: "çµåå票" }; |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType], status: "pending" }); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="å票代ç :"> |
| | | <el-input v-model="filters.invoiceCode" placeholder="请è¾å
¥å票代ç " clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="å票å·ç :"> |
| | | <el-input v-model="filters.invoiceNo" placeholder="请è¾å
¥å票å·ç " clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·:"> |
| | | <el-select v-model="filters.customerId" placeholder="è¯·éæ©å®¢æ·" clearable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">å½å
¥å票</el-button> |
| | | <el-button @click="handleImport" icon="Upload">导å
¥</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | <template #amount="{ row }"> |
| | | <span class="text-primary">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | <template #taxAmount="{ row }"> |
| | | <span class="text-danger">Â¥{{ formatMoney(row.taxAmount) }}</span> |
| | | </template> |
| | | <template #totalAmount="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.totalAmount) }}</span> |
| | | </template> |
| | | <template #invoiceType="{ row }"> |
| | | <el-tag :type="row.invoiceType === 'special' ? 'danger' : 'primary'">{{ row.invoiceTypeLabel }}</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)">ç¼è¾</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">ä½åº</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票代ç " prop="invoiceCode"> |
| | | <el-input v-model="form.invoiceCode" placeholder="请è¾å
¥å票代ç " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票å·ç " prop="invoiceNo"> |
| | | <el-input v-model="form.invoiceNo" placeholder="请è¾å
¥å票å·ç " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·" prop="customerId"> |
| | | <el-select v-model="form.customerId" placeholder="è¯·éæ©å®¢æ·" style="width: 100%;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼ç¥¨æ¥æ" prop="invoiceDate"> |
| | | <el-date-picker v-model="form.invoiceDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票类å" prop="invoiceType"> |
| | | <el-select v-model="form.invoiceType" placeholder="è¯·éæ©å票类å" style="width: 100%;" @change="handleInvoiceTypeChange"> |
| | | <el-option label="å¢å¼ç¨ä¸ç¨å票" value="special" /> |
| | | <el-option label="å¢å¼ç¨æ®éå票" value="normal" /> |
| | | <el-option label="çµåå票" value="electronic" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¨ç" prop="taxRate"> |
| | | <el-select v-model="form.taxRate" placeholder="è¯·éæ©ç¨ç" style="width: 100%;" @change="calculateTax"> |
| | | <el-option |
| | | v-for="dict in tax_rate" |
| | | :key="dict.value" |
| | | :label="dict.label" |
| | | :value="Number(dict.value)" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="éé¢(ä¸å«ç¨)" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" @change="calculateTax" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ç¨é¢"> |
| | | <el-input v-model="form.taxAmount" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ä»·ç¨å计"> |
| | | <el-input v-model="form.totalAmount" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å票å
容" prop="content"> |
| | | <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请è¾å
¥å票å
容" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "é项å票", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const { tax_rate } = proxy.useDict("tax_rate"); |
| | | |
| | | const filters = reactive({ |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | customerId: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "å票代ç ", prop: "invoiceCode", width: "130" }, |
| | | { label: "å票å·ç ", prop: "invoiceNo", width: "120" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { label: "å¼ç¥¨æ¥æ", prop: "invoiceDate", width: "120" }, |
| | | { label: "éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "ç¨é¢", prop: "taxAmount", slot: "taxAmount" }, |
| | | { label: "ä»·ç¨å计", prop: "totalAmount", slot: "totalAmount" }, |
| | | { label: "å票类å", prop: "invoiceType", slot: "invoiceType" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "180", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | |
| | | const customerList = [ |
| | | { id: 1, name: "åäº¬ç§ææéå
¬å¸" }, |
| | | { id: 2, name: "䏿µ·è´¸æå
¬å¸" }, |
| | | { id: 3, name: "广å·å®ä¸æéå
¬å¸" }, |
| | | { id: 4, name: "æ·±å³çµåå
¬å¸" }, |
| | | ]; |
| | | |
| | | const form = reactive({ |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | customerId: "", |
| | | invoiceDate: "", |
| | | invoiceType: "special", |
| | | taxRate: 13, |
| | | amount: 0, |
| | | taxAmount: 0, |
| | | totalAmount: 0, |
| | | content: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | invoiceCode: [{ required: true, message: "请è¾å
¥å票代ç ", trigger: "blur" }], |
| | | invoiceNo: [{ required: true, message: "请è¾å
¥å票å·ç ", trigger: "blur" }], |
| | | customerId: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | | invoiceDate: [{ required: true, message: "è¯·éæ©å¼ç¥¨æ¥æ", trigger: "change" }], |
| | | invoiceType: [{ required: true, message: "è¯·éæ©å票类å", trigger: "change" }], |
| | | taxRate: [{ required: true, message: "è¯·éæ©ç¨ç", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥éé¢", trigger: "blur" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, invoiceCode: "0440021001", invoiceNo: "12345678", customerId: 1, customerName: "åäº¬ç§ææéå
¬å¸", invoiceDate: "2024-01-15", amount: 5000, taxRate: 13, taxAmount: 650, totalAmount: 5650, invoiceType: "special", invoiceTypeLabel: "å¢å¼ç¨ä¸ç¨å票", content: "软件æå¡è´¹", remark: "" }, |
| | | { id: 2, invoiceCode: "0440021002", invoiceNo: "87654321", customerId: 2, customerName: "䏿µ·è´¸æå
¬å¸", invoiceDate: "2024-01-16", amount: 8000, taxRate: 13, taxAmount: 1040, totalAmount: 9040, invoiceType: "normal", invoiceTypeLabel: "å¢å¼ç¨æ®éå票", content: "ååéå®", remark: "" }, |
| | | { id: 3, invoiceCode: "0440021003", invoiceNo: "11112222", customerId: 3, customerName: "广å·å®ä¸æéå
¬å¸", invoiceDate: "2024-01-18", amount: 12000, taxRate: 6, taxAmount: 720, totalAmount: 12720, invoiceType: "electronic", invoiceTypeLabel: "çµåå票", content: "ææ¯æå¡è´¹", remark: "" }, |
| | | ]; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const calculateTax = () => { |
| | | form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2)); |
| | | form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2)); |
| | | }; |
| | | |
| | | const handleInvoiceTypeChange = () => { |
| | | if (form.invoiceType === "special") { |
| | | form.taxRate = 13; |
| | | } else { |
| | | form.taxRate = 13; |
| | | } |
| | | calculateTax(); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.invoiceCode) { |
| | | result = result.filter(item => item.invoiceCode.includes(filters.invoiceCode)); |
| | | } |
| | | if (filters.invoiceNo) { |
| | | result = result.filter(item => item.invoiceNo.includes(filters.invoiceNo)); |
| | | } |
| | | if (filters.customerId) { |
| | | result = result.filter(item => item.customerId === filters.customerId); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.invoiceCode = ""; |
| | | filters.invoiceNo = ""; |
| | | filters.customerId = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "å½å
¥å票"; |
| | | Object.assign(form, { |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | customerId: "", |
| | | invoiceDate: new Date().toISOString().split('T')[0], |
| | | invoiceType: "special", |
| | | taxRate: 13, |
| | | amount: 0, |
| | | taxAmount: 0, |
| | | totalAmount: 0, |
| | | content: "", |
| | | remark: "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾å票"; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥çå票: ${row.invoiceCode}-${row.invoiceNo}`); |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm("确认ä½åºè¯¥å票åï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | ElMessage.success("ä½åºæå"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleImport = () => { |
| | | ElMessage.info("导å
¥åè½"); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const customer = customerList.find(item => item.id === form.customerId); |
| | | const invoiceTypeMap = { special: "å¢å¼ç¨ä¸ç¨å票", normal: "å¢å¼ç¨æ®éå票", electronic: "çµåå票" }; |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] }); |
| | | ElMessage.success("å½å
¥æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="æ¶æ¬¾åå·:"> |
| | | <el-input v-model="filters.receiptCode" placeholder="请è¾å
¥æ¶æ¬¾åå·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·:"> |
| | | <el-select v-model="filters.customerId" placeholder="è¯·éæ©å®¢æ·" clearable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¶æ¬¾æ¹å¼:"> |
| | | <el-select v-model="filters.receiptMethod" placeholder="è¯·éæ©æ¶æ¬¾æ¹å¼" clearable style="width: 150px;"> |
| | | <el-option label="é¶è¡è½¬è´¦" value="bank_transfer" /> |
| | | <el-option label="ç°é" value="cash" /> |
| | | <el-option label="æ¯ç¥¨" value="check" /> |
| | | <el-option label="æ±ç¥¨" value="draft" /> |
| | | <el-option label="æ¯ä»å®" value="alipay" /> |
| | | <el-option label="微信" value="wechat" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-statistic title="æ¬ææ¶æ¬¾å计" :value="totalReceiptAmount" precision="2" prefix="Â¥" /> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢æ¶æ¬¾</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | <template #amount="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | <template #receiptMethod="{ row }"> |
| | | <el-tag>{{ getReceiptMethodLabel(row.receiptMethod) }}</el-tag> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="row.status === 'confirmed' ? 'success' : 'warning'">{{ row.status === 'confirmed' ? '已确认' : 'å¾
确认' }}</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleConfirm(row)" v-if="row.status === 'pending'">确认</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)" v-if="row.status === 'pending'">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¶æ¬¾åå·" prop="receiptCode"> |
| | | <el-input v-model="form.receiptCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·" prop="customerId"> |
| | | <el-select v-model="form.customerId" placeholder="è¯·éæ©å®¢æ·" style="width: 100%;" :disabled="isEdit"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¶æ¬¾æ¥æ" prop="receiptDate"> |
| | | <el-date-picker v-model="form.receiptDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¶æ¬¾éé¢" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¶æ¬¾æ¹å¼" prop="receiptMethod"> |
| | | <el-select v-model="form.receiptMethod" placeholder="è¯·éæ©æ¶æ¬¾æ¹å¼" style="width: 100%;"> |
| | | <el-option label="é¶è¡è½¬è´¦" value="bank_transfer" /> |
| | | <el-option label="ç°é" value="cash" /> |
| | | <el-option label="æ¯ç¥¨" value="check" /> |
| | | <el-option label="æ±ç¥¨" value="draft" /> |
| | | <el-option label="æ¯ä»å®" value="alipay" /> |
| | | <el-option label="微信" value="wechat" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¶è¡è´¦å·" prop="bankAccount" v-if="form.receiptMethod === 'bank_transfer'"> |
| | | <el-input v-model="form.bankAccount" placeholder="请è¾å
¥é¶è¡è´¦å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å
³èåæ®" prop="relatedDocs"> |
| | | <el-select v-model="form.relatedDocs" multiple placeholder="è¯·éæ©å
³èåæ®" style="width: 100%;"> |
| | | <el-option v-for="item in outList" :key="item.outCode" :label="item.outCode" :value="item.outCode" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "æ¶æ¬¾å", |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | receiptCode: "", |
| | | customerId: "", |
| | | receiptMethod: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "æ¶æ¬¾åå·", prop: "receiptCode", width: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { label: "æ¶æ¬¾æ¥æ", prop: "receiptDate", width: "120" }, |
| | | { label: "æ¶æ¬¾éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "æ¶æ¬¾æ¹å¼", prop: "receiptMethod", slot: "receiptMethod" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { label: "夿³¨", prop: "remark", showOverflowTooltip: true }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "220", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | |
| | | const customerList = [ |
| | | { id: 1, name: "åäº¬ç§ææéå
¬å¸" }, |
| | | { id: 2, name: "䏿µ·è´¸æå
¬å¸" }, |
| | | { id: 3, name: "广å·å®ä¸æéå
¬å¸" }, |
| | | { id: 4, name: "æ·±å³çµåå
¬å¸" }, |
| | | ]; |
| | | |
| | | const outList = [ |
| | | { outCode: "CK2024001", customerId: 1 }, |
| | | { outCode: "CK2024002", customerId: 2 }, |
| | | { outCode: "CK2024003", customerId: 3 }, |
| | | ]; |
| | | |
| | | const form = reactive({ |
| | | receiptCode: "", |
| | | customerId: "", |
| | | receiptDate: "", |
| | | amount: 0, |
| | | receiptMethod: "bank_transfer", |
| | | bankAccount: "", |
| | | relatedDocs: [], |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | customerId: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | | receiptDate: [{ required: true, message: "è¯·éæ©æ¶æ¬¾æ¥æ", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥æ¶æ¬¾éé¢", trigger: "blur" }], |
| | | receiptMethod: [{ required: true, message: "è¯·éæ©æ¶æ¬¾æ¹å¼", trigger: "change" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, receiptCode: "SK2024001", customerId: 1, customerName: "åäº¬ç§ææéå
¬å¸", receiptDate: "2024-01-16", amount: 3000, receiptMethod: "bank_transfer", status: "confirmed", relatedDocs: ["CK2024001"], remark: "" }, |
| | | { id: 2, receiptCode: "SK2024002", customerId: 2, customerName: "䏿µ·è´¸æå
¬å¸", receiptDate: "2024-01-18", amount: 5000, receiptMethod: "cash", status: "pending", relatedDocs: ["CK2024002"], remark: "" }, |
| | | { id: 3, receiptCode: "SK2024003", customerId: 3, customerName: "广å·å®ä¸æéå
¬å¸", receiptDate: "2024-01-20", amount: 8000, receiptMethod: "alipay", status: "confirmed", relatedDocs: ["CK2024003"], remark: "" }, |
| | | ]; |
| | | |
| | | const totalReceiptAmount = computed(() => { |
| | | return dataList.value.reduce((sum, item) => sum + Number(item.amount), 0); |
| | | }); |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getReceiptMethodLabel = (method) => { |
| | | const map = { |
| | | bank_transfer: "é¶è¡è½¬è´¦", |
| | | cash: "ç°é", |
| | | check: "æ¯ç¥¨", |
| | | draft: "æ±ç¥¨", |
| | | alipay: "æ¯ä»å®", |
| | | wechat: "微信", |
| | | }; |
| | | return map[method] || method; |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.receiptCode) { |
| | | result = result.filter(item => item.receiptCode.includes(filters.receiptCode)); |
| | | } |
| | | if (filters.customerId) { |
| | | result = result.filter(item => item.customerId === filters.customerId); |
| | | } |
| | | if (filters.receiptMethod) { |
| | | result = result.filter(item => item.receiptMethod === filters.receiptMethod); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.receiptCode = ""; |
| | | filters.customerId = ""; |
| | | filters.receiptMethod = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ°å¢æ¶æ¬¾"; |
| | | Object.assign(form, { |
| | | receiptCode: "SK" + Date.now().toString().slice(-8), |
| | | customerId: "", |
| | | receiptDate: new Date().toISOString().split('T')[0], |
| | | amount: 0, |
| | | receiptMethod: "bank_transfer", |
| | | bankAccount: "", |
| | | relatedDocs: [], |
| | | remark: "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾æ¶æ¬¾"; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥çæ¶æ¬¾å: ${row.receiptCode}`); |
| | | }; |
| | | |
| | | const handleConfirm = (row) => { |
| | | ElMessageBox.confirm("ç¡®è®¤è¯¥æ¶æ¬¾ååï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "confirmed"; |
| | | } |
| | | ElMessage.success("确认æå"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm("确认å é¤è¯¥æ¶æ¬¾ååï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const customer = customerList.find(item => item.id === form.customerId); |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, customerName: customer?.name }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, customerName: customer?.name, status: "pending" }); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="客æ·:"> |
| | | <el-select v-model="filters.customerId" placeholder="è¯·éæ©å®¢æ·" clearable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="对账æé´:"> |
| | | <el-date-picker v-model="filters.startMonth" type="month" placeholder="å¼å§æä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <span style="margin: 0 10px;">è³</span> |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="ç»ææä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-button type="primary" @click="generateStatement" icon="Document">çæå¯¹è´¦å</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button @click="handleOut" icon="Download">导åºå¯¹è´¦å</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | <template #beginBalance="{ row }"> |
| | | <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.beginBalance) }}</span> |
| | | </template> |
| | | <template #currentReceivable="{ row }"> |
| | | <span class="text-primary">Â¥{{ formatMoney(row.currentReceivable) }}</span> |
| | | </template> |
| | | <template #currentReceipt="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.currentReceipt) }}</span> |
| | | </template> |
| | | <template #endBalance="{ row }"> |
| | | <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.endBalance) }}</span> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="viewDetail(row)">æ¥çæç»</el-button> |
| | | <el-button type="primary" link @click="printStatement(row)">æå°</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog title="对账æç»" v-model="detailDialogVisible" width="900px" @confirm="printDetail" @cancel="detailDialogVisible = false" operationType="detail"> |
| | | <div class="statement-header"> |
| | | <h3>{{ currentCustomer }} åºæ¶å¯¹è´¦å</h3> |
| | | <p>对账æé´: {{ currentPeriod }}</p> |
| | | </div> |
| | | <el-table :data="detailData" border style="width: 100%"> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="type" label="ç±»å" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.type === 'åºåº' ? 'success' : row.type === 'éè´§' ? 'danger' : 'primary'">{{ row.type }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="code" label="åæ®ç¼å·" width="150" /> |
| | | <el-table-column prop="debit" label="åæ¹(åºæ¶)" width="120"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.debit > 0" class="text-danger">Â¥{{ formatMoney(row.debit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="credit" label="è´·æ¹(æ¶æ¬¾)" width="120"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.credit > 0" class="text-success">Â¥{{ formatMoney(row.credit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="balance" label="ä½é¢" width="120"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.balance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.balance) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" show-overflow-tooltip /> |
| | | </el-table> |
| | | <template #footer> |
| | | <el-button type="primary" @click="printDetail">æå°</el-button> |
| | | <el-button @click="detailDialogVisible = false">å
³é</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <FormDialog title="çæå¯¹è´¦å" v-model="generateDialogVisible" width="1000px" @confirm="confirmGenerate" @cancel="generateDialogVisible = false"> |
| | | <el-form :model="generateForm" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="鿩客æ·" prop="customerId"> |
| | | <el-select v-model="generateForm.customerId" placeholder="è¯·éæ©å®¢æ·" style="width: 100%;" @change="onCustomerChange"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="对账æä»½" prop="period"> |
| | | <el-date-picker v-model="generateForm.period" type="month" placeholder="éæ©æä»½" value-format="YYYY-MM" style="width: 100%;" @change="onPeriodChange" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <div v-if="salesData.length > 0" class="sales-section"> |
| | | <div class="section-title">æ¬æé宿°æ®</div> |
| | | <el-table :data="salesData" border style="width: 100%; margin-bottom: 15px;" v-loading="salesLoading" @selection-change="handleSalesSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="code" label="åæ®ç¼å·" width="150" /> |
| | | <el-table-column prop="type" label="ç±»å" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.type === 'åºåº' ? 'success' : row.type === 'æ¶æ¬¾' ? 'primary' : 'danger'">{{ row.type }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="amount" label="éé¢" width="120"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.type === 'åºåº' ? 'text-primary' : row.type === 'æ¶æ¬¾' ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" /> |
| | | </el-table> |
| | | |
| | | <div class="summary-row"> |
| | | <span>æåä½é¢: <strong class="text-primary">Â¥{{ formatMoney(generateForm.beginBalance) }}</strong></span> |
| | | <span>æ¬æåºæ¶: <strong class="text-primary">Â¥{{ formatMoney(generateForm.currentReceivable) }}</strong></span> |
| | | <span>æ¬ææ¶æ¬¾: <strong class="text-success">Â¥{{ formatMoney(generateForm.currentReceipt) }}</strong></span> |
| | | <span>ææ«ä½é¢: <strong :class="calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt) >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt)) }}</strong></span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else-if="generateForm.customerId && !salesLoading" class="empty-tip"> |
| | | <el-empty description="è¯¥å®¢æ·æ¬æææ é宿°æ®" /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate">确认çæ</el-button> |
| | | <el-button @click="generateDialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "åºæ¶å¯¹è´¦", |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | customerId: "", |
| | | startMonth: "", |
| | | endMonth: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "对账åå·", prop: "statementCode", width: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { label: "对账æé´", prop: "period", width: "150" }, |
| | | { label: "æåä½é¢", prop: "beginBalance", slot: "beginBalance" }, |
| | | { label: "æ¬æåºæ¶", prop: "currentReceivable", slot: "currentReceivable" }, |
| | | { label: "æ¬ææ¶æ¬¾", prop: "currentReceipt", slot: "currentReceipt" }, |
| | | { label: "ææ«ä½é¢", prop: "endBalance", slot: "endBalance" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "150", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const detailDialogVisible = ref(false); |
| | | const currentCustomer = ref(""); |
| | | const currentPeriod = ref(""); |
| | | const detailData = ref([]); |
| | | |
| | | const generateDialogVisible = ref(false); |
| | | const salesLoading = ref(false); |
| | | const salesData = ref([]); |
| | | const selectedSales = ref([]); |
| | | |
| | | const generateForm = reactive({ |
| | | customerId: "", |
| | | customerName: "", |
| | | period: "", |
| | | beginBalance: 0, |
| | | currentReceivable: 0, |
| | | currentReceipt: 0, |
| | | }); |
| | | |
| | | const canGenerate = computed(() => { |
| | | return generateForm.customerId && generateForm.period && selectedSales.value.length > 0; |
| | | }); |
| | | |
| | | const customerList = [ |
| | | { id: 1, name: "åäº¬ç§ææéå
¬å¸" }, |
| | | { id: 2, name: "䏿µ·è´¸æå
¬å¸" }, |
| | | { id: 3, name: "广å·å®ä¸æéå
¬å¸" }, |
| | | { id: 4, name: "æ·±å³çµåå
¬å¸" }, |
| | | ]; |
| | | |
| | | const mockData = [ |
| | | { id: 1, statementCode: "DZ202401001", customerId: 1, customerName: "åäº¬ç§ææéå
¬å¸", period: "2024-01", beginBalance: 10000, currentReceivable: 15000, currentReceipt: 8000, endBalance: 17000 }, |
| | | { id: 2, statementCode: "DZ202401002", customerId: 2, customerName: "䏿µ·è´¸æå
¬å¸", period: "2024-01", beginBalance: 5000, currentReceivable: 12000, currentReceipt: 10000, endBalance: 7000 }, |
| | | { id: 3, statementCode: "DZ202402001", customerId: 1, customerName: "åäº¬ç§ææéå
¬å¸", period: "2024-02", beginBalance: 17000, currentReceivable: 20000, currentReceipt: 15000, endBalance: 22000 }, |
| | | ]; |
| | | |
| | | const calculateEndBalance = (beginBalance, currentReceivable, currentReceipt) => { |
| | | return beginBalance + currentReceivable - currentReceipt; |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.customerId) { |
| | | result = result.filter(item => item.customerId === filters.customerId); |
| | | } |
| | | if (filters.startMonth && filters.endMonth) { |
| | | result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.customerId = ""; |
| | | filters.startMonth = ""; |
| | | filters.endMonth = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const generateStatement = () => { |
| | | generateForm.customerId = ""; |
| | | generateForm.customerName = ""; |
| | | generateForm.period = ""; |
| | | generateForm.beginBalance = 0; |
| | | generateForm.currentReceivable = 0; |
| | | generateForm.currentReceipt = 0; |
| | | salesData.value = []; |
| | | selectedSales.value = []; |
| | | generateDialogVisible.value = true; |
| | | }; |
| | | |
| | | const onCustomerChange = (customerId) => { |
| | | const customer = customerList.find(item => item.id === customerId); |
| | | if (customer) { |
| | | generateForm.customerName = customer.name; |
| | | } |
| | | loadSalesData(); |
| | | }; |
| | | |
| | | const onPeriodChange = () => { |
| | | loadSalesData(); |
| | | }; |
| | | |
| | | const loadSalesData = () => { |
| | | if (!generateForm.customerId || !generateForm.period) { |
| | | salesData.value = []; |
| | | return; |
| | | } |
| | | |
| | | salesLoading.value = true; |
| | | |
| | | setTimeout(() => { |
| | | const mockSalesData = [ |
| | | { id: 1, date: generateForm.period + "-03", code: "CK2024001", type: "åºåº", amount: 8000, remark: "产åAéå®" }, |
| | | { id: 2, date: generateForm.period + "-08", code: "SK2024001", type: "æ¶æ¬¾", amount: 5000, remark: "客æ·å款" }, |
| | | { id: 3, date: generateForm.period + "-12", code: "CK2024002", type: "åºåº", amount: 12000, remark: "产åBéå®" }, |
| | | { id: 4, date: generateForm.period + "-15", code: "TH2024001", type: "éè´§", amount: 2000, remark: "è´¨éé®é¢éè´§" }, |
| | | { id: 5, date: generateForm.period + "-20", code: "CK2024003", type: "åºåº", amount: 5000, remark: "产åCéå®" }, |
| | | { id: 6, date: generateForm.period + "-25", code: "SK2024002", type: "æ¶æ¬¾", amount: 8000, remark: "客æ·å款" }, |
| | | ]; |
| | | |
| | | salesData.value = mockSalesData; |
| | | |
| | | const lastPeriod = getLastPeriod(generateForm.period); |
| | | const lastStatement = mockData.find(item => |
| | | item.customerId === generateForm.customerId && item.period === lastPeriod |
| | | ); |
| | | generateForm.beginBalance = lastStatement ? lastStatement.endBalance : 0; |
| | | |
| | | calculateSummary(); |
| | | |
| | | salesLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | const getLastPeriod = (period) => { |
| | | const [year, month] = period.split("-").map(Number); |
| | | if (month === 1) { |
| | | return `${year - 1}-12`; |
| | | } |
| | | return `${year}-${String(month - 1).padStart(2, "0")}`; |
| | | }; |
| | | |
| | | const calculateSummary = () => { |
| | | let receivable = 0; |
| | | let receipt = 0; |
| | | |
| | | selectedSales.value.forEach(item => { |
| | | if (item.type === "åºåº") { |
| | | receivable += item.amount; |
| | | } else if (item.type === "éè´§") { |
| | | receivable -= item.amount; |
| | | } else if (item.type === "æ¶æ¬¾") { |
| | | receipt += item.amount; |
| | | } |
| | | }); |
| | | |
| | | generateForm.currentReceivable = receivable; |
| | | generateForm.currentReceipt = receipt; |
| | | }; |
| | | |
| | | const handleSalesSelectionChange = (selection) => { |
| | | selectedSales.value = selection; |
| | | calculateSummary(); |
| | | }; |
| | | |
| | | const confirmGenerate = () => { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | const endBalance = calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt); |
| | | |
| | | mockData.unshift({ |
| | | id: newId, |
| | | statementCode: "DZ" + Date.now(), |
| | | customerId: generateForm.customerId, |
| | | customerName: generateForm.customerName, |
| | | period: generateForm.period, |
| | | beginBalance: generateForm.beginBalance, |
| | | currentReceivable: generateForm.currentReceivable, |
| | | currentReceipt: generateForm.currentReceipt, |
| | | endBalance, |
| | | }); |
| | | |
| | | generateDialogVisible.value = false; |
| | | ElMessage.success("对账åçææå"); |
| | | getTableData(); |
| | | }; |
| | | |
| | | const viewDetail = (row) => { |
| | | currentCustomer.value = row.customerName; |
| | | currentPeriod.value = row.period; |
| | | |
| | | const saleOutAmount = Math.floor(row.currentReceivable * 0.6); |
| | | const returnAmount = Math.floor(row.currentReceivable * 0.1); |
| | | const firstReceipt = Math.floor(row.currentReceipt * 0.4); |
| | | const secondReceipt = row.currentReceipt - firstReceipt; |
| | | |
| | | let runningBalance = row.beginBalance; |
| | | |
| | | detailData.value = [ |
| | | { date: row.period + "-01", type: "æå", code: "-", debit: 0, credit: 0, balance: runningBalance, remark: "æåä½é¢" }, |
| | | { date: row.period + "-05", type: "åºåº", code: "CK2024001", debit: saleOutAmount, credit: 0, balance: runningBalance += saleOutAmount, remark: "éå®åºåº" }, |
| | | { date: row.period + "-10", type: "æ¶æ¬¾", code: "SK2024001", debit: 0, credit: firstReceipt, balance: runningBalance -= firstReceipt, remark: "客æ·å款" }, |
| | | { date: row.period + "-15", type: "åºåº", code: "CK2024002", debit: row.currentReceivable - saleOutAmount - returnAmount, credit: 0, balance: runningBalance += (row.currentReceivable - saleOutAmount - returnAmount), remark: "éå®åºåº" }, |
| | | { date: row.period + "-20", type: "éè´§", code: "TH2024001", debit: 0, credit: returnAmount, balance: runningBalance -= returnAmount, remark: "éå®éè´§" }, |
| | | { date: row.period + "-25", type: "æ¶æ¬¾", code: "SK2024002", debit: 0, credit: secondReceipt, balance: runningBalance -= secondReceipt, remark: "客æ·å款" }, |
| | | ]; |
| | | |
| | | detailDialogVisible.value = true; |
| | | }; |
| | | |
| | | const printStatement = (row) => { |
| | | ElMessage.info(`æå°å¯¹è´¦å: ${row.statementCode}`); |
| | | }; |
| | | |
| | | const printDetail = () => { |
| | | ElMessage.info("æå°æç»"); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | } |
| | | |
| | | .statement-header { |
| | | text-align: center; |
| | | margin-bottom: 20px; |
| | | h3 { |
| | | margin: 0 0 10px 0; |
| | | } |
| | | p { |
| | | color: #909399; |
| | | margin: 0; |
| | | } |
| | | } |
| | | |
| | | .sales-section { |
| | | margin-top: 20px; |
| | | |
| | | .section-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | margin-bottom: 15px; |
| | | padding-left: 10px; |
| | | border-left: 4px solid #409eff; |
| | | } |
| | | } |
| | | |
| | | .summary-row { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | padding: 15px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | margin-top: 15px; |
| | | |
| | | span { |
| | | font-size: 14px; |
| | | |
| | | strong { |
| | | font-size: 16px; |
| | | margin-left: 5px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .empty-tip { |
| | | margin-top: 30px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <!-- éå®åºåº --> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="åºåºåå·:"> |
| | | <el-input v-model="filters.outboundBatches" placeholder="请è¾å
¥åºåºåå·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°:"> |
| | | <el-input v-model="filters.customerName" placeholder="请è¾å
¥å®¢æ·åç§°" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="åºåºæ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listPageAccountSales } from "@/api/financialManagement/accountSales"; |
| | | |
| | | defineOptions({ |
| | | name: "éå®åºåº", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const filters = reactive({ |
| | | outboundBatches: "", |
| | | customerName: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "åºåºåå·", prop: "outboundBatches", minWidth: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", minWidth: "180" }, |
| | | { label: "åºåºæ¥æ", prop: "shippingDate", width: "170" }, |
| | | { label: "产ååç§°", prop: "productName", minWidth: "140" }, |
| | | { label: "产åè§æ ¼", prop: "specificationModel", minWidth: "140" }, |
| | | { |
| | | label: "éé¢", |
| | | prop: "outboundAmount", |
| | | minWidth: "120", |
| | | align: "right", |
| | | formatData: (val) => (val === null || val === undefined || val === "" ? "" : Number(val).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })), |
| | | }, |
| | | { label: "åè´§ç¼å·", prop: "shippingNo", minWidth: "140" }, |
| | | { label: "éå®è®¢åå·", prop: "salesContractNo", minWidth: "150" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | function buildFilterParams() { |
| | | const params = { |
| | | outboundBatches: filters.outboundBatches || undefined, |
| | | customerName: filters.customerName || undefined, |
| | | }; |
| | | if (filters.dateRange && filters.dateRange.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | return params; |
| | | } |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | listPageAccountSales({ |
| | | ...buildFilterParams(), |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }) |
| | | .then((res) => { |
| | | const ok = res.code === 200 || res.code === 0; |
| | | if (ok && res.data) { |
| | | pagination.total = res.data.total ?? 0; |
| | | dataList.value = res.data.records ?? []; |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | dataList.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.outboundBatches = ""; |
| | | filters.customerName = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "/accountSales/exportAccountSalesOutbound", |
| | | buildFilterParams(), |
| | | `éå®åºåº_${new Date().getTime()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <!-- éå®éè´§ --> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="éè´§åå·:"> |
| | | <el-input v-model="filters.returnNo" placeholder="请è¾å
¥éè´§åå·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°:"> |
| | | <el-input v-model="filters.customerName" placeholder="请è¾å
¥å®¢æ·åç§°" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="éè´§æ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listPageAccountSalesReturn } from "@/api/financialManagement/accountSales"; |
| | | |
| | | defineOptions({ |
| | | name: "éå®éè´§", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const filters = reactive({ |
| | | returnNo: "", |
| | | customerName: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "éè´§åå·", prop: "returnNo", minWidth: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", minWidth: "180" }, |
| | | { label: "å
³èåè´§åå·", prop: "shippingNo", minWidth: "150" }, |
| | | { label: "éè´§æ¥æ", prop: "makeTime", minWidth: "170" }, |
| | | { |
| | | label: "鿬¾æ»é¢", |
| | | prop: "refundAmount", |
| | | minWidth: "120", |
| | | align: "right", |
| | | formatData: (val) => |
| | | val === null || val === undefined || val === "" |
| | | ? "" |
| | | : Number(val).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }), |
| | | }, |
| | | { label: "éè´§åå ", prop: "returnReason", minWidth: "150", showOverflowTooltip: true }, |
| | | { label: "éå®è®¢åå·", prop: "salesContractNo", minWidth: "150" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | function buildFilterParams() { |
| | | const params = { |
| | | returnNo: filters.returnNo || undefined, |
| | | customerName: filters.customerName || undefined, |
| | | }; |
| | | if (filters.dateRange && filters.dateRange.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | return params; |
| | | } |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | listPageAccountSalesReturn({ |
| | | ...buildFilterParams(), |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }) |
| | | .then((res) => { |
| | | const ok = res.code === 200 || res.code === 0; |
| | | if (ok && res.data) { |
| | | pagination.total = res.data.total ?? 0; |
| | | dataList.value = res.data.records ?? []; |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | dataList.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.returnNo = ""; |
| | | filters.customerName = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "/accountSales/exportAccountSalesReturn", |
| | | buildFilterParams(), |
| | | `éå®éè´§_${new Date().getTime()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | </style> |
| | |
| | | </el-form-item> |
| | | <el-form-item label="æ¶æ¬¾æ¹å¼:"> |
| | | <el-select |
| | | v-model="filters.incomeMethod" |
| | | v-model="filters.incomeMethodLabel" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | style="width: 200px;" |
| | | > |
| | | <el-option |
| | | v-for="item in payment_methods" |
| | | v-for="item in incomeMethodOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #incomeMethodSlot="{ row }"> |
| | | <el-tag> |
| | | {{ getIncomeMethodLabel(row) }} |
| | | </el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button |
| | | type="primary" |
| | |
| | | </PIMTable> |
| | | </div> |
| | | <Modal ref="modalRef" @success="getTableData"></Modal> |
| | | <FileListDialog |
| | | ref="fileListRef" |
| | | v-model="fileListDialogVisible" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :upload-method="handleUpload" |
| | | :delete-method="handleFileDelete" |
| | | /> |
| | | <FileListDialog v-if="fileListDialogVisible" :record-id="currentRecordId" record-type="account_income" v-model:visible="fileListDialogVisible"/> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { usePaginationApi } from "@/hooks/usePaginationApi"; |
| | | import { listPage, delAccountIncome, fileListPage, fileAdd, fileDel } from "@/api/financialManagement/revenueManagement"; |
| | | import {listPage, delAccountIncome} from "@/api/financialManagement/revenueManagement"; |
| | | import { onMounted, getCurrentInstance, ref, computed } from "vue"; |
| | | import Modal from "./Modal.vue"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import FileListDialog from "@/components/Dialog/FileListDialog.vue"; |
| | | import request from "@/utils/request"; |
| | | import { getToken } from "@/utils/auth"; |
| | | |
| | | defineOptions({ |
| | | name: "æ¶å
¥ç®¡ç", |
| | |
| | | const { proxy } = getCurrentInstance(); |
| | | const modalRef = ref(); |
| | | const { payment_methods } = proxy.useDict("payment_methods"); |
| | | const {receipt_payment_type} = proxy.useDict("receipt_payment_type"); |
| | | const { income_types } = proxy.useDict("income_types"); |
| | | const fileListRef = ref(null); |
| | | const fileListDialogVisible = ref(false); |
| | | const currentFileRow = ref(null); |
| | | const accountType = ref('æ¶å
¥'); |
| | | const currentRecordId = ref(0); |
| | | |
| | | const incomeMethodOptions = computed(() => { |
| | | const merged = [...(payment_methods.value || []), ...(receipt_payment_type.value || [])]; |
| | | const uniqueMap = new Map(); |
| | | merged.forEach((item) => { |
| | | const label = item?.label; |
| | | if (!label) return; |
| | | if (!uniqueMap.has(label)) { |
| | | uniqueMap.set(label, {label, value: label}); |
| | | } |
| | | }); |
| | | return Array.from(uniqueMap.values()); |
| | | }); |
| | | |
| | | const { |
| | | filters, |
| | |
| | | } = usePaginationApi( |
| | | listPage, |
| | | { |
| | | incomeMethod: undefined, |
| | | incomeMethodLabel: undefined, |
| | | entryDate: undefined, |
| | | }, |
| | | [ |
| | |
| | | }, |
| | | { |
| | | label: "æ¶æ¬¾æ¹å¼", |
| | | prop: "incomeMethod", |
| | | prop: "incomeMethodLabel", |
| | | align: 'center', |
| | | width: '100', |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | if (payment_methods.value.find((m) => m.value == params)) { |
| | | return payment_methods.value.find((m) => m.value == params).label; |
| | | } else { |
| | | return null |
| | | } |
| | | }, |
| | | dataType: "slot", |
| | | slot: "incomeMethodSlot", |
| | | }, |
| | | { |
| | | label: "å票å·ç ", |
| | |
| | | align: "center", |
| | | width: "160px", |
| | | }, |
| | | ] |
| | | ], |
| | | undefined, |
| | | { |
| | | incomeMethodLabel: (value) => ({ |
| | | incomeMethodLabel: value || undefined, |
| | | }), |
| | | } |
| | | ); |
| | | |
| | | // è¡¨æ ¼åè®¡ï¼æ¶å
¥éé¢ |
| | | const summarizeMainTable = (param) => { |
| | | return proxy.summarizeTable(param, ["incomeMoney"]); |
| | | }; |
| | | |
| | | const getIncomeMethodLabel = (row) => { |
| | | const methodValue = row?.incomeMethod; |
| | | const dictList = String(row?.businessType) === "1" |
| | | ? receipt_payment_type.value |
| | | : payment_methods.value; |
| | | return dictList.find((item) => item.value == methodValue)?.label || "--"; |
| | | }; |
| | | |
| | | // å¤éååä»ä¹ |
| | |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = async (row) => { |
| | | currentFileRow.value = row; |
| | | accountType.value = 'æ¶å
¥'; |
| | | try { |
| | | const res = await fileListPage({ |
| | | accountId: row.id, |
| | | accountType: accountType.value, |
| | | current: 1, |
| | | size: 100 |
| | | }); |
| | | if (res.code === 200 && fileListRef.value) { |
| | | // å°æ°æ®è½¬æ¢ä¸º FileListDialog éè¦çæ ¼å¼ |
| | | const fileList = (res.data?.records || []).map(item => ({ |
| | | name: item.name, |
| | | url: item.url, |
| | | id: item.id, |
| | | ...item |
| | | })); |
| | | fileListRef.value.open(fileList); |
| | | currentRecordId.value = row.id; |
| | | fileListDialogVisible.value = true; |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("è·åéä»¶å表失败"); |
| | | } |
| | | }; |
| | | |
| | | // ä¸ä¼ éä»¶ |
| | | const handleUpload = async () => { |
| | | if (!currentFileRow.value) { |
| | | proxy.$modal.msgWarning("请å
éæ©æ°æ®"); |
| | | return null; |
| | | } |
| | | |
| | | return new Promise((resolve) => { |
| | | // å建ä¸ä¸ªéèçæä»¶è¾å
¥å
ç´ |
| | | const input = document.createElement('input'); |
| | | input.type = 'file'; |
| | | input.style.display = 'none'; |
| | | input.onchange = async (e) => { |
| | | const file = e.target.files[0]; |
| | | if (!file) { |
| | | resolve(null); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // ä½¿ç¨ FormData ä¸ä¼ æä»¶ |
| | | const formData = new FormData(); |
| | | formData.append('file', file); |
| | | |
| | | const uploadRes = await request({ |
| | | url: '/file/upload', |
| | | method: 'post', |
| | | data: formData, |
| | | headers: { |
| | | 'Content-Type': 'multipart/form-data', |
| | | Authorization: `Bearer ${getToken()}` |
| | | } |
| | | }); |
| | | |
| | | if (uploadRes.code === 200) { |
| | | // ä¿åéä»¶ä¿¡æ¯ |
| | | const fileData = { |
| | | accountId: currentFileRow.value.id, |
| | | accountType: accountType.value, |
| | | name: uploadRes.data.originalName || file.name, |
| | | url: uploadRes.data.tempPath || uploadRes.data.url |
| | | }; |
| | | |
| | | const saveRes = await fileAdd(fileData); |
| | | if (saveRes.code === 200) { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | // éæ°å è½½æä»¶å表 |
| | | const listRes = await fileListPage({ |
| | | accountId: currentFileRow.value.id, |
| | | accountType: accountType.value, |
| | | current: 1, |
| | | size: 100 |
| | | }); |
| | | if (listRes.code === 200 && fileListRef.value) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | | name: item.name, |
| | | url: item.url, |
| | | id: item.id, |
| | | ...item |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | } |
| | | // è¿åæ°æä»¶ä¿¡æ¯ |
| | | resolve({ |
| | | name: fileData.name, |
| | | url: fileData.url, |
| | | id: saveRes.data?.id |
| | | }); |
| | | } else { |
| | | proxy.$modal.msgError(saveRes.msg || "æä»¶ä¿å失败"); |
| | | resolve(null); |
| | | } |
| | | } else { |
| | | proxy.$modal.msgError(uploadRes.msg || "æä»¶ä¸ä¼ 失败"); |
| | | resolve(null); |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | resolve(null); |
| | | } finally { |
| | | document.body.removeChild(input); |
| | | } |
| | | }; |
| | | |
| | | document.body.appendChild(input); |
| | | input.click(); |
| | | }); |
| | | }; |
| | | |
| | | // å é¤éä»¶ |
| | | const handleFileDelete = async (row) => { |
| | | try { |
| | | const res = await fileDel([row.id]); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | // éæ°å è½½æä»¶å表 |
| | | if (currentFileRow.value && fileListRef.value) { |
| | | const listRes = await fileListPage({ |
| | | accountId: currentFileRow.value.id, |
| | | accountType: accountType.value, |
| | | current: 1, |
| | | size: 100 |
| | | }); |
| | | if (listRes.code === 200) { |
| | | const fileList = (listRes.data?.records || []).map(item => ({ |
| | | name: item.name, |
| | | url: item.url, |
| | | id: item.id, |
| | | ...item |
| | | })); |
| | | fileListRef.value.setList(fileList); |
| | | } |
| | | } |
| | | return true; // è¿å true 表示å 餿åï¼ç»ä»¶ä¼æ´æ°å表 |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "å é¤å¤±è´¥"); |
| | | return false; |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("å é¤å¤±è´¥"); |
| | | return false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | |
| | | <template> |
| | | <el-dialog v-model="visible" title="æ¶æ¬¾/鿬¾" width="90%" append-to-body> |
| | | <FormDialog v-model="visible" title="æ¶æ¬¾/鿬¾" width="90%" @confirm="submit" @cancel="visible=false"> |
| | | <div class="section"> |
| | | <div class="section-title descriptions">åºç¡èµæ</div> |
| | | <el-form :model="form" label-width="100px"> |
| | |
| | | <el-button type="primary" @click="submit">确认</el-button> |
| | | <el-button @click="visible=false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue'; |
| | | import { getToken } from '@/utils/auth'; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | |
| | | const visible = ref(false); |
| | | const form = ref({ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container ledger-page"> |
| | | <div class="ledger-layout"> |
| | | <aside class="subject-panel"> |
| | | <el-input v-model="subjectKeyword" placeholder="请è¾å
¥ç§ç®åç§°/ç¼å·" clearable prefix-icon="Search" /> |
| | | <el-scrollbar class="subject-tree-scroll"> |
| | | <el-tree |
| | | ref="subjectTreeRef" |
| | | :data="subjectOptions" |
| | | node-key="code" |
| | | :props="{ label: 'name', children: 'children' }" |
| | | highlight-current |
| | | default-expand-all |
| | | :expand-on-click-node="false" |
| | | :filter-node-method="filterSubjectNode" |
| | | @node-click="handleSubjectClick" |
| | | > |
| | | <template #default="{ data }"> |
| | | <span class="subject-node">{{ data.code }} {{ data.name }}</span> |
| | | </template> |
| | | </el-tree> |
| | | </el-scrollbar> |
| | | </aside> |
| | | |
| | | <section class="ledger-content"> |
| | | <el-form :model="filters" :inline="true" class="filter-form"> |
| | | <el-form-item label="æé´:"> |
| | | <el-date-picker v-model="filters.startMonth" type="month" placeholder="å¼å§æä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <span style="margin: 0 10px;">è³</span> |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="ç»ææä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æ¥è¯¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | <!-- <el-button @click="handlePrint" icon="Printer">æå°</el-button>--> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="table_list"> |
| | | <el-table :data="dataList" border style="width: 100%"> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="voucherNo" label="åè¯åå·" width="120" /> |
| | | <el-table-column prop="summary" label="æè¦" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column prop="debit" label="åæ¹" width="150"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.debit > 0" class="text-danger">Â¥{{ formatMoney(row.debit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="credit" label="è´·æ¹" width="150"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.credit > 0" class="text-success">Â¥{{ formatMoney(row.credit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æ¹å" width="80"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.direction === 'å' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä½é¢" width="150"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">Â¥{{ formatMoney(Math.abs(row.balance)) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <el-empty v-if="!currentSubject" description="è¯·éæ©ä¼è®¡ç§ç®æ¥è¯¢" style="margin-top: 50px;" /> |
| | | </section> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed, watch, nextTick } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listAccountSubject } from "@/api/financialManagement/accountSubject"; |
| | | import { getDetailLedger } from "@/api/financialManagement/ledger"; |
| | | |
| | | defineOptions({ |
| | | name: "ç§ç®æç»è´¦", |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | subject: "", |
| | | startMonth: "", |
| | | endMonth: "", |
| | | }); |
| | | |
| | | const dataList = ref([]); |
| | | const subjectOptions = ref([]); |
| | | const subjectKeyword = ref(""); |
| | | const subjectTreeRef = ref(); |
| | | |
| | | const getPreviousMonth = () => { |
| | | const date = new Date(); |
| | | date.setDate(1); |
| | | date.setMonth(date.getMonth() - 1); |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | return `${year}-${month}`; |
| | | }; |
| | | |
| | | const defaultMonth = getPreviousMonth(); |
| | | filters.startMonth = defaultMonth; |
| | | filters.endMonth = defaultMonth; |
| | | |
| | | const fallbackSubjects = [ |
| | | { code: "1122", name: "åºæ¶è´¦æ¬¾" }, |
| | | { code: "2202", name: "åºä»è´¦æ¬¾" }, |
| | | { code: "6602", name: "管çè´¹ç¨" }, |
| | | ]; |
| | | |
| | | const toTree = (nodes = []) => |
| | | nodes |
| | | .filter(item => item.subjectCode && item.subjectName) |
| | | .map(item => ({ |
| | | code: item.subjectCode, |
| | | name: item.subjectName, |
| | | children: toTree(item.children || []), |
| | | })); |
| | | |
| | | const findSubject = (options, code) => { |
| | | for (const item of options) { |
| | | if (item.code === code) return item; |
| | | if (item.children && item.children.length > 0) { |
| | | const found = findSubject(item.children, code); |
| | | if (found) return found; |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const currentSubject = computed(() => { |
| | | if (!filters.subject) return null; |
| | | return findSubject(subjectOptions.value, filters.subject); |
| | | }); |
| | | |
| | | const getFirstSubjectCode = (nodes = []) => { |
| | | for (const item of nodes) { |
| | | if (item.code) return item.code; |
| | | if (item.children && item.children.length > 0) { |
| | | const childCode = getFirstSubjectCode(item.children); |
| | | if (childCode) return childCode; |
| | | } |
| | | } |
| | | return ""; |
| | | }; |
| | | |
| | | const setDefaultSubjectSelection = async () => { |
| | | const firstCode = getFirstSubjectCode(subjectOptions.value); |
| | | if (!firstCode) { |
| | | filters.subject = ""; |
| | | subjectTreeRef.value?.setCurrentKey(null); |
| | | return; |
| | | } |
| | | filters.subject = firstCode; |
| | | await nextTick(); |
| | | subjectTreeRef.value?.setCurrentKey(firstCode); |
| | | }; |
| | | |
| | | const filterSubjectNode = (value, data) => { |
| | | const keyword = value?.trim(); |
| | | if (!keyword) return true; |
| | | return `${data.code}${data.name}`.includes(keyword); |
| | | }; |
| | | |
| | | watch(subjectKeyword, (value) => { |
| | | subjectTreeRef.value?.filter(value || ""); |
| | | }); |
| | | |
| | | const handleSubjectClick = async (data) => { |
| | | filters.subject = data.code; |
| | | await getTableData(); |
| | | }; |
| | | |
| | | const loadSubjectOptions = async () => { |
| | | let options = []; |
| | | try { |
| | | const { data } = await listAccountSubject({ |
| | | current: 1, |
| | | size: 1000, |
| | | }); |
| | | options = toTree(data?.records || []); |
| | | } catch (error) { |
| | | // å
¨å±æ¦æªå¨å·²æç¤ºï¼ä¸é¢èµ°å
åºç§ç® |
| | | } |
| | | if (options.length === 0) { |
| | | options = fallbackSubjects.map(item => ({ ...item, children: [] })); |
| | | } |
| | | subjectOptions.value = options; |
| | | await setDefaultSubjectSelection(); |
| | | if (filters.subject) { |
| | | await getTableData(); |
| | | } |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | // èè°çº¦å®ï¼æç»è´¦æç§ç®ä¸æé´è¿æ»¤ |
| | | const getTableData = async () => { |
| | | if (!currentSubject.value) { |
| | | dataList.value = []; |
| | | return; |
| | | } |
| | | try { |
| | | const { data } = await getDetailLedger({ |
| | | subjectCode: currentSubject.value.code, |
| | | startMonth: filters.startMonth, |
| | | endMonth: filters.endMonth, |
| | | }); |
| | | dataList.value = Array.isArray(data) ? data : data?.records || []; |
| | | } catch (error) { |
| | | // æç¤ºç±å
¨å±è¯·æ±æ¦æªå¨å¤çï¼è¿éä»
鲿¢æªæè·å¼å¸¸ |
| | | } |
| | | }; |
| | | |
| | | const resetFilters = async () => { |
| | | filters.startMonth = defaultMonth; |
| | | filters.endMonth = defaultMonth; |
| | | dataList.value = []; |
| | | subjectKeyword.value = ""; |
| | | subjectTreeRef.value?.filter(""); |
| | | await setDefaultSubjectSelection(); |
| | | if (filters.subject) { |
| | | await getTableData(); |
| | | } |
| | | }; |
| | | |
| | | const handlePrint = () => { |
| | | ElMessage.info("æå°åè½"); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | await loadSubjectOptions(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .ledger-layout { |
| | | display: flex; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .subject-panel { |
| | | width: 260px; |
| | | flex-shrink: 0; |
| | | padding: 12px; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 8px; |
| | | background-color: #fff; |
| | | } |
| | | |
| | | .subject-tree-scroll { |
| | | height: 600px; |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .subject-node { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .ledger-content { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .filter-form { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-warning { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .subject-panel :deep(.el-tree-node__content) { |
| | | height: 34px; |
| | | } |
| | | |
| | | .subject-panel :deep(.el-tree-node.is-current > .el-tree-node__content) { |
| | | background-color: #f0f7ff; |
| | | } |
| | | </style> |
| src/views/financialManagement/voucher/generalLedger.vue
src/views/financialManagement/voucher/index.vue
src/views/index.vue
src/views/inventoryManagement/dispatchLog/Record.vue
src/views/inventoryManagement/dispatchLog/index.vue
src/views/inventoryManagement/receiptManagement/Record.vue
src/views/inventoryManagement/receiptManagement/index.vue
src/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue
src/views/inventoryManagement/stockManagement/New.vue
src/views/inventoryManagement/stockManagement/Qualified.vue
src/views/inventoryManagement/stockManagement/Record.vue
src/views/inventoryManagement/stockManagement/Subtract.vue
src/views/inventoryManagement/stockReport/index.vue
src/views/lavorissue/ledger/filesDia.vue
src/views/lavorissue/statistics/index.vue
src/views/oaSystem/projectManagement/components/milestoneList.vue (已删除)
src/views/oaSystem/projectManagement/components/phaseGoalList.vue (已删除)
src/views/oaSystem/projectManagement/components/projectForm.vue (已删除)
src/views/oaSystem/projectManagement/components/taskTree.vue (已删除)
src/views/oaSystem/projectManagement/index.vue (已删除)
src/views/oaSystem/projectManagement/projectDetail.vue (已删除)
src/views/personnelManagement/contractManagement/filesDia.vue
src/views/personnelManagement/contractManagement/index.vue
src/views/personnelManagement/dimission/components/formDia.vue
src/views/personnelManagement/dimission/index.vue
src/views/personnelManagement/employeeRecord/index.vue
src/views/personnelManagement/socialSecuritySet/index.vue
src/views/procurementManagement/paymentEntry/index.vue
src/views/procurementManagement/paymentLedger/index.vue
src/views/procurementManagement/procurementInvoiceLedger/index.vue
src/views/procurementManagement/procurementLedger/fileList.vue
src/views/procurementManagement/procurementLedger/index.vue
src/views/procurementManagement/procurementReport/index.vue
src/views/procurementManagement/purchaseReturnOrder/New.vue
src/views/procurementManagement/purchaseReturnOrder/ProductList.vue
src/views/procurementManagement/purchaseReturnOrder/index.vue
src/views/productionManagement/processRoute/New.vue
src/views/productionManagement/processRoute/index.vue
src/views/productionManagement/processRoute/processRouteItem/index.vue
src/views/productionManagement/processStatistics/index.vue
src/views/productionManagement/productStructure/Detail/index.vue
src/views/productionManagement/productStructure/index.vue
src/views/productionManagement/productionCosting/index.vue
src/views/productionManagement/productionOrder/New.vue
src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue
src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue
src/views/productionManagement/productionOrder/index.vue
src/views/productionManagement/productionProcess/index.vue
src/views/productionManagement/productionReporting/index.vue
src/views/productionManagement/productionTraceability/index.vue
src/views/productionManagement/workOrder/components/filesDia.vue
src/views/productionManagement/workOrder/index.vue
src/views/productionManagement/workOrderEdit/index.vue
src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
src/views/productionManagement/workOrderManagement/components/filesDia.vue
src/views/productionManagement/workOrderManagement/index.vue
src/views/productionPlan/productionPlan/components/PIMTable.vue
src/views/productionPlan/productionPlan/index.vue
src/views/projectManagement/Management/components/formDia.vue
src/views/projectManagement/Management/index.vue
src/views/projectManagement/Management/projectDetail.vue
src/views/projectManagement/projectType/components/ProjectTypeDialog.vue
src/views/projectManagement/projectType/index.vue
src/views/qualityManagement/finalInspection/components/filesDia.vue
src/views/qualityManagement/finalInspection/index.vue
src/views/qualityManagement/nonconformingManagement/index.vue
src/views/qualityManagement/processInspection/components/filesDia.vue
src/views/qualityManagement/processInspection/components/formDia.vue
src/views/qualityManagement/processInspection/index.vue
src/views/qualityManagement/rawMaterialInspection/components/filesDia.vue
src/views/qualityManagement/rawMaterialInspection/index.vue
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
src/views/reportAnalysis/PSIDataAnalysis/index.vue
src/views/reportAnalysis/dataDashboard/components/basic/center-bottom.vue
src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue
src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
src/views/reportAnalysis/dataDashboard/components/basic/left-top.vue
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
src/views/reportAnalysis/dataDashboard/index.vue
src/views/reportAnalysis/dataDashboard/index0.vue
src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue
src/views/reportAnalysis/productionAnalysis/components/center-center.vue
src/views/reportAnalysis/productionAnalysis/components/center-top.vue
src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue
src/views/reportAnalysis/productionAnalysis/components/left-top.vue
src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue
src/views/reportAnalysis/productionAnalysis/components/right-top.vue
src/views/reportAnalysis/productionAnalysis/index.vue
src/views/safeProduction/accidentReportingRecord/index.vue
src/views/safeProduction/dangerInvestigation/index.vue
src/views/safeProduction/emergencyPlanReview/index.vue
src/views/safeProduction/hazardSourceLedger/index.vue
src/views/safeProduction/hazardousMaterialsControl/index.vue
src/views/safeProduction/safeQualifications/index.vue
src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue
src/views/safeProduction/safeWorkApproval/fileList.vue
src/views/safeProduction/safeWorkApproval/index.vue
src/views/safeProduction/safetyTrainingAssessment/index.vue
src/views/salesManagement/deliveryLedger/index.vue
src/views/salesManagement/invoiceLedger/index.vue
src/views/salesManagement/receiptPaymentLedger/index.vue
src/views/salesManagement/returnOrder/components/detailDia.vue
src/views/salesManagement/returnOrder/components/formDia.vue
src/views/salesManagement/returnOrder/index.vue
src/views/salesManagement/salesLedger/fileList.vue
src/views/salesManagement/salesLedger/index.vue
src/views/system/appVersion/index.vue
src/views/systemArchitecture/index.vue
src/views/tool/build/CodeTypeDialog.vue
vite.config.js |