Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-management into dev_New
已添加11个文件
已修改10个文件
已删除2个文件
| | |
| | | :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(); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | |
| | | /> |
| | | </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-row> |
| | | <el-form-item label="产åä¿¡æ¯ï¼"> </el-form-item> |
| | |
| | | /> |
| | | <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 |
| | | ); |
| | | |
| | | // 为æ¯ä¸ªéè´ååå建ç¬ç«ç对象 |
| | |
| | | enterDate: form.enterDate, |
| | | issUerId: form.issUerId, // å½å
¥äººid |
| | | issUer: form.issUer, // å½å
¥äºº |
| | | tempFileIds: form.tempFileIds, |
| | | |
| | | // ååå®é
ä¿¡æ¯ |
| | | purchaseLedgerId: contract.id, // 使ç¨idä½ä¸ºå段åï¼å¼ä¸ºpurchaseLedgerId |
| | |
| | | enterDate: form.enterDate, |
| | | issUerId: form.issUerId, // å½å
¥äººid |
| | | issUer: form.issUer, // å½å
¥äºº |
| | | tempFileIds: form.tempFileIds, |
| | | |
| | | // ååå®é
ä¿¡æ¯ |
| | | purchaseLedgerId: singleContract.id, // 使ç¨idä½ä¸ºå段åï¼å¼ä¸ºpurchaseLedgerId |
| | |
| | | </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" |
| | | style="width: 100%" |
| | | v-model="form.ticketsNum" |
| | | @change="inputTicketsNum" |
| | | :precision="2" /> |
| | | </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" |
| | |
| | | |
| | | <script setup> |
| | | import { useModal } from "@/hooks/useModal"; |
| | | import EditForm from "../Form/EditForm.vue"; |
| | | import { updateRegistration } from "@/api/procurementManagement/procurementInvoiceLedger"; |
| | | import useFormData from "@/hooks/useFormData"; |
| | | import { updateRegistration, getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { getCurrentInstance, ref, nextTick } from "vue"; |
| | | |
| | | defineOptions({ |
| | | name: "æ¥ç¥¨å°è´¦ç¼è¾", |
| | |
| | | const emits = defineEmits(["success"]); |
| | | |
| | | const saleLedgerProjectId = ref(""); |
| | | const editFormRef = ref(); |
| | | const temFutureTickets = ref(0); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const { |
| | | id, |
| | | visible, |
| | |
| | | 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, |
| | | }); |
| | | 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; |
| | | } |
| | | }; |
| | | |
| | | const inputTicketsNum = val => { |
| | | // ç¡®ä¿å«ç¨åä»·åå¨ä¸ä¸ä¸ºé¶ |
| | | if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) { |
| | | proxy.$modal.msgWarning("å«ç¨åä»·ä¸è½ä¸ºé¶ææªå®ä¹"); |
| | | return; |
| | | } |
| | | if (Number(form.ticketsNum) > Number(temFutureTickets.value)) { |
| | | proxy.$modal.msgWarning("å¼ç¥¨æ°ä¸å¾å¤§äºæªå¼ç¥¨æ°"); |
| | | form.ticketsNum = temFutureTickets.value; |
| | | } |
| | | |
| | | // ç¡®ä¿æææ°å¼é½è½¬æ¢ä¸ºæ°åç±»åè¿è¡è®¡ç® |
| | | const ticketsAmount = |
| | | Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice); |
| | | const futureTickets = |
| | | Number(temFutureTickets.value) - Number(form.ticketsNum); |
| | | form.futureTickets = Number(futureTickets.toFixed(2)); |
| | | form.ticketsAmount = Number(ticketsAmount.toFixed(2)); |
| | | }; |
| | | |
| | | const inputTicketsAmount = val => { |
| | | // ç¡®ä¿å«ç¨åä»·åå¨ä¸ä¸ä¸ºé¶ |
| | | if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) { |
| | | proxy.$modal.msgWarning("å«ç¨åä»·ä¸è½ä¸ºé¶ææªå®ä¹"); |
| | | return; |
| | | } |
| | | |
| | | if (Number(val) > Number(form.futureTickets * form.taxInclusiveUnitPrice)) { |
| | | proxy.$modal.msgWarning("æ¬æ¬¡æ¥ç¥¨éé¢ä¸å¾å¤§äºæ»éé¢"); |
| | | form.ticketsAmount = ( |
| | | form.futureTickets * form.taxInclusiveUnitPrice |
| | | ).toFixed(2); |
| | | const ticketsNum = |
| | | Number(form.ticketsAmount) / Number(form.taxInclusiveUnitPrice); |
| | | form.ticketsNum = Number(ticketsNum.toFixed(2)); |
| | | return; |
| | | } |
| | | |
| | | // ç¡®ä¿æææ°å¼é½è½¬æ¢ä¸ºæ°åç±»åè¿è¡è®¡ç® |
| | | const ticketsNum = Number(val) / Number(form.taxInclusiveUnitPrice); |
| | | form.ticketsNum = Number(ticketsNum.toFixed(2)); |
| | | }; |
| | | |
| | | const open = async row => { |
| | | openModal(row.id); |
| | | saleLedgerProjectId.value = row.saleLedgerProjectId; |
| | | await nextTick(); |
| | | editFormRef.value.load(row.id, row.purchaseLedgerId, row.productModelId); |
| | | load(row.id, row.purchaseLedgerId, row.productModelId); |
| | | }; |
| | | |
| | | const close = () => { |
| | | editFormRef.value.resetForm(); |
| | | resetForm(); |
| | | closeModal(); |
| | | }; |
| | | |
| | | const sendForm = async () => { |
| | | const form = editFormRef.value.form; |
| | | form.saleLedgerProjectId = saleLedgerProjectId.value; |
| | | const { code } = await updateRegistration(form); |
| | | if (code === 200) { |
| | |
| | | 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="downLoadFile(row)" |
| | | > |
| | | éä»¶ |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @click="openEdit(row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | text |
| | | 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, |
| | | } 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([]); |
| | |
| | | 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> |
| | | <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="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> |
| | | .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' |
| | | |
| | | 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: ['6/9', '6/10', '6/11', '6/12', '6/13', '6/14', '6/15'], |
| | | 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, 0.40)', width: 2 }, |
| | | itemStyle: { color: 'rgba(11, 137, 254, 0.40)', 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: [80, 100, 140, 160, 120, 150, 180], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | { |
| | | name: 'å
¥åº', |
| | | type: 'line', |
| | | smooth: false, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | |
| | | lineStyle: { color: 'rgba(11, 249, 254, 0.5)', width: 2 }, |
| | | itemStyle: { color: 'rgba(11, 249, 254, 0.5)', 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: [160, 200, 200, 200, 170, 200, 200], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'line' }, |
| | | backgroundColor: 'rgba(10, 28, 58, 0.9)', |
| | | borderColor: 'rgba(78, 228, 255, 0.3)', |
| | | borderWidth: 1, |
| | | textStyle: { color: '#B8C8E0', fontSize: 12 }, |
| | | formatter(params) { |
| | | let result = params[0].axisValue + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `${item.marker} ${item.seriesName}: ${item.value} ä»¶<br/>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | |
| | | const handleFilterChange = () => { |
| | | // 坿 productType 忢å请æ±åºå
¥åºæ¥å£ï¼æ¤å¤ä»
é¢ç |
| | | } |
| | | |
| | | onMounted(() => {}) |
| | | </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 { customerRevenueAnalysis } from '@/api/viewIndex.js' |
| | | import { listCustomer } from '@/api/basicData/customerFile.js' |
| | | |
| | | const dateType = ref(1) |
| | | const customerValue = ref(null) |
| | | const customerOptions = ref([]) |
| | | |
| | | 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 style="color: #B8C8E0">${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 getCustomerRevenueAnalysis = () => { |
| | | if (customerOptions.value.length > 0 && !customerValue.value) customerValue.value = customerOptions.value[0].value |
| | | if (!customerValue.value) return |
| | | customerRevenueAnalysis({ customerId: customerValue.value, type: dateType.value }) |
| | | .then((res) => { |
| | | xAxis1.value[0].data = [] |
| | | barSeries1.value[0].data = [] |
| | | const items = res.data?.items || [] |
| | | items.forEach((item) => { |
| | | xAxis1.value[0].data.push(item.name) |
| | | barSeries1.value[0].data.push(item.value) |
| | | }) |
| | | }) |
| | | .catch((e) => console.error('è·å客æ·è¥æ¶åæå¤±è´¥:', e)) |
| | | } |
| | | |
| | | const fetchCustomerOptions = async () => { |
| | | try { |
| | | const res = await listCustomer({ pageNum: 1, pageSize: 200 }) |
| | | const records = res?.records || res?.data?.records || res?.rows || [] |
| | | customerOptions.value = records.map((r) => ({ |
| | | label: r.customerName || r.name || r.customer || '-', |
| | | value: r.id ?? r.customerId ?? r.customerCode ?? r.customerName, |
| | | })) |
| | | if (customerOptions.value.length > 0 && !customerValue.value) { |
| | | customerValue.value = customerOptions.value[0].value |
| | | getCustomerRevenueAnalysis() |
| | | } |
| | | } catch (e) { |
| | | customerOptions.value = [ |
| | | { label: 'åä¸ç²¾å¯', value: 'åä¸ç²¾å¯' }, |
| | | { label: 'æè¾°çµå', value: 'æè¾°çµå' }, |
| | | { label: 'å¯èªç§æ', value: 'å¯èªç§æ' }, |
| | | { label: 'éè¯å¶é ', value: 'éè¯å¶é ' }, |
| | | { label: 'è¿æ¯ææ', value: 'è¿æ¯ææ' }, |
| | | ] |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchCustomerOptions() |
| | | }) |
| | | </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 class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | | <span class="card-label">éå®äº§åæ°</span> |
| | | <span class="card-value">{{ totalStaff }}</span> |
| | | <div class="card-compare" :class="compareClass(staffYoY)"> |
| | | <span>忝</span> |
| | | <span class="compare-value">{{ formatPercent(staffYoY) }}</span> |
| | | <span class="compare-icon">{{ staffYoY >= 0 ? 'â' : 'â' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | | <span class="card-label">éè´äº§åæ°</span> |
| | | <span class="card-value">{{ totalCustomers }}</span> |
| | | <div class="card-compare" :class="compareClass(customersYoY)"> |
| | | <span>忝</span> |
| | | <span class="compare-value">{{ formatPercent(customersYoY) }}</span> |
| | | <span class="compare-icon">{{ customersYoY >= 0 ? 'â' : 'â' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | | <span class="card-label">åºåæ°</span> |
| | | <span class="card-value">{{ totalSuppliers }}</span> |
| | | <div class="card-compare" :class="compareClass(suppliersYoY)"> |
| | | <span>忝</span> |
| | | <span class="compare-value">{{ formatPercent(suppliersYoY) }}</span> |
| | | <span class="compare-icon">{{ suppliersYoY >= 0 ? 'â' : 'â' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { summaryStatistics } from '@/api/viewIndex.js' |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const totalStaff = ref(0) |
| | | const totalCustomers = ref(0) |
| | | const totalSuppliers = ref(0) |
| | | // 忝 |
| | | const staffYoY = ref(0) |
| | | const customersYoY = ref(0) |
| | | const suppliersYoY = ref(0) |
| | | |
| | | const formatPercent = (val) => { |
| | | const num = Number(val) || 0 |
| | | return `${Math.abs(num).toFixed(2)}%` |
| | | } |
| | | |
| | | const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down') |
| | | |
| | | // è·ååå·¥ã客æ·ãä¾åºåæ°é |
| | | const getNum = () => { |
| | | summaryStatistics().then((res) => { |
| | | totalStaff.value = res.data.totalStaff |
| | | staffYoY.value = res.data.staffGrowthRate |
| | | totalCustomers.value = res.data.totalCustomer |
| | | customersYoY.value = res.data.customerGrowthRate |
| | | totalSuppliers.value = res.data.totalSupplier |
| | | suppliersYoY.value = res.data.supplierGrowthRate |
| | | }).catch(err => { |
| | | console.error('è·ååºç¡ç»è®¡æ°æ®å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getNum() |
| | | }) |
| | | </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 { productCategoryDistribution } 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 mockCardData = [ |
| | | { name: 'çµå产å', value: 156, rate: '28.5' }, |
| | | { name: 'æºæ¢°è®¾å¤', value: 132, rate: '24.1' }, |
| | | { name: 'åææ', value: 98, rate: '17.9' }, |
| | | { name: 'å工产å', value: 87, rate: '15.9' }, |
| | | { name: '纺ç»å', value: 45, rate: '8.2' }, |
| | | { name: 'å
¶ä»', value: 31, rate: '5.7' }, |
| | | ] |
| | | |
| | | // é¢è²å表 |
| | | 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: '60%', |
| | | 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, 30], |
| | | }, |
| | | 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 setMockData = () => { |
| | | // å¡çæ°æ® |
| | | cardItems.value = mockCardData.map(item => ({ |
| | | label: item.name, |
| | | value: item.value, |
| | | unit: 'ä»¶', |
| | | rate: item.rate |
| | | })) |
| | | // å¾è¡¨æ°æ® |
| | | dataList.value = mockCardData.map((it) => ({ |
| | | name: it.name, |
| | | value: Number(it.value || 0), |
| | | rate: it.rate, |
| | | children: [], |
| | | })) |
| | | landSeries.value[0].data = dataList.value |
| | | } |
| | | |
| | | const loadData = async () => { |
| | | setMockData() |
| | | // try { |
| | | // const res = await productCategoryDistribution() |
| | | // const items = res?.data?.items || [] |
| | | // dataList.value = items.map((it) => ({ |
| | | // name: it.name, |
| | | // value: Number(it.value || 0), |
| | | // rate: it.rate, |
| | | // children: Array.isArray(it.children) ? it.children : [], |
| | | // })) |
| | | // // å¡çæ°æ® |
| | | // cardItems.value = items.map(item => ({ |
| | | // label: item.name, |
| | | // value: parseInt(item.value), |
| | | // unit: 'ä»¶', |
| | | // rate: item.rate |
| | | // })) |
| | | // landLegend.data = dataList.value.map((d) => d.name) |
| | | // landSeries.value[0].data = dataList.value |
| | | // } catch (e) { |
| | | // console.error('è·å产å大类åå¸å¤±è´¥:', e) |
| | | // setMockData() |
| | | // } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | }) |
| | | </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 { deptStaffDistribution } 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: '60%', |
| | | 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, 30], |
| | | }, |
| | | 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 mockData = [ |
| | | { name: 'ç产é¨', value: 125, rate: '35.2' }, |
| | | { name: 'ææ¯é¨', value: 85, rate: '23.9' }, |
| | | { name: 'éå®é¨', value: 65, rate: '18.3' }, |
| | | { name: 'è´¢å¡é¨', value: 32, rate: '9.0' }, |
| | | { name: '人äºé¨', value: 28, rate: '7.9' }, |
| | | { name: 'è¡æ¿é¨', value: 20, rate: '5.6' }, |
| | | ] |
| | | |
| | | const getDeptStaffDistribution = () => { |
| | | setMockData() |
| | | // deptStaffDistribution().then(res => { |
| | | // if (res.code === 200) { |
| | | // const items = res.data.items || [] |
| | | // // å¡çæ°æ® |
| | | // cardItems.value = items.map(item => ({ |
| | | // label: item.name, |
| | | // value: parseInt(item.value), |
| | | // unit: '人' |
| | | // })) |
| | | // // å¾è¡¨æ°æ® |
| | | // pieDatas.value = items.map(item => ({ |
| | | // name: item.name, |
| | | // value: parseInt(item.value), |
| | | // rate: item.rate |
| | | // })) |
| | | // } else { |
| | | // // 使ç¨åæ°æ® |
| | | // setMockData() |
| | | // } |
| | | // }).catch(err => { |
| | | // console.error('è·åé¨é¨äººåå叿°æ®å¤±è´¥:', err) |
| | | // // 使ç¨åæ°æ® |
| | | // setMockData() |
| | | // }) |
| | | } |
| | | |
| | | const setMockData = () => { |
| | | // å¡çæ°æ® |
| | | cardItems.value = mockData.map(item => ({ |
| | | label: item.name, |
| | | value: item.value, |
| | | unit: '人' |
| | | })) |
| | | // å¾è¡¨æ°æ® |
| | | pieDatas.value = mockData.map(item => ({ |
| | | name: item.name, |
| | | value: item.value, |
| | | rate: item.rate |
| | | })) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getDeptStaffDistribution() |
| | | }) |
| | | </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"> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background" :style="{ backgroundImage: `url(${roseBorderImg})` }"></div> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | 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> |
| | |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | clearable |
| | | filterable |
| | | popper-class="customer-select-dropdown" |
| | | :teleported="false" |
| | | @change="handleFilterChange" |
| | | > |
| | | <el-option |
| | |
| | | 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" |
| | | /> |
| | |
| | | if (!children.length) return `${dot}{parent|${parentName} ${parentValue}}` |
| | | // å°åç¹ + ç¶çº§ name + ç¶çº§ valueï¼æ¢è¡å±ç¤º children ç name + value |
| | | return [ |
| | | `${dot}{parent|${parentName} ${parentValue}}`, |
| | | `${dot}{parent|${parentName}}`, |
| | | ...children.map((c) => `{child|${c.name}}`), |
| | | ].join('\n') |
| | | }, |
| | |
| | | |
| | | 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> |
| | |
| | | <div class="dashboard-content"> |
| | | <!-- 左侧åºå --> |
| | | <div class="left-panel"> |
| | | <!-- 客æ·ä¿¡æ¯ç»è®¡åæ --> |
| | | <LeftTop /> |
| | | |
| | | <!-- è´¨éç»è®¡ --> |
| | | <LeftBottom /> |
| | | </div> |
| | | |
| | |
| | | |
| | | <!-- å³ä¾§åºå --> |
| | | <div class="right-panel"> |
| | | <!-- åºæ¶åºä»ç»è®¡ --> |
| | | <RightTop /> |
| | | |
| | | <!-- 忬¾ä¸å¼ç¥¨åæ --> |
| | | <RightBottom /> |
| | | </div> |
| | | </div> |
| | |
| | | </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; |
| | | }; |