已添加17个文件
已修改25个文件
已删除2个文件
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢å·¥åéä»¶å表 |
| | | export function productWorkOrderFileListPage(query) { |
| | | return request({ |
| | | url: "/productWorkOrderFile/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢å·¥åéä»¶ |
| | | export function productWorkOrderFileAdd(data) { |
| | | return request({ |
| | | url: "/productWorkOrderFile/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤å·¥åéä»¶ |
| | | export function productWorkOrderFileDel(data) { |
| | | return request({ |
| | | url: "/productWorkOrderFile/del", |
| | | method: "delete", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¸è½½å·¥åæµè½¬å¡ï¼è¿åæä»¶æµï¼ |
| | | export function downProductWorkOrder(id) { |
| | | return request({ |
| | | url: "/productWorkOrder/down", |
| | | method: "post", |
| | | data: { id }, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // åè´§å°è´¦é¡µé¢æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function safeHazardListPage(query) { |
| | | return request({ |
| | | url: "/safeHazard/page", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢å±é©æºå°è´¦ |
| | | export function safeHazardAdd(query) { |
| | | return request({ |
| | | url: '/safeHazard', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | // ä¿®æ¹å±é©æºå°è´¦ |
| | | export function safeHazardUpdate(query) { |
| | | return request({ |
| | | url: '/safeHazard', |
| | | method: 'put', |
| | | data: query |
| | | }) |
| | | } |
| | | // å é¤å±é©æºå°è´¦ |
| | | export function safeHazardDel(ids) { |
| | | return request({ |
| | | url: '/safeHazard/' + ids, |
| | | method: 'delete', |
| | | data: ids |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // åè´§å°è´¦é¡µé¢æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function qualificationsListPage(query) { |
| | | return request({ |
| | | url: "/safeCertification/page", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢å®å
¨è§ç¨ä¸èµè´¨ç®¡ç |
| | | export function safeCertificationAdd(query) { |
| | | return request({ |
| | | url: '/safeCertification', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | // ä¿®æ¹å®å
¨è§ç¨ä¸èµè´¨ç®¡ç |
| | | export function safeCertificationUpdate(query) { |
| | | return request({ |
| | | url: '/safeCertification', |
| | | method: 'put', |
| | | data: query |
| | | }) |
| | | } |
| | | // å é¤å®å
¨è§ç¨ä¸èµè´¨ç®¡ç |
| | | export function safeCertificationDel(ids) { |
| | | return request({ |
| | | url: '/safeCertification/' + ids, |
| | | method: 'delete', |
| | | data: ids |
| | | }) |
| | | } |
| | | |
| | | // æ¥è¯¢éä»¶å表 |
| | | export function fileListPage(query) { |
| | | return request({ |
| | | url: "/safeCertificationFile/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // æ·»å éä»¶ |
| | | export function safeCertificationFileAdd(query) { |
| | | return request({ |
| | | url: '/safeCertificationFile/add', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | // å é¤éä»¶ |
| | | export function safeCertificationFileDel(ids) { |
| | | return request({ |
| | | url: '/safeCertificationFile/del', |
| | | method: 'delete', |
| | | data: ids |
| | | }) |
| | | } |
| | |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // 产åéå®éé¢åæ |
| | | export const productSalesAnalysis = () => { |
| | | return request({ |
| | | url: '/home/productSalesAnalysis', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // åææéè´éé¢å æ¯ |
| | | export const rawMaterialPurchaseAmountRatio = () => { |
| | | return request({ |
| | | url: '/home/rawMaterialPurchaseAmountRatio', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // éå®/éè´/å¨åäº§åæ° |
| | | export const salesPurchaseStorageProductCount = () => { |
| | | return request({ |
| | | url: '/home/salesPurchaseStorageProductCount', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // 产ååºå
¥åºåæï¼å¯ä¼ productType: 1 åææ 2 åæå 3 æåï¼ |
| | | export const productInOutAnalysis = (params) => { |
| | | return request({ |
| | | url: '/home/productInOutAnalysis', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // 产åå¨è½¬å¤©æ° |
| | | export const productTurnoverDays = () => { |
| | | return request({ |
| | | url: '/home/productTurnoverDays', |
| | | method: 'get' |
| | | }) |
| | | } |
| | |
| | | const params = {...page, type: props.type}; |
| | | params.timeStr = searchForm.value.timeStr; |
| | | params.productName = searchForm.value.productName; |
| | | params.recordType = searchForm.value.recordType; |
| | | getStockInRecordListPage(params) |
| | | .then(res => { |
| | | tableData.value = res.data.records; |
| | |
| | | :tableLoading="loading" |
| | | :summaryMethod="summarizeChildrenTable" |
| | | :isShowSummary="true" |
| | | :isShowPagination="false" |
| | | height="auto" |
| | | > |
| | | </PIMTable> |
| | |
| | | const getList = async (id) => { |
| | | await nextTick(); |
| | | filters.salesLedgerId = id; |
| | | // 设置ä¸ä¸ªå¾å¤§ç pageSize 以è·åæææ°æ® |
| | | pagination.pageSize = 10000; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | |
| | | v-model="form.invoiceAmount" |
| | | placeholder="请è¾å
¥å票éé¢" |
| | | clearable |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸ä¼ éä»¶"> |
| | | <FileUpload |
| | | :showTip="false" |
| | | accept="*" |
| | | :autoUpload="true" |
| | | :action="action" |
| | | :headers="{ |
| | | Authorization: 'Bearer ' + getToken(), |
| | | }" |
| | | :limit="10" |
| | | @success="uploadSuccess" |
| | | @remove="removeFile" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | /> |
| | | <el-table-column label="æ¬æ¬¡å¼ç¥¨æ°" prop="ticketsNum" width="180"> |
| | | <template #default="scope"> |
| | | <el-input-number :step="0.1" :min="0" style="width: 100%" |
| | | <el-input-number :step="0.1" :min="0" :max="scope.row.tempFutureTickets || 0" style="width: 100%" |
| | | :precision="2" |
| | | v-model="scope.row.ticketsNum" |
| | | @change="invoiceNumBlur(scope.row)" |
| | |
| | | import { defineEmits } from 'vue'; |
| | | import { useModal } from "@/hooks/useModal"; |
| | | import useFormData from "@/hooks/useFormData"; |
| | | import FileUpload from "@/components/Upload/FileUpload.vue"; |
| | | import { |
| | | getPurchaseNoById, |
| | | getInfo, |
| | | addOrUpdateRegistration, |
| | | } from "@/api/procurementManagement/invoiceEntry.js"; |
| | | import { getPurchaseById } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import dayjs from "dayjs"; |
| | | |
| | |
| | | }); |
| | | |
| | | const userStore = useUserStore(); |
| | | const action = import.meta.env.VITE_APP_BASE_API + "/file/upload"; |
| | | const formRef = ref(); |
| | | const { proxy } = getCurrentInstance(); |
| | | const { form } = useFormData({ |
| | |
| | | salesContractNoId: undefined, // å¼ç¥¨æ¥æ |
| | | enterDate: dayjs().format("YYYY-MM-DD"), |
| | | productData: [], // è¡¨æ ¼ |
| | | tempFileIds: [], // æä»¶ |
| | | }); |
| | | |
| | | const selectedContracts = ref([]); // åå¨éä¸çååæ°æ® |
| | |
| | | result.data.productData.forEach(item => { |
| | | allProductData.push({ |
| | | ...item, |
| | | id: contractId, // æç¡®è®¾ç½®ååID |
| | | purchaseLedgerId: contractId, // æ·»å ååIDç¨äºçé |
| | | purchaseLedgerNo: contract.purchaseContractNumber, // æ·»å éè´ååå· |
| | | supplierName: contract.supplierName, // æ·»å ä¾åºååç§° |
| | | projectName: contract.projectName // æ·»å 项ç®åç§° |
| | | // ä¿çäº§åæ¬èº«çidï¼ä¸è¦ç |
| | | }); |
| | | }); |
| | | } |
| | |
| | | |
| | | // è®¾ç½®äº§åæ°æ®ï¼å¹¶åå§åå¼ç¥¨æ°éåéé¢ |
| | | allProductData.forEach(item => { |
| | | // æ¬æ¬¡å¼ç¥¨æ°é»è®¤ä¸ºæ»æ°é |
| | | item.ticketsNum = Number(item.quantity || 0); |
| | | // æ¬æ¬¡å¼ç¥¨éé¢é»è®¤ä¸ºå«ç¨æ»ä»· |
| | | item.ticketsAmount = Number(item.taxInclusiveTotalPrice || 0); |
| | | // ä¿ååå§æªæ¥ç¥¨æ°åéé¢ï¼ç¨äºè®¡ç®ï¼ |
| | | item.tempFutureTickets = Number(item.quantity || 0); |
| | | item.tempFutureTicketsAmount = Number(item.taxInclusiveTotalPrice || 0); |
| | | // æªæ¥ç¥¨æ°åéé¢åå§ä¸º0ï¼å 为å
¨é¨å¼ç¥¨ï¼ |
| | | item.futureTickets = 0; |
| | | item.futureTicketsAmount = 0; |
| | | // ä¿åâåå§æªæ¥ç¥¨æ°/éé¢âï¼ç¨äºæ ¡éªä¸è®¡ç®ï¼ |
| | | // ä¼å
使ç¨å端è¿åç futureTickets/futureTicketsAmountï¼æ²¡æååéå° quantity/taxInclusiveTotalPrice |
| | | item.tempFutureTickets = Number( |
| | | item.futureTickets !== undefined ? item.futureTickets : (item.quantity || 0) |
| | | ); |
| | | item.tempFutureTicketsAmount = Number( |
| | | item.futureTicketsAmount !== undefined ? item.futureTicketsAmount : (item.taxInclusiveTotalPrice || 0) |
| | | ); |
| | | |
| | | // æ°å¢æ¶ï¼æ¬æ¬¡å¼ç¥¨é»è®¤ä¸å¡«ï¼0ï¼ï¼é¿å
䏿å¼å°±æâæªæ¥ç¥¨æ°âæ£æ 0 |
| | | item.ticketsNum = 0; |
| | | item.ticketsAmount = 0; |
| | | |
| | | // 页é¢å±ç¤ºçâæªæ¥ç¥¨æ°/æªæ¥ç¥¨éé¢âé»è®¤å±ç¤ºåå§æªæ¥å¼ |
| | | item.futureTickets = item.tempFutureTickets; |
| | | item.futureTicketsAmount = item.tempFutureTicketsAmount; |
| | | }); |
| | | |
| | | form.productData = allProductData; |
| | | |
| | | // 计ç®å票éé¢ï¼ææäº§åçå«ç¨æ»ä»·ä¹å |
| | | // 计ç®å票éé¢ï¼ææäº§åçæ¬æ¬¡å¼ç¥¨éé¢ä¹åï¼æ°å¢é»è®¤ 0ï¼ |
| | | const totalAmount = allProductData.reduce((sum, item) => { |
| | | return sum + (Number(item.taxInclusiveTotalPrice) || 0); |
| | | return sum + (Number(item.ticketsAmount) || 0); |
| | | }, 0); |
| | | form.invoiceAmount = totalAmount.toFixed(2); |
| | | |
| | |
| | | row.ticketsNum = 0; |
| | | } |
| | | if (Number(row.ticketsNum) > Number(row.tempFutureTickets)) { |
| | | proxy.$modal.msgWarning("æ¬æ¬¡å¼ç¥¨æ°ä¸å¾å¤§äºæªå¼ç¥¨æ°"); |
| | | row.ticketsNum = 0; |
| | | return; |
| | | proxy.$modal.msgWarning("æ¬æ¬¡å¼ç¥¨æ°ä¸è½å¤§äºæªæ¥ç¥¨æ°"); |
| | | row.ticketsNum = Number(row.tempFutureTickets || 0); |
| | | } |
| | | // è®¡ç®æ¬æ¬¡æ¥ç¥¨éé¢ |
| | | row.ticketsAmount = (row.ticketsNum * row.taxInclusiveUnitPrice).toFixed(2) |
| | |
| | | // è®¡ç®æ¯å¦è¶
è¿æ¥ç¥¨æ»éé¢ |
| | | if (row.ticketsAmount > row.tempFutureTicketsAmount) { |
| | | proxy.$modal.msgWarning("æ¬æ¬¡æ¥ç¥¨éé¢ä¸å¾å¤§äºæªæ¥ç¥¨éé¢"); |
| | | row.ticketsAmount = 0; |
| | | row.ticketsAmount = Number(row.tempFutureTicketsAmount || 0); |
| | | } |
| | | // è®¡ç®æ¬æ¬¡æ¥ç¥¨æ° |
| | | row.ticketsNum = Number( |
| | | (row.ticketsAmount / row.taxInclusiveUnitPrice).toFixed(2) |
| | | ); |
| | | // æ£æ¥æ¬æ¬¡å¼ç¥¨æ°æ¯å¦å¤§äºæªæ¥ç¥¨æ° |
| | | if (Number(row.ticketsNum) > Number(row.tempFutureTickets)) { |
| | | proxy.$modal.msgWarning("æ¬æ¬¡å¼ç¥¨æ°ä¸è½å¤§äºæªæ¥ç¥¨æ°"); |
| | | row.ticketsNum = Number(row.tempFutureTickets || 0); |
| | | // éæ°è®¡ç®æ¬æ¬¡æ¥ç¥¨éé¢ |
| | | row.ticketsAmount = (row.ticketsNum * row.taxInclusiveUnitPrice).toFixed(2); |
| | | } |
| | | // è®¡ç®æªæ¥ç¥¨æ° |
| | | row.futureTickets = (row.tempFutureTickets - row.ticketsNum).toFixed(2) |
| | | // è®¡ç®æªæ¥ç¥¨éé¢ |
| | |
| | | await getTableData(type, selectedRows); |
| | | }; |
| | | |
| | | const uploadSuccess = (response) => { |
| | | form.tempFileIds.push(response.data.tempId); |
| | | console.log(form); |
| | | }; |
| | | |
| | | const removeFile = (file) => { |
| | | const { tempId } = file.response.data; |
| | | form.tempFileIds = form.tempFileIds.filter((item) => item !== tempId); |
| | | }; |
| | | |
| | | const closeAndRefresh = () => { |
| | | closeModal(); |
| | |
| | | const batchData = selectedContracts.value.map(contract => { |
| | | // çéåºå±äºå½åååçäº§åæ°æ® |
| | | const contractProductData = form.productData.filter(item => |
| | | item.id === contract.id |
| | | item.purchaseLedgerId === contract.id |
| | | ); |
| | | |
| | | // 为æ¯ä¸ªéè´ååå建ç¬ç«ç对象 |
| | | return { |
| | | // åºç¡è¡¨åæ°æ® |
| | | invoiceNumber: form.invoiceNumber, |
| | | invoiceAmount: form.invoiceAmount, |
| | | entryDate: form.entryDate, |
| | | enterDate: form.enterDate, |
| | | issUerId: form.issUerId, // å½å
¥äººid |
| | | issUer: form.issUer, // å½å
¥äºº |
| | | tempFileIds: form.tempFileIds, |
| | | |
| | | // ååå®é
ä¿¡æ¯ |
| | | purchaseLedgerId: contract.id, // 使ç¨idä½ä¸ºå段åï¼å¼ä¸ºpurchaseLedgerId |
| | | // åºç¡è¡¨åæ°æ® |
| | | invoiceNumber: form.invoiceNumber, |
| | | invoiceAmount: form.invoiceAmount, |
| | | entryDate: form.entryDate, |
| | | enterDate: form.enterDate, |
| | | issUerId: form.issUerId, // å½å
¥äººid |
| | | issUer: form.issUer, // å½å
¥äºº |
| | | |
| | | // ååå®é
ä¿¡æ¯ |
| | | purchaseLedgerId: contract.id, // 使ç¨idä½ä¸ºå段åï¼å¼ä¸ºpurchaseLedgerId |
| | | purchaseContractNumber: contract.purchaseContractNumber, // 使ç¨å®é
çéè´ååå· |
| | | salesContractNo: contract.salesContractNo, // 使ç¨å®é
çéå®ååå· |
| | | supplierName: contract.supplierName, // 使ç¨å®é
çä¾åºååç§° |
| | |
| | | // å个ååæäº¤é»è¾ - 以æ°ç»æ ¼å¼ä¼ é |
| | | const singleContract = selectedContracts.value[0]; |
| | | const singleFormArray = [{ |
| | | // åºç¡è¡¨åæ°æ® |
| | | invoiceNumber: form.invoiceNumber, |
| | | invoiceAmount: form.invoiceAmount, |
| | | entryDate: form.entryDate, |
| | | enterDate: form.enterDate, |
| | | issUerId: form.issUerId, // å½å
¥äººid |
| | | issUer: form.issUer, // å½å
¥äºº |
| | | tempFileIds: form.tempFileIds, |
| | | |
| | | // ååå®é
ä¿¡æ¯ |
| | | purchaseLedgerId: singleContract.id, // 使ç¨idä½ä¸ºå段åï¼å¼ä¸ºpurchaseLedgerId |
| | | // åºç¡è¡¨åæ°æ® |
| | | invoiceNumber: form.invoiceNumber, |
| | | invoiceAmount: form.invoiceAmount, |
| | | entryDate: form.entryDate, |
| | | enterDate: form.enterDate, |
| | | issUerId: form.issUerId, // å½å
¥äººid |
| | | issUer: form.issUer, // å½å
¥äºº |
| | | |
| | | // ååå®é
ä¿¡æ¯ |
| | | purchaseLedgerId: singleContract.id, // 使ç¨idä½ä¸ºå段åï¼å¼ä¸ºpurchaseLedgerId |
| | | purchaseContractNumber: singleContract.purchaseContractNumber, // 使ç¨å®é
çéè´ååå· |
| | | salesContractNo: singleContract.salesContractNo, // 使ç¨å®é
çéå®ååå· |
| | | supplierName: singleContract.supplierName, // 使ç¨å®é
çä¾åºååç§° |
| | |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button |
| | | text |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleEdit('edit', row.id)" |
| | | > |
| | | ç¼è¾ |
| | |
| | | return val ? parseFloat(val).toFixed(2) : 0; |
| | | }, |
| | | }, |
| | | // { |
| | | // fixed: "right", |
| | | // label: "æä½", |
| | | // dataType: "slot", |
| | | // slot: "operation", |
| | | // align: "center", |
| | | // width: "200px", |
| | | // }, |
| | | { |
| | | fixed: "right", |
| | | label: "æä½", |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: 100, |
| | | }, |
| | | ] |
| | | ); |
| | | |
| | |
| | | <el-dialog :title="modalOptions.title" |
| | | v-model="visible" |
| | | @close="close"> |
| | | <EditForm ref="editFormRef" /> |
| | | <el-form :model="form"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éè´ååå·ï¼"> |
| | | <el-tag size="large">{{ form.purchaseContractNumber }}</el-tag> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éå®ååå·ï¼"> |
| | | <el-text>{{ form.salesContractNo }}</el-text> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å«ç¨åä»·(å
)ï¼"> |
| | | <el-text type="primary">{{ form.taxInclusiveUnitPrice }}</el-text> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å建æ¶é´ï¼"> |
| | | <el-text>{{ form.createdAt }}</el-text> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票å·ï¼"> |
| | | <el-input disabled |
| | | v-model="form.invoiceNumber" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥ç¥¨æ°ï¼"> |
| | | <el-input-number :step="0.1" |
| | | :min="0" |
| | | :max="maxTicketsNum" |
| | | style="width: 100%" |
| | | v-model="form.ticketsNum" |
| | | @change="inputTicketsNum" |
| | | :precision="2" /> |
| | | <div style="font-size: 12px; color: #909399; margin-top: 4px;"> |
| | | å¯å¡«åæ°éï¼{{ maxTicketsNum }} |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¬æ¬¡æ¥ç¥¨éé¢(å
)ï¼"> |
| | | <el-input-number :step="0.1" |
| | | :min="0" |
| | | style="width: 100%" |
| | | v-model="form.ticketsAmount" |
| | | @change="inputTicketsAmount" |
| | | :precision="2" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æªæ¥ç¥¨æ°ï¼"> |
| | | <el-text type="success">{{ form.futureTickets }}</el-text> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" |
| | | :loading="loading" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { useModal } from "@/hooks/useModal"; |
| | | import EditForm from "../Form/EditForm.vue"; |
| | | import { updateRegistration } from "@/api/procurementManagement/procurementInvoiceLedger"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { useModal } from "@/hooks/useModal"; |
| | | import useFormData from "@/hooks/useFormData"; |
| | | import { updateRegistration, getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { getCurrentInstance, ref, nextTick, computed } from "vue"; |
| | | |
| | | defineOptions({ |
| | | name: "æ¥ç¥¨å°è´¦ç¼è¾", |
| | | defineOptions({ |
| | | name: "æ¥ç¥¨å°è´¦ç¼è¾", |
| | | }); |
| | | const emits = defineEmits(["success"]); |
| | | |
| | | const saleLedgerProjectId = ref(""); |
| | | const temFutureTickets = ref(0); |
| | | const originalTicketsNum = ref(0); // åå§å·²æ¥ç¥¨æ° |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // è®¡ç®æå¤§å¯å¡«åæ°é = åå§å·²æ¥ç¥¨æ° + æªæ¥ç¥¨æ° |
| | | const maxTicketsNum = computed(() => { |
| | | return Number(originalTicketsNum.value) + Number(temFutureTickets.value); |
| | | }); |
| | | |
| | | const { |
| | | id, |
| | | visible, |
| | | loading, |
| | | openModal, |
| | | modalOptions, |
| | | handleConfirm, |
| | | closeModal, |
| | | } = useModal({ title: "æ¥ç¥¨å°è´¦" }); |
| | | |
| | | const { form, resetForm } = useFormData({ |
| | | id: undefined, |
| | | purchaseContractNumber: undefined, // éè´ååå· |
| | | salesContractNo: undefined, // éå®ååå· |
| | | createdAt: undefined, // å建æ¶é´ |
| | | invoiceNumber: undefined, // åç¥¨å· |
| | | ticketsNum: undefined, // æ¥ç¥¨æ° |
| | | ticketsAmount: undefined, // æ¥ç¥¨éé¢ |
| | | taxInclusiveUnitPrice: undefined, // å«ç¨åä»· |
| | | futureTickets: undefined, // æªæ¥ç¥¨æ° |
| | | }); |
| | | |
| | | const load = async (id, purchaseLedgerId, productModelId) => { |
| | | const { code, data } = await getProductRecordById({ |
| | | id: id, |
| | | purchaseLedgerId: purchaseLedgerId, |
| | | productModelId: productModelId, |
| | | }); |
| | | const emits = defineEmits(["success"]); |
| | | if (code === 200) { |
| | | form.id = data.id; |
| | | form.purchaseContractNumber = data.purchaseContractNumber; |
| | | form.salesContractNo = data.salesContractNo; |
| | | form.createdAt = data.createdAt; |
| | | form.invoiceNumber = data.invoiceNumber; |
| | | form.ticketsNum = data.ticketsNum; |
| | | form.ticketsAmount = data.ticketsAmount.toFixed(2); |
| | | form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice; |
| | | form.futureTickets = data.futureTickets; |
| | | temFutureTickets.value = data.futureTickets; |
| | | // ä¿ååå§å·²æ¥ç¥¨æ° |
| | | originalTicketsNum.value = data.ticketsNum || 0; |
| | | } |
| | | }; |
| | | |
| | | const saleLedgerProjectId = ref(""); |
| | | const editFormRef = ref(); |
| | | const { |
| | | id, |
| | | visible, |
| | | loading, |
| | | openModal, |
| | | modalOptions, |
| | | handleConfirm, |
| | | closeModal, |
| | | } = useModal({ title: "æ¥ç¥¨å°è´¦" }); |
| | | const inputTicketsNum = val => { |
| | | // ç¡®ä¿å«ç¨åä»·åå¨ä¸ä¸ä¸ºé¶ |
| | | if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) { |
| | | proxy.$modal.msgWarning("å«ç¨åä»·ä¸è½ä¸ºé¶ææªå®ä¹"); |
| | | return; |
| | | } |
| | | |
| | | // æ£æ¥æ¥ç¥¨æ°ä¸è½å¤§äºï¼åå§å·²æ¥ç¥¨æ° + æªæ¥ç¥¨æ°ï¼ |
| | | const maxNum = maxTicketsNum.value; |
| | | if (Number(form.ticketsNum) > maxNum) { |
| | | proxy.$modal.msgWarning(`æ¥ç¥¨æ°ä¸è½å¤§äº${maxNum}ï¼å·²æ¥ç¥¨æ°${originalTicketsNum.value} + æªæ¥ç¥¨æ°${temFutureTickets.value}ï¼`); |
| | | form.ticketsNum = maxNum; |
| | | return; |
| | | } |
| | | |
| | | // è®¡ç®æ¬æ¬¡æ°å¢çæ¥ç¥¨æ°ï¼å½åæ¥ç¥¨æ° - åå§å·²æ¥ç¥¨æ°ï¼ |
| | | const newTicketsNum = Number(form.ticketsNum) - Number(originalTicketsNum.value); |
| | | |
| | | // 妿æ°å¢çæ¥ç¥¨æ°å¤§äºæªæ¥ç¥¨æ°ï¼åéå¶ |
| | | if (newTicketsNum > Number(temFutureTickets.value)) { |
| | | proxy.$modal.msgWarning("æ¬æ¬¡æ°å¢æ¥ç¥¨æ°ä¸å¾å¤§äºæªæ¥ç¥¨æ°"); |
| | | form.ticketsNum = Number(originalTicketsNum.value) + Number(temFutureTickets.value); |
| | | return; |
| | | } |
| | | |
| | | const open = async row => { |
| | | openModal(row.id); |
| | | saleLedgerProjectId.value = row.saleLedgerProjectId; |
| | | await nextTick(); |
| | | editFormRef.value.load(row.id, row.purchaseLedgerId, row.productModelId); |
| | | }; |
| | | // ç¡®ä¿æææ°å¼é½è½¬æ¢ä¸ºæ°åç±»åè¿è¡è®¡ç® |
| | | const ticketsAmount = |
| | | Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice); |
| | | const futureTickets = |
| | | Number(temFutureTickets.value) - newTicketsNum; |
| | | form.futureTickets = Number(futureTickets.toFixed(2)); |
| | | form.ticketsAmount = Number(ticketsAmount.toFixed(2)); |
| | | }; |
| | | |
| | | const close = () => { |
| | | editFormRef.value.resetForm(); |
| | | closeModal(); |
| | | }; |
| | | const inputTicketsAmount = val => { |
| | | // ç¡®ä¿å«ç¨åä»·åå¨ä¸ä¸ä¸ºé¶ |
| | | if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) { |
| | | proxy.$modal.msgWarning("å«ç¨åä»·ä¸è½ä¸ºé¶ææªå®ä¹"); |
| | | return; |
| | | } |
| | | |
| | | const sendForm = async () => { |
| | | const form = editFormRef.value.form; |
| | | form.saleLedgerProjectId = saleLedgerProjectId.value; |
| | | const { code } = await updateRegistration(form); |
| | | if (code === 200) { |
| | | emits("success"); |
| | | ElMessage({ message: "æä½æå", type: "success" }); |
| | | close(); |
| | | } |
| | | }; |
| | | // è®¡ç®æå¤§å¯å¡«åéé¢ = ï¼åå§å·²æ¥ç¥¨æ° + æªæ¥ç¥¨æ°ï¼* å«ç¨åä»· |
| | | const maxAmount = maxTicketsNum.value * Number(form.taxInclusiveUnitPrice); |
| | | |
| | | if (Number(val) > maxAmount) { |
| | | proxy.$modal.msgWarning(`æ¬æ¬¡æ¥ç¥¨éé¢ä¸å¾å¤§äº${maxAmount.toFixed(2)}å
`); |
| | | form.ticketsAmount = maxAmount.toFixed(2); |
| | | form.ticketsNum = maxTicketsNum.value; |
| | | return; |
| | | } |
| | | |
| | | defineExpose({ |
| | | open, |
| | | }); |
| | | // ç¡®ä¿æææ°å¼é½è½¬æ¢ä¸ºæ°åç±»åè¿è¡è®¡ç® |
| | | const ticketsNum = Number(val) / Number(form.taxInclusiveUnitPrice); |
| | | |
| | | // æ£æ¥æ¥ç¥¨æ°ä¸è½å¤§äºæå¤§å¼ |
| | | if (ticketsNum > maxTicketsNum.value) { |
| | | proxy.$modal.msgWarning(`æ¥ç¥¨æ°ä¸è½å¤§äº${maxTicketsNum.value}`); |
| | | form.ticketsNum = maxTicketsNum.value; |
| | | form.ticketsAmount = maxAmount.toFixed(2); |
| | | return; |
| | | } |
| | | |
| | | form.ticketsNum = Number(ticketsNum.toFixed(2)); |
| | | |
| | | // è®¡ç®æªæ¥ç¥¨æ° |
| | | const newTicketsNum = form.ticketsNum - originalTicketsNum.value; |
| | | const futureTickets = Number(temFutureTickets.value) - newTicketsNum; |
| | | form.futureTickets = Number(futureTickets.toFixed(2)); |
| | | }; |
| | | |
| | | const open = async row => { |
| | | openModal(row.id); |
| | | saleLedgerProjectId.value = row.saleLedgerProjectId; |
| | | await nextTick(); |
| | | load(row.id, row.purchaseLedgerId, row.productModelId); |
| | | }; |
| | | |
| | | const close = () => { |
| | | resetForm(); |
| | | closeModal(); |
| | | }; |
| | | |
| | | const sendForm = async () => { |
| | | form.saleLedgerProjectId = saleLedgerProjectId.value; |
| | | const { code } = await updateRegistration(form); |
| | | if (code === 200) { |
| | | emits("success"); |
| | | ElMessage({ message: "æä½æå", type: "success" }); |
| | | close(); |
| | | } |
| | | }; |
| | | |
| | | defineExpose({ |
| | | open, |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped></style> |
| | |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #commonFilesRef="{ row }"> |
| | | <el-dropdown @command="(command) => handleCommand(command, row)"> |
| | | <el-button link :icon="Files" type="danger"> éä»¶ </el-button> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <el-dropdown-item |
| | | v-if="row.commonFiles.length !== 0" |
| | | :icon="Download" |
| | | command="download" |
| | | > |
| | | ä¸è½½ |
| | | </el-dropdown-item> |
| | | <el-dropdown-item :icon="Upload" command="upload"> |
| | | ä¸ä¼ |
| | | </el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </template> |
| | | </el-dropdown> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button |
| | | type="primary" |
| | | text |
| | | link |
| | | @click="openEdit(row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | text |
| | | link |
| | | @click="downLoadFile(row)" |
| | | > |
| | | éä»¶ |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @click="handleDelete(row)" |
| | | > |
| | | å é¤ |
| | |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <UploadModal ref="modalRef" @uploadSuccess="uploadSuccess"></UploadModal> |
| | | <FileListDialog |
| | | ref="fileListRef" |
| | | v-model="fileListDialogVisible" |
| | | title="éä»¶å表" |
| | | :showUploadButton="true" |
| | | :showDeleteButton="true" |
| | | :deleteMethod="handleDeleteFile" |
| | | :uploadMethod="handleFileUpload" |
| | | :rulesRegulationsManagementId="currentRowId" |
| | | /> |
| | | <EditModal ref="editmodalRef" @success="getTableData"></EditModal> |
| | | </div> |
| | | </template> |
| | |
| | | import { ref, getCurrentInstance } from "vue"; |
| | | import { usePaginationApi } from "@/hooks/usePaginationApi"; |
| | | import { |
| | | Files, |
| | | Download, |
| | | Search, |
| | | Upload, |
| | | EditPen, |
| | | Search, |
| | | } from "@element-plus/icons-vue"; |
| | | import { |
| | | delRegistration, |
| | | productRecordPage, |
| | | productUploadFile, |
| | | delCommonFile, |
| | | } from "@/api/procurementManagement/procurementInvoiceLedger.js"; |
| | | import request from "@/utils/request"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import { onMounted } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import UploadModal from "./Modal/UploadModal.vue"; |
| | | import EditModal from "./Modal/EditModal.vue"; |
| | | import FileListDialog from '@/components/Dialog/FileListDialog.vue'; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {delInvoiceLedgerByRegProductId} from "@/api/salesManagement/invoiceLedger.js"; |
| | | const userStore = useUserStore(); |
| | | |
| | | defineOptions({ |
| | | name: "æ¥ç¥¨å°è´¦", |
| | | }); |
| | | |
| | | const modalRef = ref(); |
| | | const editmodalRef = ref(); |
| | | const fileListRef = ref(null); |
| | | const fileListDialogVisible = ref(false); |
| | | const currentRowId = ref(null); // å½åæ¥çéä»¶çè¡ID |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const multipleVal = ref([]); |
| | |
| | | label: "ä¾åºååç§°", |
| | | prop: "supplierName", |
| | | width: 240, |
| | | }, |
| | | { |
| | | label: "产å大类", |
| | | prop: "productCategory", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | |
| | | width: 200, |
| | | }, |
| | | { |
| | | label: "éä»¶", |
| | | align: "center", |
| | | prop: "commonFiles", |
| | | dataType: "slot", |
| | | fixed: "right", |
| | | slot: "commonFilesRef", |
| | | width: 120, |
| | | }, |
| | | { |
| | | fixed: "right", |
| | | width: 150, |
| | | width: 200, |
| | | label: "æä½", |
| | | dataType: "slot", |
| | | slot: "operation", |
| | |
| | | }); |
| | | }; |
| | | |
| | | const handleFiles = (fileList) => { |
| | | fileList.forEach((e) => { |
| | | proxy.$download.name(e.url); |
| | | }); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | onCurrentChange(page); |
| | | }; |
| | | |
| | | const handleCommand = (command, row) => { |
| | | switch (command) { |
| | | case "download": |
| | | handleFiles(row.commonFiles); |
| | | break; |
| | | case "upload": |
| | | console.log(row.commonFiles); |
| | | openUoload(row.ticketRegistrationId); |
| | | break; |
| | | const downLoadFile = row => { |
| | | currentRowId.value = row.id; |
| | | if (fileListRef.value) { |
| | | fileListRef.value.open(row.commonFiles || []); |
| | | } |
| | | }; |
| | | |
| | | const openUoload = (id) => { |
| | | modalRef.value.handleImport(id); |
| | | // ä¸ä¼ éä»¶ï¼èªå®ä¹ä¸ä¼ æ¹æ³ï¼ |
| | | const handleFileUpload = async () => { |
| | | if (!currentRowId.value) { |
| | | proxy.$modal.msgWarning("缺å°ç»è®°IDï¼æ æ³ä¿åéä»¶"); |
| | | 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 { |
| | | // ä½¿ç¨ FormData ä¸ä¼ æä»¶ |
| | | const formData = new FormData(); |
| | | formData.append('file', file); |
| | | formData.append('type', '4'); // type åæ°ï¼ç¨æ·æªæå®å
·ä½å¼ï¼å
ä¼ ç©ºå符串 |
| | | formData.append('id', currentRowId.value); // å½åè¡ç id |
| | | |
| | | const uploadRes = await request({ |
| | | url: '/file/uploadByCommon', |
| | | method: 'post', |
| | | data: formData, |
| | | headers: { |
| | | 'Content-Type': 'multipart/form-data', |
| | | Authorization: `Bearer ${getToken()}` |
| | | } |
| | | }); |
| | | |
| | | if (uploadRes.code === 200) { |
| | | proxy.$modal.msgSuccess("éä»¶ä¸ä¼ æå"); |
| | | |
| | | // å·æ°å表è·åææ°æ°æ® |
| | | await new Promise((resolveRefresh) => { |
| | | // è°ç¨ API è·åææ°åè¡¨æ°æ® |
| | | productRecordPage({ |
| | | ...filters, |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize |
| | | }).then(({ code, data }) => { |
| | | if (code === 200) { |
| | | // æ´æ°æ°æ®å表 |
| | | dataList.value = data.records; |
| | | pagination.total = data.total; |
| | | |
| | | // ä»å¤é¨æ°æ®è·å commonFiles |
| | | const currentRow = dataList.value.find(row => row.id === currentRowId.value); |
| | | if (currentRow && fileListRef.value) { |
| | | // å·æ°éä»¶å表ï¼ä½¿ç¨ä»å¤é¨è·åçææ° commonFiles |
| | | fileListRef.value.open(currentRow.commonFiles || []); |
| | | } |
| | | resolveRefresh(); |
| | | } else { |
| | | resolveRefresh(); |
| | | } |
| | | }).catch(() => { |
| | | resolveRefresh(); |
| | | }); |
| | | }); |
| | | |
| | | resolve({ |
| | | name: uploadRes.data?.originalName || file.name, |
| | | url: uploadRes.data?.tempPath || uploadRes.data?.url, |
| | | id: uploadRes.data?.id |
| | | }); |
| | | } else { |
| | | proxy.$modal.msgError(uploadRes.msg || "æä»¶ä¸ä¼ 失败"); |
| | | resolve(null); |
| | | } |
| | | } catch (error) { |
| | | console.error("éä»¶ä¸ä¼ 失败:", error); |
| | | proxy.$modal.msgError("éä»¶ä¸ä¼ 失败"); |
| | | resolve(null); |
| | | } finally { |
| | | document.body.removeChild(input); |
| | | } |
| | | }; |
| | | |
| | | document.body.appendChild(input); |
| | | input.click(); |
| | | }); |
| | | }; |
| | | |
| | | // å é¤éä»¶ |
| | | const handleDeleteFile = async (file) => { |
| | | try { |
| | | await delCommonFile([file.id]); |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | |
| | | // å·æ°å表è·åææ°æ°æ® |
| | | await new Promise((resolveRefresh) => { |
| | | // è°ç¨ API è·åææ°åè¡¨æ°æ® |
| | | productRecordPage({ |
| | | ...filters, |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize |
| | | }).then(({ code, data }) => { |
| | | if (code === 200) { |
| | | // æ´æ°æ°æ®å表 |
| | | dataList.value = data.records; |
| | | pagination.total = data.total; |
| | | |
| | | // ä»å¤é¨æ°æ®è·å commonFiles |
| | | const currentRow = dataList.value.find(row => row.id === currentRowId.value); |
| | | if (currentRow && fileListRef.value) { |
| | | // å·æ°éä»¶å表ï¼ä½¿ç¨ä»å¤é¨è·åçææ° commonFiles |
| | | fileListRef.value.open(currentRow.commonFiles || []); |
| | | } |
| | | resolveRefresh(); |
| | | } else { |
| | | resolveRefresh(); |
| | | } |
| | | }).catch(() => { |
| | | resolveRefresh(); |
| | | }); |
| | | }); |
| | | |
| | | return true; |
| | | } catch (error) { |
| | | proxy.$modal.msgError("å é¤å¤±è´¥"); |
| | | return false; |
| | | } |
| | | }; |
| | | |
| | | const openEdit = (row) => { |
| | | editmodalRef.value.open(row); |
| | | }; |
| | | |
| | | // ä¸ä¼ æåååä»ä¹ |
| | | const uploadSuccess = async (data) => { |
| | | const { code } = await productUploadFile({ |
| | | ticketRegistrationId: data.id, |
| | | tempFileIds: data.tempFileIds, |
| | | }); |
| | | if (code === 200) { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | getTableData(); |
| | | } |
| | | }; |
| | | // å é¤ |
| | | const handleDelete = (row) => { |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ref, reactive, onMounted, getCurrentInstance } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { Download } from '@element-plus/icons-vue' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import { procurementBusinessSummaryListPage } from '@/api/procurementManagement/procurementReport' |
| | | import { productTreeList } from '@/api/basicData/product' |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | |
| | | } |
| | | |
| | | const exportReport = () => { |
| | | ElMessage.success('导åºåè½å¼åä¸...') |
| | | const params = {} |
| | | |
| | | // æ¶é´èå´ |
| | | if (searchForm.dateRange && searchForm.dateRange.length === 2) { |
| | | params.entryDateStart = searchForm.dateRange[0] |
| | | params.entryDateEnd = searchForm.dateRange[1] |
| | | } |
| | | |
| | | // 产åç±»å« |
| | | if (searchForm.productCategory) { |
| | | const categoryName = findNodeLabelById(productOptions.value, searchForm.productCategory) |
| | | if (categoryName) { |
| | | params.productCategory = categoryName |
| | | } |
| | | } |
| | | |
| | | proxy.download("/procurementBusinessSummary/export", params, "éè´ä¸å¡æ±æ»è¡¨.xlsx") |
| | | } |
| | | |
| | | |
| | |
| | | <div class="app-container"> |
| | | <PageHeader content="产åç»æè¯¦æ
"> |
| | | <template #right-button> |
| | | <el-button v-if="!dataValue.isEdit && !isOrderPage" type="primary" @click="dataValue.isEdit = true">ç¼è¾ |
| | | <el-button v-if="!dataValue.isEdit && !isOrderPage" |
| | | type="primary" |
| | | @click="dataValue.isEdit = true">ç¼è¾ |
| | | </el-button> |
| | | <el-button v-if="dataValue.isEdit && !isOrderPage" type="primary" @click="cancelEdit">åæ¶ |
| | | <el-button v-if="dataValue.isEdit && !isOrderPage" |
| | | type="primary" |
| | | @click="cancelEdit">åæ¶ |
| | | </el-button> |
| | | <el-button v-if="!isOrderPage" type="primary" :loading="dataValue.loading" @click="submit" |
| | | :disabled="!dataValue.isEdit">确认 |
| | | <el-button v-if="!isOrderPage" |
| | | type="primary" |
| | | :loading="dataValue.loading" |
| | | @click="submit" |
| | | :disabled="!dataValue.isEdit">确认 |
| | | </el-button> |
| | | </template> |
| | | </PageHeader> |
| | | <el-table :data="tableData" border :preserve-expanded-content="false" :default-expand-all="true" |
| | | style="width: 100%"> |
| | | <el-table :data="tableData" |
| | | border |
| | | :preserve-expanded-content="false" |
| | | :default-expand-all="true" |
| | | style="width: 100%"> |
| | | <el-table-column type="expand"> |
| | | <template #default="props"> |
| | | <el-form ref="form" :model="dataValue"> |
| | | <el-table :data="dataValue.dataList" row-key="tempId" default-expand-all |
| | | :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" style="width: 100%"> |
| | | <el-table-column prop="productName" label="产å" /> |
| | | <el-table-column prop="model" label="è§æ ¼"> |
| | | <el-form ref="form" |
| | | :model="dataValue"> |
| | | <el-table :data="dataValue.dataList" |
| | | row-key="tempId" |
| | | default-expand-all |
| | | :tree-props="{children: 'children', hasChildren: 'hasChildren'}" |
| | | style="width: 100%"> |
| | | <el-table-column prop="productName" |
| | | label="产å" /> |
| | | <el-table-column prop="model" |
| | | label="è§æ ¼"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="[{ required: true, message: 'è¯·éæ©è§æ ¼', trigger: ['blur', 'change'] }]" style="margin: 0"> |
| | | <el-select v-model="row.model" placeholder="è¯·éæ©è§æ ¼" clearable |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" |
| | | style="width: 100%" @visible-change="(v) => { if (v) openDialog(row.tempId) }"> |
| | | <el-option v-if="row.model" :label="row.model" :value="row.model" /> |
| | | :rules="[{ required: true, message: 'è¯·éæ©è§æ ¼', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-select v-model="row.model" |
| | | placeholder="è¯·éæ©è§æ ¼" |
| | | clearable |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" |
| | | style="width: 100%" |
| | | @visible-change="(v) => { if (v) openDialog(row.tempId) }"> |
| | | <el-option v-if="row.model" |
| | | :label="row.model" |
| | | :value="row.model" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="processName" label="æ¶èå·¥åº"> |
| | | <el-table-column prop="processName" |
| | | label="æ¶èå·¥åº"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: 'è¯·éæ©æ¶èå·¥åº', trigger: 'change' }]" |
| | | style="margin: 0"> |
| | | <el-select v-model="row.processId" placeholder="è¯·éæ©" filterable clearable style="width: 100%" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"> |
| | | <el-option v-for="item in dataValue.processOptions" :key="item.id" :label="item.name" |
| | | :value="item.id" /> |
| | | :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: 'è¯·éæ©æ¶èå·¥åº', trigger: 'change' }]" |
| | | style="margin: 0"> |
| | | <el-select v-model="row.processId" |
| | | placeholder="è¯·éæ©" |
| | | filterable |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"> |
| | | <el-option v-for="item in dataValue.processOptions" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unitQuantity" label="åä½äº§åºæéæ°é"> |
| | | <el-table-column prop="unitQuantity" |
| | | label="åä½äº§åºæéæ°é"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="[{ required: true, message: '请è¾å
¥åä½äº§åºæéæ°é', trigger: ['blur', 'change'] }]" |
| | | style="margin: 0"> |
| | | <el-input-number v-model="row.unitQuantity" :min="0" :precision="2" :step="1" |
| | | controls-position="right" style="width: 100%" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | :rules="[{ required: true, message: '请è¾å
¥åä½äº§åºæéæ°é', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-input-number v-model="row.unitQuantity" |
| | | :min="0" |
| | | :precision="2" |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column v-if="isOrderPage" prop="demandedQuantity" label="éæ±æ»é"> |
| | | <el-table-column v-if="isOrderPage" |
| | | prop="demandedQuantity" |
| | | label="éæ±æ»é"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="[{ required: true, message: '请è¾å
¥éæ±æ»é', trigger: ['blur', 'change'] }]" style="margin: 0"> |
| | | <el-input-number v-model="row.demandedQuantity" :min="0" :precision="2" :step="1" |
| | | controls-position="right" style="width: 100%" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | :rules="[{ required: true, message: '请è¾å
¥éæ±æ»é', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-input-number v-model="row.demandedQuantity" |
| | | :min="0" |
| | | :precision="2" |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unit" label="åä½"> |
| | | <el-table-column prop="unit" |
| | | label="åä½"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="[{ required: true, message: '请è¾å
¥åä½', trigger: ['blur', 'change'] }]" style="margin: 0"> |
| | | <el-input v-model="row.unit" placeholder="请è¾å
¥åä½" clearable |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | :rules="[{ required: true, message: '请è¾å
¥åä½', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-input v-model="row.unit" |
| | | placeholder="请è¾å
¥åä½" |
| | | clearable |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right" width="200"> |
| | | <el-table-column label="æä½" |
| | | fixed="right" |
| | | width="200"> |
| | | <template #default="{ row, $index }"> |
| | | <el-button |
| | | v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)" |
| | | type="danger" text @click="removeItem(row.tempId)">å é¤ |
| | | <el-button v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)" |
| | | type="danger" |
| | | text |
| | | @click="removeItem(row.tempId)">å é¤ |
| | | </el-button> |
| | | <el-button v-if="dataValue.isEdit" type="primary" text @click="addItem2(row.tempId)">æ·»å |
| | | <el-button v-if="dataValue.isEdit" |
| | | type="primary" |
| | | text |
| | | @click="addItem2(row.tempId)">æ·»å |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | </el-form> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="BOMç¼å·" prop="bomNo" /> |
| | | <el-table-column label="产ååç§°" prop="productName" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="model" /> |
| | | <el-table-column label="BOMç¼å·" |
| | | prop="bomNo" /> |
| | | <el-table-column label="产ååç§°" |
| | | prop="productName" /> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="model" /> |
| | | </el-table> |
| | | <product-select-dialog v-if="dataValue.showProductDialog" v-model:model-value="dataValue.showProductDialog" |
| | | @confirm="handleProduct" /> |
| | | <product-select-dialog v-if="dataValue.showProductDialog" |
| | | v-model:model-value="dataValue.showProductDialog" |
| | | @confirm="handleProduct" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { |
| | | computed, |
| | | defineAsyncComponent, |
| | | defineComponent, |
| | | onMounted, |
| | | reactive, |
| | | ref, |
| | | } from "vue"; |
| | | import { queryList, add } from "@/api/productionManagement/productStructure.js"; |
| | | import { listProcessBom } from "@/api/productionManagement/productionOrder.js"; |
| | | import { list } from "@/api/productionManagement/productionProcess"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { useRoute, useRouter } from "vue-router"; |
| | | import { |
| | | computed, |
| | | defineAsyncComponent, |
| | | defineComponent, |
| | | onMounted, |
| | | reactive, |
| | | ref, |
| | | } from "vue"; |
| | | import { queryList, add } from "@/api/productionManagement/productStructure.js"; |
| | | import { listProcessBom } from "@/api/productionManagement/productionOrder.js"; |
| | | import { list } from "@/api/productionManagement/productionProcess"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { useRoute, useRouter } from "vue-router"; |
| | | |
| | | defineComponent({ |
| | | name: "StructureEdit", |
| | | }); |
| | | defineComponent({ |
| | | name: "StructureEdit", |
| | | }); |
| | | |
| | | const ProductSelectDialog = defineAsyncComponent( |
| | | () => import("@/views/basicData/product/ProductSelectDialog.vue") |
| | | ); |
| | | const emit = defineEmits(["update:router"]); |
| | | const form = ref(); |
| | | const ProductSelectDialog = defineAsyncComponent( |
| | | () => import("@/views/basicData/product/ProductSelectDialog.vue") |
| | | ); |
| | | const emit = defineEmits(["update:router"]); |
| | | const form = ref(); |
| | | |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const routeId = computed({ |
| | | get() { |
| | | return route.query.id; |
| | | }, |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const routeId = computed({ |
| | | get() { |
| | | return route.query.id; |
| | | }, |
| | | |
| | | set(val) { |
| | | emit("update:router", val); |
| | | }, |
| | | }); |
| | | set(val) { |
| | | emit("update:router", val); |
| | | }, |
| | | }); |
| | | |
| | | // ä»è·¯ç±åæ°è·å产åä¿¡æ¯ |
| | | const routeBomNo = computed(() => route.query.bomNo || ""); |
| | | const routeProductName = computed(() => route.query.productName || ""); |
| | | const routeProductModelName = computed( |
| | | () => route.query.productModelName || "" |
| | | ); |
| | | const routeOrderId = computed(() => route.query.orderId); |
| | | const pageType = computed(() => route.query.type); |
| | | const isOrderPage = computed( |
| | | () => pageType.value === "order" && routeOrderId.value |
| | | ); |
| | | // ä»è·¯ç±åæ°è·å产åä¿¡æ¯ |
| | | const routeBomNo = computed(() => route.query.bomNo || ""); |
| | | const routeProductName = computed(() => route.query.productName || ""); |
| | | const routeProductModelName = computed( |
| | | () => route.query.productModelName || "" |
| | | ); |
| | | const routeOrderId = computed(() => route.query.orderId); |
| | | const pageType = computed(() => route.query.type); |
| | | const isOrderPage = computed( |
| | | () => pageType.value === "order" && routeOrderId.value |
| | | ); |
| | | |
| | | const dataValue = reactive({ |
| | | dataList: [], |
| | | productOptions: [], |
| | | processOptions: [], |
| | | showProductDialog: false, |
| | | currentRowIndex: null, |
| | | currentRowName: null, |
| | | loading: false, |
| | | isEdit: false, |
| | | }); |
| | | const dataValue = reactive({ |
| | | dataList: [], |
| | | productOptions: [], |
| | | processOptions: [], |
| | | showProductDialog: false, |
| | | currentRowIndex: null, |
| | | currentRowName: null, |
| | | loading: false, |
| | | isEdit: false, |
| | | }); |
| | | |
| | | const tableData = reactive([ |
| | | { |
| | | productName: "", |
| | | model: "", |
| | | bomNo: "", |
| | | }, |
| | | ]); |
| | | const tableData = reactive([ |
| | | { |
| | | productName: "", |
| | | model: "", |
| | | bomNo: "", |
| | | }, |
| | | ]); |
| | | |
| | | const openDialog = (tempId: any) => { |
| | | console.log(tempId, "tempId"); |
| | | dataValue.currentRowName = tempId; |
| | | dataValue.showProductDialog = true; |
| | | }; |
| | | const openDialog = (tempId: any) => { |
| | | console.log(tempId, "tempId"); |
| | | dataValue.currentRowName = tempId; |
| | | dataValue.showProductDialog = true; |
| | | }; |
| | | |
| | | const fetchData = async () => { |
| | | if (isOrderPage.value) { |
| | | // 订åæ
åµï¼ä½¿ç¨è®¢åç产åç»ææ¥å£ |
| | | const { data } = await listProcessBom({ orderId: routeOrderId.value }); |
| | | dataValue.dataList = (data as any) || []; |
| | | } else { |
| | | // é订åæ
åµï¼ä½¿ç¨åæ¥çæ¥å£ |
| | | const { data } = await queryList(routeId.value); |
| | | dataValue.dataList = (data as any) || []; |
| | | // 为ææé¡¹åå
¶å项设置name屿§ |
| | | const setNameRecursively = (items: any[]) => { |
| | | items.forEach((item: any) => { |
| | | item.tempId = item.id; |
| | | item.processName = |
| | | dataValue.processOptions.find(option => option.id === item.processId) |
| | | ?.name || ""; |
| | | if (item.children && item.children.length > 0) { |
| | | setNameRecursively(item.children); |
| | | const fetchData = async () => { |
| | | if (isOrderPage.value) { |
| | | // 订åæ
åµï¼ä½¿ç¨è®¢åç产åç»ææ¥å£ |
| | | const { data } = await listProcessBom({ orderId: routeOrderId.value }); |
| | | dataValue.dataList = (data as any) || []; |
| | | } else { |
| | | // é订åæ
åµï¼ä½¿ç¨åæ¥çæ¥å£ |
| | | const { data } = await queryList(routeId.value); |
| | | dataValue.dataList = (data as any) || []; |
| | | // 为ææé¡¹åå
¶å项设置name屿§ |
| | | const setNameRecursively = (items: any[]) => { |
| | | items.forEach((item: any) => { |
| | | item.tempId = item.id; |
| | | item.processName = |
| | | dataValue.processOptions.find(option => option.id === item.processId) |
| | | ?.name || ""; |
| | | if (item.children && item.children.length > 0) { |
| | | setNameRecursively(item.children); |
| | | } |
| | | }); |
| | | }; |
| | | setNameRecursively(dataValue.dataList); |
| | | console.log(dataValue.dataList, "dataValue.dataList"); |
| | | } |
| | | }; |
| | | |
| | | const fetchProcessOptions = async () => { |
| | | const { data } = await list(); |
| | | dataValue.processOptions = data as any; |
| | | }; |
| | | |
| | | const handleProduct = (row: any) => { |
| | | if (row?.length > 1) { |
| | | ElMessage.error("åªè½éæ©ä¸ä¸ªäº§å"); |
| | | } |
| | | const productData = row[0]; |
| | | |
| | | // æå¤å±ç»ä»¶ä¸ï¼ä¸å½å产åç¸åç产ååªè½æä¸ä¸ª |
| | | const isTopLevel = dataValue.dataList.some(item => (item as any).tempId === dataValue.currentRowName); |
| | | if (isTopLevel) { |
| | | if (productData.productName === tableData[0].productName && |
| | | productData.model === tableData[0].model) { |
| | | // æ¥æ¾æ¯å¦å·²ç»æå
¶ä»é¡¶å±è¡å·²ç»æ¯è¿ä¸ªäº§å |
| | | const hasOther = dataValue.dataList.some(item => |
| | | (item as any).tempId !== dataValue.currentRowName && |
| | | (item as any).productName === tableData[0].productName && |
| | | (item as any).model === tableData[0].model |
| | | ); |
| | | if (hasOther) { |
| | | ElMessage.warning("æå¤å±åå½å产å䏿 ·çä¸çº§åªè½æä¸ä¸ª"); |
| | | return; |
| | | } |
| | | }); |
| | | }; |
| | | setNameRecursively(dataValue.dataList); |
| | | console.log(dataValue.dataList, "dataValue.dataList"); |
| | | } |
| | | }; |
| | | |
| | | const fetchProcessOptions = async () => { |
| | | const { data } = await list(); |
| | | dataValue.processOptions = data as any; |
| | | }; |
| | | |
| | | const handleProduct = (row: any) => { |
| | | if (row?.length > 1) { |
| | | ElMessage.error("åªè½éæ©ä¸ä¸ªäº§å"); |
| | | } |
| | | const productData = row[0]; |
| | | |
| | | // æå¤å±ç»ä»¶ä¸ï¼ä¸å½å产åç¸åç产ååªè½æä¸ä¸ª |
| | | const isTopLevel = dataValue.dataList.some(item => (item as any).tempId === dataValue.currentRowName); |
| | | if (isTopLevel) { |
| | | if (productData.productName === tableData[0].productName && |
| | | productData.model === tableData[0].model) { |
| | | // æ¥æ¾æ¯å¦å·²ç»æå
¶ä»é¡¶å±è¡å·²ç»æ¯è¿ä¸ªäº§å |
| | | const hasOther = dataValue.dataList.some(item => |
| | | (item as any).tempId !== dataValue.currentRowName && |
| | | (item as any).productName === tableData[0].productName && |
| | | (item as any).model === tableData[0].model |
| | | ); |
| | | if (hasOther) { |
| | | ElMessage.warning("æå¤å±åå½å产å䏿 ·çä¸çº§åªè½æä¸ä¸ª"); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | // dataValue.dataList[dataValue.currentRowIndex].productName = |
| | | // row[0].productName; |
| | | // dataValue.dataList[dataValue.currentRowIndex].model = row[0].model; |
| | | // dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id; |
| | | // dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || ""; |
| | | dataValue.dataList.map(item => { |
| | | if (item.tempId === dataValue.currentRowName) { |
| | | // dataValue.dataList[dataValue.currentRowIndex].productName = |
| | | // row[0].productName; |
| | | // dataValue.dataList[dataValue.currentRowIndex].model = row[0].model; |
| | | // dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id; |
| | | // dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || ""; |
| | | dataValue.dataList.map(item => { |
| | | if (item.tempId === dataValue.currentRowName) { |
| | | item.productName = productData.productName; |
| | | item.model = productData.model; |
| | | item.productModelId = productData.id; |
| | | item.unit = productData.unit || ""; |
| | | return; |
| | | } |
| | | childItem(item, dataValue.currentRowName, productData); |
| | | }); |
| | | dataValue.showProductDialog = false; |
| | | }; |
| | | const childItem = (item: any, tempId: any, productData: any) => { |
| | | if (item.tempId === tempId) { |
| | | item.productName = productData.productName; |
| | | item.model = productData.model; |
| | | item.productModelId = productData.id; |
| | | item.unit = productData.unit || ""; |
| | | return; |
| | | return true; |
| | | } |
| | | childItem(item, dataValue.currentRowName, productData); |
| | | }); |
| | | dataValue.showProductDialog = false; |
| | | }; |
| | | const childItem = (item: any, tempId: any, productData: any) => { |
| | | if (item.tempId === tempId) { |
| | | item.productName = productData.productName; |
| | | item.model = productData.model; |
| | | item.productModelId = productData.id; |
| | | item.unit = productData.unit || ""; |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | for (let child of item.children) { |
| | | if (childItem(child, tempId, productData)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | // é彿 ¡éªææå±çº§çè¡¨åæ°æ® |
| | | const validateAll = () => { |
| | | let isValid = true; |
| | | |
| | | // æ ¡éªå½æ° |
| | | const validateItem = (item: any, isTopLevel = false) => { |
| | | // æ ¡éªå½å项çå¿
å¡«åæ®µ |
| | | if (!item.model) { |
| | | ElMessage.error("è¯·éæ©è§æ ¼"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (!isTopLevel && !item.processId) { |
| | | ElMessage.error("è¯·éæ©æ¶èå·¥åº"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (!item.unitQuantity) { |
| | | ElMessage.error("请è¾å
¥åä½äº§åºæéæ°é"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (isOrderPage.value && !item.demandedQuantity) { |
| | | ElMessage.error("请è¾å
¥éæ±æ»é"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (!item.unit) { |
| | | ElMessage.error("请è¾å
¥åä½"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | |
| | | // é彿 ¡éªå项 |
| | | if (item.children && item.children.length > 0) { |
| | | item.children.forEach(child => { |
| | | validateItem(child, false); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // éåææé¡¶å±é¡¹ |
| | | dataValue.dataList.forEach(item => { |
| | | validateItem(item, true); |
| | | }); |
| | | |
| | | return isValid; |
| | | }; |
| | | |
| | | const submit = () => { |
| | | dataValue.loading = true; |
| | | |
| | | // å
è¿è¡è¡¨åæ ¡éª |
| | | const valid = validateAll(); |
| | | console.log(dataValue.dataList, "dataValue.dataList"); |
| | | if (valid) { |
| | | add({ |
| | | bomId: routeId.value, |
| | | children: dataValue.dataList || [], |
| | | }) |
| | | .then(res => { |
| | | router.push({ |
| | | path: "/productionManagement/productionManagement/productStructure/index", |
| | | }); |
| | | ElMessage.success("ä¿åæå"); |
| | | dataValue.loading = false; |
| | | }) |
| | | .catch(() => { |
| | | dataValue.loading = false; |
| | | }); |
| | | } else { |
| | | dataValue.loading = false; |
| | | } |
| | | }; |
| | | |
| | | const removeItem = (tempId: string) => { |
| | | // å
å°è¯ä»é¡¶å±å é¤ |
| | | const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId); |
| | | if (topIndex !== -1) { |
| | | dataValue.dataList.splice(topIndex, 1); |
| | | return; |
| | | } |
| | | |
| | | // éå½å é¤å项 |
| | | const delchildItem = (items: any[], tempId: any) => { |
| | | for (let i = 0; i < items.length; i++) { |
| | | const item = items[i]; |
| | | if (item.tempId === tempId) { |
| | | items.splice(i, 1); |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | if (delchildItem(item.children, tempId)) { |
| | | for (let child of item.children) { |
| | | if (childItem(child, tempId, productData)) { |
| | | return true; |
| | | } |
| | | } |
| | |
| | | return false; |
| | | }; |
| | | |
| | | dataValue.dataList.forEach(item => { |
| | | if (item.children && item.children.length > 0) { |
| | | delchildItem(item.children, tempId); |
| | | // é彿 ¡éªææå±çº§çè¡¨åæ°æ® |
| | | const validateAll = () => { |
| | | let isValid = true; |
| | | |
| | | // æ ¡éªå½æ° |
| | | const validateItem = (item: any, isTopLevel = false) => { |
| | | // æ ¡éªå½å项çå¿
å¡«åæ®µ |
| | | if (!item.model) { |
| | | ElMessage.error("è¯·éæ©è§æ ¼"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (!isTopLevel && !item.processId) { |
| | | ElMessage.error("è¯·éæ©æ¶èå·¥åº"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (!item.unitQuantity) { |
| | | ElMessage.error("请è¾å
¥åä½äº§åºæéæ°é"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (isOrderPage.value && !item.demandedQuantity) { |
| | | ElMessage.error("请è¾å
¥éæ±æ»é"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | // if (!item.unit) { |
| | | // ElMessage.error("请è¾å
¥åä½"); |
| | | // isValid = false; |
| | | // return; |
| | | // } |
| | | |
| | | // é彿 ¡éªå项 |
| | | if (item.children && item.children.length > 0) { |
| | | item.children.forEach(child => { |
| | | validateItem(child, false); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // éåææé¡¶å±é¡¹ |
| | | dataValue.dataList.forEach(item => { |
| | | validateItem(item, true); |
| | | }); |
| | | |
| | | return isValid; |
| | | }; |
| | | |
| | | const submit = () => { |
| | | dataValue.loading = true; |
| | | |
| | | // å
è¿è¡è¡¨åæ ¡éª |
| | | const valid = validateAll(); |
| | | console.log(dataValue.dataList, "dataValue.dataList"); |
| | | if (valid) { |
| | | add({ |
| | | bomId: routeId.value, |
| | | children: dataValue.dataList || [], |
| | | }) |
| | | .then(res => { |
| | | router.push({ |
| | | path: "/productionManagement/productionManagement/productStructure/index", |
| | | }); |
| | | ElMessage.success("ä¿åæå"); |
| | | dataValue.loading = false; |
| | | }) |
| | | .catch(() => { |
| | | dataValue.loading = false; |
| | | }); |
| | | } else { |
| | | dataValue.loading = false; |
| | | } |
| | | }); |
| | | }; |
| | | const addItem2 = tempId => { |
| | | dataValue.dataList.map(item => { |
| | | }; |
| | | |
| | | const removeItem = (tempId:string) => { |
| | | // å
å°è¯ä»é¡¶å±å é¤ |
| | | const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId); |
| | | if (topIndex !== -1) { |
| | | dataValue.dataList.splice(topIndex, 1); |
| | | return; |
| | | } |
| | | |
| | | // éå½å é¤å项 |
| | | const delchildItem = (items: any[], tempId: any) => { |
| | | for (let i = 0; i < items.length; i++) { |
| | | const item = items[i]; |
| | | if (item.tempId === tempId) { |
| | | items.splice(i, 1); |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | if (delchildItem(item.children, tempId)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | dataValue.dataList.forEach(item => { |
| | | if (item.children && item.children.length > 0) { |
| | | delchildItem(item.children, tempId); |
| | | } |
| | | }); |
| | | }; |
| | | const addItem2 = tempId => { |
| | | dataValue.dataList.map(item => { |
| | | if (item.tempId === tempId) { |
| | | if (!item.children) { |
| | | item.children = []; |
| | | } |
| | | item.children.push({ |
| | | parentId: item.id || "", |
| | | parentTempId: item.tempId || "", |
| | | productName: "", |
| | | productId: "", |
| | | model: undefined, |
| | | productModelId: undefined, |
| | | processId: "", |
| | | processName: "", |
| | | unitQuantity: 0, |
| | | demandedQuantity: 0, |
| | | unit: "", |
| | | children: [], |
| | | |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | return; |
| | | } |
| | | addchildItem(item, tempId); |
| | | }); |
| | | }; |
| | | const addchildItem = (item: any, tempId: any) => { |
| | | if (item.tempId === tempId) { |
| | | console.log(item, "item"); |
| | | if (!item.children) { |
| | | item.children = []; |
| | | } |
| | |
| | | model: undefined, |
| | | productModelId: undefined, |
| | | processId: "", |
| | | processName: "", |
| | | unitQuantity: 0, |
| | | demandedQuantity: 0, |
| | | unit: "", |
| | | children: [], |
| | | |
| | | unit: "", |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | return; |
| | | return true; |
| | | } |
| | | addchildItem(item, tempId); |
| | | }); |
| | | }; |
| | | const addchildItem = (item: any, tempId: any) => { |
| | | if (item.tempId === tempId) { |
| | | console.log(item, "item"); |
| | | if (!item.children) { |
| | | item.children = []; |
| | | } |
| | | item.children.push({ |
| | | parentId: item.id || "", |
| | | parentTempId: item.tempId || "", |
| | | productName: "", |
| | | productId: "", |
| | | model: undefined, |
| | | productModelId: undefined, |
| | | processId: "", |
| | | unitQuantity: 0, |
| | | demandedQuantity: 0, |
| | | children: [], |
| | | unit: "", |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | for (let child of item.children) { |
| | | if (addchildItem(child, tempId)) { |
| | | return true; |
| | | if (item.children && item.children.length > 0) { |
| | | for (let child of item.children) { |
| | | if (addchildItem(child, tempId)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | return false; |
| | | }; |
| | | |
| | | const cancelEdit = () => { |
| | | dataValue.isEdit = false; |
| | | // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined); |
| | | fetchData(); |
| | | }; |
| | | const getPropPath = (row, field) => { |
| | | // 为æ¯ä¸ªrowçæå¯ä¸çè·¯å¾ |
| | | // 使ç¨row.idæç´¢å¼ä½ä¸ºå¯ä¸æ è¯ |
| | | let path = "dataList"; |
| | | |
| | | onMounted(async () => { |
| | | // ä»è·¯ç±åæ°åæ¾æ°æ® |
| | | tableData[0].productName = routeProductName.value as string; |
| | | tableData[0].model = routeProductModelName.value as string; |
| | | tableData[0].bomNo = routeBomNo.value as string; |
| | | // ç®åå®ç°ï¼ä½¿ç¨rowçidæä¸ä¸ªå¯ä¸æ è¯ |
| | | const uniqueId = row.id || Math.floor(Math.random() * 10000); |
| | | path += `.${uniqueId}`; |
| | | |
| | | // 订åæ
åµä¸ç¦ç¨ç¼è¾ |
| | | if (isOrderPage.value) { |
| | | return path + `.${field}`; |
| | | }; |
| | | |
| | | const cancelEdit = () => { |
| | | dataValue.isEdit = false; |
| | | } |
| | | // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined); |
| | | fetchData(); |
| | | }; |
| | | |
| | | // å
å 载工åºé项ï¼åå è½½æ°æ®ï¼ç¡®ä¿el-selectè½å¤æ£ç¡®åæ¾ |
| | | await fetchProcessOptions(); |
| | | await fetchData(); |
| | | }); |
| | | onMounted(async () => { |
| | | // ä»è·¯ç±åæ°åæ¾æ°æ® |
| | | tableData[0].productName = routeProductName.value as string; |
| | | tableData[0].model = routeProductModelName.value as string; |
| | | tableData[0].bomNo = routeBomNo.value as string; |
| | | |
| | | // 订åæ
åµä¸ç¦ç¨ç¼è¾ |
| | | if (isOrderPage.value) { |
| | | dataValue.isEdit = false; |
| | | } |
| | | |
| | | // å
å 载工åºé项ï¼åå è½½æ°æ®ï¼ç¡®ä¿el-selectè½å¤æ£ç¡®åæ¾ |
| | | await fetchProcessOptions(); |
| | | await fetchData(); |
| | | }); |
| | | </script> |
| | |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import {productionDispatch} from "@/api/productionManagement/productionOrder.js"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | // import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import {productionReport, productionReportUpdate} from "@/api/productionManagement/productionReporting.js"; |
| | | const { proxy } = getCurrentInstance() |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog v-model="dialogVisible" title="å·¥åéä»¶" width="50%" @close="closeDia"> |
| | | <div style="margin-bottom: 10px; text-align: right"> |
| | | <el-upload |
| | | v-model:file-list="fileList" |
| | | class="upload-demo" |
| | | :action="uploadUrl" |
| | | :on-success="handleUploadSuccess" |
| | | :on-error="handleUploadError" |
| | | :before-upload="beforeUpload" |
| | | name="file" |
| | | :show-file-list="false" |
| | | :headers="headers" |
| | | accept="image/*" |
| | | style="display: inline; margin-right: 10px" |
| | | > |
| | | <el-button type="primary">ä¸ä¼ å¾ç</el-button> |
| | | </el-upload> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :total="page.total" |
| | | :tableLoading="tableLoading" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="paginationSearch" |
| | | height="500" |
| | | /> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <filePreview ref="filePreviewRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, getCurrentInstance } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import filePreview from "@/components/filePreview/index.vue"; |
| | | import { |
| | | productWorkOrderFileAdd, |
| | | productWorkOrderFileDel, |
| | | productWorkOrderFileListPage, |
| | | } from "@/api/productionManagement/productWorkOrderFile.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["close"]); |
| | | |
| | | const dialogVisible = ref(false); |
| | | const currentWorkOrderId = ref(""); |
| | | const selectedRows = ref([]); |
| | | const filePreviewRef = ref(); |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æä»¶åç§°", |
| | | prop: "name", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | | clickFun: row => { |
| | | proxy.$download.name(row.url); |
| | | }, |
| | | }, |
| | | { |
| | | name: "é¢è§", |
| | | type: "text", |
| | | clickFun: row => { |
| | | filePreviewRef.value?.open(row.url); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const tableData = ref([]); |
| | | const fileList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const headers = ref({ |
| | | Authorization: "Bearer " + getToken(), |
| | | }); |
| | | const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); |
| | | |
| | | const beforeUpload = file => { |
| | | const isImage = file?.type?.startsWith("image/"); |
| | | if (!isImage) { |
| | | proxy.$modal.msgError("åªè½ä¸ä¼ å¾çæä»¶"); |
| | | } |
| | | return isImage; |
| | | }; |
| | | |
| | | const openDialog = row => { |
| | | dialogVisible.value = true; |
| | | currentWorkOrderId.value = row.id; |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | dialogVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | |
| | | const paginationSearch = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | productWorkOrderFileListPage({ |
| | | workOrderId: currentWorkOrderId.value, |
| | | current: page.current, |
| | | size: page.size, |
| | | }) |
| | | .then(res => { |
| | | tableData.value = res.data.records || []; |
| | | page.total = res.data.total || 0; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | function handleUploadSuccess(res) { |
| | | if (res.code == 200) { |
| | | const fileRow = { |
| | | name: res.data.originalName, |
| | | url: res.data.tempPath, |
| | | workOrderId: currentWorkOrderId.value, |
| | | }; |
| | | productWorkOrderFileAdd(fileRow).then(() => { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | getList(); |
| | | }); |
| | | } else { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | } |
| | | |
| | | function handleUploadError() { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | |
| | | const handleDelete = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | productWorkOrderFileDel(ids).then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
| | | |
| | |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <FilesDia ref="workOrderFilesRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | productWorkOrderPage, |
| | | updateProductWorkOrder, |
| | | addProductMain, |
| | | downProductWorkOrder, |
| | | } from "@/api/productionManagement/workOrder.js"; |
| | | import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import QRCode from "qrcode"; |
| | | import { getCurrentInstance, reactive, toRefs } from "vue"; |
| | | import FilesDia from "./components/filesDia.vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const tableColumn = ref([ |
| | |
| | | { |
| | | name: "æµè½¬å¡", |
| | | clickFun: row => { |
| | | showTransferCard(row); |
| | | downloadAndPrintWorkOrder(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | clickFun: row => { |
| | | openWorkOrderFiles(row); |
| | | }, |
| | | }, |
| | | { |
| | |
| | | const transferCardQrUrl = ref(""); |
| | | const transferCardRowData = ref(null); |
| | | const reportDialogVisible = ref(false); |
| | | const workOrderFilesRef = ref(null); |
| | | const userOptions = ref([]); |
| | | const reportForm = reactive({ |
| | | planQuantity: 0, |
| | |
| | | }); |
| | | }; |
| | | |
| | | // ä¸è½½å¹¶æå°å·¥åæµè½¬å¡ï¼æä»¶æµï¼ |
| | | const downloadAndPrintWorkOrder = async row => { |
| | | if (!row || !row.id) { |
| | | proxy.$modal.msgError("缺å°å·¥åIDï¼æ æ³ä¸è½½æµè½¬å¡"); |
| | | return; |
| | | } |
| | | const fileName = row.workOrderNo |
| | | ? `å·¥åæµè½¬å¡_${row.workOrderNo}.xlsx` |
| | | : "å·¥åæµè½¬å¡.xlsx"; |
| | | try { |
| | | // è°ç¨æ¥å£ï¼ä»¥ responseType: 'blob' è·åæä»¶æµ |
| | | const blob = await downProductWorkOrder(row.id); |
| | | |
| | | if (!blob) { |
| | | proxy.$modal.msgError("æªè·åå°æµè½¬å¡æä»¶"); |
| | | return; |
| | | } |
| | | |
| | | // å建 Blob URL |
| | | const fileBlob = |
| | | blob instanceof Blob ? blob : new Blob([blob], { type: blob.type || "application/octet-stream" }); |
| | | const url = window.URL.createObjectURL(fileBlob); |
| | | |
| | | // å建éè iframeï¼ç¨äºè§¦åæµè§å¨æå° |
| | | const iframe = document.createElement("iframe"); |
| | | iframe.style.position = "fixed"; |
| | | iframe.style.right = "0"; |
| | | iframe.style.bottom = "0"; |
| | | iframe.style.width = "0"; |
| | | iframe.style.height = "0"; |
| | | iframe.style.border = "0"; |
| | | iframe.src = url; |
| | | document.body.appendChild(iframe); |
| | | |
| | | iframe.onload = () => { |
| | | try { |
| | | iframe.contentWindow?.focus(); |
| | | iframe.contentWindow?.print(); |
| | | } catch (e) { |
| | | console.error("èªå¨è°ç¨æå°å¤±è´¥", e); |
| | | // éèæ±å
¶æ¬¡ï¼æå¼æ°çªå£ç±ç¨æ·æå¨æå° |
| | | window.open(url); |
| | | } |
| | | }; |
| | | } catch (e) { |
| | | console.error("ä¸è½½å·¥åæµè½¬å¡å¤±è´¥", e); |
| | | proxy.$modal.msgError("ä¸è½½å·¥åæµè½¬å¡å¤±è´¥"); |
| | | } |
| | | }; |
| | | |
| | | const showTransferCard = async row => { |
| | | transferCardRowData.value = row; |
| | | const qrContent = String(row.id); |
| | |
| | | reportDialogVisible.value = true; |
| | | }; |
| | | |
| | | const openWorkOrderFiles = row => { |
| | | workOrderFilesRef.value?.openDialog(row); |
| | | }; |
| | | |
| | | const handleReport = () => { |
| | | if (reportForm.planQuantity <= 0) { |
| | | ElMessageBox.alert("å¾
ç产æ°é为0ï¼æ æ³æ¥å·¥", "æç¤º", { |
| | |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | :disabled="operationType === 'edit'" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="model"> |
| | | <el-input v-model="form.model" placeholder="请è¾å
¥" clearable/> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="productModelId"> |
| | | <el-select v-model="form.productModelId" placeholder="è¯·éæ©" clearable :disabled="operationType === 'edit'" |
| | | filterable readonly @change="handleChangeModel"> |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½ï¼" prop="unit"> |
| | | <el-input v-model="form.unit" placeholder="请è¾å
¥" clearable/> |
| | | <el-input v-model="form.unit" placeholder="请è¾å
¥" disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | <script setup> |
| | | import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; |
| | | import {getOptions} from "@/api/procurementManagement/procurementLedger.js"; |
| | | import {productTreeList} from "@/api/basicData/product.js"; |
| | | import {modelList, productTreeList} from "@/api/basicData/product.js"; |
| | | import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js"; |
| | |
| | | checkName: "", |
| | | productName: "", |
| | | productId: "", |
| | | productModelId: "", |
| | | model: "", |
| | | testStandardId: "", |
| | | unit: "", |
| | |
| | | process: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkName: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | productId: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | model: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | productModelId: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | testStandardId: [{required: false, message: "è¯·éæ©ææ ", trigger: "change"}], |
| | | unit: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | const userList = ref([]); |
| | | const currentProductId = ref(0); |
| | | const testStandardOptions = ref([]); // ææ éæ©ä¸ææ¡æ°æ® |
| | | const modelOptions = ref([]); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | | currentProductId.value = value |
| | | form.value.productModelId = undefined; |
| | | form.value.unit = undefined; |
| | | modelOptions.value = []; |
| | | currentProductId.value = value |
| | | form.value.productName = findNodeById(productOptions.value, value); |
| | | if (currentProductId.value) { |
| | | getList(); |
| | | } |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res; |
| | | }) |
| | | if (currentProductId.value) { |
| | | getList(); |
| | | } |
| | | }; |
| | | |
| | | const handleChangeModel = (value) => { |
| | | form.value.model = modelOptions.value.find(item => item.id == value)?.model || ''; |
| | | form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || ''; |
| | | } |
| | | |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | :disabled="operationType === 'edit'" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="model"> |
| | | <el-input v-model="form.model" placeholder="请è¾å
¥" clearable/> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="productModelId"> |
| | | <el-select v-model="form.productModelId" placeholder="è¯·éæ©" clearable :disabled="operationType === 'edit'" |
| | | filterable readonly @change="handleChangeModel"> |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½ï¼" prop="unit"> |
| | | <el-input v-model="form.unit" placeholder="请è¾å
¥" clearable/> |
| | | <el-input v-model="form.unit" placeholder="请è¾å
¥" disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | <script setup> |
| | | import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; |
| | | import {getOptions} from "@/api/procurementManagement/procurementLedger.js"; |
| | | import {productTreeList} from "@/api/basicData/product.js"; |
| | | import {modelList, productTreeList} from "@/api/basicData/product.js"; |
| | | import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | |
| | | checkName: "", |
| | | productName: "", |
| | | productId: "", |
| | | productModelId: "", |
| | | model: "", |
| | | testStandardId: "", |
| | | unit: "", |
| | |
| | | process: [{ required: true, message: "请è¾å
¥å·¥åº", trigger: "blur" }], |
| | | checkName: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | productId: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | model: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | productModelId: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | testStandardId: [{required: false, message: "è¯·éæ©ææ ", trigger: "change"}], |
| | | unit: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | const tableLoading = ref(false); |
| | | const currentProductId = ref(0); |
| | | const testStandardOptions = ref([]); // ææ éæ©ä¸ææ¡æ°æ® |
| | | const modelOptions = ref([]); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | | currentProductId.value = value |
| | | form.value.productModelId = undefined; |
| | | form.value.unit = undefined; |
| | | modelOptions.value = []; |
| | | currentProductId.value = value |
| | | form.value.productName = findNodeById(productOptions.value, value); |
| | | if (currentProductId.value) { |
| | | getList(); |
| | | } |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res; |
| | | }) |
| | | if (currentProductId.value) { |
| | | getList(); |
| | | } |
| | | }; |
| | | |
| | | const handleChangeModel = (value) => { |
| | | form.value.model = modelOptions.value.find(item => item.id == value)?.model || ''; |
| | | form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || ''; |
| | | } |
| | | |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | :disabled="operationType === 'edit'" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="model"> |
| | | <el-input v-model="form.model" placeholder="请è¾å
¥" clearable/> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="productModelId"> |
| | | <el-select v-model="form.productModelId" placeholder="è¯·éæ©" clearable :disabled="operationType === 'edit'" |
| | | filterable readonly @change="handleChangeModel"> |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½ï¼" prop="unit"> |
| | | <el-input v-model="form.unit" placeholder="请è¾å
¥" clearable/> |
| | | <el-input v-model="form.unit" disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | <script setup> |
| | | import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; |
| | | import {getOptions} from "@/api/procurementManagement/procurementLedger.js"; |
| | | import {productTreeList} from "@/api/basicData/product.js"; |
| | | import {modelList, productTreeList} from "@/api/basicData/product.js"; |
| | | import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {qualityInspectParamDel, qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js"; |
| | | import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js"; |
| | | |
| | |
| | | checkName: "", |
| | | productName: "", |
| | | productId: "", |
| | | productModelId: "", |
| | | model: "", |
| | | testStandardId: "", |
| | | unit: "", |
| | |
| | | supplier: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | checkName: [{required: false, message: "请è¾å
¥", trigger: "blur"}], |
| | | productId: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | model: [{required: false, message: "请è¾å
¥", trigger: "blur"}], |
| | | productModelId: [{required: false, message: "è¯·éæ©äº§ååå·", trigger: "change"}], |
| | | testStandardId: [{required: false, message: "è¯·éæ©ææ ", trigger: "change"}], |
| | | unit: [{required: false, message: "请è¾å
¥", trigger: "blur"}], |
| | | quantity: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | |
| | | const productOptions = ref([]); |
| | | const currentProductId = ref(0); |
| | | const testStandardOptions = ref([]); // ææ éæ©ä¸ææ¡æ°æ® |
| | | const modelOptions = ref([]); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | | form.value.productModelId = undefined; |
| | | form.value.unit = undefined; |
| | | modelOptions.value = []; |
| | | currentProductId.value = value |
| | | form.value.productName = findNodeById(productOptions.value, value); |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res; |
| | | }) |
| | | if (currentProductId.value) { |
| | | getList(); |
| | | } |
| | | }; |
| | | |
| | | const handleChangeModel = (value) => { |
| | | form.value.model = modelOptions.value.find(item => item.id == value)?.model || ''; |
| | | form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || ''; |
| | | } |
| | | |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="carousel-cards"> |
| | | <button |
| | | v-if="canScrollLeft" |
| | | class="nav-button nav-button-left" |
| | | @click="scrollLeftFn" |
| | | > |
| | | <img src="@/assets/BI/jiantou.png" alt="å·¦ç®å¤´" /> |
| | | </button> |
| | | <div |
| | | class="cards-container" |
| | | :style="{ '--visible-count': visibleCount }" |
| | | ref="cardsContainerRef" |
| | | > |
| | | <div |
| | | v-for="(item, index) in items" |
| | | :key="index" |
| | | class="card-item" |
| | | > |
| | | <div v-if="item.icon" class="card-icon" :style="{ backgroundImage: `url(${item.icon})` }"></div> |
| | | <div class="card-title"> |
| | | <div class="card-label">{{ item.label }}</div> |
| | | <div class="card-value"> |
| | | <span class="value-number">{{ item.value }}</span> |
| | | <span class="value-unit">{{ item.unit }}</span> |
| | | </div> |
| | | <div v-if="item.rate ?? item.ratio ?? item.percent" class="card-rate"> |
| | | <span class="rate-value">{{ item.rate ?? item.ratio ?? item.percent }}%</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <button |
| | | v-if="canScrollRight" |
| | | class="nav-button nav-button-right" |
| | | @click="scrollRightFn" |
| | | > |
| | | <img src="@/assets/BI/jiantou.png" alt="å³ç®å¤´" /> |
| | | </button> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick, watch, computed } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | items: { |
| | | type: Array, |
| | | default: () => [], |
| | | validator: (value) => { |
| | | return value.every(item => |
| | | item && typeof item.label !== 'undefined' && |
| | | typeof item.value !== 'undefined' && |
| | | typeof item.unit !== 'undefined' |
| | | ) |
| | | } |
| | | }, |
| | | visibleCount: { |
| | | type: Number, |
| | | default: 3 |
| | | } |
| | | }) |
| | | |
| | | const cardsContainerRef = ref(null) |
| | | const currentScrollLeft = ref(0) |
| | | const maxScrollLeft = ref(0) |
| | | |
| | | // æ£æ¥æ¯å¦å¯ä»¥åå·¦æ»å¨ |
| | | const canScrollLeft = computed(() => { |
| | | return currentScrollLeft.value > 0 |
| | | }) |
| | | |
| | | // æ£æ¥æ¯å¦å¯ä»¥å峿»å¨ |
| | | const canScrollRight = computed(() => { |
| | | return currentScrollLeft.value < maxScrollLeft.value |
| | | }) |
| | | |
| | | // æ´æ°æ»å¨ç¶æ |
| | | const updateScrollState = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | currentScrollLeft.value = container.scrollLeft |
| | | maxScrollLeft.value = container.scrollWidth - container.clientWidth |
| | | } |
| | | |
| | | // åå·¦æ»å¨ |
| | | const scrollLeftFn = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | const scrollItems = Array.from(container.querySelectorAll('.card-item')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | const itemWidth = scrollItems[0]?.offsetWidth || 0 |
| | | const gap = 12 |
| | | const scrollDistance = itemWidth + gap |
| | | |
| | | container.scrollBy({ |
| | | left: -scrollDistance, |
| | | behavior: 'smooth' |
| | | }) |
| | | |
| | | // å»¶è¿æ´æ°ç¶æï¼çå¾
æ»å¨å¨ç»å®æ |
| | | setTimeout(() => { |
| | | updateScrollState() |
| | | }, 300) |
| | | } |
| | | |
| | | // å峿»å¨ |
| | | const scrollRightFn = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | const scrollItems = Array.from(container.querySelectorAll('.card-item')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | const itemWidth = scrollItems[0]?.offsetWidth || 0 |
| | | const gap = 12 |
| | | const scrollDistance = itemWidth + gap |
| | | |
| | | container.scrollBy({ |
| | | left: scrollDistance, |
| | | behavior: 'smooth' |
| | | }) |
| | | |
| | | // å»¶è¿æ´æ°ç¶æï¼çå¾
æ»å¨å¨ç»å®æ |
| | | setTimeout(() => { |
| | | updateScrollState() |
| | | }, 300) |
| | | } |
| | | |
| | | // çå¬ items ååï¼æ´æ°æ»å¨ç¶æ |
| | | watch(() => props.items, () => { |
| | | nextTick(() => { |
| | | updateScrollState() |
| | | }) |
| | | }, { deep: true }) |
| | | |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | updateScrollState() |
| | | // ç嬿»å¨äºä»¶ |
| | | const container = cardsContainerRef.value |
| | | if (container) { |
| | | container.addEventListener('scroll', updateScrollState) |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | // æ¸
çæ»å¨äºä»¶çå¬å¨ |
| | | const container = cardsContainerRef.value |
| | | if (container) { |
| | | container.removeEventListener('scroll', updateScrollState) |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .carousel-cards { |
| | | width: 100%; |
| | | overflow: hidden; |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .cards-container { |
| | | display: flex; |
| | | gap: 12px; |
| | | width: 100%; |
| | | overflow-x: auto; |
| | | overflow-y: hidden; |
| | | scrollbar-width: none; /* Firefox */ |
| | | -ms-overflow-style: none; /* IE and Edge */ |
| | | padding-bottom: 4px; |
| | | scroll-behavior: smooth; |
| | | } |
| | | |
| | | .cards-container::-webkit-scrollbar { |
| | | display: none; /* Chrome, Safari, Opera */ |
| | | } |
| | | |
| | | .nav-button { |
| | | position: absolute; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 32px; |
| | | height: 32px; |
| | | background: rgba(26, 88, 176, 0.6); |
| | | border: 1px solid rgba(26, 88, 176, 0.8); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | cursor: pointer; |
| | | z-index: 10; |
| | | transition: all 0.3s ease; |
| | | padding: 0; |
| | | } |
| | | |
| | | .nav-button:hover { |
| | | background: rgba(26, 88, 176, 0.8); |
| | | transform: translateY(-50%) scale(1.1); |
| | | } |
| | | |
| | | .nav-button-left { |
| | | left: -16px; |
| | | } |
| | | |
| | | .nav-button-left img { |
| | | width: 16px; |
| | | height: 16px; |
| | | transform: rotate(180deg); |
| | | } |
| | | |
| | | .nav-button-right { |
| | | right: -16px; |
| | | } |
| | | |
| | | .nav-button-right img { |
| | | width: 16px; |
| | | height: 16px; |
| | | } |
| | | |
| | | .card-item { |
| | | flex: 0 0 calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count)); |
| | | min-width: calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count)); |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(269deg, rgba(27,57,126,0.13) 0%, rgba(33,137,206,0.33) 98.13%, #24AFF4 100%); |
| | | border-radius: 8px 8px 8px 8px; |
| | | padding: 12px 16px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .card-item:hover { |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 80px; |
| | | height: 60px; |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | flex-shrink: 0; |
| | | margin-right: 12px; |
| | | } |
| | | |
| | | .card-title { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | margin-bottom: 4px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | width: 100%; |
| | | } |
| | | |
| | | .card-value { |
| | | display: flex; |
| | | align-items: baseline; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .card-rate { |
| | | margin-top: 4px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: rgba(255, 255, 255, 0.85); |
| | | } |
| | | |
| | | .rate-label { |
| | | opacity: 0.85; |
| | | } |
| | | |
| | | .rate-value { |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .value-number { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .value-unit { |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | font-weight: 400; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-radio-group |
| | | v-model="currentValue" |
| | | class="date-type-switch" |
| | | @change="handleChange" |
| | | > |
| | | <el-radio-button :label="1">å¨</el-radio-button> |
| | | <el-radio-button :label="2">æ</el-radio-button> |
| | | <el-radio-button :label="3">å£åº¦</el-radio-button> |
| | | </el-radio-group> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watch } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Number, |
| | | default: 1, // é»è®¤éä¸"å¨" |
| | | }, |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:modelValue', 'change']) |
| | | |
| | | const currentValue = ref(props.modelValue) |
| | | |
| | | // çå¬å¤é¨å¼åå |
| | | watch( |
| | | () => props.modelValue, |
| | | (newVal) => { |
| | | currentValue.value = newVal |
| | | } |
| | | ) |
| | | |
| | | // å¤çå¼åå |
| | | const handleChange = (value) => { |
| | | emit('update:modelValue', value) |
| | | emit('change', value) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .date-type-switch { |
| | | display: inline-flex; |
| | | } |
| | | |
| | | /* æªéä¸ç¶æçæ ·å¼ */ |
| | | .date-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | /* 第ä¸ä¸ªæé®å·¦ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | | border-top-left-radius: 4px; |
| | | border-bottom-left-radius: 4px; |
| | | } |
| | | |
| | | /* æåä¸ä¸ªæé®å³ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) { |
| | | border-top-right-radius: 4px; |
| | | border-bottom-right-radius: 4px; |
| | | } |
| | | |
| | | /* æé®ä¹é´çåé线 */ |
| | | .date-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | } |
| | | |
| | | /* éä¸ç¶æçæ ·å¼ */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | box-shadow: none; |
| | | } |
| | | |
| | | /* æ¬åææ */ |
| | | .date-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | } |
| | | |
| | | /* éä¸ç¶ææ¬å */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">{{ title }}</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | defineProps({ |
| | | title: { |
| | | type: String, |
| | | required: true, |
| | | default: '' |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-header { |
| | | background-image: url("@/assets/BI/kehuhetongback@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .panel-title { |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #D9ECFF; |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-radio-group |
| | | v-model="currentValue" |
| | | class="product-type-switch" |
| | | @change="handleChange" |
| | | > |
| | | <el-radio-button :label="1">åææ</el-radio-button> |
| | | <el-radio-button :label="3">åæå</el-radio-button> |
| | | <el-radio-button :label="2">æå</el-radio-button> |
| | | </el-radio-group> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watch } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Number, |
| | | default: 1, // é»è®¤éä¸"åææ" |
| | | }, |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:modelValue', 'change']) |
| | | |
| | | const currentValue = ref(props.modelValue) |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | (newVal) => { |
| | | currentValue.value = newVal |
| | | } |
| | | ) |
| | | |
| | | const handleChange = (value) => { |
| | | emit('update:modelValue', value) |
| | | emit('change', value) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .product-type-switch { |
| | | display: inline-flex; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | | border-top-left-radius: 4px; |
| | | border-bottom-left-radius: 4px; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) { |
| | | border-top-right-radius: 4px; |
| | | border-bottom-right-radius: 4px; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="åºå
¥åºè¶å¿" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="filters-row"> |
| | | |
| | | <ProductTypeSwitch v-model="productType" @change="handleFilterChange" /> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="lineLegend" |
| | | :series="lineSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import ProductTypeSwitch from './ProductTypeSwitch.vue' |
| | | import { productInOutAnalysis } from '@/api/viewIndex.js' |
| | | |
| | | const productType = ref(1) // 1=åææ 2=åæå 3=æå |
| | | |
| | | const chartStyle = { width: '100%', height: '130%' } |
| | | |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | top: '16%', |
| | | containLabel: true, |
| | | } |
| | | |
| | | const lineLegend = { |
| | | show: true, |
| | | top: '2%', |
| | | left: 'center', |
| | | itemGap: 24, |
| | | itemWidth: 12, |
| | | itemHeight: 12, |
| | | textStyle: { color: '#B8C8E0', fontSize: 14 }, |
| | | data: [ |
| | | { name: 'åºåº', itemStyle: { color: '#4EE4FF' } }, |
| | | { name: 'å
¥åº', itemStyle: { color: '#00D68F' } }, |
| | | ], |
| | | } |
| | | |
| | | const xAxis1 = ref([ |
| | | { |
| | | type: 'category', |
| | | data: [], |
| | | axisTick: { show: false }, |
| | | axisLine: { show: false, lineStyle: { color: 'rgba(184, 200, 224, 0.3)' } }, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12 }, |
| | | splitLine: { show: false, lineStyle: { type: 'dashed', color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | }, |
| | | ]) |
| | | |
| | | const yAxis1 = [ |
| | | { |
| | | type: 'value', |
| | | name: 'åä½: ä»¶', |
| | | nameTextStyle: { color: '#B8C8E0', fontSize: 12, padding: [0, 0, 0, 0] }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12 }, |
| | | splitLine: { lineStyle: { color: '#B8C8E0' } }, |
| | | }, |
| | | ] |
| | | |
| | | const lineSeries = ref([ |
| | | { |
| | | name: 'åºåº', |
| | | type: 'line', |
| | | smooth: false, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(11, 137, 254, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(11, 137, 254, 1)', borderWidth: 0 }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(11, 137, 254, 0.40)' }, |
| | | { offset: 1, color: 'rgba(11, 137, 254, 0.05)' }, |
| | | ]), |
| | | }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | { |
| | | name: 'å
¥åº', |
| | | type: 'line', |
| | | smooth: false, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(11, 249, 254, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(11, 249, 254, 1)', borderWidth: 0 }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(11, 249, 254, 0.5)' }, |
| | | { offset: 1, color: 'rgba(11, 249, 254, 0.05)' }, |
| | | ]), |
| | | }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'line' }, |
| | | borderWidth: 1, |
| | | textStyle: { fontSize: 12 }, |
| | | formatter(params) { |
| | | let result = params[0].axisValue + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `${item.marker} ${item.seriesName}: ${item.value} ä»¶<br/>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | |
| | | const fetchData = () => { |
| | | productInOutAnalysis({ type: productType.value }) |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | const list = res.data |
| | | xAxis1.value[0].data = list.map((d) => d.date) |
| | | lineSeries.value[0].data = list.map((d) => Number(d.outCount) || 0) |
| | | lineSeries.value[1].data = list.map((d) => Number(d.inCount) || 0) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·å产ååºå
¥åºåæå¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | const handleFilterChange = () => { |
| | | fetchData() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .filters-row { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 428px; |
| | | } |
| | | </style> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <!-- 设å¤ç»è®¡ --> |
| | | <div class="equipment-stats"> |
| | | <div class="equipment-header"> |
| | | <img |
| | | src="@/assets/BI/shujutongjiicon@2x.png" |
| | | alt="徿 " |
| | | class="equipment-icon" |
| | | /> |
| | | <span class="equipment-title">产åå¨è½¬å¤©æ°</span> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="barLegend" |
| | | :series="barSeries1" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { productTurnoverDays } from '@/api/viewIndex.js' |
| | | |
| | | const chartStyle = { width: '100%', height: '100%' } |
| | | const grid = { left: '3%', right: '4%', bottom: '3%', top: '4%', containLabel: true } |
| | | const barLegend = { show: false, textStyle: { color: '#B8C8E0' }, data: ['å¨è½¬å¤©æ°'] } |
| | | const barSeries1 = ref([ |
| | | { |
| | | name: 'å¨è½¬å¤©æ°', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | barWidth: 30, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, y: 1, x2: 0, y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(0,164,237,0)' }, |
| | | { offset: 1, color: '#4EE4FF' }, |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'shadow' }, |
| | | formatter(params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `<div>${item.marker} ${item.seriesName}: ${item.value} 天</div>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | const xAxis1 = ref([{ type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }]) |
| | | const yAxis1 = [{ type: 'value', axisLabel: { color: '#B8C8E0' } }] |
| | | |
| | | const fetchData = () => { |
| | | productTurnoverDays() |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | const list = res.data |
| | | xAxis1.value[0].data = list.map((d) => d.name) |
| | | barSeries1.value[0].data = list.map((d) => Number(d.value) || 0) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·å产åå¨è½¬å¤©æ°å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .equipment-stats { |
| | | border: 1px solid #1a58b0; |
| | | padding: 0 18px 18px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .equipment-header { |
| | | font-weight: 500; |
| | | font-size: 21px; |
| | | display: flex; |
| | | border-bottom: 1px solid; |
| | | border-image: linear-gradient( |
| | | 270deg, |
| | | rgba(0, 126, 255, 0) 0%, |
| | | rgba(0, 126, 255, 0.4549) 35%, |
| | | #007eff 78%, |
| | | #007eff 100% |
| | | ) |
| | | 1; |
| | | padding-bottom: 2px; |
| | | } |
| | | |
| | | .equipment-title { |
| | | font-weight: 500; |
| | | font-size: 18px; |
| | | background: linear-gradient(360deg, #056dff 0%, #43e8fc 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | line-height: 50px; |
| | | } |
| | | |
| | | .equipment-icon { |
| | | width: 50px; |
| | | height: 50px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <!-- é¡¶é¨ç»è®¡å¡ç --> |
| | | <div class="stats-cards"> |
| | | <div |
| | | v-for="item in statItems" |
| | | :key="item.name" |
| | | class="stat-card" |
| | | > |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | | <span class="card-label">{{ item.name }}</span> |
| | | <span class="card-value">{{ item.value }}</span> |
| | | <div class="card-compare" :class="compareClass(Number(item.rate))"> |
| | | <span>忝</span> |
| | | <span class="compare-value">{{ formatPercent(item.rate) }}</span> |
| | | <span class="compare-icon">{{ Number(item.rate) >= 0 ? 'â' : 'â' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js' |
| | | |
| | | const statItems = ref([]) |
| | | |
| | | const formatPercent = (val) => { |
| | | const num = Number(val) || 0 |
| | | return `${num.toFixed(2)}%` |
| | | } |
| | | |
| | | const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down') |
| | | |
| | | const fetchData = () => { |
| | | salesPurchaseStorageProductCount() |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | statItems.value = res.data.map((item) => ({ |
| | | name: item.name, |
| | | value: item.value, |
| | | rate: item.rate, |
| | | })) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åéå®/éè´/å¨åäº§åæ°å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .stats-cards { |
| | | display: flex; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .stat-card { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | background-image: url('@/assets/BI/border@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | height: 142px; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin: 20px 20px 0 10px; |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .card-value { |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 19px; |
| | | color: rgba(208, 231, 255, 0.7); |
| | | } |
| | | |
| | | .card-compare { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-size: 15px; |
| | | color: #d0e7ff; |
| | | } |
| | | |
| | | .card-compare > span:first-child { |
| | | font-size: 13px; |
| | | opacity: 0.8; |
| | | } |
| | | |
| | | .compare-value { |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .compare-icon { |
| | | font-size: 14px; |
| | | position: relative; |
| | | top: -1px; /* 轻微ä¸ç§»ï¼è®©ç®å¤´ä¸æååç´å±
ä¸å¯¹é½ */ |
| | | } |
| | | |
| | | .compare-up .compare-value, |
| | | .compare-up .compare-icon { |
| | | color: #00c853; |
| | | } |
| | | |
| | | .compare-down .compare-value, |
| | | .compare-down .compare-icon { |
| | | color: #ff5252; |
| | | } |
| | | |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="产åéè´éé¢åæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background"></div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :legend="landLegend" |
| | | :series="landSeries" |
| | | :tooltip="landTooltip" |
| | | :color="landColors" |
| | | :options="pieOptions" |
| | | style="height: 320px" |
| | | class="land-chart" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | import { rawMaterialPurchaseAmountRatio } from '@/api/viewIndex.js' |
| | | |
| | | /** |
| | | * @introduction ææ°ç»ä¸keyå¼ç¸åçé£ä¸é¡¹æååºæ¥ï¼ç»æä¸ä¸ªå¯¹è±¡ |
| | | * @param {åæ°ç±»å} array ä¼ å
¥çæ°ç» [{a:"1",b:"2"},{a:"2",b:"3"}] |
| | | * @param {åæ°ç±»å} key 屿§å a |
| | | * @return {è¿åç±»å说æ} |
| | | */ |
| | | function array2obj(array, key) { |
| | | const resObj = {} |
| | | for (let i = 0; i < array.length; i++) { |
| | | resObj[array[i][key]] = array[i] |
| | | } |
| | | return resObj |
| | | } |
| | | |
| | | // æ°æ®åè¡¨ï¼æ¥èªæ¥å£ï¼ |
| | | const dataList = ref([]) |
| | | |
| | | // å¡çæ°æ® |
| | | const cardItems = ref([]) |
| | | |
| | | // é¢è²å表 |
| | | const landColors = ['#26FFCB', '#24CBFF', '#35FBF4', '#2651FF', '#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF'] |
| | | |
| | | const landObjData = computed(() => array2obj(dataList.value, 'name')) |
| | | |
| | | // å¾ä¾é
ç½®ï¼å³ä¾§ç«æï¼ |
| | | const landLegend = computed(() => { |
| | | const data = dataList.value.map((d, idx) => ({ |
| | | name: d.name, |
| | | icon: 'circle', |
| | | textStyle: { |
| | | fontSize: 18, |
| | | color: landColors[idx % landColors.length], |
| | | }, |
| | | })) |
| | | |
| | | return { |
| | | orient: 'vertical', |
| | | top: 'center', |
| | | left: '52%', |
| | | itemGap: 30, |
| | | data: data, |
| | | formatter: function (name) { |
| | | const item = landObjData.value[name] |
| | | if (!item) return name |
| | | return `{title|${name}}{value|${item.value}}{unit|å
}{percent|${item.rate}}{unit|%}` |
| | | }, |
| | | textStyle: { |
| | | rich: { |
| | | value: { |
| | | color: '#43e8fc', |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 10], |
| | | }, |
| | | unit: { |
| | | color: '#82baff', |
| | | fontSize: 12, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | percent: { |
| | | color: '#43e8fc', |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | | title: { |
| | | fontSize: 12, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | | }, |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | // æç¤ºæ¡ |
| | | const landTooltip = { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c}å
({d}%)', |
| | | } |
| | | |
| | | // åå±ç¯å½¢é¥¼å¾ |
| | | const landSeries = ref([ |
| | | { |
| | | name: '产åéè´éé¢åæ', |
| | | type: 'pie', |
| | | radius: ['40%', '60%'], |
| | | center: ['25%', '50%'], |
| | | itemStyle: { |
| | | borderColor: '#0a1c3a', |
| | | borderWidth: 2, |
| | | color: function (params) { |
| | | return landColors[params.dataIndex % landColors.length] |
| | | }, |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | minAngle: 15, |
| | | data: dataList.value, |
| | | animationType: 'scale', |
| | | animationEasing: 'elasticOut', |
| | | animationDelay: function () { |
| | | return Math.random() * 200 |
| | | }, |
| | | }, |
| | | { |
| | | // å
å |
| | | type: 'pie', |
| | | radius: ['40%', '45%'], |
| | | center: ['25%', '50%'], |
| | | silent: true, |
| | | label: { |
| | | show: false, |
| | | }, |
| | | labelLine: { |
| | | show: false, |
| | | }, |
| | | itemStyle: { |
| | | color: 'rgba(0, 127, 255, 0.25)', |
| | | }, |
| | | data: [1], |
| | | }, |
| | | ]) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | const fetchData = () => { |
| | | rawMaterialPurchaseAmountRatio() |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | const items = res.data |
| | | cardItems.value = items.map((item) => ({ |
| | | label: item.name, |
| | | value: item.value, |
| | | unit: 'å
', |
| | | rate: item.rate, |
| | | })) |
| | | dataList.value = items.map((it) => ({ |
| | | name: it.name, |
| | | value: parseFloat(it.value) || 0, |
| | | rate: it.rate, |
| | | children: [], |
| | | })) |
| | | landSeries.value[0].data = dataList.value |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·ååææéè´éé¢å æ¯å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | background: transparent; |
| | | } |
| | | |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | left: 25%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -50%); |
| | | width: 310px; |
| | | height: 310px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="产åéå®éé¢åæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background"></div> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | | :legend="pieLegend" |
| | | :series="pieSeries" |
| | | :tooltip="pieTooltip" |
| | | :color="pieColors" |
| | | :options="pieOptions" |
| | | style="height: 320px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { productSalesAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | |
| | | /** |
| | | * @introduction ææ°ç»ä¸keyå¼ç¸åçé£ä¸é¡¹æååºæ¥ï¼ç»æä¸ä¸ªå¯¹è±¡ |
| | | * @param {åæ°ç±»å} array ä¼ å
¥çæ°ç» [{a:"1",b:"2"},{a:"2",b:"3"}] |
| | | * @param {åæ°ç±»å} key 屿§å a |
| | | * @return {è¿åç±»å说æ} |
| | | */ |
| | | function array2obj(array, key) { |
| | | const resObj = {} |
| | | for (let i = 0; i < array.length; i++) { |
| | | resObj[array[i][key]] = array[i] |
| | | } |
| | | return resObj |
| | | } |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const echartsRef = ref(null) |
| | | const pieDatas = ref([]) |
| | | const pieColors = ['#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF', '#43e8fc', '#27EBE7'] |
| | | |
| | | const pieObjData = computed(() => array2obj(pieDatas.value, 'name')) |
| | | |
| | | const pieLegend = computed(() => { |
| | | const data = pieDatas.value.map((d, idx) => ({ |
| | | name: d.name, |
| | | icon: 'circle', |
| | | textStyle: { |
| | | fontSize: 18, |
| | | color: pieColors[idx % pieColors.length], |
| | | }, |
| | | })) |
| | | |
| | | return { |
| | | orient: 'vertical', |
| | | top: 'center', |
| | | left: '52%', |
| | | itemGap: 30, |
| | | data: data, |
| | | formatter: function (name) { |
| | | const item = pieObjData.value[name] |
| | | if (!item) return name |
| | | return `{title|${name}}{value|${item.value}}{unit|å
}{percent|${item.rate}}{unit|%}` |
| | | }, |
| | | textStyle: { |
| | | rich: { |
| | | value: { |
| | | color: '#43e8fc', |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 10], |
| | | }, |
| | | unit: { |
| | | color: '#82baff', |
| | | fontSize: 12, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | percent: { |
| | | color: '#43e8fc', |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | | title: { |
| | | fontSize: 12, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | | }, |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | const pieTooltip = { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c}å
({d}%)', |
| | | } |
| | | |
| | | const pieSeries = computed(() => [ |
| | | { |
| | | name: '产åéå®éé¢åæ', |
| | | type: 'pie', |
| | | radius: '60%', |
| | | center: ['25%', '50%'], |
| | | itemStyle: { |
| | | borderColor: '#0a1c3a', |
| | | borderWidth: 2, |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | minAngle: 15, |
| | | data: pieDatas.value, |
| | | animationType: 'scale', |
| | | animationEasing: 'elasticOut', |
| | | animationDelay: function () { |
| | | return Math.random() * 200 |
| | | }, |
| | | }, |
| | | ]) |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | const cardItems = ref([]) |
| | | |
| | | const fetchData = () => { |
| | | productSalesAnalysis() |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | const items = res.data |
| | | cardItems.value = items.map((item) => ({ |
| | | label: item.name, |
| | | value: item.value, |
| | | unit: 'å
', |
| | | rate: item.rate, |
| | | })) |
| | | pieDatas.value = items.map((item) => ({ |
| | | name: item.name, |
| | | value: parseFloat(item.value) || 0, |
| | | rate: item.rate, |
| | | })) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·å产åéå®éé¢åæå¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | } |
| | | .pie-background { |
| | | position: absolute; |
| | | left: 25%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -50%); |
| | | width: 310px; |
| | | height: 310px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="scale-container"> |
| | | <div class="data-dashboard" :style="{ transform: `scale(${scaleRatio})` }"> |
| | | <!-- å
¨å±æé® - ç§»å¨å°å·¦ä¸è§ --> |
| | | <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? 'éåºå
¨å±' : 'å
¨å±æ¾ç¤º'"> |
| | | <svg v-if="!isFullscreen" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/> |
| | | </svg> |
| | | <svg v-else width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/> |
| | | </svg> |
| | | </button> |
| | | |
| | | <!-- 顶鍿 颿 --> |
| | | <div class="dashboard-header"> |
| | | <div class="factory-name">PSI æ°æ®åæ</div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | | <div class="dashboard-content"> |
| | | <!-- 左侧åºå --> |
| | | <div class="left-panel"> |
| | | <LeftTop /> |
| | | |
| | | <LeftBottom /> |
| | | </div> |
| | | |
| | | <!-- ä¸é´åºå --> |
| | | <div class="center-panel"> |
| | | <CenterTop /> |
| | | <CenterCenter/> |
| | | <CenterBottom /> |
| | | </div> |
| | | |
| | | <!-- å³ä¾§åºå --> |
| | | <div class="right-panel"> |
| | | <RightBottom /> |
| | | <RightTop /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import LeftBottom from './components/left-bottom.vue' |
| | | import CenterCenter from './components/center-center.vue' |
| | | import RightTop from '../dataDashboard/components/basic/right-top.vue' |
| | | import RightBottom from '../dataDashboard/components/basic/right-bottom.vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import LeftTop from './components/left-top.vue' |
| | | import CenterTop from './components/center-top.vue' |
| | | import CenterBottom from './components/center-bottom.vue' |
| | | |
| | | // å
¨å±ç¸å
³ç¶æ |
| | | const isFullscreen = ref(false); |
| | | |
| | | // ç¼©æ¾æ¯ä¾ |
| | | const scaleRatio = ref(1) |
| | | // 设计尺寸ï¼åºå尺寸ï¼- æ ¹æ®å®é
è®¾è®¡ç¨¿è°æ´ |
| | | const designWidth = 1920 |
| | | const designHeight = 1080 |
| | | |
| | | // ç¨æ·store |
| | | const userStore = useUserStore() |
| | | |
| | | // 计ç®ç¼©æ¾æ¯ä¾ |
| | | const calculateScale = () => { |
| | | const container = document.querySelector('.scale-container') |
| | | if (!container) return |
| | | |
| | | // è·å容å¨çå®é
尺寸 |
| | | const rect = container.getBoundingClientRect?.() |
| | | const containerWidth = container.clientWidth || rect?.width || window.innerWidth |
| | | const containerHeight = container.clientHeight || rect?.height || window.innerHeight |
| | | |
| | | // 计ç®å®½é«ç¼©æ¾æ¯ä¾ï¼åè¾å°å¼ä»¥ä¿è¯å
容宿´æ¾ç¤ºï¼çæ¯ç¼©æ¾ï¼ |
| | | const scaleX = containerWidth / designWidth |
| | | const scaleY = containerHeight / designHeight |
| | | scaleRatio.value = Math.min(scaleX, scaleY) |
| | | } |
| | | |
| | | // çªå£å¤§å°ååå¤ç |
| | | const handleResize = () => { |
| | | // å»¶è¿æ§è¡ï¼ç¡®ä¿DOMæ´æ°å®æ |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 100) |
| | | } |
| | | |
| | | // å
¨å±åè½å®ç° - é对scale-containerå
ç´ |
| | | const toggleFullscreen = () => { |
| | | const element = document.querySelector('.scale-container') |
| | | |
| | | if (!element) return |
| | | |
| | | if (!isFullscreen.value) { |
| | | if (element.requestFullscreen) { |
| | | element.requestFullscreen() |
| | | } else if (element.webkitRequestFullscreen) { |
| | | element.webkitRequestFullscreen() |
| | | } else if (element.msRequestFullscreen) { |
| | | element.msRequestFullscreen() |
| | | } |
| | | } else { |
| | | if (document.exitFullscreen) { |
| | | document.exitFullscreen() |
| | | } else if (document.webkitExitFullscreen) { |
| | | document.webkitExitFullscreen() |
| | | } else if (document.msExitFullscreen) { |
| | | document.msExitFullscreen() |
| | | } |
| | | } |
| | | } |
| | | |
| | | // çå¬å
¨å±ååäºä»¶ |
| | | const handleFullscreenChange = () => { |
| | | const fullscreenElement = document.fullscreenElement || |
| | | document.webkitFullscreenElement || |
| | | document.msFullscreenElement |
| | | isFullscreen.value = fullscreenElement && fullscreenElement.classList.contains('scale-container') |
| | | |
| | | // å
¨å±ç¶æååæ¶ï¼å»¶è¿éæ°è®¡ç®ç¼©æ¾æ¯ä¾ï¼ç¡®ä¿DOMæ´æ°å®æï¼ |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 200) |
| | | } |
| | | |
| | | // çå½å¨æé©å |
| | | onMounted(() => { |
| | | // 使ç¨nextTickç¡®ä¿DOMå®å
¨æ¸²æåååå§å |
| | | nextTick(() => { |
| | | // 计ç®åå§ç¼©æ¾æ¯ä¾ |
| | | calculateScale() |
| | | }) |
| | | |
| | | window.addEventListener('resize', handleResize) |
| | | window.addEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | window.removeEventListener('resize', handleResize) |
| | | window.removeEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | // ç§»é¤æä»¬æ·»å çautofitå¨æè°æ´çå¬å¨ |
| | | if (window._autofitUpdateHandler) { |
| | | window.removeEventListener('resize', window._autofitUpdateHandler) |
| | | delete window._autofitUpdateHandler |
| | | } |
| | | // å
³éautofit |
| | | autofit.off() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* å¤é¨ç¼©æ¾å®¹å¨ - å æ®æ´ä¸ªè§å£ */ |
| | | .scale-container { |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | height: calc(100vh - 84px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: #000; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* å
é¨å
容åºå - åºå®è®¾è®¡å°ºå¯¸ */ |
| | | .data-dashboard { |
| | | position: relative; |
| | | width: 1920px; |
| | | height: 1080px; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | transform-origin: center center; |
| | | } |
| | | |
| | | /* å
¨å±ç¶æçæ ·å¼ - ä½ç¨äºscale-container */ |
| | | .scale-container:fullscreen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | /* Webkitæµè§å¨åç¼ */ |
| | | .scale-container:-webkit-full-screen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | /* MSæµè§å¨åç¼ */ |
| | | .scale-container:-ms-fullscreen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | |
| | | .dashboard-header { |
| | | position: relative; |
| | | z-index: 1; |
| | | height: 86px; |
| | | background-image: url("@/assets/BI/biaoti.png"); |
| | | background-size: cover; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 52px; |
| | | color: #FFFFFF; |
| | | top: 16px; |
| | | position: absolute; |
| | | } |
| | | |
| | | .fullscreen-btn { |
| | | position: absolute; |
| | | top: 10px; |
| | | left: 20px; |
| | | width: 40px; |
| | | height: 40px; |
| | | background: rgba(0, 20, 60, 0.8); |
| | | border: 1px solid rgba(0, 212, 255, 0.3); |
| | | border-radius: 6px; |
| | | color: #00d4ff; |
| | | cursor: pointer; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s; |
| | | z-index: 10000; |
| | | } |
| | | |
| | | .fullscreen-btn:hover { |
| | | background: rgba(0, 30, 90, 0.9); |
| | | border-color: rgba(0, 212, 255, 0.5); |
| | | } |
| | | |
| | | .dashboard-content { |
| | | position: relative; |
| | | z-index: 1; |
| | | display: flex; |
| | | gap: 30px; |
| | | padding: 0 30px; |
| | | height: calc(100% - 86px); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* ç¡®ä¿å颿¿è½å¤æ£ç¡®æ¾ç¤º */ |
| | | .left-panel, .center-panel, .right-panel { |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .left-panel, |
| | | .right-panel { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 24px; |
| | | width: 520px; |
| | | } |
| | | |
| | | .center-panel { |
| | | flex: 1.5; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | </style> |
| | |
| | | <div> |
| | | <PanelHeader title="人ååå¸" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | | :legend="pieLegend" |
| | | :series="pieSeries" |
| | | :tooltip="pieTooltip" |
| | | :color="pieColors" |
| | | :options="pieOptions" |
| | | style="height: 320px" |
| | | /> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background" :style="{ backgroundImage: `url(${roseBorderImg})` }"></div> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | | :legend="pieLegend" |
| | | :series="pieSeries" |
| | | :tooltip="pieTooltip" |
| | | :color="pieColors" |
| | | :options="pieOptions" |
| | | style="height: 320px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | import { deptStaffDistribution } from '@/api/viewIndex.js' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import roseBorderImg from '@/assets/BI/ç«ç°å¾è¾¹æ¡.png' |
| | | |
| | | /** |
| | | * @introduction ææ°ç»ä¸keyå¼ç¸åçé£ä¸é¡¹æååºæ¥ï¼ç»æä¸ä¸ªå¯¹è±¡ |
| | |
| | | center: ['20%', '50%'], |
| | | itemStyle: { |
| | | borderColor: '#0a1c3a', |
| | | borderWidth: 2, |
| | | borderWidth:5, |
| | | }, |
| | | label: { |
| | | show: false |
| | |
| | | width: 100%; |
| | | height: 370px; |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | background: transparent; |
| | | } |
| | | |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-113%, -50%); |
| | | width: 360px; |
| | | height: 360px; |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | } |
| | | </style> |
| | |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | height: 240px; |
| | | padding-top: 0px; |
| | | } |
| | | |
| | | .equipment-header { |
| | |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | clearable |
| | | filterable |
| | | popper-class="customer-select-dropdown" |
| | | :teleported="false" |
| | | @change="handleFilterChange" |
| | | > |
| | | <el-option |
| | |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>` |
| | | result += `<div>${item.marker} ${item.seriesName}: ${item.value}</div>` |
| | | }) |
| | | return result |
| | | }, |
| | |
| | | height: 478px; |
| | | } |
| | | </style> |
| | | |
| | | <style> |
| | | /* å
¨å±æ¨¡å¼ä¸ä¸ææ¡å±çº§ä¿®å¤ */ |
| | | .customer-select-dropdown { |
| | | z-index: 10001 !important; |
| | | } |
| | | |
| | | /* ç¡®ä¿å¨å
¨å±å®¹å¨å
ç䏿æ¡ä¹è½æ£å¸¸æ¾ç¤º */ |
| | | .scale-container:fullscreen .customer-select-dropdown, |
| | | .scale-container:-webkit-full-screen .customer-select-dropdown, |
| | | .scale-container:-ms-fullscreen .customer-select-dropdown { |
| | | z-index: 10001 !important; |
| | | } |
| | | </style> |
| | |
| | | <div> |
| | | <PanelHeader title="产å大类" /> |
| | | <div class="panel-item-customers"> |
| | | <div style="height: 70%"> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background"></div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | |
| | | :series="landSeries" |
| | | :tooltip="landTooltip" |
| | | :color="landColors" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | :options="pieOptions" |
| | | style="height: 100%" |
| | | class="land-chart" |
| | | /> |
| | |
| | | |
| | | // æç¤ºæ¡ |
| | | const landTooltip = { |
| | | triggerOn: 'click', |
| | | // triggerOn: 'hover', |
| | | alwaysShowContent: true, |
| | | position: function (pt) { |
| | | return [pt[0], 130] |
| | | }, |
| | | formatter: function (params) { |
| | | return `${params.name} (${params.value}ç±»)` |
| | | }, |
| | | } |
| | | |
| | | // åå±ç¯å½¢é¥¼å¾ |
| | | const landSeries = ref([ |
| | | { |
| | | name: 'å¤å', |
| | | name: '产å大类', |
| | | type: 'pie', |
| | | radius: ['35%', '55%'], |
| | | center: ['50%', '50%'], |
| | |
| | | lineHeight: 18, |
| | | rich: { |
| | | ...dotRich, |
| | | parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20 }, |
| | | parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20, overflow: 'break' }, |
| | | child: { fontSize: 12, color: '#fff', lineHeight: 18 }, |
| | | }, |
| | | formatter: function (params) { |
| | | const children = params?.data?.children || [] |
| | | const parentName = params?.data?.name || '' |
| | | const parentValue = params?.data?.value ?? 0 |
| | | const rawVal = params?.data?.value |
| | | const parentValue = typeof rawVal === 'number' && !Number.isNaN(rawVal) ? rawVal : (Number(rawVal) || 0) |
| | | const dotKey = `dot${(params?.dataIndex || 0) % landColors.length}` |
| | | const dot = `{${dotKey}|} ` |
| | | if (!children.length) return `${dot}{parent|${parentName} ${parentValue}}` |
| | | // å°åç¹ + ç¶çº§ name + ç¶çº§ valueï¼æ¢è¡å±ç¤º children ç name + value |
| | | return [ |
| | | `${dot}{parent|${parentName} ${parentValue}}`, |
| | | ...children.map((c) => `{child|${c.name}}`), |
| | | ].join('\n') |
| | | const parentLine = `${dot}{parent|${parentName} (${parentValue}ç±»)}` |
| | | if (!children.length) return parentLine |
| | | // ç¶çº§å
¨é¨æ¾ç¤ºï¼å级æå¤ 5 个ï¼è¶
åºæ¾ç¤ºçç¥å· |
| | | const displayed = children.slice(0, 5).map((c) => `{child|${c.name}}`) |
| | | if (children.length > 5) displayed.push('{child|â¦}') |
| | | return [parentLine, ...displayed].join('\n') |
| | | }, |
| | | }, |
| | | labelLine: { |
| | |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '150%', |
| | | height: '126%', |
| | | } |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | const loadData = async () => { |
| | |
| | | width: 100%; |
| | | height: 420px; |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | background: transparent; |
| | | } |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -39%); |
| | | width: 360px; |
| | | height: 360px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | } |
| | | </style> |
| | |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>` |
| | | result += `<div>${item.marker} ${item.seriesName}: ${item.value}å
</div>` |
| | | }) |
| | | return result |
| | | }, |
| | |
| | | textStyle: { fontSize: '100%' }, |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | result += `<div style="">${params[0].marker}${params[0].value}</div>` |
| | | result += `<div>${params[0].marker}${params[0].value}å
</div>` |
| | | return result |
| | | }, |
| | | } |
| | |
| | | <div class="dashboard-content"> |
| | | <!-- 左侧åºå --> |
| | | <div class="left-panel"> |
| | | <!-- 客æ·ä¿¡æ¯ç»è®¡åæ --> |
| | | <LeftTop /> |
| | | |
| | | <!-- è´¨éç»è®¡ --> |
| | | <LeftBottom /> |
| | | </div> |
| | | |
| | |
| | | |
| | | <!-- å³ä¾§åºå --> |
| | | <div class="right-panel"> |
| | | <!-- åºæ¶åºä»ç»è®¡ --> |
| | | <RightTop /> |
| | | |
| | | <!-- 忬¾ä¸å¼ç¥¨åæ --> |
| | | <RightBottom /> |
| | | </div> |
| | | </div> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å±é©æºåç§°ï¼</span> |
| | | <el-input v-model="searchForm.name" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å±é©æºåç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span class="search_title ml10">å±é©æºç±»åï¼</span> |
| | | <el-select v-model="searchForm.type" |
| | | clearable |
| | | @change="handleQuery" |
| | | style="width: 240px"> |
| | | <el-option v-for="item in knowledgeTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.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 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" |
| | | :total="page.total" |
| | | :rowClassName="getRowClass"></PIMTable> |
| | | </div> |
| | | <!-- æ°å¢/ç¼è¾ç¥è¯å¼¹çª --> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="800px" |
| | | :close-on-click-modal="false"> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å±é©æºç¼ç " |
| | | prop="code"> |
| | | <el-input v-model="form.code" |
| | | placeholder="请è¾å
¥å±é©æºç¼ç " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å±é©æºåç§°" |
| | | prop="name"> |
| | | <el-input v-model="form.name" |
| | | placeholder="请è¾å
¥å±é©æºåç§°" /> |
| | | </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="controlMeasures"> |
| | | <el-input v-model="form.controlMeasures" |
| | | placeholder="请è¾å
¥ç®¡æ§æªæ½" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å±é©æºç±»å" |
| | | prop="type"> |
| | | <el-select v-model="form.type" |
| | | placeholder="è¯·éæ©å±é©æºç±»å" |
| | | style="width: 100%"> |
| | | <el-option v-for="item in knowledgeTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåæ°é" |
| | | prop="stockQty"> |
| | | <el-input v-model="form.stockQty" |
| | | type="number" |
| | | min="0" |
| | | placeholder="请è¾å
¥åºåæ°é" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="管æ§è´£ä»»äºº" |
| | | prop="principalUserId"> |
| | | <el-select v-model="form.principalUserId" |
| | | placeholder="è¯·éæ©ç®¡æ§è´£ä»»äºº" |
| | | @change="handleUserChange" |
| | | clearable> |
| | | <el-option v-for="item in userList" |
| | | :key="item.userId" |
| | | :label="item.nickName" |
| | | :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="责任人èç³»çµè¯" |
| | | prop="principalMobile"> |
| | | <el-input v-model="form.principalMobile" |
| | | readonly |
| | | placeholder="èªå¨å¸¦åº" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é£é©ç级" |
| | | prop="riskLevel"> |
| | | <el-select v-model="form.riskLevel" |
| | | placeholder="è¯·éæ©é£é©ç级" |
| | | clearable> |
| | | <el-option v-for="item in riskLevelOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="è§æ ¼ / é£é©æè¿°" |
| | | prop="specInfo"> |
| | | <el-input v-model="form.specInfo" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥è§æ ¼ / é£é©æè¿°" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- æ¥çç¥è¯è¯¦æ
å¼¹çª --> |
| | | <el-dialog v-model="viewDialogVisible" |
| | | title="å±é©æºè¯¦æ
" |
| | | width="900px" |
| | | :close-on-click-modal="false"> |
| | | <div class="knowledge-detail"> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="å±é©æºåç§°" |
| | | :span="2"> |
| | | <span class="detail-title">{{ currentKnowledge.name }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å±é©æºç¼ç "> |
| | | {{ currentKnowledge.code }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å±é©æºç±»å"> |
| | | {{ getTypeLabel(currentKnowledge.type) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æå¨ä½ç½®"> |
| | | {{ currentKnowledge.location }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç®¡æ§æªæ½"> |
| | | {{ currentKnowledge.controlMeasures }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="åºåæ°é"> |
| | | {{ currentKnowledge.stockQty }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="管æ§è´£ä»»äºº"> |
| | | {{ currentKnowledge.principalUserId }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="责任人èç³»çµè¯"> |
| | | {{ currentKnowledge.principalMobile }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="é£é©ç级"> |
| | | <el-tag :type="getTypeTagType(currentKnowledge.riskLevel)"> |
| | | {{ currentKnowledge.riskLevel }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="è§æ ¼ / é£é©æè¿°"> |
| | | {{ currentKnowledge.specInfo }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="viewDialogVisible = false">å
³é</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { |
| | | onMounted, |
| | | ref, |
| | | reactive, |
| | | toRefs, |
| | | getCurrentInstance, |
| | | computed, |
| | | } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { |
| | | listKnowledgeBase, |
| | | delKnowledgeBase, |
| | | addKnowledgeBase, |
| | | updateKnowledgeBase, |
| | | } from "@/api/collaborativeApproval/knowledgeBase.js"; |
| | | import { |
| | | safeHazardListPage, |
| | | safeHazardAdd, |
| | | safeHazardUpdate, |
| | | safeHazardDel, |
| | | } from "@/api/safeProduction/hazardSourceLedger.js"; |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | code: [{ required: true, message: "请è¾å
¥å±é©æºç¼ç ", trigger: "blur" }], |
| | | name: [{ required: true, message: "请è¾å
¥å±é©æºåç§°", trigger: "blur" }], |
| | | type: [{ required: true, message: "è¯·éæ©å±é©æºç±»å", trigger: "change" }], |
| | | location: [{ required: true, message: "请è¾å
¥æå¨ä½ç½®", trigger: "blur" }], |
| | | controlMeasures: [ |
| | | { required: true, message: "请è¾å
¥ç®¡æ§æªæ½", trigger: "blur" }, |
| | | ], |
| | | stockQty: [{ required: true, message: "请è¾å
¥åºåæ°é", trigger: "blur" }], |
| | | principalUser: [ |
| | | { required: true, message: "请è¾å
¥ç®¡æ§è´£ä»»äºº", trigger: "blur" }, |
| | | ], |
| | | riskLevel: [{ required: true, message: "è¯·éæ©é£é©ç级", trigger: "change" }], |
| | | }; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | name: "", |
| | | type: "", |
| | | }, |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 20, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | selectedIds: [], |
| | | form: { |
| | | code: "", |
| | | name: "", |
| | | location: "", |
| | | controlMeasures: "", |
| | | riskLevel: "", |
| | | principalUser: "", |
| | | principalMobile: "", |
| | | specInfo: "", |
| | | principalUserId: "", |
| | | controlMeasures: "", |
| | | stockQty: "", |
| | | type: "", |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | viewDialogVisible: false, |
| | | currentKnowledge: {}, |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | | dialogTitle, |
| | | dialogType, |
| | | viewDialogVisible, |
| | | currentKnowledge, |
| | | } = toRefs(data); |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | const riskLevelOptions = ref([ |
| | | { value: "ä½é£é©", label: "ä½é£é©" }, |
| | | { value: "ä¸è¬é£é©", label: "ä¸è¬é£é©" }, |
| | | { value: "è¾å¤§é£é©", label: "è¾å¤§é£é©" }, |
| | | { value: "é大é£é©", label: "é大é£é©" }, |
| | | ]); |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "å±é©æºç¼ç ", |
| | | prop: "code", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "å±é©æºåç§°", |
| | | prop: "name", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "å±é©æºç±»å", |
| | | prop: "type", |
| | | showOverflowTooltip: true, |
| | | formatData: params => { |
| | | return getTypeLabel(params); |
| | | }, |
| | | }, |
| | | { |
| | | label: "é£é©ç级", |
| | | prop: "riskLevel", |
| | | showOverflowTooltip: true, |
| | | dataType: "tag", |
| | | formatType: params => { |
| | | const typeMap = { |
| | | ä½é£é©: "info", |
| | | ä¸è¬é£é©: "info", |
| | | è¾å¤§é£é©: "warning", |
| | | é大é£é©: "danger", |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "æå¨ä½ç½®", |
| | | prop: "location", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "ç®¡æ§æªæ½", |
| | | prop: "controlMeasures", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "åºåæ°é", |
| | | prop: "stockQty", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "管æ§è´£ä»»äºº", |
| | | prop: "principalUser", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "责任人èç³»çµè¯", |
| | | prop: "principalMobile", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "è§æ ¼ / é£é©æè¿°", |
| | | prop: "specInfo", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 200, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "æ¥ç", |
| | | type: "text", |
| | | clickFun: row => { |
| | | viewKnowledge(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const userList = ref([]); |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | getList(); |
| | | startAutoRefresh(); |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | }); |
| | | |
| | | // å¤çç¨æ·éæ©åå |
| | | const handleUserChange = userId => { |
| | | const selectedUser = userList.value.find(user => user.userId === userId); |
| | | if (selectedUser) { |
| | | form.value.principalUser = selectedUser.nickName; |
| | | form.value.principalMobile = selectedUser.phonenumber; |
| | | } |
| | | }; |
| | | |
| | | // å¼å§èªå¨å·æ° |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | | getList(); |
| | | }, 600000); // 10åéå·æ°ä¸æ¬¡ (10 * 60 * 1000 = 600000ms) |
| | | }; |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | safeHazardListPage({ ...page.value, ...searchForm.value }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.value.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // å页å¤ç |
| | | const pagination = obj => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | | const handleSelectionChange = selection => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row = null) => { |
| | | dialogType.value = type; |
| | | if (type === "add") { |
| | | dialogTitle.value = "æ°å¢å±é©æº"; |
| | | // é置表å |
| | | Object.assign(form.value, { |
| | | code: "", |
| | | name: "", |
| | | location: "", |
| | | controlMeasures: "", |
| | | riskLevel: "", |
| | | principalUser: "", |
| | | principalMobile: "", |
| | | specInfo: "", |
| | | principalUserId: "", |
| | | controlMeasures: "", |
| | | stockQty: "", |
| | | type: "", |
| | | }); |
| | | } else if (type === "edit" && row) { |
| | | dialogTitle.value = "ç¼è¾å±é©æº"; |
| | | Object.assign(form.value, { |
| | | id: row.id, |
| | | code: row.code, |
| | | name: row.name, |
| | | location: row.location, |
| | | controlMeasures: row.controlMeasures, |
| | | riskLevel: row.riskLevel, |
| | | principalUser: row.principalUser, |
| | | principalMobile: row.principalMobile, |
| | | specInfo: row.specInfo, |
| | | principalUserId: row.principalUserId, |
| | | controlMeasures: row.controlMeasures, |
| | | stockQty: row.stockQty, |
| | | type: row.type, |
| | | }); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // æ¥çå±é©æºè¯¦æ
|
| | | const viewKnowledge = row => { |
| | | currentKnowledge.value = { ...row }; |
| | | viewDialogVisible.value = true; |
| | | }; |
| | | |
| | | // è·åç±»åæ ç¾ç±»å |
| | | const getTypeTagType = type => { |
| | | const typeMap = { |
| | | è¾å¤§é£é©: "warning", |
| | | ä½é£é©: "info", |
| | | ä¸è¬é£é©: "info", |
| | | é大é£é©: "danger", |
| | | }; |
| | | return typeMap[type] || "info"; |
| | | }; |
| | | |
| | | // è·åç±»åæ ç¾ææ¬ |
| | | const getTypeLabel = type => { |
| | | return getKnowledgeTypeLabel(type); |
| | | }; |
| | | |
| | | // è·åæçæ ç¾ç±»å |
| | | const getEfficiencyTagType = efficiency => { |
| | | const typeMap = { |
| | | high: "success", |
| | | medium: "warning", |
| | | low: "info", |
| | | }; |
| | | return typeMap[efficiency] || "info"; |
| | | }; |
| | | |
| | | // è·åæçæ ç¾ææ¬ |
| | | const getEfficiencyLabel = efficiency => { |
| | | const efficiencyMap = { |
| | | high: "æ¾èæå", |
| | | medium: "ä¸è¬æå", |
| | | low: "轻微æå", |
| | | }; |
| | | return efficiencyMap[efficiency] || efficiency; |
| | | }; |
| | | |
| | | // è·åæçæåç¾åæ¯ |
| | | const getEfficiencyScore = efficiency => { |
| | | const scoreMap = { |
| | | high: 40, |
| | | medium: 25, |
| | | low: 15, |
| | | }; |
| | | return scoreMap[efficiency] || 0; |
| | | }; |
| | | |
| | | // è·åå¹³åèçæ¶é´ |
| | | const getTimeSaved = efficiency => { |
| | | const timeMap = { |
| | | high: "2-3天", |
| | | medium: "1-2天", |
| | | low: "0.5-1天", |
| | | }; |
| | | return timeMap[efficiency] || "æªç¥"; |
| | | }; |
| | | |
| | | /** |
| | | * è·åè¡ç±»åï¼ç¨äºå¤æé£é©ç级æ¯å¦ä¸ºé大é£é© |
| | | * @param row è¡æ°æ® |
| | | * @returns ç±»å |
| | | */ |
| | | const getRowClass = ({ row }) => { |
| | | if (row.riskLevel === "é大é£é©") { |
| | | return "danger-row"; |
| | | } |
| | | return ""; |
| | | }; |
| | | |
| | | // æäº¤å±é©æºè¡¨å |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | if (dialogType.value === "add") { |
| | | // æ°å¢å±é©æºå°è´¦ |
| | | safeHazardAdd({ ...form.value }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("æ·»å æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else { |
| | | // æ´æ°å±é©æºå°è´¦ |
| | | safeHazardUpdate({ ...form.value }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("æ´æ°æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // å é¤å±é©æº |
| | | const handleDelete = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | ElMessage.warning("è¯·éæ©è¦å é¤çå±é©æº"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // console.log(selectedIds.value); |
| | | safeHazardDel(selectedIds.value).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | } |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const { proxy } = getCurrentInstance(); |
| | | const { hazard_source_type } = proxy.useDict("hazard_source_type"); |
| | | |
| | | // åå
¸å·¥å
· |
| | | const knowledgeTypeOptions = computed(() => hazard_source_type?.value || []); |
| | | const getKnowledgeTypeLabel = val => { |
| | | const item = knowledgeTypeOptions.value.find( |
| | | i => String(i.value) === String(val) |
| | | ); |
| | | return item ? item.label : val; |
| | | }; |
| | | const getKnowledgeTypeTagType = val => { |
| | | const item = knowledgeTypeOptions.value.find( |
| | | i => String(i.value) === String(val) |
| | | ); |
| | | return item?.elTagType || "info"; |
| | | }; |
| | | const handleExport = () => { |
| | | proxy.download( |
| | | "/knowledgeBase/export", |
| | | { ...searchForm.value }, |
| | | "ç¥è¯åº.xlsx" |
| | | ); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .auto-refresh-info { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .auto-refresh-info .el-alert { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | .knowledge-detail { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .detail-title { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .detail-section { |
| | | margin-top: 24px; |
| | | } |
| | | |
| | | .detail-section h4 { |
| | | margin: 0 0 12px 0; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | border-left: 4px solid #409eff; |
| | | padding-left: 12px; |
| | | } |
| | | |
| | | .detail-content { |
| | | background: #f8f9fa; |
| | | padding: 16px; |
| | | border-radius: 6px; |
| | | line-height: 1.6; |
| | | color: #606266; |
| | | white-space: pre-wrap; |
| | | } |
| | | |
| | | .key-points { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .usage-stats { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .stat-item { |
| | | text-align: center; |
| | | padding: 20px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .stat-number { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | |
| | | :deep(.danger-row td) { |
| | | color: #e95a66 !important; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" |
| | | :inline="true"> |
| | | <el-form-item label="è§ç¨èµè´¨åç§°ï¼"> |
| | | <el-input v-model="searchForm.name" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="è§ç¨èµè´¨ç±»åï¼"> |
| | | <el-select v-model="searchForm.type" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | style="width: 200px" |
| | | prefix-icon="Search" |
| | | @change="handleQuery"> |
| | | <el-option v-for="item in type_qualification" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="è§ç¨èµè´¨ç¼å·ï¼"> |
| | | <el-input v-model="searchForm.code" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery"> æç´¢ </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" |
| | | @click="openForm('add')"> |
| | | æ°å¢è§ç¨èµè´¨ |
| | | </el-button> |
| | | <!-- <el-button type="primary" |
| | | plain |
| | | @click="handleImport">导å
¥</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> --> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">å é¤</el-button> |
| | | <!-- <el-button type="primary" |
| | | plain |
| | | @click="handlePrint">æå°</el-button> --> |
| | | </div> |
| | | </div> |
| | | <el-table :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" |
| | | :row-key="(row) => row.id" |
| | | :row-class-name="getRowClass" |
| | | style="width: 100%" |
| | | height="calc(100vh - 18.5em)"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" |
| | | fixed="left" /> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="è§ç¨èµè´¨åç§°" |
| | | prop="name" |
| | | width="180" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="è§ç¨èµè´¨ç¼å·" |
| | | prop="code" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="è§ç¨èµè´¨ç±»å" |
| | | prop="type" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="çæ¬å·" |
| | | prop="version" |
| | | width="180" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" |
| | | prop="remark" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="æææ" |
| | | prop="effectiveTime" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="æä½" |
| | | min-width="100" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | @click="openForm('edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | @click="downLoadFile(scope.row)">éä»¶</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" /> |
| | | </div> |
| | | <FormDialog v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢è§ç¨èµè´¨é¡µé¢' : 'ç¼è¾è§ç¨èµè´¨é¡µé¢'" |
| | | :width="'70%'" |
| | | :operation-type="operationType" |
| | | @close="closeDia" |
| | | @confirm="submitForm" |
| | | @cancel="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="name"> |
| | | <el-input v-model="form.name" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§ç¨èµè´¨ç¼å·ï¼" |
| | | prop="code"> |
| | | <el-input v-model="form.code" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§ç¨èµè´¨ç±»åï¼" |
| | | prop="type"> |
| | | <el-select v-model="form.type" |
| | | placeholder="è¯·éæ©" |
| | | clearable> |
| | | <el-option v-for="item in type_qualification" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="çæ¬å·ï¼" |
| | | prop="version"> |
| | | <el-input v-model="form.version" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææï¼" |
| | | prop="effectiveTime"> |
| | | <el-date-picker style="width: 100%" |
| | | v-model="form.effectiveTime" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="夿³¨Â·ï¼" |
| | | prop="remark"> |
| | | <el-input v-model="form.remark" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | type="textarea" |
| | | :rows="1" |
| | | :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </FormDialog> |
| | | <!-- éä»¶åè¡¨å¼¹çª --> |
| | | <FileListDialog ref="fileListRef" |
| | | v-model="fileListDialogVisible" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :upload-method="handleUpload" |
| | | :delete-method="handleFileDelete" |
| | | title="éä»¶å表" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { getToken } from "@/utils/auth"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { onMounted, ref, getCurrentInstance } from "vue"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import FileListDialog from "@/components/Dialog/FileListDialog.vue"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; |
| | | import { |
| | | qualificationsListPage, |
| | | safeCertificationAdd, |
| | | safeCertificationUpdate, |
| | | safeCertificationDel, |
| | | fileListPage, |
| | | safeCertificationFileAdd, |
| | | safeCertificationFileDel, |
| | | } from "@/api/safeProduction/safeQualifications.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | import request from "@/utils/request"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const userStore = useUserStore(); |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const userList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | name: "", // è§ç¨èµè´¨åç§° |
| | | code: "", // è§ç¨èµè´¨ç¼å· |
| | | type: "", // è§ç¨èµè´¨ç±»å |
| | | }, |
| | | form: { |
| | | salesContractNo: "", |
| | | salesman: "", |
| | | customerId: "", |
| | | entryPerson: "", |
| | | entryDate: "", |
| | | maintenanceTime: "", |
| | | executionDate: "", |
| | | }, |
| | | rules: { |
| | | code: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | name: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | type: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | effectiveTime: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | version: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | // è§ç¨èµè´¨ç±»åé项 |
| | | const { type_qualification } = proxy.useDict("type_qualification"); |
| | | const { form, rules } = toRefs(data); |
| | | const { form: searchForm } = useFormData(data.searchForm); |
| | | // 产å表åå¼¹æ¡æ°æ® |
| | | const productFormVisible = ref(false); |
| | | |
| | | const quotationLoading = ref(false); |
| | | const quotationList = ref([]); |
| | | const quotationSearchForm = reactive({ |
| | | quotationNo: "", |
| | | customer: "", |
| | | }); |
| | | |
| | | // 导å
¥ç¸å
³ |
| | | const importUploadRef = ref(null); |
| | | const importUpload = reactive({ |
| | | title: "导å
¥éå®å°è´¦", |
| | | open: false, |
| | | url: import.meta.env.VITE_APP_BASE_API + "/sales/ledger/import", |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | isUploading: false, |
| | | beforeUpload: file => { |
| | | const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls"); |
| | | const isLt10M = file.size / 1024 / 1024 < 10; |
| | | if (!isExcel) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶åªè½æ¯ xlsx/xls æ ¼å¼!"); |
| | | return false; |
| | | } |
| | | if (!isLt10M) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿ 10MB!"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }, |
| | | onChange: (file, fileList) => { |
| | | console.log("æä»¶ç¶ææ¹å", file, fileList); |
| | | }, |
| | | onProgress: (event, file, fileList) => { |
| | | console.log("ä¸ä¼ ä¸...", event.percent); |
| | | }, |
| | | onSuccess: (response, file, fileList) => { |
| | | console.log("ä¸ä¼ æå", response, file, fileList); |
| | | importUpload.isUploading = false; |
| | | if (response.code === 200) { |
| | | proxy.$modal.msgSuccess("导å
¥æå"); |
| | | importUpload.open = false; |
| | | if (importUploadRef.value) { |
| | | importUploadRef.value.clearFiles(); |
| | | } |
| | | getList(); |
| | | } else { |
| | | proxy.$modal.msgError(response.msg || "导å
¥å¤±è´¥"); |
| | | } |
| | | }, |
| | | onError: (error, file, fileList) => { |
| | | console.error("ä¸ä¼ 失败", error, file, fileList); |
| | | importUpload.isUploading = false; |
| | | proxy.$modal.msgError("导å
¥å¤±è´¥ï¼è¯·éè¯"); |
| | | }, |
| | | }); |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | // åªæå¨ç¹å»æç´¢æé®æ¶æé置页ç å°ç¬¬ä¸é¡µ |
| | | // é¿å
表ååæ®µchangeäºä»¶å¹²æ°å页 |
| | | if (arguments.length === 0) { |
| | | page.current = 1; |
| | | } |
| | | expandedRowKeys.value = []; |
| | | getList(); |
| | | }; |
| | | const paginationChange = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const { entryDate, ...rest } = searchForm; |
| | | // å°èå´æ¥æåæ®µä¼ éç»å端 |
| | | const params = { ...rest, ...page }; |
| | | // ç§»é¤å½å
¥æ¥æçé»è®¤å¼è®¾ç½®ï¼åªä¿çèå´æ¥æå段 |
| | | delete params.entryDate; |
| | | return qualificationsListPage(params) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | return res; |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // æ¾å°èç¹ï¼è¿å该èç¹ |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundNode = findNodeById(nodes[i].children, productId); |
| | | if (foundNode) { |
| | | return foundNode; // å¨åèç¹ä¸æ¾å°ï¼è¿å该èç¹ |
| | | } |
| | | } |
| | | } |
| | | return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | console.log("selection", selectedRows.value); |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = async (type, row) => { |
| | | operationType.value = type; |
| | | if (type === "add") { |
| | | form.value = { |
| | | salesContractNo: "", |
| | | salesman: "", |
| | | customerId: "", |
| | | entryPerson: "", |
| | | entryDate: "", |
| | | maintenanceTime: "", |
| | | executionDate: "", |
| | | }; |
| | | } else { |
| | | form.value = row; |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | |
| | | const fetchQuotationList = async () => { |
| | | quotationLoading.value = true; |
| | | try { |
| | | const params = { |
| | | // å
¼å®¹å端å页忮µï¼è¿éæ²¿ç¨æ¥ä»·é¡µé¢å·²æå¯ç¨çåæ®µå½å |
| | | currentPage: 1, |
| | | pageSize: 100, |
| | | ...quotationSearchForm, |
| | | status: "éè¿", |
| | | }; |
| | | const res = await getQuotationList(params); |
| | | quotationList.value = res?.data?.records || []; |
| | | } finally { |
| | | quotationLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "add") { |
| | | safeCertificationAdd(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | } else { |
| | | safeCertificationUpdate(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // å
³é产åå¼¹æ¡ |
| | | const closeProductDia = () => { |
| | | proxy.resetForm("productFormRef"); |
| | | productFormVisible.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(() => { |
| | | safeCertificationDel(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * 夿æ¯å¦å¯ä»¥åè´§ |
| | | * åªæå¨äº§åç¶ææ¯å
è¶³ï¼åè´§ç¶ææ¯å¾
åè´§åå®¡æ ¸æç»çæ¶åæå¯ä»¥åè´§ |
| | | * @param row è¡æ°æ® |
| | | */ |
| | | const canShip = row => { |
| | | // 产åç¶æå¿
é¡»æ¯å
è¶³ï¼approveStatus === 1ï¼ |
| | | if (row.approveStatus !== 1) { |
| | | return false; |
| | | } |
| | | |
| | | // è·ååè´§ç¶æ |
| | | const shippingStatus = row.shippingStatus; |
| | | |
| | | // 妿已åè´§ï¼æåè´§æ¥ææè½¦çå·ï¼ï¼ä¸è½å次åè´§ |
| | | if (row.shippingDate || row.shippingCarNumber) { |
| | | return false; |
| | | } |
| | | |
| | | // åè´§ç¶æå¿
é¡»æ¯"å¾
åè´§"æ"å®¡æ ¸æç»" |
| | | const statusStr = shippingStatus ? String(shippingStatus).trim() : ""; |
| | | return statusStr === "å¾
åè´§" || statusStr === "å®¡æ ¸æç»"; |
| | | }; |
| | | |
| | | /** |
| | | * ä¸è½½æä»¶ |
| | | * |
| | | * @param row ä¸è½½æä»¶çç¸å
³ä¿¡æ¯å¯¹è±¡ |
| | | */ |
| | | const fileListRef = ref(null); |
| | | const fileListDialogVisible = ref(false); |
| | | const currentFileRow = ref(null); |
| | | const downLoadFile = row => { |
| | | currentFileRow.value = row; |
| | | fileListPage({ safeCertificationId: row.id }).then(res => { |
| | | if (fileListRef.value) { |
| | | fileListRef.value.open(res.data.records); |
| | | } |
| | | }); |
| | | }; |
| | | const currentFactoryName = ref(""); |
| | | const getCurrentFactoryName = async () => { |
| | | let res = await userStore.getInfo(); |
| | | currentFactoryName.value = res.user.currentFactoryName; |
| | | }; |
| | | |
| | | /** |
| | | * è·åè¡ç±»åï¼ç¨äºå¤æå½åæ¥ææ¯å¦æ¥è¿æææ15天å
|
| | | * @param row è¡æ°æ® |
| | | * @returns ç±»å |
| | | */ |
| | | const getRowClass = ({ row }) => { |
| | | if (!row.effectiveTime) return ""; |
| | | |
| | | const now = new Date(); |
| | | const effectiveTime = new Date(row.effectiveTime); |
| | | const diffTime = effectiveTime - now; |
| | | const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); |
| | | |
| | | if (diffDays >= 0 && diffDays <= 15) { |
| | | return "warning-row"; |
| | | } |
| | | |
| | | return ""; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | getCurrentFactoryName(); |
| | | }); |
| | | // ä¸ä¼ éä»¶ |
| | | 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 = { |
| | | safeCertificationId: currentFileRow.value.id, |
| | | name: uploadRes.data.originalName || file.name, |
| | | url: uploadRes.data.tempPath || uploadRes.data.url, |
| | | }; |
| | | |
| | | const saveRes = await safeCertificationFileAdd(fileData); |
| | | if (saveRes.code === 200) { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | // éæ°å è½½æä»¶å表 |
| | | const listRes = await fileListPage({ |
| | | safeCertificationId: currentFileRow.value.id, |
| | | }); |
| | | 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 safeCertificationFileDel([row.id]); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | // éæ°å è½½æä»¶å表 |
| | | if (currentFileRow.value && fileListRef.value) { |
| | | const listRes = await fileListPage({ |
| | | safeCertificationId: currentFileRow.value.id, |
| | | }); |
| | | 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; |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .ml-10 { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | |
| | | :deep(.warning-row) { |
| | | background-color: #fef0f0 !important; |
| | | } |
| | | |
| | | :deep(.warning-row td) { |
| | | // color: #cf1322 !important; |
| | | } |
| | | |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | .print-preview-dialog { |
| | | .el-dialog__body { |
| | | padding: 0; |
| | | max-height: 80vh; |
| | | overflow-y: auto; |
| | | } |
| | | } |
| | | |
| | | .print-preview-container { |
| | | .print-preview-header { |
| | | padding: 15px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | text-align: center; |
| | | |
| | | .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | } |
| | | |
| | | .print-preview-content { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 400px; |
| | | } |
| | | } |
| | | |
| | | .print-page { |
| | | width: 220mm; |
| | | height: 90mm; |
| | | padding: 10mm; |
| | | margin: 0 auto; |
| | | background: white; |
| | | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| | | margin-bottom: 10px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-family: "SimSun", serif; |
| | | font-size: 10px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .header { |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .info-row { |
| | | line-height: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .table-section { |
| | | margin-bottom: 4px; |
| | | flex: 1; |
| | | |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | |
| | | th, |
| | | td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 14px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | th { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-label { |
| | | text-align: right; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .footer-section { |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 20px; |
| | | justify-content: space-between; |
| | | |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | &.address-item { |
| | | .address-value { |
| | | min-width: 200px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @media print { |
| | | .app-container { |
| | | display: none; |
| | | } |
| | | |
| | | .print-page { |
| | | box-shadow: none; |
| | | margin: 0; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item :label="`å票éé¢(å
)ï¼ ååæ»é¢(${form.taxInclusiveTotalPrice}å
)`" prop="invoiceTotal"> |
| | | <el-input-number :step="0.01" :min="0" :max="form.taxInclusiveTotalPrice" style="width: 100%" v-model="form.invoiceTotal" placeholder="请è¾å
¥" clearable :precision="2"/> |
| | | <el-form-item :label="`å票éé¢(å
)ï¼ `" prop="invoiceTotal"> |
| | | <el-input-number :step="0.01" :min="0" :max="maxInvoiceAmount || form.taxInclusiveTotalPrice" style="width: 100%" v-model="form.invoiceTotal" placeholder="请è¾å
¥" clearable :precision="2"/> |
| | | <div v-if="maxInvoiceAmount > 0" style="color: #909399; font-size: 12px; margin-top: 5px;"> |
| | | å¯å¡«æå¤§éé¢ä¸ºï¼Â¥{{ maxInvoiceAmount.toFixed(2) }}å
|
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | const { form: searchForm, resetForm } = useFormData(data.searchForm); |
| | | const currentId = ref(""); |
| | | const userStore = useUserStore(); |
| | | const maxInvoiceAmount = ref(0); // å票éé¢æå¤§å¼ |
| | | const upload = reactive({ |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/invoiceLedger/uploadFile", |
| | |
| | | if (!form.value.invoicePerson) { |
| | | form.value.invoicePerson = userStore.nickName; |
| | | } |
| | | |
| | | // 计ç®å票é颿大å¼ï¼noInvoiceAmount + invoiceAmount |
| | | const noInvoiceAmount = parseFloat(res.data.noInvoiceAmount || 0); |
| | | const invoiceAmount = parseFloat(res.data.invoiceAmount || 0); |
| | | maxInvoiceAmount.value = noInvoiceAmount + invoiceAmount; |
| | | }); |
| | | dialogFormVisible.value = true; |
| | | }; |
| | |
| | | specificationModel: row.specificationModel || "", |
| | | pendingInvoiceTotal: Number(row.pendingInvoiceTotal || 0), |
| | | taxRate: row.taxRate ?? "", |
| | | receiptPaymentAmount: "", |
| | | // é»è®¤æ¬æ¬¡å款éé¢ = å¾
忬¾éé¢ |
| | | receiptPaymentAmount: Number(row.pendingInvoiceTotal || 0), |
| | | receiptPaymentType: "", |
| | | registrant: userStore.nickName, |
| | | receiptPaymentDate: "", |
| | |
| | | |
| | | // ä¿å忬¾è®°å½ |
| | | const saveReceiptPayment = (row) => { |
| | | // åè¡¨åæ¬¾éé¢åè®¡æ ¡éªï¼ææå款记å½éé¢ä¹åä¸è½å¤§äºç¶æ°æ®ååéé¢ |
| | | // è¿éç¶æ°æ®âååéé¢âæï¼å·²å款éé¢( invoiceTotal ) + å¾
忬¾éé¢( pendingInvoiceTotal ) è®¡ç® |
| | | const findParentRowByChildId = (childId) => { |
| | | return tableData.value.find((p) => |
| | | Array.isArray(p.children) && p.children.some((c) => c.id === childId) |
| | | ); |
| | | }; |
| | | const parentRow = findParentRowByChildId(row.id); |
| | | if (parentRow) { |
| | | const contractAmount = |
| | | Number(parentRow.invoiceTotal || 0) + Number(parentRow.pendingInvoiceTotal || 0); |
| | | const sumReceipt = (parentRow.children || []).reduce((sum, item) => { |
| | | const val = Number(item?.receiptPaymentAmount ?? 0); |
| | | return sum + (Number.isFinite(val) ? val : 0); |
| | | }, 0); |
| | | if (sumReceipt > contractAmount) { |
| | | proxy.$modal.msgError( |
| | | `忬¾éé¢å计(${sumReceipt.toFixed(2)})ä¸è½å¤§äºååéé¢(${contractAmount.toFixed(2)})` |
| | | ); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | let updateData = { |
| | | id: row.id, |
| | | receiptPaymentType: row.receiptPaymentType, |