已添加50个文件
已修改198个文件
已删除2个文件
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # æ¬å°æä»¶ä¸ä¼ 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> |
| | | ``` |
| | | |
| | | å¦æä½ çç®æ æ¯âå
ä¸ä¼ ï¼åè·ä¸å¡ä¸èµ·ä¿åâï¼è¿å¥åæ³å¯ä»¥ç´æ¥ä½ä¸ºåºç¡æ¨¡æ¿ä½¿ç¨ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | { |
| | | "compilerOptions": { |
| | | "baseUrl": ".", |
| | | "paths": { |
| | | "@/*": ["src/*"], |
| | | "~/*": ["./*"] |
| | | } |
| | | }, |
| | | "include": ["src/**/*.js", "src/**/*.vue", "vite.config.js"] |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 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 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 |
| | | }) |
| | | } |
| | | |
| | | |
| | | // æ°å¢å®¢æ·è·è¿ |
| | | export function addCustomerFollow(data) { |
| | | return request({ |
| | | url: '/basic/customer-follow/add', |
| | |
| | | }) |
| | | } |
| | | |
| | | // ä¿®æ¹å®¢æ·è·è¿ |
| | | export function updateCustomerFollow(data) { |
| | | return request({ |
| | | url: '/basic/customer-follow/edit', |
| | | method: 'put', |
| | | data: data, |
| | | }) |
| | | return request({ |
| | | url: '/basic/customer-follow/edit', |
| | | method: 'put', |
| | | data: data, |
| | | }) |
| | | } |
| | | // å é¤å®¢æ·è·è¿ |
| | | |
| | | export function delCustomerFollow(id) { |
| | | return request({ |
| | | url: '/basic/customer-follow/'+id, |
| | | url: '/basic/customer-follow/' + id, |
| | | method: 'delete', |
| | | }) |
| | | } |
| | | |
| | | // å访æé-æ°å¢/æ´æ° |
| | | export function addReturnVisit(data) { |
| | | return request({ |
| | | url: '/basic/customer-follow/return-visit', |
| | |
| | | data: data |
| | | }) |
| | | } |
| | | // è·åå访æé详æ
|
| | | |
| | | export function getReturnVisit(id) { |
| | | return request({ |
| | | url: '/basic/customer-follow/return-visit/' + id, |
| | | method: 'get' |
| | | }) |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // åæ°ç»´æ¤é¡µé¢æ¥å£ |
| | | 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", |
| | | }); |
| | | } |
| | |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | export function productModelListByUrl(url, query) { |
| | | return request({ |
| | | url, |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | |
| | | 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 |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // 审æ¹ç®¡çé
ç½® |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢å®¡æ¹æµç¨é
ç½®èç¹å表 |
| | | export function getApproveProcessConfigNodeList(type) { |
| | | return request({ |
| | | url: '/approveProcessConfigNode/list', |
| | | method: 'get', |
| | | params: { type }, |
| | | }) |
| | | } |
| | | |
| | | // æ°å¢å®¡æ¹æµç¨é
ç½®èç¹ |
| | | export function addApproveProcessConfigNode(data) { |
| | | return request({ |
| | | url: '/approveProcessConfigNode/add', |
| | | method: 'post', |
| | | data: data, |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** |
| | | * å¤ä»¶é¢ç¨è®°å½ - å页æ¥è¯¢ |
| | | * params: { current, size, sparePartId?, sparePartName?, source?, deviceId?, startTime?, endTime? } |
| | | */ |
| | | export const getSparePartsUsagePage = (params) => { |
| | | return request({ |
| | | url: "/sparePartsRequisitionRecord/listPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * å¤ä»¶é¢ç¨è®°å½ - æ°å¢ |
| | | * data 示ä¾ï¼ |
| | | * { |
| | | * source: "repair" | "upkeep" | "manual", |
| | | * sourceId?: number | string, |
| | | * deviceId?: number | string, |
| | | * deviceName?: string, |
| | | * operatorId?: number | string, |
| | | * operator?: string, |
| | | * useTime?: string, // YYYY-MM-DD HH:mm:ss |
| | | * items: [{ sparePartId: number|string, qty: number }] |
| | | * } |
| | | */ |
| | | export const addSparePartsUsage = (data) => { |
| | | return request({ |
| | | url: "/sparePartsUsage/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | |
| | | method: "delete", |
| | | 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 getStockInventoryListPageCombined = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/pageListCombinedStockInventory", |
| | | 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({ |
| | |
| | | url: "/personalShift/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | } |
| | |
| | | }); |
| | | } |
| | | |
| | | export function productUploadFile(data) { |
| | | return request({ |
| | | url: "/file/uploadFile", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // export function getProductRecordById(params) { |
| | | // return request({ |
| | | // url: "/purchase/registration/getProductRecordById", |
| | |
| | | method: "post", |
| | | data |
| | | }); |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | // purchaseReturnOrders/selectById/xxx |
| | | export function getPurchaseReturnOrderDetail(id) { |
| | | return request({ |
| | | url: "/purchaseReturnOrders/selectById/" + id, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // éè´éè´§åå é¤ |
| | | // POST purchaseReturnOrders/deleteById/xxx |
| | | export function deletePurchaseReturnOrder(id) { |
| | | return request({ |
| | | url: "/purchaseReturnOrders/deleteById/" + id, |
| | | method: "post", |
| | | }); |
| | | } |
| | |
| | | // å页æ¥è¯¢ |
| | | 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, |
| | | }); |
| | | } |
| | | |
| | |
| | | url: "/productOrder/listProcessBom", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // ç产订å-颿å°è´¦å表 |
| | | export function listMaterialPickingLedger(query) { |
| | | return request({ |
| | | url: "/productOrderMaterial/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // ç产订å-ä¿å颿å°è´¦ |
| | | // export function saveMaterialPickingLedger(data) { |
| | | // return request({ |
| | | // url: "/productOrderMaterial/save", |
| | | // method: "post", |
| | | // data, |
| | | // }); |
| | | // } |
| | | export function saveMaterialPickingLedger(data) { |
| | | return request({ |
| | | url: "/productionOrderPick/savePick", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | export function updateMaterialPickingLedger(data) { |
| | | return request({ |
| | | url: "/productionOrderPick/updatePick", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // çäº§è®¢åæº¯æºè¯¦æ
|
| | | export function getOrderDetail(npsNo) { |
| | | return request({ |
| | | 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 getProductOrderSource(id) { |
| | | return request({ |
| | | url: `/productionOrder/source/${id}`, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // ç产订å-éæç¡®è®¤ |
| | | export function confirmMaterialReturn(data) { |
| | | return request({ |
| | | url: "/productOrderMaterial/confirmReturn", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | } |
| | |
| | | // å页æ¥è¯¢ |
| | | 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 listWorkOrderMaterialLedger(query) { |
| | | return request({ |
| | | url: "/productOrderMaterial/reportMaterials", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å·¥å-è¡¥æ |
| | | export function addWorkOrderMaterialSupplement(data) { |
| | | return request({ |
| | | url: "/productionOperationTask/material/supplement", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // å·¥å-éæ |
| | | export function addWorkOrderMaterialReturn(data) { |
| | | return request({ |
| | | url: "/productionOperationTask/material/return", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // å·¥å-è¡¥æè®°å½ |
| | | export function listWorkOrderMaterialSupplementRecord(query) { |
| | | return request({ |
| | | url: "/productionOperationTask/material/supplementRecord", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å·¥å-é¢ç¨ï¼æäº¤å®é
é¢ç¨æ°éï¼ |
| | | export function pickWorkOrderMaterial(data) { |
| | | return request({ |
| | | 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 addOrUpdateDeliveryLedger(query) { |
| | | return request({ |
| | | url: "/shippingInfo/update", |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢ APP çæ¬å页å表 |
| | | export function listAppVersion(params) { |
| | | return request({ |
| | | url: "/app/getAllVersion", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | // ä¸ä¼ APK |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/app/add", |
| | | method: "post", |
| | | data |
| | | }); |
| | | } |
| | |
| | | }
|
| | |
|
| | | // to fixed https://github.com/ElemeFE/element/issues/2461
|
| | | .el-dialog {
|
| | | transform: none;
|
| | | left: 0;
|
| | | position: relative;
|
| | | margin: 0 auto;
|
| | | border-radius: 8px;
|
| | | padding: 0 !important;
|
| | | }
|
| | | .el-dialog__header {
|
| | | background: #f5f6f7;
|
| | | padding: 12px 16px;
|
| | | border-radius: 8px 8px 0 0;
|
| | | }
|
| | | .el-dialog__title {
|
| | | font-weight: 400;
|
| | | font-size: 16px;
|
| | | color: #2e3033;
|
| | | }
|
| | | .el-dialog__body {
|
| | | padding: 16px 40px 0 40px;
|
| | | max-height: 74vh;
|
| | | overflow-y: auto;
|
| | | }
|
| | | .el-dialog__footer {
|
| | | text-align: center;
|
| | | padding: 16px;
|
| | | }
|
| | | .el-message-box {
|
| | | padding: 0 !important;
|
| | | border-radius: 8px;
|
| | | }
|
| | | .el-message-box__header {
|
| | | background: #f5f6f7;
|
| | | padding: 12px 16px;
|
| | | border-radius: 8px 8px 0 0;
|
| | | }
|
| | | .el-message-box__title {
|
| | | font-weight: 400;
|
| | | font-size: 16px;
|
| | | color: #2e3033;
|
| | | }
|
| | | .el-message-box__content {
|
| | | padding: 16px 40px 0 40px;
|
| | | }
|
| | | .el-dialog { |
| | | transform: none; |
| | | left: 0; |
| | | position: relative; |
| | | margin: 0 auto; |
| | | border-radius: 24px; |
| | | padding: 0 !important; |
| | | border: 1px solid var(--surface-border); |
| | | box-shadow: var(--shadow-md); |
| | | background: rgba(255, 255, 255, 0.96); |
| | | } |
| | | .el-dialog__header { |
| | | background: linear-gradient(180deg, rgba(247, 250, 248, 0.98), rgba(242, 247, 244, 0.88)); |
| | | padding: 18px 24px 14px; |
| | | border-bottom: 1px solid var(--surface-border); |
| | | border-radius: 24px 24px 0 0; |
| | | } |
| | | .el-dialog__title { |
| | | font-weight: 600; |
| | | font-size: 17px; |
| | | color: var(--text-primary); |
| | | } |
| | | .el-dialog__body { |
| | | padding: 24px 24px 0; |
| | | max-height: 74vh; |
| | | overflow-y: auto; |
| | | } |
| | | .el-dialog__footer { |
| | | text-align: center; |
| | | padding: 18px 24px 24px; |
| | | } |
| | | .el-message-box { |
| | | padding: 0 !important; |
| | | border-radius: 22px; |
| | | border: 1px solid var(--surface-border); |
| | | box-shadow: var(--shadow-md); |
| | | } |
| | | .el-message-box__header { |
| | | background: linear-gradient(180deg, rgba(247, 250, 248, 0.98), rgba(242, 247, 244, 0.88)); |
| | | padding: 18px 24px 14px; |
| | | border-bottom: 1px solid var(--surface-border); |
| | | border-radius: 22px 22px 0 0; |
| | | } |
| | | .el-message-box__title { |
| | | font-weight: 600; |
| | | font-size: 17px; |
| | | color: var(--text-primary); |
| | | } |
| | | .el-message-box__content { |
| | | padding: 24px 24px 0; |
| | | } |
| | | .el-message-box__container {
|
| | | justify-content: center;
|
| | | }
|
| | |
| | | margin-right: 12px;
|
| | | }
|
| | | }
|
| | | .el-table__expanded-cell {
|
| | | padding: 0 !important;
|
| | | .el-table__header-wrapper {
|
| | | background-color: #f5f8ff !important;
|
| | | }
|
| | | }
|
| | | .el-table__expanded-cell { |
| | | padding: 0 !important; |
| | | .el-table__header-wrapper { |
| | | background-color: var(--surface-soft) !important; |
| | | } |
| | | } |
| | |
|
| | | // refine element ui upload
|
| | | .upload-container {
|
| | |
| | | display: none;
|
| | | }
|
| | |
|
| | | .el-dropdown .el-dropdown-link {
|
| | | color: var(--el-color-primary) !important;
|
| | | }
|
| | | .el-dropdown .el-dropdown-link { |
| | | color: var(--el-color-primary) !important; |
| | | } |
| | | |
| | | .el-button { |
| | | border-radius: 12px; |
| | | font-weight: 600; |
| | | box-shadow: none !important; |
| | | } |
| | | |
| | | .el-button--primary { |
| | | --el-button-bg-color: var(--el-color-primary); |
| | | --el-button-border-color: var(--el-color-primary); |
| | | --el-button-hover-bg-color: var(--el-color-primary-light-3); |
| | | --el-button-hover-border-color: var(--el-color-primary-light-3); |
| | | --el-button-active-bg-color: var(--el-color-primary-dark-2); |
| | | --el-button-active-border-color: var(--el-color-primary-dark-2); |
| | | } |
| | | |
| | | .el-input__wrapper, |
| | | .el-textarea__inner, |
| | | .el-select__wrapper, |
| | | .el-date-editor.el-input__wrapper, |
| | | .el-date-editor .el-input__wrapper { |
| | | border-radius: 12px; |
| | | box-shadow: 0 0 0 1px rgba(216, 225, 219, 0.92) inset !important; |
| | | background: rgba(255, 255, 255, 0.9); |
| | | } |
| | | |
| | | .el-input__wrapper.is-focus, |
| | | .el-select__wrapper.is-focused, |
| | | .el-textarea__inner:focus { |
| | | box-shadow: 0 0 0 1px rgba(0, 47, 167, 0.28) inset !important; |
| | | } |
| | | |
| | | .el-card { |
| | | border: 1px solid var(--surface-border); |
| | | box-shadow: var(--shadow-sm); |
| | | background: rgba(255, 255, 255, 0.88); |
| | | } |
| | | |
| | | .el-table { |
| | | --el-table-border-color: var(--surface-border); |
| | | --el-table-header-bg-color: var(--surface-soft); |
| | | --el-table-row-hover-bg-color: #f1f6f4; |
| | | --el-table-current-row-bg-color: #e9f0ed; |
| | | border-radius: 18px; |
| | | } |
| | | |
| | | .el-table th.el-table__cell { |
| | | background: var(--surface-soft) !important; |
| | | color: var(--text-secondary); |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .el-table tr, |
| | | .el-table td.el-table__cell, |
| | | .el-table__body tr > td.el-table__cell { |
| | | background: var(--surface-base) !important; |
| | | } |
| | | |
| | | .el-table .el-table__body tr:hover > td.el-table__cell { |
| | | background: var(--el-table-row-hover-bg-color) !important; |
| | | } |
| | | |
| | | .el-table .el-table__body tr.current-row > td.el-table__cell { |
| | | background: var(--el-table-current-row-bg-color) !important; |
| | | } |
| | | |
| | | .el-table .el-table__footer-wrapper { |
| | | border-top: 1px solid var(--surface-border); |
| | | } |
| | | |
| | | .el-table .el-table__footer-wrapper tbody td.el-table__cell, |
| | | .el-table .el-table__footer-wrapper tfoot td.el-table__cell { |
| | | background: var(--surface-base) !important; |
| | | border-top: 1px solid var(--surface-border); |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .el-pagination { |
| | | margin-top: 18px; |
| | | } |
| | |
| | | -moz-osx-font-smoothing: grayscale;
|
| | | -webkit-font-smoothing: antialiased;
|
| | | text-rendering: optimizeLegibility;
|
| | | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
| | | font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
| | | background:
|
| | | radial-gradient(circle at top left, rgba(214, 226, 219, 0.8), transparent 28%),
|
| | | linear-gradient(180deg, #f7faf8 0%, var(--app-bg) 100%);
|
| | | color: var(--text-primary);
|
| | | }
|
| | |
|
| | | label {
|
| | | font-weight: 700;
|
| | | font-weight: 600;
|
| | | color: var(--text-secondary);
|
| | | }
|
| | |
|
| | | html {
|
| | |
| | |
|
| | | #app {
|
| | | height: 100%;
|
| | | }
|
| | |
|
| | | html,
|
| | | body,
|
| | | #app {
|
| | | background-color: var(--app-bg);
|
| | | }
|
| | |
|
| | | *,
|
| | |
| | |
|
| | | //main-containerå
¨å±æ ·å¼
|
| | | .app-container {
|
| | | padding: 20px;
|
| | | padding: 20px 24px 24px;
|
| | | }
|
| | | .search_form {
|
| | | display: flex;
|
| | |
| | | justify-content: space-between;
|
| | | .search_title {
|
| | | font-size: 14px;
|
| | | font-weight: 700;
|
| | | color: #333333;
|
| | | font-weight: 600;
|
| | | letter-spacing: 0.04em;
|
| | | color: var(--text-secondary);
|
| | | }
|
| | | }
|
| | | .table_list {
|
| | | height: calc(100vh - 11em);
|
| | | margin-top: 20px;
|
| | | background: #fff;
|
| | | padding: 18px
|
| | | background: rgba(255, 255, 255, 0.88);
|
| | | border: 1px solid var(--surface-border);
|
| | | border-radius: var(--radius-md);
|
| | | box-shadow: var(--shadow-sm);
|
| | | padding: 18px;
|
| | | }
|
| | | .components-container {
|
| | | margin: 30px 50px;
|
| | |
| | |
|
| | | .link-type,
|
| | | .link-type:focus {
|
| | | color: #337ab7;
|
| | | color: var(--el-color-primary);
|
| | | cursor: pointer;
|
| | |
|
| | | &:hover {
|
| | | color: rgb(32, 160, 255);
|
| | | color: #165e57;
|
| | | }
|
| | | }
|
| | |
|
| | |
| | | margin-bottom: 10px;
|
| | | }
|
| | | }
|
| | |
|
| | | .app-container,
|
| | | .table_list,
|
| | | .components-container {
|
| | | .el-card,
|
| | | .el-dialog,
|
| | | .el-drawer,
|
| | | .el-table,
|
| | | .el-descriptions,
|
| | | .el-collapse-item__wrap,
|
| | | .el-tabs__content {
|
| | | border-radius: var(--radius-md);
|
| | | }
|
| | | }
|
| | |
| | | transition: margin-left 0.28s;
|
| | | margin-left: $base-sidebar-width;
|
| | | position: relative;
|
| | | background: #f5f7fb;
|
| | | background: transparent;
|
| | | }
|
| | |
|
| | | .sidebarHide {
|
| | |
| | | left: 0;
|
| | | z-index: 1001;
|
| | | overflow: hidden;
|
| | | -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
| | | box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
| | | padding: 12px 0 16px 16px;
|
| | | background: transparent;
|
| | | box-shadow: none;
|
| | |
|
| | | // reset element-ui css
|
| | | .horizontal-collapse-transition {
|
| | |
| | |
|
| | | &.has-logo {
|
| | | .el-scrollbar {
|
| | | height: calc(100% - 50px);
|
| | | height: calc(100% - 72px);
|
| | | margin-top: 10px;
|
| | | }
|
| | | }
|
| | |
|
| | |
| | | margin-right: 16px;
|
| | | }
|
| | |
|
| | | .el-menu {
|
| | | border: none;
|
| | | height: 100%;
|
| | | width: 100% !important;
|
| | | }
|
| | | .el-menu { |
| | | border: none; |
| | | height: 100%; |
| | | width: 100% !important; |
| | | padding: 10px 8px 18px; |
| | | border-radius: 22px; |
| | | background: var(--menu-surface); |
| | | backdrop-filter: blur(18px); |
| | | box-shadow: var(--shadow-sm); |
| | | } |
| | |
|
| | | .el-menu-item,
|
| | | .menu-title {
|
| | |
| | | display: inline-block !important;
|
| | | }
|
| | |
|
| | | // menu hover
|
| | | .sub-menu-title-noDropdown,
|
| | | .el-sub-menu__title {
|
| | | &:hover {
|
| | | background-color: rgba(212, 221, 255, 0.8) !important;
|
| | | }
|
| | | }
|
| | | // menu hover |
| | | .submenu-title-noDropdown, |
| | | .el-sub-menu__title { |
| | | &:hover { |
| | | background-color: var(--menu-hover) !important; |
| | | border-radius: 14px; |
| | | } |
| | | } |
| | | & .theme-light .is-active > .el-sub-menu__title {
|
| | | color: #fff !important;
|
| | | color: var(--current-color) !important;
|
| | | }
|
| | |
|
| | | & .nest-menu .el-sub-menu > .el-sub-menu__title,
|
| | | & .el-sub-menu .el-menu-item {
|
| | | min-width: $base-sidebar-width !important;
|
| | | & .nest-menu .el-sub-menu > .el-sub-menu__title, |
| | | & .el-sub-menu .el-menu-item { |
| | | min-width: 0 !important; |
| | | margin: 0 12px 6px; |
| | | width: calc(100% - 24px); |
| | | padding-left: 8px !important; |
| | | padding-right: 8px !important; |
| | | box-sizing: border-box; |
| | | |
| | | &:hover { |
| | | background-color: var(--menu-hover) !important; |
| | | } |
| | | &.is-active { |
| | | background-color: var(--menu-active-bg) !important; |
| | | border-radius: 14px; |
| | | } |
| | | } |
| | |
|
| | | &:hover {
|
| | | background-color: rgba(212, 221, 255, 0.8) !important;
|
| | | }
|
| | | &.is-active {
|
| | | background-color: #fff !important;
|
| | | }
|
| | | }
|
| | | & .theme-light .nest-menu .el-sub-menu > .el-sub-menu__title, |
| | | & .theme-light .el-sub-menu .el-menu-item { |
| | | //background-color: transparent; |
| | | |
| | | &:hover { |
| | | background-color: var(--menu-hover) !important; |
| | | border-radius: 14px; |
| | | } |
| | | } |
| | | } |
| | |
|
| | | & .theme-light .nest-menu .el-sub-menu > .el-sub-menu__title,
|
| | | & .theme-light .el-sub-menu .el-menu-item {
|
| | | //background-color: transparent;
|
| | | .hideSidebar { |
| | | .sidebar-container { |
| | | width: 68px !important; |
| | | padding-left: 0; |
| | | padding-right: 0; |
| | | } |
| | | |
| | | .main-container { |
| | | margin-left: 84px; |
| | | } |
| | |
|
| | | &:hover {
|
| | | background-color: rgba(212, 221, 255, 0.8) !important;
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | .hideSidebar {
|
| | | .sidebar-container {
|
| | | width: 54px !important;
|
| | | }
|
| | |
|
| | | .main-container {
|
| | | margin-left: 54px;
|
| | | }
|
| | |
|
| | | .sub-menu-title-noDropdown {
|
| | | padding: 0 !important;
|
| | | position: relative;
|
| | |
|
| | | .el-tooltip {
|
| | | padding: 0 !important;
|
| | |
|
| | | .svg-icon {
|
| | | margin-left: 20px;
|
| | | }
|
| | | }
|
| | | }
|
| | | .el-sub-menu {
|
| | | overflow: hidden;
|
| | |
|
| | | & > .el-sub-menu__title {
|
| | | padding: 0 !important;
|
| | |
|
| | | .svg-icon {
|
| | | margin-left: 20px;
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | .el-menu--collapse {
|
| | | .el-sub-menu {
|
| | | & > .el-sub-menu__title {
|
| | | & > span {
|
| | | height: 0;
|
| | | width: 0;
|
| | | overflow: hidden;
|
| | | visibility: hidden;
|
| | | .submenu-title-noDropdown { |
| | | padding: 0 !important; |
| | | position: relative; |
| | | display: flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .svg-icon { |
| | | margin-right: 0; |
| | | } |
| | | |
| | | .el-tooltip { |
| | | padding: 0 !important; |
| | | display: inline-flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 100%; |
| | | |
| | | .svg-icon { |
| | | margin-left: 0; |
| | | } |
| | | } |
| | | |
| | | .el-menu-tooltip__trigger { |
| | | width: 100%; |
| | | display: inline-flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .svg-icon { |
| | | width: 18px; |
| | | height: 18px; |
| | | margin-right: 0; |
| | | flex-shrink: 0; |
| | | } |
| | | } |
| | | } |
| | | .el-sub-menu { |
| | | overflow: hidden; |
| | | |
| | | & > .el-sub-menu__title { |
| | | padding: 0 !important; |
| | | display: flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .svg-icon { |
| | | margin-left: 0; |
| | | margin-right: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .el-menu--collapse { |
| | | width: 100% !important; |
| | | padding: 10px 6px 18px; |
| | | |
| | | > .el-menu-item, |
| | | .el-sub-menu { |
| | | & > .el-sub-menu__title, |
| | | &.el-menu-item { |
| | | margin: 0 0 6px; |
| | | width: 100%; |
| | | padding-left: 0 !important; |
| | | padding-right: 0 !important; |
| | | box-sizing: border-box; |
| | | display: flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .svg-icon { |
| | | width: 18px; |
| | | height: 18px; |
| | | margin-right: 0; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | &:hover { |
| | | border-radius: 14px; |
| | | } |
| | | |
| | | & > span { |
| | | height: 0; |
| | | width: 0; |
| | | overflow: hidden; |
| | | visibility: hidden; |
| | | display: inline-block;
|
| | | }
|
| | | & > i {
|
| | |
| | | overflow: hidden;
|
| | | visibility: hidden;
|
| | | display: inline-block;
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
|
| | | .el-menu--collapse .el-menu .el-sub-menu {
|
| | | min-width: $base-sidebar-width !important;
|
| | |
| | | }
|
| | | }
|
| | |
|
| | | .nest-menu .el-sub-menu > .el-sub-menu__title,
|
| | | .el-menu-item {
|
| | | &:hover {
|
| | | // you can use $sub-menuHover
|
| | | background-color: rgba(212, 221, 255, 0.56) !important;
|
| | | }
|
| | | &.is-active {
|
| | | background-color: rgba(212, 221, 255, 0.56) !important;
|
| | | }
|
| | | }
|
| | | .nest-menu .el-sub-menu > .el-sub-menu__title, |
| | | .el-menu-item { |
| | | min-width: 0 !important; |
| | | margin: 0 12px 6px; |
| | | width: calc(100% - 24px); |
| | | padding-left: 8px !important; |
| | | padding-right: 8px !important; |
| | | box-sizing: border-box; |
| | | |
| | | &:hover { |
| | | // you can use $sub-menuHover |
| | | background-color: var(--menu-hover) !important; |
| | | } |
| | | &.is-active { |
| | | background-color: var(--menu-active-bg) !important; |
| | | border-radius: 14px; |
| | | } |
| | | } |
| | |
|
| | | // the scroll bar appears when the sub-menu is too long
|
| | | > .el-menu--popup {
|
| | | max-height: 100vh;
|
| | | overflow-y: auto;
|
| | | padding: 8px;
|
| | | border-radius: 18px;
|
| | | border: 1px solid var(--surface-border);
|
| | | box-shadow: var(--shadow-md);
|
| | |
|
| | | &::-webkit-scrollbar-track-piece {
|
| | | background: #d3dce6;
|
| | | background: #dfe7e1;
|
| | | }
|
| | |
|
| | | &::-webkit-scrollbar {
|
| | |
| | | }
|
| | |
|
| | | &::-webkit-scrollbar-thumb {
|
| | | background: #99a9bf;
|
| | | background: #9aa79e;
|
| | | border-radius: 20px;
|
| | | }
|
| | | }
|
| | |
| | | // base color
|
| | | $blue: #324157;
|
| | | $light-blue: #333c46;
|
| | | $red: #c03639;
|
| | | $pink: #e65d6e;
|
| | | $green: #30b08f;
|
| | | $tiffany: #4ab7bd;
|
| | | $yellow: #fec171;
|
| | | $panGreen: #30b08f;
|
| | |
|
| | | // é»è®¤ä¸»é¢åé
|
| | | $menuText: #bfcbd9;
|
| | | $menuActiveText: #409eff;
|
| | | $menuBg: #304156;
|
| | | $menuHover: #263445;
|
| | |
|
| | | // æµ
è²ä¸»é¢theme-light
|
| | | $menuLightBg: #002fa7;
|
| | | $menuLightHover: #f0f1f5;
|
| | | $menuLightText: #fff;
|
| | | $menuLightActiveText: #002fa7;
|
| | |
|
| | | // åºç¡åé
|
| | | $base-sidebar-width: 200px;
|
| | | $sideBarWidth: 200px;
|
| | |
|
| | | // èåæè²åé
|
| | | $base-menu-color: #bfcbd9;
|
| | | $base-menu-color-active: #f4f4f5;
|
| | | $base-menu-background: #304156;
|
| | | $base-sub-menu-background: #1f2d3d;
|
| | | $base-sub-menu-hover: #fff;
|
| | |
|
| | | // ç»ä»¶åé
|
| | | $--color-primary: #409eff;
|
| | | $--color-success: #67c23a;
|
| | | $--color-warning: #e6a23c;
|
| | | $--color-danger: #f56c6c;
|
| | | $--color-info: #909399;
|
| | |
|
| | | :export {
|
| | | menuText: $menuText;
|
| | | menuActiveText: $menuActiveText;
|
| | | menuBg: $menuBg;
|
| | | menuHover: $menuHover;
|
| | | menuLightBg: $menuLightBg;
|
| | | menuLightHover: $menuLightHover;
|
| | | menuLightText: $menuLightText;
|
| | | menuLightActiveText: $menuLightActiveText;
|
| | | sideBarWidth: $sideBarWidth;
|
| | | // 导åºåºç¡é¢è²
|
| | | blue: $blue;
|
| | | lightBlue: $light-blue;
|
| | | red: $red;
|
| | | pink: $pink;
|
| | | green: $green;
|
| | | tiffany: $tiffany;
|
| | | yellow: $yellow;
|
| | | panGreen: $panGreen;
|
| | | // 导åºç»ä»¶é¢è²
|
| | | colorPrimary: $--color-primary;
|
| | | colorSuccess: $--color-success;
|
| | | colorWarning: $--color-warning;
|
| | | colorDanger: $--color-danger;
|
| | | colorInfo: $--color-info;
|
| | | }
|
| | |
|
| | | // CSSåéå®ä¹
|
| | | :root {
|
| | | /* äº®è²æ¨¡å¼åé */
|
| | | --sidebar-bg: #{$menuBg};
|
| | | --sidebar-text: #{$menuText};
|
| | | --menu-hover: #{$menuHover};
|
| | |
|
| | | --navbar-bg: #ffffff;
|
| | | --navbar-text: #303133;
|
| | |
|
| | | /* splitpanes default-theme åé */
|
| | | --splitpanes-default-bg: #ffffff;
|
| | | }
|
| | |
|
| | | // æé»æ¨¡å¼åé
|
| | | html.dark {
|
| | | /* é»è®¤éç¨ */
|
| | | --el-bg-color: #141414;
|
| | | --el-bg-color-overlay: #1d1e1f;
|
| | | --el-text-color-primary: #ffffff;
|
| | | --el-text-color-regular: #d0d0d0;
|
| | | --el-border-color: #434343;
|
| | | --el-border-color-light: #434343;
|
| | |
|
| | | /* ä¾§è¾¹æ */
|
| | | --sidebar-bg: #141414;
|
| | | --sidebar-text: #ffffff;
|
| | | --menu-hover: #2d2d2d;
|
| | | --menu-active-text: #{$menuActiveText};
|
| | |
|
| | | /* é¡¶é¨å¯¼èªæ */
|
| | | --navbar-bg: #141414;
|
| | | --navbar-text: #ffffff;
|
| | | --navbar-hover: #141414;
|
| | |
|
| | | /* æ ç¾æ */
|
| | | --tags-bg: #141414;
|
| | | --tags-item-bg: #1d1e1f;
|
| | | --tags-item-border: #303030;
|
| | | --tags-item-text: #d0d0d0;
|
| | | --tags-item-hover: #2d2d2d;
|
| | | --tags-close-hover: #64666a;
|
| | |
|
| | | /* splitpanes ç»ä»¶æé»æ¨¡å¼åé */
|
| | | --splitpanes-bg: #141414;
|
| | | --splitpanes-border: #303030;
|
| | | --splitpanes-splitter-bg: #1d1e1f;
|
| | | --splitpanes-splitter-hover-bg: #2d2d2d;
|
| | |
|
| | | /* blockquote æé»æ¨¡å¼åé */
|
| | | --blockquote-bg: #1d1e1f;
|
| | | --blockquote-border: #303030;
|
| | | --blockquote-text: #d0d0d0;
|
| | |
|
| | | /* Cron æ¶é´è¡¨è¾¾å¼ 模å¼åé */
|
| | | --cron-border: #303030;
|
| | |
|
| | | /* splitpanes default-theme æé»æ¨¡å¼åé */
|
| | | --splitpanes-default-bg: #141414;
|
| | |
|
| | | /* ä¾§è¾¹æ èåè¦ç */
|
| | | .sidebar-container {
|
| | | .el-menu-item,
|
| | | .menu-title {
|
| | | color: var(--el-text-color-regular);
|
| | | }
|
| | | & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
|
| | | & .theme-dark .el-sub-menu .el-menu-item {
|
| | | background-color: var(--el-bg-color) !important;
|
| | | }
|
| | | }
|
| | |
|
| | | /* 顶鍿 æ èåè¦ç */
|
| | | .el-menu--horizontal {
|
| | | .el-menu-item {
|
| | | &:not(.is-disabled) {
|
| | | &:hover,
|
| | | &:focus {
|
| | | background-color: var(--navbar-hover) !important;
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | /* åå²çªæ ¼è¦ç */
|
| | | .splitpanes {
|
| | | background-color: var(--splitpanes-bg);
|
| | |
|
| | | .splitpanes__pane {
|
| | | background-color: var(--splitpanes-bg);
|
| | | border-color: var(--splitpanes-border);
|
| | | }
|
| | |
|
| | | .splitpanes__splitter {
|
| | | background-color: var(--splitpanes-splitter-bg);
|
| | | border-color: var(--splitpanes-border);
|
| | |
|
| | | &:hover {
|
| | | background-color: var(--splitpanes-splitter-hover-bg);
|
| | | }
|
| | |
|
| | | &:before,
|
| | | &:after {
|
| | | background-color: var(--splitpanes-border);
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | /* è¡¨æ ¼æ ·å¼è¦ç */
|
| | | .el-table {
|
| | | --el-table-header-bg-color: var(--el-bg-color-overlay) !important;
|
| | | --el-table-header-text-color: var(--el-text-color-regular) !important;
|
| | | --el-table-border-color: var(--el-border-color-light) !important;
|
| | | --el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important;
|
| | |
|
| | | .el-table__header-wrapper,
|
| | | .el-table__fixed-header-wrapper {
|
| | | th {
|
| | | background-color: var(--el-bg-color-overlay, #f0f1f5) !important;
|
| | | color: var(--el-text-color-regular, #515a6e);
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | /* æ ç»ä»¶é«äº®æ ·å¼è¦ç */
|
| | | .el-tree {
|
| | | .el-tree-node.is-current > .el-tree-node__content {
|
| | | background-color: var(--el-bg-color-overlay) !important;
|
| | | color: var(--el-color-primary);
|
| | | }
|
| | |
|
| | | .el-tree-node__content:hover {
|
| | | background-color: var(--el-bg-color-overlay);
|
| | | }
|
| | | }
|
| | |
|
| | | /* 䏿èåæ ·å¼è¦ç */
|
| | | .el-dropdown-menu__item:not(.is-disabled):focus,
|
| | | .el-dropdown-menu__item:not(.is-disabled):hover {
|
| | | background-color: var(--navbar-hover) !important;
|
| | | }
|
| | |
|
| | | /* blockquoteæ ·å¼è¦ç */
|
| | | blockquote {
|
| | | background-color: var(--blockquote-bg) !important;
|
| | | border-left-color: var(--blockquote-border) !important;
|
| | | color: var(--blockquote-text) !important;
|
| | | }
|
| | |
|
| | | /* æ¶é´è¡¨è¾¾å¼æ 颿 ·å¼è¦ç */
|
| | | .popup-result .title {
|
| | | background: var(--cron-border);
|
| | | }
|
| | | }
|
| | | // base color |
| | | $blue: #324157; |
| | | $light-blue: #333c46; |
| | | $red: #c03639; |
| | | $pink: #e65d6e; |
| | | $green: #30b08f; |
| | | $tiffany: #4ab7bd; |
| | | $yellow: #fec171; |
| | | $panGreen: #30b08f; |
| | | |
| | | // menu palette |
| | | $menuText: #677287; |
| | | $menuActiveText: #1f7a72; |
| | | $menuBg: #f4f7f4; |
| | | $menuHover: #e7eeea; |
| | | |
| | | // light theme |
| | | $menuLightBg: #f4f7f4; |
| | | $menuLightHover: #e7eeea; |
| | | $menuLightText: #3b4658; |
| | | $menuLightActiveText: #1f7a72; |
| | | |
| | | // layout |
| | | $base-sidebar-width: 216px; |
| | | $sideBarWidth: 216px; |
| | | |
| | | // sidebar |
| | | $base-menu-color: #677287; |
| | | $base-menu-color-active: #1f7a72; |
| | | $base-menu-background: #f4f7f4; |
| | | $base-sub-menu-background: #eef3ef; |
| | | $base-sub-menu-hover: #ffffff; |
| | | |
| | | // component |
| | | $--color-primary: #1f7a72; |
| | | $--color-success: #67c23a; |
| | | $--color-warning: #d89b41; |
| | | $--color-danger: #d25b52; |
| | | $--color-info: #7d8797; |
| | | |
| | | :export { |
| | | menuText: $menuText; |
| | | menuActiveText: $menuActiveText; |
| | | menuBg: $menuBg; |
| | | menuHover: $menuHover; |
| | | menuLightBg: $menuLightBg; |
| | | menuLightHover: $menuLightHover; |
| | | menuLightText: $menuLightText; |
| | | menuLightActiveText: $menuLightActiveText; |
| | | sideBarWidth: $sideBarWidth; |
| | | blue: $blue; |
| | | lightBlue: $light-blue; |
| | | red: $red; |
| | | pink: $pink; |
| | | green: $green; |
| | | tiffany: $tiffany; |
| | | yellow: $yellow; |
| | | panGreen: $panGreen; |
| | | colorPrimary: $--color-primary; |
| | | colorSuccess: $--color-success; |
| | | colorWarning: $--color-warning; |
| | | colorDanger: $--color-danger; |
| | | colorInfo: $--color-info; |
| | | } |
| | | |
| | | :root { |
| | | --sidebar-bg: #{$menuBg}; |
| | | --sidebar-text: #{$menuText}; |
| | | --sidebar-muted: #93a0b1; |
| | | --menu-hover: #{$menuHover}; |
| | | --menu-active-bg: #dfe9e4; |
| | | --menu-surface: rgba(255, 255, 255, 0.72); |
| | | |
| | | --app-bg: #eef2ee; |
| | | --app-bg-accent: #dfe8e2; |
| | | --surface-base: #ffffff; |
| | | --surface-soft: #f7faf8; |
| | | --surface-muted: #eff4f1; |
| | | --surface-border: #d8e1db; |
| | | --surface-border-strong: #c9d5ce; |
| | | --text-primary: #21313f; |
| | | --text-secondary: #5f6d7e; |
| | | --text-tertiary: #8a98a8; |
| | | --shadow-sm: 0 10px 30px rgba(31, 49, 38, 0.06); |
| | | --shadow-md: 0 18px 50px rgba(31, 49, 38, 0.1); |
| | | --radius-lg: 24px; |
| | | --radius-md: 18px; |
| | | --radius-sm: 12px; |
| | | |
| | | --navbar-bg: rgba(255, 255, 255, 0.78); |
| | | --navbar-text: #21313f; |
| | | --navbar-hover: rgba(31, 122, 114, 0.08); |
| | | |
| | | --tags-bg: transparent; |
| | | --tags-item-bg: rgba(255, 255, 255, 0.74); |
| | | --tags-item-border: rgba(201, 213, 206, 0.88); |
| | | --tags-item-text: #5f6d7e; |
| | | --tags-item-hover: rgba(31, 122, 114, 0.08); |
| | | --tags-close-hover: rgba(31, 122, 114, 0.18); |
| | | |
| | | --splitpanes-default-bg: #ffffff; |
| | | } |
| | | |
| | | html.dark { |
| | | --el-bg-color: #141414; |
| | | --el-bg-color-overlay: #1d1e1f; |
| | | --el-text-color-primary: #ffffff; |
| | | --el-text-color-regular: #d0d0d0; |
| | | --el-border-color: #434343; |
| | | --el-border-color-light: #434343; |
| | | |
| | | --sidebar-bg: #141414; |
| | | --sidebar-text: #ffffff; |
| | | --menu-hover: #2d2d2d; |
| | | --menu-active-text: #{$menuActiveText}; |
| | | |
| | | --navbar-bg: #141414; |
| | | --navbar-text: #ffffff; |
| | | --navbar-hover: #141414; |
| | | |
| | | --tags-bg: #141414; |
| | | --tags-item-bg: #1d1e1f; |
| | | --tags-item-border: #303030; |
| | | --tags-item-text: #d0d0d0; |
| | | --tags-item-hover: #2d2d2d; |
| | | --tags-close-hover: #64666a; |
| | | |
| | | --splitpanes-bg: #141414; |
| | | --splitpanes-border: #303030; |
| | | --splitpanes-splitter-bg: #1d1e1f; |
| | | --splitpanes-splitter-hover-bg: #2d2d2d; |
| | | |
| | | --blockquote-bg: #1d1e1f; |
| | | --blockquote-border: #303030; |
| | | --blockquote-text: #d0d0d0; |
| | | --cron-border: #303030; |
| | | --splitpanes-default-bg: #141414; |
| | | |
| | | .sidebar-container { |
| | | .el-menu-item, |
| | | .menu-title { |
| | | color: var(--el-text-color-regular); |
| | | } |
| | | |
| | | & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title, |
| | | & .theme-dark .el-sub-menu .el-menu-item { |
| | | background-color: var(--el-bg-color) !important; |
| | | } |
| | | } |
| | | |
| | | .el-menu--horizontal { |
| | | .el-menu-item { |
| | | &:not(.is-disabled) { |
| | | &:hover, |
| | | &:focus { |
| | | background-color: var(--navbar-hover) !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .splitpanes { |
| | | background-color: var(--splitpanes-bg); |
| | | |
| | | .splitpanes__pane { |
| | | background-color: var(--splitpanes-bg); |
| | | border-color: var(--splitpanes-border); |
| | | } |
| | | |
| | | .splitpanes__splitter { |
| | | background-color: var(--splitpanes-splitter-bg); |
| | | border-color: var(--splitpanes-border); |
| | | |
| | | &:hover { |
| | | background-color: var(--splitpanes-splitter-hover-bg); |
| | | } |
| | | |
| | | &:before, |
| | | &:after { |
| | | background-color: var(--splitpanes-border); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .el-table { |
| | | --el-table-header-bg-color: var(--el-bg-color-overlay) !important; |
| | | --el-table-header-text-color: var(--el-text-color-regular) !important; |
| | | --el-table-border-color: var(--el-border-color-light) !important; |
| | | --el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important; |
| | | |
| | | .el-table__header-wrapper, |
| | | .el-table__fixed-header-wrapper { |
| | | th { |
| | | background-color: var(--el-bg-color-overlay, #f0f1f5) !important; |
| | | color: var(--el-text-color-regular, #515a6e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .el-tree { |
| | | .el-tree-node.is-current > .el-tree-node__content { |
| | | background-color: var(--el-bg-color-overlay) !important; |
| | | color: var(--el-color-primary); |
| | | } |
| | | |
| | | .el-tree-node__content:hover { |
| | | background-color: var(--el-bg-color-overlay); |
| | | } |
| | | } |
| | | |
| | | .el-dropdown-menu__item:not(.is-disabled):focus, |
| | | .el-dropdown-menu__item:not(.is-disabled):hover { |
| | | background-color: var(--navbar-hover) !important; |
| | | } |
| | | |
| | | blockquote { |
| | | background-color: var(--blockquote-bg) !important; |
| | | border-left-color: var(--blockquote-border) !important; |
| | | color: var(--blockquote-text) !important; |
| | | } |
| | | |
| | | .popup-result .title { |
| | | background: var(--cron-border); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | </script>
|
| | |
|
| | | <style lang='scss' scoped>
|
| | | .app-breadcrumb.el-breadcrumb {
|
| | | display: inline-block;
|
| | | font-size: 14px;
|
| | | line-height: 50px;
|
| | | margin-left: 8px;
|
| | |
|
| | | .no-redirect {
|
| | | color: #002FA7;
|
| | | cursor: text;
|
| | | }
|
| | | }
|
| | | </style> |
| | | .app-breadcrumb.el-breadcrumb { |
| | | display: inline-block; |
| | | font-size: 14px; |
| | | line-height: 56px; |
| | | margin-left: 8px; |
| | | |
| | | :deep(.el-breadcrumb__inner) { |
| | | color: var(--text-secondary); |
| | | font-weight: 500; |
| | | transition: color 0.2s ease; |
| | | } |
| | | |
| | | :deep(.el-breadcrumb__separator) { |
| | | color: var(--text-tertiary); |
| | | } |
| | | |
| | | a { |
| | | color: var(--text-secondary); |
| | | |
| | | &:hover { |
| | | color: var(--current-color); |
| | | } |
| | | } |
| | | |
| | | .no-redirect { |
| | | color: var(--current-color); |
| | | font-weight: 600; |
| | | cursor: text; |
| | | } |
| | | } |
| | | </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="120" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <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> |
| | | </template> |
| | | |
| | | <script setup> |
| | | 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"; |
| | | |
| | | 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 handleUpload = () => { |
| | | uploadDialogVisible.value = true; |
| | | }; |
| | | |
| | | const saveUpload = async () => { |
| | | // æ£æ¥æ¯å¦ææ°ä¸ä¼ çæä»¶ |
| | | if (newFileList.value.length > 0) { |
| | | try { |
| | | await createAttachment({ |
| | | application: "file", |
| | | recordType: props.recordType, |
| | | recordId: props.recordId, |
| | | storageBlobDTOs: [...newFileList.value, ...tableData.value], |
| | | }); |
| | | newFileList.value = []; |
| | | // å·æ°å表 |
| | | setList(); |
| | | } catch (error) { |
| | | proxy?.$modal?.msgError("ä¸ä¼ 失败"); |
| | | } |
| | | } |
| | | uploadDialogVisible.value = false; |
| | | }; |
| | | |
| | | const closeUpload = () => { |
| | | newFileList.value = []; |
| | | uploadDialogVisible.value = false; |
| | | }; |
| | | |
| | | const handleDelete = async (row, index) => { |
| | | try { |
| | | await deleteAttachment([row.storageAttachmentId]); |
| | | proxy?.$modal?.msgSuccess("å 餿å"); |
| | | setList(); |
| | | } catch (error) { |
| | | proxy?.$modal?.msgError("å é¤å¤±è´¥"); |
| | | } |
| | | }; |
| | | |
| | | const setList = () => { |
| | | attachmentList({ |
| | | recordType: props.recordType, |
| | | recordId: props.recordId, |
| | | }).then(res => { |
| | | if (res && res.data) { |
| | | tableData.value = 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, |
| | | }, |
| | | }) |
| | | .then((res) => { |
| | | handleUploadSuccess(res.data); |
| | | }); |
| | | formData.append("files", file); |
| | | uploadPublicFile(formData).then((res) => { |
| | | handleUploadSuccess(res) |
| | | }) |
| | | } |
| | | </script> |
| | | |
| | |
| | | <template> |
| | | <el-table |
| | | ref="multipleTable" |
| | | v-loading="tableLoading" |
| | | :border="border" |
| | | :data="tableData" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | :height="height" |
| | | :highlight-current-row="highlightCurrentRow" |
| | | :row-class-name="rowClassName" |
| | | :row-style="rowStyle" |
| | | :row-key="rowKey" |
| | | :style="tableStyle" |
| | | tooltip-effect="dark" |
| | | :expand-row-keys="expandRowKeys" |
| | | :show-summary="isShowSummary" |
| | | :summary-method="summaryMethod" |
| | | @row-click="rowClick" |
| | | @current-change="currentChange" |
| | | @selection-change="handleSelectionChange" |
| | | @expand-change="expandChange" |
| | | class="lims-table" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | type="selection" |
| | | width="55" |
| | | 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" |
| | | :filter-multiple="item.filterMultiple" |
| | | :filtered-value="item.filteredValue" |
| | | :filters="item.filters" |
| | | :fixed="item.fixed" |
| | | :label="item.label" |
| | | :prop="item.prop" |
| | | :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'" |
| | | :align="item.align" |
| | | :sortable="!!item.sortable" |
| | | :type="item.type" |
| | | :width="item.width" |
| | | > |
| | | <el-table ref="multipleTable" |
| | | v-loading="tableLoading" |
| | | :border="border" |
| | | :data="tableData" |
| | | :header-cell-style="mergedHeaderCellStyle" |
| | | :height="height" |
| | | :highlight-current-row="highlightCurrentRow" |
| | | :row-class-name="rowClassName" |
| | | :row-style="rowStyle" |
| | | :row-key="rowKey" |
| | | :style="tableStyle" |
| | | tooltip-effect="dark" |
| | | :expand-row-keys="expandRowKeys" |
| | | :show-summary="isShowSummary" |
| | | :summary-method="summaryMethod" |
| | | @row-click="rowClick" |
| | | @current-change="currentChange" |
| | | @selection-change="handleSelectionChange" |
| | | @expand-change="expandChange" |
| | | 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" |
| | | :key="index" |
| | | :column-key="item.columnKey" |
| | | :filter-method="item.filterHandler" |
| | | :filter-multiple="item.filterMultiple" |
| | | :filtered-value="item.filteredValue" |
| | | :filters="item.filters" |
| | | :fixed="item.fixed" |
| | | :label="item.label" |
| | | :prop="item.prop" |
| | | :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'" |
| | | :align="item.align" |
| | | :sortable="!!item.sortable" |
| | | :type="item.type" |
| | | :width="item.width" |
| | | :minWidth="item.minWidth"> |
| | | <template #header="scope"> |
| | | <div class="pim-table-header-cell"> |
| | | <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" |
| | | :name="item.theadSlot" |
| | | :index="scope.$index" |
| | | :row="scope.row" |
| | | /> |
| | | <template v-if="item.hasOwnProperty('colunmTemplate')" |
| | | #[item.colunmTemplate]="scope"> |
| | | <slot v-if="item.theadSlot" |
| | | :name="item.theadSlot" |
| | | :index="scope.$index" |
| | | :row="scope.row" /> |
| | | </template> |
| | | |
| | | <template #default="scope"> |
| | | <!-- ææ§½ --> |
| | | <div v-if="item.dataType == 'slot'"> |
| | | <slot |
| | | v-if="item.slot" |
| | | :index="scope.$index" |
| | | :name="item.slot" |
| | | :row="scope.row" |
| | | /> |
| | | <slot v-if="item.slot" |
| | | :index="scope.$index" |
| | | :name="item.slot" |
| | | :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]" |
| | | alt="" |
| | | style="width: 40px; height: 40px; margin-top: 10px" |
| | | /> |
| | | <img :src="javaApi + '/img/' + scope.row[item.prop]" |
| | | alt="" |
| | | 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)" |
| | | > |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :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 |
| | | )" |
| | | v-else-if=" |
| | | v-else-if=" |
| | | typeof dataTypeFn(scope.row[item.prop], item.formatData) === |
| | | 'object' |
| | | " |
| | | :key="index" |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :type="formatType(tag, item.formatType)" |
| | | > |
| | | :key="index" |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :type="formatType(tag, item.formatType)"> |
| | | {{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }} |
| | | </el-tag> |
| | | |
| | | <el-tag |
| | | v-else |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :type="formatType(scope.row[item.prop], item.formatType)" |
| | | > |
| | | <el-tag v-else |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :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'" |
| | | v-if="o.showHide ? o.showHide(scope.row) : true" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
| | | :plain="o.plain" |
| | | type="primary" |
| | | :style="{ |
| | | color: |
| | | o.name === 'å é¤' || o.name === 'delete' |
| | | ? '#f56c6c' |
| | | : o.color, |
| | | <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" |
| | | type="primary" |
| | | :style="{ |
| | | color: getOperationColor(o, scope.row), |
| | | fontWeight: 'bold', |
| | | }" |
| | | link |
| | | @click.stop="o.clickFun(scope.row)" |
| | | :key="key" |
| | | > |
| | | link |
| | | @click.stop="o.clickFun(scope.row)" |
| | | :key="key"> |
| | | {{ o.name }} |
| | | </el-button> |
| | | <el-upload |
| | | :action=" |
| | | <el-upload :action=" |
| | | javaApi + |
| | | o.url + |
| | | '?id=' + |
| | | (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id) |
| | | " |
| | | ref="uploadRef" |
| | | :multiple="o.multiple ? o.multiple : false" |
| | | :limit="1" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
| | | :accept=" |
| | | ref="uploadRef" |
| | | :multiple="o.multiple ? o.multiple : false" |
| | | :limit="1" |
| | | :disabled="isOperationDisabled(o, scope.row)" |
| | | :accept=" |
| | | o.accept |
| | | ? o.accept |
| | | : '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar' |
| | | " |
| | | v-if="o.type == 'upload'" |
| | | style="display: inline-block; width: 50px" |
| | | v-show="o.showHide ? o.showHide(scope.row) : true" |
| | | :headers="uploadHeader" |
| | | :before-upload="(file) => beforeUpload(file, scope.$index)" |
| | | :on-change=" |
| | | v-if="o.type == 'upload'" |
| | | style="display: inline-block; width: 50px" |
| | | v-show="o.showHide ? o.showHide(scope.row) : true" |
| | | :headers="uploadHeader" |
| | | :before-upload="(file) => beforeUpload(file, scope.$index)" |
| | | :on-change=" |
| | | (file, fileList) => handleChange(file, fileList, scope.$index) |
| | | " |
| | | :on-error=" |
| | | :on-error=" |
| | | (error, file, fileList) => |
| | | onError(error, file, fileList, scope.$index) |
| | | " |
| | | :on-success=" |
| | | :on-success=" |
| | | (response, file, fileList) => |
| | | handleSuccessUp(response, file, fileList, scope.$index) |
| | | " |
| | | :on-exceed="onExceed" |
| | | :show-file-list="false" |
| | | > |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
| | | >{{ o.name }}</el-button |
| | | > |
| | | :on-exceed="onExceed" |
| | | :show-file-list="false"> |
| | | <el-button link |
| | | type="primary" |
| | | :disabled="isOperationDisabled(o, scope.row)" |
| | | :style="{ |
| | | color: getOperationColor(o, scope.row), |
| | | }">{{ o.name }}</el-button> |
| | | </el-upload> |
| | | </template> |
| | | </div> |
| | | <!-- å¯ç¹å»çæå --> |
| | | <div |
| | | v-else-if="item.dataType == 'link'" |
| | | class="cell link" |
| | | style="width: 100%" |
| | | @click="goLink(scope.row, item.linkMethod)" |
| | | > |
| | | <div v-else-if="item.dataType == 'link'" |
| | | class="cell link" |
| | | style="width: 100%" |
| | | @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" |
| | | :total="page.total" |
| | | :layout="page.layout" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationSearch" |
| | | /> |
| | | <pagination v-if="isShowPagination" |
| | | :total="page.total" |
| | | :layout="page.layout" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationSearch" /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import pagination from "./Pagination.vue"; |
| | | import { ref, inject, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import pagination from "./Pagination.vue"; |
| | | import { computed, ref, inject, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | |
| | | // è·åå
¨å±ç uploadHeader |
| | | const { proxy } = getCurrentInstance(); |
| | | const uploadHeader = proxy.uploadHeader; |
| | | const javaApi = proxy.javaApi; |
| | | // è·åå
¨å±ç uploadHeader |
| | | const { proxy } = getCurrentInstance(); |
| | | 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) => { |
| | | return typeof val === "function" ? val(row) : val; |
| | | }; |
| | | // Filters |
| | | const typeFn = (val, row) => { |
| | | return typeof val === "function" ? val(row) : val; |
| | | }; |
| | | |
| | | const formatters = (val, format) => { |
| | | return typeof format === "function" ? format(val) : val; |
| | | }; |
| | | const formatters = (val, format) => { |
| | | return typeof format === "function" ? format(val) : val; |
| | | }; |
| | | |
| | | // Propsï¼ä½¿ç¨ defineProps çé TS å½¢å¼ï¼ |
| | | const props = defineProps({ |
| | | tableLoading: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | height: { |
| | | type: [Number, String], |
| | | default: "calc(100vh - 22em)", |
| | | }, |
| | | expandRowKeys: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | summaryMethod: { |
| | | type: Function, |
| | | default: () => {}, |
| | | }, |
| | | rowClick: { |
| | | type: Function, |
| | | default: () => {}, |
| | | }, |
| | | currentChange: { |
| | | type: Function, |
| | | default: () => {}, |
| | | }, |
| | | border: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | isSelection: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | isShowPagination: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | isShowSummary: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | highlightCurrentRow: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | headerCellStyle: { |
| | | type: Object, |
| | | default: () => ({}), |
| | | }, |
| | | column: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | rowClassName: { |
| | | type: Function, |
| | | default: () => "", |
| | | }, |
| | | rowStyle: { |
| | | type: [Object, Function], |
| | | default: () => ({}), |
| | | }, |
| | | tableData: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | rowKey: { |
| | | type: String, |
| | | default: 'id', |
| | | }, |
| | | page: { |
| | | type: Object, |
| | | default: () => ({ |
| | | total: 0, |
| | | current: 0, |
| | | size: 10, |
| | | layout: "total, sizes, prev, pager, next, jumper", |
| | | }), |
| | | }, |
| | | total: { |
| | | type: Number, |
| | | default: 0, |
| | | }, |
| | | tableStyle: { |
| | | type: [String, Object], |
| | | default: () => ({ width: "100%" }), |
| | | }, |
| | | }); |
| | | // Propsï¼ä½¿ç¨ defineProps çé TS å½¢å¼ï¼ |
| | | const props = defineProps({ |
| | | tableLoading: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | height: { |
| | | type: [Number, String], |
| | | default: "calc(100vh - 22em)", |
| | | }, |
| | | expandRowKeys: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | summaryMethod: { |
| | | type: Function, |
| | | default: () => {}, |
| | | }, |
| | | rowClick: { |
| | | type: Function, |
| | | default: () => {}, |
| | | }, |
| | | currentChange: { |
| | | type: Function, |
| | | default: () => {}, |
| | | }, |
| | | border: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | isSelection: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | selectable: { |
| | | type: Function, |
| | | default: () => true, |
| | | }, |
| | | isShowPagination: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | isShowSummary: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | highlightCurrentRow: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | headerCellStyle: { |
| | | type: Object, |
| | | default: () => ({}), |
| | | }, |
| | | column: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | rowClassName: { |
| | | type: Function, |
| | | default: () => "", |
| | | }, |
| | | rowStyle: { |
| | | type: [Object, Function], |
| | | default: () => ({}), |
| | | }, |
| | | tableData: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | rowKey: { |
| | | type: String, |
| | | default: "id", |
| | | }, |
| | | page: { |
| | | type: Object, |
| | | default: () => ({ |
| | | total: 0, |
| | | current: 0, |
| | | size: 10, |
| | | layout: "total, sizes, prev, pager, next, jumper", |
| | | }), |
| | | }, |
| | | total: { |
| | | type: Number, |
| | | default: 0, |
| | | }, |
| | | tableStyle: { |
| | | type: [String, Object], |
| | | default: () => ({ width: "100%" }), |
| | | }, |
| | | }); |
| | | |
| | | // Data |
| | | const uploadRefs = ref([]); |
| | | const currentFiles = ref({}); |
| | | const uploadKeys = ref({}); |
| | | const mergedHeaderCellStyle = computed(() => ({ |
| | | background: "var(--surface-soft)", |
| | | color: "var(--text-secondary)", |
| | | fontWeight: 600, |
| | | ...props.headerCellStyle, |
| | | })); |
| | | |
| | | const indexMethod = (index) => { |
| | | return (props.page.current - 1) * props.page.size + index + 1; |
| | | }; |
| | | // Data |
| | | const uploadRefs = ref([]); |
| | | const currentFiles = ref({}); |
| | | const uploadKeys = ref({}); |
| | | |
| | | // ç¹å» link äºä»¶ |
| | | const goLink = (row, linkMethod) => { |
| | | if (!linkMethod) { |
| | | return ElMessage.warning("请é
ç½® link äºä»¶"); |
| | | } |
| | | const parentMethod = getParentMethod(linkMethod); |
| | | if (typeof parentMethod === "function") { |
| | | parentMethod(row); |
| | | } else { |
| | | console.warn(`ç¶ç»ä»¶ä¸æªæ¾å°æ¹æ³: ${linkMethod}`); |
| | | } |
| | | }; |
| | | const indexMethod = index => { |
| | | return (props.page.current - 1) * props.page.size + index + 1; |
| | | }; |
| | | |
| | | // è·åç¶ç»ä»¶æ¹æ³ï¼ç¤ºä¾å®ç°ï¼ |
| | | const getParentMethod = (methodName) => { |
| | | const parentMethods = inject("parentMethods", {}); |
| | | return parentMethods[methodName]; |
| | | }; |
| | | |
| | | const dataTypeFn = (val, format) => { |
| | | if (typeof format === "function") { |
| | | return format(val); |
| | | } else return val; |
| | | }; |
| | | |
| | | const formatType = (val, format) => { |
| | | if (typeof format === "function") { |
| | | return format(val); |
| | | } else return ""; |
| | | }; |
| | | |
| | | // æä»¶ååå¤ç |
| | | const handleChange = (file, fileList, index) => { |
| | | if (fileList.length > 1) { |
| | | const earliestFile = fileList[0]; |
| | | uploadRefs.value[index]?.handleRemove(earliestFile); |
| | | } |
| | | currentFiles.value[index] = file; |
| | | }; |
| | | |
| | | // æä»¶ä¸ä¼ åæ ¡éª |
| | | const beforeUpload = (rawFile, index) => { |
| | | currentFiles.value[index] = {}; |
| | | if (rawfile.size > 1024 * 1024 * 10 * 10) { |
| | | ElMessage.error("ä¸ä¼ æä»¶ä¸è¶
è¿10M"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | // ä¸ä¼ æå |
| | | const handleSuccessUp = (response, file, fileList, index) => { |
| | | if (response.code == 200) { |
| | | if (uploadRefs[index]) { |
| | | uploadRefs[index].clearFiles(); |
| | | // ç¹å» link äºä»¶ |
| | | const goLink = (row, linkMethod) => { |
| | | if (!linkMethod) { |
| | | return ElMessage.warning("请é
ç½® link äºä»¶"); |
| | | } |
| | | currentFiles[index] = file; |
| | | ElMessage.success("ä¸ä¼ æå"); |
| | | resetUploadComponent(index); |
| | | } else { |
| | | ElMessage.error(response.message); |
| | | } |
| | | }; |
| | | const parentMethod = getParentMethod(linkMethod); |
| | | if (typeof parentMethod === "function") { |
| | | parentMethod(row); |
| | | } else { |
| | | console.warn(`ç¶ç»ä»¶ä¸æªæ¾å°æ¹æ³: ${linkMethod}`); |
| | | } |
| | | }; |
| | | |
| | | const resetUploadComponent = (index) => { |
| | | uploadKeys[index] = Date.now(); |
| | | }; |
| | | // è·åç¶ç»ä»¶æ¹æ³ï¼ç¤ºä¾å®ç°ï¼ |
| | | const getParentMethod = methodName => { |
| | | const parentMethods = inject("parentMethods", {}); |
| | | return parentMethods[methodName]; |
| | | }; |
| | | |
| | | // ä¸ä¼ 失败 |
| | | const onError = (error, file, fileList, index) => { |
| | | ElMessage.error("æä»¶ä¸ä¼ 失败ï¼è¯·éè¯"); |
| | | if (uploadRefs.value[index]) { |
| | | uploadRefs.value[index].clearFiles(); |
| | | } |
| | | }; |
| | | const dataTypeFn = (val, format) => { |
| | | if (typeof format === "function") { |
| | | return format(val); |
| | | } else return val; |
| | | }; |
| | | const validTagTypes = ["primary", "success", "info", "warning", "danger"]; |
| | | |
| | | // æä»¶æ°éè¶
éæç¤º |
| | | const onExceed = () => { |
| | | ElMessage.warning("è¶
åºæä»¶ä¸ªæ°"); |
| | | }; |
| | | const formatType = (val, format) => { |
| | | const type = typeof format === "function" ? format(val) : undefined; |
| | | return validTagTypes.includes(type) ? type : undefined; |
| | | }; |
| | | |
| | | const paginationSearch = ({ page, limit }) => { |
| | | emit("pagination", { page: page, limit: limit }); |
| | | }; |
| | | const isOperationDisabled = (operation, row) => { |
| | | if (!operation?.disabled) return false; |
| | | return typeof operation.disabled === "function" |
| | | ? !!operation.disabled(row) |
| | | : !!operation.disabled; |
| | | }; |
| | | |
| | | const rowClick = (row) => { |
| | | emit("row-click", row); |
| | | }; |
| | | 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; |
| | | 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; |
| | | return { r, g, b }; |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const expandChange = (row, expandedRows) => { |
| | | emit("expand-change", row, expandedRows); |
| | | }; |
| | | const fadeColor = (color, alpha = 0.35) => { |
| | | const c = String(color || "").trim(); |
| | | if (!c) return undefined; |
| | | if (c.startsWith("#")) { |
| | | const rgb = parseHexToRgb(c); |
| | | 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 |
| | | ); |
| | | 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; |
| | | return `rgba(${r}, ${g}, ${b}, ${alpha})`; |
| | | } |
| | | if (c.includes("--el-color-primary")) { |
| | | return "var(--el-color-primary-light-5)"; |
| | | } |
| | | if (c.includes("--el-color-danger")) { |
| | | return "var(--el-color-danger-light-5)"; |
| | | } |
| | | return "var(--el-text-color-disabled)"; |
| | | }; |
| | | |
| | | const handleSelectionChange = (newSelection) => { |
| | | emit("selection-change", newSelection); |
| | | }; |
| | | const getOperationColor = (operation, row) => { |
| | | const baseColor = |
| | | operation?.name === "å é¤" || operation?.name === "delete" |
| | | ? "#D93025" |
| | | : operation?.name === "详æ
" |
| | | ? "#67C23A" |
| | | : operation?.color || "var(--el-color-primary)"; |
| | | |
| | | if (isOperationDisabled(operation, row)) { |
| | | return fadeColor(baseColor, 0.35); |
| | | } |
| | | return baseColor; |
| | | }; |
| | | |
| | | // æä»¶ååå¤ç |
| | | const handleChange = (file, fileList, index) => { |
| | | if (fileList.length > 1) { |
| | | const earliestFile = fileList[0]; |
| | | uploadRefs.value[index]?.handleRemove(earliestFile); |
| | | } |
| | | currentFiles.value[index] = file; |
| | | }; |
| | | |
| | | // æä»¶ä¸ä¼ åæ ¡éª |
| | | const beforeUpload = (rawFile, index) => { |
| | | currentFiles.value[index] = {}; |
| | | if (rawfile.size > 1024 * 1024 * 10 * 10) { |
| | | ElMessage.error("ä¸ä¼ æä»¶ä¸è¶
è¿10M"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | // ä¸ä¼ æå |
| | | const handleSuccessUp = (response, file, fileList, index) => { |
| | | if (response.code == 200) { |
| | | if (uploadRefs[index]) { |
| | | uploadRefs[index].clearFiles(); |
| | | } |
| | | currentFiles[index] = file; |
| | | ElMessage.success("ä¸ä¼ æå"); |
| | | resetUploadComponent(index); |
| | | } else { |
| | | ElMessage.error(response.message); |
| | | } |
| | | }; |
| | | |
| | | const resetUploadComponent = index => { |
| | | uploadKeys[index] = Date.now(); |
| | | }; |
| | | |
| | | // ä¸ä¼ 失败 |
| | | const onError = (error, file, fileList, index) => { |
| | | ElMessage.error("æä»¶ä¸ä¼ 失败ï¼è¯·éè¯"); |
| | | if (uploadRefs.value[index]) { |
| | | uploadRefs.value[index].clearFiles(); |
| | | } |
| | | }; |
| | | |
| | | // æä»¶æ°éè¶
éæç¤º |
| | | const onExceed = () => { |
| | | ElMessage.warning("è¶
åºæä»¶ä¸ªæ°"); |
| | | }; |
| | | |
| | | const paginationSearch = ({ page, limit }) => { |
| | | emit("pagination", { page: page, limit: limit }); |
| | | }; |
| | | |
| | | const rowClick = row => { |
| | | emit("row-click", row); |
| | | }; |
| | | |
| | | const expandChange = (row, expandedRows) => { |
| | | emit("expand-change", row, expandedRows); |
| | | }; |
| | | |
| | | const handleSelectionChange = newSelection => { |
| | | emit("selection-change", newSelection); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .cell { |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | padding-right: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | .lims-table { |
| | | border: 1px solid var(--surface-border); |
| | | border-radius: 18px; |
| | | background: rgba(255, 255, 255, 0.9); |
| | | } |
| | | |
| | | .pim-table-header-extra :deep(.el-input), |
| | | .pim-table-header-extra :deep(.el-select) { |
| | | width: 100%; |
| | | } |
| | | .cell { |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | padding-right: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | .pim-table-header-extra :deep(.el-input), |
| | | .pim-table-header-extra :deep(.el-select) { |
| | | width: 100%; |
| | | } |
| | | |
| | | .pim-table-header-title { |
| | | font-weight: 600; |
| | | } |
| | | </style> |
| | |
| | | <style scoped> |
| | | .pagination-container { |
| | | background: #fff; |
| | | padding: 16px 0; |
| | | margin-top: 0; |
| | | } |
| | | .pagination-container.hidden { |
| | |
| | | <style scoped> |
| | | .page-header-wrapper { |
| | | margin-bottom: 16px; |
| | | padding: 16px 18px; |
| | | border: 1px solid var(--surface-border); |
| | | border-radius: var(--radius-md); |
| | | background: rgba(255, 255, 255, 0.82); |
| | | box-shadow: var(--shadow-sm); |
| | | } |
| | | |
| | | .page-header-wrapper :deep(.el-page-header__extra) { |
| | |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .page-header-wrapper :deep(.el-page-header__content) { |
| | | font-weight: 600; |
| | | color: var(--text-primary); |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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" |
| | | 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" |
| | | 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 editParamRules = ref({ |
| | | // standardValue: [{ required: true, message: "请è¾å
¥æ åå¼", 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 { ShoppingCart } from '@element-plus/icons-vue' |
| | | import AIChatSidebar from '@/components/AIChatSidebar/index.vue' |
| | | |
| | | const assistants = [ |
| | | { |
| | | key: 'purchase', |
| | | label: 'éè´å©ç', |
| | | title: 'éè´æºè½å©ç', |
| | | tooltip: 'éè´æºè½å©ç', |
| | | icon: ShoppingCart, |
| | | apiBase: '/purchase-ai', |
| | | storageKey: 'purchase_ai_chat_uuid', |
| | | placeholder: '请è¾å
¥éè´é®é¢... (Enter åé, Shift+Enter æ¢è¡)', |
| | | welcomeMessage: 'ä½ å¥½', |
| | | allowFileUpload: true, |
| | | allowMultipleFileUpload: true, |
| | | fileAnalyzeUrl: '/purchase-ai/analyze-files', |
| | | emptySessionText: 'ææ éè´ä¼è¯' |
| | | } |
| | | ] |
| | | </script> |
| | |
| | | props: {
|
| | | iconClass: {
|
| | | type: String,
|
| | | required: true
|
| | | default: ''
|
| | | },
|
| | | className: {
|
| | | type: String,
|
| | |
| | | width: 100%;
|
| | | position: relative;
|
| | | overflow: hidden;
|
| | | background: #F5F7FB;
|
| | | background: transparent;
|
| | | }
|
| | |
|
| | | .route-view-wrapper {
|
| | | width: 100%;
|
| | | height: 100%;
|
| | | padding: 120px 16px 24px 0;
|
| | | }
|
| | |
|
| | | .fixed-header + .app-main {
|
| | | padding-top: 50px;
|
| | | padding-top: 0;
|
| | | }
|
| | |
|
| | | .hasTagsView {
|
| | |
| | | }
|
| | |
|
| | | .fixed-header + .app-main {
|
| | | padding-top: 84px;
|
| | | padding-top: 0;
|
| | | }
|
| | | }
|
| | | </style>
|
| | |
| | | }
|
| | |
|
| | | ::-webkit-scrollbar-track {
|
| | | background-color: #f1f1f1;
|
| | | background-color: rgba(218, 225, 220, 0.8);
|
| | | }
|
| | |
|
| | | ::-webkit-scrollbar-thumb {
|
| | | background-color: #c0c0c0;
|
| | | background-color: #b2bdb5;
|
| | | border-radius: 3px;
|
| | | }
|
| | | </style>
|
| | |
| | | </script>
|
| | |
|
| | | <style lang='scss' scoped>
|
| | | .navbar {
|
| | | height: 50px;
|
| | | overflow: hidden;
|
| | | position: relative;
|
| | | background: var(--navbar-bg);
|
| | | box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
| | | .navbar { |
| | | height: 56px; |
| | | overflow: hidden; |
| | | position: relative; |
| | | background: var(--navbar-bg); |
| | | border: 1px solid rgba(216, 225, 219, 0.9); |
| | | border-radius: 22px; |
| | | backdrop-filter: blur(18px); |
| | | box-shadow: var(--shadow-sm); |
| | | padding: 0 18px; |
| | |
|
| | | .hamburger-container {
|
| | | line-height: 46px;
|
| | | line-height: 52px; |
| | | height: 100%;
|
| | | float: left;
|
| | | cursor: pointer;
|
| | |
| | | -webkit-tap-highlight-color: transparent;
|
| | |
|
| | | &:hover {
|
| | | background: rgba(0, 0, 0, 0.025);
|
| | | background: var(--navbar-hover); |
| | | }
|
| | | }
|
| | |
|
| | |
| | | vertical-align: top;
|
| | | }
|
| | |
|
| | | .right-menu {
|
| | | float: right;
|
| | | height: 100%;
|
| | | line-height: 50px;
|
| | | display: flex;
|
| | | .right-menu { |
| | | float: right; |
| | | height: 100%; |
| | | align-items: center; |
| | | display: flex; |
| | |
|
| | | &:focus {
|
| | | outline: none;
|
| | | }
|
| | |
|
| | | .right-menu-item {
|
| | | display: inline-block;
|
| | | padding: 0 8px;
|
| | | height: 100%;
|
| | | font-size: 18px;
|
| | | color: var(--navbar-text);
|
| | | vertical-align: text-bottom;
|
| | | .right-menu-item { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 0 8px; |
| | | height: 100%; |
| | | font-size: 18px; |
| | | color: var(--navbar-text); |
| | | border-radius: 14px; |
| | |
|
| | | &.hover-effect {
|
| | | cursor: pointer;
|
| | | transition: background 0.3s;
|
| | |
|
| | | &:hover {
|
| | | background: rgba(0, 0, 0, 0.025);
|
| | | background: var(--navbar-hover); |
| | | }
|
| | | }
|
| | |
|
| | |
| | | }
|
| | |
|
| | | .notification-container {
|
| | | margin-right: 20px;
|
| | | margin-right: 12px; |
| | | display: flex;
|
| | | align-items: center;
|
| | | cursor: pointer;
|
| | |
| | | }
|
| | | }
|
| | |
|
| | | .avatar-container {
|
| | | margin-right: 40px;
|
| | |
|
| | | .avatar-wrapper {
|
| | | margin-top: 5px;
|
| | | position: relative;
|
| | |
|
| | | .user-avatar {
|
| | | cursor: pointer;
|
| | | width: 40px;
|
| | | height: 40px;
|
| | | border-radius: 50px;
|
| | | }
|
| | |
|
| | | i {
|
| | | cursor: pointer;
|
| | | position: absolute;
|
| | | right: -20px;
|
| | | top: 14px;
|
| | | font-size: 12px;
|
| | | }
|
| | | }
|
| | | .avatar-container { |
| | | margin-right: 4px; |
| | | height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | :deep(.el-dropdown) { |
| | | height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .avatar-wrapper { |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 10px; |
| | | padding: 6px 10px 6px 6px; |
| | | height: 44px; |
| | | border-radius: 999px; |
| | | background: rgba(247, 250, 248, 0.92); |
| | | border: 1px solid var(--surface-border); |
| | | |
| | | .user-avatar { |
| | | cursor: pointer; |
| | | width: 34px; |
| | | height: 34px; |
| | | border-radius: 50px; |
| | | } |
| | | |
| | | i { |
| | | cursor: pointer; |
| | | position: static; |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | }
|
| | | }
|
| | | }
|
| | |
| | | </style>
|
| | |
|
| | | <style lang="scss">
|
| | | .notification-popover {
|
| | | padding: 0 !important;
|
| | | .notification-popover { |
| | | padding: 0 !important; |
| | | border-radius: 20px !important; |
| | | border: 1px solid var(--surface-border) !important; |
| | | box-shadow: var(--shadow-md) !important; |
| | |
|
| | | .el-popover__title {
|
| | | display: none;
|
| | |
| | | <template>
|
| | | <div class="sidebar-logo-container" :class="{ 'collapse': collapse }">
|
| | | <transition name="sidebarLogoFade">
|
| | | <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
| | | <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="å
¬å¸Logo" />
|
| | | <h1 class="sidebar-title">{{ title }}</h1>
|
| | | </router-link>
|
| | | <router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
| | | <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="å
¬å¸Logo" />
|
| | | <h1 class="sidebar-title">{{ title }}</h1>
|
| | | </router-link>
|
| | | </transition>
|
| | | </div>
|
| | | </template>
|
| | |
|
| | | <script setup>
|
| | | import { ref, computed, onMounted, watch } from 'vue'
|
| | | import useUserStore from '@/store/modules/user'
|
| | | import defaultLogo from '@/assets/logo/logo.png' // 导å
¥é»è®¤logo
|
| | |
|
| | | defineProps({
|
| | | collapse: {
|
| | | type: Boolean,
|
| | | required: true
|
| | | }
|
| | | })
|
| | |
|
| | | const title = import.meta.env.VITE_APP_TITLE
|
| | | const userStore = useUserStore()
|
| | |
|
| | | // å¤çå·¥ååç§°ï¼çæåæ³çæä»¶å
|
| | | const cleanFactoryName = computed(() => {
|
| | | if (!userStore.currentFactoryName) return ''
|
| | | return userStore.currentFactoryName.trim()
|
| | | })
|
| | |
|
| | | // 卿logoè·¯å¾
|
| | | const logoUrl = ref('')
|
| | |
|
| | | // æ£æ¥logoæ¯å¦åå¨å¹¶è®¾ç½®url
|
| | | const updateLogoUrl = () => {
|
| | | if (!cleanFactoryName.value) {
|
| | | logoUrl.value = defaultLogo
|
| | | return
|
| | | }
|
| | |
|
| | | // 使ç¨Viteç卿坼å
¥
|
| | | try {
|
| | | const dynamicLogo = import.meta.glob('/src/assets/logo/*.png', { eager: true })
|
| | | const logoPath = `/src/assets/logo/${cleanFactoryName.value}.png`
|
| | |
|
| | | if (dynamicLogo[logoPath]) {
|
| | | logoUrl.value = dynamicLogo[logoPath].default
|
| | | } else {
|
| | | logoUrl.value = defaultLogo
|
| | | }
|
| | | } catch (error) {
|
| | | console.error('å 载工åLogo失败:', error)
|
| | | logoUrl.value = defaultLogo
|
| | | }
|
| | | }
|
| | |
|
| | | // åå§ååçå¬åå
|
| | | onMounted(() => {
|
| | | updateLogoUrl()
|
| | |
|
| | | // çå¬å·¥ååç§°åå
|
| | | watch(() => userStore.currentFactoryName, updateLogoUrl)
|
| | | })
|
| | |
|
| | | // å¾çå è½½é误å¤ç
|
| | | const handleImageError = (event) => {
|
| | | console.warn('Logoå 载失败ï¼ä½¿ç¨é»è®¤Logo')
|
| | | logoUrl.value = defaultLogo
|
| | | }
|
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | | @import '@/assets/styles/variables.module.scss';
|
| | |
|
| | | .sidebarLogoFade-enter-active {
|
| | | transition: opacity 1.5s;
|
| | | }
|
| | |
|
| | | .sidebarLogoFade-enter,
|
| | | .sidebarLogoFade-leave-to {
|
| | | opacity: 0;
|
| | | }
|
| | |
|
| | | .sidebar-logo-container {
|
| | | position: relative;
|
| | | width: 100% !important;
|
| | | height: 50px !important;
|
| | | line-height: 50px;
|
| | | background: #fff;
|
| | | text-align: center;
|
| | | overflow: hidden;
|
| | |
|
| | | & .sidebar-logo-link {
|
| | | height: 100%;
|
| | | width: 100%;
|
| | |
|
| | | & .sidebar-logo {
|
| | | width: 100%;
|
| | | height: 100%;
|
| | | // height: 32px;
|
| | | vertical-align: middle;
|
| | | margin-right: 12px;
|
| | | }
|
| | |
|
| | | & .sidebar-title {
|
| | | display: inline-block;
|
| | | margin: 0;
|
| | | color: v-bind(getLogoTextColor);
|
| | | font-weight: 600;
|
| | | line-height: 50px;
|
| | | font-size: 14px;
|
| | | font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
| | | vertical-align: middle;
|
| | | }
|
| | | }
|
| | |
|
| | | &.collapse {
|
| | | .sidebar-logo {
|
| | | margin-right: 0px;
|
| | | }
|
| | | }
|
| | | }
|
| | | </style>
|
| | | <template> |
| | | <div class="sidebar-logo-container" :class="{ collapse }"> |
| | | <transition name="sidebarLogoFade"> |
| | | <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> |
| | | <img :src="faviconUrl" class="sidebar-logo sidebar-favicon" alt="ç«ç¹å¾æ " /> |
| | | </router-link> |
| | | <router-link v-else key="expand" class="sidebar-logo-link" to="/"> |
| | | <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="å
¬å¸Logo" /> |
| | | <h1 v-if="!logoUrl" class="sidebar-title">{{ title }}</h1> |
| | | </router-link> |
| | | </transition> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, watch } from 'vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import defaultLogo from '@/assets/logo/logo.png' |
| | | |
| | | defineProps({ |
| | | collapse: { |
| | | type: Boolean, |
| | | required: true |
| | | } |
| | | }) |
| | | |
| | | const title = import.meta.env.VITE_APP_TITLE |
| | | const userStore = useUserStore() |
| | | const baseUrl = import.meta.env.BASE_URL || '/' |
| | | const faviconUrl = `${baseUrl.replace(/\/?$/, '/') }favicon.ico`.replace(/([^:]\/)\/+/g, '$1') |
| | | |
| | | const cleanFactoryName = computed(() => { |
| | | if (!userStore.currentFactoryName) return '' |
| | | return userStore.currentFactoryName.trim() |
| | | }) |
| | | |
| | | const logoUrl = ref('') |
| | | |
| | | const updateLogoUrl = () => { |
| | | if (!cleanFactoryName.value) { |
| | | logoUrl.value = defaultLogo |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const dynamicLogo = import.meta.glob('/src/assets/logo/*.png', { eager: true }) |
| | | const logoPath = `/src/assets/logo/${cleanFactoryName.value}.png` |
| | | |
| | | if (dynamicLogo[logoPath]) { |
| | | logoUrl.value = dynamicLogo[logoPath].default |
| | | } else { |
| | | logoUrl.value = defaultLogo |
| | | } |
| | | } catch (error) { |
| | | console.error('å 载工å Logo 失败:', error) |
| | | logoUrl.value = defaultLogo |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | updateLogoUrl() |
| | | watch(() => userStore.currentFactoryName, updateLogoUrl) |
| | | }) |
| | | |
| | | const handleImageError = () => { |
| | | logoUrl.value = defaultLogo |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import '@/assets/styles/variables.module.scss'; |
| | | |
| | | .sidebarLogoFade-enter-active { |
| | | transition: opacity 1.5s; |
| | | } |
| | | |
| | | .sidebarLogoFade-enter, |
| | | .sidebarLogoFade-leave-to { |
| | | opacity: 0; |
| | | } |
| | | |
| | | .sidebar-logo-container { |
| | | position: relative; |
| | | width: 100% !important; |
| | | height: 56px !important; |
| | | line-height: 56px; |
| | | background: rgba(255, 255, 255, 0.78); |
| | | border: 1px solid var(--surface-border); |
| | | border-radius: 22px; |
| | | text-align: center; |
| | | overflow: hidden; |
| | | box-shadow: var(--shadow-sm); |
| | | |
| | | .sidebar-logo-link { |
| | | height: 100%; |
| | | width: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 0 18px 0 14px; |
| | | } |
| | | |
| | | .sidebar-logo { |
| | | width: auto; |
| | | max-width: 250px; |
| | | max-height: 50px; |
| | | height: auto; |
| | | vertical-align: middle; |
| | | object-fit: contain; |
| | | object-position: center; |
| | | } |
| | | |
| | | .sidebar-title { |
| | | display: inline-block; |
| | | margin: 0; |
| | | color: var(--text-primary); |
| | | font-weight: 600; |
| | | line-height: 1.2; |
| | | font-size: 14px; |
| | | font-family: "Segoe UI", "PingFang SC", sans-serif; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | &.collapse { |
| | | .sidebar-logo-link { |
| | | padding: 0; |
| | | } |
| | | |
| | | .sidebar-logo { |
| | | max-width: 30px; |
| | | max-height: 30px; |
| | | } |
| | | |
| | | .sidebar-favicon { |
| | | width: 24px; |
| | | height: 24px; |
| | | max-width: 24px; |
| | | max-height: 24px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <template>
|
| | | <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" :collapse="isCollapse" :background-color="getMenuBackground"
|
| | | :text-color="getMenuTextColor" :unique-opened="true" :active-text-color="theme" :collapse-transition="false"
|
| | | mode="vertical" :class="sideTheme">
|
| | | <sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route"
|
| | | :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'
|
| | |
|
| | | 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 getMenuBackground = computed(() => {
|
| | | if (settingsStore.isDark) {
|
| | | return 'var(--sidebar-bg)'
|
| | | }
|
| | | // æµ
è²ä¸»é¢æ¶ï¼ç´æ¥ç¨ä¸»é¢è²
|
| | | return sideTheme.value === 'theme-dark' ? variables.menuBg : settingsStore.theme
|
| | | })
|
| | |
|
| | | // è·åèåæåé¢è²
|
| | | const getMenuTextColor = computed(() => {
|
| | | if (settingsStore.isDark) {
|
| | | return 'var(--sidebar-text)'
|
| | | }
|
| | | return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText
|
| | | })
|
| | |
|
| | | const activeMenu = computed(() => {
|
| | | const { meta, path } = route
|
| | | if (meta.activeMenu) {
|
| | | return meta.activeMenu
|
| | | }
|
| | | return path
|
| | | })
|
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | | .sidebar-container {
|
| | | background-color: v-bind(getMenuBackground);
|
| | |
|
| | | .scrollbar-wrapper {
|
| | | background-color: v-bind(getMenuBackground);
|
| | | }
|
| | |
|
| | | .el-menu {
|
| | | border: none;
|
| | | height: 100%;
|
| | | width: 100% !important;
|
| | |
|
| | | .el-menu-item,
|
| | | .el-sub-menu__title {
|
| | | &:hover {
|
| | | background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
|
| | | }
|
| | | }
|
| | |
|
| | | .el-menu-item {
|
| | | color: v-bind(getMenuTextColor);
|
| | |
|
| | | &.is-active {
|
| | | color: var(--menu-active-text, #409eff);
|
| | | background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
|
| | | }
|
| | | }
|
| | |
|
| | | .el-sub-menu__title {
|
| | | color: v-bind(getMenuTextColor);
|
| | | }
|
| | | }
|
| | | }
|
| | | </style>
|
| | | <template> |
| | | <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" |
| | | :collapse="isCollapse" |
| | | :background-color="getMenuBackground" |
| | | :text-color="getMenuTextColor" |
| | | :unique-opened="true" |
| | | :active-text-color="theme" |
| | | :collapse-transition="false" |
| | | mode="vertical" |
| | | :class="sideTheme"> |
| | | <sidebar-item v-for="(route, index) in sidebarRouters" |
| | | :key="route.path + index" |
| | | :item="route" |
| | | :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"; |
| | | |
| | | 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 getMenuBackground = computed(() => "var(--sidebar-bg)"); |
| | | |
| | | const getMenuTextColor = computed(() => { |
| | | if (settingsStore.isDark) { |
| | | return "var(--sidebar-text)"; |
| | | } |
| | | return sideTheme.value === "theme-dark" |
| | | ? variables.menuText |
| | | : variables.menuLightText; |
| | | }); |
| | | |
| | | const activeMenu = computed(() => { |
| | | const { meta, path } = route; |
| | | if (meta.activeMenu) { |
| | | return meta.activeMenu; |
| | | } |
| | | return path; |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .sidebar-container { |
| | | background-color: v-bind(getMenuBackground); |
| | | border-radius: 22px; |
| | | overflow: hidden; |
| | | |
| | | .scrollbar-wrapper { |
| | | background-color: v-bind(getMenuBackground); |
| | | } |
| | | |
| | | .el-menu { |
| | | border: none; |
| | | height: 100%; |
| | | width: 100% !important; |
| | | border-radius: 22px; |
| | | |
| | | .el-menu-item, |
| | | .el-sub-menu__title { |
| | | margin-bottom: 6px; |
| | | border-radius: 14px; |
| | | color: v-bind(getMenuTextColor); |
| | | |
| | | &:hover { |
| | | background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important; |
| | | border-radius: 14px; |
| | | } |
| | | } |
| | | |
| | | .el-menu-item { |
| | | &.is-active { |
| | | color: v-bind(theme); |
| | | background-color: var(--menu-active-bg, rgba(0, 0, 0, 0.06)) !important; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | |
| | | .el-sub-menu__title { |
| | | color: v-bind(getMenuTextColor); |
| | | } |
| | | |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title) { |
| | | color: v-bind(theme) !important; |
| | | font-weight: 600; |
| | | 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; |
| | | padding-left: 10px !important; |
| | | padding-right: 10px !important; |
| | | box-sizing: border-box; |
| | | overflow: hidden; |
| | | background-clip: padding-box; |
| | | } |
| | | |
| | | :deep(.el-menu-item.is-active) { |
| | | margin: 0 10px 6px !important; |
| | | width: calc(100% - 20px) !important; |
| | | padding-left: 10px !important; |
| | | padding-right: 10px !important; |
| | | box-sizing: border-box; |
| | | overflow: hidden; |
| | | background-clip: padding-box; |
| | | border-radius: 14px; |
| | | } |
| | | |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title .menu-title), |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title .svg-icon), |
| | | :deep(.el-menu-item.is-active .menu-title), |
| | | :deep(.el-menu-item.is-active .svg-icon) { |
| | | color: v-bind(theme) !important; |
| | | } |
| | | |
| | | :deep(.el-sub-menu__title:hover), |
| | | :deep(.el-menu-item:hover) { |
| | | border-radius: 14px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | bottom: 0px;
|
| | | }
|
| | | :deep(.el-scrollbar__wrap) {
|
| | | height: 39px;
|
| | | height: 42px;
|
| | | }
|
| | | }
|
| | | </style> |
| | |
| | | @contextmenu.prevent="openMenu(tag, $event)"
|
| | | >
|
| | | {{ tag.title }}
|
| | | <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
|
| | | <close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
|
| | | </span>
|
| | | <span v-if="!isAffix(tag)" class="tags-view-close" @click.prevent.stop="closeSelectedTag(tag)"> |
| | | <close class="el-icon-close" /> |
| | | </span> |
| | | </router-link>
|
| | | </scroll-pane>
|
| | | <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
|
| | |
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | | .tags-view-container {
|
| | | height: 34px;
|
| | | width: 100%;
|
| | | background: transparent;
|
| | |
|
| | | .tags-view-wrapper {
|
| | | .tags-view-item {
|
| | | display: inline-block;
|
| | | position: relative;
|
| | | cursor: pointer;
|
| | | height: 32px;
|
| | | line-height: 32px;
|
| | | //border: 1px solid var(--tags-item-border, #d8dce5);
|
| | | color: #4E5463;
|
| | | background: #E5E7EA;
|
| | | padding: 0 16px;
|
| | | font-size: 12px;
|
| | | //margin-left: 5px;
|
| | | //margin-top: 4px;
|
| | |
|
| | | //&:first-of-type {
|
| | | // margin-left: 8px;
|
| | | //}
|
| | | //
|
| | | //&:last-of-type {
|
| | | // margin-right: 15px;
|
| | | //}
|
| | |
|
| | | &.active {
|
| | | background-color: #FFFFFF !important;
|
| | | color: #2C51D9;
|
| | | }
|
| | | }
|
| | | //.tags-view-item div {
|
| | | // transform: skew(12deg);
|
| | | // display: inline-block;
|
| | | //}
|
| | | }
|
| | | .tags-view-container { |
| | | height: 42px; |
| | | width: 100%; |
| | | margin-top: 10px; |
| | | padding: 4px 10px; |
| | | background: rgba(255, 255, 255, 0.9); |
| | | border: 1px solid rgba(216, 225, 219, 0.92); |
| | | border-radius: 20px; |
| | | backdrop-filter: blur(18px); |
| | | box-shadow: var(--shadow-sm); |
| | | |
| | | .tags-view-wrapper { |
| | | display: flex; |
| | | align-items: center; |
| | | min-height: 42px; |
| | | |
| | | .tags-view-item { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | position: relative; |
| | | cursor: pointer; |
| | | height: 34px; |
| | | line-height: 1; |
| | | color: var(--tags-item-text, #4E5463); |
| | | background: var(--tags-item-bg, #E5E7EA); |
| | | border: 1px solid var(--tags-item-border, #d8dce5); |
| | | border-radius: 999px; |
| | | padding: 0 16px; |
| | | font-size: 12px; |
| | | margin-right: 8px; |
| | | flex-shrink: 0; |
| | | gap: 6px; |
| | | transition: all 0.24s ease; |
| | | |
| | | &:hover { |
| | | background: var(--tags-item-hover, #eee); |
| | | border-color: rgba(31, 122, 114, 0.18); |
| | | } |
| | | |
| | | &.active { |
| | | background-color: #FFFFFF !important; |
| | | color: var(--el-color-primary); |
| | | box-shadow: 0 10px 24px rgba(31, 122, 114, 0.12); |
| | | border-color: rgba(31, 122, 114, 0.2) !important; |
| | | } |
| | | } |
| | | } |
| | |
|
| | | .contextmenu {
|
| | | margin: 0;
|
| | |
| | | position: absolute;
|
| | | list-style-type: none;
|
| | | padding: 5px 0;
|
| | | border-radius: 4px;
|
| | | border-radius: 16px; |
| | | font-size: 12px;
|
| | | font-weight: 400;
|
| | | color: var(--tags-item-text, #333);
|
| | | box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
|
| | | border: 1px solid var(--el-border-color-light, #e4e7ed);
|
| | | box-shadow: var(--shadow-md); |
| | | border: 1px solid var(--surface-border, #e4e7ed); |
| | |
|
| | | li {
|
| | | margin: 0;
|
| | |
| | |
|
| | | <style lang="scss">
|
| | | //reset element css of el-icon-close
|
| | | .tags-view-wrapper {
|
| | | .tags-view-item {
|
| | | .el-icon-close {
|
| | | width: 16px;
|
| | | height: 16px;
|
| | | vertical-align: 2px;
|
| | | border-radius: 50%;
|
| | | text-align: center;
|
| | | transition: all .3s cubic-bezier(.645, .045, .355, 1);
|
| | | transform-origin: 100% 50%;
|
| | |
|
| | | &:before {
|
| | | transform: scale(.6);
|
| | | display: inline-block;
|
| | | vertical-align: -3px;
|
| | | }
|
| | |
|
| | | &:hover {
|
| | | background-color: var(--tags-close-hover, #b4bccc);
|
| | | color: #fff;
|
| | | width: 12px !important;
|
| | | height: 12px !important;
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | </style> |
| | | .tags-view-wrapper { |
| | | .el-scrollbar__view { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .tags-view-item { |
| | | .tags-view-close { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 12px; |
| | | height: 12px; |
| | | line-height: 1; |
| | | align-self: center; |
| | | transform: translateY(1px); |
| | | } |
| | | |
| | | .el-icon-close { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 12px; |
| | | height: 12px; |
| | | line-height: 1; |
| | | vertical-align: initial !important; |
| | | border-radius: 50%; |
| | | text-align: center; |
| | | transition: all .3s cubic-bezier(.645, .045, .355, 1); |
| | | transform-origin: 100% 50%; |
| | | align-self: center; |
| | | |
| | | &:before { |
| | | transform: scale(.6); |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | svg { |
| | | display: block; |
| | | width: 10px; |
| | | height: 10px; |
| | | } |
| | | |
| | | &:hover { |
| | | background-color: var(--tags-close-hover, #b4bccc); |
| | | color: #fff; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <template>
|
| | | <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
|
| | | <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
| | | <sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
| | | <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
|
| | | <div :class="{ 'fixed-header': fixedHeader }">
|
| | | <navbar @setLayout="setLayout" />
|
| | | <tags-view v-if="needTagsView" />
|
| | | </div>
|
| | | <app-main />
|
| | | <settings ref="settingRef" />
|
| | | </div>
|
| | | </div>
|
| | | </template>
|
| | |
|
| | | <script setup>
|
| | | import { useWindowSize } from '@vueuse/core'
|
| | | import Sidebar from './components/Sidebar/index.vue'
|
| | | import { AppMain, Navbar, Settings, TagsView } from './components'
|
| | | import defaultSettings from '@/settings'
|
| | |
|
| | | import useAppStore from '@/store/modules/app'
|
| | | import useSettingsStore from '@/store/modules/settings'
|
| | |
|
| | | const settingsStore = useSettingsStore()
|
| | | 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 classObj = computed(() => ({
|
| | | hideSidebar: !sidebar.value.opened,
|
| | | openSidebar: sidebar.value.opened,
|
| | | withoutAnimation: sidebar.value.withoutAnimation,
|
| | | mobile: device.value === 'mobile'
|
| | | }))
|
| | |
|
| | | const { width, height } = useWindowSize()
|
| | | const WIDTH = 992 // refer to Bootstrap's responsive design
|
| | |
|
| | | watch(() => device.value, () => {
|
| | | if (device.value === 'mobile' && sidebar.value.opened) {
|
| | | useAppStore().closeSideBar({ withoutAnimation: false })
|
| | | }
|
| | | })
|
| | |
|
| | | watchEffect(() => {
|
| | | if (width.value - 1 < WIDTH) {
|
| | | useAppStore().toggleDevice('mobile')
|
| | | useAppStore().closeSideBar({ withoutAnimation: true })
|
| | | } else {
|
| | | useAppStore().toggleDevice('desktop')
|
| | | }
|
| | | })
|
| | |
|
| | | function handleClickOutside() {
|
| | | useAppStore().closeSideBar({ withoutAnimation: false })
|
| | | }
|
| | |
|
| | | const settingRef = ref(null)
|
| | | function setLayout() {
|
| | | settingRef.value.openSetting()
|
| | | }
|
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | | @import "@/assets/styles/mixin.scss";
|
| | | @import "@/assets/styles/variables.module.scss";
|
| | |
|
| | | .app-wrapper {
|
| | | @include clearfix;
|
| | | position: relative;
|
| | | height: 100%;
|
| | | width: 100%;
|
| | |
|
| | | &.mobile.openSidebar {
|
| | | position: fixed;
|
| | | top: 0;
|
| | | }
|
| | | }
|
| | |
|
| | | .drawer-bg {
|
| | | background: #000;
|
| | | opacity: 0.3;
|
| | | width: 100%;
|
| | | top: 0;
|
| | | height: 100%;
|
| | | position: absolute;
|
| | | z-index: 999;
|
| | | }
|
| | |
|
| | | .fixed-header {
|
| | | position: fixed;
|
| | | top: 0;
|
| | | right: 0;
|
| | | z-index: 9;
|
| | | width: calc(100% - #{$base-sidebar-width});
|
| | | transition: width 0.28s;
|
| | | }
|
| | |
|
| | | .hideSidebar .fixed-header {
|
| | | width: calc(100% - 54px);
|
| | | }
|
| | |
|
| | | .sidebarHide .fixed-header {
|
| | | width: 100%;
|
| | | }
|
| | |
|
| | | .mobile .fixed-header {
|
| | | width: 100%;
|
| | | }
|
| | | </style> |
| | | <template> |
| | | <div :class="classObj" |
| | | class="app-wrapper" |
| | | :style="{ '--current-color': theme }"> |
| | | <div v-if="device === 'mobile' && sidebar.opened" |
| | | class="drawer-bg" |
| | | @click="handleClickOutside" /> |
| | | <sidebar v-if="!sidebar.hide" |
| | | class="sidebar-container" /> |
| | | <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" |
| | | class="main-container"> |
| | | <div :class="{ 'fixed-header': fixedHeader }"> |
| | | <navbar @setLayout="setLayout" /> |
| | | <tags-view v-if="needTagsView" /> |
| | | </div> |
| | | <app-main /> |
| | | <settings ref="settingRef" /> |
| | | </div> |
| | | <AIChatSidebar v-if="aiEnabled" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { useWindowSize } from "@vueuse/core"; |
| | | 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 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 classObj = computed(() => ({ |
| | | hideSidebar: !sidebar.value.opened, |
| | | openSidebar: sidebar.value.opened, |
| | | withoutAnimation: sidebar.value.withoutAnimation, |
| | | mobile: device.value === "mobile", |
| | | })); |
| | | |
| | | const { width, height } = useWindowSize(); |
| | | const WIDTH = 992; // refer to Bootstrap's responsive design |
| | | |
| | | watch( |
| | | () => device.value, |
| | | () => { |
| | | if (device.value === "mobile" && sidebar.value.opened) { |
| | | useAppStore().closeSideBar({ withoutAnimation: false }); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | watchEffect(() => { |
| | | if (width.value - 1 < WIDTH) { |
| | | useAppStore().toggleDevice("mobile"); |
| | | useAppStore().closeSideBar({ withoutAnimation: true }); |
| | | } else { |
| | | useAppStore().toggleDevice("desktop"); |
| | | } |
| | | }); |
| | | |
| | | function handleClickOutside() { |
| | | useAppStore().closeSideBar({ withoutAnimation: false }); |
| | | } |
| | | |
| | | const settingRef = ref(null); |
| | | function setLayout() { |
| | | settingRef.value.openSetting(); |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "@/assets/styles/mixin.scss"; |
| | | @import "@/assets/styles/variables.module.scss"; |
| | | |
| | | .app-wrapper { |
| | | @include clearfix; |
| | | position: relative; |
| | | height: 100%; |
| | | width: 100%; |
| | | background: radial-gradient( |
| | | circle at top, |
| | | rgba(223, 232, 226, 0.95), |
| | | transparent 32% |
| | | ), |
| | | linear-gradient(180deg, #f7faf8 0%, var(--app-bg) 100%); |
| | | |
| | | &.mobile.openSidebar { |
| | | position: fixed; |
| | | top: 0; |
| | | } |
| | | } |
| | | |
| | | .drawer-bg { |
| | | background: #000; |
| | | opacity: 0.3; |
| | | width: 100%; |
| | | top: 0; |
| | | height: 100%; |
| | | position: absolute; |
| | | z-index: 999; |
| | | } |
| | | |
| | | .fixed-header { |
| | | position: fixed; |
| | | top: 0px; |
| | | padding-top: 12px; |
| | | right: 16px; |
| | | z-index: 9; |
| | | width: calc(100% - #{$base-sidebar-width} - 32px); |
| | | transition: width 0.28s, right 0.28s; |
| | | padding-bottom: 8px; |
| | | background-color: #f3f6f4; |
| | | } |
| | | .hideSidebar .fixed-header { |
| | | width: calc(100% - 100px); |
| | | } |
| | | |
| | | .sidebarHide .fixed-header { |
| | | width: calc(100% - 32px); |
| | | } |
| | | |
| | | .mobile .fixed-header { |
| | | width: 100%; |
| | | } |
| | | </style> |
| | |
| | | // 坿æ¬ç»ä»¶ |
| | | 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); |
| | |
| | | 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: "/financial", |
| | | // component: Layout, |
| | | // hidden: false, |
| | | // redirect: "/financial/general-ledger", |
| | | // alwaysShow: true, |
| | | // meta: { title: "è´¢å¡ç®¡ç", icon: "money" }, |
| | | // children: [ |
| | | // { |
| | | // path: "general-ledger", |
| | | // component: () => import("@/views/financialManagement/generalLedger/index.vue"), |
| | | // name: "GeneralLedger", |
| | | // meta: { title: "æ»å¸ç§ç®" }, |
| | | // }, |
| | | // { |
| | | // 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: "receivable-reconciliation", |
| | | // component: () => import("@/views/financialManagement/receivable/reconciliation.vue"), |
| | | // name: "ReceivableReconciliation", |
| | | // 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: "purchase-in", |
| | | // component: () => import("@/views/financialManagement/payable/purchaseIn.vue"), |
| | | // name: "PurchaseIn", |
| | | // meta: { title: "éè´å
¥åº" }, |
| | | // }, |
| | | // { |
| | | // path: "payable-reconciliation", |
| | | // component: () => import("@/views/financialManagement/payable/reconciliation.vue"), |
| | | // name: "PayableReconciliation", |
| | | // 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: "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: "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 auth from '@/plugins/auth'
|
| | | import router, { constantRoutes, dynamicRoutes } from '@/router'
|
| | | import { getRouters } from '@/api/menu'
|
| | | import Layout from '@/layout/index'
|
| | | import ParentView from '@/components/ParentView'
|
| | | import InnerLink from '@/layout/components/InnerLink'
|
| | | 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)
|
| | | // å°è´¢å¡ç®¡çè·¯ç±åå¹¶å°ä¾§è¾¹æ
|
| | | this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
|
| | | this.setDefaultRoutes(sidebarRoutes)
|
| | | this.setTopbarRoutes(defaultRoutes)
|
| | |
| | | })
|
| | |
|
| | | // éååå°ä¼ æ¥çè·¯ç±å符串ï¼è½¬æ¢ä¸ºç»ä»¶å¯¹è±¡
|
| | | function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
| | | 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) {
|
| | | route.children = filterChildren(route.children)
|
| | |
| | | const useUserStore = defineStore(
|
| | | 'user',
|
| | | {
|
| | | state: () => ({
|
| | | token: getToken(),
|
| | | id: '',
|
| | | name: '',
|
| | | avatar: '',
|
| | | roles: [],
|
| | | permissions: []
|
| | | state: () => ({ |
| | | token: getToken(), |
| | | id: '', |
| | | name: '', |
| | | avatar: '', |
| | | roles: [], |
| | | permissions: [], |
| | | aiEnabled: 0 |
| | | }),
|
| | | actions: {
|
| | | // ç»å½
|
| | |
| | | this.id = user.userId
|
| | | this.name = user.userName
|
| | | this.avatar = avatar
|
| | | this.currentFactoryName = user.currentFactoryName
|
| | | this.nickName = user.nickName
|
| | | this.roleName = user.roles[0].roleName
|
| | | this.currentDeptId = user.tenantId
|
| | | this.currentLoginTime = this.getCurrentTime()
|
| | | resolve(res)
|
| | | }).catch(error => {
|
| | | reject(error)
|
| | | })
|
| | | })
|
| | | this.currentFactoryName = user.currentFactoryName |
| | | this.nickName = user.nickName |
| | | 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) |
| | | }) |
| | | }) |
| | | },
|
| | | // éåºç³»ç»
|
| | | logOut() {
|
| | | return new Promise((resolve, reject) => {
|
| | | logout(this.token).then(() => {
|
| | | this.token = ''
|
| | | this.roles = []
|
| | | this.permissions = []
|
| | | removeToken()
|
| | | resolve()
|
| | | }).catch(error => {
|
| | | reject(error)
|
| | | })
|
| | | this.token = '' |
| | | this.roles = [] |
| | | this.permissions = [] |
| | | this.aiEnabled = 0 |
| | | removeToken() |
| | | resolve() |
| | | }).catch(error => { |
| | | reject(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 type="primary" @click="handelConfirm">ç¡®å®</el-button> |
| | | <el-button @click="close">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog>` |
| | | } |
| | |
| | | <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" |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitReminderForm">确认</el-button> |
| | | <el-button @click="closeReminderDialog">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="submitReminderForm">æäº¤</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitNegotiationForm">确认</el-button> |
| | | <el-button @click="closeNegotiationDialog">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="submitNegotiationForm">æäº¤</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | <template #default="{ row }"> |
| | | <el-button type="info" |
| | | link |
| | | size="small" |
| | | @click="openAttachmentDialog(row)"> |
| | | <el-icon> |
| | | <Paperclip /> |
| | |
| | | <template #default="{ row, $index }"> |
| | | <el-button type="primary" |
| | | link |
| | | size="small" |
| | | @click="editNegotiationRecord(row, $index)"> |
| | | ä¿®æ¹ |
| | | </el-button> |
| | | <el-button type="danger" |
| | | link |
| | | size="small" |
| | | @click="deleteNegotiationRecord(row, $index)"> |
| | | å é¤ |
| | | </el-button> |
| | |
| | | <template #default="{ row, $index }"> |
| | | <el-button type="primary" |
| | | link |
| | | size="small" |
| | | @click="downloadAttachment(row)"> |
| | | ä¸è½½ |
| | | </el-button> |
| | | <el-button type="danger" |
| | | link |
| | | size="small" |
| | | @click="deleteAttachment(row, $index)"> |
| | | å é¤ |
| | | </el-button> |
| | |
| | | import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue"; |
| | | import { Search, Paperclip, Upload } from "@element-plus/icons-vue"; |
| | | import { |
| | | addCustomer, |
| | | delCustomer, |
| | | getCustomer, |
| | | listCustomer, |
| | | updateCustomer, |
| | | addCustomerFollow, |
| | | updateCustomerFollow, |
| | | delCustomerFollow, |
| | | addReturnVisit, |
| | | getReturnVisit, |
| | | } from "@/api/basicData/customerFile.js"; |
| | | import {listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer} 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: "", |
| | | customerId: "", |
| | | customerId: "", |
| | | followUpMethod: "", |
| | | followUpLevel: "", |
| | | followUpTime: "", |
| | |
| | | }, |
| | | { |
| | | label: "å°ååèç³»æ¹å¼", |
| | | prop: "addressPhone", |
| | | prop: "companyAddress", |
| | | width: 250, |
| | | }, |
| | | { |
| | |
| | | prop: "maintainer", |
| | | }, |
| | | { |
| | | label: "å®¢æ·æ¥æº", |
| | | prop: "type", |
| | | dataType: "tag", |
| | | width: 100, |
| | | formatData: value => { |
| | | if (value === 1 || value === "1") { |
| | | return "å
¬æµ·"; |
| | | } |
| | | return "ç§æµ·"; |
| | | }, |
| | | formatType: value => { |
| | | if (value === 1 || value === "1") { |
| | | return "warning"; |
| | | } |
| | | return "success"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintenanceTime", |
| | | width: 100, |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 250, |
| | | width: 290, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openDetailDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "æ·»å æ´½è°è¿åº¦", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openNegotiationDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å访æé", |
| | | type: "text", |
| | |
| | | openReminderDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "æ·»å æ´½è°è¿åº¦", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openNegotiationDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openDetailDialog(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | searchForm: { |
| | | customerName: "", |
| | | customerType: "", |
| | | type: 0 |
| | | }, |
| | | form: { |
| | | customerName: "", |
| | |
| | | bankAccount: "", |
| | | bankCode: "", |
| | | customerType: "", |
| | | type: 0 |
| | | }, |
| | | rules: { |
| | | customerName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData", |
| | | data: { |
| | | type: 0 |
| | | }, |
| | | // æä»¶ä¸ä¼ åçåè° |
| | | beforeUpload: file => { |
| | | console.log("æä»¶å³å°ä¸ä¼ ", file); |
| | |
| | | tableLoading.value = true; |
| | | listCustomer({ ...searchForm.value, ...page }).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.records; |
| | | page.total = res.total; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | |
| | | contactPhone: "", |
| | | }, |
| | | ]; |
| | | form.value.type = 0; |
| | | form.value.maintenanceTime = getCurrentDate(); |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/basic/customer/export", {}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | proxy.download("/basic/customer/export", {type: 0}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | const unauthorizedData = selectedRows.value.filter( |
| | | item => item.maintainer !== userStore.nickName |
| | | item => item.type === 1 || item.type === "1" |
| | | ); |
| | | if (unauthorizedData.length > 0) { |
| | | proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); |
| | | proxy.$modal.msgWarning("å
¬æµ·åé
ç客æ·ä¸è½å é¤"); |
| | | return; |
| | | } |
| | | ids = selectedRows.value.map(item => item.id); |
| | |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delCustomer(ids) |
| | | .then(res => { |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | |
| | | if (reminderForm.id) { |
| | | submitvalue.value = { |
| | | id: reminderForm.id, |
| | | customerId: currentCustomerId.value, |
| | | customerId: currentCustomerId.value, |
| | | isEnabled: reminderForm.reminderSwitch ? 1 : 0, |
| | | content: reminderForm.reminderContent, |
| | | reminderTime: reminderForm.reminderTime, |
| | |
| | | remindUserId: userStore.id, |
| | | }; |
| | | } |
| | | |
| | | console.log("æäº¤å访æéæ°æ®:", submitvalue.value); |
| | | |
| | | // è°ç¨æ¥å£ |
| | | addReturnVisit(submitvalue.value) |
| | |
| | | negotiationForm.followUpTime = ""; |
| | | negotiationForm.followerUserName = userStore.nickName; // é»è®¤å½åç»å½äºº |
| | | negotiationForm.content = ""; |
| | | // { |
| | | // "customerId": 152, |
| | | // "followUpMethod": "çµè¯æ²é", |
| | | // "followUpLevel": "没ææå", |
| | | // "followUpTime": "2026-03-04T15:30:00", |
| | | // "followerUserName": "管çåè´¦å·", |
| | | // "content": "111" |
| | | // } |
| | | negotiationDialogVisible.value = true; |
| | | }; |
| | | |
| | |
| | | |
| | | if (isEdit) { |
| | | // ä¿®æ¹æä½ |
| | | console.log("ä¿®æ¹æ´½è°è¿åº¦æ°æ®:", negotiationForm); |
| | | // è¿éå¯ä»¥è°ç¨æ´æ°æ¥å£ |
| | | // å®é
项ç®ä¸éè¦æ ¹æ®å端æ¥å£è¿è¡è°æ´ |
| | | // 示ä¾ï¼updateCustomerFollow(negotiationForm).then(res => { |
| | | // // æ´æ°æ¬å°æ°æ® |
| | | // const index = negotiationForm.editIndex; |
| | | // negotiationRecords.value[index] = { |
| | | // followUpTime: negotiationForm.followUpTime, |
| | | // followUpMethod: negotiationForm.followUpMethod, |
| | | // followUpLevel: negotiationForm.followUpLevel, |
| | | // followerUserName: negotiationForm.followerUserName, |
| | | // content: negotiationForm.content, |
| | | // id: negotiationForm.id, |
| | | // }; |
| | | // proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | // closeNegotiationDialog(); |
| | | // }); |
| | | updateCustomerFollow(negotiationForm).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | getCustomer(negotiationForm.customerId).then(res => { |
| | |
| | | |
| | | // æå¼è¯¦æ
å¼¹çª |
| | | const openDetailDialog = row => { |
| | | // è°ç¨getCustomeræ¥å£è·å客æ·è¯¦æ
|
| | | getCustomer(row.id).then(res => { |
| | | // å¡«å
客æ·åºæ¬ä¿¡æ¯ |
| | | Object.assign(detailForm, res.data); |
| | |
| | | // å°å½åè®°å½æ°æ®å¡«å
å°è¡¨å |
| | | Object.assign(negotiationForm, { |
| | | customerName: row.customerName, |
| | | customerId: row.customerId, |
| | | customerId: row.customerId, |
| | | followUpMethod: row.followUpMethod, |
| | | followUpLevel: row.followUpLevel, |
| | | followUpTime: row.followUpTime, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form" style="margin-bottom: 20px;"> |
| | | <div> |
| | | <span class="search_title">客æ·åç§°ï¼</span> |
| | | <el-input v-model="searchForm.customerName" |
| | | style="width: 240px;margin-right: 10px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span class="search_title">客æ·åç±»ï¼</span> |
| | | <el-select v-model="searchForm.customerType" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 240px" |
| | | clearable |
| | | @change="handleQuery"> |
| | | <el-option label="é¶å®å®¢æ·" |
| | | value="é¶å®å®¢æ·" /> |
| | | <el-option label="è¿éå客æ·" |
| | | value="è¿éå客æ·" /> |
| | | </el-select> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" |
| | | @click="openForm('add')">æ°å¢å®¢æ·</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="info" |
| | | plain |
| | | icon="Upload" |
| | | @click="handleImport">导å
¥</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination"></PIMTable> |
| | | </div> |
| | | <el-dialog v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢å®¢æ·ä¿¡æ¯' : 'ç¼è¾å®¢æ·ä¿¡æ¯'" |
| | | width="70%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°ï¼" |
| | | prop="customerName"> |
| | | <el-input v-model="form.customerName" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="纳ç¨äººè¯å«å·ï¼" |
| | | prop="taxpayerIdentificationNumber"> |
| | | <el-input v-model="form.taxpayerIdentificationNumber" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬å¸å°åï¼" |
| | | prop="companyAddress"> |
| | | <el-input v-model="form.companyAddress" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬å¸çµè¯ï¼" |
| | | prop="companyPhone"> |
| | | <el-input v-model="form.companyPhone" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¶è¡åºæ¬æ·ï¼" |
| | | prop="basicBankAccount"> |
| | | <el-input v-model="form.basicBankAccount" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¶è¡è´¦å·ï¼" |
| | | prop="bankAccount"> |
| | | <el-input v-model="form.bankAccount" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="弿·è¡å·ï¼" |
| | | prop="bankCode"> |
| | | <el-input v-model="form.bankCode" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç±»ï¼" |
| | | prop="customerType"> |
| | | <el-select v-model="form.customerType" |
| | | placeholder="è¯·éæ©" |
| | | clearable> |
| | | <el-option label="é¶å®å®¢æ·" |
| | | value="é¶å®å®¢æ·" /> |
| | | <el-option label="è¿éå客æ·" |
| | | value="è¿éå客æ·" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30" |
| | | v-for="(contact, index) in formYYs.contactList" |
| | | :key="index"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è系人ï¼" |
| | | prop="contactPerson"> |
| | | <el-input v-model="contact.contactPerson" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯ï¼" |
| | | prop="contactPhone"> |
| | | <div style="display: flex; align-items: center;width: 100%;"> |
| | | <el-input v-model="contact.contactPhone" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | <el-button @click="removeContact(index)" |
| | | type="danger" |
| | | circle |
| | | style="margin-left: 5px;"> |
| | | <el-icon> |
| | | <Close /> |
| | | </el-icon> |
| | | </el-button> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-button @click="addNewContact" |
| | | style="margin-bottom: 10px;">+ æ°å¢è系人</el-button> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»´æ¤äººï¼" |
| | | prop="maintainer"> |
| | | <el-select v-model="form.maintainer" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | disabled> |
| | | <el-option v-for="item in userList" |
| | | :key="item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.nickName" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»´æ¤æ¶é´ï¼" |
| | | prop="maintenanceTime"> |
| | | <el-date-picker style="width: 100%" |
| | | v-model="form.maintenanceTime" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable /> |
| | | </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 @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog v-model="assignDialogVisible" |
| | | title="åé
客æ·" |
| | | width="500px" |
| | | @close="closeAssignDialog"> |
| | | <el-form :model="assignForm" |
| | | :rules="assignRules" |
| | | ref="assignFormRef" |
| | | label-width="100px"> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <el-input v-model="assignForm.customerName" |
| | | disabled /> |
| | | </el-form-item> |
| | | <el-form-item label="åé
人å" |
| | | prop="boundId"> |
| | | <el-select v-model="assignForm.boundId" |
| | | placeholder="è¯·éæ©åé
人å" |
| | | style="width: 100%" |
| | | filterable> |
| | | <el-option v-for="item in userList" |
| | | :key="item.userId || item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitAssignForm">确认</el-button> |
| | | <el-button @click="closeAssignDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog v-model="shareDialogVisible" |
| | | title="å
±äº«å®¢æ·" |
| | | width="500px" |
| | | @close="closeShareDialog"> |
| | | <el-form :model="shareForm" |
| | | :rules="shareRules" |
| | | ref="shareFormRef" |
| | | label-width="100px"> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <el-input v-model="shareForm.customerName" |
| | | disabled /> |
| | | </el-form-item> |
| | | <el-form-item label="å
±äº«äººå" |
| | | prop="boundIds"> |
| | | <el-select v-model="shareForm.boundIds" |
| | | placeholder="è¯·éæ©å
±äº«äººå" |
| | | style="width: 100%" |
| | | filterable |
| | | multiple |
| | | collapse-tags |
| | | collapse-tags-tooltip> |
| | | <el-option v-for="item in userList" |
| | | :key="item.userId || item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitShareForm">确认</el-button> |
| | | <el-button @click="closeShareDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- ç¨æ·å¯¼å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog :title="upload.title" |
| | | v-model="upload.open" |
| | | width="400px" |
| | | append-to-body> |
| | | <el-upload ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url" |
| | | :data="upload.data" |
| | | :disabled="upload.isUploading" |
| | | :before-upload="upload.beforeUpload" |
| | | :on-progress="upload.onProgress" |
| | | :on-success="upload.onSuccess" |
| | | :on-error="upload.onError" |
| | | :on-change="upload.onChange" |
| | | :auto-upload="false" |
| | | drag> |
| | | <el-icon class="el-icon--upload"><upload-filled /></el-icon> |
| | | <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> |
| | | <template #tip> |
| | | <div class="el-upload__tip text-center"> |
| | | <span>ä»
å
许导å
¥xlsãxlsxæ ¼å¼æä»¶ã</span> |
| | | <el-link type="primary" |
| | | :underline="false" |
| | | style="font-size: 12px; vertical-align: baseline" |
| | | @click="importTemplate">ä¸è½½æ¨¡æ¿</el-link> |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button @click="upload.open = false">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- å访æéå¯¹è¯æ¡ --> |
| | | <el-dialog title="å访æé" |
| | | v-model="reminderDialogVisible" |
| | | width="500px" |
| | | @close="closeReminderDialog"> |
| | | <el-form :model="reminderForm" |
| | | label-width="100px" |
| | | :rules="reminderRules" |
| | | ref="reminderFormRef"> |
| | | <el-form-item label="客æ·åç§°ï¼"> |
| | | <el-input v-model="reminderForm.customerName" |
| | | disabled /> |
| | | </el-form-item> |
| | | <el-form-item label="æéå¼å
³ï¼"> |
| | | <el-switch v-model="reminderForm.reminderSwitch" /> |
| | | </el-form-item> |
| | | <el-form-item label="æéå
容ï¼" |
| | | prop="reminderContent"> |
| | | <el-input v-model="reminderForm.reminderContent" |
| | | type="textarea" |
| | | :maxlength="100" |
| | | show-word-limit |
| | | placeholder="请è¾å
¥æéå
容" /> |
| | | </el-form-item> |
| | | <el-form-item label="æéæ¶é´ï¼" |
| | | prop="reminderTime"> |
| | | <el-date-picker v-model="reminderForm.reminderTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | placeholder="è¯·éæ©æéæ¶é´" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitReminderForm">确认</el-button> |
| | | <el-button @click="closeReminderDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- æ·»å /ä¿®æ¹æ´½è°è¿åº¦å¯¹è¯æ¡ --> |
| | | <el-dialog :title="negotiationForm.editIndex !== undefined ? 'ä¿®æ¹è¿åº¦' : 'æ·»å è¿åº¦'" |
| | | v-model="negotiationDialogVisible" |
| | | width="600px" |
| | | @close="closeNegotiationDialog"> |
| | | <el-form :model="negotiationForm" |
| | | label-width="100px" |
| | | :rules="negotiationRules" |
| | | ref="negotiationFormRef"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è·è¿æ¹å¼ï¼" |
| | | prop="followUpMethod"> |
| | | <el-select v-model="negotiationForm.followUpMethod" |
| | | 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-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è·è¿ç¨åº¦ï¼" |
| | | prop="followUpLevel"> |
| | | <el-select v-model="negotiationForm.followUpLevel" |
| | | 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-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è·è¿æ¶é´ï¼" |
| | | prop="followUpTime"> |
| | | <el-date-picker v-model="negotiationForm.followUpTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è·è¿äººï¼"> |
| | | <el-input v-model="negotiationForm.followerUserName" |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å
容ï¼" |
| | | prop="content"> |
| | | <el-input v-model="negotiationForm.content" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitNegotiationForm">确认</el-button> |
| | | <el-button @click="closeNegotiationDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 客æ·è¯¦æ
å¯¹è¯æ¡ --> |
| | | <el-dialog title="客æ·è¯¦æ
" |
| | | v-model="detailDialogVisible" |
| | | width="1000px" |
| | | @close="closeDetailDialog"> |
| | | <!-- 客æ·åºæ¬ä¿¡æ¯ --> |
| | | <div class="detail-section"> |
| | | <h3 class="section-title">客æ·åºæ¬ä¿¡æ¯</h3> |
| | | <div class="info-display"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">客æ·åç§°ï¼</span> |
| | | <span class="info-value">{{ detailForm.customerName }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">客æ·åç±»ï¼</span> |
| | | <span class="info-value">{{ detailForm.customerType }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">纳ç¨äººè¯å«å·ï¼</span> |
| | | <span class="info-value">{{ detailForm.taxpayerIdentificationNumber }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">å
¬å¸çµè¯ï¼</span> |
| | | <span class="info-value">{{ detailForm.companyPhone }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">å
¬å¸å°åï¼</span> |
| | | <span class="info-value">{{ detailForm.companyAddress }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">é¶è¡åºæ¬æ·ï¼</span> |
| | | <span class="info-value">{{ detailForm.basicBankAccount }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">é¶è¡è´¦å·ï¼</span> |
| | | <span class="info-value">{{ detailForm.bankAccount }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">弿·è¡å·ï¼</span> |
| | | <span class="info-value">{{ detailForm.bankCode }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">è系人ï¼</span> |
| | | <span class="info-value">{{ detailForm.contactPerson }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">èç³»çµè¯ï¼</span> |
| | | <span class="info-value">{{ detailForm.contactPhone }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">ç»´æ¤äººï¼</span> |
| | | <span class="info-value">{{ detailForm.maintainer }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">ç»´æ¤æ¶é´ï¼</span> |
| | | <span class="info-value">{{ detailForm.maintenanceTime }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </div> |
| | | <!-- æ´½è°è¿åº¦è®°å½ --> |
| | | <div class="detail-section"> |
| | | <div class="section-header"> |
| | | <h3 class="section-title">æ´½è°è¿åº¦è®°å½</h3> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="openNegotiationDialog(detailForm)"> |
| | | æ·»å è¿åº¦ |
| | | </el-button> |
| | | </div> |
| | | <el-table :data="negotiationRecords" |
| | | border |
| | | style="width: 100%"> |
| | | <el-table-column prop="followUpTime" |
| | | label="è·è¿æ¶é´" |
| | | width="160" /> |
| | | <el-table-column prop="followUpMethod" |
| | | label="è·è¿æ¹å¼" |
| | | width="100" /> |
| | | <el-table-column prop="followUpLevel" |
| | | label="è·è¿ç¨åº¦" /> |
| | | <el-table-column prop="followerUserName" |
| | | label="è·è¿äºº" |
| | | width="100" /> |
| | | <el-table-column prop="content" |
| | | label="å
容" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="éä»¶" |
| | | width="100" |
| | | align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="info" |
| | | link |
| | | @click="openAttachmentDialog(row)"> |
| | | <el-icon> |
| | | <Paperclip /> |
| | | </el-icon> |
| | | éä»¶ |
| | | <!-- {{ row.fileList && row.fileList.length > 0 ? row.fileList.length : 'ä¸ä¼ ' }} --> |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" |
| | | width="150" |
| | | align="center"> |
| | | <template #default="{ row, $index }"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="editNegotiationRecord(row, $index)"> |
| | | ä¿®æ¹ |
| | | </el-button> |
| | | <el-button type="danger" |
| | | link |
| | | @click="deleteNegotiationRecord(row, $index)"> |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div v-if="negotiationRecords.length === 0" |
| | | class="no-records"> |
| | | ææ æ´½è°è¿åº¦è®°å½ |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDetailDialog">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- éä»¶ä¸ä¼ å¼¹çª --> |
| | | <el-dialog title="é件管ç" |
| | | v-model="attachmentDialogVisible" |
| | | width="600px" |
| | | @close="closeAttachmentDialog"> |
| | | <div class="attachment-section"> |
| | | <div class="upload-area"> |
| | | <el-upload ref="attachmentUploadRef" |
| | | :action="getAttachmentUploadUrl()" |
| | | :headers="attachmentUploadHeaders" |
| | | :file-list="currentAttachmentList" |
| | | :on-success="handleAttachmentSuccess" |
| | | :on-error="handleAttachmentError" |
| | | :on-remove="handleAttachmentRemove" |
| | | :before-upload="beforeAttachmentUpload" |
| | | multiple |
| | | :limit="10" |
| | | name="files"> |
| | | <el-button type="primary"> |
| | | <el-icon> |
| | | <Upload /> |
| | | </el-icon> |
| | | ä¸ä¼ éä»¶ |
| | | </el-button> |
| | | <template #tip> |
| | | <div class="el-upload__tip"> |
| | | æ¯æä¸ä¼ å¾çãææ¡£çæä»¶ï¼å个æä»¶ä¸è¶
è¿50MB |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </div> |
| | | <div v-if="currentAttachmentList.length > 0" |
| | | class="attachment-list"> |
| | | <h4>å·²ä¸ä¼ éä»¶ï¼</h4> |
| | | <el-table :data="currentAttachmentList" |
| | | border |
| | | size="small"> |
| | | <el-table-column prop="name" |
| | | label="æä»¶å" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="size" |
| | | label="大å°" |
| | | width="100"> |
| | | <template #default="{ row }"> |
| | | {{ formatFileSize(row.size) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" |
| | | width="120" |
| | | align="center"> |
| | | <template #default="{ row, $index }"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="downloadAttachment(row)"> |
| | | ä¸è½½ |
| | | </el-button> |
| | | <el-button type="danger" |
| | | link |
| | | @click="deleteAttachment(row, $index)"> |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <div v-else |
| | | class="no-attachment"> |
| | | ææ éä»¶ |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeAttachmentDialog">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue"; |
| | | import { Search, Paperclip, Upload } from "@element-plus/icons-vue"; |
| | | import { |
| | | 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"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | const assignDialogVisible = ref(false); |
| | | const assignFormRef = ref(); |
| | | const assignForm = reactive({ |
| | | id: undefined, |
| | | customerName: "", |
| | | boundId: undefined, |
| | | }); |
| | | const assignRules = { |
| | | boundId: [{ required: true, message: "è¯·éæ©åé
人å", trigger: "change" }], |
| | | }; |
| | | const shareDialogVisible = ref(false); |
| | | const shareFormRef = ref(); |
| | | const shareForm = reactive({ |
| | | id: undefined, |
| | | customerName: "", |
| | | boundIds: [], |
| | | }); |
| | | const shareRules = { |
| | | boundIds: [{ required: true, message: "è¯·éæ©å
±äº«äººå", trigger: "change" }], |
| | | }; |
| | | |
| | | // å访æéç¸å
³ |
| | | const reminderDialogVisible = ref(false); |
| | | const reminderFormRef = ref(); |
| | | const currentCustomerId = ref(); |
| | | const reminderForm = reactive({ |
| | | customerName: "", |
| | | reminderSwitch: false, |
| | | reminderContent: "", |
| | | reminderTime: "", |
| | | }); |
| | | const reminderRules = { |
| | | reminderContent: [ |
| | | { required: true, message: "请è¾å
¥æéå
容", trigger: "blur" }, |
| | | ], |
| | | reminderTime: [ |
| | | { required: true, message: "è¯·éæ©æéæ¶é´", trigger: "change" }, |
| | | ], |
| | | }; |
| | | |
| | | // æ´½è°è¿åº¦ç¸å
³ |
| | | const negotiationDialogVisible = ref(false); |
| | | const negotiationFormRef = ref(); |
| | | const negotiationForm = reactive({ |
| | | customerName: "", |
| | | customerId: "", |
| | | followUpMethod: "", |
| | | followUpLevel: "", |
| | | followUpTime: "", |
| | | followerUserName: "", |
| | | content: "", |
| | | }); |
| | | const negotiationRules = { |
| | | followUpMethod: [ |
| | | { required: true, message: "è¯·éæ©è·è¿æ¹å¼", trigger: "change" }, |
| | | ], |
| | | followUpLevel: [ |
| | | { required: true, message: "è¯·éæ©è·è¿ç¨åº¦", trigger: "change" }, |
| | | ], |
| | | followUpTime: [ |
| | | { required: true, message: "è¯·éæ©è·è¿æ¶é´", trigger: "change" }, |
| | | ], |
| | | content: [{ required: true, message: "请è¾å
¥å
容", trigger: "blur" }], |
| | | }; |
| | | |
| | | // 详æ
ç¸å
³ |
| | | const detailDialogVisible = ref(false); |
| | | const detailForm = reactive({ |
| | | customerName: "", |
| | | customerType: "", |
| | | taxpayerIdentificationNumber: "", |
| | | companyPhone: "", |
| | | companyAddress: "", |
| | | basicBankAccount: "", |
| | | bankAccount: "", |
| | | bankCode: "", |
| | | contactPerson: "", |
| | | contactPhone: "", |
| | | maintainer: "", |
| | | maintenanceTime: "", |
| | | }); |
| | | const negotiationRecords = ref([]); |
| | | |
| | | // éä»¶ç¸å
³ |
| | | const attachmentDialogVisible = ref(false); |
| | | const attachmentUploadRef = ref(); |
| | | const currentAttachmentList = ref([]); |
| | | const currentFollowRecord = ref({}); |
| | | const attachmentUploadHeaders = { Authorization: "Bearer " + getToken() }; |
| | | |
| | | // 卿æå»ºä¸ä¼ URL |
| | | const getAttachmentUploadUrl = () => { |
| | | const baseUrl = |
| | | import.meta.env.VITE_APP_BASE_API + "/basic/customer-follow/upload"; |
| | | return currentFollowRecord.value.id |
| | | ? `${baseUrl}/${currentFollowRecord.value.id}` |
| | | : baseUrl; |
| | | }; |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "客æ·åç±»", |
| | | prop: "customerType", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "客æ·åç§°", |
| | | prop: "customerName", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "纳ç¨äººè¯å«ç ", |
| | | prop: "taxpayerIdentificationNumber", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "å°ååèç³»æ¹å¼", |
| | | prop: "addressPhone", |
| | | width: 250, |
| | | }, |
| | | { |
| | | label: "è系人", |
| | | prop: "contactPerson", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "contactPhone", |
| | | width: 150, |
| | | }, |
| | | // { |
| | | // label: "è·è¿è¿åº¦", |
| | | // prop: "followUpLevel", |
| | | // width: 120, |
| | | // }, |
| | | // { |
| | | // label: "è·è¿æ¶é´", |
| | | // prop: "followUpTime", |
| | | // width: 120, |
| | | // }, |
| | | { |
| | | label: "é¶è¡åºæ¬æ·", |
| | | prop: "basicBankAccount", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "é¶è¡è´¦å·", |
| | | prop: "bankAccount", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "弿·è¡å·", |
| | | prop: "bankCode", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "ç»´æ¤äºº", |
| | | prop: "maintainer", |
| | | }, |
| | | { |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintenanceTime", |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "é¢ç¨äºº", |
| | | prop: "usageUserName", |
| | | width: 120, |
| | | fixed: "right", |
| | | }, |
| | | { |
| | | label: "é¢ç¨ç¶æ", |
| | | prop: "usageStatus", |
| | | dataType: "tag", |
| | | width: 100, |
| | | fixed: "right", |
| | | formatData: value => { |
| | | if (value === 1 || value === "1") { |
| | | return "å·²é¢ç¨"; |
| | | } |
| | | return "æªé¢ç¨"; |
| | | }, |
| | | formatType: value => { |
| | | if (value === 1 || value === "1") { |
| | | return "success"; |
| | | } |
| | | return "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "å
±äº«äºº", |
| | | prop: "togetherUserNames", |
| | | width: 120, |
| | | fixed: "right", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 200, |
| | | operation: [ |
| | | { |
| | | name: "åé
", |
| | | type: "text", |
| | | showHide: row => row.usageStatus != 1, |
| | | clickFun: row => { |
| | | openAssignDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "åæ¶", |
| | | type: "text", |
| | | showHide: row => row.usageStatus == 1, |
| | | clickFun: row => { |
| | | recycle(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å
񄧮", |
| | | type: "text", |
| | | showHide: row => row.usageStatus == 1, |
| | | clickFun: row => { |
| | | openShareDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | // { |
| | | // name: "详æ
", |
| | | // type: "text", |
| | | // clickFun: row => { |
| | | // openDetailDialog(row); |
| | | // }, |
| | | // }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const userList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const total = ref(0); |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const formYYs = ref({ |
| | | // å
¶ä»å段... |
| | | contactList: [ |
| | | { |
| | | contactPerson: "", |
| | | contactPhone: "", |
| | | }, |
| | | ], |
| | | }); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | customerName: "", |
| | | customerType: "", |
| | | type: 1 |
| | | }, |
| | | form: { |
| | | customerName: "", |
| | | taxpayerIdentificationNumber: "", |
| | | companyAddress: "", |
| | | companyPhone: "", |
| | | contactPerson: "", |
| | | contactPhone: "", |
| | | maintainer: "", |
| | | maintenanceTime: "", |
| | | basicBankAccount: "", |
| | | bankAccount: "", |
| | | bankCode: "", |
| | | customerType: "", |
| | | type: 1 |
| | | }, |
| | | rules: { |
| | | customerName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxpayerIdentificationNumber: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | companyAddress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | companyPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | // contactPerson: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | // contactPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | maintainer: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | maintenanceTime: [ |
| | | { required: false, message: "è¯·éæ©", trigger: "change" }, |
| | | ], |
| | | basicBankAccount: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | bankAccount: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | bankCode: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | customerType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼å®¢æ·å¯¼å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼å®¢æ·å¯¼å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData", |
| | | data: { |
| | | type: 1 |
| | | }, |
| | | // æä»¶ä¸ä¼ åçåè° |
| | | beforeUpload: file => { |
| | | console.log("æä»¶å³å°ä¸ä¼ ", file); |
| | | // å¯ä»¥å¨æ¤å¤åæä»¶ç±»åæå¤§å°æ ¡éª |
| | | const isValid = |
| | | file.type === |
| | | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || |
| | | file.name.endsWith(".xlsx") || |
| | | file.name.endsWith(".xls"); |
| | | if (!isValid) { |
| | | proxy.$modal.msgError("åªè½ä¸ä¼ Excel æä»¶"); |
| | | } |
| | | return isValid; |
| | | }, |
| | | // æä»¶ç¶ææ¹åæ¶çåè° |
| | | onChange: (file, fileList) => { |
| | | console.log("æä»¶ç¶ææ¹å", file, fileList); |
| | | }, |
| | | // æä»¶ä¸ä¼ æåæ¶çåè° |
| | | onSuccess: (response, file, fileList) => { |
| | | console.log("ä¸ä¼ æå", response, file, fileList); |
| | | upload.isUploading = false; |
| | | if (response.code === 200) { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | upload.open = false; |
| | | proxy.$refs["uploadRef"].clearFiles(); |
| | | getList(); |
| | | } else if (response.code === 500) { |
| | | proxy.$modal.msgError(response.msg); |
| | | } else { |
| | | proxy.$modal.msgWarning(response.msg); |
| | | } |
| | | }, |
| | | // æä»¶ä¸ä¼ 失败æ¶çåè° |
| | | onError: (error, file, fileList) => { |
| | | console.error("ä¸ä¼ 失败", error, file, fileList); |
| | | upload.isUploading = false; |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | }, |
| | | // æä»¶ä¸ä¼ è¿åº¦åè° |
| | | onProgress: (event, file, fileList) => { |
| | | console.log("ä¸ä¼ ä¸...", event.percent); |
| | | }, |
| | | }); |
| | | const { searchForm, form, rules } = toRefs(data); |
| | | const addNewContact = () => { |
| | | formYYs.value.contactList.push({ |
| | | contactPerson: "", |
| | | contactPhone: "", |
| | | }); |
| | | }; |
| | | |
| | | const removeContact = index => { |
| | | if (formYYs.value.contactList.length > 1) { |
| | | formYYs.value.contactList.splice(index, 1); |
| | | } |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const { total, ...queryPage } = page; |
| | | listCustomer({ ...searchForm.value, ...queryPage }).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | upload.isUploading = true; |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "客æ·å¯¼å
¥"; |
| | | upload.open = true; |
| | | } |
| | | /** ä¸è½½æ¨¡æ¿ */ |
| | | function importTemplate() { |
| | | proxy.download("/basic/customer/downloadTemplate", {}, "客æ·å¯¼å
¥æ¨¡æ¿.xlsx"); |
| | | } |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | form.value.maintainer = userStore.nickName; |
| | | formYYs.value.contactList = [ |
| | | { |
| | | contactPerson: "", |
| | | contactPhone: "", |
| | | }, |
| | | ]; |
| | | form.value.maintenanceTime = getCurrentDate(); |
| | | form.value.type = 1; |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | if (type === "edit") { |
| | | getCustomer(row.id).then(res => { |
| | | form.value = { ...res.data }; |
| | | formYYs.value.contactList = res.data.contactPerson |
| | | .split(",") |
| | | .map((item, index) => { |
| | | return { |
| | | contactPerson: item, |
| | | contactPhone: res.data.contactPhone.split(",")[index], |
| | | }; |
| | | }); |
| | | }); |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "edit") { |
| | | submitEdit(); |
| | | } else { |
| | | submitAdd(); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | // æäº¤æ°å¢ |
| | | const submitAdd = () => { |
| | | if (formYYs.value.contactList.length < 1) { |
| | | return proxy.$modal.msgWarning("请è³å°æ·»å ä¸ä¸ªè系人"); |
| | | } |
| | | form.value.contactPerson = formYYs.value.contactList |
| | | .map(item => item.contactPerson) |
| | | .join(","); |
| | | form.value.contactPhone = formYYs.value.contactList |
| | | .map(item => item.contactPhone) |
| | | .join(","); |
| | | addCustomer(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }; |
| | | // æäº¤ä¿®æ¹ |
| | | const submitEdit = () => { |
| | | form.value.contactPerson = formYYs.value.contactList |
| | | .map(item => item.contactPerson) |
| | | .join(","); |
| | | form.value.contactPhone = formYYs.value.contactList |
| | | .map(item => item.contactPhone) |
| | | .join(","); |
| | | updateCustomer(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | const ensureUserList = () => { |
| | | if (userList.value.length) { |
| | | return Promise.resolve(); |
| | | } |
| | | return userListNoPage().then(res => { |
| | | userList.value = res.data || []; |
| | | }); |
| | | }; |
| | | const openAssignDialog = row => { |
| | | assignForm.id = row.id; |
| | | assignForm.customerName = row.customerName; |
| | | assignForm.boundId = undefined; |
| | | ensureUserList().then(() => { |
| | | assignDialogVisible.value = true; |
| | | }); |
| | | }; |
| | | const closeAssignDialog = () => { |
| | | proxy.resetForm("assignFormRef"); |
| | | assignForm.id = undefined; |
| | | assignForm.customerName = ""; |
| | | assignForm.boundId = undefined; |
| | | assignDialogVisible.value = false; |
| | | }; |
| | | const openShareDialog = row => { |
| | | shareForm.id = row.id; |
| | | shareForm.customerName = row.customerName; |
| | | shareForm.boundIds = row.userIds || []; |
| | | ensureUserList().then(() => { |
| | | shareDialogVisible.value = true; |
| | | }); |
| | | }; |
| | | const closeShareDialog = () => { |
| | | proxy.resetForm("shareFormRef"); |
| | | shareForm.id = undefined; |
| | | shareForm.customerName = ""; |
| | | shareForm.boundIds = []; |
| | | shareDialogVisible.value = false; |
| | | }; |
| | | const submitAssignForm = () => { |
| | | proxy.$refs.assignFormRef.validate(valid => { |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | assignCustomer({ |
| | | id: assignForm.id, |
| | | usageUser: assignForm.boundId, |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("åé
æå"); |
| | | closeAssignDialog(); |
| | | getList(); |
| | | }); |
| | | }); |
| | | }; |
| | | const submitShareForm = () => { |
| | | proxy.$refs.shareFormRef.validate(valid => { |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | shareCustomer({ |
| | | id: shareForm.id, |
| | | userIds: shareForm.boundIds, |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("å
±äº«æå"); |
| | | closeShareDialog(); |
| | | getList(); |
| | | }); |
| | | }); |
| | | }; |
| | | const recycle = row => { |
| | | ElMessageBox.confirm("ç¡®è®¤åæ¶å®¢æ·â" + row.customerName + "âåï¼", "åæ¶æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | return recycleCustomer({id: row.id}).then(() => { |
| | | proxy.$modal.msgSuccess("åæ¶æå"); |
| | | getList(); |
| | | }) |
| | | }) |
| | | .catch(error => { |
| | | if (error === "cancel" || error === "close") { |
| | | proxy.$modal.msg("已忶"); |
| | | } |
| | | }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/basic/customer/export", {type: 1}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | const unauthorizedData = selectedRows.value.filter( |
| | | item => item.maintainer !== userStore.nickName |
| | | ); |
| | | if (unauthorizedData.length > 0) { |
| | | proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); |
| | | return; |
| | | } |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delCustomer(ids) |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // æå¼å访æéå¼¹çª |
| | | const openReminderDialog = row => { |
| | | currentCustomerId.value = row.id; |
| | | reminderForm.customerName = row.customerName; |
| | | reminderForm.reminderSwitch = false; |
| | | reminderForm.reminderContent = ""; |
| | | reminderForm.reminderTime = ""; |
| | | |
| | | // å°è¯è·åå·²æçå访æé |
| | | getReturnVisit(row.id) |
| | | .then(res => { |
| | | if (res.code === 200 && res.data) { |
| | | reminderForm.reminderSwitch = res.data.isEnabled === 1; |
| | | reminderForm.reminderContent = res.data.content; |
| | | reminderForm.reminderTime = res.data.reminderTime; |
| | | reminderForm.id = res.data.id; |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("è·åå访æé失败:", error); |
| | | }); |
| | | |
| | | reminderDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³éå访æéå¼¹çª |
| | | const closeReminderDialog = () => { |
| | | proxy.resetForm("reminderFormRef"); |
| | | reminderDialogVisible.value = false; |
| | | }; |
| | | const submitvalue = ref({}); |
| | | |
| | | // æäº¤å访æé |
| | | const submitReminderForm = () => { |
| | | console.log("æäº¤å访æéæ°æ®:", userStore.id, userStore); |
| | | proxy.$refs.reminderFormRef.validate(valid => { |
| | | if (valid) { |
| | | if (reminderForm.id) { |
| | | submitvalue.value = { |
| | | id: reminderForm.id, |
| | | customerId: currentCustomerId.value, |
| | | isEnabled: reminderForm.reminderSwitch ? 1 : 0, |
| | | content: reminderForm.reminderContent, |
| | | reminderTime: reminderForm.reminderTime, |
| | | remindUserId: userStore.id, |
| | | }; |
| | | } else { |
| | | submitvalue.value = { |
| | | customerId: currentCustomerId.value, |
| | | isEnabled: reminderForm.reminderSwitch ? 1 : 0, |
| | | content: reminderForm.reminderContent, |
| | | reminderTime: reminderForm.reminderTime, |
| | | remindUserId: userStore.id, |
| | | }; |
| | | } |
| | | |
| | | console.log("æäº¤å访æéæ°æ®:", submitvalue.value); |
| | | |
| | | // è°ç¨æ¥å£ |
| | | addReturnVisit(submitvalue.value) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("å访æé设置æå"); |
| | | closeReminderDialog(); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "设置失败"); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("设置å访æé失败:", error); |
| | | proxy.$modal.msgError("设置失败"); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æå¼æ´½è°è¿åº¦å¼¹çª |
| | | const openNegotiationDialog = row => { |
| | | negotiationForm.customerName = row.customerName; |
| | | negotiationForm.customerId = row.id; |
| | | negotiationForm.followUpMethod = ""; |
| | | negotiationForm.followUpLevel = ""; |
| | | negotiationForm.followUpTime = ""; |
| | | negotiationForm.followerUserName = userStore.nickName; // é»è®¤å½åç»å½äºº |
| | | negotiationForm.content = ""; |
| | | // { |
| | | // "customerId": 152, |
| | | // "followUpMethod": "çµè¯æ²é", |
| | | // "followUpLevel": "没ææå", |
| | | // "followUpTime": "2026-03-04T15:30:00", |
| | | // "followerUserName": "管çåè´¦å·", |
| | | // "content": "111" |
| | | // } |
| | | negotiationDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³éæ´½è°è¿åº¦å¼¹çª |
| | | const closeNegotiationDialog = () => { |
| | | proxy.resetForm("negotiationFormRef"); |
| | | // æ¸
é¤ç¼è¾ç¶æ |
| | | delete negotiationForm.editIndex; |
| | | delete negotiationForm.id; |
| | | negotiationDialogVisible.value = false; |
| | | }; |
| | | |
| | | // æäº¤æ´½è°è¿åº¦ |
| | | const submitNegotiationForm = () => { |
| | | proxy.$refs.negotiationFormRef.validate(valid => { |
| | | if (valid) { |
| | | // å¤ææ¯æ°å¢è¿æ¯ä¿®æ¹ |
| | | const isEdit = negotiationForm.editIndex !== undefined; |
| | | |
| | | if (isEdit) { |
| | | // ä¿®æ¹æä½ |
| | | console.log("ä¿®æ¹æ´½è°è¿åº¦æ°æ®:", negotiationForm); |
| | | // è¿éå¯ä»¥è°ç¨æ´æ°æ¥å£ |
| | | // å®é
项ç®ä¸éè¦æ ¹æ®å端æ¥å£è¿è¡è°æ´ |
| | | // 示ä¾ï¼updateCustomerFollow(negotiationForm).then(res => { |
| | | // // æ´æ°æ¬å°æ°æ® |
| | | // const index = negotiationForm.editIndex; |
| | | // negotiationRecords.value[index] = { |
| | | // followUpTime: negotiationForm.followUpTime, |
| | | // followUpMethod: negotiationForm.followUpMethod, |
| | | // followUpLevel: negotiationForm.followUpLevel, |
| | | // followerUserName: negotiationForm.followerUserName, |
| | | // content: negotiationForm.content, |
| | | // id: negotiationForm.id, |
| | | // }; |
| | | // proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | // closeNegotiationDialog(); |
| | | // }); |
| | | updateCustomerFollow(negotiationForm).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | getCustomer(negotiationForm.customerId).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | negotiationRecords.value = res.data.followUpList || []; |
| | | }); |
| | | }); |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | closeNegotiationDialog(); |
| | | } else { |
| | | // æ°å¢æä½ |
| | | console.log("æäº¤æ´½è°è¿åº¦æ°æ®:", negotiationForm); |
| | | addCustomerFollow(negotiationForm).then(res => { |
| | | // æ·»å æååæ´æ°è¯¦æ
页é¢çè¿åº¦è®°å½ |
| | | const newRecord = { |
| | | followUpTime: negotiationForm.followUpTime, |
| | | followUpMethod: negotiationForm.followUpMethod, |
| | | followUpLevel: negotiationForm.followUpLevel, |
| | | followerUserName: negotiationForm.followerUserName, |
| | | content: negotiationForm.content, |
| | | }; |
| | | negotiationRecords.value.unshift(newRecord); |
| | | |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeNegotiationDialog(); |
| | | getList(); |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æå¼è¯¦æ
å¼¹çª |
| | | const openDetailDialog = row => { |
| | | // è°ç¨getCustomeræ¥å£è·å客æ·è¯¦æ
|
| | | getCustomer(row.id).then(res => { |
| | | // å¡«å
客æ·åºæ¬ä¿¡æ¯ |
| | | Object.assign(detailForm, res.data); |
| | | |
| | | // è·åæ´½è°è¿åº¦è®°å½ |
| | | negotiationRecords.value = res.data.followUpList || []; |
| | | |
| | | detailDialogVisible.value = true; |
| | | }); |
| | | }; |
| | | |
| | | // å
³é详æ
å¼¹çª |
| | | const closeDetailDialog = () => { |
| | | detailDialogVisible.value = false; |
| | | }; |
| | | |
| | | // ä¿®æ¹æ´½è°è®°å½ |
| | | const editNegotiationRecord = (row, index) => { |
| | | // å°å½åè®°å½æ°æ®å¡«å
å°è¡¨å |
| | | Object.assign(negotiationForm, { |
| | | customerName: row.customerName, |
| | | customerId: row.customerId, |
| | | followUpMethod: row.followUpMethod, |
| | | followUpLevel: row.followUpLevel, |
| | | followUpTime: row.followUpTime, |
| | | followerUserName: row.followerUserName, |
| | | content: row.content, |
| | | id: row.id, // è®°å½IDç¨äºæ´æ° |
| | | editIndex: index, // è®°å½ç´¢å¼ç¨äºæ¬å°æ´æ° |
| | | }); |
| | | negotiationDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å 餿´½è°è®°å½ |
| | | const deleteNegotiationRecord = (row, index) => { |
| | | ElMessageBox.confirm("ç¡®å®è¦å é¤è¿æ¡æ´½è°è®°å½åï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // è¿éå¯ä»¥è°ç¨å 餿¥å£ |
| | | // å®é
项ç®ä¸éè¦æ ¹æ®å端æ¥å£è¿è¡è°æ´ |
| | | // 示ä¾ï¼deleteCustomerFollow(row.id).then(() => { |
| | | // negotiationRecords.value.splice(index, 1); |
| | | // proxy.$modal.msgSuccess("å 餿å"); |
| | | // }); |
| | | delCustomerFollow(row.id).then(() => { |
| | | // å 餿ååæ´æ°æ¬å°æ°æ® |
| | | getCustomer(row.customerId).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | negotiationRecords.value = res.data.followUpList || []; |
| | | }); |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }); |
| | | // æ¬å°å é¤ï¼æ¨¡æï¼ |
| | | negotiationRecords.value.splice(index, 1); |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶å é¤"); |
| | | }); |
| | | }; |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const openAttachmentDialog = row => { |
| | | currentFollowRecord.value = row; |
| | | // 转æ¢ä¸ºç¬¦åElement Plus fileListæ ¼å¼çæ°ç» |
| | | currentAttachmentList.value = (row.fileList || []).map((file, index) => ({ |
| | | name: file.fileName, |
| | | url: file.fileUrl, |
| | | size: file.fileSize, |
| | | id: file.id, |
| | | uid: file.id || index, |
| | | status: "success", |
| | | })); |
| | | |
| | | attachmentDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³ééä»¶å¼¹çª |
| | | const closeAttachmentDialog = () => { |
| | | attachmentDialogVisible.value = false; |
| | | currentFollowRecord.value = {}; |
| | | currentAttachmentList.value = []; |
| | | }; |
| | | |
| | | // éä»¶ä¸ä¼ æå |
| | | const handleAttachmentSuccess = (response, file, fileList) => { |
| | | if (response.code === 200) { |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | // æ´æ°å½åè®°å½çéä»¶å表 |
| | | currentAttachmentList.value = fileList.map(item => ({ |
| | | name: item.name, |
| | | size: item.size, |
| | | url: item.response?.data?.url || item.url, |
| | | id: item.response?.data?.id, |
| | | uid: item.uid, |
| | | status: "success", |
| | | })); |
| | | // æ´æ°åè®°å½ä¸çfilesåæ®µ |
| | | if (currentFollowRecord.value) { |
| | | currentFollowRecord.value.files = [...currentAttachmentList.value]; |
| | | } |
| | | } else { |
| | | proxy.$modal.msgError(response.msg || "ä¸ä¼ 失败"); |
| | | } |
| | | }; |
| | | |
| | | // éä»¶ä¸ä¼ 失败 |
| | | const handleAttachmentError = (error, file, fileList) => { |
| | | console.error("ä¸ä¼ 失败:", error); |
| | | proxy.$modal.msgError("ä¸ä¼ 失败"); |
| | | }; |
| | | |
| | | // éä»¶ç§»é¤ |
| | | const handleAttachmentRemove = (file, fileList) => { |
| | | currentAttachmentList.value = fileList; |
| | | // æ´æ°åè®°å½ä¸çfilesåæ®µ |
| | | if (currentFollowRecord.value) { |
| | | currentFollowRecord.value.files = [...fileList]; |
| | | } |
| | | }; |
| | | |
| | | // éä»¶ä¸ä¼ åæ ¡éª |
| | | const beforeAttachmentUpload = file => { |
| | | const maxSize = 50 * 1024 * 1024; // 50MB |
| | | if (file.size > maxSize) { |
| | | proxy.$modal.msgError("æä»¶å¤§å°ä¸è½è¶
è¿50MB"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | // æ ¼å¼åæä»¶å¤§å° |
| | | const formatFileSize = size => { |
| | | if (size < 1024) { |
| | | return size + " B"; |
| | | } else if (size < 1024 * 1024) { |
| | | return (size / 1024).toFixed(2) + " KB"; |
| | | } else { |
| | | return (size / (1024 * 1024)).toFixed(2) + " MB"; |
| | | } |
| | | }; |
| | | |
| | | // ä¸è½½éä»¶ |
| | | const downloadAttachment = row => { |
| | | if (row.url) { |
| | | // proxy.download(row.url, {}, row.name); |
| | | proxy.$download.name(row.url); |
| | | } else { |
| | | proxy.$modal.msgError("ä¸è½½é¾æ¥ä¸åå¨"); |
| | | } |
| | | }; |
| | | |
| | | // å é¤éä»¶ |
| | | const deleteAttachment = (row, index) => { |
| | | ElMessageBox.confirm("ç¡®å®è¦å é¤è¿ä¸ªéä»¶åï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // è°ç¨å端æ¥å£å é¤éä»¶ |
| | | const deleteUrl = |
| | | import.meta.env.VITE_APP_BASE_API + |
| | | "/basic/customer-follow/file/" + |
| | | row.id; |
| | | fetch(deleteUrl, { |
| | | method: "DELETE", |
| | | headers: { |
| | | Authorization: "Bearer " + getToken(), |
| | | "Content-Type": "application/json", |
| | | }, |
| | | }) |
| | | .then(response => response.json()) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | // å 餿ååæ´æ°æ¬å°æä»¶å表 |
| | | currentAttachmentList.value.splice(index, 1); |
| | | // æ´æ°åè®°å½ä¸çfilesåæ®µ |
| | | if (currentFollowRecord.value) { |
| | | currentFollowRecord.value.files = [ |
| | | ...currentAttachmentList.value, |
| | | ]; |
| | | } |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("å é¤é件失败:", error); |
| | | proxy.$modal.msgError("å é¤å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶å é¤"); |
| | | }); |
| | | }; |
| | | |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .detail-section { |
| | | margin-bottom: 20px; |
| | | padding: 15px; |
| | | background-color: #f9f9f9; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .section-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | margin: 0 0 15px 0; |
| | | color: #333; |
| | | } |
| | | |
| | | .info-display { |
| | | background-color: #fff; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .info-item { |
| | | margin-bottom: 12px; |
| | | display: flex; |
| | | align-items: flex-start; |
| | | } |
| | | |
| | | .info-label { |
| | | width: 120px; |
| | | font-weight: 500; |
| | | color: #606266; |
| | | margin-right: 10px; |
| | | } |
| | | |
| | | .info-value { |
| | | flex: 1; |
| | | color: #303133; |
| | | word-break: break-word; |
| | | } |
| | | |
| | | .no-records { |
| | | text-align: center; |
| | | padding: 30px; |
| | | color: #999; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .attachment-section { |
| | | .upload-area { |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background-color: #f9f9f9; |
| | | border-radius: 4px; |
| | | border: 1px dashed #d9d9d9; |
| | | |
| | | .el-upload__tip { |
| | | margin-top: 10px; |
| | | color: #909399; |
| | | } |
| | | } |
| | | |
| | | .attachment-list { |
| | | h4 { |
| | | margin: 0 0 10px 0; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | } |
| | | |
| | | .no-attachment { |
| | | text-align: center; |
| | | padding: 40px; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | <template> |
| | | <el-dialog v-model="visible" title="éæ©äº§å" width="900px" destroy-on-close :close-on-click-modal="false"> |
| | | <el-form :inline="true" :model="query" class="mb-2"> |
| | | <el-form-item label="产å大类"> |
| | | <el-input v-model="query.productName" placeholder="è¾å
¥äº§å大类" clearable @keyup.enter="onSearch" /> |
| | | <el-form-item label="产ååç§°"> |
| | | <el-input v-model="query.productName" placeholder="è¾å
¥äº§ååç§°" clearable @keyup.enter="onSearch" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="åå·åç§°"> |
| | | <el-input v-model="query.model" placeholder="è¾å
¥åå·åç§°" clearable @keyup.enter="onSearch" /> |
| | | <el-form-item label="产ååå·"> |
| | | <el-input v-model="query.model" placeholder="è¾å
¥äº§ååå·" clearable @keyup.enter="onSearch" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | |
| | | @selection-change="handleSelectionChange" @select="handleSelect"> |
| | | <el-table-column type="selection" width="55" /> |
| | | <el-table-column type="index" label="åºå·" width="60" /> |
| | | <el-table-column prop="productName" label="产å大类" min-width="160" /> |
| | | <el-table-column prop="model" label="åå·åç§°" min-width="200" /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="160" /> |
| | | <el-table-column prop="model" label="产ååå·" min-width="200" /> |
| | | <el-table-column prop="unit" label="åä½" min-width="160" /> |
| | | </el-table> |
| | | |
| | |
| | | <script setup lang="ts"> |
| | | import { computed, onMounted, reactive, ref, watch, nextTick } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { productModelList } from '@/api/basicData/productModel' |
| | | import { productModelList, productModelListByUrl } from '@/api/basicData/productModel' |
| | | |
| | | export type ProductRow = { |
| | | id: number; |
| | |
| | | const props = defineProps<{ |
| | | modelValue: boolean; |
| | | single?: boolean; // æ¯å¦åªè½éæ©ä¸ä¸ªï¼é»è®¤falseï¼å¯éæ©å¤ä¸ªï¼ |
| | | topProductParentId?: number; // ä¸çº§äº§åid |
| | | requestUrl?: string; // èªå®ä¹æ¥è¯¢æ¥å£ |
| | | }>(); |
| | | |
| | | const emit = defineEmits(['update:modelValue', 'confirm']); |
| | |
| | | loading.value = true; |
| | | try { |
| | | multipleSelection.value = []; // 翻页/æç´¢åæ¸
ç©ºéæ©æ´ç¬¦å颿 |
| | | const res: any = await productModelList({ |
| | | const params = { |
| | | productName: query.productName.trim(), |
| | | model: query.model.trim(), |
| | | current: page.pageNum, |
| | | size: page.pageSize, |
| | | }); |
| | | tableData.value = res.records; |
| | | total.value = res.total; |
| | | topProductParentId: props.topProductParentId, |
| | | }; |
| | | const res: any = props.requestUrl |
| | | ? await productModelListByUrl(props.requestUrl, params) |
| | | : await productModelList(params); |
| | | const records = res?.records || res?.data?.records || res?.data || []; |
| | | tableData.value = Array.isArray(records) ? records : []; |
| | | total.value = Number(res?.total ?? res?.data?.total ?? tableData.value.length); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | |
| | | <div class="app-container product-view"> |
| | | <div class="left"> |
| | | <div> |
| | | <el-input |
| | | v-model="search" |
| | | style="width: 210px" |
| | | placeholder="è¾å
¥å
³é®åè¿è¡æç´¢" |
| | | @change="searchFilter" |
| | | @clear="searchFilter" |
| | | clearable |
| | | prefix-icon="Search" |
| | | /> |
| | | <el-button |
| | | type="primary" |
| | | @click="openProDia('addOne')" |
| | | style="margin-left: 10px" |
| | | >æ°å¢äº§å大类</el-button |
| | | > |
| | | <el-input v-model="search" |
| | | style="width: 210px" |
| | | placeholder="è¾å
¥å
³é®åè¿è¡æç´¢" |
| | | @change="searchFilter" |
| | | @clear="searchFilter" |
| | | clearable |
| | | prefix-icon="Search" /> |
| | | <el-button v-if="false" |
| | | type="primary" |
| | | @click="openProDia('addOne')" |
| | | style="margin-left: 10px">æ°å¢äº§å大类</el-button> |
| | | </div> |
| | | <div ref="containerRef"> |
| | | <el-tree |
| | | ref="tree" |
| | | v-loading="treeLoad" |
| | | :data="list" |
| | | @node-click="handleNodeClick" |
| | | :expand-on-click-node="false" |
| | | :default-expanded-keys="expandedKeys" |
| | | :filter-node-method="filterNode" |
| | | :props="{ children: 'children', label: 'label' }" |
| | | highlight-current |
| | | node-key="id" |
| | | class="product-tree-scroll" |
| | | style="height: calc(100vh - 190px); overflow-y: auto" |
| | | > |
| | | <el-tree ref="tree" |
| | | v-loading="treeLoad" |
| | | :data="list" |
| | | @node-click="handleNodeClick" |
| | | :expand-on-click-node="false" |
| | | @node-expand="handleNodeExpand" |
| | | @node-collapse="handleNodeCollapse" |
| | | :key="treeKey" |
| | | :default-expanded-keys="expandedKeys" |
| | | :filter-node-method="filterNode" |
| | | :props="{ children: 'children', label: 'label' }" |
| | | highlight-current |
| | | node-key="id" |
| | | class="product-tree-scroll" |
| | | 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" |
| | | link |
| | | @click="openProDia('edit', data)" |
| | | > |
| | | <el-button type="primary" |
| | | link |
| | | :disabled="isTopLevelNode(data, node)" |
| | | @click="openProDia('edit', data)"> |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button type="primary" link @click="openProDia('add', data)" :disabled="node.level >= 3"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="openProDia('add', data)"> |
| | | æ·»å 产å |
| | | </el-button> |
| | | <el-button |
| | | v-if="!node.childNodes.length" |
| | | style="margin-left: 4px" |
| | | type="danger" |
| | | link |
| | | @click="remove(node, data)" |
| | | > |
| | | <el-button v-if="!node.childNodes.length" |
| | | style="margin-left: 4px" |
| | | type="danger" |
| | | link |
| | | :disabled="isTopLevelNode(data, node)" |
| | | @click="remove(node, data)"> |
| | | å é¤ |
| | | </el-button> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | <div class="right"> |
| | | <div style="margin-bottom: 10px" v-if="isShowButton"> |
| | | <el-button type="primary" @click="openModelDia('add')"> |
| | | <div style="margin-bottom: 10px" |
| | | v-if="isShowButton"> |
| | | <el-button type="primary" |
| | | @click="openModelDia('add')"> |
| | | æ°å¢è§æ ¼åå· |
| | | </el-button> |
| | | <ImportExcel :product-id="currentId" @uploadSuccess="getModelList" /> |
| | | <el-button |
| | | type="danger" |
| | | @click="handleDelete" |
| | | style="margin-left: 10px" |
| | | plain |
| | | > |
| | | <ImportExcel :product-id="currentId" |
| | | @uploadSuccess="getModelList" /> |
| | | <el-button type="danger" |
| | | @click="handleDelete" |
| | | style="margin-left: 10px" |
| | | plain> |
| | | å é¤ |
| | | </el-button> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | ></PIMTable> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination"></PIMTable> |
| | | </div> |
| | | <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" |
| | | > |
| | | <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"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="产ååç§°ï¼" prop="productName"> |
| | | <el-input |
| | | v-model="form.productName" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | maxlength="20" |
| | | show-word-limit |
| | | clearable |
| | | @keydown.enter.prevent |
| | | /> |
| | | <el-form-item label="产ååç§°ï¼" |
| | | prop="productName"> |
| | | <el-input v-model="form.productName" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | maxlength="20" |
| | | show-word-limit |
| | | clearable |
| | | @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" |
| | | title="è§æ ¼åå·" |
| | | width="400px" |
| | | @close="closeModelDia" |
| | | @keydown.enter.prevent |
| | | > |
| | | <el-form |
| | | :model="modelForm" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="modelRules" |
| | | ref="modelFormRef" |
| | | > |
| | | <el-dialog v-model="modelDia" |
| | | title="è§æ ¼åå·" |
| | | width="400px" |
| | | @close="closeModelDia" |
| | | @keydown.enter.prevent> |
| | | <el-form :model="modelForm" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="modelRules" |
| | | ref="modelFormRef"> |
| | | <el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <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 |
| | | /> |
| | | <el-form-item label="è§æ ¼åå·ï¼" |
| | | prop="model"> |
| | | <el-input v-model="modelForm.model" |
| | | placeholder="请è¾å
¥è§æ ¼åå·" |
| | | clearable |
| | | @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" |
| | | placeholder="请è¾å
¥åä½" |
| | | clearable |
| | | @keydown.enter.prevent |
| | | /> |
| | | <el-form-item label="åä½ï¼" |
| | | prop="unit"> |
| | | <el-input v-model="modelForm.unit" |
| | | placeholder="请è¾å
¥åä½" |
| | | clearable |
| | | @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> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { |
| | | addOrEditProduct, |
| | | addOrEditProductModel, |
| | | delProduct, |
| | | delProductModel, |
| | | modelListPage, |
| | | productTreeList, |
| | | } from "@/api/basicData/product.js"; |
| | | import ImportExcel from "./ImportExcel/index.vue"; |
| | | import { nextTick, ref } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { |
| | | addOrEditProduct, |
| | | addOrEditProductModel, |
| | | delProduct, |
| | | delProductModel, |
| | | modelListPage, |
| | | productTreeList, |
| | | } from "@/api/basicData/product.js"; |
| | | import ImportExcel from "./ImportExcel/index.vue"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const tree = ref(null); |
| | | const containerRef = ref(null); |
| | | const { proxy } = getCurrentInstance(); |
| | | const tree = ref(null); |
| | | const containerRef = ref(null); |
| | | const treeKey = ref(0); |
| | | const expandedKeySet = new Set(); |
| | | const EXPANDED_STORAGE_KEY = "basicData_product_tree_expanded_keys_v2"; |
| | | |
| | | const productDia = ref(false); |
| | | const modelDia = ref(false); |
| | | const modelOperationType = ref(""); |
| | | const search = ref(""); |
| | | const currentId = ref(""); |
| | | const currentParentId = ref(""); |
| | | const operationType = ref(""); |
| | | const treeLoad = ref(false); |
| | | const list = ref([]); |
| | | const expandedKeys = ref([]); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "model", |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openModelDia("edit", row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const isShowButton = ref(false); |
| | | const selectedRows = ref([]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const data = reactive({ |
| | | form: { |
| | | productName: "", |
| | | }, |
| | | rules: { |
| | | productName: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | { max: 20, message: "产ååç§°ä¸è½è¶
è¿20个å符", trigger: "blur" }, |
| | | ], |
| | | }, |
| | | modelForm: { |
| | | model: "", |
| | | unit: "", |
| | | }, |
| | | modelRules: { |
| | | model: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | unit: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const { form, rules, modelForm, modelRules } = toRefs(data); |
| | | // æ¥è¯¢äº§åæ |
| | | const getProductTreeList = () => { |
| | | treeLoad.value = true; |
| | | productTreeList() |
| | | .then((res) => { |
| | | list.value = res; |
| | | list.value.forEach((a) => { |
| | | expandedKeys.value.push(a.label); |
| | | const loadExpandedKeys = () => { |
| | | if (typeof window === "undefined") { |
| | | return []; |
| | | } |
| | | try { |
| | | const saved = localStorage.getItem(EXPANDED_STORAGE_KEY); |
| | | return saved ? JSON.parse(saved) : []; |
| | | } catch (error) { |
| | | console.error(error); |
| | | return []; |
| | | } |
| | | }; |
| | | |
| | | const saveExpandedKeys = () => { |
| | | if (typeof window === "undefined") { |
| | | return; |
| | | } |
| | | localStorage.setItem( |
| | | EXPANDED_STORAGE_KEY, |
| | | JSON.stringify(Array.from(expandedKeySet)) |
| | | ); |
| | | }; |
| | | |
| | | loadExpandedKeys().forEach(key => expandedKeySet.add(key)); |
| | | |
| | | const syncExpandedKeysFromTree = () => { |
| | | const keys = []; |
| | | const walk = nodes => { |
| | | (nodes || []).forEach(item => { |
| | | if (item.expanded && item.data?.id !== undefined) { |
| | | keys.push(item.data.id); |
| | | } |
| | | if (item.childNodes && item.childNodes.length) { |
| | | walk(item.childNodes); |
| | | } |
| | | }); |
| | | treeLoad.value = false; |
| | | }) |
| | | .catch((err) => { |
| | | treeLoad.value = false; |
| | | }); |
| | | }; |
| | | // è¿æ»¤äº§åæ |
| | | const searchFilter = () => { |
| | | proxy.$refs.tree.filter(search.value); |
| | | }; |
| | | // æå¼äº§åå¼¹æ¡ |
| | | const openProDia = (type, data) => { |
| | | operationType.value = type; |
| | | productDia.value = true; |
| | | form.value.productName = ""; |
| | | if (type === "edit") { |
| | | form.value.productName = data.productName; |
| | | } |
| | | }; |
| | | // æå¼è§æ ¼åå·å¼¹æ¡ |
| | | const openModelDia = (type, data) => { |
| | | modelOperationType.value = type; |
| | | modelDia.value = true; |
| | | modelForm.value.model = ""; |
| | | modelForm.value.model = ""; |
| | | modelForm.value.id = ""; |
| | | if (type === "edit") { |
| | | modelForm.value = { ...data }; |
| | | } |
| | | }; |
| | | // æäº¤äº§ååç§°ä¿®æ¹ |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate((valid) => { |
| | | if (valid) { |
| | | if (operationType.value === "add") { |
| | | form.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 = ""; |
| | | }; |
| | | |
| | | walk(tree.value?.root?.childNodes); |
| | | expandedKeySet.clear(); |
| | | keys.forEach(key => expandedKeySet.add(key)); |
| | | expandedKeys.value = keys; |
| | | saveExpandedKeys(); |
| | | }; |
| | | |
| | | const normalizeExpandedKeys = treeData => { |
| | | const parentMap = new Map(); |
| | | const walk = (nodes, parentId = null) => { |
| | | (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 => { |
| | | if (!parentMap.has(key)) { |
| | | return false; |
| | | } |
| | | addOrEditProduct(form.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeProDia(); |
| | | getProductTreeList(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | // å
³é产åå¼¹æ¡ |
| | | const closeProDia = () => { |
| | | proxy.$refs.formRef.resetFields(); |
| | | productDia.value = false; |
| | | }; |
| | | let currentId = key; |
| | | while (parentMap.has(currentId)) { |
| | | const parentId = parentMap.get(currentId); |
| | | if (!parentId) { |
| | | return true; |
| | | } |
| | | if (!expandedKeySet.has(parentId)) { |
| | | return false; |
| | | } |
| | | currentId = parentId; |
| | | } |
| | | return true; |
| | | }); |
| | | |
| | | // å é¤äº§å |
| | | const remove = (node, data) => { |
| | | let ids = []; |
| | | ids.push(data.id); |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delProduct(ids) |
| | | .then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | if (normalizedKeys.length !== expandedKeySet.size) { |
| | | expandedKeySet.clear(); |
| | | normalizedKeys.forEach(key => expandedKeySet.add(key)); |
| | | saveExpandedKeys(); |
| | | } |
| | | }; |
| | | |
| | | const productDia = ref(false); |
| | | const modelDia = ref(false); |
| | | const modelOperationType = ref(""); |
| | | const search = ref(""); |
| | | const currentId = ref(""); |
| | | const currentParentId = ref(""); |
| | | const operationType = ref(""); |
| | | const treeLoad = ref(false); |
| | | const list = ref([]); |
| | | const expandedKeys = ref([]); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "产åç¼å·", |
| | | prop: "productCode", |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "model", |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openModelDia("edit", row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const isShowButton = ref(false); |
| | | const selectedRows = ref([]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const data = reactive({ |
| | | form: { |
| | | productName: "", |
| | | }, |
| | | rules: { |
| | | productName: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | { max: 20, message: "产ååç§°ä¸è½è¶
è¿20个å符", trigger: "blur" }, |
| | | ], |
| | | }, |
| | | 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 => { |
| | | list.value = res || []; |
| | | normalizeExpandedKeys(list.value); |
| | | expandedKeys.value = Array.from(expandedKeySet); |
| | | treeKey.value += 1; |
| | | nextTick(() => { |
| | | tree.value?.setDefaultExpandedKeys?.(expandedKeys.value); |
| | | }); |
| | | }) |
| | | .catch(err => { |
| | | console.error(err); |
| | | }) |
| | | .finally(() => { |
| | | treeLoad.value = false; |
| | | }); |
| | | }; |
| | | const handleNodeExpand = data => { |
| | | nextTick(syncExpandedKeysFromTree); |
| | | }; |
| | | const handleNodeCollapse = (data, node) => { |
| | | node?.eachNode?.(item => { |
| | | item.collapse(); |
| | | }); |
| | | nextTick(syncExpandedKeysFromTree); |
| | | }; |
| | | // è¿æ»¤äº§åæ |
| | | const searchFilter = () => { |
| | | proxy.$refs.tree.filter(search.value); |
| | | }; |
| | | const isTopLevelNode = (data, node) => { |
| | | if (node?.level !== undefined) { |
| | | return node.level === 1; |
| | | } |
| | | return [null, undefined, "", 0, "0"].includes(data?.parentId); |
| | | }; |
| | | // æå¼äº§åå¼¹æ¡ |
| | | const openProDia = (type, data) => { |
| | | if (data && type === "edit" && isTopLevelNode(data)) { |
| | | proxy.$modal.msgWarning("ä¸çº§èç¹ä¸è½ç¼è¾æå é¤"); |
| | | return; |
| | | } |
| | | operationType.value = type; |
| | | productDia.value = true; |
| | | form.value.productName = ""; |
| | | if (type === "edit") { |
| | | form.value.productName = data.productName; |
| | | } |
| | | }; |
| | | // æå¼è§æ ¼åå·å¼¹æ¡ |
| | | const openModelDia = (type, data) => { |
| | | modelOperationType.value = type; |
| | | modelDia.value = true; |
| | | 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 => { |
| | | if (valid) { |
| | | if (operationType.value === "add") { |
| | | form.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 = ""; |
| | | } |
| | | addOrEditProduct(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeProDia(); |
| | | getProductTreeList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | } |
| | | }); |
| | | }; |
| | | // éæ©äº§å |
| | | const handleNodeClick = (val, node, el) => { |
| | | // 夿æ¯å¦ä¸ºå¶åèç¹ |
| | | isShowButton.value = !(val.children && val.children.length > 0); |
| | | // åªæå¶åèç¹ææ§è¡ä»¥ä¸é»è¾ |
| | | currentId.value = val.id; |
| | | currentParentId.value = val.parentId; |
| | | getModelList(); |
| | | }; |
| | | }; |
| | | // å
³é产åå¼¹æ¡ |
| | | const closeProDia = () => { |
| | | proxy.$refs.formRef.resetFields(); |
| | | productDia.value = false; |
| | | }; |
| | | |
| | | // æäº¤è§æ ¼åå·ä¿®æ¹ |
| | | const submitModelForm = () => { |
| | | proxy.$refs.modelFormRef.validate((valid) => { |
| | | if (valid) { |
| | | modelForm.value.productId = currentId.value; |
| | | addOrEditProductModel(modelForm.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeModelDia(); |
| | | getModelList(); |
| | | }); |
| | | // å é¤äº§å |
| | | const remove = (node, data) => { |
| | | if (isTopLevelNode(data, node)) { |
| | | proxy.$modal.msgWarning("ä¸çº§èç¹ä¸è½ç¼è¾æå é¤"); |
| | | return; |
| | | } |
| | | }); |
| | | }; |
| | | // å
³éåå·å¼¹æ¡ |
| | | const closeModelDia = () => { |
| | | proxy.$refs.modelFormRef.resetFields(); |
| | | modelDia.value = false; |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æ¥è¯¢è§æ ¼åå· |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getModelList(); |
| | | }; |
| | | const getModelList = () => { |
| | | tableLoading.value = true; |
| | | modelListPage({ |
| | | id: currentId.value, |
| | | current: page.current, |
| | | size: page.size, |
| | | }).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); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delProductModel(ids) |
| | | .then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getModelList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | let ids = []; |
| | | ids.push(data.id); |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delProduct(ids) |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getProductTreeList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // éæ©äº§å |
| | | const handleNodeClick = (val, node, el) => { |
| | | // 夿æ¯å¦ä¸ºå¶åèç¹ |
| | | isShowButton.value = !(val.children && val.children.length > 0); |
| | | // åªæå¶åèç¹ææ§è¡ä»¥ä¸é»è¾ |
| | | currentId.value = val.id; |
| | | currentParentId.value = val.parentId; |
| | | getModelList(); |
| | | }; |
| | | |
| | | // æäº¤è§æ ¼åå·ä¿®æ¹ |
| | | const submitModelForm = () => { |
| | | proxy.$refs.modelFormRef.validate(valid => { |
| | | if (valid) { |
| | | modelForm.value.productId = currentId.value; |
| | | addOrEditProductModel(modelForm.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeModelDia(); |
| | | getModelList(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | // è°ç¨treeè¿æ»¤æ¹æ³ 䏿è±è¿æ»¤ |
| | | const filterNode = (value, data, node) => { |
| | | if (!value) { |
| | | //å¦ææ°æ®ä¸ºç©ºï¼åè¿åtrue,æ¾ç¤ºææçæ°æ®é¡¹ |
| | | return true; |
| | | } |
| | | // æ¥è¯¢å表æ¯å¦æå¹é
æ°æ®ï¼å°å¼å°åï¼å¹é
è±ææ°æ® |
| | | let val = value.toLowerCase(); |
| | | return chooseNode(val, data, node); // è°ç¨è¿æ»¤äºå±æ¹æ³ |
| | | }; |
| | | // è¿æ»¤ç¶èç¹ / åèç¹ (妿è¾å
¥çåæ°æ¯ç¶èç¹ä¸è½å¹é
ï¼åè¿å该èç¹ä»¥åå
¶ä¸çææåèç¹ï¼å¦æåæ°æ¯åèç¹ï¼åè¿å该èç¹çç¶èç¹ãnameæ¯ä¸æå符ï¼enNameæ¯è±æå符. |
| | | const chooseNode = (value, data, node) => { |
| | | if (data.label.indexOf(value) !== -1) { |
| | | return true; |
| | | } |
| | | const level = node.level; |
| | | // å¦æä¼ å
¥çèç¹æ¬èº«å°±æ¯ä¸çº§èç¹å°±ä¸ç¨æ ¡éªäº |
| | | if (level === 1) { |
| | | return false; |
| | | } |
| | | // å
åå½åèç¹çç¶èç¹ |
| | | let parentData = node.parent; |
| | | // éåå½åèç¹çç¶èç¹ |
| | | let index = 0; |
| | | while (index < level - 1) { |
| | | // 妿å¹é
å°ç´æ¥è¿åï¼æ¤å¤name弿¯ä¸æå符ï¼enNameæ¯è±æå符ã夿å¹é
ä¸è±æè¿æ»¤ |
| | | if (parentData.data.label.indexOf(value) !== -1) { |
| | | }; |
| | | // å
³éåå·å¼¹æ¡ |
| | | const closeModelDia = () => { |
| | | proxy.$refs.modelFormRef.resetFields(); |
| | | modelDia.value = false; |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æ¥è¯¢è§æ ¼åå· |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getModelList(); |
| | | }; |
| | | const getModelList = () => { |
| | | tableLoading.value = true; |
| | | modelListPage({ |
| | | id: currentId.value, |
| | | current: page.current, |
| | | size: page.size, |
| | | }).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); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delProductModel(ids) |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getModelList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // è°ç¨treeè¿æ»¤æ¹æ³ 䏿è±è¿æ»¤ |
| | | const filterNode = (value, data, node) => { |
| | | if (!value) { |
| | | //å¦ææ°æ®ä¸ºç©ºï¼åè¿åtrue,æ¾ç¤ºææçæ°æ®é¡¹ |
| | | return true; |
| | | } |
| | | // å¦åçè¯åå¾ä¸ä¸å±åå¹é
|
| | | parentData = parentData.parent; |
| | | index++; |
| | | } |
| | | // 没å¹é
å°è¿åfalse |
| | | return false; |
| | | }; |
| | | getProductTreeList(); |
| | | // æ¥è¯¢å表æ¯å¦æå¹é
æ°æ®ï¼å°å¼å°åï¼å¹é
è±ææ°æ® |
| | | let val = value.toLowerCase(); |
| | | return chooseNode(val, data, node); // è°ç¨è¿æ»¤äºå±æ¹æ³ |
| | | }; |
| | | // è¿æ»¤ç¶èç¹ / åèç¹ (妿è¾å
¥çåæ°æ¯ç¶èç¹ä¸è½å¹é
ï¼åè¿å该èç¹ä»¥åå
¶ä¸çææåèç¹ï¼å¦æåæ°æ¯åèç¹ï¼åè¿å该èç¹çç¶èç¹ãnameæ¯ä¸æå符ï¼enNameæ¯è±æå符. |
| | | const chooseNode = (value, data, node) => { |
| | | if (data.label.indexOf(value) !== -1) { |
| | | return true; |
| | | } |
| | | const level = node.level; |
| | | // å¦æä¼ å
¥çèç¹æ¬èº«å°±æ¯ä¸çº§èç¹å°±ä¸ç¨æ ¡éªäº |
| | | if (level === 1) { |
| | | return false; |
| | | } |
| | | // å
åå½åèç¹çç¶èç¹ |
| | | let parentData = node.parent; |
| | | // éåå½åèç¹çç¶èç¹ |
| | | let index = 0; |
| | | while (index < level - 1) { |
| | | // 妿å¹é
å°ç´æ¥è¿åï¼æ¤å¤name弿¯ä¸æå符ï¼enNameæ¯è±æå符ã夿å¹é
ä¸è±æè¿æ»¤ |
| | | if (parentData.data.label.indexOf(value) !== -1) { |
| | | return true; |
| | | } |
| | | // å¦åçè¯åå¾ä¸ä¸å±åå¹é
|
| | | parentData = parentData.parent; |
| | | index++; |
| | | } |
| | | // 没å¹é
å°è¿åfalse |
| | | return false; |
| | | }; |
| | | getProductTreeList(); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .product-view { |
| | | display: flex; |
| | | } |
| | | .left { |
| | | width: 450px; |
| | | min-width: 450px; |
| | | padding: 16px; |
| | | background: #ffffff; |
| | | } |
| | | .right { |
| | | flex: 1; |
| | | min-width: 0; |
| | | padding: 16px; |
| | | margin-left: 20px; |
| | | background: #ffffff; |
| | | } |
| | | .custom-tree-node { |
| | | flex: 1; |
| | | min-width: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | font-size: 14px; |
| | | padding-right: 8px; |
| | | } |
| | | .tree-node-content { |
| | | flex: 1; |
| | | min-width: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | } |
| | | .tree-node-content .orange-icon { |
| | | flex-shrink: 0; |
| | | } |
| | | .tree-node-label { |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | .orange-icon { |
| | | color: orange; |
| | | font-size: 18px; |
| | | margin-right: 8px; /* 徿 䏿åä¹é´å ç¹é´è· */ |
| | | } |
| | | .product-tree-scroll { |
| | | scrollbar-width: thin; |
| | | scrollbar-color: #c0c4cc #f5f7fa; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar { |
| | | width: 8px; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar-track { |
| | | background: #f5f7fa; |
| | | border-radius: 4px; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar-thumb { |
| | | background: #c0c4cc; |
| | | border-radius: 4px; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar-thumb:hover { |
| | | background: #909399; |
| | | } |
| | | .product-view { |
| | | display: flex; |
| | | } |
| | | .left { |
| | | width: 450px; |
| | | min-width: 450px; |
| | | padding: 16px; |
| | | background: #ffffff; |
| | | } |
| | | .right { |
| | | flex: 1; |
| | | min-width: 0; |
| | | padding: 16px; |
| | | margin-left: 20px; |
| | | background: #ffffff; |
| | | } |
| | | .custom-tree-node { |
| | | flex: 1; |
| | | min-width: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | font-size: 14px; |
| | | padding-right: 8px; |
| | | } |
| | | .tree-node-content { |
| | | flex: 1; |
| | | min-width: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | } |
| | | .tree-node-content .orange-icon { |
| | | flex-shrink: 0; |
| | | } |
| | | .tree-node-label { |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | .orange-icon { |
| | | color: orange; |
| | | font-size: 18px; |
| | | margin-right: 8px; /* 徿 䏿åä¹é´å ç¹é´è· */ |
| | | } |
| | | .product-tree-scroll { |
| | | scrollbar-width: thin; |
| | | scrollbar-color: #c0c4cc #f5f7fa; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar { |
| | | width: 8px; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar-track { |
| | | background: #f5f7fa; |
| | | border-radius: 4px; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar-thumb { |
| | | background: #c0c4cc; |
| | | border-radius: 4px; |
| | | } |
| | | .product-tree-scroll::-webkit-scrollbar-thumb:hover { |
| | | background: #909399; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div> |
| | | <div class="search_form"> |
| | | <div> |
| | | <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 |
| | | > |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <div class="header-title"> |
| | | <el-icon class="title-icon"><Setting /></el-icon> |
| | | <span>å®¡æ¹æµç¨é
ç½®</span> |
| | | </div> |
| | | <div class="header-desc">为ä¸å审æ¹ç±»åé
ç½®å®¡æ¹æµç¨å审æ¹äºº</div> |
| | | </div> |
| | | |
| | | <!-- 审æ¹ç±»å忢 - ç´§åæ ç¾å¼ --> |
| | | <div class="type-tabs"> |
| | | <div |
| | | v-for="type in approveTypes" |
| | | :key="type.value" |
| | | class="type-tab" |
| | | :class="{ active: activeTab === type.value }" |
| | | @click="activeTab = type.value; handleTabChange()" |
| | | > |
| | | <el-icon :size="14" :style="{ color: activeTab === type.value ? type.color : '#909399' }"> |
| | | <component :is="type.icon" /> |
| | | </el-icon> |
| | | <span class="tab-name">{{ type.label }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | | <el-card class="config-card" shadow="hover" v-loading="loading"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <div class="header-left"> |
| | | <div class="type-icon" :style="{ backgroundColor: getTypeColor(currentApproveType) }"> |
| | | <el-icon :size="20" color="#fff"><component :is="getTypeIcon(currentApproveType)" /></el-icon> |
| | | </div> |
| | | <div class="header-info"> |
| | | <span class="type-name">{{ currentApproveTypeName }}</span> |
| | | <el-tag :type="approverList.length > 0 ? 'success' : 'warning'" size="small" effect="light"> |
| | | {{ approverList.length > 0 ? `å·²é
ç½® ${approverList.length} 个审æ¹äºº` : 'æªé
置审æ¹äºº' }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | <div class="header-actions" v-if="approverList.length > 0"> |
| | | <el-button @click="handleReset" size="default"> |
| | | <el-icon><RefreshLeft /></el-icon> |
| | | éç½® |
| | | </el-button> |
| | | <el-button type="primary" @click="handleSave" :loading="saveLoading" size="default"> |
| | | <el-icon><Check /></el-icon> |
| | | ä¿åé
ç½® |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- å®¡æ¹æµç¨å±ç¤º --> |
| | | <div class="flow-wrapper" v-if="approverList.length > 0"> |
| | | <div class="flow-container"> |
| | | <div |
| | | v-for="(item, index) in approverList" |
| | | :key="index" |
| | | class="flow-item" |
| | | > |
| | | <!-- 审æ¹èç¹å¡ç --> |
| | | <div class="node-card" :class="{ 'empty': !item.approverId }"> |
| | | <!-- é¡¶é¨åºå·åçº§å« --> |
| | | <div class="node-badge">{{ index + 1 }}</div> |
| | | |
| | | <!-- 头ååºå --> |
| | | <div class="node-avatar-section"> |
| | | <div |
| | | class="node-avatar" |
| | | :class="{ 'has-user': item.approverId }" |
| | | :style="item.approverId ? { backgroundColor: getAvatarColor(item.approverName) } : {}" |
| | | > |
| | | <span v-if="item.approverId">{{ item.approverName.charAt(0) }}</span> |
| | | <el-icon v-else :size="24"><User /></el-icon> |
| | | </div> |
| | | <div class="node-level">{{ getLevelText(index) }}</div> |
| | | </div> |
| | | |
| | | <!-- éæ©åºå --> |
| | | <div class="node-select-section"> |
| | | <el-select |
| | | v-model="item.approverId" |
| | | placeholder="鿩审æ¹äºº" |
| | | filterable |
| | | size="default" |
| | | @change="(val) => handleApproverChange(val, item)" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | </div> |
| | | |
| | | <!-- æä½æé® --> |
| | | <div class="node-actions"> |
| | | <el-button |
| | | type="primary" |
| | | circle |
| | | :disabled="index === 0" |
| | | @click="moveLeft(index)" |
| | | size="small" |
| | | class="action-btn" |
| | | title="åç§»" |
| | | > |
| | | <el-icon><ArrowLeft /></el-icon> |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | circle |
| | | :disabled="index === approverList.length - 1" |
| | | @click="moveRight(index)" |
| | | size="small" |
| | | class="action-btn" |
| | | title="åç§»" |
| | | > |
| | | <el-icon><ArrowRight /></el-icon> |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | circle |
| | | @click="handleDelete(index)" |
| | | size="small" |
| | | class="action-btn" |
| | | > |
| | | <el-icon><Delete /></el-icon> |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¿æ¥ç®å¤´ --> |
| | | <div class="arrow-connector" v-if="index < approverList.length - 1"> |
| | | <div class="arrow-line"></div> |
| | | <el-icon class="arrow-icon"><ArrowRight /></el-icon> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ°å¢èç¹æé® - æ¾å¨æµç¨æå --> |
| | | <div class="add-node-item"> |
| | | <div class="arrow-connector" v-if="approverList.length > 0"> |
| | | <div class="arrow-line"></div> |
| | | <el-icon class="arrow-icon"><ArrowRight /></el-icon> |
| | | </div> |
| | | <div class="add-node-card" @click="handleAdd"> |
| | | <div class="add-icon-wrapper"> |
| | | <el-icon :size="28"><Plus /></el-icon> |
| | | </div> |
| | | <span class="add-text">æ°å¢å®¡æ¹äºº</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç©ºç¶æ --> |
| | | <div class="empty-state" v-else> |
| | | <div class="empty-content"> |
| | | <div class="empty-icon-wrapper"> |
| | | <el-icon :size="48" color="#c0c4cc"><User /></el-icon> |
| | | </div> |
| | | <div class="empty-text">ææ å®¡æ¹äººé
ç½®</div> |
| | | <div class="empty-subtext">ç¹å»ä¸æ¹æé®æ·»å 第ä¸ä¸ªå®¡æ¹äºº</div> |
| | | <el-button type="primary" size="large" @click="handleAdd" class="empty-add-btn"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ°å¢å®¡æ¹äºº |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- åºé¨æç¤º --> |
| | | <div class="bottom-tips"> |
| | | <el-icon><InfoFilled /></el-icon> |
| | | <span>æç¤ºï¼æ¯ä¸ªæµç¨è³å°é
ç½®ä¸ä¸ªå®¡æ¹äººï¼å®¡æ¹æé¡ºåºæµè½¬ï¼å¯éè¿ç®å¤´è°æ´é¡ºåº</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from 'vue'; |
| | | import { ElMessage, ElMessageBox } from 'element-plus'; |
| | | import { |
| | | Plus, ArrowLeft, Delete, Check, RefreshLeft, Setting, |
| | | Suitcase, Calendar, Location, Money, ShoppingCart, DocumentChecked, |
| | | Van, ArrowRight, User, InfoFilled |
| | | } from '@element-plus/icons-vue'; |
| | | import { getApproveProcessConfigNodeList, addApproveProcessConfigNode } from '@/api/collaborativeApproval/approvalManagement'; |
| | | import { userListNoPage } from '@/api/system/user'; |
| | | |
| | | // å½åéä¸çæ ç¾é¡µ |
| | | const activeTab = ref('1'); |
| | | |
| | | // 审æ¹ç±»åé
ç½®æ°ç» |
| | | const approveTypes = [ |
| | | { value: '1', label: 'å
¬åºç®¡ç', icon: 'Suitcase', color: '#409EFF' }, |
| | | { value: '2', label: '请å管ç', icon: 'Calendar', color: '#67C23A' }, |
| | | { value: '3', label: 'åºå·®ç®¡ç', icon: 'Location', color: '#E6A23C' }, |
| | | { value: '4', label: 'æ¥é管ç', icon: 'Money', color: '#F56C6C' }, |
| | | { value: '5', label: 'éè´å®¡æ¹', icon: 'ShoppingCart', color: '#909399' }, |
| | | { value: '6', label: 'æ¥ä»·å®¡æ¹', icon: 'DocumentChecked', color: '#9B59B6' }, |
| | | { value: '7', label: 'å货审æ¹', icon: 'Van', color: '#1ABC9C' }, |
| | | ]; |
| | | |
| | | // 审æ¹ç±»ååç§°æ å° |
| | | const approveTypeNameMap = { |
| | | 1: 'å
¬åºç®¡ç', |
| | | 2: '请å管ç', |
| | | 3: 'åºå·®ç®¡ç', |
| | | 4: 'æ¥é管ç', |
| | | 5: 'éè´å®¡æ¹', |
| | | 6: 'æ¥ä»·å®¡æ¹', |
| | | 7: 'å货审æ¹', |
| | | }; |
| | | |
| | | // 审æ¹ç±»å徿 æ å° |
| | | const typeIconMap = { |
| | | 1: 'Suitcase', |
| | | 2: 'Calendar', |
| | | 3: 'Location', |
| | | 4: 'Money', |
| | | 5: 'ShoppingCart', |
| | | 6: 'DocumentChecked', |
| | | 7: 'Van', |
| | | }; |
| | | |
| | | // 审æ¹ç±»åé¢è²æ å° |
| | | const typeColorMap = { |
| | | 1: '#409EFF', |
| | | 2: '#67C23A', |
| | | 3: '#E6A23C', |
| | | 4: '#F56C6C', |
| | | 5: '#909399', |
| | | 6: '#9B59B6', |
| | | 7: '#1ABC9C', |
| | | }; |
| | | |
| | | // 头åé¢è²æ± |
| | | const avatarColors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#9B59B6', '#1ABC9C', '#FF6B6B', '#4ECDC4']; |
| | | |
| | | // å½å审æ¹ç±»ååç§° |
| | | const currentApproveTypeName = computed(() => { |
| | | return approveTypeNameMap[activeTab.value] || 'æªç¥ç±»å'; |
| | | }); |
| | | |
| | | // å½å审æ¹ç±»å |
| | | const currentApproveType = computed(() => { |
| | | return Number(activeTab.value); |
| | | }); |
| | | |
| | | // è·åç±»å徿 |
| | | const getTypeIcon = (type) => typeIconMap[type] || 'Setting'; |
| | | |
| | | // è·åç±»åé¢è² |
| | | const getTypeColor = (type) => typeColorMap[type] || '#409EFF'; |
| | | |
| | | // è·å头åé¢è² |
| | | const getAvatarColor = (name) => { |
| | | if (!name) return '#C0C4CC'; |
| | | let hash = 0; |
| | | for (let i = 0; i < name.length; i++) { |
| | | hash = name.charCodeAt(i) + ((hash << 5) - hash); |
| | | } |
| | | return avatarColors[Math.abs(hash) % avatarColors.length]; |
| | | }; |
| | | |
| | | // è·åçº§å«ææ¬ |
| | | const getLevelText = (index) => { |
| | | const texts = ['第ä¸çº§', '第äºçº§', '第ä¸çº§', '第å级', '第äºçº§', '第å
级', '第ä¸çº§', '第å
«çº§']; |
| | | return texts[index] || `第${index + 1}级`; |
| | | }; |
| | | |
| | | // 审æ¹äººå表ï¼ç宿¥å£ï¼ |
| | | const userList = ref([]); |
| | | |
| | | // 审æ¹äººå表 |
| | | const approverList = ref([]); |
| | | |
| | | // åå§æ°æ®ï¼ç¨äºéç½® |
| | | const originalList = ref([]); |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | const saveLoading = ref(false); |
| | | |
| | | // æ ç¾é¡µåæ¢å¤ç |
| | | const handleTabChange = () => { |
| | | loadData(); |
| | | }; |
| | | |
| | | // å 载审æ¹é
ç½®æ°æ®ï¼æ¨¡æï¼ |
| | | const loadData = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const res = await getApproveProcessConfigNodeList(currentApproveType.value); |
| | | const source = Array.isArray(res?.data) |
| | | ? res.data |
| | | : Array.isArray(res?.rows) |
| | | ? res.rows |
| | | : Array.isArray(res?.data?.records) |
| | | ? res.data.records |
| | | : []; |
| | | const data = source.map((item, index) => ({ |
| | | ...item, |
| | | sortOrder: item.nodeOrder ?? item.sortOrder ?? index + 1, |
| | | })); |
| | | approverList.value = data.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)); |
| | | originalList.value = JSON.parse(JSON.stringify(approverList.value)); |
| | | } catch (error) { |
| | | approverList.value = []; |
| | | originalList.value = []; |
| | | ElMessage.error('å 载审æ¹é
置失败'); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const loadUserList = async () => { |
| | | try { |
| | | const res = await userListNoPage(); |
| | | userList.value = Array.isArray(res?.data) ? res.data : []; |
| | | } catch (error) { |
| | | userList.value = []; |
| | | ElMessage.error('å 载人åå表失败'); |
| | | } |
| | | }; |
| | | |
| | | // 审æ¹äººéæ©åå |
| | | const handleApproverChange = (userId, row) => { |
| | | const user = userList.value.find((u) => u.userId === userId); |
| | | if (user) { |
| | | row.approverName = user.nickName; |
| | | } |
| | | }; |
| | | |
| | | // æ°å¢å®¡æ¹äºº |
| | | const handleAdd = () => { |
| | | const newOrder = approverList.value.length + 1; |
| | | approverList.value.push({ |
| | | id: null, |
| | | approveType: currentApproveType.value, |
| | | approverId: null, |
| | | approverName: '', |
| | | sortOrder: newOrder, |
| | | }); |
| | | }; |
| | | |
| | | // å é¤å®¡æ¹äºº |
| | | const handleDelete = (index) => { |
| | | ElMessageBox.confirm('ç¡®å®å é¤è¯¥å®¡æ¹äººåï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }) |
| | | .then(() => { |
| | | approverList.value.splice(index, 1); |
| | | approverList.value.forEach((item, idx) => { |
| | | item.sortOrder = idx + 1; |
| | | }); |
| | | ElMessage.success('å 餿å'); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // åç§» |
| | | const moveLeft = (index) => { |
| | | if (index === 0) return; |
| | | const temp = approverList.value[index]; |
| | | approverList.value[index] = approverList.value[index - 1]; |
| | | approverList.value[index - 1] = temp; |
| | | approverList.value[index].sortOrder = index + 1; |
| | | approverList.value[index - 1].sortOrder = index; |
| | | }; |
| | | |
| | | // åç§» |
| | | const moveRight = (index) => { |
| | | if (index === approverList.value.length - 1) return; |
| | | const temp = approverList.value[index]; |
| | | approverList.value[index] = approverList.value[index + 1]; |
| | | approverList.value[index + 1] = temp; |
| | | approverList.value[index].sortOrder = index + 1; |
| | | approverList.value[index + 1].sortOrder = index + 2; |
| | | }; |
| | | |
| | | // ä¿åé
ç½® |
| | | const handleSave = async () => { |
| | | if (approverList.value.length === 0) { |
| | | ElMessage.warning('请è³å°é
ç½®ä¸ä¸ªå®¡æ¹äºº'); |
| | | return; |
| | | } |
| | | |
| | | const hasEmptyApprover = approverList.value.some((item) => !item.approverId); |
| | | if (hasEmptyApprover) { |
| | | ElMessage.warning('è¯·éæ©ææå®¡æ¹äºº'); |
| | | return; |
| | | } |
| | | |
| | | const approverIds = approverList.value.map((item) => item.approverId); |
| | | const uniqueIds = [...new Set(approverIds)]; |
| | | if (uniqueIds.length !== approverIds.length) { |
| | | ElMessage.warning('审æ¹äººä¸è½éå¤'); |
| | | return; |
| | | } |
| | | |
| | | saveLoading.value = true; |
| | | try { |
| | | const payload = approverList.value.map((item, index) => ({ |
| | | approveType: currentApproveType.value, |
| | | nodeOrder: index + 1, |
| | | approverId: item.approverId, |
| | | approverName: item.approverName, |
| | | })); |
| | | await addApproveProcessConfigNode(payload); |
| | | ElMessage.success('ä¿åæå'); |
| | | await loadData(); |
| | | } catch (error) { |
| | | ElMessage.error('ä¿å失败'); |
| | | } finally { |
| | | saveLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // éç½® |
| | | const handleReset = () => { |
| | | if (originalList.value.length === 0) { |
| | | approverList.value = []; |
| | | return; |
| | | } |
| | | ElMessageBox.confirm('ç¡®å®è¦éç½®å½åé
ç½®åï¼æªä¿åçæ´æ¹å°ä¸¢å¤±ã', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }) |
| | | .then(() => { |
| | | approverList.value = JSON.parse(JSON.stringify(originalList.value)); |
| | | ElMessage.success('å·²éç½®'); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | await loadUserList(); |
| | | await loadData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .header-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | margin-bottom: 6px; |
| | | } |
| | | |
| | | .title-icon { |
| | | font-size: 24px; |
| | | color: var(--el-color-primary, #409EFF); |
| | | } |
| | | |
| | | .header-desc { |
| | | font-size: 13px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | margin-left: 34px; |
| | | } |
| | | |
| | | /* 审æ¹ç±»å忢 - ç´§åæ ç¾å¼ */ |
| | | .type-tabs { |
| | | display: flex; |
| | | gap: 4px; |
| | | margin-bottom: 16px; |
| | | padding: 4px; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | border-radius: 8px; |
| | | overflow-x: auto; |
| | | } |
| | | |
| | | .type-tab { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | padding: 8px 14px; |
| | | border-radius: 6px; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | white-space: nowrap; |
| | | font-size: 13px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | } |
| | | |
| | | .type-tab:hover { |
| | | background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.1)); |
| | | color: var(--el-color-primary, #409EFF); |
| | | } |
| | | |
| | | .type-tab.active { |
| | | background: var(--el-bg-color, #fff); |
| | | color: var(--el-color-primary, #409EFF); |
| | | font-weight: 600; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
| | | } |
| | | |
| | | .tab-name { |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .tab-count { |
| | | min-width: 16px; |
| | | height: 16px; |
| | | padding: 0 5px; |
| | | background: var(--el-color-success, #67C23A); |
| | | color: #fff; |
| | | border-radius: 8px; |
| | | font-size: 11px; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .config-card { |
| | | margin-bottom: 16px; |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid var(--el-border-color-light, #ebeef5); |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .header-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 14px; |
| | | } |
| | | |
| | | .type-icon { |
| | | width: 44px; |
| | | height: 44px; |
| | | border-radius: 10px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .header-info { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .type-name { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | } |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .flow-wrapper { |
| | | overflow-x: auto; |
| | | padding: 8px 4px; |
| | | } |
| | | |
| | | .flow-container { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 0; |
| | | min-width: min-content; |
| | | } |
| | | |
| | | .flow-item { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .node-card { |
| | | width: 200px; |
| | | background: var(--el-bg-color, #fff); |
| | | border: 2px solid var(--el-border-color, #e4e7ed); |
| | | border-radius: 12px; |
| | | padding: 16px; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .node-card:hover { |
| | | border-color: var(--el-color-primary, #409EFF); |
| | | box-shadow: 0 4px 16px rgba(64, 158, 255, 0.15); |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .node-card.empty { |
| | | border-style: dashed; |
| | | border-color: var(--el-border-color, #c0c4cc); |
| | | background: var(--el-fill-color-light, #fafbfc); |
| | | } |
| | | |
| | | .node-card.empty:hover { |
| | | border-color: var(--el-color-primary, #409EFF); |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | } |
| | | |
| | | .node-badge { |
| | | position: absolute; |
| | | top: -10px; |
| | | left: 16px; |
| | | width: 24px; |
| | | height: 24px; |
| | | background: var(--el-color-primary, #409EFF); |
| | | color: #fff; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 13px; |
| | | font-weight: 700; |
| | | box-shadow: 0 2px 8px rgba(64, 158, 255, 0.4); |
| | | } |
| | | |
| | | .node-avatar-section { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .node-avatar { |
| | | width: 56px; |
| | | height: 56px; |
| | | border-radius: 50%; |
| | | background: var(--el-fill-color, #f0f2f5); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 8px; |
| | | color: var(--el-text-color-placeholder, #c0c4cc); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .node-avatar.has-user { |
| | | color: #fff; |
| | | font-size: 22px; |
| | | font-weight: 600; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .node-level { |
| | | font-size: 12px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .node-select-section { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .node-select-section :deep(.el-select) { |
| | | width: 100%; |
| | | } |
| | | |
| | | .node-actions { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | padding-top: 12px; |
| | | border-top: 1px solid var(--el-border-color-light, #ebeef5); |
| | | } |
| | | |
| | | .action-btn { |
| | | transition: all 0.2s; |
| | | } |
| | | |
| | | .action-btn:hover:not(:disabled) { |
| | | transform: scale(1.1); |
| | | } |
| | | |
| | | .arrow-connector { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 50px; |
| | | position: relative; |
| | | } |
| | | |
| | | .arrow-line { |
| | | flex: 1; |
| | | height: 2px; |
| | | background: var(--el-border-color, #c0c4cc); |
| | | } |
| | | |
| | | .arrow-icon { |
| | | color: var(--el-text-color-placeholder, #c0c4cc); |
| | | font-size: 14px; |
| | | margin-left: -2px; |
| | | } |
| | | |
| | | /* æ°å¢èç¹æ ·å¼ */ |
| | | .add-node-item { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .add-node-card { |
| | | width: 140px; |
| | | height: 200px; |
| | | border: 2px dashed var(--el-border-color, #c0c4cc); |
| | | border-radius: 12px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 12px; |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | background: var(--el-fill-color-light, #fafbfc); |
| | | flex-shrink: 0; |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .add-node-card:hover { |
| | | border-color: var(--el-color-primary, #409EFF); |
| | | background: var(--el-color-primary-light-9, #f0f7ff); |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 16px rgba(64, 158, 255, 0.15); |
| | | } |
| | | |
| | | .add-icon-wrapper { |
| | | width: 48px; |
| | | height: 48px; |
| | | border-radius: 50%; |
| | | background: var(--el-color-primary, #409EFF); |
| | | color: #fff; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .add-node-card:hover .add-icon-wrapper { |
| | | transform: scale(1.1); |
| | | box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4); |
| | | } |
| | | |
| | | .add-text { |
| | | font-size: 14px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .add-node-card:hover .add-text { |
| | | color: var(--el-color-primary, #409EFF); |
| | | } |
| | | |
| | | /* ç©ºç¶æ */ |
| | | .empty-state { |
| | | padding: 50px 20px; |
| | | } |
| | | |
| | | .empty-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .empty-icon-wrapper { |
| | | width: 80px; |
| | | height: 80px; |
| | | border-radius: 50%; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .empty-text { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-regular, #606266); |
| | | } |
| | | |
| | | .empty-subtext { |
| | | font-size: 13px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | } |
| | | |
| | | .empty-add-btn { |
| | | margin-top: 8px; |
| | | padding: 12px 28px; |
| | | } |
| | | |
| | | /* åºé¨æç¤º */ |
| | | .bottom-tips { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | padding: 12px 20px; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | border-radius: 8px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .bottom-tips .el-icon { |
| | | color: var(--el-color-primary, #409EFF); |
| | | font-size: 16px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .type-tabs { |
| | | padding: 3px; |
| | | } |
| | | |
| | | .type-tab { |
| | | padding: 6px 10px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .tab-name { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .flow-container { |
| | | flex-wrap: wrap; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .arrow-connector { |
| | | width: 100%; |
| | | height: 30px; |
| | | flex-direction: row; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .arrow-line { |
| | | width: 2px; |
| | | height: 30px; |
| | | } |
| | | |
| | | .arrow-icon { |
| | | right: auto; |
| | | top: auto; |
| | | bottom: -5px; |
| | | transform: rotate(90deg); |
| | | } |
| | | |
| | | .add-node-item { |
| | | width: 100%; |
| | | justify-content: center; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .add-node-item .arrow-connector { |
| | | display: none; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- 审æ¹äººéæ©ï¼å¨æèç¹ï¼ --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·äººï¼" prop="approveUser"> |
| | | <el-select |
| | | v-model="form.approveUser" |
| | | placeholder="éæ©äººå" |
| | | disabled |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·æ¥æï¼" prop="approveTime"> |
| | | <el-date-picker |
| | | v-model="form.approveTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <!-- æ¥ä»·å®¡æ¹ï¼å±ç¤ºæ¥ä»·è¯¦æ
ï¼å¤ç¨é宿¥ä»·"æ¥ç详æ
å¯¹è¯æ¡"å
å®¹ç»æï¼ --> |
| | |
| | | updateApproveNode |
| | | } from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | 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 formRef = ref(null); |
| | | const userStore = useUserStore() |
| | | const productOptions = ref([]); |
| | | const userList = ref([]) |
| | | const quotationLoading = ref(false) |
| | | const currentQuotation = ref({}) |
| | | const purchaseLoading = ref(false) |
| | |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | | approveId: "", |
| | | approveUser: "", |
| | | approveDeptId: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | |
| | | dialogFormVisible.value = true; |
| | | currentQuotation.value = {} |
| | | currentPurchase.value = {} |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value = {...row} |
| | | // ç«å³æ¸
é¤è¡¨åéªè¯ç¶æï¼å ä¸ºåæ®µæ¯disabledçï¼ä¸éè¦éªè¯ï¼ |
| | | nextTick(() => { |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- 审æ¹äººéæ©ï¼å¨æèç¹ï¼ --> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <template #label> |
| | | <span>审æ¹äººéæ©ï¼</span> |
| | | <el-button type="primary" @click="addApproverNode" style="margin-left: 8px;">æ°å¢èç¹</el-button> |
| | | </template> |
| | | <div style="display: flex; align-items: flex-end; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(node, index) in approverNodes" |
| | | :key="node.id" |
| | | style="margin-right: 30px; text-align: center; margin-bottom: 10px;" |
| | | > |
| | | <div> |
| | | <span>审æ¹äºº</span> |
| | | â |
| | | </div> |
| | | <el-select |
| | | v-model="node.userId" |
| | | placeholder="éæ©äººå" |
| | | style="width: 120px; margin-bottom: 8px;" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | <div> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | @click="removeApproverNode(index)" |
| | | v-if="approverNodes.length > 1" |
| | | >å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·äººï¼" prop="approveUser"> |
| | | <el-select |
| | | v-model="form.approveUser" |
| | | placeholder="éæ©äººå" |
| | | filterable |
| | | default-first-option |
| | | :reserve-keyword="false" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·æ¥æï¼" prop="approveTime"> |
| | | <el-date-picker |
| | | v-model="form.approveTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <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> |
| | |
| | | import { |
| | | delLedgerFile, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import { getToken } from "@/utils/auth"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | import log from "@/views/monitor/job/log.vue"; |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | const userStore = useUserStore(); |
| | | |
| | | const dialogFormVisible = ref(false); |
| | |
| | | }); |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | | approveId: "", |
| | | approveUser: "", |
| | | approveDeptId: "", |
| | | approveDeptId: "", |
| | | approveDeptName: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | approverList: [], // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid |
| | | startDate: "", // 请åå¼å§æ¶é´ |
| | | endDate: "", // 请åç»ææ¶é´ |
| | | price: null, // æ¥ééé¢ |
| | | location: "" // åºå·®å°ç¹ |
| | | }, |
| | | rules: { |
| | | approveTime: [{ required: false, message: "请è¾å
¥", trigger: "change" },], |
| | | approveId: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | approveUser: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | approveDeptName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | approveReason: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkResult: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | } |
| | | }) |
| | | |
| | | // 审æ¹äººèç¹ç¸å
³ |
| | | const approverNodes = ref([ |
| | | { id: 1, userId: null } |
| | | ]) |
| | | let nextApproverId = 2 |
| | | const userList = ref([]) |
| | | function addApproverNode() { |
| | | approverNodes.value.push({ id: nextApproverId++, userId: null }) |
| | | } |
| | | function removeApproverNode(index) { |
| | | approverNodes.value.splice(index, 1) |
| | | } |
| | | |
| | | // å¤çé¨é¨éæ©åå |
| | | const handleDeptChange = (deptId) => { |
| | | if (deptId) { |
| | |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value = {} |
| | | approverNodes.value = [ |
| | | { id: 1, userId: null } |
| | | ] |
| | | form.value.approveUser = userStore.id; |
| | | form.value.approveTime = getCurrentDate(); |
| | | |
| | | form.value = {} |
| | | |
| | | // è·åå½åç¨æ·ä¿¡æ¯å¹¶è®¾ç½®é¨é¨ID |
| | | form.value.approveDeptId = userStore.currentDeptId |
| | | |
| | | |
| | | // å è½½é¨é¨é项ï¼å¹¶å¨å è½½å®æå设置é¨é¨åç§° |
| | | getProductOptions(); |
| | | if (operationType.value === 'edit') { |
| | | fileList.value = row.commonFileList |
| | | form.value.tempFileIds = fileList.value.map(file => file.id) |
| | | currentApproveStatus.value = row.approveStatus |
| | | currentApproveStatus.value = row.approveStatus |
| | | approveProcessGetInfo({id: row.approveId,approveReason: '1'}).then(res => { |
| | | form.value = {...res.data} |
| | | // 忾审æ¹äºº |
| | | if (res.data && res.data.approveUserIds) { |
| | | const userIds = res.data.approveUserIds.split(',') |
| | | approverNodes.value = userIds.map((userId, idx) => ({ |
| | | id: idx + 1, |
| | | userId: parseInt(userId.trim()) |
| | | })) |
| | | nextApproverId = userIds.length + 1 |
| | | } else { |
| | | approverNodes.value = [{ id: 1, userId: null }] |
| | | nextApproverId = 2 |
| | | } |
| | | form.value = {...res.data} |
| | | fileList.value = res.data.storageBlobVOS |
| | | }) |
| | | } |
| | | } |
| | |
| | | productOptions.value = res.data; |
| | | // 妿已æé¨é¨IDï¼èªå¨è®¾ç½®é¨é¨åç§°ï¼ç¨äºéªè¯ï¼ |
| | | if (form.value.approveDeptId && productOptions.value.length > 0) { |
| | | const matchedDept = productOptions.value.find(dept => |
| | | dept.deptId == form.value.approveDeptId || |
| | | const matchedDept = productOptions.value.find(dept => |
| | | dept.deptId == form.value.approveDeptId || |
| | | String(dept.deptId) === String(form.value.approveDeptId) |
| | | ); |
| | | if (matchedDept) { |
| | |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | | // æäº¤äº§å表å |
| | | const submitForm = () => { |
| | | // æ¶éææèç¹ç审æ¹äººid |
| | | form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',') |
| | | form.value.approveType = props.approveType |
| | | // 审æ¹äººå¿
å¡«æ ¡éª |
| | | const hasEmptyApprover = approverNodes.value.some(node => !node.userId) |
| | | if (hasEmptyApprover) { |
| | | proxy.$modal.msgError("请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼") |
| | | return |
| | | } |
| | | // å½ approveType 为 2 æ¶ï¼æ ¡éªè¯·åæ¶é´ |
| | | if (props.approveType == 2) { |
| | | if (!form.value.startDate) { |
| | |
| | | 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, |
| | |
| | | <el-table-column label="éä»¶åç§°" prop="name" min-width="400" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" width="150" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">ä¸è½½</el-button> |
| | | <el-button link type="primary" size="small" @click="lookFile(scope.row)">é¢è§</el-button> |
| | | <el-button link type="danger" size="small" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | <el-button link type="primary" @click="downLoadFile(scope.row)">ä¸è½½</el-button> |
| | | <el-button link type="primary" @click="lookFile(scope.row)">é¢è§</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æ ç¾é¡µåæ¢ä¸åç审æ¹ç±»å --> |
| | | <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="approval-tabs"> |
| | | <el-tab-pane label="å
¬åºç®¡ç" name="1"></el-tab-pane> |
| | | <el-tab-pane label="请å管ç" name="2"></el-tab-pane> |
| | | <el-tab-pane label="åºå·®ç®¡ç" name="3"></el-tab-pane> |
| | | <el-tab-pane label="æ¥é管ç" name="4"></el-tab-pane> |
| | | <el-tab-pane label="éè´å®¡æ¹" name="5"></el-tab-pane> |
| | | <el-tab-pane label="æ¥ä»·å®¡æ¹" name="6"></el-tab-pane> |
| | | <el-tab-pane label="å货审æ¹" name="7"></el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">æµç¨ç¼å·ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.approveId" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥æµç¨ç¼å·æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">审æ¹ç¶æï¼</span> |
| | | <el-select v-model="searchForm.approveStatus" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="å¾
å®¡æ ¸" :value="0" /> |
| | | <el-option label="å®¡æ ¸ä¸" :value="1" /> |
| | | <el-option label="å®¡æ ¸å®æ" :value="2" /> |
| | | <el-option label="å®¡æ ¸æªéè¿" :value="3" /> |
| | | <el-option label="已鿰æäº¤" :value="4" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div> |
| | | <el-button |
| | | type="primary" |
| | | @click="openForm('add')" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" |
| | | >æ°å¢</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button |
| | | type="danger" |
| | | plain |
| | | @click="handleDelete" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" |
| | | >å é¤</el-button> |
| | | <!-- 审æ¹ç±»å忢 - ç´§åæ ç¾å¼ --> |
| | | <div class="type-tabs"> |
| | | <div |
| | | v-for="type in approveTypes" |
| | | :key="type.value" |
| | | class="type-tab" |
| | | :class="{ active: activeTab === type.value }" |
| | | @click="activeTab = type.value; handleTabChange()" |
| | | > |
| | | <el-icon :size="14" :style="{ color: activeTab === type.value ? type.color : '#909399' }"> |
| | | <component :is="type.icon" /> |
| | | </el-icon> |
| | | <span class="tab-name">{{ type.label }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | |
| | | <!-- æç´¢åæä½åºå --> |
| | | <el-card class="search-card" shadow="never"> |
| | | <div class="search-content"> |
| | | <div class="search-filters"> |
| | | <div class="filter-item"> |
| | | <span class="filter-label">æµç¨ç¼å·</span> |
| | | <el-input |
| | | v-model="searchForm.approveId" |
| | | placeholder="请è¾å
¥æµç¨ç¼å·" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @keyup.enter="handleQuery" |
| | | class="search-input" |
| | | /> |
| | | </div> |
| | | <div class="filter-item"> |
| | | <span class="filter-label">审æ¹ç¶æ</span> |
| | | <el-select |
| | | v-model="searchForm.approveStatus" |
| | | clearable |
| | | @change="handleQuery" |
| | | placeholder="è¯·éæ©ç¶æ" |
| | | class="search-select" |
| | | > |
| | | <el-option label="å¾
å®¡æ ¸" :value="0"> |
| | | <el-tag size="small" type="warning">å¾
å®¡æ ¸</el-tag> |
| | | </el-option> |
| | | <el-option label="å®¡æ ¸ä¸" :value="1"> |
| | | <el-tag size="small" type="primary">å®¡æ ¸ä¸</el-tag> |
| | | </el-option> |
| | | <el-option label="å®¡æ ¸å®æ" :value="2"> |
| | | <el-tag size="small" type="success">å®¡æ ¸å®æ</el-tag> |
| | | </el-option> |
| | | <el-option label="å®¡æ ¸æªéè¿" :value="3"> |
| | | <el-tag size="small" type="danger">å®¡æ ¸æªéè¿</el-tag> |
| | | </el-option> |
| | | <el-option label="已鿰æäº¤" :value="4"> |
| | | <el-tag size="small" type="info">已鿰æäº¤</el-tag> |
| | | </el-option> |
| | | </el-select> |
| | | </div> |
| | | <el-button type="primary" @click="handleQuery" class="search-btn"> |
| | | <el-icon><Search /></el-icon> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetQuery" class="reset-btn"> |
| | | <el-icon><RefreshRight /></el-icon> |
| | | éç½® |
| | | </el-button> |
| | | </div> |
| | | <div class="search-actions"> |
| | | <el-button |
| | | type="primary" |
| | | @click="openForm('add')" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" |
| | | class="action-btn primary" |
| | | > |
| | | <el-icon><Plus /></el-icon> |
| | | æ°å¢ |
| | | </el-button> |
| | | <el-button @click="handleOut" class="action-btn"> |
| | | <el-icon><Download /></el-icon> |
| | | å¯¼åº |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | plain |
| | | @click="handleDelete" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" |
| | | class="action-btn danger" |
| | | > |
| | | <el-icon><Delete /></el-icon> |
| | | å é¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- æ°æ®è¡¨æ ¼ --> |
| | | <el-card class="table-card" shadow="never" v-loading="tableLoading"> |
| | | <template #header> |
| | | <div class="table-header"> |
| | | <div class="table-title"> |
| | | <div class="type-tag" :style="{ backgroundColor: currentTypeInfo.color }"> |
| | | <el-icon color="#fff" :size="16"><component :is="currentTypeInfo.icon" /></el-icon> |
| | | </div> |
| | | <span>{{ currentTypeInfo.label }}å表</span> |
| | | <el-tag type="info" size="small" effect="plain" class="count-tag"> |
| | | å
± {{ page.total }} æ¡ |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumnCopy" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | rowKey="id" |
| | | :column="tableColumnCopy" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | class="custom-table" |
| | | ></PIMTable> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- å¼¹çªç»ä»¶ --> |
| | | <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia> |
| | | <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia> |
| | | <FileList ref="fileListRef" /> |
| | |
| | | |
| | | <script setup> |
| | | import FileList from "./fileList.vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { Search, Plus, Delete, Download, RefreshRight, DocumentChecked } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import { useRoute } from 'vue-router'; |
| | |
| | | // å½åéä¸çæ ç¾é¡µï¼é»è®¤ä¸ºå
¬åºç®¡ç |
| | | const activeTab = ref('1'); |
| | | |
| | | // åç±»åæ°éç»è®¡ |
| | | const typeCounts = ref({}); |
| | | |
| | | // 审æ¹ç±»åé
ç½® |
| | | const approveTypes = [ |
| | | { value: '1', label: 'å
¬åºç®¡ç', icon: 'Suitcase', color: '#409EFF' }, |
| | | { value: '2', label: '请å管ç', icon: 'Calendar', color: '#67C23A' }, |
| | | { value: '3', label: 'åºå·®ç®¡ç', icon: 'Location', color: '#E6A23C' }, |
| | | { value: '4', label: 'æ¥é管ç', icon: 'Money', color: '#F56C6C' }, |
| | | { value: '5', label: 'éè´å®¡æ¹', icon: 'ShoppingCart', color: '#909399' }, |
| | | { value: '6', label: 'æ¥ä»·å®¡æ¹', icon: 'DocumentChecked', color: '#9B59B6' }, |
| | | { value: '7', label: 'å货审æ¹', icon: 'Van', color: '#1ABC9C' }, |
| | | ]; |
| | | |
| | | // å½å审æ¹ç±»åä¿¡æ¯ |
| | | const currentTypeInfo = computed(() => { |
| | | return approveTypes.find(t => t.value === activeTab.value) || approveTypes[0]; |
| | | }); |
| | | |
| | | // è·åç±»åæ°é |
| | | const getTypeCount = (value) => { |
| | | return typeCounts.value[value] || 0; |
| | | }; |
| | | |
| | | // å½å审æ¹ç±»åï¼æ ¹æ®éä¸çæ ç¾é¡µè®¡ç® |
| | | const currentApproveType = computed(() => { |
| | | return Number(activeTab.value); |
| | | }); |
| | | |
| | | // æ ç¾é¡µåæ¢å¤ç |
| | | const handleTabChange = (tabName) => { |
| | | const handleTabChange = () => { |
| | | // 忢æ ç¾é¡µæ¶éç½®æç´¢æ¡ä»¶åå页ï¼å¹¶éæ°å è½½æ°æ® |
| | | searchForm.value.approveId = ''; |
| | | searchForm.value.approveStatus = ''; |
| | |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | approveId: "", |
| | | approveStatus: "", |
| | | approveId: "", |
| | | approveStatus: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | // éç½®æç´¢ |
| | | const resetQuery = () => { |
| | | searchForm.value.approveId = ''; |
| | | searchForm.value.approveStatus = ''; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // å¨æè¡¨æ ¼åé
ç½®ï¼æ ¹æ®å®¡æ¹ç±»åçæå |
| | | const tableColumnCopy = computed(() => { |
| | |
| | | }, |
| | | ]; |
| | | |
| | | // æ¥ä»·å®¡æ¹ï¼ç±»å 6ï¼ä¸å±ç¤ºâéä»¶âæä½ |
| | | // æ¥ä»·å®¡æ¹ï¼ç±»å 6ï¼ä¸å±ç¤º"éä»¶"æä½ |
| | | if (!isQuotationType) { |
| | | actionOperations.push({ |
| | | name: "éä»¶", |
| | |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | // æ´æ°å½åç±»åæ°é |
| | | typeCounts.value[activeTab.value] = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .approval-tabs { |
| | | margin-bottom: 10px; |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .header-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .title-icon { |
| | | font-size: 28px; |
| | | color: var(--el-color-primary, #409EFF); |
| | | } |
| | | |
| | | .header-text { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .main-title { |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | } |
| | | |
| | | .sub-title { |
| | | font-size: 13px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | } |
| | | |
| | | /* 审æ¹ç±»å忢 - ç´§åæ ç¾å¼ */ |
| | | .type-tabs { |
| | | display: flex; |
| | | gap: 4px; |
| | | margin-bottom: 16px; |
| | | padding: 4px; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | border-radius: 8px; |
| | | overflow-x: auto; |
| | | } |
| | | |
| | | .type-tab { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | padding: 8px 14px; |
| | | border-radius: 6px; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | white-space: nowrap; |
| | | font-size: 13px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | } |
| | | |
| | | .type-tab:hover { |
| | | background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.1)); |
| | | color: var(--el-color-primary, #409EFF); |
| | | } |
| | | |
| | | .type-tab.active { |
| | | background: var(--el-bg-color, #fff); |
| | | color: var(--el-color-primary, #409EFF); |
| | | font-weight: 600; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
| | | } |
| | | |
| | | .tab-name { |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .tab-count { |
| | | min-width: 16px; |
| | | height: 16px; |
| | | padding: 0 5px; |
| | | background: var(--el-color-success, #67C23A); |
| | | color: #fff; |
| | | border-radius: 8px; |
| | | font-size: 11px; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | /* æç´¢å¡ç */ |
| | | .search-card { |
| | | margin-bottom: 16px; |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .search-content { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .search-filters { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .filter-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .filter-label { |
| | | font-size: 14px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | font-weight: 500; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .search-input, |
| | | .search-select { |
| | | width: 200px; |
| | | } |
| | | |
| | | .search-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .reset-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .search-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .action-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .action-btn.primary { |
| | | background: var(--el-color-primary, #409EFF); |
| | | border: none; |
| | | } |
| | | |
| | | .action-btn.danger { |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .action-btn.danger:hover { |
| | | background: #f56c6c; |
| | | color: #fff; |
| | | } |
| | | |
| | | /* è¡¨æ ¼å¡ç */ |
| | | .table-card { |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid var(--el-border-color-light, #ebeef5); |
| | | } |
| | | |
| | | .table-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .table-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | } |
| | | |
| | | .type-tag { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 8px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .count-tag { |
| | | margin-left: 8px; |
| | | } |
| | | |
| | | .custom-table { |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | /* ååºå¼ */ |
| | | @media (max-width: 1200px) { |
| | | .search-content { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .search-filters { |
| | | justify-content: flex-start; |
| | | } |
| | | |
| | | .search-actions { |
| | | justify-content: flex-end; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .type-tabs { |
| | | padding: 3px; |
| | | } |
| | | |
| | | .type-tab { |
| | | padding: 6px 10px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .tab-name { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .search-filters { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .filter-item { |
| | | width: 100%; |
| | | } |
| | | |
| | | .search-input, |
| | | .search-select { |
| | | width: 100%; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | |
| | | <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 label="æè®¿äºº" prop="visitingPeople" width="120" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="viewDetail(scope.row)">æ¥ç</el-button> |
| | | <el-button link type="primary" size="small" @click="viewDetail(scope.row)" style="color: #67C23A">æ¥ç</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | </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 |
| | |
| | | } |
| | | }, |
| | | { |
| | | name: "æ¥ç", |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | viewKnowledge(row); |
| | |
| | | |
| | | <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"; |
| | | |
| | |
| | | v-if="scope.row.editing" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleSaveNoticeType(scope.row)" |
| | | > |
| | | ä¿å |
| | |
| | | v-if="scope.row.editing" |
| | | link |
| | | type="info" |
| | | size="small" |
| | | @click="handleCancelEdit(scope.row)" |
| | | > |
| | | åæ¶ |
| | |
| | | v-if="!scope.row.editing" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleEditNoticeType(scope.row)" |
| | | > |
| | | ç¼è¾ |
| | |
| | | v-if="!scope.row.editing" |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | @click="handleDeleteNoticeType(scope.row)" |
| | | > |
| | | å é¤ |
| | |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | text-align: center; |
| | | } |
| | | |
| | | .notice-type-container { |
| | |
| | | </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-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="cancel">å æ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡® å®</el-button> |
| | | <el-button @click="cancel">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | text-align: center; |
| | | } |
| | | </style> |
| | |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="minutesDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="submitMinutes">ä¿ å</el-button> |
| | | <el-button @click="minutesDialogVisible = false">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .content-section h4 { |
| | |
| | | </div> |
| | | <div class="plan-actions"> |
| | | <el-button size="small" @click="handleEditPlan(plan)">ç¼è¾</el-button> |
| | | <el-button size="small" @click="handleViewDetail(plan)">详æ
</el-button> |
| | | <el-button size="small" @click="handleViewDetail(plan)" style="color: #67C23A">详æ
</el-button> |
| | | <el-dropdown @command="(command) => handleMoreAction(plan, command)"> |
| | | <el-button size="small"> |
| | | æ´å¤<el-icon class="el-icon--right"><ArrowDown /></el-icon> |
| | |
| | | <el-table-column label="æä½" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-button type="text" @click="updateStatus(row)">æ´æ°ç¶æ</el-button> |
| | | <el-button type="text" @click="viewDetails(row)">详æ
</el-button> |
| | | <el-button type="text" @click="viewDetails(row)" style="color: #67C23A">详æ
</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <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" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <el-input v-model="searchForm.purchaseContractNumber" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | | clearable |
| | | :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" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" |
| | | :row-key="(row) => row.id" |
| | | show-summary |
| | | :summary-method="summarizeMainTable" |
| | | @expand-change="expandChange" |
| | | height="calc(100vh - 18.5em)" |
| | | :row-class-name="tableRowClassName" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" |
| | | :row-key="(row) => row.id" |
| | | show-summary |
| | | :summary-method="summarizeMainTable" |
| | | @expand-change="expandChange" |
| | | height="calc(100vh - 18.5em)" |
| | | :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" |
| | | border |
| | | show-summary |
| | | :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="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="ä¸å«ç¨æ»ä»·(å
)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table :data="props.row.children" |
| | | border |
| | | show-summary |
| | | :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="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="ä¸å«ç¨æ»ä»·(å
)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" /> |
| | | </el-table> |
| | | </template> |
| | | </el-table-column> |
| | | <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="éå®ååå·" |
| | | prop="salesContractNo" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¾åºååç§°" |
| | | width="240" |
| | | prop="supplierName" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="订åç¶æ" width="100" align="center"> |
| | | <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="éå®ååå·" |
| | | prop="salesContractNo" |
| | | width="200" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="ä¾åºååç§°" |
| | | width="240" |
| | | prop="supplierName" |
| | | 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="项ç®åç§°" |
| | | prop="projectName" |
| | | width="420" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="审æ¹ç¶æ" |
| | | prop="approvalStatus" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | > |
| | | <el-table-column label="项ç®åç§°" |
| | | prop="projectName" |
| | | width="420" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="审æ¹ç¶æ" |
| | | prop="approvalStatus" |
| | | width="200" |
| | | 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="仿¬¾æ¹å¼" |
| | | width="100" |
| | | prop="paymentMethod" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ååéé¢(å
)" |
| | | prop="contractAmount" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="å½å
¥äºº" |
| | | prop="recorderName" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å½å
¥æ¥æ" |
| | | prop="entryDate" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | fixed="right" |
| | | label="æä½" |
| | | min-width="150" |
| | | align="center" |
| | | > |
| | | <el-table-column label="仿¬¾æ¹å¼" |
| | | width="100" |
| | | prop="paymentMethod" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="ååéé¢(å
)" |
| | | prop="contractAmount" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="å½å
¥äºº" |
| | | prop="recorderName" |
| | | width="100" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="å½å
¥æ¥æ" |
| | | prop="entryDate" |
| | | width="100" |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="æä½" |
| | | min-width="150" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="approvePurchase(scope.row)" |
| | | :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 |
| | | > |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | @click="approvePurchase(scope.row)" |
| | | :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> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @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 { Search } from "@element-plus/icons-vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { |
| | | getSalesLedgerWithProducts, |
| | | addOrUpdateSalesLedgerProduct, |
| | | delProduct, |
| | | delLedgerFile, |
| | | getProductInfoByContractNo, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import { |
| | | addOrEditPurchase, |
| | | delPurchase, |
| | | getSalesNo, |
| | | purchaseListPage, |
| | | productList, |
| | | getPurchaseById, |
| | | getOptions, |
| | | createPurchaseNo, updateApprovalStatus, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | import QRCode from "qrcode"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import pagination from "@/components/PIMTable/Pagination.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"; |
| | | import { |
| | | getSalesLedgerWithProducts, |
| | | addOrUpdateSalesLedgerProduct, |
| | | delProduct, |
| | | delLedgerFile, |
| | | getProductInfoByContractNo, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import { |
| | | addOrEditPurchase, |
| | | delPurchase, |
| | | getSalesNo, |
| | | purchaseListPage, |
| | | productList, |
| | | getPurchaseById, |
| | | getOptions, |
| | | createPurchaseNo, |
| | | updateApprovalStatus, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | import QRCode from "qrcode"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const productData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const productSelectedRows = ref([]); |
| | | const modelOptions = ref([]); |
| | | const userList = ref([]); |
| | | const productOptions = ref([]); |
| | | const salesContractList = ref([]); |
| | | const supplierList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const fileList = ref([]); |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import dayjs from "dayjs"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const productData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const productSelectedRows = ref([]); |
| | | const modelOptions = ref([]); |
| | | const userList = ref([]); |
| | | const productOptions = ref([]); |
| | | const salesContractList = ref([]); |
| | | const supplierList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const fileList = ref([]); |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import dayjs from "dayjs"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | const userStore = useUserStore(); |
| | | |
| | | const userStore = useUserStore(); |
| | | // äºç»´ç ç¸å
³åé |
| | | const qrCodeDialogVisible = ref(false); |
| | | const qrCodeUrl = ref(""); |
| | | |
| | | // äºç»´ç ç¸å
³åé |
| | | const qrCodeDialogVisible = ref(false); |
| | | const qrCodeUrl = ref(""); |
| | | // 订å审æ¹ç¶ææ¾ç¤ºææ¬ |
| | | const approvalStatusText = { |
| | | 0: "å¾
审æ¹", |
| | | 1: "审æ¹éè¿", |
| | | 2: "审æ¹å¤±è´¥", |
| | | }; |
| | | |
| | | // 订å审æ¹ç¶ææ¾ç¤ºææ¬ |
| | | const approvalStatusText = { |
| | | 0: 'å¾
审æ¹', |
| | | 1: '审æ¹éè¿', |
| | | 2: '审æ¹å¤±è´¥' |
| | | }; |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", // ä¾åºååç§° |
| | | purchaseContractNumber: "", // éè´ååç¼å· |
| | | salesContractNo: "", // éå®ååç¼å· |
| | | projectName: "", // 项ç®åç§° |
| | | entryDate: null, // å½å
¥æ¥æ |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | }, |
| | | form: { |
| | | purchaseContractNumber: "", |
| | | salesLedgerId: "", |
| | | projectName: "", |
| | | recorderId: "", |
| | | entryDate: "", |
| | | productData: [], |
| | | supplierName: "", |
| | | supplierId: "", |
| | | paymentMethod: "", |
| | | executionDate: "", |
| | | approvalStatus: "0", |
| | | }, |
| | | rules: { |
| | | purchaseContractNumber: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | projectName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | supplierId: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | entryDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | executionDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const { form: searchForm } = useFormData(data.searchForm); |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", // ä¾åºååç§° |
| | | purchaseContractNumber: "", // éè´ååç¼å· |
| | | salesContractNo: "", // éå®ååç¼å· |
| | | projectName: "", // 项ç®åç§° |
| | | entryDate: null, // å½å
¥æ¥æ |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | }, |
| | | form: { |
| | | purchaseContractNumber: "", |
| | | salesLedgerId: "", |
| | | projectName: "", |
| | | recorderId: "", |
| | | entryDate: "", |
| | | productData: [], |
| | | supplierName: "", |
| | | supplierId: "", |
| | | paymentMethod: "", |
| | | executionDate: "", |
| | | approvalStatus: "0", |
| | | }, |
| | | rules: { |
| | | purchaseContractNumber: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | projectName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | supplierId: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | entryDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | executionDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const { form: searchForm } = useFormData(data.searchForm); |
| | | // 产å表åå¼¹æ¡æ°æ® |
| | | const productFormVisible = ref(false); |
| | | const productOperationType = ref(""); |
| | | const productOperationIndex = ref(""); |
| | | const currentId = ref(""); |
| | | const productFormData = reactive({ |
| | | productForm: { |
| | | productId: "", |
| | | productCategory: "", |
| | | productModelId: "", |
| | | specificationModel: "", |
| | | unit: "", |
| | | quantity: "", |
| | | taxInclusiveUnitPrice: "", |
| | | taxRate: "", |
| | | taxInclusiveTotalPrice: "", |
| | | taxExclusiveTotalPrice: "", |
| | | invoiceType: "", |
| | | warnNum: "", |
| | | }, |
| | | productRules: { |
| | | productId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | unit: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxInclusiveUnitPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | taxRate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | warnNum: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | taxInclusiveTotalPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | taxExclusiveTotalPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | invoiceType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { productForm, productRules } = toRefs(productFormData); |
| | | // const upload = reactive({ |
| | | // // ä¸ä¼ çå°å |
| | | // url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | // headers: { Authorization: "Bearer " + getToken() }, |
| | | // }); |
| | | |
| | | // 产å表åå¼¹æ¡æ°æ® |
| | | const productFormVisible = ref(false); |
| | | const productOperationType = ref(""); |
| | | const productOperationIndex = ref(""); |
| | | const currentId = ref(""); |
| | | const productFormData = reactive({ |
| | | productForm: { |
| | | productId: "", |
| | | productCategory: "", |
| | | productModelId: "", |
| | | specificationModel: "", |
| | | unit: "", |
| | | quantity: "", |
| | | taxInclusiveUnitPrice: "", |
| | | taxRate: "", |
| | | taxInclusiveTotalPrice: "", |
| | | taxExclusiveTotalPrice: "", |
| | | invoiceType: "", |
| | | warnNum: "", |
| | | }, |
| | | productRules: { |
| | | productId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | unit: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxInclusiveUnitPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | taxRate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | warnNum: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | taxInclusiveTotalPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | taxExclusiveTotalPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | invoiceType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { productForm, productRules } = toRefs(productFormData); |
| | | const upload = reactive({ |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | const changeDaterange = value => { |
| | | if (value) { |
| | | searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } else { |
| | | searchForm.entryDateStart = undefined; |
| | | searchForm.entryDateEnd = undefined; |
| | | } |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const changeDaterange = (value) => { |
| | | if (value) { |
| | | searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } else { |
| | | searchForm.entryDateStart = undefined; |
| | | searchForm.entryDateEnd = undefined; |
| | | } |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeChildrenTable = (param) => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeChildrenTable = param => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | "ticketsNum", |
| | | "ticketsAmount", |
| | | "futureTickets", |
| | | "futureTicketsAmount", |
| | | ], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | }; |
| | | const paginationChange = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const { entryDate, ...rest } = searchForm; |
| | | purchaseListPage({ ...rest, ...page }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | // tableData.value = res.data.records; |
| | | // å¤çæ°æ®ï¼æ·»å 失æç¶ææ è®° |
| | | tableData.value = res.data.records.map(record => ({ |
| | | ...record, |
| | | isInvalid: record.isWhite === 1, |
| | | })); |
| | | tableData.value.map(item => { |
| | | item.children = []; |
| | | }); |
| | | total.value = res.data.total; |
| | | expandedRowKeys.value = []; |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const productSelected = selectedRows => { |
| | | productSelectedRows.value = selectedRows; |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | // å±å¼è¡ |
| | | const expandChange = (row, expandedRows) => { |
| | | 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); |
| | | if (index > -1) { |
| | | tableData.value[index].children = res.data; |
| | | } |
| | | expandedRowKeys.value.push(row.id); |
| | | }); |
| | | } catch (error) { |
| | | console.log(error); |
| | | } |
| | | } else { |
| | | expandedRowKeys.value = []; |
| | | } |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable(param, ["contractAmount"]); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeProTable = param => { |
| | | return proxy.summarizeTable(param, [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | "ticketsNum", |
| | | "ticketsAmount", |
| | | "futureTickets", |
| | | "futureTicketsAmount", |
| | | ], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | }; |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const { entryDate, ...rest } = searchForm; |
| | | purchaseListPage({ ...rest, ...page }) |
| | | .then((res) => { |
| | | tableLoading.value = false; |
| | | // tableData.value = res.data.records; |
| | | // å¤çæ°æ®ï¼æ·»å 失æç¶ææ è®° |
| | | tableData.value = res.data.records.map(record => ({ |
| | | ...record, |
| | | isInvalid: record.isWhite === 1 |
| | | })); |
| | | tableData.value.map((item) => { |
| | | item.children = []; |
| | | ]); |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | productData.value = []; |
| | | fileList.value = []; |
| | | if (operationType.value == "add") { |
| | | createPurchaseNo().then(res => { |
| | | form.value.purchaseContractNumber = res.data; |
| | | }); |
| | | total.value = res.data.total; |
| | | expandedRowKeys.value = []; |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | } |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const productSelected = (selectedRows) => { |
| | | productSelectedRows.value = selectedRows; |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | // å±å¼è¡ |
| | | const expandChange = (row, expandedRows) => { |
| | | 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); |
| | | if (index > -1) { |
| | | tableData.value[index].children = res.data; |
| | | getSalesNo().then(res => { |
| | | salesContractList.value = res; |
| | | }); |
| | | getOptions().then(res => { |
| | | // ä¾åºåè¿æ»¤åº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 => { |
| | | form.value = { ...res }; |
| | | productData.value = form.value.productData; |
| | | if (form.value.salesLedgerFiles) { |
| | | fileList.value = form.value.salesLedgerFiles; |
| | | } else { |
| | | fileList.value = []; |
| | | } |
| | | expandedRowKeys.value.push(row.id); |
| | | }); |
| | | } catch (error) { |
| | | console.log(error); |
| | | } |
| | | } else { |
| | | expandedRowKeys.value = []; |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | function handleBeforeUpload(file) { |
| | | // æ ¡æ£æä»¶å¤§å° |
| | | if (file.size > 1024 * 1024 * 10) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿10MB!"); |
| | | return false; |
| | | } |
| | | proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); |
| | | return true; |
| | | } |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = (param) => { |
| | | return proxy.summarizeTable(param, ["contractAmount"]); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeProTable = (param) => { |
| | | return proxy.summarizeTable(param, [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | productData.value = []; |
| | | fileList.value = []; |
| | | if (operationType.value == "add") { |
| | | createPurchaseNo().then((res) => { |
| | | form.value.purchaseContractNumber = res.data; |
| | | }); |
| | | // ä¸ä¼ 失败 |
| | | function handleUploadError(err) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | userListNoPage().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | getSalesNo().then((res) => { |
| | | salesContractList.value = res; |
| | | }); |
| | | getOptions().then((res) => { |
| | | // ä¾åºåè¿æ»¤åº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) => { |
| | | form.value = { ...res }; |
| | | productData.value = form.value.productData; |
| | | if (form.value.salesLedgerFiles) { |
| | | fileList.value = form.value.salesLedgerFiles; |
| | | } else { |
| | | fileList.value = []; |
| | | // ä¸ä¼ æååè° |
| | | function handleUploadSuccess(res, file, uploadFiles) { |
| | | proxy.$modal.closeLoading(); |
| | | if (res.code === 200) { |
| | | file.tempId = res.data.tempId; |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | proxy.$refs.fileUpload.handleRemove(file); |
| | | } |
| | | } |
| | | // ç§»é¤æä»¶ |
| | | function handleRemove(file) { |
| | | console.log("handleRemove", file.id); |
| | | if (file.size > 1024 * 1024 * 10) { |
| | | // ä»
å端æ¸
çï¼ä¸è°ç¨å 餿¥å£åæç¤º |
| | | return; |
| | | } |
| | | if (operationType.value === "edit") { |
| | | let ids = []; |
| | | ids.push(file.id); |
| | | delLedgerFile(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }); |
| | | } |
| | | } |
| | | // æäº¤è¡¨å |
| | | const submitForm = n => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | if (productData.value.length > 0) { |
| | | form.value.productData = proxy.HaveJson(productData.value); |
| | | } else { |
| | | proxy.$modal.msgWarning("请添å 产åä¿¡æ¯"); |
| | | return; |
| | | } |
| | | let tempFileIds = []; |
| | | if (fileList.value.length > 0) { |
| | | tempFileIds = fileList.value.map(item => item.tempId); |
| | | } |
| | | form.value.tempFileIds = tempFileIds; |
| | | form.value.type = 2; |
| | | form.value.approvalStatus = n; |
| | | addOrEditPurchase(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | 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) { |
| | | file.tempId = res.data.tempId; |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | proxy.$refs.fileUpload.handleRemove(file); |
| | | } |
| | | } |
| | | // ç§»é¤æä»¶ |
| | | function handleRemove(file) { |
| | | console.log("handleRemove", file.id); |
| | | if (file.size > 1024 * 1024 * 10) { |
| | | // ä»
å端æ¸
çï¼ä¸è°ç¨å 餿¥å£åæç¤º |
| | | return; |
| | | } |
| | | if (operationType.value === "edit") { |
| | | let ids = []; |
| | | ids.push(file.id); |
| | | delLedgerFile(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // æå¼äº§åå¼¹æ¡ |
| | | const openProductForm = (type, row, index) => { |
| | | productOperationType.value = type; |
| | | productOperationIndex.value = index; |
| | | productForm.value = {}; |
| | | proxy.resetForm("productFormRef"); |
| | | if (type === "edit") { |
| | | productForm.value = { ...row }; |
| | | } |
| | | productFormVisible.value = true; |
| | | getProductOptions(); |
| | | }; |
| | | const getProductOptions = () => { |
| | | productTreeList().then(res => { |
| | | productOptions.value = convertIdToValue(res); |
| | | }); |
| | | }; |
| | | const getModels = value => { |
| | | if (value) { |
| | | productForm.value.productCategory = |
| | | findNodeById(productOptions.value, value) || ""; |
| | | modelList({ id: value }).then(res => { |
| | | modelOptions.value = res; |
| | | }); |
| | | } else { |
| | | productForm.value.productCategory = ""; |
| | | modelOptions.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; |
| | | } else { |
| | | productForm.value.specificationModel = null; |
| | | productForm.value.unit = null; |
| | | } |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // æ¾å°èç¹ï¼è¿å该èç¹çlabel |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundNode = findNodeById(nodes[i].children, productId); |
| | | if (foundNode) { |
| | | return foundNode; // å¨åèç¹ä¸æ¾å°ï¼ç´æ¥è¿åï¼å·²ç»æ¯labelåç¬¦ä¸²ï¼ |
| | | } |
| | | } |
| | | } |
| | | return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull |
| | | }; |
| | | function convertIdToValue(data) { |
| | | return data.map(item => { |
| | | const { id, children, ...rest } = item; |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, // å° id æ¹ä¸º value |
| | | }; |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | | } |
| | | // æäº¤è¡¨å |
| | | const submitForm = (n) => { |
| | | proxy.$refs["formRef"].validate((valid) => { |
| | | if (valid) { |
| | | if (productData.value.length > 0) { |
| | | form.value.productData = proxy.HaveJson(productData.value); |
| | | } else { |
| | | proxy.$modal.msgWarning("请添å 产åä¿¡æ¯"); |
| | | // æäº¤äº§å表å |
| | | const submitProduct = () => { |
| | | proxy.$refs["productFormRef"].validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "edit") { |
| | | submitProductEdit(); |
| | | } else { |
| | | if (productOperationType.value === "add") { |
| | | productData.value.push({ ...productForm.value }); |
| | | console.log("productData.value---", productData.value); |
| | | } else { |
| | | productData.value[productOperationIndex.value] = { |
| | | ...productForm.value, |
| | | }; |
| | | } |
| | | closeProductDia(); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | const submitProductEdit = () => { |
| | | productForm.value.salesLedgerId = currentId.value; |
| | | productForm.value.type = 2; |
| | | addOrUpdateSalesLedgerProduct(productForm.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeProductDia(); |
| | | getPurchaseById({ id: currentId.value, type: 2 }).then(res => { |
| | | productData.value = res.productData; |
| | | }); |
| | | }); |
| | | }; |
| | | // å é¤äº§å |
| | | const deleteProduct = () => { |
| | | if (productSelectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | if (operationType.value === "add") { |
| | | productSelectedRows.value.forEach(selectedRow => { |
| | | const index = productData.value.findIndex( |
| | | 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); |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delProduct(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | closeProductDia(); |
| | | getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then( |
| | | res => { |
| | | productData.value = res.productData; |
| | | } |
| | | ); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | } |
| | | }; |
| | | // å
³é产åå¼¹æ¡ |
| | | const closeProductDia = () => { |
| | | proxy.resetForm("productFormRef"); |
| | | 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("å®¡æ¹æå"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .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("å®¡æ¹æå"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶审æ¹"); |
| | | }); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/purchase/ledger/export", {}, "éè´å°è´¦.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | const unauthorizedData = selectedRows.value.filter( |
| | | item => item.recorderName !== userStore.nickName |
| | | ); |
| | | if (unauthorizedData.length > 0) { |
| | | proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); |
| | | return; |
| | | } |
| | | let tempFileIds = []; |
| | | if (fileList.value.length > 0) { |
| | | tempFileIds = fileList.value.map((item) => item.tempId); |
| | | } |
| | | form.value.tempFileIds = tempFileIds; |
| | | form.value.type = 2; |
| | | form.value.approvalStatus = n; |
| | | addOrEditPurchase(form.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // æå¼äº§åå¼¹æ¡ |
| | | const openProductForm = (type, row, index) => { |
| | | productOperationType.value = type; |
| | | productOperationIndex.value = index; |
| | | productForm.value = {}; |
| | | proxy.resetForm("productFormRef"); |
| | | if (type === "edit") { |
| | | productForm.value = { ...row }; |
| | | } |
| | | productFormVisible.value = true; |
| | | getProductOptions(); |
| | | }; |
| | | const getProductOptions = () => { |
| | | productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | | if (value) { |
| | | productForm.value.productCategory = findNodeById(productOptions.value, value) || ""; |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res; |
| | | }); |
| | | } else { |
| | | productForm.value.productCategory = ""; |
| | | modelOptions.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; |
| | | } else { |
| | | productForm.value.specificationModel = null; |
| | | productForm.value.unit = null; |
| | | } |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // æ¾å°èç¹ï¼è¿å该èç¹çlabel |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundNode = findNodeById(nodes[i].children, productId); |
| | | if (foundNode) { |
| | | return foundNode; // å¨åèç¹ä¸æ¾å°ï¼ç´æ¥è¿åï¼å·²ç»æ¯labelåç¬¦ä¸²ï¼ |
| | | } |
| | | } |
| | | } |
| | | return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull |
| | | }; |
| | | function convertIdToValue(data) { |
| | | return data.map((item) => { |
| | | const { id, children, ...rest } = item; |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, // å° id æ¹ä¸º value |
| | | }; |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | | // æäº¤äº§å表å |
| | | const submitProduct = () => { |
| | | proxy.$refs["productFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | if (operationType.value === "edit") { |
| | | submitProductEdit(); |
| | | } else { |
| | | if (productOperationType.value === "add") { |
| | | productData.value.push({ ...productForm.value }); |
| | | console.log("productData.value---", productData.value); |
| | | } else { |
| | | productData.value[productOperationIndex.value] = { |
| | | ...productForm.value, |
| | | }; |
| | | } |
| | | closeProductDia(); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | const submitProductEdit = () => { |
| | | productForm.value.salesLedgerId = currentId.value; |
| | | productForm.value.type = 2; |
| | | addOrUpdateSalesLedgerProduct(productForm.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeProductDia(); |
| | | getPurchaseById({ id: currentId.value, type: 2 }).then((res) => { |
| | | productData.value = res.productData; |
| | | }); |
| | | }); |
| | | }; |
| | | // å é¤äº§å |
| | | const deleteProduct = () => { |
| | | if (productSelectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | if (operationType.value === "add") { |
| | | productSelectedRows.value.forEach((selectedRow) => { |
| | | const index = productData.value.findIndex( |
| | | (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 = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delProduct(ids).then((res) => { |
| | | delPurchase(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | closeProductDia(); |
| | | getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then( |
| | | (res) => { |
| | | productData.value = res.productData; |
| | | } |
| | | ); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | } |
| | | }; |
| | | // å
³é产åå¼¹æ¡ |
| | | const closeProductDia = () => { |
| | | proxy.resetForm("productFormRef"); |
| | | 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('å®¡æ¹æå'); |
| | | getList(); |
| | | }) |
| | | }).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('å®¡æ¹æå'); |
| | | getList(); |
| | | }) |
| | | }).catch(() => { |
| | | proxy.$modal.msg('已忶审æ¹'); |
| | | }); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/purchase/ledger/export", {}, "éè´å°è´¦.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | 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); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delPurchase(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | const mathNum = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请å
éæ©ç¨ç"); |
| | | return; |
| | | } |
| | | if (!productForm.value.taxInclusiveUnitPrice) { |
| | | return; |
| | | } |
| | | if (!productForm.value.quantity) { |
| | | return; |
| | | } |
| | | // å«ç¨æ»ä»·è®¡ç® |
| | | productForm.value.taxInclusiveTotalPrice = |
| | | proxy.calculateTaxIncludeTotalPrice( |
| | | productForm.value.taxInclusiveUnitPrice, |
| | | productForm.value.quantity |
| | | ); |
| | | if (productForm.value.taxRate) { |
| | | // ä¸å«ç¨æ»ä»·è®¡ç® |
| | | productForm.value.taxExclusiveTotalPrice = |
| | | proxy.calculateTaxExclusiveTotalPrice( |
| | | productForm.value.taxInclusiveTotalPrice, |
| | | productForm.value.taxRate |
| | | ); |
| | | } |
| | | }; |
| | | const reverseMathNum = (field) => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请å
éæ©ç¨ç"); |
| | | return; |
| | | } |
| | | const taxRate = Number(productForm.value.taxRate); |
| | | if (!taxRate) return; |
| | | if (field === 'taxInclusiveTotalPrice') { |
| | | // å·²ç¥å«ç¨æ»ä»·åæ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | 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.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); |
| | | // å·²ç¥æ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | 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); |
| | | } |
| | | } |
| | | }; |
| | | // éå®ååéæ©æ¹åæ¹æ³ |
| | | const salesLedgerChange = async (row) => { |
| | | console.log("row", row); |
| | | var index = salesContractList.value.findIndex((item) => item.id == row); |
| | | console.log("index", index); |
| | | if (index > -1) { |
| | | form.value.projectName = salesContractList.value[index].projectName; |
| | | await querygProductInfoByContractNo(); |
| | | } |
| | | }; |
| | | |
| | | const querygProductInfoByContractNo = async () => { |
| | | const { code, data } = await getProductInfoByContractNo({ |
| | | contractNo: form.value.salesLedgerId, |
| | | }); |
| | | if (code == 200) { |
| | | productData.value = data; |
| | | } |
| | | }; |
| | | |
| | | // æ¾ç¤ºäºç»´ç |
| | | const showQRCode = async (row) => { |
| | | try { |
| | | // æå»ºäºç»´ç å
容ï¼åªå
å«éè´ååå·ï¼çº¯ææ¬ï¼ |
| | | const qrContent = row.purchaseContractNumber || ''; |
| | | // æ£æ¥å
容æ¯å¦ä¸ºç©º |
| | | if (!qrContent || qrContent.trim() === '') { |
| | | proxy.$modal.msgWarning("è¯¥è¡æ²¡æéè´ååå·ï¼æ æ³çæäºç»´ç "); |
| | | }; |
| | | const mathNum = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请å
éæ©ç¨ç"); |
| | | return; |
| | | } |
| | | qrCodeUrl.value = await QRCode.toDataURL(qrContent, { |
| | | width: 200, |
| | | margin: 2, |
| | | color: { |
| | | dark: '#000000', |
| | | light: '#FFFFFF' |
| | | if (!productForm.value.taxInclusiveUnitPrice) { |
| | | return; |
| | | } |
| | | if (!productForm.value.quantity) { |
| | | return; |
| | | } |
| | | // å«ç¨æ»ä»·è®¡ç® |
| | | productForm.value.taxInclusiveTotalPrice = |
| | | proxy.calculateTaxIncludeTotalPrice( |
| | | productForm.value.taxInclusiveUnitPrice, |
| | | productForm.value.quantity |
| | | ); |
| | | if (productForm.value.taxRate) { |
| | | // ä¸å«ç¨æ»ä»·è®¡ç® |
| | | productForm.value.taxExclusiveTotalPrice = |
| | | proxy.calculateTaxExclusiveTotalPrice( |
| | | productForm.value.taxInclusiveTotalPrice, |
| | | productForm.value.taxRate |
| | | ); |
| | | } |
| | | }; |
| | | const reverseMathNum = field => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请å
éæ©ç¨ç"); |
| | | return; |
| | | } |
| | | const taxRate = Number(productForm.value.taxRate); |
| | | if (!taxRate) return; |
| | | if (field === "taxInclusiveTotalPrice") { |
| | | // å·²ç¥å«ç¨æ»ä»·åæ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | 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.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); |
| | | // å·²ç¥æ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | 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); |
| | | } |
| | | } |
| | | }; |
| | | // éå®ååéæ©æ¹åæ¹æ³ |
| | | const salesLedgerChange = async row => { |
| | | console.log("row", row); |
| | | var index = salesContractList.value.findIndex(item => item.id == row); |
| | | console.log("index", index); |
| | | if (index > -1) { |
| | | form.value.projectName = salesContractList.value[index].projectName; |
| | | await querygProductInfoByContractNo(); |
| | | } |
| | | }; |
| | | |
| | | const querygProductInfoByContractNo = async () => { |
| | | const { code, data } = await getProductInfoByContractNo({ |
| | | contractNo: form.value.salesLedgerId, |
| | | }); |
| | | if (code == 200) { |
| | | productData.value = data; |
| | | } |
| | | }; |
| | | |
| | | // æ¾ç¤ºäºç»´ç |
| | | const showQRCode = async row => { |
| | | try { |
| | | // æå»ºäºç»´ç å
容ï¼åªå
å«éè´ååå·ï¼çº¯ææ¬ï¼ |
| | | const qrContent = row.purchaseContractNumber || ""; |
| | | // æ£æ¥å
容æ¯å¦ä¸ºç©º |
| | | if (!qrContent || qrContent.trim() === "") { |
| | | proxy.$modal.msgWarning("è¯¥è¡æ²¡æéè´ååå·ï¼æ æ³çæäºç»´ç "); |
| | | return; |
| | | } |
| | | qrCodeUrl.value = await QRCode.toDataURL(qrContent, { |
| | | width: 200, |
| | | margin: 2, |
| | | color: { |
| | | dark: "#000000", |
| | | light: "#FFFFFF", |
| | | }, |
| | | }); |
| | | qrCodeDialogVisible.value = true; |
| | | } catch (error) { |
| | | console.error("çæäºç»´ç 失败:", error); |
| | | proxy.$modal.msgError("çæäºç»´ç 失败ï¼" + error.message); |
| | | } |
| | | }; |
| | | |
| | | // ä¸è½½äºç»´ç |
| | | const downloadQRCode = () => { |
| | | if (!qrCodeUrl.value) { |
| | | proxy.$modal.msgWarning("äºç»´ç æªçæ"); |
| | | return; |
| | | } |
| | | |
| | | const a = document.createElement("a"); |
| | | a.href = qrCodeUrl.value; |
| | | a.download = `éè´ååå·äºç»´ç _${new Date().getTime()}.png`; |
| | | document.body.appendChild(a); |
| | | a.click(); |
| | | document.body.removeChild(a); |
| | | proxy.$modal.msgSuccess("ä¸è½½æå"); |
| | | }; |
| | | |
| | | // æ«ç æ°å¢å¯¹è¯æ¡ç¸å
³åé |
| | | const scanAddDialogVisible = ref(false); |
| | | const scanAddForm = reactive({ |
| | | scanContent: "", |
| | | purchaseContractNumber: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | contractAmount: "", |
| | | paymentMethod: "", |
| | | recorderName: "", |
| | | scanRemark: "", |
| | | }); |
| | | const scanAddRules = { |
| | | purchaseContractNumber: [ |
| | | { required: true, message: "请è¾å
¥éè´ååå·", trigger: "blur" }, |
| | | ], |
| | | supplierName: [ |
| | | { required: true, message: "请è¾å
¥ä¾åºååç§°", trigger: "blur" }, |
| | | ], |
| | | projectName: [{ required: true, message: "请è¾å
¥é¡¹ç®åç§°", trigger: "blur" }], |
| | | }; |
| | | |
| | | // æ«ç ç»è®°å¯¹è¯æ¡ç¸å
³åé |
| | | const scanDialogVisible = ref(false); |
| | | const scanForm = reactive({ |
| | | purchaseContractNumber: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | scanTime: "", |
| | | scannerName: "", |
| | | scanStatus: "æªæ«ç ", |
| | | scanRemark: "", |
| | | }); |
| | | const scanRules = { |
| | | scanRemark: [{ required: true, message: "请è¾å
¥æ«ç 夿³¨", trigger: "blur" }], |
| | | }; |
| | | const scanRecords = ref([]); |
| | | |
| | | // æå¼æ«ç æ°å¢å¯¹è¯æ¡ |
| | | const openScanAddDialog = () => { |
| | | scanAddForm.scanContent = ""; |
| | | scanAddForm.purchaseContractNumber = ""; |
| | | scanAddForm.supplierName = ""; |
| | | scanAddForm.projectName = ""; |
| | | scanAddForm.contractAmount = ""; |
| | | scanAddForm.paymentMethod = ""; |
| | | scanAddForm.recorderName = userStore.nickName; |
| | | scanAddForm.scanRemark = ""; |
| | | scanAddDialogVisible.value = true; |
| | | }; |
| | | |
| | | // è§£ææ«ç å
å®¹ï¼æ¨¡æè§£æäºç»´ç æ°æ®ï¼ |
| | | const parseScanContent = content => { |
| | | if (!content) return; |
| | | |
| | | // 模æè§£æäºç»´ç å
容ï¼è¿éå¯ä»¥æ ¹æ®å®é
éæ±è°æ´è§£æé»è¾ |
| | | // å设æ«ç å
å®¹æ ¼å¼ä¸ºï¼ååå·|ä¾åºå|项ç®|éé¢|仿¬¾æ¹å¼ |
| | | const parts = content.split("|"); |
| | | if (parts.length >= 3) { |
| | | scanAddForm.purchaseContractNumber = parts[0] || ""; |
| | | scanAddForm.supplierName = parts[1] || ""; |
| | | scanAddForm.projectName = parts[2] || ""; |
| | | scanAddForm.contractAmount = parts[3] || ""; |
| | | scanAddForm.paymentMethod = parts[4] || ""; |
| | | } |
| | | }; |
| | | |
| | | // å
³éæ«ç æ°å¢å¯¹è¯æ¡ |
| | | const closeScanAddDialog = () => { |
| | | scanAddDialogVisible.value = false; |
| | | proxy.resetForm("scanAddFormRef"); |
| | | }; |
| | | |
| | | // æäº¤æ«ç æ°å¢ |
| | | const submitScanAdd = () => { |
| | | proxy.$refs["scanAddFormRef"].validate(valid => { |
| | | if (valid) { |
| | | // æå»ºæ°å¢æ°æ® |
| | | const newData = { |
| | | purchaseContractNumber: scanAddForm.purchaseContractNumber, |
| | | supplierName: scanAddForm.supplierName, |
| | | projectName: scanAddForm.projectName, |
| | | contractAmount: scanAddForm.contractAmount, |
| | | paymentMethod: scanAddForm.paymentMethod, |
| | | recorderName: scanAddForm.recorderName, |
| | | entryDate: getCurrentDate(), |
| | | remark: scanAddForm.scanRemark, |
| | | type: 2, |
| | | }; |
| | | |
| | | // æ¨¡ææ°å¢æå |
| | | proxy.$modal.msgSuccess("æ«ç æ°å¢æåï¼"); |
| | | closeScanAddDialog(); |
| | | |
| | | // å¯ä»¥éæ©æ¯å¦å·æ°å表 |
| | | // getList(); |
| | | } |
| | | }); |
| | | qrCodeDialogVisible.value = true; |
| | | } catch (error) { |
| | | console.error('çæäºç»´ç 失败:', error); |
| | | proxy.$modal.msgError("çæäºç»´ç 失败ï¼" + error.message); |
| | | }; |
| | | |
| | | // æå¼æ«ç ç»è®°å¯¹è¯æ¡ |
| | | const openScanDialog = row => { |
| | | scanForm.purchaseContractNumber = row.purchaseContractNumber; |
| | | scanForm.supplierName = row.supplierName; |
| | | scanForm.projectName = row.projectName; |
| | | scanForm.scanTime = getCurrentDateTime(); |
| | | scanForm.scannerName = userStore.nickName; |
| | | scanForm.scanStatus = "æªæ«ç "; |
| | | scanForm.scanRemark = ""; |
| | | scanRecords.value = []; |
| | | scanDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³éæ«ç ç»è®°å¯¹è¯æ¡ |
| | | const closeScanDialog = () => { |
| | | scanDialogVisible.value = false; |
| | | proxy.resetForm("scanFormRef"); |
| | | }; |
| | | |
| | | // æäº¤æ«ç ç»è®° |
| | | const submitScan = () => { |
| | | proxy.$refs["scanFormRef"].validate(valid => { |
| | | if (valid) { |
| | | // æ·»å æ«ç è®°å½ |
| | | scanRecords.value.push({ |
| | | ...scanForm, |
| | | id: Date.now(), // 模æID |
| | | scanTime: getCurrentDateTime(), |
| | | }); |
| | | scanForm.scanStatus = "å·²æ«ç "; |
| | | scanForm.scanRemark = scanForm.scanRemark || "æ "; |
| | | proxy.$modal.msgSuccess("æ«ç ç»è®°æåï¼"); |
| | | closeScanDialog(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // è·åå½åæ¥ææ¶é´ |
| | | function getCurrentDateTime() { |
| | | 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"); |
| | | const hours = String(now.getHours()).padStart(2, "0"); |
| | | const minutes = String(now.getMinutes()).padStart(2, "0"); |
| | | const seconds = String(now.getSeconds()).padStart(2, "0"); |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | } |
| | | }; |
| | | |
| | | // ä¸è½½äºç»´ç |
| | | const downloadQRCode = () => { |
| | | if (!qrCodeUrl.value) { |
| | | proxy.$modal.msgWarning("äºç»´ç æªçæ"); |
| | | return; |
| | | } |
| | | |
| | | const a = document.createElement('a'); |
| | | a.href = qrCodeUrl.value; |
| | | a.download = `éè´ååå·äºç»´ç _${new Date().getTime()}.png`; |
| | | document.body.appendChild(a); |
| | | a.click(); |
| | | document.body.removeChild(a); |
| | | proxy.$modal.msgSuccess("ä¸è½½æå"); |
| | | }; |
| | | // æ·»å è¡ç±»åæ¹æ³ |
| | | const tableRowClassName = ({ row }) => { |
| | | return row.isInvalid ? "invalid-row" : ""; |
| | | }; |
| | | |
| | | // æ«ç æ°å¢å¯¹è¯æ¡ç¸å
³åé |
| | | const scanAddDialogVisible = ref(false); |
| | | const scanAddForm = reactive({ |
| | | scanContent: "", |
| | | purchaseContractNumber: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | contractAmount: "", |
| | | paymentMethod: "", |
| | | recorderName: "", |
| | | scanRemark: "", |
| | | }); |
| | | const scanAddRules = { |
| | | purchaseContractNumber: [{ required: true, message: "请è¾å
¥éè´ååå·", trigger: "blur" }], |
| | | supplierName: [{ required: true, message: "请è¾å
¥ä¾åºååç§°", trigger: "blur" }], |
| | | projectName: [{ required: true, message: "请è¾å
¥é¡¹ç®åç§°", trigger: "blur" }], |
| | | }; |
| | | |
| | | // æ«ç ç»è®°å¯¹è¯æ¡ç¸å
³åé |
| | | const scanDialogVisible = ref(false); |
| | | const scanForm = reactive({ |
| | | purchaseContractNumber: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | scanTime: "", |
| | | scannerName: "", |
| | | scanStatus: "æªæ«ç ", |
| | | scanRemark: "", |
| | | }); |
| | | const scanRules = { |
| | | scanRemark: [{ required: true, message: "请è¾å
¥æ«ç 夿³¨", trigger: "blur" }], |
| | | }; |
| | | const scanRecords = ref([]); |
| | | |
| | | // æå¼æ«ç æ°å¢å¯¹è¯æ¡ |
| | | const openScanAddDialog = () => { |
| | | scanAddForm.scanContent = ""; |
| | | scanAddForm.purchaseContractNumber = ""; |
| | | scanAddForm.supplierName = ""; |
| | | scanAddForm.projectName = ""; |
| | | scanAddForm.contractAmount = ""; |
| | | scanAddForm.paymentMethod = ""; |
| | | scanAddForm.recorderName = userStore.nickName; |
| | | scanAddForm.scanRemark = ""; |
| | | scanAddDialogVisible.value = true; |
| | | }; |
| | | |
| | | // è§£ææ«ç å
å®¹ï¼æ¨¡æè§£æäºç»´ç æ°æ®ï¼ |
| | | const parseScanContent = (content) => { |
| | | if (!content) return; |
| | | |
| | | // 模æè§£æäºç»´ç å
容ï¼è¿éå¯ä»¥æ ¹æ®å®é
éæ±è°æ´è§£æé»è¾ |
| | | // å设æ«ç å
å®¹æ ¼å¼ä¸ºï¼ååå·|ä¾åºå|项ç®|éé¢|仿¬¾æ¹å¼ |
| | | const parts = content.split('|'); |
| | | if (parts.length >= 3) { |
| | | scanAddForm.purchaseContractNumber = parts[0] || ""; |
| | | scanAddForm.supplierName = parts[1] || ""; |
| | | scanAddForm.projectName = parts[2] || ""; |
| | | scanAddForm.contractAmount = parts[3] || ""; |
| | | scanAddForm.paymentMethod = parts[4] || ""; |
| | | } |
| | | }; |
| | | |
| | | // å
³éæ«ç æ°å¢å¯¹è¯æ¡ |
| | | const closeScanAddDialog = () => { |
| | | scanAddDialogVisible.value = false; |
| | | proxy.resetForm("scanAddFormRef"); |
| | | }; |
| | | |
| | | // æäº¤æ«ç æ°å¢ |
| | | const submitScanAdd = () => { |
| | | proxy.$refs["scanAddFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | // æå»ºæ°å¢æ°æ® |
| | | const newData = { |
| | | purchaseContractNumber: scanAddForm.purchaseContractNumber, |
| | | supplierName: scanAddForm.supplierName, |
| | | projectName: scanAddForm.projectName, |
| | | contractAmount: scanAddForm.contractAmount, |
| | | paymentMethod: scanAddForm.paymentMethod, |
| | | recorderName: scanAddForm.recorderName, |
| | | entryDate: getCurrentDate(), |
| | | remark: scanAddForm.scanRemark, |
| | | type: 2 |
| | | }; |
| | | |
| | | // æ¨¡ææ°å¢æå |
| | | proxy.$modal.msgSuccess("æ«ç æ°å¢æåï¼"); |
| | | closeScanAddDialog(); |
| | | |
| | | // å¯ä»¥éæ©æ¯å¦å·æ°å表 |
| | | // getList(); |
| | | } |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | }; |
| | | |
| | | // æå¼æ«ç ç»è®°å¯¹è¯æ¡ |
| | | const openScanDialog = (row) => { |
| | | scanForm.purchaseContractNumber = row.purchaseContractNumber; |
| | | scanForm.supplierName = row.supplierName; |
| | | scanForm.projectName = row.projectName; |
| | | scanForm.scanTime = getCurrentDateTime(); |
| | | scanForm.scannerName = userStore.nickName; |
| | | scanForm.scanStatus = "æªæ«ç "; |
| | | scanForm.scanRemark = ""; |
| | | scanRecords.value = []; |
| | | scanDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³éæ«ç ç»è®°å¯¹è¯æ¡ |
| | | const closeScanDialog = () => { |
| | | scanDialogVisible.value = false; |
| | | proxy.resetForm("scanFormRef"); |
| | | }; |
| | | |
| | | // æäº¤æ«ç ç»è®° |
| | | const submitScan = () => { |
| | | proxy.$refs["scanFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | // æ·»å æ«ç è®°å½ |
| | | scanRecords.value.push({ |
| | | ...scanForm, |
| | | id: Date.now(), // 模æID |
| | | scanTime: getCurrentDateTime(), |
| | | }); |
| | | scanForm.scanStatus = "å·²æ«ç "; |
| | | scanForm.scanRemark = scanForm.scanRemark || "æ "; |
| | | proxy.$modal.msgSuccess("æ«ç ç»è®°æåï¼"); |
| | | closeScanDialog(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // è·åå½åæ¥ææ¶é´ |
| | | function getCurrentDateTime() { |
| | | 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"); |
| | | const hours = String(now.getHours()).padStart(2, "0"); |
| | | const minutes = String(now.getMinutes()).padStart(2, "0"); |
| | | const seconds = String(now.getSeconds()).padStart(2, "0"); |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | } |
| | | |
| | | // æ·»å è¡ç±»åæ¹æ³ |
| | | const tableRowClassName = ({ row }) => { |
| | | return row.isInvalid ? 'invalid-row' : ''; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .invalid-row { |
| | | opacity: 0.6; |
| | | background-color: #f5f7fa; |
| | | } |
| | | .invalid-row { |
| | | opacity: 0.6; |
| | | background-color: #f5f7fa; |
| | | } |
| | | </style> |
| | |
| | | </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-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" @click="submitRegulation">åå¸å¶åº¦</el-button> |
| | | <el-button @click="showRegulationDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="submitRegulation">åå¸å¶åº¦</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); |
| | |
| | | fixed: "right", |
| | | align: "center", |
| | | operation: [ |
| | | { name: "æ¥ç", clickFun: (row) => viewRegulation(row) }, |
| | | { name: "ç¼è¾", clickFun: (row) => handleEdit(row) }, |
| | | { name: "åºå¼", clickFun: (row) => repealEdit(row) }, |
| | | { name: "çæ¬åå²", clickFun: (row) => viewVersionHistory(row) }, |
| | | { name: "详æ
", clickFun: (row) => viewRegulation(row) }, |
| | | { name: "éä»¶", clickFun: (row) => openFileDialog(row) }, |
| | | ], |
| | | }, |
| | |
| | | ); |
| | | }; |
| | | |
| | | // éä»¶ï¼æ¥è¯¢ |
| | | 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; |
| | | } |
| | | await delRuleFile([row.id]); |
| | | ElMessage.success("å 餿å"); |
| | | await refreshFileList(); |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFileDialog = async (row) => { |
| | | recordId.value = row.id |
| | | fileDialogVisible.value = true |
| | | } |
| | | |
| | | // è·åè§ç« å¶åº¦åè¡¨æ°æ® |
| | | const getRegulationList = async () => { |
| | |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | text-align: center; |
| | | } |
| | | </style> |
| | |
| | | fixed: 'right', |
| | | align: 'center', |
| | | operation: [ |
| | | { name: 'æ¥ç', clickFun: (row) => viewSealDetail(row) }, |
| | | { |
| | | name: '审æ¹', |
| | | clickFun: (row) => approveSeal(row), |
| | |
| | | name: 'æç»', |
| | | clickFun: (row) => rejectSeal(row), |
| | | showHide: (row) => row.status === 'pending' |
| | | } |
| | | }, |
| | | { name: '详æ
', clickFun: (row) => viewSealDetail(row) } |
| | | ] |
| | | } |
| | | ]) |
| | |
| | | <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" |
| | | /> |
| | | </div> |
| | | <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 |
| | | } |
| | | |
| | | // æ¥è¯¢å表 |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div class="search_form mb20"> |
| | | <div> |
| | | <span class="search_title">ä¸´ææ¥æï¼</span> |
| | | <el-date-picker |
| | |
| | | >éç½®</el-button |
| | | > |
| | | </div> |
| | | <div class="table_actions"> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> |
| | | <el-button type="danger" @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_actions" style="margin-bottom: 10px;"> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> |
| | | <el-button type="danger" @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="é®é¢æè¿°ï¼" prop="disRes"> |
| | | <el-form-item label="é®é¢æè¿°ï¼" prop="proDesc"> |
| | | <el-input |
| | | v-model="form.disRes" |
| | | v-model="form.proDesc" |
| | | placeholder="请è¾å
¥é®é¢æè¿°" |
| | | /> |
| | | </el-form-item> |
| | |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | > |
| | | <template #approveStatus="{ row }"> |
| | | <el-tag :type="getApproveStatusType(row)" size="small"> |
| | | {{ getApproveStatusText(row) }} |
| | | </el-tag> |
| | | </template> |
| | | <template #shippingStatus="{ row }"> |
| | | <el-tag :type="getShippingStatusType(row)" size="small"> |
| | | {{ getShippingStatusText(row) }} |
| | |
| | | productModelIds: "", |
| | | customerId: null, |
| | | salesContractNo: "", |
| | | disRes: "", |
| | | proDesc: "", |
| | | customerName: "" |
| | | }, |
| | | rules: { |
| | |
| | | taxInclusiveUnitPrice: row?.taxInclusiveUnitPrice ?? 0, |
| | | taxInclusiveTotalPrice: row?.taxInclusiveTotalPrice ?? 0, |
| | | taxExclusiveTotalPrice: row?.taxExclusiveTotalPrice ?? 0, |
| | | noQuantity: row?.noQuantity ?? 0, |
| | | } |
| | | } |
| | | |
| | |
| | | prop: "approveStatus", |
| | | width: 100, |
| | | align: "center", |
| | | dataType: "tag", |
| | | formatData: (v) => (v === 1 ? "å
è¶³" : "ä¸è¶³"), |
| | | formatType: (v) => (v === 1 ? "success" : "danger"), |
| | | dataType: "slot", |
| | | slot: "approveStatus", |
| | | }, |
| | | { |
| | | label: "åè´§ç¶æ", |
| | |
| | | }) |
| | | |
| | | const customerNameChange = (val) => { |
| | | form.value.salesContractNo = ""; |
| | | form.value.salesLedgerId = null; |
| | | tableData.value = []; |
| | | associatedSalesOrderNumberOptions.value = []; |
| | | const opt = customerNameOptions.value.find(item => item.value === val); |
| | | if (opt) { |
| | | form.value.customerId = opt.id; |
| | | } else { |
| | | form.value.customerId = null; |
| | | } |
| | | getSalesLedger({ |
| | | customerName: form.value.customerName |
| | |
| | | })) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | 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 'ä¸è¶³' |
| | | } |
| | | |
| | | const getApproveStatusType = (row) => { |
| | | const statusText = getApproveStatusText(row) |
| | | return statusText === 'ä¸è¶³' ? 'danger' : 'success' |
| | | } |
| | | |
| | | const getShippingStatusText = (row) => { |
| | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog =async (type, row) => { |
| | | // 请æ±å¤ä¸ªæ¥å£ï¼è·åæ°æ® |
| | | let res = await getAllCustomerList(); |
| | | let res = await getAllCustomerList({ |
| | | current: 1, |
| | | size: 1000, |
| | | total: 0, |
| | | }); |
| | | if(res.records){ |
| | | customerNameOptions.value = res.records.map(item => ({ |
| | | label: item.customerName, |
| | |
| | | }, |
| | | { |
| | | label: "é®é¢æè¿°", |
| | | prop: "disRes", |
| | | prop: "proDesc", |
| | | width:300, |
| | | }, |
| | | { |
| | |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | operation: [ |
| | | { |
| | |
| | | }); |
| | | }; |
| | | |
| | | const getStatsCountByStatus = (list, status) => { |
| | | if (!Array.isArray(list)) return 0; |
| | | return list.find((item) => item?.status === status)?.count || 0; |
| | | }; |
| | | |
| | | // è·åç»è®¡æ°æ®å¹¶å·æ°é¡¶é¨å¡ç |
| | | const getSalesLedgerDetails = () => { |
| | | getSalesLedgerDetail({}).then((res) => { |
| | | if (res.code === 200) { |
| | | statsList.value[0].count = res.data.filter((item) => item.status === 3)[0].count; |
| | | statsList.value[1].count = res.data.filter((item) => item.status === 2)[0].count; |
| | | statsList.value[2].count = res.data.filter((item) => item.status === 1)[0].count; |
| | | |
| | | // }); |
| | | const statsData = Array.isArray(res.data) ? res.data : []; |
| | | statsList.value[0].count = getStatsCountByStatus(statsData, 3); |
| | | statsList.value[1].count = getStatsCountByStatus(statsData, 2); |
| | | statsList.value[2].count = getStatsCountByStatus(statsData, 1); |
| | | } |
| | | }); |
| | | } |
| | |
| | | |
| | | .table_list { |
| | | height: calc(100vh - 380px); |
| | | min-height: 360px; |
| | | background: #fff; |
| | | margin-top: 20px; |
| | | display: flex; |
| | |
| | | <el-table-column prop="lastUpdate" label="æåæ´æ°" /> |
| | | <el-table-column label="æä½"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | size="small" |
| | | <el-button |
| | | @click="updateModel(scope.row)" |
| | | > |
| | | æ´æ°æ¨¡å |
| | | </el-button> |
| | | <el-button |
| | | size="small" |
| | | <el-button |
| | | type="danger" |
| | | @click="deleteModel(scope.row)" |
| | | > |
| | |
| | | </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-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="cancel">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ä¿å</el-button> |
| | | <el-button @click="cancel">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | 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 urlLower = fileUrl.toLowerCase(); |
| | | if (urlLower.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/)) { |
| | | const fileUrl = item.previewURL; |
| | | const contentType = String(item.contentType).toLowerCase(); |
| | | |
| | | // æ ¹æ® 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; |
| | |
| | | </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: "设å¤å°è´¦è¡¨å", |
| | |
| | | <el-form-item label="设å¤åç§°"> |
| | | <el-input |
| | | v-model="filters.deviceName" |
| | | style="width: 240px" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥è®¾å¤åç§°" |
| | | clearable |
| | | @change="getTableData" |
| | |
| | | <el-form-item label="è§æ ¼åå·"> |
| | | <el-input |
| | | v-model="filters.deviceModel" |
| | | style="width: 240px" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥è§æ ¼åå·" |
| | | clearable |
| | | @change="getTableData" |
| | |
| | | <el-form-item label="ä¾åºå"> |
| | | <el-input |
| | | v-model="filters.supplierName" |
| | | style="width: 240px" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥ä¾åºå" |
| | | clearable |
| | | @change="getTableData" |
| | |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus"> æ°å¢ </el-button> |
| | | <el-button type="info" @click="handleImport" icon="Upload">导å
¥</el-button> |
| | | <el-button @click="handleOut" icon="download">导åº</el-button> |
| | | <el-button |
| | | type="danger" |
| | |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- 导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body> |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :auto-upload="false" |
| | | drag |
| | | > |
| | | <el-icon class="el-icon--upload"><upload-filled /></el-icon> |
| | | <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> |
| | | <template #tip> |
| | | <div class="el-upload__tip text-center"> |
| | | <span>ä»
å
许导å
¥xlsãxlsxæ ¼å¼æä»¶ã</span> |
| | | <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline; margin-left: 5px;" @click="importTemplate">ä¸è½½æ¨¡æ¿</el-link> |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button @click="upload.open = false">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { usePaginationApi } from "@/hooks/usePaginationApi"; |
| | | // import { Search } from "@element-plus/icons-vue"; |
| | | import { getLedgerPage, delLedger } from "@/api/equipmentManagement/ledger"; |
| | | import { onMounted, getCurrentInstance } from "vue"; |
| | | import { onMounted, getCurrentInstance, ref, reactive } from "vue"; |
| | | import Modal from "./Modal.vue"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import { UploadFilled } from "@element-plus/icons-vue"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import dayjs from "dayjs"; |
| | | import QRCode from "qrcode"; |
| | | import { ref } from "vue"; |
| | | |
| | | defineOptions({ |
| | | name: "设å¤å°è´¦", |
| | |
| | | const qrDialogVisible = ref(false); |
| | | const qrCodeUrl = ref(""); |
| | | const qrRowData = ref(null); |
| | | |
| | | // 导å
¥ç¸å
³ |
| | | const uploadRef = ref(null) |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå± |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import" |
| | | }) |
| | | |
| | | const { |
| | | filters, |
| | |
| | | a.click(); |
| | | }; |
| | | |
| | | // 导å
¥æé®æä½ |
| | | const handleImport = () => { |
| | | upload.title = "设å¤å°è´¦å¯¼å
¥" |
| | | upload.open = true |
| | | } |
| | | |
| | | // ä¸è½½æ¨¡æ¿æä½ |
| | | const importTemplate = () => { |
| | | proxy.download("/device/ledger/downloadTemplate", {}, `设å¤å°è´¦å¯¼å
¥æ¨¡æ¿_${new Date().getTime()}.xlsx`) |
| | | } |
| | | |
| | | // æä»¶ä¸ä¼ ä¸å¤ç |
| | | const handleFileUploadProgress = (event, file, fileList) => { |
| | | upload.isUploading = true |
| | | } |
| | | |
| | | // æä»¶ä¸ä¼ æåå¤ç |
| | | const handleFileSuccess = (response, file, fileList) => { |
| | | upload.open = false |
| | | upload.isUploading = false |
| | | proxy.$refs["uploadRef"].handleRemove(file) |
| | | proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导å
¥ç»æ", { dangerouslyUseHTMLString: true }) |
| | | getTableData() |
| | | } |
| | | |
| | | // æäº¤ä¸ä¼ æä»¶ |
| | | const submitFileForm = () => { |
| | | proxy.$refs["uploadRef"].submit() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | |
| | | 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) { |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div class="search_form mb20"> |
| | | <div> |
| | | <span class="search_title">å½å
¥æ¥æï¼</span> |
| | | <el-date-picker |
| | |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="设å¤å¤ä»¶"> |
| | | <el-select v-model="form.sparePartsIds" :loading="loadingSparePartOptions" placeholder="è¯·éæ©è®¾å¤å¤ä»¶" multiple filterable> |
| | | <el-option |
| | | v-for="item in sparePartOptions" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item v-if="selectedSpareParts.length" label="é¢ç¨æ°é"> |
| | | <div style="width: 100%"> |
| | | <div |
| | | v-for="item in selectedSpareParts" |
| | | :key="item.id" |
| | | style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;" |
| | | > |
| | | <div style="flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> |
| | | {{ item.name }} |
| | | <span v-if="item.quantity !== null && item.quantity !== undefined" style="color: #909399;"> |
| | | ï¼åºåï¼{{ item.quantity }}ï¼ |
| | | </span> |
| | | </div> |
| | | <el-input-number |
| | | v-model="sparePartQtyMap[item.id]" |
| | | :min="1" |
| | | :max="item.quantity !== null && item.quantity !== undefined ? Number(item.quantity) : undefined" |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 180px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-form> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, getCurrentInstance, nextTick, ref } from "vue"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { addMaintain } from "@/api/equipmentManagement/repair"; |
| | | import useFormData from "@/hooks/useFormData"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import dayjs from "dayjs"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { getSparePartsList } from "@/api/equipmentManagement/spareParts"; |
| | | |
| | | defineOptions({ |
| | | name: "ç»´ä¿®æ¨¡ææ¡", |
| | | }); |
| | | |
| | | const emits = defineEmits(["ok"]); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // ä¿åæ¥ä¿®è®°å½çid |
| | | const repairId = ref(); |
| | |
| | | maintenanceResult: undefined, // ç»´ä¿®ç»æ |
| | | maintenanceTime: undefined, // ç»´ä¿®æ¥æ |
| | | status: 0, |
| | | sparePartsIds: [], |
| | | }); |
| | | const sparePartOptions = ref([]) |
| | | const loadingSparePartOptions = ref(true) |
| | | const sparePartQtyMap = ref({}) |
| | | |
| | | const selectedSpareParts = computed(() => { |
| | | const ids = Array.isArray(form.sparePartsIds) ? form.sparePartsIds : []; |
| | | const set = new Set(ids.map((i) => String(i))); |
| | | return (sparePartOptions.value || []).filter((p) => set.has(String(p.id))); |
| | | }); |
| | | |
| | | const setForm = (data) => { |
| | |
| | | ? dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss") |
| | | : dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | form.status = 1; // é»è®¤ç¶æä¸ºå®ç» |
| | | // multiple éæ©å¨è¦æ±æ°ç»ï¼å端常è¿å "1,2,3" |
| | | if (Array.isArray(data?.sparePartsIds)) { |
| | | form.sparePartsIds = data.sparePartsIds.map((v) => Number(v)).filter((v) => Number.isFinite(v)); |
| | | } else if (typeof data?.sparePartsIds === "string") { |
| | | form.sparePartsIds = data.sparePartsIds |
| | | .split(",") |
| | | .map((s) => Number(String(s).trim())) |
| | | .filter((v) => Number.isFinite(v)); |
| | | } else if (typeof data?.sparePartsIds === "number") { |
| | | form.sparePartsIds = [data.sparePartsIds]; |
| | | } else { |
| | | form.sparePartsIds = []; |
| | | } |
| | | }; |
| | | |
| | | const sendForm = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const { code } = await addMaintain({ id: repairId.value, ...form }); |
| | | // é¢ç¨æ°éæ ¡éª |
| | | if (Array.isArray(form.sparePartsIds) && form.sparePartsIds.length > 0) { |
| | | for (const partId of form.sparePartsIds) { |
| | | const qty = Number(sparePartQtyMap.value?.[partId]); |
| | | if (!Number.isFinite(qty) || qty <= 0) { |
| | | proxy?.$modal?.msgError?.("请填åå¤ä»¶é¢ç¨æ°é"); |
| | | return; |
| | | } |
| | | const part = sparePartOptions.value.find((p) => String(p.id) === String(partId)); |
| | | const stock = part?.quantity; |
| | | if (stock !== null && stock !== undefined && Number.isFinite(Number(stock))) { |
| | | if (qty > Number(stock)) { |
| | | proxy?.$modal?.msgError?.(`å¤ä»¶ã${part?.name || ""}ãé¢ç¨æ°éä¸è½è¶
è¿åºåï¼${stock}ï¼`); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | const data = { |
| | | id: repairId.value, |
| | | ...form, |
| | | sparePartsIds: form.sparePartsIds ? form.sparePartsIds.join(",") : "", |
| | | sparePartsQty: form.sparePartsIds |
| | | ? form.sparePartsIds.map((id) => sparePartQtyMap.value?.[id] ?? 1).join(",") |
| | | : "", |
| | | sparePartsUseList: form.sparePartsIds |
| | | ? form.sparePartsIds.map((id) => ({ id, quantity: sparePartQtyMap.value?.[id] ?? 1 })) |
| | | : [], |
| | | } |
| | | const { code } = await addMaintain(data); |
| | | if (code == 200) { |
| | | ElMessage.success("ç»´ä¿®æå"); |
| | | emits("ok"); |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | } |
| | | } finally { |
| | |
| | | } |
| | | }; |
| | | |
| | | const fetchSparePartOptions = () => { |
| | | loadingSparePartOptions.value = true; |
| | | // åå¤ä»¶ç®¡ç页ä¸è´ï¼/spareParts/listPage â res.data.records |
| | | getSparePartsList({ current: 1, size: 1000 }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | sparePartOptions.value = res?.data?.records || []; |
| | | } else { |
| | | sparePartOptions.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | sparePartOptions.value = []; |
| | | }) |
| | | .finally(() => { |
| | | loadingSparePartOptions.value = false; |
| | | }); |
| | | } |
| | | |
| | | const handleCancel = () => { |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | }; |
| | | |
| | | const handleClose = () => { |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | }; |
| | | |
| | |
| | | visible.value = true; |
| | | await nextTick(); |
| | | setForm(row); |
| | | fetchSparePartOptions() |
| | | }; |
| | | |
| | | defineExpose({ |
| | |
| | | <el-input v-model="form.repairName" placeholder="请è¾å
¥æ¥ä¿®äºº" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®"> |
| | | <el-input v-model="form.machineryCategory" placeholder="请è¾å
¥é¡¹ç®" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="id"> |
| | | <el-col :span="12"> |
| | |
| | | </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(); |
| | |
| | | repairName: userStore.nickName, // æ¥ä¿®äºº |
| | | remark: undefined, // æ
éç°è±¡ |
| | | status: 0, // æ¥ä¿®ç¶æ |
| | | machineryCategory: undefined, |
| | | storageBlobDTOs: [], |
| | | }); |
| | | |
| | | const setDeviceModel = (deviceId) => { |
| | |
| | | form.repairName = data.repairName; |
| | | form.remark = data.remark; |
| | | form.status = data.status; |
| | | form.machineryCategory = data.machineryCategory; |
| | | form.storageBlobDTOs = data.storageBlobVOs || []; |
| | | }; |
| | | |
| | | const sendForm = async () => { |
| | |
| | | const openAdd = async () => { |
| | | id.value = undefined; |
| | | visible.value = true; |
| | | fileList.value = []; |
| | | await nextTick(); |
| | | await loadDeviceName(); |
| | | }; |
| | |
| | | > |
| | | å é¤ |
| | | </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"/> |
| | | <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"; |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | |
| | | defineOptions({ |
| | | name: "è®¾å¤æ¥ä¿®", |
| | |
| | | label: "è§æ ¼åå·", |
| | | align: "center", |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "项ç®", |
| | | align: "center", |
| | | prop: "machineryCategory", |
| | | }, |
| | | { |
| | | label: "æ¥ä¿®æ¥æ", |
| | |
| | | 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; |
| | |
| | | <template> |
| | | <div class="spare-part-category"> |
| | | <div class="search_form"> |
| | | <el-form :inline="true" :model="queryParams" class="search-form"> |
| | | <el-form-item label="å¤ä»¶åç§°"> |
| | | <el-input |
| | | v-model="queryParams.name" |
| | | placeholder="请è¾å
¥å¤ä»¶åç§°" |
| | | clearable |
| | | style="width: 240px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div> |
| | | <el-button type="primary" @click="addCategory" >æ°å¢</el-button> |
| | | </div> |
| | | </div> |
| | | <el-tabs v-model="activeTab" @tab-change="handleTabChange"> |
| | | <el-tab-pane label="å¤ä»¶å表" name="list"> |
| | | <div class="search_form"> |
| | | <el-form :inline="true" :model="queryParams" class="search-form"> |
| | | <el-form-item label="å¤ä»¶åç§°"> |
| | | <el-input |
| | | v-model="queryParams.name" |
| | | placeholder="请è¾å
¥å¤ä»¶åç§°" |
| | | clearable |
| | | style="width: 240px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div> |
| | | <el-button type="primary" @click="addCategory">æ°å¢</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="renderTableData" |
| | | :tableLoading="loading" |
| | | :page="pagination" |
| | | :isShowPagination="true" |
| | | @pagination="handleSizeChange" |
| | | > |
| | | <template #status="{ row }"> |
| | | <el-tag type="success" size="small">{{ row.status }}</el-tag> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="renderTableData" |
| | | :tableLoading="loading" |
| | | :page="pagination" |
| | | :isShowPagination="true" |
| | | @pagination="handleSizeChange" |
| | | > |
| | | <template #status="{ row }"> |
| | | <el-tag type="success" size="small">{{ row.status }}</el-tag> |
| | | </template> |
| | | </PIMTable> |
| | | |
| | | <el-dialog title="å类管ç" v-model="dialogVisible" width="60%"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-form-item label="设å¤" prop="deviceLedgerIds"> |
| | | <el-select |
| | | v-model="form.deviceLedgerIds" |
| | | placeholder="è¯·éæ©è®¾å¤" |
| | | filterable |
| | | default-first-option |
| | | :reserve-keyword="false" |
| | | multiple |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="(item, index) in deviceOptions" |
| | | :key="index" |
| | | :label="item.deviceName" |
| | | :value="item.id" |
| | | ></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="å¤ä»¶åç§°" prop="name"> |
| | | <el-input v-model="form.name"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="å¤ä»¶ç¼å·" prop="sparePartsNo"> |
| | | <el-input v-model="form.sparePartsNo"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="æ°é" prop="quantity"> |
| | | <el-input type="number" v-model="form.quantity"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ"> |
| | | <el-option label="æ£å¸¸" value="æ£å¸¸"></el-option> |
| | | <el-option label="ç¦ç¨" value="ç¦ç¨"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æè¿°" prop="description"> |
| | | <el-input v-model="form.description"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="ä»·æ ¼" prop="price"> |
| | | <el-input-number |
| | | v-model="form.price" |
| | | placeholder="请è¾å
¥ä»·æ ¼" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | </el-form-item> |
| | | </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> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog title="å类管ç" v-model="dialogVisible" width="60%"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-form-item label="设å¤" prop="deviceLedgerIds"> |
| | | <el-select |
| | | v-model="form.deviceLedgerIds" |
| | | placeholder="è¯·éæ©è®¾å¤" |
| | | filterable |
| | | default-first-option |
| | | :reserve-keyword="false" |
| | | multiple |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="(item, index) in deviceOptions" |
| | | :key="index" |
| | | :label="item.deviceName" |
| | | :value="item.id" |
| | | ></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="å¤ä»¶åç§°" prop="name"> |
| | | <el-input v-model="form.name"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="å¤ä»¶ç¼å·" prop="sparePartsNo"> |
| | | <el-input v-model="form.sparePartsNo"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="æ°é" prop="quantity"> |
| | | <el-input type="number" v-model="form.quantity"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ"> |
| | | <el-option label="æ£å¸¸" value="æ£å¸¸"></el-option> |
| | | <el-option label="ç¦ç¨" value="ç¦ç¨"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æè¿°" prop="description"> |
| | | <el-input v-model="form.description"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="ä»·æ ¼" prop="price"> |
| | | <el-input-number |
| | | v-model="form.price" |
| | | placeholder="请è¾å
¥ä»·æ ¼" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm" :loading="formLoading">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false" :disabled="formLoading">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="å¤ä»¶é¢ç¨è®°å½" name="usage"> |
| | | <div class="search_form"> |
| | | <el-form :inline="true" :model="usageQuery" class="search-form"> |
| | | <el-form-item label="å¤ä»¶åç§°"> |
| | | <el-input v-model="usageQuery.sparePartsName" placeholder="请è¾å
¥å¤ä»¶åç§°" clearable style="width: 240px" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥æº"> |
| | | <el-select v-model="usageQuery.sourceType" placeholder="è¯·éæ©" clearable style="width: 200px"> |
| | | <el-option label="ç»´ä¿®" :value="0" /> |
| | | <el-option label="ä¿å
»" :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleUsageQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetUsageQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="rowKey" |
| | | :column="usageColumns" |
| | | :tableData="usageTableData" |
| | | :tableLoading="usageLoading" |
| | | :page="usagePagination" |
| | | :isShowPagination="true" |
| | | @pagination="handleUsagePageChange" |
| | | /> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, reactive, watch } from 'vue'; |
| | | import { ref, computed, onMounted, reactive } from 'vue'; |
| | | import { ElMessage, ElMessageBox } from 'element-plus'; |
| | | import { getSparePartsList, addSparePart, editSparePart, delSparePart } from "@/api/equipmentManagement/spareParts"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { getSparePartsUsagePage } from "@/api/equipmentManagement/sparePartsUsage"; |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | const formLoading = ref(false); |
| | | const activeTab = ref("list"); |
| | | // å¯¹è¯æ¡æ¾ç¤ºç¶æ |
| | | const dialogVisible = ref(false); |
| | | // ç¼è¾ ID |
| | |
| | | size: 10, |
| | | total: 0 |
| | | }); |
| | | |
| | | // å¤ä»¶é¢ç¨è®°å½ |
| | | const usageLoading = ref(false); |
| | | const usageQuery = reactive({ |
| | | sparePartsName: "", |
| | | sourceType: "", |
| | | }); |
| | | const usagePagination = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const usageTableData = ref([]); |
| | | const usageColumns = ref([ |
| | | { label: "æ¥æº", prop: "sourceText" }, |
| | | { label: "åæ®/è®°å½ID", prop: "sourceId" }, |
| | | { label: "设å¤åç§°", prop: "deviceName" }, |
| | | { label: "å¤ä»¶åç§°", prop: "sparePartsName" }, |
| | | { label: "é¢ç¨æ°é", prop: "quantity" }, |
| | | { label: "æä½äºº", prop: "operator" }, |
| | | { label: "æ¶é´", prop: "createTime" }, |
| | | ]); |
| | | |
| | | const handleTabChange = async (name) => { |
| | | if (name === "usage") { |
| | | usagePagination.current = 1; |
| | | await fetchUsageData(); |
| | | } |
| | | }; |
| | | const columns = ref([ |
| | | { |
| | | label: "设å¤åç§°", |
| | |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | const fetchUsageData = async () => { |
| | | usageLoading.value = true; |
| | | try { |
| | | const res = await getSparePartsUsagePage({ |
| | | current: usagePagination.current, |
| | | size: usagePagination.size, |
| | | sparePartsName: usageQuery.sparePartsName || undefined, |
| | | sourceType: usageQuery.sourceType || undefined, |
| | | }); |
| | | if (res?.code === 200) { |
| | | const records = res?.data?.records || []; |
| | | usagePagination.total = res?.data?.total || 0; |
| | | usageTableData.value = records.map((r, idx) => ({ |
| | | rowKey: r.id ?? `${usagePagination.current}-${idx}`, |
| | | ...r, |
| | | sourceText: r.sourceText === "" ? "-" : r.sourceText, |
| | | })); |
| | | } else { |
| | | usagePagination.total = 0; |
| | | usageTableData.value = []; |
| | | } |
| | | } finally { |
| | | usageLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleUsageQuery = () => { |
| | | usagePagination.current = 1; |
| | | fetchUsageData(); |
| | | }; |
| | | const resetUsageQuery = () => { |
| | | usageQuery.sparePartsName = ""; |
| | | usageQuery.sourceType = ""; |
| | | usagePagination.current = 1; |
| | | fetchUsageData(); |
| | | }; |
| | | const handleUsagePageChange = (obj) => { |
| | | usagePagination.current = obj.page; |
| | | usagePagination.size = obj.limit; |
| | | fetchUsageData(); |
| | | }; |
| | | |
| | | // æ¥è¯¢ |
| | | const handleQuery = () => { |
| | |
| | | margin-top: 20px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | padding: 16px 0; |
| | | } |
| | | |
| | | .el-table__header-wrapper th { |
| | |
| | | placeholder="请è¾å
¥ä¿å
ȍȾ" |
| | | type="text" /> |
| | | </el-form-item> |
| | | <el-form-item label="设å¤å¤ä»¶"> |
| | | <el-select v-model="form.sparePartsIds" :loading="loadingSparePartOptions" placeholder="è¯·éæ©è®¾å¤å¤ä»¶" multiple filterable> |
| | | <el-option |
| | | v-for="item in sparePartOptions" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item v-if="selectedSpareParts.length" label="é¢ç¨æ°é"> |
| | | <div style="width: 100%"> |
| | | <div |
| | | v-for="item in selectedSpareParts" |
| | | :key="item.id" |
| | | style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;" |
| | | > |
| | | <div style="flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> |
| | | {{ item.name }} |
| | | <span v-if="item.quantity !== null && item.quantity !== undefined" style="color: #909399;"> |
| | | ï¼åºåï¼{{ item.quantity }}ï¼ |
| | | </span> |
| | | </div> |
| | | <el-input-number |
| | | v-model="sparePartQtyMap[item.id]" |
| | | :min="1" |
| | | :max="item.quantity !== null && item.quantity !== undefined ? Number(item.quantity) : undefined" |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 180px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-form> |
| | | </FormDialog> |
| | | </template> |
| | |
| | | import dayjs from "dayjs"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { ElMessage } from "element-plus"; |
| | | import {computed, ref} from "vue"; |
| | | import {getSparePartsList} from "@/api/equipmentManagement/spareParts.js"; |
| | | |
| | | defineOptions({ |
| | | name: "ä¿å
»æ¨¡ææ¡", |
| | |
| | | maintenanceActuallyTime: undefined, // å®é
ä¿å
»æ¥æ |
| | | maintenanceResult: undefined, // ä¿å
ȍȾ |
| | | status: 0, // ä¿å
»ç¶æ |
| | | sparePartsIds: [], |
| | | }); |
| | | |
| | | const sparePartOptions = ref([]) |
| | | const loadingSparePartOptions = ref(true) |
| | | const sparePartQtyMap = ref({}) |
| | | |
| | | const selectedSpareParts = computed(() => { |
| | | const ids = Array.isArray(form.sparePartsIds) ? form.sparePartsIds : []; |
| | | const set = new Set(ids.map((i) => String(i))); |
| | | return (sparePartOptions.value || []).filter((p) => set.has(String(p.id))); |
| | | }); |
| | | |
| | | const setForm = (data) => { |
| | |
| | | : dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | form.maintenanceResult = data.maintenanceResult; |
| | | form.status = 1; // é»è®¤ç¶æä¸ºå®ç» |
| | | // multiple éæ©å¨è¦æ±æ°ç»ï¼å端常è¿å "1,2,3" |
| | | if (Array.isArray(data?.sparePartsIds)) { |
| | | form.sparePartsIds = data.sparePartsIds.map((v) => Number(v)).filter((v) => Number.isFinite(v)); |
| | | } else if (typeof data?.sparePartsIds === "string") { |
| | | form.sparePartsIds = data.sparePartsIds |
| | | .split(",") |
| | | .map((s) => Number(String(s).trim())) |
| | | .filter((v) => Number.isFinite(v)); |
| | | } else if (typeof data?.sparePartsIds === "number") { |
| | | form.sparePartsIds = [data.sparePartsIds]; |
| | | } else { |
| | | form.sparePartsIds = []; |
| | | } |
| | | }; |
| | | |
| | | /** |
| | |
| | | const sendForm = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const { code } = await addMaintenance({ id: planId.value, ...form }); |
| | | // é¢ç¨æ°éæ ¡éª |
| | | if (Array.isArray(form.sparePartsIds) && form.sparePartsIds.length > 0) { |
| | | for (const partId of form.sparePartsIds) { |
| | | const qty = Number(sparePartQtyMap.value?.[partId]); |
| | | if (!Number.isFinite(qty) || qty <= 0) { |
| | | proxy?.$modal?.msgError?.("请填åå¤ä»¶é¢ç¨æ°é"); |
| | | return; |
| | | } |
| | | const part = sparePartOptions.value.find((p) => String(p.id) === String(partId)); |
| | | const stock = part?.quantity; |
| | | if (stock !== null && stock !== undefined && Number.isFinite(Number(stock))) { |
| | | if (qty > Number(stock)) { |
| | | proxy?.$modal?.msgError?.(`å¤ä»¶ã${part?.name || ""}ãé¢ç¨æ°éä¸è½è¶
è¿åºåï¼${stock}ï¼`); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | const data = { |
| | | id: planId.value, |
| | | ...form, |
| | | sparePartsIds: form.sparePartsIds ? form.sparePartsIds.join(",") : "", |
| | | sparePartsQty: form.sparePartsIds |
| | | ? form.sparePartsIds.map((id) => sparePartQtyMap.value?.[id] ?? 1).join(",") |
| | | : "", |
| | | sparePartsUseList: form.sparePartsIds |
| | | ? form.sparePartsIds.map((id) => ({ id, quantity: sparePartQtyMap.value?.[id] ?? 1 })) |
| | | : [], |
| | | } |
| | | const { code } = await addMaintenance(data); |
| | | if (code == 200) { |
| | | ElMessage.success("ä¿å
»æå"); |
| | | emits("ok"); |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | } |
| | | } finally { |
| | |
| | | } |
| | | }; |
| | | |
| | | const fetchSparePartOptions = () => { |
| | | loadingSparePartOptions.value = true; |
| | | // åå¤ä»¶ç®¡ç页ä¸è´ï¼/spareParts/listPage â res.data.records |
| | | getSparePartsList({ current: 1, size: 1000 }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | sparePartOptions.value = res?.data?.records || []; |
| | | } else { |
| | | sparePartOptions.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | sparePartOptions.value = []; |
| | | }) |
| | | .finally(() => { |
| | | loadingSparePartOptions.value = false; |
| | | }); |
| | | } |
| | | |
| | | const handleCancel = () => { |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | }; |
| | | |
| | | const handleClose = () => { |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | }; |
| | | |
| | |
| | | planId.value = id; // ä¿å计åä¿å
»è®°å½çid |
| | | visible.value = true; |
| | | await nextTick(); |
| | | fetchSparePartOptions() |
| | | setForm(row); |
| | | }; |
| | | |
| | |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="项ç®"> |
| | | <el-input |
| | | v-model="form.machineryCategory" |
| | | placeholder="请è¾å
¥é¡¹ç®" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="å½å
¥äºº"> |
| | | <el-select |
| | | v-model="form.createUser" |
| | |
| | | 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: "设å¤ä¿å
»æ°å¢è®¡å", |
| | |
| | | maintenancePlanTime: undefined, // 计åä¿å
»æ¥æ |
| | | createUser: undefined, // å½å
¥äºº |
| | | status: 0, //ä¿ä¿®ç¶æ |
| | | machineryCategory: undefined, |
| | | storageBlobDTOs: [], |
| | | }); |
| | | |
| | | const setDeviceModel = (deviceId) => { |
| | |
| | | form.deviceModel = data.deviceModel; |
| | | form.createUser = Number(data.createUser); |
| | | form.status = data.status; |
| | | form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format( |
| | | "YYYY-MM-DD HH:mm:ss" |
| | | ); |
| | | form.machineryCategory = data.machineryCategory; |
| | | if (data.maintenancePlanTime) { |
| | | form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format( |
| | | "YYYY-MM-DD HH:mm:ss" |
| | | ); |
| | | } |
| | | form.storageBlobDTOs = data.storageBlobVOs || []; |
| | | }; |
| | | |
| | | // ç¨æ·å表 |
| | |
| | | <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" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥ä»»å¡åç§°" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getScheduledTableData" |
| | | /> |
| | | <el-input v-model="scheduledFilters.taskName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥ä»»å¡åç§°" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @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" |
| | | icon="Delete" |
| | | :disabled="scheduledMultipleList.length <= 0" |
| | | @click="delScheduledTaskByIds(scheduledMultipleList.map((item) => item.id))" |
| | | > |
| | | <el-button type="danger" |
| | | icon="Delete" |
| | | :disabled="scheduledMultipleList.length <= 0" |
| | | @click="delScheduledTaskByIds(scheduledMultipleList.map((item) => item.id))"> |
| | | æ¹éå é¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="scheduledColumns" |
| | | :tableData="scheduledDataList" |
| | | :page="{ |
| | | <PIMTable rowKey="id" |
| | | isSelection |
| | | :column="scheduledColumns" |
| | | :tableData="scheduledDataList" |
| | | :page="{ |
| | | current: scheduledPagination.currentPage, |
| | | size: scheduledPagination.pageSize, |
| | | total: scheduledPagination.total, |
| | | }" |
| | | @selection-change="handleScheduledSelectionChange" |
| | | @pagination="changeScheduledPage" |
| | | > |
| | | @selection-change="handleScheduledSelectionChange" |
| | | @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" |
| | | link |
| | | @click="editScheduledTask(row)" |
| | | > |
| | | <el-button type="primary" |
| | | link |
| | | @click="editScheduledTask(row)"> |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | @click="delScheduledTaskByIds(row.id)" |
| | | > |
| | | <el-button type="danger" |
| | | link |
| | | @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" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥è®¾å¤åç§°" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | <el-input v-model="filters.deviceName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥è®¾å¤åç§°" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getTableData" /> |
| | | </el-form-item> |
| | | <el-form-item label="计åä¿å
»æ¥æ"> |
| | | <el-date-picker |
| | | v-model="filters.maintenancePlanTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©è®¡åä¿å
»æ¥æ" |
| | | size="default" |
| | | @change="(date) => handleDateChange(date,2)" |
| | | /> |
| | | <el-date-picker v-model="filters.maintenancePlanTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©è®¡åä¿å
»æ¥æ" |
| | | size="default" |
| | | @change="(date) => handleDateChange(date,2)" /> |
| | | </el-form-item> |
| | | <el-form-item label="å®é
ä¿å
»æ¥æ"> |
| | | <el-date-picker |
| | | v-model="filters.maintenanceActuallyTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©å®é
ä¿å
»æ¥æ" |
| | | size="default" |
| | | @change="(date) => handleDateChange(date,1)" |
| | | /> |
| | | <el-date-picker v-model="filters.maintenanceActuallyTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©å®é
ä¿å
»æ¥æ" |
| | | size="default" |
| | | @change="(date) => handleDateChange(date,1)" /> |
| | | </el-form-item> |
| | | <el-form-item label="å®é
ä¿å
»äºº"> |
| | | <el-input |
| | | v-model="filters.maintenanceActuallyName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å®é
ä¿å
»äºº" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | <el-input v-model="filters.maintenanceActuallyName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å®é
ä¿å
»äºº" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @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" |
| | | icon="Delete" |
| | | :disabled="multipleList.length <= 0 || hasFinishedStatus" |
| | | @click="delRepairByIds(multipleList.map((item) => item.id))" |
| | | > |
| | | <el-button type="danger" |
| | | icon="Delete" |
| | | :disabled="multipleList.length <= 0 || hasFinishedStatus" |
| | | @click="delRepairByIds(multipleList.map((item) => item.id))"> |
| | | æ¹éå é¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | <PIMTable rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @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> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <!-- è¿ä¸ªåè½è·æ°å¢ä¿å
»åè½ä¸æ¨¡ä¸æ ·ï¼æå¥æä¹ï¼ --> |
| | | <!-- <el-button |
| | | @selection-change="handleSelectionChange" |
| | | @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> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <!-- è¿ä¸ªåè½è·æ°å¢ä¿å
»åè½ä¸æ¨¡ä¸æ ·ï¼æå¥æä¹ï¼ --> |
| | | <!-- <el-button |
| | | type="primary" |
| | | text |
| | | @click="addMaintain(row)" |
| | | > |
| | | æ°å¢ä¿å
» |
| | | </el-button> --> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="editPlan(row.id)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="success" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="addMaintain(row)" |
| | | > |
| | | ä¿å
» |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="delRepairByIds(row.id)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @click="openFileDialog(row)" |
| | | > |
| | | éä»¶ |
| | | </el-button> |
| | | </template> |
| | | </PIMTable> |
| | | <el-button type="primary" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="editPlan(row.id)"> |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button type="success" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="addMaintain(row)"> |
| | | ä¿å
» |
| | | </el-button> |
| | | <el-button type="danger" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="delRepairByIds(row.id)"> |
| | | å é¤ |
| | | </el-button> |
| | | <el-button type="primary" |
| | | link |
| | | @click="openFileDialog(row)"> |
| | | éä»¶ |
| | | </el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </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 { |
| | | getUpkeepPage, |
| | | delUpkeep, |
| | | deviceMaintenanceTaskList, |
| | | deviceMaintenanceTaskDel, |
| | | } from '@/api/equipmentManagement/upkeep' |
| | | import { |
| | | listMaintenanceTaskFiles, |
| | | addMaintenanceTaskFile, |
| | | delMaintenanceTaskFile, |
| | | } from '@/api/equipmentManagement/maintenanceTaskFile' |
| | | import dayjs from 'dayjs' |
| | | 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 dayjs from "dayjs"; |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const { proxy } = getCurrentInstance(); |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | |
| | | // Tabç¸å
³ |
| | | const activeTab = ref('scheduled') |
| | | // Tabç¸å
³ |
| | | const activeTab = ref("scheduled"); |
| | | |
| | | // 计åå¼¹çªæ§å¶å¨ |
| | | const planModalRef = ref() |
| | | // ä¿å
»å¼¹çªæ§å¶å¨ |
| | | const maintainModalRef = ref() |
| | | // 宿¶ä»»å¡å¼¹çªæ§å¶å¨ |
| | | const formDiaRef = ref() |
| | | // éä»¶å¼¹çª |
| | | const fileListDialogRef = ref(null) |
| | | const fileDialogVisible = ref(false) |
| | | const currentMaintenanceTaskId = ref(null) |
| | | // 计åå¼¹çªæ§å¶å¨ |
| | | const planModalRef = ref(); |
| | | // ä¿å
»å¼¹çªæ§å¶å¨ |
| | | const maintainModalRef = ref(); |
| | | // 宿¶ä»»å¡å¼¹çªæ§å¶å¨ |
| | | const formDiaRef = ref(); |
| | | // éä»¶å¼¹çª |
| | | const fileListDialogRef = ref(null); |
| | | const fileDialogVisible = ref(false); |
| | | const currentMaintenanceTaskId = ref(null); |
| | | |
| | | // ä»»å¡è®°å½tabï¼å设å¤ä¿å
»é¡µé¢ï¼ç¸å
³åé |
| | | const filters = reactive({ |
| | | deviceName: '', |
| | | maintenancePlanTime: '', |
| | | maintenanceActuallyTime: '', |
| | | maintenanceActuallyName: '', |
| | | }) |
| | | // ä»»å¡è®°å½tabï¼å设å¤ä¿å
»é¡µé¢ï¼ç¸å
³åé |
| | | const filters = reactive({ |
| | | deviceName: "", |
| | | maintenancePlanTime: "", |
| | | maintenanceActuallyTime: "", |
| | | maintenanceActuallyName: "", |
| | | }); |
| | | |
| | | const dataList = ref([]) |
| | | const pagination = ref({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }) |
| | | const multipleList = ref([]) |
| | | const dataList = ref([]); |
| | | const pagination = ref({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | const multipleList = ref([]); |
| | | |
| | | // 宿¶ä»»å¡ç®¡çtabç¸å
³åé |
| | | const scheduledFilters = reactive({ |
| | | taskName: '', |
| | | status: '', |
| | | }) |
| | | // 宿¶ä»»å¡ç®¡çtabç¸å
³åé |
| | | const scheduledFilters = reactive({ |
| | | taskName: "", |
| | | status: "", |
| | | }); |
| | | |
| | | const scheduledDataList = ref([]) |
| | | const scheduledPagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }) |
| | | const scheduledMultipleList = ref([]) |
| | | const scheduledDataList = ref([]); |
| | | const scheduledPagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | const scheduledMultipleList = ref([]); |
| | | |
| | | // 宿¶ä»»å¡ç®¡çè¡¨æ ¼åé
ç½® |
| | | const scheduledColumns = ref([ |
| | | { prop: "taskName", label: "设å¤åç§°"}, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | | minWidth: 150, |
| | | // PIMTable 使ç¨çæ¯ formatDataï¼è䏿¯ Element-Plus ç formatter |
| | | formatData: (cell) => ({ |
| | | DAILY: "æ¯æ¥", |
| | | WEEKLY: "æ¯å¨", |
| | | MONTHLY: "æ¯æ", |
| | | QUARTERLY: "å£åº¦" |
| | | }[cell] || "") |
| | | }, |
| | | { |
| | | prop: "frequencyDetail", |
| | | label: "å¼å§æ¥æä¸æ¶é´", |
| | | minWidth: 150, |
| | | // åæ ·æ¹ç¨ formatDataï¼PIMTable å
é¨ä¼æåå
æ ¼å¼ä¼ è¿æ¥ |
| | | formatData: (cell) => { |
| | | if (typeof cell !== 'string') return ''; |
| | | let val = cell; |
| | | const replacements = { |
| | | MON: 'å¨ä¸', |
| | | TUE: 'å¨äº', |
| | | WED: 'å¨ä¸', |
| | | THU: 'å¨å', |
| | | FRI: 'å¨äº', |
| | | SAT: 'å¨å
', |
| | | SUN: '卿¥' |
| | | }; |
| | | // ä½¿ç¨æ£å䏿¬¡æ§æ¿æ¢ææå¹é
项 |
| | | return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]); |
| | | } |
| | | }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "registrationDate", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | { |
| | | fixed: "right", |
| | | label: "æä½", |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "200px", |
| | | }, |
| | | ]) |
| | | // 宿¶ä»»å¡ç®¡çè¡¨æ ¼åé
ç½® |
| | | const scheduledColumns = ref([ |
| | | { prop: "taskName", label: "设å¤åç§°" }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | | minWidth: 150, |
| | | // PIMTable 使ç¨çæ¯ formatDataï¼è䏿¯ Element-Plus ç formatter |
| | | formatData: cell => |
| | | ({ |
| | | DAILY: "æ¯æ¥", |
| | | WEEKLY: "æ¯å¨", |
| | | MONTHLY: "æ¯æ", |
| | | QUARTERLY: "å£åº¦", |
| | | }[cell] || ""), |
| | | }, |
| | | { |
| | | prop: "frequencyDetail", |
| | | label: "å¼å§æ¥æä¸æ¶é´", |
| | | minWidth: 150, |
| | | // åæ ·æ¹ç¨ formatDataï¼PIMTable å
é¨ä¼æåå
æ ¼å¼ä¼ è¿æ¥ |
| | | formatData: cell => { |
| | | if (typeof cell !== "string") return ""; |
| | | let val = cell; |
| | | const replacements = { |
| | | MON: "å¨ä¸", |
| | | TUE: "å¨äº", |
| | | WED: "å¨ä¸", |
| | | THU: "å¨å", |
| | | FRI: "å¨äº", |
| | | SAT: "å¨å
", |
| | | SUN: "卿¥", |
| | | }; |
| | | // ä½¿ç¨æ£å䏿¬¡æ§æ¿æ¢ææå¹é
项 |
| | | return val.replace( |
| | | /MON|TUE|WED|THU|FRI|SAT|SUN/g, |
| | | match => replacements[match] |
| | | ); |
| | | }, |
| | | }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "registrationDate", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | { |
| | | fixed: "right", |
| | | label: "æä½", |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "200px", |
| | | }, |
| | | ]); |
| | | |
| | | // ä»»å¡è®°å½è¡¨æ ¼åé
ç½®ï¼å设å¤ä¿å
»è¡¨æ ¼åï¼ |
| | | const columns = ref([ |
| | | { |
| | | label: "设å¤åç§°", |
| | | align: "center", |
| | | prop: "deviceName", |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | align: "center", |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "计åä¿å
»æ¥æ", |
| | | align: "center", |
| | | prop: "maintenancePlanTime", |
| | | formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"), |
| | | }, |
| | | { |
| | | label: "å½å
¥äºº", |
| | | align: "center", |
| | | prop: "createUserName", |
| | | }, |
| | | // { |
| | | // label: "å½å
¥æ¥æ", |
| | | // align: "center", |
| | | // prop: "createTime", |
| | | // formatData: (cell) => dayjs(cell).format("YYYY-MM-DD HH:mm:ss"), |
| | | // width: 200, |
| | | // }, |
| | | { |
| | | label: "å®é
ä¿å
»äºº", |
| | | align: "center", |
| | | prop: "maintenanceActuallyName", |
| | | }, |
| | | { |
| | | label: "å®é
ä¿å
»æ¥æ", |
| | | align: "center", |
| | | prop: "maintenanceActuallyTime", |
| | | formatData: (cell) => |
| | | cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-", |
| | | }, |
| | | { |
| | | label: "ä¿å
ȍȾ", |
| | | align: "center", |
| | | prop: "maintenanceResult", |
| | | dataType: "slot", |
| | | slot: "maintenanceResultRef", |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | align: "center", |
| | | prop: "status", |
| | | dataType: "slot", |
| | | slot: "statusRef", |
| | | }, |
| | | { |
| | | fixed: "right", |
| | | label: "æä½", |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "350px", |
| | | }, |
| | | ]) |
| | | // ä»»å¡è®°å½è¡¨æ ¼åé
ç½®ï¼å设å¤ä¿å
»è¡¨æ ¼åï¼ |
| | | const columns = ref([ |
| | | { |
| | | label: "设å¤åç§°", |
| | | align: "center", |
| | | prop: "deviceName", |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | align: "center", |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "计åä¿å
»æ¥æ", |
| | | align: "center", |
| | | prop: "maintenancePlanTime", |
| | | formatData: cell => dayjs(cell).format("YYYY-MM-DD"), |
| | | }, |
| | | { |
| | | label: "å½å
¥äºº", |
| | | align: "center", |
| | | prop: "createUserName", |
| | | }, |
| | | { |
| | | label: "项ç®", |
| | | align: "center", |
| | | prop: "machineryCategory", |
| | | }, |
| | | // { |
| | | // label: "å½å
¥æ¥æ", |
| | | // align: "center", |
| | | // prop: "createTime", |
| | | // formatData: (cell) => dayjs(cell).format("YYYY-MM-DD HH:mm:ss"), |
| | | // width: 200, |
| | | // }, |
| | | { |
| | | label: "å®é
ä¿å
»äºº", |
| | | align: "center", |
| | | prop: "maintenanceActuallyName", |
| | | }, |
| | | { |
| | | label: "å®é
ä¿å
»æ¥æ", |
| | | align: "center", |
| | | prop: "maintenanceActuallyTime", |
| | | formatData: cell => |
| | | cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-", |
| | | }, |
| | | { |
| | | label: "ä¿å
ȍȾ", |
| | | align: "center", |
| | | prop: "maintenanceResult", |
| | | dataType: "slot", |
| | | slot: "maintenanceResultRef", |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | align: "center", |
| | | prop: "status", |
| | | dataType: "slot", |
| | | slot: "statusRef", |
| | | }, |
| | | { |
| | | fixed: "right", |
| | | label: "æä½", |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "350px", |
| | | }, |
| | | ]); |
| | | |
| | | // Tab忢å¤ç |
| | | const handleTabChange = (tabName) => { |
| | | if (tabName === 'record') { |
| | | getTableData() |
| | | } else if (tabName === 'scheduled') { |
| | | getScheduledTableData() |
| | | } |
| | | } |
| | | |
| | | // 宿¶ä»»å¡ç®¡çç¸å
³æ¹æ³ |
| | | const getScheduledTableData = async () => { |
| | | try { |
| | | const params = { |
| | | current: scheduledPagination.currentPage, |
| | | size: scheduledPagination.pageSize, |
| | | taskName: scheduledFilters.taskName || undefined, |
| | | status: scheduledFilters.status || undefined, |
| | | // Tab忢å¤ç |
| | | const handleTabChange = tabName => { |
| | | if (tabName === "record") { |
| | | getTableData(); |
| | | } else if (tabName === "scheduled") { |
| | | getScheduledTableData(); |
| | | } |
| | | const { code, data } = await deviceMaintenanceTaskList(params) |
| | | if (code === 200) { |
| | | scheduledDataList.value = data?.records || [] |
| | | scheduledPagination.total = data?.total || 0 |
| | | }; |
| | | |
| | | // 宿¶ä»»å¡ç®¡çç¸å
³æ¹æ³ |
| | | const getScheduledTableData = async () => { |
| | | try { |
| | | const params = { |
| | | current: scheduledPagination.currentPage, |
| | | size: scheduledPagination.pageSize, |
| | | taskName: scheduledFilters.taskName || undefined, |
| | | status: scheduledFilters.status || undefined, |
| | | }; |
| | | const { code, data } = await deviceMaintenanceTaskList(params); |
| | | if (code === 200) { |
| | | scheduledDataList.value = data?.records || []; |
| | | scheduledPagination.total = data?.total || 0; |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("è·å宿¶ä»»å¡å表失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('è·å宿¶ä»»å¡å表失败') |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const resetScheduledFilters = () => { |
| | | scheduledFilters.taskName = '' |
| | | scheduledFilters.status = '' |
| | | getScheduledTableData() |
| | | } |
| | | const resetScheduledFilters = () => { |
| | | 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'); |
| | | }); |
| | | } |
| | | const addScheduledTask = () => { |
| | | nextTick(() => { |
| | | formDiaRef.value?.openDialog("add"); |
| | | }); |
| | | }; |
| | | |
| | | const editScheduledTask = (row) => { |
| | | if (row) { |
| | | nextTick(() => { |
| | | formDiaRef.value?.openDialog('edit', row); |
| | | }); |
| | | } |
| | | } |
| | | const editScheduledTask = row => { |
| | | if (row) { |
| | | nextTick(() => { |
| | | formDiaRef.value?.openDialog("edit", row); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const delScheduledTaskByIds = async (ids) => { |
| | | try { |
| | | await ElMessageBox.confirm('ç¡®å®å é¤éä¸ç宿¶ä»»å¡åï¼', 'æç¤º', { |
| | | type: 'warning', |
| | | const delScheduledTaskByIds = async ids => { |
| | | try { |
| | | await ElMessageBox.confirm("ç¡®å®å é¤éä¸ç宿¶ä»»å¡åï¼", "æç¤º", { |
| | | type: "warning", |
| | | }); |
| | | const payload = Array.isArray(ids) ? ids : [ids]; |
| | | await deviceMaintenanceTaskDel(payload); |
| | | ElMessage.success("å é¤å®æ¶ä»»å¡æå"); |
| | | getScheduledTableData(); |
| | | } catch (error) { |
| | | // ç¨æ·åæ¶å é¤ |
| | | } |
| | | }; |
| | | |
| | | const handleScheduledOut = () => { |
| | | ElMessage.info("导åºå®æ¶ä»»å¡åè½å¾
å®ç°"); |
| | | }; |
| | | |
| | | // ä»»å¡è®°å½ç¸å
³æ¹æ³ï¼å设å¤ä¿å
»é¡µé¢æ¹æ³ï¼ |
| | | const getTableData = async () => { |
| | | try { |
| | | const params = { |
| | | 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, |
| | | maintenanceActuallyName: filters.maintenanceActuallyName || undefined, |
| | | }; |
| | | |
| | | const { code, data } = await getUpkeepPage(params); |
| | | if (code === 200) { |
| | | 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(); |
| | | }; |
| | | |
| | | const handleSelectionChange = selection => { |
| | | multipleList.value = selection; |
| | | }; |
| | | |
| | | // æ£æ¥éä¸çè®°å½ä¸æ¯å¦æå®ç»ç¶æç |
| | | const hasFinishedStatus = computed(() => { |
| | | return multipleList.value.some(item => item.status === 1); |
| | | }); |
| | | |
| | | const changePage = page => { |
| | | pagination.value.currentPage = page.page; |
| | | pagination.value.pageSize = page.limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const addMaintain = row => { |
| | | maintainModalRef.value.open(row.id, row); |
| | | }; |
| | | |
| | | const addPlan = () => { |
| | | planModalRef.value.openModal(); |
| | | }; |
| | | |
| | | const editPlan = id => { |
| | | planModalRef.value.openEdit(id); |
| | | }; |
| | | |
| | | const delRepairByIds = async ids => { |
| | | // æ£æ¥æ¯å¦æå®ç»ç¶æçè®°å½ |
| | | const hasFinished = multipleList.value.some(item => item.status === 1); |
| | | if (hasFinished) { |
| | | ElMessage.warning("ä¸è½å é¤ç¶æä¸ºå®ç»çè®°å½"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | await ElMessageBox.confirm("确认å é¤ä¿å
»æ°æ®, æ¤æä½ä¸å¯é?", "è¦å", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }); |
| | | |
| | | const { code } = await delUpkeep(ids); |
| | | if (code === 200) { |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | } |
| | | } catch (error) { |
| | | // ç¨æ·åæ¶å é¤ |
| | | } |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | const payload = Array.isArray(ids) ? ids : [ids] |
| | | await deviceMaintenanceTaskDel(payload) |
| | | ElMessage.success('å é¤å®æ¶ä»»å¡æå') |
| | | getScheduledTableData() |
| | | } catch (error) { |
| | | // ç¨æ·åæ¶å é¤ |
| | | } |
| | | } |
| | | .then(() => { |
| | | proxy.download("/device/maintenance/export", {}, "设å¤ä¿å
».xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.info("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | const handleScheduledOut = () => { |
| | | ElMessage.info('导åºå®æ¶ä»»å¡åè½å¾
å®ç°') |
| | | } |
| | | |
| | | // ä»»å¡è®°å½ç¸å
³æ¹æ³ï¼å设å¤ä¿å
»é¡µé¢æ¹æ³ï¼ |
| | | const getTableData = async () => { |
| | | try { |
| | | const params = { |
| | | 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, |
| | | maintenanceActuallyName: filters.maintenanceActuallyName || undefined, |
| | | const handleDateChange = (date, type) => { |
| | | if (type === 1) { |
| | | filters.maintenanceActuallyTime = date |
| | | ? dayjs(date).format("YYYY-MM-DD") |
| | | : ""; |
| | | } else { |
| | | filters.maintenancePlanTime = date ? dayjs(date).format("YYYY-MM-DD") : ""; |
| | | } |
| | | getTableData(); |
| | | }; |
| | | |
| | | const { code, data } = await getUpkeepPage(params) |
| | | if (code === 200) { |
| | | dataList.value = data.records |
| | | pagination.value.total = data.total |
| | | // æå¼éä»¶å¼¹çª |
| | | const openFileDialog = async row => { |
| | | currentMaintenanceTaskId.value = row.id; |
| | | fileDialogVisible.value = true; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // æ ¹æ®é»è®¤æ¿æ´»ç Tab è°ç¨å¯¹åºçæ¥è¯¢æ¥å£ |
| | | if (activeTab.value === "scheduled") { |
| | | getScheduledTableData(); |
| | | } else { |
| | | getTableData(); |
| | | } |
| | | } catch (error) { |
| | | console.log(error); |
| | | |
| | | } |
| | | } |
| | | |
| | | const resetFilters = () => { |
| | | filters.deviceName = '' |
| | | filters.maintenancePlanTime = '' |
| | | filters.maintenanceActuallyTime = '' |
| | | filters.maintenanceActuallyName = '' |
| | | getTableData() |
| | | } |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | multipleList.value = selection |
| | | } |
| | | |
| | | // æ£æ¥éä¸çè®°å½ä¸æ¯å¦æå®ç»ç¶æç |
| | | const hasFinishedStatus = computed(() => { |
| | | return multipleList.value.some(item => item.status === 1) |
| | | }) |
| | | |
| | | const changePage = (page) => { |
| | | pagination.value.currentPage = page.page |
| | | pagination.value.pageSize = page.limit |
| | | getTableData() |
| | | } |
| | | |
| | | const addMaintain = (row) => { |
| | | maintainModalRef.value.open(row.id, row) |
| | | } |
| | | |
| | | const addPlan = () => { |
| | | planModalRef.value.openModal() |
| | | } |
| | | |
| | | const editPlan = (id) => { |
| | | planModalRef.value.openEdit(id) |
| | | } |
| | | |
| | | const delRepairByIds = async (ids) => { |
| | | // æ£æ¥æ¯å¦æå®ç»ç¶æçè®°å½ |
| | | const hasFinished = multipleList.value.some(item => item.status === 1) |
| | | if (hasFinished) { |
| | | ElMessage.warning('ä¸è½å é¤ç¶æä¸ºå®ç»çè®°å½') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | await ElMessageBox.confirm('确认å é¤ä¿å
»æ°æ®, æ¤æä½ä¸å¯é?', 'è¦å', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }) |
| | | |
| | | const { code } = await delUpkeep(ids) |
| | | if (code === 200) { |
| | | ElMessage.success('å 餿å') |
| | | getTableData() |
| | | } |
| | | } catch (error) { |
| | | // ç¨æ·åæ¶å é¤ |
| | | } |
| | | } |
| | | |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm('éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼', '导åº', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }) |
| | | .then(() => { |
| | | proxy.download('/device/maintenance/export', {}, '设å¤ä¿å
».xlsx') |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.info('已忶') |
| | | }) |
| | | } |
| | | |
| | | const handleDateChange = (date, type) => { |
| | | if (type === 1) { |
| | | filters.maintenanceActuallyTime = date ? dayjs(date).format('YYYY-MM-DD') : '' |
| | | } else { |
| | | 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('è·åéä»¶å表失败') |
| | | } |
| | | } |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | 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 |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | // æ ¹æ®é»è®¤æ¿æ´»ç Tab è°ç¨å¯¹åºçæ¥è¯¢æ¥å£ |
| | | if (activeTab.value === 'scheduled') { |
| | | getScheduledTableData() |
| | | } else { |
| | | getTableData() |
| | | } |
| | | }) |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | </style> |
| | | |
| | | |
| | |
| | | |
| | | <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-tree> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="keepVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="keepVisible = false" >ç¡® å®</el-button> |
| | | <el-button @click="keepVisible = false">å æ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </el-row> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="warehouseVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="confirmWarehouse" :loading="upLoadWarehouse">ç¡® å®</el-button> |
| | | <el-button @click="warehouseVisible = false">å æ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </el-row> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="shelvesVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="confirmShelves" :loading="upLoadShelves">ç¡® å®</el-button> |
| | | <el-button @click="shelvesVisible = false">å æ¶</el-button> |
| | | </span> |
| | | </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] = ""; |
| | | }); |
| | | // 设置é»è®¤ç¶æ |
| | | borrowForm.borrowStatus = "åé
"; |
| | | currentEditDocName.value = ''; // æ¸
空ç¼è¾æ¶çææ¡£åç§° |
| | | // 设置é»è®¤ç¶æ |
| | | borrowForm.borrowStatus = "åé
"; |
| | | // 设置å½åæ¥æä¸ºåé
æ¥æ |
| | | borrowForm.borrowDate = new Date().toISOString().split('T')[0]; |
| | | } |
| | |
| | | proxy.$refs.borrowFormRef.resetFields(); |
| | | borrowDia.value = false; |
| | | scanContent.value = ''; // æ¸
空æ«ç å
容 |
| | | currentEditDocName.value = ''; // æ¸
空ç¼è¾æ¶çææ¡£åç§° |
| | | }; |
| | | |
| | | // æäº¤åé
表å |
| | |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | text-align: center; |
| | | } |
| | | |
| | | :deep(.el-form-item__label) { |
| | |
| | | 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; |
| | | } |
| | | } |
| | | }; |
| | |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | text-align: center; |
| | | } |
| | | |
| | | .operation-column { |
| | |
| | | <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 = ''; // æ¸
空ç¼è¾æ¶çææ¡£åç§° |
| | | }; |
| | | |
| | | // æäº¤å½è¿è¡¨å |
| | |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | text-align: center; |
| | | } |
| | | |
| | | :deep(.el-form-item__label) { |
| | |
| | | |
| | | /* 页é¢èæ¯ */ |
| | | main { |
| | | background: #f5f5f5; |
| | | padding: 0; |
| | | margin: 0 -20px; |
| | | padding: 0 20px 20px; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @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" 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 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({ |
| | | 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", slot: "category" }, |
| | | { label: "è§æ ¼åå·", prop: "specification", width: "120" }, |
| | | { label: "èµäº§åå¼", prop: "originalValue", slot: "originalValue" }, |
| | | { label: "ç´¯è®¡ææ§", prop: "accumulatedDepreciation", slot: "accumulatedDepreciation" }, |
| | | { label: "èµäº§åå¼", prop: "netValue", slot: "netValue" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { 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 form = reactive({ |
| | | assetCode: "", |
| | | assetName: "", |
| | | category: "", |
| | | specification: "", |
| | | purchaseDate: "", |
| | | originalValue: 0, |
| | | usefulLife: 5, |
| | | residualRate: 5, |
| | | accumulatedDepreciation: 0, |
| | | netValue: 0, |
| | | location: "", |
| | | department: "", |
| | | keeper: "", |
| | | status: "in_use", |
| | | remark: "", |
| | | }); |
| | | |
| | | 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 mockData = [ |
| | | { id: 1, assetCode: "GD2024001", assetName: "åå
¬çµè", category: "electronic", specification: "èæ³ThinkPad X1", purchaseDate: "2023-01-15", originalValue: 8000, usefulLife: 5, residualRate: 5, accumulatedDepreciation: 1520, netValue: 6480, location: "åå
¬å®¤", department: "è´¢å¡é¨", keeper: "å¼ ä¸", status: "in_use", remark: "" }, |
| | | { id: 2, assetCode: "GD2024002", assetName: "æå°æº", category: "electronic", specification: "æ æ®M479fdw", purchaseDate: "2023-03-20", originalValue: 3500, usefulLife: 5, residualRate: 5, accumulatedDepreciation: 532, netValue: 2968, location: "æå°å®¤", department: "è¡æ¿é¨", keeper: "æå", status: "in_use", remark: "" }, |
| | | { id: 3, assetCode: "GD2024003", assetName: "åå
¬æ¡æ¤
", category: "furniture", specification: "宿¨åå
¬æ¡", purchaseDate: "2023-06-10", originalValue: 2500, usefulLife: 10, residualRate: 5, accumulatedDepreciation: 118.75, netValue: 2381.25, location: "åå
¬å®¤", department: "éå®é¨", keeper: "çäº", status: "in_use", remark: "" }, |
| | | { id: 4, assetCode: "GD2024004", assetName: "åå¡è½¦", category: "vehicle", specification: "å«å
GL8", purchaseDate: "2022-08-01", originalValue: 280000, usefulLife: 10, residualRate: 5, accumulatedDepreciation: 53200, netValue: 226800, location: "å车åº", department: "è¡æ¿é¨", keeper: "èµµå
", status: "in_use", remark: "" }, |
| | | ]; |
| | | |
| | | 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 map = { in_use: "å¨ç¨", idle: "é²ç½®", scrapped: "æ¥åº" }; |
| | | return map[status] || status; |
| | | }; |
| | | |
| | | const getStatusType = (status) => { |
| | | const map = { in_use: "success", idle: "warning", scrapped: "info" }; |
| | | return map[status] || ""; |
| | | }; |
| | | |
| | | const calculateNetValue = () => { |
| | | form.netValue = Number((form.originalValue - form.accumulatedDepreciation).toFixed(2)); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.assetCode) { |
| | | result = result.filter(item => item.assetCode.includes(filters.assetCode)); |
| | | } |
| | | if (filters.assetName) { |
| | | result = result.filter(item => item.assetName.includes(filters.assetName)); |
| | | } |
| | | if (filters.category) { |
| | | result = result.filter(item => item.category === filters.category); |
| | | } |
| | | 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.assetCode = ""; |
| | | filters.assetName = ""; |
| | | filters.category = ""; |
| | | 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, { |
| | | assetCode: "GD" + Date.now().toString().slice(-8), |
| | | assetName: "", |
| | | category: "", |
| | | specification: "", |
| | | purchaseDate: new Date().toISOString().split('T')[0], |
| | | originalValue: 0, |
| | | usefulLife: 5, |
| | | residualRate: 5, |
| | | accumulatedDepreciation: 0, |
| | | netValue: 0, |
| | | location: "", |
| | | department: "", |
| | | keeper: "", |
| | | status: "in_use", |
| | | 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.assetName}`); |
| | | }; |
| | | |
| | | 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 handleDepreciation = () => { |
| | | ElMessageBox.confirm("确认è¿è¡æ¬æææ§è®¡æåï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(() => { |
| | | mockData.forEach(item => { |
| | | if (item.status === "in_use") { |
| | | const monthlyDepreciation = (item.originalValue * (1 - item.residualRate / 100)) / (item.usefulLife * 12); |
| | | item.accumulatedDepreciation = Number((item.accumulatedDepreciation + monthlyDepreciation).toFixed(2)); |
| | | item.netValue = Number((item.originalValue - item.accumulatedDepreciation).toFixed(2)); |
| | | } |
| | | }); |
| | | ElMessage.success("ææ§è®¡æå®æ"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | calculateNetValue(); |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form }); |
| | | 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; |
| | | |
| | | > 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" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @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" 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 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({ |
| | | 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", slot: "category" }, |
| | | { label: "è¯ä¹¦ç¼å·", prop: "certificateNo", width: "150" }, |
| | | { label: "èµäº§åå¼", prop: "originalValue", slot: "originalValue" }, |
| | | { label: "累计æé", prop: "accumulatedAmortization", slot: "accumulatedAmortization" }, |
| | | { label: "èµäº§åå¼", prop: "netValue", slot: "netValue" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { 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 form = reactive({ |
| | | assetCode: "", |
| | | assetName: "", |
| | | category: "", |
| | | certificateNo: "", |
| | | acquisitionDate: "", |
| | | originalValue: 0, |
| | | amortizationPeriod: 10, |
| | | residualRate: 0, |
| | | accumulatedAmortization: 0, |
| | | netValue: 0, |
| | | validityDate: "", |
| | | status: "in_use", |
| | | description: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | 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 mockData = [ |
| | | { id: 1, assetCode: "WX2024001", assetName: "ERP软件许å¯", category: "software", certificateNo: "SW-2023-001", acquisitionDate: "2023-01-01", originalValue: 50000, amortizationPeriod: 10, residualRate: 0, accumulatedAmortization: 5000, netValue: 45000, validityDate: "2033-01-01", status: "in_use", description: "ä¼ä¸èµæºè®¡å管çç³»ç»", remark: "" }, |
| | | { id: 2, assetCode: "WX2024002", assetName: "åæä¸å©", category: "patent", certificateNo: "ZL202210123456.7", acquisitionDate: "2022-06-15", originalValue: 100000, amortizationPeriod: 20, residualRate: 0, accumulatedAmortization: 3750, netValue: 96250, validityDate: "2042-06-15", status: "in_use", description: "ä¸ç§æ°åç产工èº", remark: "" }, |
| | | { id: 3, assetCode: "WX2024003", assetName: "åæ æ", category: "trademark", certificateNo: "TM-2023-008", acquisitionDate: "2023-03-10", originalValue: 20000, amortizationPeriod: 10, residualRate: 0, accumulatedAmortization: 1500, netValue: 18500, validityDate: "2033-03-10", status: "in_use", description: "å
¬å¸åçåæ ", remark: "" }, |
| | | { id: 4, assetCode: "WX2024004", assetName: "åå°ä½¿ç¨æ", category: "land", certificateNo: "åå½ç¨(2023)第001å·", acquisitionDate: "2023-07-01", originalValue: 500000, amortizationPeriod: 50, residualRate: 0, accumulatedAmortization: 5000, netValue: 495000, validityDate: "2073-07-01", status: "in_use", description: "å·¥ä¸ç¨å°ä½¿ç¨æ", remark: "" }, |
| | | ]; |
| | | |
| | | 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 map = { in_use: "å¨ç¨", idle: "é²ç½®", amortized: "å·²æé宿¯" }; |
| | | return map[status] || status; |
| | | }; |
| | | |
| | | const getStatusType = (status) => { |
| | | const map = { in_use: "success", idle: "warning", amortized: "info" }; |
| | | return map[status] || ""; |
| | | }; |
| | | |
| | | const calculateNetValue = () => { |
| | | form.netValue = Number((form.originalValue - form.accumulatedAmortization).toFixed(2)); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.assetCode) { |
| | | result = result.filter(item => item.assetCode.includes(filters.assetCode)); |
| | | } |
| | | if (filters.assetName) { |
| | | result = result.filter(item => item.assetName.includes(filters.assetName)); |
| | | } |
| | | if (filters.category) { |
| | | result = result.filter(item => item.category === filters.category); |
| | | } |
| | | 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.assetCode = ""; |
| | | filters.assetName = ""; |
| | | filters.category = ""; |
| | | 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, { |
| | | assetCode: "WX" + Date.now().toString().slice(-8), |
| | | assetName: "", |
| | | category: "", |
| | | certificateNo: "", |
| | | acquisitionDate: new Date().toISOString().split('T')[0], |
| | | originalValue: 0, |
| | | amortizationPeriod: 10, |
| | | residualRate: 0, |
| | | accumulatedAmortization: 0, |
| | | netValue: 0, |
| | | validityDate: "", |
| | | status: "in_use", |
| | | description: "", |
| | | 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.assetName}`); |
| | | }; |
| | | |
| | | 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 handleAmortization = () => { |
| | | ElMessageBox.confirm("确认è¿è¡æ¬ææé计æåï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(() => { |
| | | mockData.forEach(item => { |
| | | if (item.status === "in_use") { |
| | | const monthlyAmortization = (item.originalValue * (1 - item.residualRate / 100)) / (item.amortizationPeriod * 12); |
| | | item.accumulatedAmortization = Number((item.accumulatedAmortization + monthlyAmortization).toFixed(2)); |
| | | item.netValue = Number((item.originalValue - item.accumulatedAmortization).toFixed(2)); |
| | | if (item.netValue <= 0) { |
| | | item.status = "amortized"; |
| | | item.netValue = 0; |
| | | } |
| | | } |
| | | }); |
| | | ElMessage.success("æé计æå®æ"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | calculateNetValue(); |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form }); |
| | | 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; |
| | | |
| | | > 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; |
| | | } |
| | | } 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; |
| | | } |
| | | }; |
| | | recordId.value = row.id |
| | | fileDialogVisible.value = true |
| | | } |
| | | |
| | | 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="asset" /> |
| | | <el-option label="è´åºç±»" value="liability" /> |
| | | <el-option label="æçç±»" value="equity" /> |
| | | <el-option label="ææ¬ç±»" value="cost" /> |
| | | <el-option label="æçç±»" value="profit_loss" /> |
| | | </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> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | <template #subjectType="{ row }"> |
| | | <el-tag :type="getSubjectTypeType(row.subjectType)">{{ getSubjectTypeLabel(row.subjectType) }}</el-tag> |
| | | </template> |
| | | <template #balanceDirection="{ row }"> |
| | | <el-tag :type="row.balanceDirection === 'debit' ? 'success' : 'danger'"> |
| | | {{ row.balanceDirection === 'debit' ? 'åæ¹' : 'è´·æ¹' }} |
| | | </el-tag> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="row.status === 'active' ? 'success' : 'info'"> |
| | | {{ row.status === 'active' ? 'å¯ç¨' : 'ç¦ç¨' }} |
| | | </el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <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="600px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <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="asset" /> |
| | | <el-option label="è´åºç±»" value="liability" /> |
| | | <el-option label="æçç±»" value="equity" /> |
| | | <el-option label="ææ¬ç±»" value="cost" /> |
| | | <el-option label="æçç±»" value="profit_loss" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä½é¢æ¹å" prop="balanceDirection"> |
| | | <el-radio-group v-model="form.balanceDirection"> |
| | | <el-radio label="debit">åæ¹</el-radio> |
| | | <el-radio label="credit">è´·æ¹</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio label="active">å¯ç¨</el-radio> |
| | | <el-radio label="inactive">ç¦ç¨</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 } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "æ»å¸ç§ç®", |
| | | }); |
| | | |
| | | 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", slot: "subjectType" }, |
| | | { label: "ä½é¢æ¹å", prop: "balanceDirection", slot: "balanceDirection" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { label: "夿³¨", prop: "remark", showOverflowTooltip: true }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "150", 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 form = reactive({ |
| | | subjectCode: "", |
| | | subjectName: "", |
| | | subjectType: "", |
| | | balanceDirection: "debit", |
| | | status: "active", |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | subjectCode: [{ required: true, message: "请è¾å
¥ç§ç®ç¼ç ", trigger: "blur" }], |
| | | subjectName: [{ required: true, message: "请è¾å
¥ç§ç®åç§°", trigger: "blur" }], |
| | | subjectType: [{ required: true, message: "è¯·éæ©ç§ç®ç±»å", trigger: "change" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, subjectCode: "1001", subjectName: "åºåç°é", subjectType: "asset", balanceDirection: "debit", status: "active", remark: "" }, |
| | | { id: 2, subjectCode: "1002", subjectName: "é¶è¡å款", subjectType: "asset", balanceDirection: "debit", status: "active", remark: "" }, |
| | | { id: 3, subjectCode: "1122", subjectName: "åºæ¶è´¦æ¬¾", subjectType: "asset", balanceDirection: "debit", status: "active", remark: "" }, |
| | | { id: 4, subjectCode: "2202", subjectName: "åºä»è´¦æ¬¾", subjectType: "liability", balanceDirection: "credit", status: "active", remark: "" }, |
| | | { id: 5, subjectCode: "4001", subjectName: "宿¶èµæ¬", subjectType: "equity", balanceDirection: "credit", status: "active", remark: "" }, |
| | | { id: 6, subjectCode: "5001", subjectName: "çäº§ææ¬", subjectType: "cost", balanceDirection: "debit", status: "active", remark: "" }, |
| | | { id: 7, subjectCode: "6001", subjectName: "主è¥ä¸å¡æ¶å
¥", subjectType: "profit_loss", balanceDirection: "credit", status: "active", remark: "" }, |
| | | { id: 8, subjectCode: "6401", subjectName: "主è¥ä¸å¡ææ¬", subjectType: "profit_loss", balanceDirection: "debit", status: "active", remark: "" }, |
| | | ]; |
| | | |
| | | const getSubjectTypeLabel = (type) => { |
| | | const map = { |
| | | asset: "èµäº§ç±»", |
| | | liability: "è´åºç±»", |
| | | equity: "æçç±»", |
| | | cost: "ææ¬ç±»", |
| | | profit_loss: "æçç±»", |
| | | }; |
| | | return map[type] || type; |
| | | }; |
| | | |
| | | const getSubjectTypeType = (type) => { |
| | | const map = { |
| | | asset: "success", |
| | | liability: "danger", |
| | | equity: "warning", |
| | | cost: "info", |
| | | profit_loss: "primary", |
| | | }; |
| | | return map[type] || ""; |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.subjectCode) { |
| | | result = result.filter(item => item.subjectCode.includes(filters.subjectCode)); |
| | | } |
| | | if (filters.subjectName) { |
| | | result = result.filter(item => item.subjectName.includes(filters.subjectName)); |
| | | } |
| | | if (filters.subjectType) { |
| | | result = result.filter(item => item.subjectType === filters.subjectType); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.subjectCode = ""; |
| | | filters.subjectName = ""; |
| | | filters.subjectType = ""; |
| | | 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, { |
| | | subjectCode: "", |
| | | subjectName: "", |
| | | subjectType: "", |
| | | balanceDirection: "debit", |
| | | status: "active", |
| | | remark: "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾ç§ç®"; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form }); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | 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("å¯¼åºæå"); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | </style> |
| src/views/financialManagement/payable/input-invoice.vue
src/views/financialManagement/payable/payment.vue
src/views/financialManagement/payable/paymentApply.vue
src/views/financialManagement/payable/purchaseIn.vue
src/views/financialManagement/payable/reconciliation.vue
src/views/financialManagement/receivable/invoiceApply.vue
src/views/financialManagement/receivable/outputInvoice.vue
src/views/financialManagement/receivable/receipt.vue
src/views/financialManagement/receivable/reconciliation.vue
src/views/financialManagement/receivable/salesOut.vue
src/views/financialManagement/receivable/salesReturn.vue
src/views/financialManagement/revenueManagement/index.vue
src/views/financialManagement/salesRefund/components/ReceiptandRefundPopupWindow.vue
src/views/financialManagement/salesRefund/index.vue
src/views/financialManagement/voucher/detailLedger.vue
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/issueManagement/index.vue
src/views/inventoryManagement/receiptManagement/Record.vue
src/views/inventoryManagement/receiptManagement/index.vue
src/views/inventoryManagement/stockManagement/FrozenAndThaw.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/stockManagement/Unqualified.vue
src/views/inventoryManagement/stockManagement/index.vue
src/views/inventoryManagement/stockWarning/index.vue
src/views/inventoryManagement/transportTaskManagement/index.vue
src/views/inventoryManagement/vehicleFuelManagement/index.vue
src/views/lavorissue/statistics/index.vue
src/views/login.vue
src/views/oaSystem/projectManagement/components/milestoneList.vue
src/views/oaSystem/projectManagement/components/taskTree.vue
src/views/oaSystem/projectManagement/index.vue
src/views/oaSystem/projectManagement/projectDetail.vue
src/views/personnelManagement/analytics/index.vue
src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
src/views/personnelManagement/attendanceCheckin/index.vue
src/views/personnelManagement/classsSheduling/index.vue
src/views/personnelManagement/contractManagement/index.vue
src/views/personnelManagement/dimission/components/formDia.vue
src/views/personnelManagement/dimission/index.vue
src/views/personnelManagement/employeeRecord/components/JobInfoSection.vue
src/views/personnelManagement/employeeRecord/index.vue
src/views/personnelManagement/monthlyStatistics/index.vue
src/views/personnelManagement/socialSecuritySet/index.vue
src/views/procurementManagement/paymentEntry/index.vue
src/views/procurementManagement/paymentHistory/index.vue
src/views/procurementManagement/paymentLedger/index.vue
src/views/procurementManagement/procurementInvoiceLedger/index.vue
src/views/procurementManagement/procurementLedger/index.vue
src/views/procurementManagement/procurementReport/index.vue
src/views/procurementManagement/purchaseReturnOrder/New.vue
src/views/procurementManagement/purchaseReturnOrder/index.vue
src/views/productManagement/productIdentifier/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/Edit.vue
src/views/productionManagement/productionProcess/New.vue
src/views/productionManagement/productionProcess/index.vue
src/views/productionManagement/productionReporting/index.vue
src/views/productionManagement/productionTraceability/index.vue
src/views/productionManagement/workOrder/index.vue
src/views/productionManagement/workOrderEdit/index.vue
src/views/productionManagement/workOrderManagement/components/MaterialDialog.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/projectManagement/roles/index.vue
src/views/qualityManagement/finalInspection/index.vue
src/views/qualityManagement/metricBinding/index.vue
src/views/qualityManagement/metricMaintenance/index.vue
src/views/qualityManagement/nonconformingManagement/index.vue
src/views/qualityManagement/processInspection/index.vue
src/views/qualityManagement/rawMaterialInspection/index.vue
src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
src/views/reportAnalysis/dataDashboard/index0.vue
src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue
src/views/reportAnalysis/productionAnalysis/components/left-top.vue
src/views/reportAnalysis/reportManagement/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/index.vue
src/views/safeProduction/safetyTrainingAssessment/index.vue
src/views/salesManagement/deliveryLedger/index.vue
src/views/salesManagement/indicatorStats/index.vue
src/views/salesManagement/invoiceLedger/index.vue
src/views/salesManagement/invoiceRegistration/index.vue
src/views/salesManagement/orderManagement/index.vue
src/views/salesManagement/receiptPayment/index.vue
src/views/salesManagement/receiptPaymentHistory/index.vue
src/views/salesManagement/receiptPaymentLedger/index.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/salesManagement/salesQuotation/index.vue
src/views/salesManagement/strategyControl/index.vue
src/views/system/appVersion/index.vue
src/views/system/role/index.vue
src/views/systemArchitecture/index.vue
src/views/tool/build/CodeTypeDialog.vue
vite.config.js |