| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢ä¿å
»ä»»å¡éä»¶å表 |
| | | export function listMaintenanceTaskFiles(query) { |
| | | return request({ |
| | | url: "/maintenanceTaskFile/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢ä¿å
»ä»»å¡éä»¶ |
| | | export function addMaintenanceTaskFile(data) { |
| | | return request({ |
| | | url: "/maintenanceTaskFile/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤ä¿å
»ä»»å¡éä»¶ |
| | | export function delMaintenanceTaskFile(id) { |
| | | return request({ |
| | | url: "/maintenanceTaskFile/del", |
| | | method: "delete", |
| | | data: Array.isArray(id) ? id : [id], |
| | | }); |
| | | } |
| | |
| | | }); |
| | | }; |
| | | |
| | | export const exportStockInventory = (params) => { |
| | | export const getStockInventoryReportList = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/exportStockInventory", |
| | | url: "/stockInventory/stockInventoryPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryInAndOutReportList = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/stockInAndOutRecord", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å»ç»åºåè®°å½ |
| | | export const frozenStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/frozenStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // è§£å»åºåè®°å½ |
| | | export const thawStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/thawStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // å»ç»åºåè®°å½ |
| | | export const frozenStockUninventory = (params) => { |
| | | return request({ |
| | | url: "/stockUninventory/frozenStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // è§£å»åºåè®°å½ |
| | | export const thawStockUninventory = (params) => { |
| | | return request({ |
| | | url: "/stockUninventory/thawStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | |
| | | // å页æ¥è¯¢ |
| | | export function paymentLedgerList(query) { |
| | | return request({ |
| | | url: "/purchase/paymentRegistration/paymentLedgerList", |
| | | url: "/purchase/paymentRegistration/supplierNameListPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | // å页æ¥è¯¢ |
| | | export function paymentRecordList(supplierId) { |
| | | return request({ |
| | | url: "/purchase/paymentRegistration/getPaymentRecordList/" + supplierId, |
| | | url: "/purchase/paymentRegistration/supplierNameListPageDetails", |
| | | method: "get", |
| | | params: supplierId, |
| | | }); |
| | | } |
| | |
| | | method: "delete", |
| | | data: id, |
| | | }); |
| | | } |
| | | // æ¥è¯¢éè´è¯¦æ
|
| | | export function getPurchaseByCode(id) { |
| | | return request({ |
| | | url: "/purchase/ledger/getPurchaseByCode", |
| | | method: "get", |
| | | params: id, |
| | | }); |
| | | } |
| | |
| | | |
| | | //å¨å¶åå¨è½¬æ
åµ |
| | | //home/workInProcessTurnover |
| | | export const getWorkInProcessTurnover= ()=>{ |
| | | export const getWorkInProcessTurnover = () => { |
| | | return request({ |
| | | url: '/home/workInProcessTurnover', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // 客æ·è¥æ¶è´¡ç®æ°å¼åæ |
| | | export const customerRevenueAnalysis = (params) => { |
| | | return request({ |
| | | url: '/home/customerRevenueAnalysis', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // åå·¥-客æ·-ä¾åºåæ»æ° |
| | | export const summaryStatistics = () => { |
| | | return request({ |
| | | url: '/home/summaryStatistics', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // åé¨é¨äººååå¸ |
| | | export const deptStaffDistribution = () => { |
| | | return request({ |
| | | url: '/home/deptStaffDistribution', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // ä¾åºåéè´æå |
| | | export const supplierPurchaseRanking = (query) => { |
| | | return request({ |
| | | url: '/home/supplierPurchaseRanking', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // 客æ·éé¢è´¡ç®æå |
| | | export const customerContributionRanking = (query) => { |
| | | return request({ |
| | | url: '/home/customerContributionRanking', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // å产å大类åå¸ |
| | | export const productCategoryDistribution = () => { |
| | | return request({ |
| | | url: '/home/productCategoryDistribution', |
| | | method: 'get' |
| | | }) |
| | | } |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/system/supplier/export", {}, "ä¾åºåæ¡£æ¡.xlsx"); |
| | | proxy.download("/system/supplier/export", { isWhite: 1 }, "ä¾åºåæ¡£æ¡.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | defineExpose({ |
| | | getList, |
| | | }); |
| | | </script> |
| | | |
| | | |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/system/supplier/export", {}, "ä¾åºåæ¡£æ¡.xlsx"); |
| | | proxy.download("/system/supplier/export", { isWhite: 0 }, "ä¾åºåæ¡£æ¡.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | defineExpose({ |
| | | getList, |
| | | }); |
| | | </script> |
| | | |
| | |
| | | <!-- å¨ä½ ç主页é¢ä¸ --> |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-tabs v-model="activeTab" type="card"> |
| | | <el-tabs v-model="activeTab" @tab-change="handleTabChange"> |
| | | <el-tab-pane label="æ£å¸¸ä¾åºå" name="home"> |
| | | <HomeTab /> |
| | | <HomeTab ref="homeTab" /> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="é»åå" name="blacklist"> |
| | | <BlacklistTab /> |
| | | <BlacklistTab ref="blacklistTab" /> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | |
| | | activeTab: 'home' |
| | | } |
| | | }, |
| | | watch: { |
| | | activeTab(newVal) { |
| | | if (newVal === 'home') { |
| | | this.$refs.homeTab && this.$refs.homeTab.getList() |
| | | } else if (newVal === 'blacklist') { |
| | | this.$refs.blacklistTab && this.$refs.blacklistTab.getList() |
| | | } |
| | | } |
| | | methods: { |
| | | handleTabChange(tabName) { |
| | | this.activeTab = tabName |
| | | this.$nextTick(() => { |
| | | if (tabName === 'home') { |
| | | this.$refs.homeTab && this.$refs.homeTab.getList && this.$refs.homeTab.getList() |
| | | } else if (tabName === 'blacklist') { |
| | | this.$refs.blacklistTab && this.$refs.blacklistTab.getList && this.$refs.blacklistTab.getList() |
| | | } |
| | | }) |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | <style> |
| | | .main-container :deep(.el-tabs__item.is-active) { |
| | | color: #1883f6 !important; |
| | | border-bottom: 2px solid #409EFF; |
| | | } |
| | | |
| | | </style> |
| | |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢å®¡æ¹æµç¨' : 'ç¼è¾å®¡æ¹æµç¨'" |
| | | :title="operationType === 'approval' ? '审æ¹' : '详æ
'" |
| | | width="700px" |
| | | @close="closeDia" |
| | | > |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="!isQuotationApproval"> |
| | | <el-row v-if="!isQuotationApproval && !isPurchaseApproval"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="审æ¹äºç±ï¼" prop="approveReason"> |
| | | <el-form-item :label="props.approveType == 5 ? 'éè´ååå·ï¼' : '审æ¹äºç±ï¼'" prop="approveReason"> |
| | | <el-input v-model="form.approveReason" placeholder="请è¾å
¥" clearable type="textarea" disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <!-- æ¥ä»·å®¡æ¹ï¼å±ç¤ºæ¥ä»·è¯¦æ
ï¼å¤ç¨é宿¥ä»·âæ¥ç详æ
å¯¹è¯æ¡âå
å®¹ç»æï¼ --> |
| | | <!-- æ¥ä»·å®¡æ¹ï¼å±ç¤ºæ¥ä»·è¯¦æ
ï¼å¤ç¨é宿¥ä»·"æ¥ç详æ
å¯¹è¯æ¡"å
å®¹ç»æï¼ --> |
| | | <div v-if="isQuotationApproval" style="margin: 10px 0 18px;"> |
| | | <el-divider content-position="left">æ¥ä»·è¯¦æ
</el-divider> |
| | | <el-skeleton :loading="quotationLoading" animated> |
| | |
| | | <div v-if="currentQuotation.remark" style="margin-top: 20px;"> |
| | | <h4>夿³¨</h4> |
| | | <p>{{ currentQuotation.remark }}</p> |
| | | </div> |
| | | </template> |
| | | </template> |
| | | </el-skeleton> |
| | | </div> |
| | | |
| | | <!-- éè´å®¡æ¹ï¼å±ç¤ºéè´è¯¦æ
--> |
| | | <div v-if="isPurchaseApproval" style="margin: 10px 0 18px;"> |
| | | <el-divider content-position="left">éè´è¯¦æ
</el-divider> |
| | | <el-skeleton :loading="purchaseLoading" animated> |
| | | <template #template> |
| | | <el-skeleton-item variant="h3" style="width: 30%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | </template> |
| | | <template #default> |
| | | <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" description="æªæ¥è¯¢å°å¯¹åºéè´è¯¦æ
" /> |
| | | <template v-else> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="éè´ååå·">{{ currentPurchase.purchaseContractNumber }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¾åºååç§°">{{ currentPurchase.supplierName }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®åç§°">{{ currentPurchase.projectName }}</el-descriptions-item> |
| | | <el-descriptions-item label="éå®ååå·">{{ currentPurchase.salesContractNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç¾è®¢æ¥æ">{{ currentPurchase.executionDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="å½å
¥æ¥æ">{{ currentPurchase.entryDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="仿¬¾æ¹å¼">{{ currentPurchase.paymentMethod }}</el-descriptions-item> |
| | | <el-descriptions-item label="ååéé¢" :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <h4>产åæç»</h4> |
| | | <el-table :data="currentPurchase.productData || []" border style="width: 100%"> |
| | | <el-table-column prop="productCategory" label="产ååç§°" /> |
| | | <el-table-column prop="specificationModel" label="è§æ ¼åå·" /> |
| | | <el-table-column prop="unit" label="åä½" /> |
| | | <el-table-column prop="quantity" label="æ°é" /> |
| | | <el-table-column prop="taxInclusiveUnitPrice" label="å«ç¨åä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="taxInclusiveTotalPrice" label="å«ç¨æ»ä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </template> |
| | | </template> |
| | |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue' |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js"; |
| | | const emit = defineEmits(['close']) |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | |
| | | const userList = ref([]) |
| | | const quotationLoading = ref(false) |
| | | const currentQuotation = ref({}) |
| | | const purchaseLoading = ref(false) |
| | | const currentPurchase = ref({}) |
| | | const isQuotationApproval = computed(() => Number(props.approveType) === 6) |
| | | const isPurchaseApproval = computed(() => Number(props.approveType) === 5) |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | currentQuotation.value = {} |
| | | currentPurchase.value = {} |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | |
| | | }); |
| | | }); |
| | | |
| | | // æ¥ä»·å®¡æ¹ï¼ç¨å®¡æ¹äºç±å段æ¿è½½çâæ¥ä»·åå·â廿¥æ¥ä»·å表 |
| | | // æ¥ä»·å®¡æ¹ï¼ç¨å®¡æ¹äºç±å段æ¿è½½ç"æ¥ä»·åå·"廿¥æ¥ä»·å表 |
| | | if (isQuotationApproval.value) { |
| | | const quotationNo = row?.approveReason; |
| | | if (quotationNo) { |
| | |
| | | currentQuotation.value = records[0] || {} |
| | | }).finally(() => { |
| | | quotationLoading.value = false |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // éè´å®¡æ¹ï¼ç¨å®¡æ¹äºç±å段æ¿è½½ç"éè´ååå·"廿¥éè´è¯¦æ
|
| | | if (isPurchaseApproval.value) { |
| | | const purchaseContractNumber = row?.approveReason; |
| | | if (purchaseContractNumber) { |
| | | purchaseLoading.value = true |
| | | getPurchaseByCode({ purchaseContractNumber }).then((res) => { |
| | | currentPurchase.value = res |
| | | }).catch((err) => { |
| | | console.error('æ¥è¯¢éè´è¯¦æ
失败:', err) |
| | | proxy.$modal.msgError('æ¥è¯¢éè´è¯¦æ
失败') |
| | | }).finally(() => { |
| | | purchaseLoading.value = false |
| | | }) |
| | | } |
| | | } |
| | |
| | | dialogFormVisible.value = false; |
| | | quotationLoading.value = false |
| | | currentQuotation.value = {} |
| | | purchaseLoading.value = false |
| | | currentPurchase.value = {} |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item :label="props.approveType == 5 ? 'éè´è¯´æï¼' : '审æ¹äºç±ï¼'" prop="approveReason"> |
| | | <el-form-item :label="props.approveType == 5 ? 'éè´ååå·ï¼' : '审æ¹äºç±ï¼'" prop="approveReason"> |
| | | <el-input v-model="form.approveReason" placeholder="请è¾å
¥" clearable type="textarea" /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | const isLeaveType = currentApproveType.value === 2; // 请å管ç |
| | | const isReimburseType = currentApproveType.value === 4; // æ¥é管ç |
| | | const isQuotationType = currentApproveType.value === 6; // æ¥ä»·å®¡æ¹ |
| | | const isPurchaseType = currentApproveType.value === 5; // éè´å®¡æ¹ |
| | | |
| | | // åºç¡åé
ç½® |
| | | const baseColumns = [ |
| | |
| | | width: 220 |
| | | }, |
| | | { |
| | | label: isQuotationType ? "æ¥ä»·åå·" : "审æ¹äºç±", |
| | | label: isQuotationType ? "æ¥ä»·åå·" : isPurchaseType ? "éè´ååå·" : "审æ¹äºç±", |
| | | prop: "approveReason", |
| | | width: 200 |
| | | }, |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog :title="operationType === 'add' ? 'æ°å¢å·¡æ£ä»»å¡' : 'ç¼è¾å·¡æ£ä»»å¡'" |
| | | v-model="dialogVisitable" width="800px" @close="cancel"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤åç§°" prop="taskId"> |
| | | <el-select v-model="form.taskId" @change="setDeviceModel" filterable> |
| | | <el-option |
| | | v-for="(item, index) in deviceOptions" |
| | | :key="index" |
| | | :label="item.deviceName" |
| | | :value="item.id" |
| | | ></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å·¡æ£äºº" prop="inspector"> |
| | | <el-select v-model="form.inspector" filterable |
| | | default-first-option |
| | | :reserve-keyword="false" placeholder="è¯·éæ©" multiple clearable> |
| | | <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="夿³¨" prop="remarks"> |
| | | <el-input v-model="form.remarks" placeholder="请è¾å
¥å¤æ³¨" type="textarea" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»è®°æ¶é´" prop="dateStr"> |
| | | <el-date-picker |
| | | v-model="form.dateStr" |
| | | type="date" |
| | | placeholder="éæ©ç»è®°æ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä»»å¡é¢ç" prop="frequencyType"> |
| | | <el-select v-model="form.frequencyType" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="æ¯æ¥" value="DAILY"/> |
| | | <el-option label="æ¯å¨" value="WEEKLY"/> |
| | | <el-option label="æ¯æ" value="MONTHLY"/> |
| | | <!-- <el-option label="å£åº¦" value="QUARTERLY"/> --> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-time-picker v-model="form.frequencyDetail" placeholder="éæ©æ¶é´" format="HH:mm" |
| | | value-format="HH:mm" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-select v-model="form.week" placeholder="è¯·éæ©" clearable style="width: 50%"> |
| | | <el-option label="å¨ä¸" value="MON"/> |
| | | <el-option label="å¨äº" value="TUE"/> |
| | | <el-option label="å¨ä¸" value="WED"/> |
| | | <el-option label="å¨å" value="THU"/> |
| | | <el-option label="å¨äº" value="FRI"/> |
| | | <el-option label="å¨å
" value="SAT"/> |
| | | <el-option label="卿¥" value="SUN"/> |
| | | </el-select> |
| | | <el-time-picker v-model="form.time" placeholder="éæ©æ¶é´" format="HH:mm" |
| | | value-format="HH:mm" style="width: 50%"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-date-picker |
| | | v-model="form.frequencyDetail" |
| | | type="datetime" |
| | | clearable |
| | | placeholder="éæ©å¼å§æ¥æ" |
| | | format="DD,HH:mm" |
| | | value-format="DD,HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-date-picker |
| | | v-model="form.frequencyDetail" |
| | | type="datetime" |
| | | clearable |
| | | placeholder="éæ©å¼å§æ¥æ" |
| | | format="MM,DD,HH:mm" |
| | | value-format="MM,DD,HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="cancel">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ä¿å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | <div> |
| | | <el-dialog :title="operationType === 'add' ? 'æ°å¢å·¡æ£ä»»å¡' : 'ç¼è¾å·¡æ£ä»»å¡'" |
| | | v-model="dialogVisitable" width="800px" @close="cancel"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤åç§°" prop="taskId"> |
| | | <el-select v-model="form.taskId" @change="setDeviceModel"> |
| | | <el-option |
| | | v-for="(item, index) in deviceOptions" |
| | | :key="index" |
| | | :label="item.deviceName" |
| | | :value="item.id" |
| | | ></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å·¡æ£äºº" prop="inspector"> |
| | | <el-select v-model="form.inspector" placeholder="è¯·éæ©" multiple clearable> |
| | | <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="夿³¨" prop="remarks"> |
| | | <el-input v-model="form.remarks" placeholder="请è¾å
¥å¤æ³¨" type="textarea" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä»»å¡é¢ç" prop="frequencyType"> |
| | | <el-select v-model="form.frequencyType" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="æ¯æ¥" value="DAILY"/> |
| | | <el-option label="æ¯å¨" value="WEEKLY"/> |
| | | <el-option label="æ¯æ" value="MONTHLY"/> |
| | | <!-- <el-option label="å£åº¦" value="QUARTERLY"/> --> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-time-picker v-model="form.frequencyDetail" placeholder="éæ©æ¶é´" format="HH:mm" |
| | | value-format="HH:mm" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-select v-model="form.week" placeholder="è¯·éæ©" clearable style="width: 50%"> |
| | | <el-option label="å¨ä¸" value="MON"/> |
| | | <el-option label="å¨äº" value="TUE"/> |
| | | <el-option label="å¨ä¸" value="WED"/> |
| | | <el-option label="å¨å" value="THU"/> |
| | | <el-option label="å¨äº" value="FRI"/> |
| | | <el-option label="å¨å
" value="SAT"/> |
| | | <el-option label="卿¥" value="SUN"/> |
| | | </el-select> |
| | | <el-time-picker v-model="form.time" placeholder="éæ©æ¶é´" format="HH:mm" |
| | | value-format="HH:mm" style="width: 50%"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-date-picker |
| | | v-model="form.frequencyDetail" |
| | | type="datetime" |
| | | clearable |
| | | placeholder="éæ©å¼å§æ¥æ" |
| | | format="DD,HH:mm" |
| | | value-format="DD,HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-date-picker |
| | | v-model="form.frequencyDetail" |
| | | type="datetime" |
| | | clearable |
| | | placeholder="éæ©å¼å§æ¥æ" |
| | | format="MM,DD,HH:mm" |
| | | value-format="MM,DD,HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="cancel">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ä¿å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | const operationType = ref('add'); |
| | | const deviceOptions = ref([]); |
| | | const data = reactive({ |
| | | form: { |
| | | taskId: undefined, |
| | | taskName: undefined, |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | | week: '', |
| | | time: '', |
| | | dateStr: '' |
| | | }, |
| | | form: { |
| | | taskId: undefined, |
| | | taskName: undefined, |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | | week: '', |
| | | time: '' |
| | | }, |
| | | rules: { |
| | | taskId: [{ required: true, message: "è¯·éæ©è®¾å¤", trigger: "change" },], |
| | | inspector: [{ required: true, message: "请è¾å
¥å·¡æ£äºº", trigger: "blur" },], |
| | | dateStr: [{ required: true, message: "è¯·éæ©ç»è®°æ¶é´", trigger: "change" }], |
| | | frequencyType: [{ required: true, message: "è¯·éæ©ä»»å¡é¢ç", trigger: "change" }], |
| | | frequencyDetail: [ |
| | | { |
| | | required: true, |
| | | message: "è¯·éæ©æ¥æ", |
| | | { |
| | | required: true, |
| | | message: "è¯·éæ©æ¥æ", |
| | | trigger: "change", |
| | | validator: (rule, value, callback) => { |
| | | if (!form.value.frequencyType) { |
| | |
| | | } |
| | | ], |
| | | week: [ |
| | | { |
| | | required: true, |
| | | message: "è¯·éæ©ææ", |
| | | { |
| | | required: true, |
| | | message: "è¯·éæ©ææ", |
| | | trigger: "change", |
| | | validator: (rule, value, callback) => { |
| | | if (form.value.frequencyType === 'WEEKLY' && !value) { |
| | |
| | | } |
| | | ], |
| | | time: [ |
| | | { |
| | | required: true, |
| | | message: "è¯·éæ©æ¶é´", |
| | | { |
| | | required: true, |
| | | message: "è¯·éæ©æ¶é´", |
| | | trigger: "change", |
| | | validator: (rule, value, callback) => { |
| | | if (form.value.frequencyType === 'WEEKLY' && !value) { |
| | |
| | | const userList = ref([]) |
| | | |
| | | const loadDeviceName = async () => { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceOptions.value = data; |
| | | const { data } = await getDeviceLedger(); |
| | | deviceOptions.value = data; |
| | | }; |
| | | |
| | | const setDeviceModel = (id) => { |
| | | const option = deviceOptions.value.find((item) => item.id === id); |
| | | if (option) { |
| | | form.value.taskName = option.deviceName; |
| | | } |
| | | const option = deviceOptions.value.find((item) => item.id === id); |
| | | if (option) { |
| | | form.value.taskName = option.deviceName; |
| | | } |
| | | } |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | | dialogVisitable.value = true |
| | | operationType.value = type |
| | | |
| | | // é置表å |
| | | resetForm(); |
| | | |
| | | // å è½½ç¨æ·å表 |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | |
| | | // å 载设å¤å表 |
| | | await loadDeviceName(); |
| | | |
| | | if (type === 'edit' && row) { |
| | | form.value = {...row} |
| | | form.value.inspector = form.value.inspectorIds.split(',').map(Number) |
| | | |
| | | // 妿æè®¾å¤IDï¼èªå¨è®¾ç½®è®¾å¤ä¿¡æ¯ |
| | | if (form.value.taskId) { |
| | | setDeviceModel(form.value.taskId); |
| | | } |
| | | } |
| | | dialogVisitable.value = true |
| | | operationType.value = type |
| | | |
| | | // é置表å |
| | | resetForm(); |
| | | |
| | | // å è½½ç¨æ·å表 |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | |
| | | // å 载设å¤å表 |
| | | await loadDeviceName(); |
| | | |
| | | if (type === 'edit' && row) { |
| | | form.value = {...row} |
| | | form.value.inspector = form.value.inspectorIds.split(',').map(Number) |
| | | |
| | | // 妿æè®¾å¤IDï¼èªå¨è®¾ç½®è®¾å¤ä¿¡æ¯ |
| | | if (form.value.taskId) { |
| | | setDeviceModel(form.value.taskId); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // å
³éå¯¹è¯æ¡ |
| | | const cancel = () => { |
| | | resetForm() |
| | | dialogVisitable.value = false |
| | | emit('closeDia') |
| | | resetForm() |
| | | dialogVisitable.value = false |
| | | emit('closeDia') |
| | | } |
| | | |
| | | // é置表å彿° |
| | | const resetForm = () => { |
| | | if (proxy.$refs.formRef) { |
| | | proxy.$refs.formRef.resetFields() |
| | | } |
| | | // éç½®è¡¨åæ°æ®ç¡®ä¿è®¾å¤ä¿¡æ¯æ£ç¡®éç½® |
| | | form.value = { |
| | | taskId: undefined, |
| | | taskName: undefined, |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | | week: '', |
| | | time: '' |
| | | } |
| | | if (proxy.$refs.formRef) { |
| | | proxy.$refs.formRef.resetFields() |
| | | } |
| | | // éç½®è¡¨åæ°æ®ç¡®ä¿è®¾å¤ä¿¡æ¯æ£ç¡®éç½® |
| | | form.value = { |
| | | taskId: undefined, |
| | | taskName: undefined, |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | | week: '', |
| | | time: '' |
| | | } |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(async valid => { |
| | | if (valid) { |
| | | try { |
| | | form.value.inspectorIds = form.value.inspector.join(',') |
| | | delete form.value.inspector |
| | | |
| | | if (form.value.frequencyType === 'WEEKLY') { |
| | | let frequencyDetail = '' |
| | | frequencyDetail = form.value.week + ',' + form.value.time |
| | | form.value.frequencyDetail = frequencyDetail |
| | | } |
| | | |
| | | let res = await userStore.getInfo() |
| | | form.value.registrantId = res.user.userId |
| | | |
| | | await addOrEditTimingTask(form.value) |
| | | cancel() |
| | | proxy.$modal.msgSuccess('æäº¤æå') |
| | | } catch (error) { |
| | | proxy.$modal.msgError('æäº¤å¤±è´¥ï¼è¯·éè¯') |
| | | } |
| | | } |
| | | }) |
| | | proxy.$refs["formRef"].validate(async valid => { |
| | | if (valid) { |
| | | try { |
| | | form.value.inspectorIds = form.value.inspector.join(',') |
| | | delete form.value.inspector |
| | | |
| | | if (form.value.frequencyType === 'WEEKLY') { |
| | | let frequencyDetail = '' |
| | | frequencyDetail = form.value.week + ',' + form.value.time |
| | | form.value.frequencyDetail = frequencyDetail |
| | | } |
| | | |
| | | let res = await userStore.getInfo() |
| | | form.value.registrantId = res.user.userId |
| | | |
| | | await addOrEditTimingTask(form.value) |
| | | cancel() |
| | | proxy.$modal.msgSuccess('æäº¤æå') |
| | | } catch (error) { |
| | | proxy.$modal.msgError('æäº¤å¤±è´¥ï¼è¯·éè¯') |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | defineExpose({ openDialog }) |
| | | </script> |
| | |
| | | |
| | | <!-- ç产å --> |
| | | <div class="form-container"> |
| | | <div class="title">ç产ä¸</div> |
| | | <div class="title">ç产å</div> |
| | | |
| | | <!-- å¾çå表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | |
| | | |
| | | <!-- ç产é®é¢ --> |
| | | <div class="form-container"> |
| | | <div class="title">ç产å</div> |
| | | <div class="title">ç产é®é¢</div> |
| | | |
| | | <!-- å¾çå表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | |
| | | |
| | | <!-- è§é¢ --> |
| | | <div v-else-if="mediaType === 'video'" style="position: relative;"> |
| | | <Video |
| | | <video |
| | | :src="mediaList[currentMediaIndex]" |
| | | autoplay |
| | | controls |
| | |
| | | <script setup> |
| | | import { ref } from 'vue'; |
| | | import VueEasyLightbox from 'vue-easy-lightbox'; |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // æ§å¶å¼¹çªæ¾ç¤º |
| | | const dialogVisitable = ref(false); |
| | |
| | | const currentMediaIndex = ref(0); |
| | | const mediaList = ref([]); // åå¨å½åè¦æ¥ççåªä½å表ï¼å«å¾çåè§é¢å¯¹è±¡ï¼ |
| | | const mediaType = ref('image'); // image | video |
| | | const javaApi = proxy.javaApi; |
| | | |
| | | // å¤ç URLï¼å° Windows è·¯å¾è½¬æ¢ä¸ºå¯è®¿é®ç URL |
| | | function processFileUrl(fileUrl) { |
| | | if (!fileUrl) return ''; |
| | | |
| | | // 妿 URL æ¯ Windows è·¯å¾æ ¼å¼ï¼å
å«åææ ï¼ï¼éè¦è½¬æ¢ |
| | | if (fileUrl && fileUrl.indexOf('\\') > -1) { |
| | | // æ¥æ¾ uploads å
³é®åçä½ç½®ï¼ä»é£éå¼å§æåç¸å¯¹è·¯å¾ |
| | | const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads'); |
| | | if (uploadsIndex > -1) { |
| | | // ä» uploads å¼å§æåè·¯å¾ï¼å¹¶å°åææ æ¿æ¢ä¸ºæ£ææ |
| | | const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/'); |
| | | fileUrl = '/' + relativePath; |
| | | } else { |
| | | // å¦ææ²¡ææ¾å° uploadsï¼æåæåä¸ä¸ªç®å½åæä»¶å |
| | | const parts = fileUrl.split('\\'); |
| | | const fileName = parts[parts.length - 1]; |
| | | fileUrl = '/uploads/' + fileName; |
| | | } |
| | | } |
| | | |
| | | // ç¡®ä¿ææé http å¼å¤´ç URL 齿¼æ¥ baseUrl |
| | | if (fileUrl && !fileUrl.startsWith('http')) { |
| | | // ç¡®ä¿è·¯å¾ä»¥ / å¼å¤´ |
| | | if (!fileUrl.startsWith('/')) { |
| | | fileUrl = '/' + fileUrl; |
| | | } |
| | | // æ¼æ¥ baseUrl |
| | | fileUrl = javaApi + fileUrl; |
| | | } |
| | | |
| | | return fileUrl; |
| | | } |
| | | |
| | | // å¤çæ¯ä¸ç±»æ°æ®ï¼å离å¾çåè§é¢ |
| | | function processItems(items) { |
| | | const images = []; |
| | | const videos = []; |
| | | |
| | | // æ£æ¥ items æ¯å¦åå¨ä¸ä¸ºæ°ç» |
| | | if (!items || !Array.isArray(items)) { |
| | | return { images, videos }; |
| | | } |
| | | |
| | | items.forEach(item => { |
| | | if (item.contentType?.startsWith('image/')) { |
| | | images.push(item.url); |
| | | } else if (item.contentType?.startsWith('video/')) { |
| | | videos.push(item.url); |
| | | if (!item || !item.url) return; |
| | | |
| | | // å¤çæä»¶ URL |
| | | const fileUrl = processFileUrl(item.url); |
| | | |
| | | // æ ¹æ®æä»¶æ©å±å夿æ¯å¾çè¿æ¯è§é¢ |
| | | const urlLower = fileUrl.toLowerCase(); |
| | | if (urlLower.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/)) { |
| | | images.push(fileUrl); |
| | | } else if (urlLower.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/)) { |
| | | videos.push(fileUrl); |
| | | } else if (item.contentType) { |
| | | // 妿æ contentTypeï¼ä½¿ç¨ contentType 夿 |
| | | if (item.contentType.startsWith('image/')) { |
| | | images.push(fileUrl); |
| | | } else if (item.contentType.startsWith('video/')) { |
| | | videos.push(fileUrl); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | return { images, videos }; |
| | | } |
| | | |
| | | // æå¼å¼¹çªå¹¶å è½½æ°æ® |
| | | const openDialog = async (row) => { |
| | | const { images: beforeImgs, videos: beforeVids } = processItems(row.beforeProduction); |
| | | const { images: afterImgs, videos: afterVids } = processItems(row.afterProduction); |
| | | const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues); |
| | | // ä½¿ç¨æ£ç¡®çåæ®µåï¼commonFileListBefore, commonFileListAfter |
| | | // productionIssues å¯è½ä¸åå¨ï¼ä½¿ç¨ç©ºæ°ç» |
| | | const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBefore || []); |
| | | const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfter || []); |
| | | const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues || []); |
| | | |
| | | beforeProductionImgs.value = beforeImgs; |
| | | beforeProductionVideos.value = beforeVids; |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :inline="true" :model="queryParams" class="search-form"> |
| | | <el-form-item label="å·¡æ£ä»»å¡åç§°"> |
| | | <el-input |
| | | v-model="queryParams.taskName" |
| | | placeholder="请è¾å
¥å·¡æ£ä»»å¡åç§°" |
| | | clearable |
| | | :style="{ width: '100%' }" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <el-card> |
| | | <div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom: 10px;"> |
| | | <el-radio-group v-model="activeRadio" @change="radioChange"> |
| | | <el-radio-button v-for="tab in radios" |
| | | :key="tab.name" |
| | | :label="tab.label" |
| | | :value="tab.name"/> |
| | | </el-radio-group> |
| | | <!-- æä½æé®åº --> |
| | | <el-space v-if="activeRadio !== 'task'"> |
| | | <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">æ°å»º</el-button> |
| | | <el-button type="danger" :icon="Delete" @click="handleDelete">å é¤</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | </el-space> |
| | | <el-space v-else> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | </el-space> |
| | | </div> |
| | | <div> |
| | | <div> |
| | | <PIMTable :table-loading="tableLoading" |
| | | :table-data="tableData" |
| | | :column="tableColumns" |
| | | @selection-change="handleSelectionChange" |
| | | :is-selection="true" |
| | | :border="true" |
| | | :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }" |
| | | :page="{ |
| | | current: pageNum, |
| | | size: pageSize, |
| | | total: total, |
| | | }" |
| | | @pagination="pagination" |
| | | > |
| | | <template #inspector="{ row }"> |
| | | <div class="person-tags"> |
| | | <!-- è°è¯ä¿¡æ¯ï¼ä¸çº¿æ¶å é¤ --> |
| | | <!-- {{ console.log('inspector data:', row.inspector) }} --> |
| | | <template v-if="row.inspector && row.inspector.length > 0"> |
| | | <el-tag |
| | | v-for="(person, index) in row.inspector" |
| | | :key="index" |
| | | size="small" |
| | | type="primary" |
| | | class="person-tag" |
| | | > |
| | | {{ person }} |
| | | </el-tag> |
| | | </template> |
| | | <span v-else class="no-data">--</span> |
| | | </div> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | <form-dia ref="formDia" @closeDia="handleQuery"></form-dia> |
| | | <view-files ref="viewFiles"></view-files> |
| | | </div> |
| | | <div class="app-container"> |
| | | <el-form :inline="true" :model="queryParams" class="search-form"> |
| | | <el-form-item label="å·¡æ£ä»»å¡åç§°"> |
| | | <el-input |
| | | v-model="queryParams.taskName" |
| | | placeholder="请è¾å
¥å·¡æ£ä»»å¡åç§°" |
| | | clearable |
| | | style="width: 200px " |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <el-card> |
| | | <div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom: 10px;"> |
| | | <el-radio-group v-model="activeRadio" @change="radioChange"> |
| | | <el-radio-button v-for="tab in radios" |
| | | :key="tab.name" |
| | | :label="tab.label" |
| | | :value="tab.name"/> |
| | | </el-radio-group> |
| | | <!-- æä½æé®åº --> |
| | | <el-space v-if="activeRadio !== 'task'"> |
| | | <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">æ°å»º</el-button> |
| | | <el-button type="danger" :icon="Delete" @click="handleDelete">å é¤</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | </el-space> |
| | | <el-space v-else> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | </el-space> |
| | | </div> |
| | | <div> |
| | | <PIMTable :table-loading="tableLoading" |
| | | :table-data="tableData" |
| | | :column="tableColumns" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="handlePagination" |
| | | :is-selection="true" |
| | | :border="true" |
| | | :page="{ |
| | | current: pageNum, |
| | | size: pageSize, |
| | | total: total, |
| | | layout: 'total, sizes, prev, pager, next, jumper' |
| | | }" |
| | | :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }" |
| | | > |
| | | <template #inspector="{ row }"> |
| | | <div class="person-tags"> |
| | | <!-- è°è¯ä¿¡æ¯ï¼ä¸çº¿æ¶å é¤ --> |
| | | <!-- {{ console.log('inspector data:', row.inspector) }} --> |
| | | <template v-if="row.inspector && row.inspector.length > 0"> |
| | | <el-tag |
| | | v-for="(person, index) in row.inspector" |
| | | :key="index" |
| | | size="small" |
| | | type="primary" |
| | | class="person-tag" |
| | | > |
| | | {{ person }} |
| | | </el-tag> |
| | | </template> |
| | | <span v-else class="no-data">--</span> |
| | | </div> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </el-card> |
| | | <form-dia ref="formDia" @closeDia="handleQuery"></form-dia> |
| | | <view-files ref="viewFiles"></view-files> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | import { ElMessageBox } from "element-plus"; |
| | | |
| | | // ç»ä»¶å¼å
¥ |
| | | import Pagination from "@/components/Pagination/index.vue"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue"; |
| | | import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue"; |
| | | |
| | | // æ¥å£å¼å
¥ |
| | | import { |
| | | delTimingTask, |
| | | inspectionTaskList, |
| | | timingTaskList |
| | | delTimingTask, |
| | | inspectionTaskList, |
| | | timingTaskList |
| | | } from "@/api/inspectionManagement/index.js"; |
| | | |
| | | // å
¨å±åé |
| | |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | taskName: "", |
| | | taskName: "", |
| | | }); |
| | | |
| | | // åéæ¡é
ç½® |
| | | const activeRadio = ref("taskManage"); |
| | | const radios = reactive([ |
| | | { name: "taskManage", label: "宿¶ä»»å¡ç®¡ç" }, |
| | | { name: "task", label: "宿¶ä»»å¡è®°å½" }, |
| | | { name: "taskManage", label: "宿¶ä»»å¡ç®¡ç" }, |
| | | { name: "task", label: "宿¶ä»»å¡è®°å½" }, |
| | | ]); |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | |
| | | |
| | | // åé
ç½® |
| | | const columns = ref([ |
| | | { prop: "taskName", label: "å·¡æ£ä»»å¡åç§°", minWidth: 160 }, |
| | | { prop: "remarks", label: "夿³¨", minWidth: 150 }, |
| | | { prop: "inspector", label: "æ§è¡å·¡æ£äºº", minWidth: 150, slot: "inspector" }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | | minWidth: 150, |
| | | formatData: (cell) => ({ |
| | | DAILY: "æ¯æ¥", |
| | | WEEKLY: "æ¯å¨", |
| | | MONTHLY: "æ¯æ", |
| | | QUARTERLY: "å£åº¦" |
| | | }[cell] || "") |
| | | }, |
| | | { |
| | | prop: "frequencyDetail", |
| | | label: "å¼å§æ¥æä¸æ¶é´", |
| | | minWidth: 150, |
| | | formatter: (row, column, cellValue) => { |
| | | // å
夿æ¯å¦æ¯å符串 |
| | | if (typeof cellValue !== 'string') return ''; |
| | | let val = cellValue; |
| | | const replacements = { |
| | | MON: 'å¨ä¸', |
| | | TUE: 'å¨äº', |
| | | WED: 'å¨ä¸', |
| | | THU: 'å¨å', |
| | | FRI: 'å¨äº', |
| | | SAT: 'å¨å
', |
| | | SUN: '卿¥' |
| | | }; |
| | | // ä½¿ç¨æ£å䏿¬¡æ§æ¿æ¢ææå¹é
项 |
| | | return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]); |
| | | } |
| | | }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "dateStr", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | { prop: "taskName", label: "å·¡æ£ä»»å¡åç§°", minWidth: 160 }, |
| | | { prop: "remarks", label: "夿³¨", minWidth: 150 }, |
| | | { prop: "inspector", label: "æ§è¡å·¡æ£äºº", minWidth: 150, slot: "inspector" }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | | minWidth: 150, |
| | | formatter: (_, __, val) => ({ |
| | | DAILY: "æ¯æ¥", |
| | | WEEKLY: "æ¯å¨", |
| | | MONTHLY: "æ¯æ", |
| | | QUARTERLY: "å£åº¦" |
| | | }[val] || "") |
| | | }, |
| | | { |
| | | prop: "frequencyDetail", |
| | | label: "å¼å§æ¥æä¸æ¶é´", |
| | | minWidth: 150, |
| | | formatter: (row, column, cellValue) => { |
| | | // å
夿æ¯å¦æ¯å符串 |
| | | if (typeof cellValue !== 'string') return ''; |
| | | let val = cellValue; |
| | | const replacements = { |
| | | MON: 'å¨ä¸', |
| | | TUE: 'å¨äº', |
| | | WED: 'å¨ä¸', |
| | | THU: 'å¨å', |
| | | FRI: 'å¨äº', |
| | | SAT: 'å¨å
', |
| | | SUN: '卿¥' |
| | | }; |
| | | // ä½¿ç¨æ£å䏿¬¡æ§æ¿æ¢ææå¹é
项 |
| | | return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]); |
| | | } |
| | | }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "createTime", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | ]); |
| | | |
| | | // æä½åé
ç½® |
| | | const getOperationColumn = (operations) => { |
| | | if (!operations || operations.length === 0) return null; |
| | | |
| | | const operationConfig = { |
| | | label: "æä½", |
| | | width: 130, |
| | | fixed: "right", |
| | | dataType: "action", |
| | | operation: operations.map(op => { |
| | | switch (op) { |
| | | case 'edit': |
| | | return { |
| | | name: "ç¼è¾", |
| | | clickFun: handleAdd, |
| | | color: "#409EFF" |
| | | }; |
| | | case 'viewFile': |
| | | return { |
| | | name: "æ¥çéä»¶", |
| | | clickFun: viewFile, |
| | | color: "#67C23A" |
| | | }; |
| | | default: |
| | | return null; |
| | | } |
| | | }).filter(Boolean) |
| | | }; |
| | | |
| | | return operationConfig; |
| | | if (!operations || operations.length === 0) return null; |
| | | |
| | | const operationConfig = { |
| | | label: "æä½", |
| | | width: 130, |
| | | fixed: "right", |
| | | dataType: "action", |
| | | operation: operations.map(op => { |
| | | switch (op) { |
| | | case 'edit': |
| | | return { |
| | | name: "ç¼è¾", |
| | | clickFun: handleAdd, |
| | | color: "#409EFF" |
| | | }; |
| | | case 'viewFile': |
| | | return { |
| | | name: "æ¥çéä»¶", |
| | | clickFun: viewFile, |
| | | color: "#67C23A" |
| | | }; |
| | | default: |
| | | return null; |
| | | } |
| | | }).filter(Boolean) |
| | | }; |
| | | |
| | | return operationConfig; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | radioChange('taskManage'); |
| | | radioChange('taskManage'); |
| | | }); |
| | | |
| | | // åéåå |
| | | const radioChange = (value) => { |
| | | if (value === "taskManage") { |
| | | const operationColumn = getOperationColumn(['edit']); |
| | | tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])]; |
| | | operationsArr.value = ['edit']; |
| | | } else if (value === "task") { |
| | | const operationColumn = getOperationColumn(['viewFile']); |
| | | tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])]; |
| | | operationsArr.value = ['viewFile']; |
| | | } |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | if (value === "taskManage") { |
| | | const operationColumn = getOperationColumn(['edit']); |
| | | tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])]; |
| | | operationsArr.value = ['edit']; |
| | | } else if (value === "task") { |
| | | const operationColumn = getOperationColumn(['viewFile']); |
| | | tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])]; |
| | | operationsArr.value = ['viewFile']; |
| | | } |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | }; |
| | | |
| | | // æ¥è¯¢æä½ |
| | | const handleQuery = () => { |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | pageNum.value = obj.page; |
| | | pageSize.value = obj.limit; |
| | | // å页å¤ç |
| | | const handlePagination = (val) => { |
| | | pageNum.value = val.page; |
| | | pageSize.value = val.limit; |
| | | getList(); |
| | | }; |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | const params = { ...queryParams, size: pageSize.value, current: pageNum.value }; |
| | | |
| | | let apiCall; |
| | | if (activeRadio.value === "task") { |
| | | apiCall = inspectionTaskList(params); |
| | | } else { |
| | | apiCall = timingTaskList(params); |
| | | } |
| | | |
| | | apiCall.then(res => { |
| | | const rawData = res.data.records || []; |
| | | // å¤ç inspector åæ®µï¼å°å符串转æ¢ä¸ºæ°ç»ï¼éç¨äºæææ
åµï¼ |
| | | tableData.value = rawData.map(item => { |
| | | const processedItem = { ...item }; |
| | | |
| | | // å¤ç inspector åæ®µ |
| | | if (processedItem.inspector) { |
| | | if (typeof processedItem.inspector === 'string') { |
| | | // å符串æéå·åå² |
| | | processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s); |
| | | } else if (!Array.isArray(processedItem.inspector)) { |
| | | // éæ°ç»è½¬ä¸ºæ°ç» |
| | | processedItem.inspector = [processedItem.inspector]; |
| | | } |
| | | } else { |
| | | // 空å¼è®¾ä¸ºç©ºæ°ç» |
| | | processedItem.inspector = []; |
| | | } |
| | | |
| | | return processedItem; |
| | | }); |
| | | total.value = res.data.total || 0; |
| | | }).finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | tableLoading.value = true; |
| | | |
| | | const params = { ...queryParams, size: pageSize.value, current: pageNum.value }; |
| | | |
| | | let apiCall; |
| | | if (activeRadio.value === "task") { |
| | | apiCall = inspectionTaskList(params); |
| | | } else { |
| | | apiCall = timingTaskList(params); |
| | | } |
| | | |
| | | apiCall.then(res => { |
| | | const rawData = res.data.records || []; |
| | | // å¤ç inspector åæ®µï¼å°å符串转æ¢ä¸ºæ°ç»ï¼éç¨äºæææ
åµï¼ |
| | | tableData.value = rawData.map(item => { |
| | | const processedItem = { ...item }; |
| | | |
| | | // å¤ç inspector åæ®µ |
| | | if (processedItem.inspector) { |
| | | if (typeof processedItem.inspector === 'string') { |
| | | // å符串æéå·åå² |
| | | processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s); |
| | | } else if (!Array.isArray(processedItem.inspector)) { |
| | | // éæ°ç»è½¬ä¸ºæ°ç» |
| | | processedItem.inspector = [processedItem.inspector]; |
| | | } |
| | | } else { |
| | | // 空å¼è®¾ä¸ºç©ºæ°ç» |
| | | processedItem.inspector = []; |
| | | } |
| | | |
| | | return processedItem; |
| | | }); |
| | | total.value = res.data.total || 0; |
| | | }).finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // éç½®æ¥è¯¢ |
| | | const resetQuery = () => { |
| | | for (const key in queryParams) { |
| | | if (!["pageNum", "pageSize"].includes(key)) { |
| | | queryParams[key] = ""; |
| | | } |
| | | } |
| | | handleQuery(); |
| | | for (const key in queryParams) { |
| | | if (!["pageNum", "pageSize"].includes(key)) { |
| | | queryParams[key] = ""; |
| | | } |
| | | } |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // æ°å¢ / ç¼è¾ |
| | | const handleAdd = (row) => { |
| | | const type = row ? 'edit' : 'add'; |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row); |
| | | }); |
| | | const type = row ? 'edit' : 'add'; |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | |
| | | // æ¥çéä»¶ |
| | | const viewFile = (row) => { |
| | | nextTick(() => { |
| | | viewFiles.value?.openDialog(row); |
| | | }); |
| | | nextTick(() => { |
| | | viewFiles.value?.openDialog(row); |
| | | }); |
| | | }; |
| | | |
| | | // å é¤æä½ |
| | | const handleDelete = () => { |
| | | if (!selectedRows.value.length) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦å é¤çæ°æ®"); |
| | | return; |
| | | } |
| | | |
| | | const deleteIds = selectedRows.value.map(item => item.id); |
| | | |
| | | proxy.$modal.confirm('æ¯å¦ç¡®è®¤å 餿鿰æ®é¡¹ï¼').then(() => { |
| | | return delTimingTask(deleteIds); |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | handleQuery(); |
| | | }).catch(() => {}); |
| | | if (!selectedRows.value.length) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦å é¤çæ°æ®"); |
| | | return; |
| | | } |
| | | |
| | | const deleteIds = selectedRows.value.map(item => item.id); |
| | | |
| | | proxy.$modal.confirm('æ¯å¦ç¡®è®¤å 餿鿰æ®é¡¹ï¼').then(() => { |
| | | return delTimingTask(deleteIds); |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | handleQuery(); |
| | | }).catch(() => {}); |
| | | }; |
| | | |
| | | // å¤éåæ´ |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // æ ¹æ®å½åéä¸çæ ç¾é¡µè°ç¨ä¸åçå¯¼åºæ¥å£ |
| | | if (activeRadio.value === "taskManage") { |
| | | // 宿¶ä»»å¡ç®¡ç |
| | | proxy.download("/timingTask/export", {}, "宿¶ä»»å¡ç®¡ç.xlsx"); |
| | | } else if (activeRadio.value === "task") { |
| | | // 宿¶ä»»å¡è®°å½ |
| | | proxy.download("/inspectionTask/export", {}, "宿¶ä»»å¡è®°å½.xlsx"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // æ ¹æ®å½åéä¸çæ ç¾é¡µè°ç¨ä¸åçå¯¼åºæ¥å£ |
| | | if (activeRadio.value === "taskManage") { |
| | | // 宿¶ä»»å¡ç®¡ç |
| | | proxy.download("/timingTask/export", {}, "宿¶ä»»å¡ç®¡ç.xlsx"); |
| | | } else if (activeRadio.value === "task") { |
| | | // 宿¶ä»»å¡è®°å½ |
| | | proxy.download("/inspectionTask/export", {}, "宿¶ä»»å¡è®°å½.xlsx"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .person-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 4px; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .person-tag { |
| | | margin-right: 4px; |
| | | margin-bottom: 2px; |
| | | margin-right: 4px; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .no-data { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @click="openFileDialog(row)" |
| | | > |
| | | éä»¶ |
| | | </el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | |
| | | <PlanModal ref="planModalRef" @ok="getTableData" /> |
| | | <MaintenanceModal ref="maintainModalRef" @ok="getTableData" /> |
| | | <FormDia ref="formDiaRef" @closeDia="getScheduledTableData" /> |
| | | <FileListDialog |
| | | ref="fileListDialogRef" |
| | | v-model="fileDialogVisible" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :delete-method="handleAttachmentDelete" |
| | | :name-column-label="'éä»¶åç§°'" |
| | | :rulesRegulationsManagementId="currentMaintenanceTaskId" |
| | | @upload="handleAttachmentUpload" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import PlanModal from './Form/PlanModal.vue' |
| | | import MaintenanceModal from './Form/MaintenanceModal.vue' |
| | | import FormDia from './Form/formDia.vue' |
| | | import FileListDialog from '@/components/Dialog/FileListDialog.vue' |
| | | import { |
| | | getUpkeepPage, |
| | | delUpkeep, |
| | | deviceMaintenanceTaskList, |
| | | deviceMaintenanceTaskDel, |
| | | } from '@/api/equipmentManagement/upkeep' |
| | | import { |
| | | listMaintenanceTaskFiles, |
| | | addMaintenanceTaskFile, |
| | | delMaintenanceTaskFile, |
| | | } from '@/api/equipmentManagement/maintenanceTaskFile' |
| | | import dayjs from 'dayjs' |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | |
| | | const maintainModalRef = ref() |
| | | // 宿¶ä»»å¡å¼¹çªæ§å¶å¨ |
| | | const formDiaRef = ref() |
| | | // éä»¶å¼¹çª |
| | | const fileListDialogRef = ref(null) |
| | | const fileDialogVisible = ref(false) |
| | | const currentMaintenanceTaskId = ref(null) |
| | | |
| | | // ä»»å¡è®°å½tabï¼å设å¤ä¿å
»é¡µé¢ï¼ç¸å
³åé |
| | | const filters = reactive({ |
| | |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "300px", |
| | | width: "350px", |
| | | }, |
| | | ]) |
| | | |
| | |
| | | getTableData() |
| | | } |
| | | |
| | | // éä»¶ç¸å
³æ¹æ³ |
| | | // æ¥è¯¢éä»¶å表 |
| | | const fetchMaintenanceTaskFiles = async (deviceMaintenanceId) => { |
| | | try { |
| | | const params = { |
| | | current: 1, |
| | | size: 100, |
| | | deviceMaintenanceId, |
| | | rulesRegulationsManagementId:deviceMaintenanceId |
| | | } |
| | | const res = await listMaintenanceTaskFiles(params) |
| | | const records = res?.data?.records || [] |
| | | const mapped = records.map(item => ({ |
| | | id: item.id, |
| | | name: item.fileName || item.name, |
| | | url: item.fileUrl || item.url, |
| | | raw: item, |
| | | })) |
| | | fileListDialogRef.value?.setList(mapped) |
| | | } catch (error) { |
| | | ElMessage.error('è·åéä»¶å表失败') |
| | | } |
| | | } |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const openFileDialog = async (row) => { |
| | | currentMaintenanceTaskId.value = row.id |
| | | fileDialogVisible.value = true |
| | | await fetchMaintenanceTaskFiles(row.id) |
| | | } |
| | | |
| | | // å·æ°éä»¶å表 |
| | | const refreshFileList = async () => { |
| | | if (!currentMaintenanceTaskId.value) return |
| | | await fetchMaintenanceTaskFiles(currentMaintenanceTaskId.value) |
| | | } |
| | | |
| | | // ä¸ä¼ éä»¶ |
| | | const handleAttachmentUpload = async (filePayload) => { |
| | | if (!currentMaintenanceTaskId.value) return |
| | | try { |
| | | const payload = { |
| | | name: filePayload?.fileName || filePayload?.name, |
| | | url: filePayload?.fileUrl || filePayload?.url, |
| | | deviceMaintenanceId: currentMaintenanceTaskId.value, |
| | | } |
| | | await addMaintenanceTaskFile(payload) |
| | | ElMessage.success('æä»¶ä¸ä¼ æå') |
| | | await refreshFileList() |
| | | } catch (error) { |
| | | ElMessage.error('æä»¶ä¸ä¼ 失败') |
| | | } |
| | | } |
| | | |
| | | // å é¤éä»¶ |
| | | const handleAttachmentDelete = async (row) => { |
| | | if (!row?.id) return false |
| | | try { |
| | | await ElMessageBox.confirm('确认å é¤è¯¥éä»¶ï¼', 'æç¤º', { type: 'warning' }) |
| | | } catch { |
| | | return false |
| | | } |
| | | try { |
| | | await delMaintenanceTaskFile(row.id) |
| | | ElMessage.success('å 餿å') |
| | | await refreshFileList() |
| | | return true |
| | | } catch (error) { |
| | | ElMessage.error('å é¤å¤±è´¥') |
| | | return false |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | // æ ¹æ®é»è®¤æ¿æ´»ç Tab è°ç¨å¯¹åºçæ¥è¯¢æ¥å£ |
| | | if (activeTab.value === 'scheduled') { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="isShow" |
| | | :title="operationType === 'frozen' ? 'å»ç»åºå' : 'è§£å»åºå'" |
| | | width="800" |
| | | @close="closeModal" |
| | | > |
| | | <el-form label-width="140px" :model="formState" ref="formRef"> |
| | | <el-form-item |
| | | :label="operationType === 'frozen' ? 'å»ç»æ°éï¼' : 'è§£å»æ°éï¼'" |
| | | prop="lockedQuantity" |
| | | > |
| | | <el-input-number v-model="formState.lockedQuantity" :step="1" :min="1" precision="0" style="width: 100%" :max="maxCount" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="handleSubmit">确认</el-button> |
| | | <el-button @click="closeModal">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, computed, getCurrentInstance} from "vue"; |
| | | import {frozenStockInventory, thawStockInventory} from "@/api/inventoryManagement/stockInventory.js"; |
| | | import {frozenStockUninventory, thawStockUninventory} from "@/api/inventoryManagement/stockUninventory.js"; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | | required: true, |
| | | }, |
| | | |
| | | operationType: { |
| | | type: String, |
| | | required: true, |
| | | default: 'frozen', |
| | | }, |
| | | |
| | | type: { |
| | | type: String, |
| | | required: true, |
| | | default: 'qualified', |
| | | }, |
| | | |
| | | record: { |
| | | type: Object, |
| | | default: () => {}, |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:visible', 'completed']); |
| | | |
| | | // ååºå¼æ°æ®ï¼æ¿ä»£é项å¼ç dataï¼ |
| | | const formState = ref({ |
| | | lockedQuantity: 0, |
| | | }); |
| | | |
| | | const isShow = computed({ |
| | | get() { |
| | | return props.visible; |
| | | }, |
| | | set(val) { |
| | | emit('update:visible', val); |
| | | }, |
| | | }); |
| | | |
| | | |
| | | let { proxy } = getCurrentInstance() |
| | | |
| | | const closeModal = () => { |
| | | // éç½®è¡¨åæ°æ® |
| | | formState.value = { |
| | | lockedQuantity: undefined |
| | | }; |
| | | isShow.value = false; |
| | | }; |
| | | |
| | | const maxCount = computed(() => { |
| | | // å»ç»åºåæå¤§æ°é为æªè§£å»æ°é |
| | | if (props.operationType === 'frozen') { |
| | | return props.record.unLockedQuantity |
| | | } |
| | | // è§£å»åºåæå¤§æ°é为已å»ç»æ°é |
| | | return props.record.lockedQuantity |
| | | }) |
| | | |
| | | const handleSubmit = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | const data = Object.assign({id: props.record.id}, formState.value); |
| | | if (props.type === 'qualified') { |
| | | // å»ç» |
| | | if (props.operationType === 'frozen') { |
| | | frozenStockInventory(data).then(res => { |
| | | if (res.code === 200) { |
| | | // å
³éæ¨¡ææ¡ |
| | | isShow.value = false; |
| | | // åç¥ç¶ç»ä»¶å·²å®æ |
| | | emit('completed'); |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | } |
| | | }) |
| | | } else { |
| | | thawStockInventory(data).then(res => { |
| | | if (res.code === 200) { |
| | | // å
³éæ¨¡ææ¡ |
| | | isShow.value = false; |
| | | // åç¥ç¶ç»ä»¶å·²å®æ |
| | | emit('completed'); |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | } |
| | | }) |
| | | } |
| | | } else { |
| | | if (props.operationType === 'frozen') { |
| | | frozenStockUninventory(data).then(res => { |
| | | if (res.code === 200) { |
| | | // å
³éæ¨¡ææ¡ |
| | | isShow.value = false; |
| | | // åç¥ç¶ç»ä»¶å·²å®æ |
| | | emit('completed'); |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | } |
| | | }) |
| | | } else { |
| | | thawStockUninventory(data).then(res => { |
| | | if (res.code === 200) { |
| | | // å
³éæ¨¡ææ¡ |
| | | isShow.value = false; |
| | | // åç¥ç¶ç»ä»¶å·²å®æ |
| | | emit('completed'); |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | formState.value.lockedQuantity = maxCount.value; |
| | | }) |
| | | |
| | | defineExpose({ |
| | | closeModal, |
| | | handleSubmit, |
| | | isShow, |
| | | }); |
| | | </script> |
| | |
| | | :headers="upload.headers" |
| | | :action="upload.url" |
| | | :disabled="upload.isUploading" |
| | | :showTip="false" |
| | | :showTip="true" |
| | | @success="handleFileSuccess" |
| | | :downloadTemplate="downloadTemplate" |
| | | /> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {computed, reactive} from "vue"; |
| | | import {computed, getCurrentInstance, reactive} from "vue"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import { FileUpload } from "@/components/Upload"; |
| | | import { ElMessage } from "element-plus"; |
| | |
| | | defineOptions({ |
| | | name: "导å
¥åºå", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | |
| | | } |
| | | }; |
| | | |
| | | const downloadTemplate = () => { |
| | | proxy.download("/stockInventory/downloadStockInventory", {}, "åºå导å
¥æ¨¡æ¿.xlsx"); |
| | | } |
| | | |
| | | const closeModal = () => { |
| | | isShow.value = false; |
| | | }; |
| | |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="æ°é" |
| | | label="åºåæ°é" |
| | | prop="qualitity" |
| | | > |
| | | <el-input-number v-model="formState.qualitity" :step="1" :min="0" style="width: 100%" /> |
| | | <el-input-number v-model="formState.qualitity" :step="1" :min="1" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | v-if="type === 'qualified'" |
| | | label="åºåé¢è¦æ°é" |
| | | prop="warnNum" |
| | | > |
| | | <el-input-number v-model="formState.warnNum" :step="1" :min="0" :max="formState.qualitity" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | |
| | | productModelName: "", |
| | | unit: "", |
| | | qualitity: 0, |
| | | warnNum: 0, |
| | | remark: '', |
| | | }); |
| | | |
| | |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="isShowNewModal = true">æ°å¢åºå</el-button> |
| | | <el-button @click="importTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</el-button> |
| | | <el-button type="info" plain icon="Upload" @click="isShowImportModal = true"> |
| | | 导å
¥åºå |
| | | </el-button> |
| | |
| | | <el-table-column label="è§æ ¼åå·" prop="model" show-overflow-tooltip /> |
| | | <el-table-column label="åä½" prop="unit" show-overflow-tooltip /> |
| | | <el-table-column label="åºåæ°é" prop="qualitity" show-overflow-tooltip /> |
| | | <el-table-column label="å»ç»æ°é" prop="lockedQuantity" show-overflow-tooltip /> |
| | | <el-table-column label="åºåé¢è¦æ°é" prop="warnNum" show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | <el-table-column label="æè¿æ´æ°æ¶é´" prop="updateTime" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" min-width="60" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.qualitity === 0">é¢ç¨</el-button> |
| | | <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">é¢ç¨</el-button> |
| | | <el-button link type="primary" size="small" v-if="scope.row.unLockedQuantity > 0" @click="showFrozenModal(scope.row)">å»ç»</el-button> |
| | | <el-button link type="primary" size="small" v-if="scope.row.lockedQuantity > 0" @click="showThawModal(scope.row)">è§£å»</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <subtract-stock-inventory v-if="isShowSubtractModal" |
| | | v-model:visible="isShowSubtractModal" |
| | | :record="record" |
| | | type="qualified" |
| | | @completed="handleQuery" /> |
| | | <!-- 导å
¥åºå--> |
| | | <import-stock-inventory v-if="isShowImportModal" |
| | | v-model:visible="isShowImportModal" |
| | | type="qualified" |
| | | @uploadSuccess="handleQuery" /> |
| | | <!-- å»ç»/è§£å»åºå--> |
| | | <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal" |
| | | v-model:visible="isShowFrozenAndThawModal" |
| | | :record="record" |
| | | :operation-type="operationType" |
| | | type="qualified" |
| | | @completed="handleQuery" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | const NewStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/New.vue")); |
| | | const SubtractStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Subtract.vue")); |
| | | const ImportStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Import.vue")); |
| | | const FrozenAndThawStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/FrozenAndThaw.vue")); |
| | | const { proxy } = getCurrentInstance() |
| | | const tableData = ref([]) |
| | | const selectedRows = ref([]) |
| | |
| | | const isShowNewModal = ref(false) |
| | | // æ¯å¦æ¾ç¤ºé¢ç¨å¼¹æ¡ |
| | | const isShowSubtractModal = ref(false) |
| | | // æ¯å¦æ¾ç¤ºå»ç»/è§£å»å¼¹æ¡ |
| | | const isShowFrozenAndThawModal = ref(false) |
| | | // æä½ç±»å |
| | | const operationType = ref('frozen') |
| | | // æ¯å¦æ¾ç¤ºå¯¼å
¥å¼¹æ¡ |
| | | const isShowImportModal = ref(false) |
| | | const data = reactive({ |
| | |
| | | isShowSubtractModal.value = true |
| | | } |
| | | |
| | | // ç¹å»å»ç» |
| | | const showFrozenModal = (row) => { |
| | | record.value = row |
| | | isShowFrozenAndThawModal.value = true |
| | | operationType.value = 'frozen' |
| | | } |
| | | |
| | | // ç¹å»è§£å» |
| | | const showThawModal = (row) => { |
| | | record.value = row |
| | | isShowFrozenAndThawModal.value = true |
| | | operationType.value = 'thaw' |
| | | } |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | // è¿æ»¤æåæ°æ® |
| | |
| | | |
| | | // è¡¨æ ¼è¡ç±»å |
| | | const tableRowClassName = ({ row }) => { |
| | | const stock = Number(row?.inboundNum0 ?? 0); |
| | | const stock = Number(row?.unLockedQuantity ?? 0); |
| | | const warn = Number(row?.warnNum ?? 0); |
| | | if (!Number.isFinite(stock) || !Number.isFinite(warn)) { |
| | | return ''; |
| | |
| | | }).catch(() => { |
| | | proxy.$modal.msg("已忶") |
| | | }) |
| | | } |
| | | |
| | | const importTemplate =() =>{ |
| | | proxy.download("/stockInventory/downloadStockInventory", {}, "åºå导å
¥æ¨¡æ¿.xlsx"); |
| | | } |
| | | |
| | | onMounted(() => { |
| | |
| | | }) |
| | | |
| | | const maxQuality = computed(() => { |
| | | return props.record.qualitity ? props.record.qualitity : 0; |
| | | return props.record.unLockedQuantity ? props.record.unLockedQuantity : 0; |
| | | }) |
| | | |
| | | const initFormData = () => { |
| | |
| | | <el-table-column label="è§æ ¼åå·" prop="model" show-overflow-tooltip /> |
| | | <el-table-column label="åä½" prop="unit" show-overflow-tooltip /> |
| | | <el-table-column label="åºåæ°é" prop="qualitity" show-overflow-tooltip /> |
| | | <el-table-column label="åºåé¢è¦æ°é" prop="warnNum" show-overflow-tooltip /> |
| | | <el-table-column label="å»ç»æ°é" prop="lockedQuantity" show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | <el-table-column label="æè¿æ´æ°æ¶é´" prop="updateTime" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" min-width="60" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.qualitity === 0">é¢ç¨</el-button> |
| | | <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">é¢ç¨</el-button> |
| | | <el-button link type="primary" size="small" v-if="scope.row.unLockedQuantity > 0" @click="showFrozenModal(scope.row)">å»ç»</el-button> |
| | | <el-button link type="primary" size="small" v-if="scope.row.lockedQuantity > 0" @click="showThawModal(scope.row)">è§£å»</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <subtract-stock-inventory v-if="isShowSubtractModal" |
| | | v-model:visible="isShowSubtractModal" |
| | | :record="record" |
| | | type="unqualified" |
| | | @completed="handleQuery" /> |
| | | <!-- å»ç»/è§£å»åºå--> |
| | | <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal" |
| | | v-model:visible="isShowFrozenAndThawModal" |
| | | :record="record" |
| | | :operation-type="operationType" |
| | | type="unqualified" |
| | | @completed="handleQuery" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { getStockUninventoryListPage } from "@/api/inventoryManagement/stockUninventory.js"; |
| | | const NewStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/New.vue")); |
| | | const SubtractStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Subtract.vue")); |
| | | const FrozenAndThawStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/FrozenAndThaw.vue")); |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const tableData = ref([]) |
| | |
| | | const isShowNewModal = ref(false) |
| | | // æ¯å¦æ¾ç¤ºé¢ç¨å¼¹æ¡ |
| | | const isShowSubtractModal = ref(false) |
| | | // æ¯å¦æ¾ç¤ºå»ç»/è§£å»å¼¹æ¡ |
| | | const isShowFrozenAndThawModal = ref(false) |
| | | // æä½ç±»å |
| | | const operationType = ref('frozen') |
| | | const data = reactive({ |
| | | searchForm: { |
| | | productName: '', |
| | |
| | | isShowSubtractModal.value = true |
| | | } |
| | | |
| | | // ç¹å»å»ç» |
| | | const showFrozenModal = (row) => { |
| | | record.value = row |
| | | isShowFrozenAndThawModal.value = true |
| | | operationType.value = 'frozen' |
| | | } |
| | | |
| | | // ç¹å»è§£å» |
| | | const showThawModal = (row) => { |
| | | record.value = row |
| | | isShowFrozenAndThawModal.value = true |
| | | operationType.value = 'thaw' |
| | | } |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | // è¿æ»¤æåæ°æ® |
| | |
| | | |
| | | // è¡¨æ ¼è¡ç±»å |
| | | const tableRowClassName = ({ row }) => { |
| | | const stock = Number(row?.inboundNum0 ?? 0); |
| | | const warn = Number(row?.warnNum ?? 0); |
| | | if (!Number.isFinite(stock) || !Number.isFinite(warn)) { |
| | | return ''; |
| | | } |
| | | return stock < warn ? 'row-low-stock' : ''; |
| | | // const stock = Number(row?.unLockedQuantity ?? 0); |
| | | // const warn = Number(row?.warnNum ?? 0); |
| | | // if (!Number.isFinite(stock) || !Number.isFinite(warn)) { |
| | | // return ''; |
| | | // } |
| | | // return stock < warn ? 'row-low-stock' : ''; |
| | | }; |
| | | |
| | | // å¯¼åº |
| | |
| | | > |
| | | <el-option label="æ¥æ¥" value="daily" /> |
| | | <el-option label="ææ¥" value="monthly" /> |
| | | <el-option label="ä½ä¸æ¥è¡¨" value="work" /> |
| | | <el-option label="è¿åºåæ¥è¡¨" value="inout" /> |
| | | </el-select> |
| | | |
| | |
| | | </el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | | </div> |
| | | |
| | | |
| | | <div class="search_right"> |
| | | <el-button type="success" @click="handleExport" icon="Download"> |
| | | å¯¼åºæ¥è¡¨ |
| | | </el-button> |
| | | <!-- <el-button type="success" @click="handleExport" icon="Download">--> |
| | | <!-- å¯¼åºæ¥è¡¨--> |
| | | <!-- </el-button>--> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç»è®¡å¡ç --> |
| | | <div class="stats_cards" v-if="reportData.summary"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-card class="stats_card"> |
| | | <div class="stats_content"> |
| | | <div class="stats_icon in"> |
| | | <el-icon><TrendCharts /></el-icon> |
| | | </div> |
| | | <div class="stats_info"> |
| | | <div class="stats_value">{{ reportData.summary.totalIn || 0 }}</div> |
| | | <div class="stats_label">æ»å
¥åºé</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stats_card"> |
| | | <div class="stats_content"> |
| | | <div class="stats_icon out"> |
| | | <el-icon><TrendCharts /></el-icon> |
| | | </div> |
| | | <div class="stats_info"> |
| | | <div class="stats_value">{{ reportData.summary.totalOut || 0 }}</div> |
| | | <div class="stats_label">æ»åºåºé</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stats_card"> |
| | | <div class="stats_content"> |
| | | <div class="stats_icon stock"> |
| | | <el-icon><Box /></el-icon> |
| | | </div> |
| | | <div class="stats_info"> |
| | | <div class="stats_value">{{ reportData.summary.currentStock || 0 }}</div> |
| | | <div class="stats_label">å½ååºå</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stats_card"> |
| | | <div class="stats_content"> |
| | | <div class="stats_icon turnover"> |
| | | <el-icon><Refresh /></el-icon> |
| | | </div> |
| | | <div class="stats_info"> |
| | | <div class="stats_value">{{ reportData.summary.turnoverRate || 0 }}%</div> |
| | | <div class="stats_label">å¨è½¬ç</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <!-- <!– ç»è®¡å¡ç –>--> |
| | | <!-- <div class="stats_cards" v-if="reportData.summary">--> |
| | | <!-- <el-row :gutter="20">--> |
| | | <!-- <el-col :span="6">--> |
| | | <!-- <el-card class="stats_card">--> |
| | | <!-- <div class="stats_content">--> |
| | | <!-- <div class="stats_icon in">--> |
| | | <!-- <el-icon><TrendCharts /></el-icon>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="stats_info">--> |
| | | <!-- <div class="stats_value">{{ reportData.summary.totalIn || 0 }}</div>--> |
| | | <!-- <div class="stats_label">æ»å
¥åºé</div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </el-card>--> |
| | | <!-- </el-col>--> |
| | | <!-- <el-col :span="6">--> |
| | | <!-- <el-card class="stats_card">--> |
| | | <!-- <div class="stats_content">--> |
| | | <!-- <div class="stats_icon out">--> |
| | | <!-- <el-icon><TrendCharts /></el-icon>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="stats_info">--> |
| | | <!-- <div class="stats_value">{{ reportData.summary.totalOut || 0 }}</div>--> |
| | | <!-- <div class="stats_label">æ»åºåºé</div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </el-card>--> |
| | | <!-- </el-col>--> |
| | | <!-- <el-col :span="6">--> |
| | | <!-- <el-card class="stats_card">--> |
| | | <!-- <div class="stats_content">--> |
| | | <!-- <div class="stats_icon stock">--> |
| | | <!-- <el-icon><Box /></el-icon>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="stats_info">--> |
| | | <!-- <div class="stats_value">{{ reportData.summary.currentStock || 0 }}</div>--> |
| | | <!-- <div class="stats_label">å½ååºå</div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </el-card>--> |
| | | <!-- </el-col>--> |
| | | <!-- <el-col :span="6">--> |
| | | <!-- <el-card class="stats_card">--> |
| | | <!-- <div class="stats_content">--> |
| | | <!-- <div class="stats_icon turnover">--> |
| | | <!-- <el-icon><Refresh /></el-icon>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="stats_info">--> |
| | | <!-- <div class="stats_value">{{ reportData.summary.turnoverRate || 0 }}%</div>--> |
| | | <!-- <div class="stats_label">å¨è½¬ç</div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </el-card>--> |
| | | <!-- </el-col>--> |
| | | <!-- </el-row>--> |
| | | <!-- </div>--> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <div class="chart_section" v-if="reportData.chartData"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card> |
| | | <template #header> |
| | | <span>åºåè¶å¿å¾</span> |
| | | </template> |
| | | <div ref="trendChart" style="height: 300px;"></div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-card> |
| | | <template #header> |
| | | <span>è¿åºåºå¯¹æ¯</span> |
| | | </template> |
| | | <div ref="comparisonChart" style="height: 300px;"></div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <!-- <!– å¾è¡¨åºå –>--> |
| | | <!-- <div class="chart_section" v-if="reportData.chartData">--> |
| | | <!-- <el-row :gutter="20">--> |
| | | <!-- <el-col :span="12">--> |
| | | <!-- <el-card>--> |
| | | <!-- <template #header>--> |
| | | <!-- <span>åºåè¶å¿å¾</span>--> |
| | | <!-- </template>--> |
| | | <!-- <div ref="trendChart" style="height: 300px;"></div>--> |
| | | <!-- </el-card>--> |
| | | <!-- </el-col>--> |
| | | <!-- <el-col :span="12">--> |
| | | <!-- <el-card>--> |
| | | <!-- <template #header>--> |
| | | <!-- <span>è¿åºåºå¯¹æ¯</span>--> |
| | | <!-- </template>--> |
| | | <!-- <div ref="comparisonChart" style="height: 300px;"></div>--> |
| | | <!-- </el-card>--> |
| | | <!-- </el-col>--> |
| | | <!-- </el-row>--> |
| | | <!-- </div>--> |
| | | |
| | | <!-- è¯¦ç»æ°æ®è¡¨æ ¼ --> |
| | | <div class="table_section"> |
| | |
| | | width="60" |
| | | /> |
| | | <el-table-column |
| | | v-if="searchForm.reportType === 'daily'" |
| | | label="æ¥æ" |
| | | prop="createTime" |
| | | width="100" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | v-if="searchForm.reportType === 'monthly'" |
| | | label="æä»½" |
| | | prop="createTime" |
| | | width="100" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | label="å
¥åºæ¶é´" |
| | | prop="createTime" |
| | | width="100" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | v-if="searchForm.reportType !== 'inout'" |
| | | /> |
| | | <el-table-column |
| | | label="å
¥åºæ¹æ¬¡" |
| | | prop="inboundBatches" |
| | | width="160" |
| | | width="240" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¾åºååç§°" |
| | | prop="supplierName" |
| | | min-width="240" |
| | | show-overflow-tooltip |
| | | v-if="searchForm.reportType !== 'inout'" |
| | | /> |
| | | <el-table-column |
| | | label="产å大类" |
| | | prop="productCategory" |
| | | width="100" |
| | | prop="productName" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="è§æ ¼åå·" |
| | | prop="specificationModel" |
| | | min-width="200" |
| | | prop="model" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åä½" |
| | | prop="unit" |
| | | width="70" |
| | | show-overflow-tooltip |
| | | /> |
| | | <!-- <el-table-column |
| | | label="æååºå" |
| | | prop="beginStock" |
| | | width="100" |
| | | align="center" |
| | | /> --> |
| | | <el-table-column |
| | | label="å
¥åºæ°é" |
| | | prop="inboundNum" |
| | | width="100" |
| | | prop="totalStockIn" |
| | | align="center" |
| | | v-if="searchForm.reportType === 'inout'" |
| | | /> |
| | | <!-- <el-table-column |
| | | <el-table-column |
| | | label="å
¥åºæ°é" |
| | | prop="stockInNum" |
| | | align="center" |
| | | v-else |
| | | /> |
| | | <el-table-column |
| | | label="åºåºæ°é" |
| | | prop="" |
| | | prop="totalStockOut" |
| | | width="100" |
| | | align="center" |
| | | /> --> |
| | | v-if="searchForm.reportType === 'inout'" |
| | | /> |
| | | <el-table-column |
| | | label="ç°å¨åºå" |
| | | prop="inboundNum0" |
| | | width="100" |
| | | prop="currentStock" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨åä»·" |
| | | prop="taxInclusiveUnitPrice" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨æ»ä»·" |
| | | prop="taxInclusiveTotalPrice" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ç¨ç(%)" |
| | | prop="taxRate" |
| | | width="80" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¸å«ç¨æ»ä»·" |
| | | prop="taxExclusiveTotalPrice" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="æ¥æº" |
| | | prop="recordType" |
| | | v-if="searchForm.reportType !== 'inout'" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ getRecordType(scope.row.recordType) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="å
¥åºäºº" |
| | | prop="createBy" |
| | | width="80" |
| | | v-if="searchForm.reportType !== 'inout'" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | v-if="searchForm.reportType === 'work'" |
| | | label="æä½äººå" |
| | | prop="operator" |
| | | width="80" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | v-if="searchForm.reportType === 'work'" |
| | | label="æä½æ¶é´" |
| | | prop="operateTime" |
| | | width="150" |
| | | align="center" |
| | | /> |
| | | </el-table> |
| | | </el-card> |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import * as echarts from 'echarts' |
| | | import { |
| | | getStockDailyReport, |
| | | getStockMonthlyReport, |
| | | getWorkReport, |
| | | getStockInOutReport, |
| | | exportStockReport |
| | | } from '@/api/inventoryManagement/stockReport' |
| | | import { |
| | | getStockInventoryInAndOutReportList, |
| | | getStockInventoryReportList |
| | | } from "@/api/inventoryManagement/stockInventory.js"; |
| | | import {findAllQualifiedStockRecordTypeOptions} from "@/api/basicData/enum.js"; |
| | | |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | |
| | | tableData: [] |
| | | }) |
| | | |
| | | const stockRecordTypeOptions = ref([]) |
| | | |
| | | const getRecordType = (recordType) => { |
| | | return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || '' |
| | | } |
| | | |
| | | // è·åæ¥æºç±»åé项 |
| | | const fetchStockRecordTypeOptions = () => { |
| | | findAllQualifiedStockRecordTypeOptions() |
| | | .then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }) |
| | | } |
| | | |
| | | // è·åè¡¨æ ¼æ é¢ |
| | | const getTableTitle = () => { |
| | | const typeMap = { |
| | | daily: 'æ¥æ¥è¯¦ç»æ°æ®', |
| | | monthly: 'ææ¥è¯¦ç»æ°æ®', |
| | | work: 'ä½ä¸æ¥è¡¨è¯¦ç»æ°æ®', |
| | | inout: 'è¿åºåæ¥è¡¨è¯¦ç»æ°æ®' |
| | | } |
| | | return typeMap[searchForm.reportType] || 'æ¥è¡¨è¯¦ç»æ°æ®' |
| | |
| | | try { |
| | | const params = getQueryParams() |
| | | let response |
| | | |
| | | switch (searchForm.reportType) { |
| | | case 'daily': |
| | | response = await getStockDailyReport(params) |
| | | break |
| | | case 'monthly': |
| | | response = await getStockMonthlyReport(params) |
| | | break |
| | | case 'work': |
| | | response = await getWorkReport(params) |
| | | break |
| | | case 'inout': |
| | | response = await getStockInOutReport(params) |
| | | break |
| | | default: |
| | | throw new Error('æªç¥çæ¥è¡¨ç±»å') |
| | | |
| | | if (searchForm.reportType === 'inout') { |
| | | response = await getStockInventoryInAndOutReportList(params) |
| | | } else { |
| | | response = await getStockInventoryReportList(params) |
| | | } |
| | | |
| | | if (response.code === 200) { |
| | | // generateMockData() |
| | | reportData.value.tableData = response.data.tableData |
| | | reportData.value.summary = response.data.summary |
| | | reportData.value.chartData = response.data.chartData |
| | | nextTick(() => { |
| | | initCharts() |
| | | }) |
| | | reportData.value.tableData = response.data.records |
| | | // reportData.value.summary = response.data.summary |
| | | // reportData.value.chartData = response.data.chartData |
| | | // nextTick(() => { |
| | | // initCharts() |
| | | // }) |
| | | |
| | | } |
| | | } catch (error) { |
| | |
| | | ElMessage.warning('è¯·éæ©æ¥æ') |
| | | return false |
| | | } |
| | | } else if (searchForm.reportType === 'work' || searchForm.reportType === 'inout') { |
| | | } else if (searchForm.reportType === 'inout') { |
| | | if (!searchForm.dateRange || searchForm.dateRange.length !== 2) { |
| | | ElMessage.warning('è¯·éæ©æ¥æèå´') |
| | | return false |
| | |
| | | yesterday.toISOString().split('T')[0], |
| | | today.toISOString().split('T')[0] |
| | | ] |
| | | |
| | | fetchStockRecordTypeOptions() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | |
| | | const getPaymenRecordtList = (supplierId) => { |
| | | tableLoadingSon.value = true; |
| | | paymentRecordList(supplierId) |
| | | paymentRecordList({supplierId: supplierId}) |
| | | .then((res) => { |
| | | tableLoadingSon.value = false; |
| | | tableDataSon.value = res.data; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | | <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> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { deptStaffDistribution } from '@/api/viewIndex.js' |
| | | import PanelHeader from '../PanelHeader.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: '50%', |
| | | 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: 18, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 30], |
| | | }, |
| | | unit: { |
| | | color: '#82baff', |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 50, 0, 0], |
| | | }, |
| | | percent: { |
| | | color: '#43e8fc', |
| | | fontSize: 18, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | title: { |
| | | fontSize: 18, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | | }, |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | const pieTooltip = { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c} ({d}%)', |
| | | } |
| | | |
| | | const pieSeries = computed(() => [ |
| | | { |
| | | name: '人ååå¸', |
| | | type: 'pie', |
| | | radius: '70%', |
| | | center: ['20%', '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 getDeptStaffDistribution = () => { |
| | | deptStaffDistribution().then(res => { |
| | | if (res.code === 200) { |
| | | pieDatas.value = res.data.items.map(item => ({ |
| | | name: item.name, |
| | | value: parseInt(item.value), |
| | | rate: item.rate |
| | | })) |
| | | } |
| | | }).catch(err => { |
| | | console.error('è·åé¨é¨äººåå叿°æ®å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | 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: 370px; |
| | | } |
| | | </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 class="equipment-stats"> |
| | | <div class="equipment-header"> |
| | | <img |
| | | src="@/assets/BI/shujutongjiicon@2x.png" |
| | | alt="徿 " |
| | | class="equipment-icon" |
| | | /> |
| | | <span class="equipment-title">设å¤ç»è®¡</span> |
| | | </div> |
| | | <div class="equipment-items"> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{ equipmentNum }}</span> |
| | | <span class="equipment-label">è®¾å¤æ»æ°</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{ equipmentRepair }}</span> |
| | | <span class="equipment-label">å¾
维修设å¤</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{ equipmentMaintain }}</span> |
| | | <span class="equipment-label">å¾
ä¿å
»è®¾å¤</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{ totalMeasuring }}</span> |
| | | <span class="equipment-label">计éå¨å
·æ»æ°</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- äºä»¶åç§° --> |
| | | <div class="event-info"> |
| | | <div class="event-header"> |
| | | <img |
| | | src="@/assets/BI/shijianmingxiicon@2x.png" |
| | | alt="徿 " |
| | | class="event-icon" |
| | | /> |
| | | <span class="event-title">äºä»¶åç§°</span> |
| | | </div> |
| | | <div class="event-content"> |
| | | <ul class="todo-list" v-if="todoList.length > 0" ref="refTodoList"> |
| | | <li v-for="item in todoList" :key="item.id"> |
| | | <div |
| | | style=" |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | width: 100%; |
| | | gap: 20px; |
| | | " |
| | | > |
| | | <div class="todo-division">å¾
åäºç±ï¼{{ item.approveReason }}</div> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;" |
| | | > |
| | | <div class="todo-title">ç³è¯·ç±»åï¼{{ item.approveTypeName }}</div> |
| | | <div class="todo-division">ç³è¯·é¨é¨ï¼{{ item.approveDeptName }}</div> |
| | | <div class="todo-time">{{ item.approveTime }}</div> |
| | | </div> |
| | | |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <div v-else style="text-align: center">ææ æ°æ®</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { homeTodos, summaryStatistics } from '@/api/viewIndex.js' |
| | | import { getLedgerPage } from '@/api/equipmentManagement/ledger.js' |
| | | import { getRepairPage } from '@/api/equipmentManagement/repair.js' |
| | | import { getUpkeepPage } from '@/api/equipmentManagement/upkeep.js' |
| | | import { measuringInstrumentListPage } from '@/api/equipmentManagement/measurementEquipment.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 equipmentNum = ref(0) |
| | | const equipmentRepair = ref(0) |
| | | const equipmentMaintain = ref(0) |
| | | const totalMeasuring = ref(0) |
| | | |
| | | // å¾
åäºé¡¹ |
| | | const todoList = ref([]) |
| | | const refTodoList = ref(null) |
| | | |
| | | 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) |
| | | }) |
| | | } |
| | | |
| | | // è·å设å¤ç¸å
³æ°é |
| | | const getLedgerNum = () => { |
| | | const params = { |
| | | pageNum: -1, |
| | | pageSize: -1, |
| | | } |
| | | getLedgerPage(params).then((res) => { |
| | | equipmentNum.value = res.data.total |
| | | }) |
| | | getRepairPage({ ...params, status: 0 }).then((res) => { |
| | | equipmentRepair.value = res.data.total |
| | | }) |
| | | getUpkeepPage({ ...params, status: 0 }).then((res) => { |
| | | equipmentMaintain.value = res.data.total |
| | | }) |
| | | measuringInstrumentListPage(params).then((res) => { |
| | | totalMeasuring.value = res.data.total |
| | | }) |
| | | } |
| | | |
| | | // åå§åå¾
åäºé¡¹å表æ»å¨åè½ |
| | | const initTodoListScroll = () => { |
| | | const todoListEl = refTodoList.value |
| | | // 强å¶å¯ç¨æ»å¨ï¼ä¸æ£æ¥ä»»ä½æ¡ä»¶ |
| | | if (todoListEl) { |
| | | // å建ä¸ä¸ªå
é项ï¼ç¨äºå®ç°æ ç¼æ»å¨ |
| | | const scrollItems = Array.from(todoListEl.querySelectorAll('li')) |
| | | if (scrollItems.length > 0) { |
| | | // ç¡®ä¿æè¶³å¤ç项ç®ç¨äºæ»å¨ |
| | | // 妿项ç®å¤ªå°ï¼å¤å¤å¶å æ¬¡ä»¥ç¡®ä¿æ»å¨ææ |
| | | if (scrollItems.length < 4) { |
| | | const originalItems = [...scrollItems] |
| | | for (let i = 0; i < 4; i++) { |
| | | originalItems.forEach((item) => { |
| | | const clone = item.cloneNode(true) |
| | | todoListEl.appendChild(clone) |
| | | }) |
| | | } |
| | | // éæ°è·åææé¡¹ç® |
| | | scrollItems.push( |
| | | ...Array.from(todoListEl.querySelectorAll('li')).slice( |
| | | scrollItems.length |
| | | ) |
| | | ) |
| | | } |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 0 |
| | | const containerHeight = todoListEl.clientHeight |
| | | const cloneCount = Math.ceil(containerHeight / itemHeight) + 2 |
| | | |
| | | // å
éåå 个项ç®å¹¶æ·»å å°å表æ«å°¾ï¼å®ç°æ ç¼æ»å¨ |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % scrollItems.length].cloneNode(true) |
| | | todoListEl.appendChild(clone) |
| | | } |
| | | |
| | | let scrollPosition = 0 |
| | | const scrollSpeed = 1.5 // å¢å æ»å¨é度ï¼ä½¿æ»å¨æ´å ææ¾ |
| | | const pauseTime = 3000 // æ»å¨æåæ¶é´ |
| | | let isPaused = false |
| | | let lastTimestamp = 0 |
| | | |
| | | // è¿ç»æ»å¨å¨ç»å½æ° |
| | | function scrollAnimation(timestamp) { |
| | | if (!lastTimestamp) lastTimestamp = timestamp |
| | | const deltaTime = timestamp - lastTimestamp |
| | | lastTimestamp = timestamp |
| | | |
| | | if (!isPaused) { |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) // æ åå为60fpsçé度 |
| | | |
| | | // 彿»å¨è¶
è¿åå§å
容é¿åº¦æ¶ï¼éç½®ä½ç½®å®ç°æ ç¼æ»å¨ |
| | | const maxScroll = Math.max( |
| | | todoListEl.scrollHeight - |
| | | containerHeight - |
| | | cloneCount * itemHeight, |
| | | itemHeight * scrollItems.length |
| | | ) |
| | | if (scrollPosition >= maxScroll) { |
| | | scrollPosition = 0 |
| | | todoListEl.scrollTop = 0 |
| | | } else { |
| | | todoListEl.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | |
| | | todoListEl._animationFrame = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | |
| | | // å¯å¨æ»å¨å¨ç» |
| | | todoListEl._animationFrame = requestAnimationFrame(scrollAnimation) |
| | | |
| | | // 设置æ»å¨-æå-æ»å¨çå¾ªç¯ææ |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | |
| | | // æ¸
ç宿¶å¨ |
| | | todoListEl._pauseTimer = pauseTimer |
| | | } |
| | | } |
| | | } |
| | | |
| | | // å¾
åäºé¡¹ |
| | | const todoInfoS = () => { |
| | | homeTodos().then((res) => { |
| | | todoList.value = res.data |
| | | // å¨è·åå°å¾
åäºé¡¹æ°æ®åï¼åå§åæ»å¨åè½ |
| | | nextTick(() => { |
| | | initTodoListScroll() |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getNum() |
| | | getLedgerNum() |
| | | todoInfoS() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | // æ¸
çå¾
åäºé¡¹å表çå¨ç»å宿¶å¨ |
| | | const todoListEl = refTodoList.value |
| | | if (todoListEl) { |
| | | if (todoListEl._animationFrame) { |
| | | cancelAnimationFrame(todoListEl._animationFrame) |
| | | todoListEl._animationFrame = null |
| | | } |
| | | if (todoListEl._pauseTimer) { |
| | | clearInterval(todoListEl._pauseTimer) |
| | | todoListEl._pauseTimer = null |
| | | } |
| | | } |
| | | }) |
| | | </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; |
| | | } |
| | | |
| | | .equipment-stats { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | height: 240px; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .equipment-items { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .equipment-item { |
| | | text-align: center; |
| | | } |
| | | |
| | | .equipment-value { |
| | | display: block; |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | color: #ffffff; |
| | | width: 120px; |
| | | height: 110px; |
| | | line-height: 110px; |
| | | background-image: url('@/assets/BI/shujutongji@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .equipment-label { |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #fffffe; |
| | | } |
| | | |
| | | .event-info { |
| | | background-image: url('@/assets/BI/shijianmingchengbeijing@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | padding: 20px; |
| | | height: 186px; |
| | | } |
| | | |
| | | .event-header { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .event-icon { |
| | | width: 40px; |
| | | height: 40px; |
| | | } |
| | | |
| | | .event-title { |
| | | font-weight: 500; |
| | | font-size: 18px; |
| | | color: #fffffe; |
| | | line-height: 30px; |
| | | } |
| | | |
| | | .todo-list { |
| | | list-style: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | height: 120px; /* æç¨æ·è¦æ±è°æ´é«åº¦ */ |
| | | overflow: hidden; |
| | | font-size: 15px; |
| | | } |
| | | |
| | | .todo-list li { |
| | | border-radius: 8px; |
| | | margin-bottom: 12px; |
| | | padding: 12px 40px; |
| | | height: 74px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .todo-title { |
| | | font-weight: 400; |
| | | font-size: 16px; |
| | | color: #fffffe; |
| | | position: relative; |
| | | } |
| | | |
| | | |
| | | |
| | | .todo-division { |
| | | font-weight: 400; |
| | | font-size: 16px; |
| | | color: #fffffe; |
| | | position: relative; |
| | | } |
| | | |
| | | .todo-division::before { |
| | | content: ''; |
| | | position: absolute; |
| | | left: -20px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 6px; |
| | | height: 6px; |
| | | background: #498ceb; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | .todo-time { |
| | | font-weight: 400; |
| | | font-size: 16px; |
| | | color: #fffffe; |
| | | background: rgba(24, 93, 190, 0.4); |
| | | border-radius: 5px 5px 5px 5px; |
| | | padding: 5px 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="客æ·è¥æ¶è´¡ç®æ°å¼åæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="filters-row"> |
| | | <el-select |
| | | v-model="customerValue" |
| | | class="customer-select" |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | clearable |
| | | filterable |
| | | @change="handleFilterChange" |
| | | > |
| | | <el-option |
| | | v-for="item in customerOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | |
| | | <DateTypeSwitch v-model="dateType" @change="handleFilterChange" /> |
| | | </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 PanelHeader from '../PanelHeader.vue' |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | | import { customerRevenueAnalysis } from '@/api/viewIndex.js' |
| | | import { listCustomer } from '@/api/basicData/customerFile.js' |
| | | |
| | | const dateType = ref(1) // 1=å¨ 2=æ 3=å£åº¦ |
| | | const customerValue = ref(null) |
| | | const customerOptions = ref([]) |
| | | |
| | | // è¥æ¶åææ°æ® |
| | | const revenueData = ref({ |
| | | items: [] |
| | | }) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '150%', |
| | | } |
| | | |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true, |
| | | } |
| | | |
| | | const barLegend = { |
| | | show: false, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['è¥æ¶'], |
| | | } |
| | | |
| | | const barSeries1 = ref([ |
| | | { |
| | | name: 'è¥æ¶', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series', |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 1, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | // linear-gradient(360deg, rgba(0,164,237,0) 0%, #4EE4FF 100%) |
| | | { offset: 0, color: 'rgba(0,164,237,0)' }, |
| | | { offset: 1, color: '#4EE4FF' }, |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow', |
| | | }, |
| | | formatter: function (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 |
| | | |
| | | const params = { |
| | | customerId: customerValue.value, |
| | | type: dateType.value |
| | | } |
| | | |
| | | customerRevenueAnalysis(params) |
| | | .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) |
| | | }) |
| | | revenueData.value = res.data |
| | | }) |
| | | .catch((error) => { |
| | | console.error('è·å客æ·è¥æ¶åæå¤±è´¥:', error) |
| | | }) |
| | | } |
| | | |
| | | const fetchCustomerOptions = async () => { |
| | | try { |
| | | const params = { pageNum: 1, pageSize: 200 } |
| | | const res = await listCustomer(params) |
| | | 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) { |
| | | // æ¥å£å¼å¸¸æ¶ç»ä¸ç»æ¨¡æå®¢æ·ï¼ä¿è¯UIå¯ç¨ |
| | | customerOptions.value = [ |
| | | { label: 'åä¸ç²¾å¯', value: 'åä¸ç²¾å¯' }, |
| | | { label: 'æè¾°çµå', value: 'æè¾°çµå' }, |
| | | { label: 'å¯èªç§æ', value: 'å¯èªç§æ' }, |
| | | { label: 'éè¯å¶é ', value: 'éè¯å¶é ' }, |
| | | { label: 'è¿æ¯ææ', value: 'è¿æ¯ææ' }, |
| | | ] |
| | | } |
| | | } |
| | | |
| | | const handleFilterChange = () => { |
| | | getCustomerRevenueAnalysis() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchCustomerOptions() |
| | | }) |
| | | </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; |
| | | } |
| | | |
| | | .customer-select { |
| | | width: 180px; |
| | | } |
| | | |
| | | /* 䏿æ¡é£æ ¼ï¼ä¸ DateTypeSwitch ä¿æä¸è´ï¼æ·±è²åéæãæµ
è²æåãç»è¾¹æ¡ï¼ */ |
| | | .customer-select :deep(.el-input__wrapper), |
| | | .customer-select :deep(.el-select__wrapper) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | border: 1px solid rgba(255, 255, 255, 0.2); |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .customer-select :deep(.el-input__inner) { |
| | | color: rgba(184, 200, 224, 0.9); |
| | | } |
| | | |
| | | .customer-select :deep(.el-input__inner::placeholder) { |
| | | color: rgba(184, 200, 224, 0.6); |
| | | } |
| | | |
| | | .customer-select :deep(.el-select__caret), |
| | | .customer-select :deep(.el-icon) { |
| | | color: rgba(184, 200, 224, 0.8); |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 478px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="产å大类" /> |
| | | <div class="panel-item-customers"> |
| | | <div style="height: 70%"> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :legend="landLegend" |
| | | :series="landSeries" |
| | | :tooltip="landTooltip" |
| | | :color="landColors" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 100%" |
| | | class="land-chart" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import { productCategoryDistribution } from '@/api/viewIndex.js' |
| | | |
| | | // æ°æ®åè¡¨ï¼æ¥èªæ¥å£ï¼ |
| | | const dataList = ref([]) |
| | | |
| | | // é¢è²å表 |
| | | const landColors = ['#26FFCB', '#24CBFF', '#35FBF4', '#2651FF', '#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF'] |
| | | |
| | | // label 坿æ¬ï¼ä¸ºæ¯ä¸ªé¢è²çæä¸ä¸ªå°åç¹æ ·å¼ï¼ç¡®ä¿å¨ label ä¸å¯è§ï¼ |
| | | const dotRich = landColors.reduce((acc, color, idx) => { |
| | | acc[`dot${idx}`] = { |
| | | width: 8, |
| | | height: 8, |
| | | borderRadius: 8, |
| | | backgroundColor: color, |
| | | align: 'center', |
| | | } |
| | | return acc |
| | | }, {}) |
| | | |
| | | // å¾ä¾é
ç½®ï¼å³ä¾§ç«æï¼ |
| | | const landLegend = { |
| | | show: false, |
| | | icon: 'circle', |
| | | data: [], |
| | | right: '8%', |
| | | top: '40%', |
| | | orient: 'vertical', |
| | | itemGap: 14, |
| | | itemWidth: 6, |
| | | itemHeight: 6, |
| | | textStyle: { |
| | | fontSize: 12, |
| | | rich: { |
| | | unit: { |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | text: { |
| | | width: 60, |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | }, |
| | | }, |
| | | }, |
| | | formatter: function (name) { |
| | | const list = dataList.value || [] |
| | | const item = list.find((d) => d.name === name) |
| | | if (!item) return name |
| | | const val = Number(item.value || 0) |
| | | const totalValue = list.reduce((sum, it) => sum + Number(it.value || 0), 0) |
| | | const percent = totalValue ? ((val / totalValue) * 100).toFixed(2) : '0.00' |
| | | return `{text|${name}}${val}{unit| å
¬é¡·}${percent}{unit|%}` |
| | | }, |
| | | } |
| | | |
| | | // æç¤ºæ¡ |
| | | const landTooltip = { |
| | | triggerOn: 'click', |
| | | alwaysShowContent: true, |
| | | position: function (pt) { |
| | | return [pt[0], 130] |
| | | }, |
| | | } |
| | | |
| | | // åå±ç¯å½¢é¥¼å¾ |
| | | const landSeries = ref([ |
| | | { |
| | | name: 'å¤å', |
| | | type: 'pie', |
| | | radius: ['35%', '55%'], |
| | | center: ['50%', '50%'], |
| | | label: { |
| | | show: true, |
| | | position: 'outside', |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | lineHeight: 18, |
| | | rich: { |
| | | ...dotRich, |
| | | parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20 }, |
| | | 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 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') |
| | | }, |
| | | }, |
| | | labelLine: { |
| | | show: true, |
| | | length: 20, |
| | | length2: 20, |
| | | lineStyle: { |
| | | color: '#B8C8E0', |
| | | }, |
| | | }, |
| | | itemStyle: { |
| | | color: function (params) { |
| | | return landColors[params.dataIndex % landColors.length] |
| | | }, |
| | | }, |
| | | data: dataList.value, |
| | | }, |
| | | { |
| | | // å
å |
| | | type: 'pie', |
| | | radius: ['35%', '40%'], |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { |
| | | show: false, |
| | | }, |
| | | labelLine: { |
| | | show: false, |
| | | }, |
| | | itemStyle: { |
| | | color: 'rgba(0, 127, 255, 0.25)', |
| | | }, |
| | | data: [1], |
| | | }, |
| | | ]) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '150%', |
| | | } |
| | | |
| | | const loadData = async () => { |
| | | 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 : [], |
| | | })) |
| | | landLegend.data = dataList.value.map((d) => d.name) |
| | | landSeries.value[0].data = dataList.value |
| | | } catch (e) { |
| | | console.error('è·å产å大类åå¸å¤±è´¥:', e) |
| | | dataList.value = [] |
| | | landLegend.data = [] |
| | | landSeries.value[0].data = [] |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 420px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="客æ·éé¢è´¡ç®æå" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="switch-container"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="{ show: false }" |
| | | :series="series" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 360px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | | import { customerContributionRanking } from '@/api/viewIndex.js' |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const dateType = ref(1) // 1=å¨ 2=æ 3=å£åº¦ |
| | | |
| | | // 飿ºå¾æ SVG pathï¼ä¸ right-top ä¸è´ï¼ |
| | | const aircraft = |
| | | 'path://M107.000,71.000 C104.936,71.000 102.665,70.806 100.273,70.467 C94.592,76.922 86.275,81.000 77.000,81.000 C70.794,81.000 65.020,79.170 60.172,76.029 C66.952,74.165 72.647,69.714 76.173,63.817 C69.821,61.362 64.063,58.593 60.000,56.039 L60.000,52.813 C70.456,53.950 80.723,55.000 83.000,55.000 C88.972,55.000 93.000,53.723 93.000,50.000 C93.000,47.071 89.222,45.000 83.000,45.000 C80.723,45.000 70.456,46.050 60.000,47.187 L60.000,43.989 C64.057,41.431 69.807,38.644 76.168,36.173 C72.641,30.281 66.948,25.834 60.172,23.971 C65.020,20.830 70.794,19.000 77.000,19.000 C86.270,19.000 94.584,23.074 100.265,29.524 C102.647,29.191 104.918,29.000 107.000,29.000 C129.644,29.000 148.000,50.000 148.000,50.000 C148.000,50.000 129.644,71.000 107.000,71.000 ZM113.000,38.000 C106.373,38.000 101.000,43.373 101.000,50.000 C101.000,56.627 106.373,62.000 113.000,62.000 C119.627,62.000 125.000,56.627 125.000,50.000 C125.000,43.373 119.627,38.000 113.000,38.000 ZM113.000,56.000 C109.686,56.000 107.000,53.314 107.000,50.000 C107.000,46.686 109.686,44.000 113.000,44.000 C116.314,44.000 119.000,46.686 119.000,50.000 C119.000,53.314 116.314,56.000 113.000,56.000 ZM110.500,19.000 C109.567,19.000 108.763,18.483 108.334,17.726 C100.231,9.857 89.187,5.000 77.000,5.000 C64.813,5.000 53.769,9.857 45.666,17.726 C45.237,18.483 44.433,19.000 43.500,19.000 C42.119,19.000 41.000,17.881 41.000,16.500 C41.000,15.847 41.256,15.259 41.665,14.813 L41.575,14.718 C50.629,5.628 63.156,-0.000 77.000,-0.000 C90.844,-0.000 103.371,5.628 112.425,14.718 L112.335,14.813 C112.744,15.259 113.000,15.847 113.000,16.500 C113.000,17.881 111.881,19.000 110.500,19.000 ZM53.000,49.484 C61.406,48.626 77.810,47.000 81.345,47.000 C87.353,47.000 91.000,48.243 91.000,50.000 C91.000,52.234 87.111,53.000 81.345,53.000 C77.810,53.000 61.406,51.374 53.000,50.516 L53.000,49.484 ZM53.000,47.000 L9.000,50.000 L53.000,53.000 L53.000,56.000 L-0.000,50.000 L53.000,44.000 L53.000,47.000 ZM43.500,81.000 C44.433,81.000 45.237,81.517 45.666,82.274 C53.769,90.143 64.813,95.000 77.000,95.000 C89.187,95.000 100.231,90.143 108.334,82.274 C108.763,81.517 109.567,81.000 110.500,81.000 C111.881,81.000 113.000,82.119 113.000,83.500 C113.000,84.153 112.744,84.741 112.335,85.187 L112.425,85.282 C103.371,94.372 90.844,100.000 77.000,100.000 C63.156,100.000 50.629,94.372 41.575,85.282 L41.665,85.187 C41.256,84.741 41.000,84.153 41.000,83.500 C41.000,82.119 42.119,81.000 43.500,81.000 Z' |
| | | |
| | | // é¢è²é
ç½®ï¼ä¸ right-top ä¸è´ï¼ |
| | | const color = { |
| | | 0: '#ff5676', |
| | | 1: '#ffd83e', |
| | | 2: '#fbff94', |
| | | 3: '#7daeff', |
| | | } |
| | | |
| | | // åå§æ°æ®ï¼ç»ä¸æ { NAME, NUM }ï¼ |
| | | const dataArr = ref([]) |
| | | |
| | | const dataArray = computed(() => { |
| | | const sortedAsc = [...dataArr.value].sort((a, b) => a.NUM - b.NUM) |
| | | return sortedAsc.length > 5 ? sortedAsc.slice(-5) : sortedAsc |
| | | }) |
| | | |
| | | const total = computed(() => dataArray.value.reduce((sum, v) => sum + Number(v.NUM || 0), 0)) |
| | | |
| | | const xdataName = computed(() => dataArray.value.map((v) => v.NAME)) |
| | | |
| | | const dataNum = computed(() => { |
| | | return dataArray.value.map((v, i) => { |
| | | const index = dataArray.value.length - i - 1 |
| | | const isTop3 = index < 3 |
| | | |
| | | return { |
| | | value: Number(v.NUM), |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: isTop3 ? '#ffdae1' : '#ecf3ff' }, |
| | | { offset: 0.07, color: isTop3 ? color[index] : color[3] }, |
| | | { |
| | | offset: 1, |
| | | color: isTop3 ? 'rgba(255, 86, 118, .1)' : 'rgba(125,174,255, .1)', |
| | | }, |
| | | ], |
| | | global: false, |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | }, |
| | | symbol: isTop3 ? aircraft : 'none', |
| | | symbolPosition: 'end', |
| | | symbolSize: [30, 25], |
| | | symbolOffset: [35, 0], |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | const bgData = computed(() => { |
| | | const maxValue = Math.max(0, ...dataNum.value.map((v) => v.value)) |
| | | return dataNum.value.map(() => maxValue + 200) |
| | | }) |
| | | |
| | | const tooltip = computed(() => ({ |
| | | trigger: 'axis', |
| | | textStyle: { fontSize: '100%' }, |
| | | formatter: function (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 grid = computed(() => ({ top: 0, left: '20%', right: '10%', bottom: 0 })) |
| | | |
| | | const xAxis = computed(() => [ |
| | | { |
| | | splitLine: { show: false }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { show: false }, |
| | | axisTick: { show: false }, |
| | | }, |
| | | ]) |
| | | |
| | | const yAxis = computed(() => [ |
| | | { |
| | | type: 'category', |
| | | inverse: false, |
| | | data: xdataName.value, |
| | | axisLabel: { |
| | | formatter: (value) => { |
| | | if (!value) return '' |
| | | const maxLen = 6 // æ¯è¡æå¤å符æ°ï¼å¯æéè°æ´ |
| | | if (value.length <= maxLen) return `{a|${value}}` |
| | | |
| | | const lines = [] |
| | | for (let i = 0; i < value.length; i += maxLen) { |
| | | lines.push(value.slice(i, i + maxLen)) |
| | | } |
| | | return lines.map((line) => `{a|${line}}`).join('\n') |
| | | }, |
| | | rich: { |
| | | a: { |
| | | width: 120, |
| | | fontSize: 14, |
| | | color: '#fff', |
| | | padding: [5, 4, 5, 0], |
| | | align: 'right', |
| | | }, |
| | | }, |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | { |
| | | type: 'category', |
| | | data: dataNum.value.map((item) => item.value), |
| | | axisLabel: { |
| | | formatter: (params, index) => { |
| | | const value = typeof params === 'object' ? params.value : params |
| | | const percent = total.value ? ((value / total.value) * 100).toFixed(0) : 0 |
| | | const rank = dataArray.value.length - index |
| | | const isTop3 = rank < 4 |
| | | |
| | | return `{a${isTop3 ? rank : ''}|${percent} }{b${isTop3 ? rank : ''}|%}` |
| | | }, |
| | | rich: { |
| | | a: { fontSize: 18, color: '#98bfff', verticalAlign: 'bottom' }, |
| | | a1: { fontSize: 18, color: '#ff7f97', verticalAlign: 'bottom' }, |
| | | a2: { fontSize: 18, color: '#ffce64', verticalAlign: 'bottom' }, |
| | | a3: { fontSize: 18, color: '#e8ed66', verticalAlign: 'bottom' }, |
| | | b: { fontSize: 12, color: '#98bfff', verticalAlign: 'bottom' }, |
| | | b1: { fontSize: 12, color: '#ff7f97', verticalAlign: 'bottom' }, |
| | | b2: { fontSize: 12, color: '#ffce64', verticalAlign: 'bottom' }, |
| | | b3: { fontSize: 12, color: '#e8ed66', verticalAlign: 'bottom' }, |
| | | }, |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | ]) |
| | | |
| | | const series = computed(() => [ |
| | | { |
| | | name: 'éé¢', |
| | | z: 6, |
| | | type: 'pictorialBar', |
| | | data: dataNum.value, |
| | | }, |
| | | { |
| | | name: 'èæ¯', |
| | | z: 6, |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: 'rgba(255,255,255,.1)', |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | }, |
| | | data: bgData.value, |
| | | }, |
| | | { |
| | | name: 'é颿¸å', |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | barGap: '-100%', |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(255, 218, 220)' }, |
| | | { offset: 0.07, color: 'rgba(255, 86, 118)' }, |
| | | { offset: 1, color: 'rgba(255, 86, 118, 0)' }, |
| | | ], |
| | | global: false, |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | }, |
| | | data: dataNum.value, |
| | | }, |
| | | ]) |
| | | |
| | | const normalizeItem = (item) => { |
| | | const name = |
| | | item?.NAME ?? |
| | | item?.name ?? |
| | | item?.customerName ?? |
| | | item?.customer ?? |
| | | item?.label ?? |
| | | '-' |
| | | |
| | | const num = |
| | | item?.NUM ?? |
| | | item?.num ?? |
| | | item?.value ?? |
| | | item?.amount ?? |
| | | item?.money ?? |
| | | 0 |
| | | |
| | | return { NAME: String(name), NUM: Number(num) || 0 } |
| | | } |
| | | |
| | | const getMockListByType = (type) => { |
| | | // 模æåæ°æ®ï¼éé¢è´¡ç®æåï¼ |
| | | // type: 1=å¨ 2=æ 3=å£åº¦ |
| | | if (type === 2) { |
| | | return [ |
| | | { NAME: 'åä¸ç²¾å¯', NUM: 5120000 }, |
| | | { NAME: 'æè¾°çµå', NUM: 3860000 }, |
| | | { NAME: 'å¯èªç§æ', NUM: 2720000 }, |
| | | { NAME: 'éè¯å¶é ', NUM: 2160000 }, |
| | | { NAME: 'è¿æ¯ææ', NUM: 1430000 }, |
| | | { NAME: '德润贸æ', NUM: 910000 }, |
| | | { NAME: 'å®è¾¾é
å¥', NUM: 680000 }, |
| | | ] |
| | | } |
| | | if (type === 3) { |
| | | return [ |
| | | { NAME: 'åä¸ç²¾å¯', NUM: 16800000 }, |
| | | { NAME: 'æè¾°çµå', NUM: 12960000 }, |
| | | { NAME: 'å¯èªç§æ', NUM: 9720000 }, |
| | | { NAME: 'éè¯å¶é ', NUM: 7560000 }, |
| | | { NAME: 'è¿æ¯ææ', NUM: 5430000 }, |
| | | { NAME: '德润贸æ', NUM: 3910000 }, |
| | | { NAME: 'å®è¾¾é
å¥', NUM: 2680000 }, |
| | | ] |
| | | } |
| | | return [ |
| | | { NAME: 'åä¸ç²¾å¯', NUM: 1280000 }, |
| | | { NAME: 'æè¾°çµå', NUM: 860000 }, |
| | | { NAME: 'å¯èªç§æ', NUM: 720000 }, |
| | | { NAME: 'éè¯å¶é ', NUM: 560000 }, |
| | | { NAME: 'è¿æ¯ææ', NUM: 430000 }, |
| | | { NAME: '德润贸æ', NUM: 310000 }, |
| | | { NAME: 'å®è¾¾é
å¥', NUM: 180000 }, |
| | | ] |
| | | } |
| | | |
| | | const setMockData = (type) => { |
| | | dataArr.value = getMockListByType(type).map(normalizeItem) |
| | | } |
| | | |
| | | const fetchCustomerRanking = () => { |
| | | customerContributionRanking({ type: dateType.value }) |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | dataArr.value = res.data.map(item => ({ |
| | | NAME: item.customerName, |
| | | NUM: item.totalAmount |
| | | })) |
| | | } else { |
| | | setMockData(dateType.value) |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | console.error('è·å客æ·éé¢è´¡ç®æå失败:', error) |
| | | setMockData(dateType.value) |
| | | }) |
| | | } |
| | | |
| | | const handleDateTypeChange = () => { |
| | | fetchCustomerRanking() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchCustomerRanking() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | |
| | | .switch-container { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 16px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="ä¾åºåéè´æå" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="switch-container"> |
| | | <DateTypeSwitch v-model="radio1" @change="handleDateTypeChange" /> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :series="series" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 360px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | | import { supplierPurchaseRanking } from '@/api/viewIndex.js' |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const radio1 = ref(1) |
| | | |
| | | // 飿ºå¾æ SVG path |
| | | const aircraft = |
| | | 'path://M107.000,71.000 C104.936,71.000 102.665,70.806 100.273,70.467 C94.592,76.922 86.275,81.000 77.000,81.000 C70.794,81.000 65.020,79.170 60.172,76.029 C66.952,74.165 72.647,69.714 76.173,63.817 C69.821,61.362 64.063,58.593 60.000,56.039 L60.000,52.813 C70.456,53.950 80.723,55.000 83.000,55.000 C88.972,55.000 93.000,53.723 93.000,50.000 C93.000,47.071 89.222,45.000 83.000,45.000 C80.723,45.000 70.456,46.050 60.000,47.187 L60.000,43.989 C64.057,41.431 69.807,38.644 76.168,36.173 C72.641,30.281 66.948,25.834 60.172,23.971 C65.020,20.830 70.794,19.000 77.000,19.000 C86.270,19.000 94.584,23.074 100.265,29.524 C102.647,29.191 104.918,29.000 107.000,29.000 C129.644,29.000 148.000,50.000 148.000,50.000 C148.000,50.000 129.644,71.000 107.000,71.000 ZM113.000,38.000 C106.373,38.000 101.000,43.373 101.000,50.000 C101.000,56.627 106.373,62.000 113.000,62.000 C119.627,62.000 125.000,56.627 125.000,50.000 C125.000,43.373 119.627,38.000 113.000,38.000 ZM113.000,56.000 C109.686,56.000 107.000,53.314 107.000,50.000 C107.000,46.686 109.686,44.000 113.000,44.000 C116.314,44.000 119.000,46.686 119.000,50.000 C119.000,53.314 116.314,56.000 113.000,56.000 ZM110.500,19.000 C109.567,19.000 108.763,18.483 108.334,17.726 C100.231,9.857 89.187,5.000 77.000,5.000 C64.813,5.000 53.769,9.857 45.666,17.726 C45.237,18.483 44.433,19.000 43.500,19.000 C42.119,19.000 41.000,17.881 41.000,16.500 C41.000,15.847 41.256,15.259 41.665,14.813 L41.575,14.718 C50.629,5.628 63.156,-0.000 77.000,-0.000 C90.844,-0.000 103.371,5.628 112.425,14.718 L112.335,14.813 C112.744,15.259 113.000,15.847 113.000,16.500 C113.000,17.881 111.881,19.000 110.500,19.000 ZM53.000,49.484 C61.406,48.626 77.810,47.000 81.345,47.000 C87.353,47.000 91.000,48.243 91.000,50.000 C91.000,52.234 87.111,53.000 81.345,53.000 C77.810,53.000 61.406,51.374 53.000,50.516 L53.000,49.484 ZM53.000,47.000 L9.000,50.000 L53.000,53.000 L53.000,56.000 L-0.000,50.000 L53.000,44.000 L53.000,47.000 ZM43.500,81.000 C44.433,81.000 45.237,81.517 45.666,82.274 C53.769,90.143 64.813,95.000 77.000,95.000 C89.187,95.000 100.231,90.143 108.334,82.274 C108.763,81.517 109.567,81.000 110.500,81.000 C111.881,81.000 113.000,82.119 113.000,83.500 C113.000,84.153 112.744,84.741 112.335,85.187 L112.425,85.282 C103.371,94.372 90.844,100.000 77.000,100.000 C63.156,100.000 50.629,94.372 41.575,85.282 L41.665,85.187 C41.256,84.741 41.000,84.153 41.000,83.500 C41.000,82.119 42.119,81.000 43.500,81.000 Z' |
| | | |
| | | // é¢è²é
ç½® |
| | | const color = { |
| | | 0: '#ff5676', |
| | | 1: '#ffd83e', |
| | | 2: '#fbff94', |
| | | 3: '#7daeff', |
| | | } |
| | | |
| | | // åå§æ°æ® |
| | | const dataArr = ref([]) |
| | | |
| | | // æåºåçæ°æ® |
| | | const dataArray = computed(() => { |
| | | return [...dataArr.value].sort((a, b) => a.NUM - b.NUM) |
| | | }) |
| | | |
| | | // è®¡ç®æ»æ° |
| | | const total = computed(() => { |
| | | return dataArray.value.reduce((sum, v) => sum + Number(v.NUM), 0) |
| | | }) |
| | | |
| | | // xè½´æ°æ®ï¼åç§°ï¼ |
| | | const xdataName = computed(() => { |
| | | return dataArray.value.map((v) => v.NAME) |
| | | }) |
| | | |
| | | // yè½´æ°æ®ï¼æ°å¼ï¼å¸¦æ ·å¼ï¼ |
| | | const dataNum = computed(() => { |
| | | return dataArray.value.map((v, i) => { |
| | | const index = dataArray.value.length - i - 1 |
| | | const isTop3 = index < 3 |
| | | |
| | | return { |
| | | value: Number(v.NUM), |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { |
| | | offset: 0, |
| | | color: isTop3 ? '#ffdae1' : '#ecf3ff', |
| | | }, |
| | | { |
| | | offset: 0.07, |
| | | color: isTop3 ? color[index] : color[3], |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: isTop3 |
| | | ? 'rgba(255, 86, 118, .1)' |
| | | : 'rgba(125,174,255, .1)', |
| | | }, |
| | | ], |
| | | global: false, |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | }, |
| | | symbol: isTop3 ? aircraft : 'none', |
| | | symbolPosition: 'end', |
| | | symbolSize: [30, 25], |
| | | symbolOffset: [35, 0], |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | // èæ¯æ°æ® |
| | | const bgData = computed(() => { |
| | | const maxValue = Math.max(...dataNum.value.map((v) => v.value)) |
| | | return dataNum.value.map(() => maxValue + 200) |
| | | }) |
| | | |
| | | // tooltip |
| | | const tooltip = computed(() => { |
| | | return { |
| | | trigger: 'axis', |
| | | textStyle: { fontSize: '100%' }, |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | result += `<div style="">${params[0].marker}${params[0].value}</div>` |
| | | return result |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | // grid |
| | | const grid = computed(() => { |
| | | return { top: 0, left: '20%', right: '10%', bottom: 0 } |
| | | }) |
| | | |
| | | // xAxis |
| | | const xAxis = computed(() => { |
| | | return [ |
| | | { |
| | | splitLine: { show: false }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { show: false }, |
| | | axisTick: { show: false }, |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | // yAxis |
| | | const yAxis = computed(() => { |
| | | return [ |
| | | { |
| | | type: 'category', |
| | | inverse: false, |
| | | data: xdataName.value, |
| | | axisLabel: { |
| | | formatter: (value) => { |
| | | if (!value) return '' |
| | | const maxLen = 6 // æ¯è¡æå¤å符æ°ï¼å¯æéè°æ´ |
| | | if (value.length <= maxLen) return `{a|${value}}` |
| | | |
| | | const lines = [] |
| | | for (let i = 0; i < value.length; i += maxLen) { |
| | | lines.push(value.slice(i, i + maxLen)) |
| | | } |
| | | // å¤è¡ææ¬ï¼æ¯è¡é½å¥åä¸ä¸ª rich æ ·å¼ |
| | | return lines.map((line) => `{a|${line}}`).join('\n') |
| | | }, |
| | | rich: { |
| | | a: { |
| | | width: 120, |
| | | fontSize: 14, |
| | | color: '#fff', |
| | | padding: [5, 4, 5, 0], |
| | | align: 'right', |
| | | }, |
| | | }, |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | { |
| | | type: 'category', |
| | | data: dataNum.value.map((item) => item.value), |
| | | axisLabel: { |
| | | formatter: (params, index) => { |
| | | const value = typeof params === 'object' ? params.value : params |
| | | const percent = ((value / total.value) * 100).toFixed(0) |
| | | const rank = dataArray.value.length - index |
| | | const isTop3 = rank < 4 |
| | | |
| | | return `{a${isTop3 ? rank : ''}|${percent} }{b${isTop3 ? rank : ''}|%}` |
| | | }, |
| | | rich: { |
| | | a: { |
| | | fontSize: 18, |
| | | color: '#98bfff', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | a1: { |
| | | fontSize: 18, |
| | | color: '#ff7f97', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | a2: { |
| | | fontSize: 18, |
| | | color: '#ffce64', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | a3: { |
| | | fontSize: 18, |
| | | color: '#e8ed66', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | b: { |
| | | fontSize: 12, |
| | | color: '#98bfff', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | b1: { |
| | | fontSize: 12, |
| | | color: '#ff7f97', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | b2: { |
| | | fontSize: 12, |
| | | color: '#ffce64', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | b3: { |
| | | fontSize: 12, |
| | | color: '#e8ed66', |
| | | verticalAlign: 'bottom', |
| | | }, |
| | | }, |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | // series |
| | | const series = computed(() => { |
| | | return [ |
| | | { |
| | | z: 6, |
| | | type: 'pictorialBar', |
| | | data: dataNum.value, |
| | | }, |
| | | { |
| | | z: 6, |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: 'rgba(255,255,255,.1)', |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | }, |
| | | data: bgData.value, |
| | | }, |
| | | { |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | barGap: '-100%', |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(255, 218, 220)', |
| | | }, |
| | | { |
| | | offset: 0.07, |
| | | color: 'rgba(255, 86, 118)', |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(255, 86, 118, 0)', |
| | | }, |
| | | ], |
| | | global: false, |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0], |
| | | }, |
| | | data: dataNum.value, |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | // ä¾åºåéè´æå |
| | | const fetchSupplierRanking = () => { |
| | | supplierPurchaseRanking({ type: radio1.value }) |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | dataArr.value = res.data.map(item => ({ |
| | | NAME: item.supplierName, |
| | | NUM: item.totalAmount |
| | | })) |
| | | } else { |
| | | // å¦ææ²¡ææ°æ®ï¼ä½¿ç¨æ¨¡ææ°æ® |
| | | dataArr.value = [ |
| | | { NAME: 'ä¾åºåA', NUM: 102 }, |
| | | { NAME: 'ä¾åºåB', NUM: 122 }, |
| | | { NAME: 'ä¾åºåC', NUM: 282 }, |
| | | { NAME: 'ä¾åºåD', NUM: 453 }, |
| | | { NAME: 'ä¾åºåE', NUM: 753 }, |
| | | ] |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | console.error('è·åä¾åºåéè´æå失败:', error) |
| | | // ä½¿ç¨æ¨¡ææ°æ® |
| | | dataArr.value = [ |
| | | { NAME: 'ä¾åºåA', NUM: 102 }, |
| | | { NAME: 'ä¾åºåB', NUM: 122 }, |
| | | { NAME: 'ä¾åºåC', NUM: 282 }, |
| | | { NAME: 'ä¾åºåD', NUM: 453 }, |
| | | { NAME: 'ä¾åºåE', NUM: 753 }, |
| | | ] |
| | | }) |
| | | } |
| | | |
| | | // å¤çæ¥æç±»å忢 |
| | | const handleDateTypeChange = (value) => { |
| | | fetchSupplierRanking() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchSupplierRanking() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | |
| | | .switch-container { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .section-title { |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #d9ecff; |
| | | } |
| | | </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="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">{{ userStore.currentFactoryName }}</div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | | <div class="dashboard-content"> |
| | | <!-- 左侧åºå --> |
| | | <div class="left-panel"> |
| | | <!-- 客æ·ä¿¡æ¯ç»è®¡åæ --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">å¨å¶åç»è®¡åæ</span> |
| | | </div> |
| | | <div class="panel-item-customers"> |
| | | <div class="quality-cards"> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card one"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>æ»å¨å¶æ°é</div> |
| | | <div>{{workInProcessStatistics.totalQuantity}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card two"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>å¹³åå¨è½¬å¤©æ°</div> |
| | | <div>{{workInProcessStatistics.avgTurnoverDays}}天</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card three"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>å¨è½¬æç</div> |
| | | <div>{{workInProcessStatistics.turnoverEfficiency}}%</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- å·¥åºå¨å¶åæ°éæ±ç¶å¾ --> |
| | | <div style="height: 70%"> |
| | | <Echarts ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="workInProcessBarLegend" |
| | | :series="workInProcessBarSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="workInProcessXAxis" |
| | | :yAxis="workInProcessYAxis" |
| | | :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}" |
| | | style="height: 100%"></Echarts> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è´¨éç»è®¡ --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">è¿4æè´¨éç»è®¡</span> |
| | | </div> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | | <div class="quality-cards"> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card one"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>åæææ£æ°</div> |
| | | <div>{{qualityStatisticsObject.supplierNum}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card two"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>è¿ç¨æ£æ°</div> |
| | | <div>{{qualityStatisticsObject.processNum}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card three"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>åºåæ£æ°</div> |
| | | <div>{{qualityStatisticsObject.factoryNum}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | </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"></Echarts> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ä¸é´åºå --> |
| | | <div class="center-panel"> |
| | | <!-- é¡¶é¨ç»è®¡å¡ç --> |
| | | <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> |
| | | </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> |
| | | </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> |
| | | </div> |
| | | </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> |
| | | <div class="equipment-items"> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{equipmentNum}}</span> |
| | | <span class="equipment-label">è®¾å¤æ»æ°</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{equipmentRepair}}</span> |
| | | <span class="equipment-label">å¾
维修设å¤</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{equipmentMaintain}}</span> |
| | | <span class="equipment-label">å¾
ä¿å
»è®¾å¤</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{totalMeasuring}}</span> |
| | | <span class="equipment-label">计éå¨å
·æ»æ°</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- äºä»¶åç§° --> |
| | | <div class="event-info"> |
| | | <div class="event-header"> |
| | | <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="徿 " class="event-icon" /> |
| | | <span class="event-title">äºä»¶åç§°</span> |
| | | </div> |
| | | <div class="event-content"> |
| | | <ul class="todo-list" v-if="todoList.length > 0" ref="refTodoList"> |
| | | <li v-for="item in todoList" :key="item.id"> |
| | | <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px"> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;"> |
| | | <div class="todo-title">å¾
åç¼å·ï¼{{item.approveId}}</div> |
| | | <div class="todo-division">é¨é¨ï¼{{item.approveDeptName}}</div> |
| | | <div class="todo-time">{{item.approveTime}}</div> |
| | | </div> |
| | | <div class="todo-division">å¾
åäºç±ï¼{{item.approveReason}}</div> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <div v-else style="text-align: center"> |
| | | ææ æ°æ® |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="financial-header"> |
| | | <span class="financial-title">åç产订åç宿è¿åº¦ç»è®¡</span> |
| | | </div> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | | <div class="order-statistics-cards" style="margin-bottom: 0px;"> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card four"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>æ»è®¢åæ°</div> |
| | | <div>{{orderStatisticsObject.totalOrderCount}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card five"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>æªå®æè®¢åæ°</div> |
| | | <div>{{orderStatisticsObject.uncompletedOrderCount}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card six"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>é¨åå®æè®¢åæ°</div> |
| | | <div>{{orderStatisticsObject.partialCompletedOrderCount}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card seven"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>å·²å®æè®¢åæ°</div> |
| | | <div>{{orderStatisticsObject.completedOrderCount}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="progress-table-container" ref="progressTableRef" style="margin-top: 0px;" @scroll="handleTableScroll"> |
| | | <table class="progress-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>ç产订åå·</th> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼</th> |
| | | <th>éæ±æ°é</th> |
| | | <th>宿æ°é</th> |
| | | <th>宿è¿åº¦</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr |
| | | v-for="(item, index) in progressTableData" |
| | | :key="index" |
| | | :ref="el => setRowRef(el, index)" |
| | | :class="{ 'row-under-header': isRowUnderHeader(index) }" |
| | | > |
| | | <td>{{ item.npsNo || '-' }}</td> |
| | | <td>{{ item.productCategory || '-' }}</td> |
| | | <td>{{ item.specificationModel || '-' }}</td> |
| | | <td>{{ item.quantity || 0 }}</td> |
| | | <td>{{ item.completeQuantity || 0 }}</td> |
| | | <td> |
| | | <el-progress |
| | | :percentage="calculateProgress(item)" |
| | | :color="progressColor(calculateProgress(item))" |
| | | :status="calculateProgress(item) >= 100 ? 'success' : ''" |
| | | :stroke-width="8" |
| | | /> |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å³ä¾§åºå --> |
| | | <div class="right-panel"> |
| | | <!-- åºæ¶åºä»ç»è®¡ --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">åºæ¶åºä»ç»è®¡</span> |
| | | </div> |
| | | <div class="panel-item-customers"> |
| | | <div style="display: flex;justify-content: space-between;margin-bottom: 20px;"> |
| | | <div class="section-title">åºæ¶åºä»ç»è®¡</div> |
| | | <!-- <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable" class="custom-radio-group">--> |
| | | <!-- <el-radio-button label="æå¨" :value="1" />--> |
| | | <!-- <el-radio-button label="ææ" :value="2" />--> |
| | | <!-- <el-radio-button label="æå£åº¦" :value="3" />--> |
| | | <!-- </el-radio-group>--> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :color="barColors2" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="barLegend2" |
| | | :series="barSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}" |
| | | style="height: 260px"></Echarts> |
| | | </div> |
| | | |
| | | <!-- 忬¾ä¸å¼ç¥¨åæ --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">è¿ä¸æå款ä¸å¼ç¥¨åæ</span> |
| | | </div> |
| | | <div class="panel-item-customers" style="padding-top: 60px;"> |
| | | <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries" |
| | | :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" :options="{backgroundColor: 'transparent', textStyle: {color: '#FFFFFF'}}" style="height: 270px;"></Echarts> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 顶鍿 颿 --> |
| | | <div class="dashboard-header"> |
| | | <div class="factory-name">åºç¡æ°æ®</div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | | <div class="dashboard-content"> |
| | | <!-- 左侧åºå --> |
| | | <div class="left-panel"> |
| | | <!-- 客æ·ä¿¡æ¯ç»è®¡åæ --> |
| | | <LeftTop /> |
| | | |
| | | <!-- è´¨éç»è®¡ --> |
| | | <LeftBottom /> |
| | | </div> |
| | | |
| | | <!-- ä¸é´åºå --> |
| | | <div class="center-panel"> |
| | | <CenterTop /> |
| | | |
| | | <CenterBottom /> |
| | | </div> |
| | | |
| | | <!-- å³ä¾§åºå --> |
| | | <div class="right-panel"> |
| | | <!-- åºæ¶åºä»ç»è®¡ --> |
| | | <RightTop /> |
| | | |
| | | <!-- 忬¾ä¸å¼ç¥¨åæ --> |
| | | <RightBottom /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import * as echarts from 'echarts' |
| | | import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import LeftTop from './components/basic/left-top.vue' |
| | | import LeftBottom from './components/basic/left-bottom.vue' |
| | | import CenterTop from './components/basic/center-top.vue' |
| | | import CenterBottom from './components/basic/center-bottom.vue' |
| | | import RightTop from './components/basic/right-top.vue' |
| | | import RightBottom from './components/basic/right-bottom.vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import { |
| | | analysisCustomerContractAmounts, getAmountHalfYear, |
| | | homeTodos, |
| | | qualityStatistics, |
| | | statisticsReceivablePayable, |
| | | getProgressStatistics, |
| | | getWorkInProcessTurnover |
| | | } from "@/api/viewIndex.js"; |
| | | import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js"; |
| | | import {listCustomer} from "@/api/basicData/customerFile.js"; |
| | | import {listSupplier} from "@/api/basicData/supplierManageFile.js"; |
| | | import {getLedgerPage} from "@/api/equipmentManagement/ledger.js"; |
| | | import {getRepairPage} from "@/api/equipmentManagement/repair.js"; |
| | | import {getUpkeepPage} from "@/api/equipmentManagement/upkeep.js"; |
| | | import {measuringInstrumentListPage} from "@/api/equipmentManagement/measurementEquipment.js"; |
| | | import {listPageAnalysis} from "@/api/financialManagement/expenseManagement.js"; |
| | | import {productOrderListPage} from "@/api/productionManagement/productionOrder.js"; |
| | | |
| | | // å
¨å±ç¸å
³ç¶æ |
| | | const isFullscreen = ref(false); |
| | |
| | | // ç¨æ·store |
| | | const userStore = useUserStore() |
| | | |
| | | // ååºå¼æ°æ® |
| | | const currentTime = ref('') |
| | | const currentDate = ref('') |
| | | const timer = ref(null) |
| | | const charts = ref([]) |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const customerPieChartRef = ref(null) |
| | | const salesBarChartRef = ref(null) |
| | | const dataBarChartRef = ref(null) |
| | | const financialAreaChartRef = ref(null) |
| | | const realtimeLineChartRef = ref(null) |
| | | const refContractList = ref(null) |
| | | const refTodoList = ref(null) |
| | | const progressTableRef = ref(null) |
| | | const timerScroll = ref(null) |
| | | const progressTableScrollTimer = ref(null) |
| | | const isTableScrolling = ref(false) |
| | | const tableScrollTimeout = ref(null) |
| | | const tableRowRefs = ref([]) |
| | | const rowsUnderHeader = ref(new Set()) |
| | | |
| | | const chartStylePie = { |
| | | width: '100%', |
| | | height: '100%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | } |
| | | const materialPieSeries = ref([ |
| | | { |
| | | type: 'pie', |
| | | radius: ['0%', '90%'], |
| | | avoidLabelOverlap: false, |
| | | itemStyle: { |
| | | borderColor: '#fff', |
| | | borderWidth: 0 |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | data: [] |
| | | } |
| | | ]) |
| | | const pieLegend = reactive({ |
| | | show: false, |
| | | }) |
| | | const sum = ref(0) |
| | | const totalStaff = ref(0) |
| | | const totalCustomers = ref(0) |
| | | const totalSuppliers = ref(0) |
| | | const yny = ref(0) |
| | | const chain = ref(0) |
| | | const equipmentNum = ref(0) |
| | | const equipmentRepair = ref(0) |
| | | const equipmentMaintain = ref(0) |
| | | const totalMeasuring = ref(0) |
| | | const pieTooltip = reactive({ |
| | | trigger: 'item', |
| | | formatter: function (params) { |
| | | // å¨æçææç¤ºä¿¡æ¯ï¼åºäºæ°æ®é¡¹ç name 屿§ |
| | | const description = params.name === 'æ¬æåæ¬¾éé¢' ? 'æ¬æåæ¬¾éé¢' : 'åºæ¶æ¬¾éé¢'; |
| | | return `<div style="color: #B8C8E0">${description} ${params.value}å
${params.percent}%</div>`; |
| | | }, |
| | | position: 'right' |
| | | }) |
| | | |
| | | const qualityStatisticsObject = ref({ |
| | | supplierNum: 0, |
| | | processNum: 0, |
| | | factoryNum: 0, |
| | | }) |
| | | |
| | | // 订åç»è®¡å¯¹è±¡ |
| | | const orderStatisticsObject = ref({ |
| | | totalOrderCount: 0, |
| | | uncompletedOrderCount: 0, |
| | | partialCompletedOrderCount: 0, |
| | | completedOrderCount: 0, |
| | | }) |
| | | |
| | | // å¨å¶åå¨è½¬ç»è®¡å¯¹è±¡ |
| | | const workInProcessStatistics = ref({ |
| | | totalQuantity: 0, |
| | | avgTurnoverDays: 0, |
| | | turnoverEfficiency: 0, |
| | | }) |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '150%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | } |
| | | const barSeries = ref([ |
| | | { |
| | | name: 'åºä»éé¢', |
| | | type: 'bar', |
| | | data: [], |
| | | label: { |
| | | show: true, |
| | | }, |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: '#00A4ED' }, |
| | | { offset: 1, color: '#4EE4FF' } |
| | | ]) |
| | | } |
| | | }, |
| | | { |
| | | name: 'åºæ¶éé¢', |
| | | type: 'bar', |
| | | data: [], |
| | | label: { |
| | | show: true, |
| | | }, |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: '#537EF5' }, |
| | | { offset: 1, color: '#9061F8' } |
| | | ]) |
| | | } |
| | | } |
| | | ]) |
| | | const radio1 = ref(1) |
| | | const barColors2 = ['#5181DB', '#D369E0', '#F2CA6D', '#60CCA8'] |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | } |
| | | const lineLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['å¼ç¥¨', '忬¾'] |
| | | } |
| | | const lineSeries = ref([ |
| | | { |
| | | type: 'line', |
| | | data: [], |
| | | label: { |
| | | show: true |
| | | }, |
| | | showSymbol: true, // æ¾ç¤ºåç¹ |
| | | }, |
| | | ]) |
| | | const tooltipLine = { |
| | | trigger: 'axis', |
| | | } |
| | | const yAxis2 = ref([ |
| | | { |
| | | type: 'value', |
| | | } |
| | | ]) |
| | | const xAxis2 = ref([ |
| | | { |
| | | type: 'category', |
| | | data: [], |
| | | axisLabel: { |
| | | interval: 0, |
| | | formatter: function(value) { |
| | | return value.replace(/~/g, '\n'); |
| | | }, |
| | | } |
| | | } |
| | | ]) |
| | | const barLegend2 = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['åºä»éé¢', 'åºæ¶éé¢'] |
| | | } |
| | | const barLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['åææåæ ¼æ°', 'è¿ç¨åæ ¼æ°', 'åºä¸åæ ¼æ°'] |
| | | } |
| | | const barLegend1 = { |
| | | show: false, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: [] |
| | | } |
| | | const barSeries11 = ref([ |
| | | { |
| | | name: 'ç产订åç»è®¡', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | // 使ç¨å½æ°æ ¹æ®æ°æ®ç´¢å¼è¿åä¸åé¢è² |
| | | color: function(params) { |
| | | const colorStops = [ |
| | | [ |
| | | { offset: 1, color: '#00A4ED' }, |
| | | { offset: 0, color: '#4EE4FF' } |
| | | ], |
| | | [ |
| | | { offset: 1, color: '#3378FF' }, |
| | | { offset: 0, color: '#4E8AFF' } |
| | | ], |
| | | [ |
| | | { offset: 1, color: '#FF6B6B' }, |
| | | { offset: 0, color: '#FF8E8E' } |
| | | ], |
| | | [ |
| | | { offset: 1, color: '#537EF5' }, |
| | | { offset: 0, color: '#9061F8' } |
| | | ] |
| | | ] |
| | | const stops = colorStops[params.dataIndex] || colorStops[0] |
| | | return { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: stops |
| | | } |
| | | } |
| | | }, |
| | | data: [] |
| | | } |
| | | ]) |
| | | const barSeries1 = ref([ |
| | | { |
| | | name: 'åææåæ ¼æ°', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: '#00A4ED' }, |
| | | { offset: 0, color: '#4EE4FF' } |
| | | ] |
| | | } |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'è¿ç¨åæ ¼æ°', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: '#3378FF' }, |
| | | { offset: 0, color: '#4E8AFF' } |
| | | ] |
| | | } |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'åºååæ ¼æ°', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: '#537EF5' }, |
| | | { offset: 0, color: '#9061F8' } |
| | | ] |
| | | } |
| | | }, |
| | | data: [] |
| | | }, |
| | | ]) |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | }, |
| | | formatter: function (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 xAxis = [{ |
| | | type: 'value', |
| | | }] |
| | | const yAxis = [{ |
| | | type: 'category', |
| | | data: ['åºæ¶åºä»ç»è®¡'] |
| | | }] |
| | | const xAxis1 = ref([{ |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | data: [] |
| | | }]) |
| | | const yAxis1 = [{ |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' } |
| | | }] |
| | | const xAxis3 = ref([{ |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | data: [] |
| | | }]) |
| | | const yAxis3 = [{ |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' } |
| | | }] |
| | | |
| | | // å¨å¶åå·¥åºæ±ç¶å¾é
ç½® |
| | | const workInProcessXAxis = ref([{ |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | data: [] |
| | | }]) |
| | | const workInProcessYAxis = [{ |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | name: '' |
| | | }] |
| | | const workInProcessBarLegend = { |
| | | show: false, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: [] |
| | | } |
| | | const workInProcessBarSeries = ref([ |
| | | { |
| | | name: 'å¨å¶åæ°é', |
| | | type: 'bar', |
| | | barWidth: 25, // åºå®æ±ç¶å¾å®½åº¦ä¸º40px |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 0, color: '#4EE4FF' }, |
| | | { offset: 1, color: '#00A4ED' } |
| | | ] |
| | | } |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | color: '#B8C8E0' |
| | | }, |
| | | data: [] |
| | | } |
| | | ]) |
| | | |
| | | // å¾
åäºé¡¹ |
| | | const todoList = ref([]) |
| | | |
| | | // ç产订å宿è¿åº¦è¡¨æ ¼æ°æ® |
| | | const progressTableData = ref([]) |
| | | |
| | | // 计ç®å®æè¿åº¦ç¾åæ¯ |
| | | const calculateProgress = (item) => { |
| | | if (!item) return 0 |
| | | // ä¼å
使ç¨completionStatusåæ®µ |
| | | if (item.completionStatus !== undefined && item.completionStatus !== null) { |
| | | const percentage = Number(item.completionStatus) |
| | | if (isNaN(percentage)) return 0 |
| | | return Math.min(Math.max(Math.round(percentage), 0), 100) |
| | | } |
| | | // å¦ææ²¡æcompletionStatusï¼åæ ¹æ®å®ææ°éåéæ±æ°éè®¡ç® |
| | | if (!item.quantity || item.quantity === 0) return 0 |
| | | const percentage = (item.completeQuantity || 0) / item.quantity * 100 |
| | | return Math.min(Math.max(Math.round(percentage), 0), 100) |
| | | } |
| | | |
| | | // æ ¹æ®è¿åº¦ç¾åæ¯è¿åé¢è² |
| | | const progressColor = (percentage) => { |
| | | const p = percentage || 0 |
| | | if (p < 30) return "#f56c6c" |
| | | if (p < 50) return "#e6a23c" |
| | | if (p < 80) return "#409eff" |
| | | return "#67c23a" |
| | | } |
| | | |
| | | // 计ç®ç¼©æ¾æ¯ä¾ |
| | | 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 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) |
| | | |
| | | // 触åå¾è¡¨resize |
| | | charts.value.forEach(chart => { |
| | | if (chart && chart.resize) { |
| | | chart.resize() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // çªå£å¤§å°ååå¤ç |
| | |
| | | }, 100) |
| | | } |
| | | |
| | | // 鿝å¾è¡¨å®ä¾ |
| | | const disposeCharts = () => { |
| | | charts.value.forEach(chart => { |
| | | if (chart && chart.dispose) { |
| | | chart.dispose() |
| | | } |
| | | }) |
| | | charts.value = [] |
| | | } |
| | | // ååéé¢ |
| | | const analysisCustomer = () => { |
| | | analysisCustomerContractAmounts().then((res) => { |
| | | sum.value = res.data.sum |
| | | yny.value = res.data.yny |
| | | chain.value = res.data.chain |
| | | // 为æ¯ä¸ªæ°æ®é¡¹åé
éæºé¢è² |
| | | materialPieSeries.value[0].data = res.data.item.map(item => ({ |
| | | ...item, |
| | | itemStyle: { color: getRandomColor() } |
| | | })) |
| | | }) |
| | | } |
| | | // å¨å¶åå¨è½¬ç»è®¡ |
| | | const workInProcessTurnoverInfo = () => { |
| | | getWorkInProcessTurnover().then((res) => { |
| | | console.log("å¨å¶åå¨è½¬ç»è®¡æ°æ®:", res) |
| | | |
| | | if (!res || !res.data) { |
| | | console.warn('å¨å¶åå¨è½¬ç»è®¡æ°æ®ä¸ºç©º') |
| | | return |
| | | } |
| | | |
| | | // 仿¥å£è·åç»è®¡æ°æ® |
| | | workInProcessStatistics.value = { |
| | | totalQuantity: res.data.totalOrderCount || 0, |
| | | avgTurnoverDays: res.data.averageTurnoverDays || 0, |
| | | turnoverEfficiency: res.data.turnoverEfficiency || 0, |
| | | } |
| | | |
| | | // è®¾ç½®å·¥åºæ±ç¶å¾æ°æ® |
| | | // Xè½´ï¼processDetails (å·¥åºè¯¦æ
æ°ç») |
| | | // Yè½´ï¼processQuantityDetails (å·¥åºæ°é详æ
æ°ç») |
| | | if (res.data.processDetails && Array.isArray(res.data.processDetails)) { |
| | | // 设置Xè½´æ°æ®ï¼å·¥åºåç§°ï¼ |
| | | workInProcessXAxis.value[0].data = res.data.processDetails |
| | | } else { |
| | | workInProcessXAxis.value[0].data = [] |
| | | } |
| | | |
| | | if (res.data.processQuantityDetails && Array.isArray(res.data.processQuantityDetails)) { |
| | | // 设置Yè½´æ°æ®ï¼å¨å¶åæ°éï¼ |
| | | workInProcessBarSeries.value[0].data = res.data.processQuantityDetails |
| | | } else { |
| | | workInProcessBarSeries.value[0].data = [] |
| | | } |
| | | }).catch((error) => { |
| | | console.error('è·åå¨å¶åå¨è½¬ç»è®¡å¤±è´¥:', error) |
| | | }) |
| | | } |
| | | // è´¨æ£ç»è®¡ |
| | | const qualityStatisticsInfo = () => { |
| | | qualityStatistics().then((res) => { |
| | | res.data.item.forEach(item => { |
| | | xAxis1.value[0].data.push(item.date) |
| | | barSeries1.value[0].data.push(item.supplierNum) |
| | | barSeries1.value[1].data.push(item.processNum) |
| | | barSeries1.value[2].data.push(item.factoryNum) |
| | | }) |
| | | qualityStatisticsObject.value.supplierNum = res.data.supplierNum |
| | | qualityStatisticsObject.value.processNum = res.data.processNum |
| | | qualityStatisticsObject.value.factoryNum = res.data.factoryNum |
| | | }) |
| | | } |
| | | // åç产订åç宿è¿åº¦ç»è®¡ |
| | | const progressStatisticsInfo = () => { |
| | | // ä»ç»è®¡æ¥å£è·åç»è®¡æ°æ® |
| | | getProgressStatistics().then((res) => { |
| | | console.log("ç产订å宿è¿åº¦ç»è®¡æ°æ®:", res) |
| | | |
| | | if (!res || !res.data) { |
| | | console.warn('ç产订å宿è¿åº¦ç»è®¡æ°æ®ä¸ºç©º') |
| | | return |
| | | } |
| | | |
| | | // 仿¥å£è·åç»è®¡æ°æ® |
| | | orderStatisticsObject.value = { |
| | | totalOrderCount: res.data.totalOrderCount || 0, |
| | | uncompletedOrderCount: res.data.uncompletedOrderCount || 0, |
| | | partialCompletedOrderCount: res.data.partialCompletedOrderCount || 0, |
| | | completedOrderCount: res.data.completedOrderCount || 0 |
| | | } |
| | | progressTableData.value = res.data.completedOrderDetails || [] |
| | | // éç½®è¡å¼ç¨ |
| | | tableRowRefs.value = [] |
| | | rowsUnderHeader.value.clear() |
| | | |
| | | // å¨è·åå°æ°æ®åï¼åå§åæ»å¨åè½ |
| | | nextTick(() => { |
| | | initProgressTableScroll() |
| | | }) |
| | | }).catch((error) => { |
| | | console.error('è·åç产订å宿è¿åº¦ç»è®¡å¤±è´¥:', error) |
| | | }) |
| | | } |
| | | // è´¢å¡ç»è®¡ |
| | | // const accountStatisticsInfo = () => { |
| | | // listPageAnalysis().then((res) => { |
| | | // xAxis3.value[0].data = res.data.days |
| | | // barSeries11.value[0].data = res.data.totalIncome |
| | | // }) |
| | | // } |
| | | const getNum = () => { |
| | | const params = { |
| | | pageNum: -1, |
| | | pageSize: -1, |
| | | } |
| | | staffOnJobListPage({...params, staffState: 1}).then(res => { |
| | | totalStaff.value = res.data.total |
| | | }) |
| | | listCustomer(params).then((res) => { |
| | | totalCustomers.value = res.total; |
| | | }); |
| | | listSupplier(params).then((res) => { |
| | | totalSuppliers.value = res.data.total |
| | | }); |
| | | } |
| | | const getLedgerNum = () => { |
| | | const params = { |
| | | pageNum: -1, |
| | | pageSize: -1, |
| | | } |
| | | getLedgerPage(params).then((res) => { |
| | | equipmentNum.value = res.data.total |
| | | }); |
| | | getRepairPage({...params, status:0}).then((res) => { |
| | | equipmentRepair.value = res.data.total |
| | | }); |
| | | getUpkeepPage({...params, status:0}).then((res) => { |
| | | equipmentMaintain.value = res.data.total |
| | | }); |
| | | measuringInstrumentListPage(params).then((res) => { |
| | | totalMeasuring.value = res.data.total |
| | | }); |
| | | } |
| | | // å¾
åäºé¡¹ |
| | | const todoInfoS = () => { |
| | | homeTodos().then((res) => { |
| | | todoList.value = res.data |
| | | // å¨è·åå°å¾
åäºé¡¹æ°æ®åï¼åå§åæ»å¨åè½ |
| | | nextTick(() => { |
| | | initTodoListScroll() |
| | | }) |
| | | }) |
| | | } |
| | | // åºä»åºæ¶ç»è®¡ |
| | | const statisticsReceivable = (type) => { |
| | | statisticsReceivablePayable({type: radio1.value}).then((res) => { |
| | | // 设置åºä»é颿°æ® |
| | | barSeries.value[0].data = [ |
| | | { value: res.data.payableMoney } |
| | | ] |
| | | // è®¾ç½®åºæ¶é颿°æ® |
| | | barSeries.value[1].data = [ |
| | | { value: res.data.receivableMoney } |
| | | ] |
| | | }) |
| | | } |
| | | const getAmountHalfYearNum = async () => { |
| | | const res = await getAmountHalfYear() |
| | | console.log(res) |
| | | const monthName = [] |
| | | const receiptAmount = [] |
| | | const invoiceAmount = [] |
| | | res.data.forEach(item => { |
| | | monthName.push(item.month) |
| | | receiptAmount.push(item.receiptAmount) |
| | | invoiceAmount.push(item.invoiceAmount) |
| | | }) |
| | | // æ£ç¡®ååºå¼èµå¼ï¼å建æ°ç xAxis å series 对象 |
| | | xAxis2.value[0].data = monthName |
| | | xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~')); |
| | | lineSeries.value = [ |
| | | { |
| | | name: 'å¼ç¥¨', |
| | | type: 'line', |
| | | data: receiptAmount, |
| | | stack: 'Total', |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(131, 207, 255, 1)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(186, 228, 255, 1)' |
| | | } |
| | | ]) |
| | | }, |
| | | itemStyle: { |
| | | color: '#2D99FF', |
| | | borderColor: '#2D99FF' |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | showSymbol: true, |
| | | }, |
| | | { |
| | | name: '忬¾', |
| | | type: 'line', |
| | | data: invoiceAmount, |
| | | stack: 'Total', |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | itemStyle: { |
| | | color: '#83CFFF', |
| | | borderColor: '#83CFFF' |
| | | }, |
| | | showSymbol: true, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(54, 153, 255, 1)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(89, 169, 254, 1)' |
| | | } |
| | | ]) |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | } |
| | | ] |
| | | } |
| | | |
| | | // èªå¨è½®æ¢å¨ãæãå£åº¦ç宿¶å¨ |
| | | const autoSwitchTimer = ref(null) |
| | | |
| | | // 设置è¡å¼ç¨ |
| | | const setRowRef = (el, index) => { |
| | | if (el) { |
| | | tableRowRefs.value[index] = el |
| | | } |
| | | } |
| | | |
| | | // å¤æè¡æ¯å¦å¨è¡¨å¤´ä¸æ¹ |
| | | const isRowUnderHeader = (index) => { |
| | | return rowsUnderHeader.value.has(index) |
| | | } |
| | | |
| | | // å¤çè¡¨æ ¼æ»å¨äºä»¶ |
| | | const handleTableScroll = () => { |
| | | const tableContainer = progressTableRef.value |
| | | if (!tableContainer) return |
| | | |
| | | const thead = tableContainer.querySelector('thead') |
| | | if (!thead) return |
| | | |
| | | const theadHeight = thead.offsetHeight |
| | | const containerRect = tableContainer.getBoundingClientRect() |
| | | const containerTop = containerRect.top |
| | | const theadBottom = containerTop + theadHeight |
| | | |
| | | // æ¸
空ä¹åçè®°å½ |
| | | rowsUnderHeader.value.clear() |
| | | |
| | | // æ£æ¥æ¯ä¸è¡æ¯å¦å¨è¡¨å¤´ä¸æ¹ï¼è¢«è¡¨å¤´é®æ¡ï¼ |
| | | tableRowRefs.value.forEach((row, index) => { |
| | | if (row) { |
| | | const rowRect = row.getBoundingClientRect() |
| | | const rowTop = rowRect.top |
| | | const rowBottom = rowRect.bottom |
| | | |
| | | // 妿è¡ä¸è¡¨å¤´æéå ï¼è¡å¨è¡¨å¤´ä¸æ¹è¢«é®æ¡ï¼ |
| | | // è¡çé¡¶é¨å¨è¡¨å¤´åºé¨ä¸æ¹ï¼ä½è¡çåºé¨å¨è¡¨å¤´åºé¨ä¸æ¹ï¼è¯´æè¢«é®æ¡ |
| | | if (rowTop < theadBottom && rowBottom > containerTop) { |
| | | rowsUnderHeader.value.add(index) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // æ¸
é¤ä¹åç宿¶å¨ |
| | | if (tableScrollTimeout.value) { |
| | | clearTimeout(tableScrollTimeout.value) |
| | | } |
| | | |
| | | // æ»å¨åæ¢åæ¸
ç©ºæ·¡åæ è®° |
| | | tableScrollTimeout.value = setTimeout(() => { |
| | | rowsUnderHeader.value.clear() |
| | | }, 150) |
| | | } |
| | | |
| | | // åå§åç产订åè¿åº¦è¡¨æ ¼æ»å¨åè½ |
| | | const initProgressTableScroll = () => { |
| | | const tableContainer = progressTableRef.value |
| | | if (!tableContainer) return |
| | | |
| | | // æ¸
çä¹åçæ»å¨å¨ç»å宿¶å¨ |
| | | if (progressTableScrollTimer.value) { |
| | | cancelAnimationFrame(progressTableScrollTimer.value) |
| | | progressTableScrollTimer.value = null |
| | | } |
| | | if (tableContainer._pauseTimer) { |
| | | clearInterval(tableContainer._pauseTimer) |
| | | tableContainer._pauseTimer = null |
| | | } |
| | | |
| | | const tbody = tableContainer.querySelector('tbody') |
| | | if (!tbody) return |
| | | |
| | | // æ¸
çä¹åå¯è½åå¨çå
éè¡ï¼ä¿çåå§æ°æ®è¡ï¼ |
| | | // åå§æ°æ®è¡çæ°éåºè¯¥çäº progressTableData.value.length |
| | | const originalCount = progressTableData.value.length |
| | | const allRows = Array.from(tbody.querySelectorAll('tr')) |
| | | if (allRows.length > originalCount) { |
| | | // ç§»é¤ææè¶
è¿åå§æ°éçè¡ï¼è¿äºæ¯å
éçè¡ï¼ |
| | | for (let i = originalCount; i < allRows.length; i++) { |
| | | allRows[i].remove() |
| | | } |
| | | } |
| | | |
| | | const scrollItems = Array.from(tbody.querySelectorAll('tr')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | // è·ååå§æ°æ®é¡¹æ°é |
| | | const originalItemCount = scrollItems.length |
| | | |
| | | // 计ç®å®¹å¨é«åº¦å表头é«åº¦ |
| | | const thead = tableContainer.querySelector('thead') |
| | | const theadHeight = thead ? thead.offsetHeight : 40 |
| | | const containerHeight = tableContainer.clientHeight |
| | | const visibleHeight = containerHeight - theadHeight |
| | | |
| | | // 计ç®åå§æ°æ®çæ»é«åº¦ |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 40 |
| | | const totalContentHeight = itemHeight * originalItemCount |
| | | |
| | | // å¦ææ°æ®éä¸å¤ï¼å®¹å¨å¯ä»¥å®å
¨æ¾ç¤ºæææ°æ®ï¼å°±ä¸éè¦æ»å¨åå
é |
| | | if (totalContentHeight <= visibleHeight) { |
| | | // æ°æ®éå°ï¼ä¸éè¦æ»å¨ï¼ç´æ¥è¿å |
| | | return |
| | | } |
| | | |
| | | // æ°æ®éè¶³å¤ï¼éè¦æ»å¨ï¼è¿è¡å
é以å®ç°æ ç¼æ»å¨ |
| | | const cloneCount = Math.ceil(visibleHeight / itemHeight) + 2 |
| | | |
| | | // å
éåå 个项ç®å¹¶æ·»å å°å表æ«å°¾ï¼å®ç°æ ç¼æ»å¨ |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % originalItemCount].cloneNode(true) |
| | | tbody.appendChild(clone) |
| | | } |
| | | |
| | | let scrollPosition = 0 |
| | | const scrollSpeed = 1.5 |
| | | const pauseTime = 3000 |
| | | let isPaused = false |
| | | let lastTimestamp = 0 |
| | | |
| | | // è¿ç»æ»å¨å¨ç»å½æ° |
| | | function scrollAnimation(timestamp) { |
| | | if (!lastTimestamp) lastTimestamp = timestamp |
| | | const deltaTime = timestamp - lastTimestamp |
| | | lastTimestamp = timestamp |
| | | |
| | | if (!isPaused) { |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) |
| | | |
| | | // è®¡ç®æå¤§æ»å¨ä½ç½®ï¼åå§å
容çé«åº¦ï¼ |
| | | const maxScroll = itemHeight * originalItemCount |
| | | |
| | | // 彿»å¨è¶
è¿åå§å
容é¿åº¦æ¶ï¼éç½®ä½ç½®å®ç°æ ç¼æ»å¨ |
| | | if (scrollPosition >= maxScroll) { |
| | | scrollPosition = 0 |
| | | tableContainer.scrollTop = 0 |
| | | } else { |
| | | tableContainer.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | |
| | | progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | |
| | | // å¯å¨æ»å¨å¨ç» |
| | | progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation) |
| | | |
| | | // 设置æ»å¨-æå-æ»å¨çå¾ªç¯ææ |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | |
| | | // æ¸
ç宿¶å¨ |
| | | tableContainer._pauseTimer = pauseTimer |
| | | } |
| | | |
| | | // åå§åå¾
åäºé¡¹å表æ»å¨åè½ |
| | | const initTodoListScroll = () => { |
| | | const todoList = refTodoList.value |
| | | // 强å¶å¯ç¨æ»å¨ï¼ä¸æ£æ¥ä»»ä½æ¡ä»¶ |
| | | if (todoList) { |
| | | // å建ä¸ä¸ªå
é项ï¼ç¨äºå®ç°æ ç¼æ»å¨ |
| | | const scrollItems = Array.from(todoList.querySelectorAll('li')) |
| | | if (scrollItems.length > 0) { |
| | | // ç¡®ä¿æè¶³å¤ç项ç®ç¨äºæ»å¨ |
| | | // 妿项ç®å¤ªå°ï¼å¤å¤å¶å æ¬¡ä»¥ç¡®ä¿æ»å¨ææ |
| | | if (scrollItems.length < 4) { |
| | | const originalItems = [...scrollItems] |
| | | for (let i = 0; i < 4; i++) { |
| | | originalItems.forEach(item => { |
| | | const clone = item.cloneNode(true) |
| | | todoList.appendChild(clone) |
| | | }) |
| | | } |
| | | // éæ°è·åææé¡¹ç® |
| | | scrollItems.push(...Array.from(todoList.querySelectorAll('li')).slice(scrollItems.length)); |
| | | } |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 0 |
| | | const containerHeight = todoList.clientHeight |
| | | const cloneCount = Math.ceil(containerHeight / itemHeight) + 2 |
| | | |
| | | // å
éåå 个项ç®å¹¶æ·»å å°å表æ«å°¾ï¼å®ç°æ ç¼æ»å¨ |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % scrollItems.length].cloneNode(true) |
| | | todoList.appendChild(clone) |
| | | } |
| | | |
| | | let scrollPosition = 0 |
| | | const scrollSpeed = 1.5 // å¢å æ»å¨é度ï¼ä½¿æ»å¨æ´å ææ¾ |
| | | const pauseTime = 3000 // æ»å¨æåæ¶é´ |
| | | let isPaused = false |
| | | let lastTimestamp = 0 |
| | | |
| | | // è¿ç»æ»å¨å¨ç»å½æ° |
| | | function scrollAnimation(timestamp) { |
| | | if (!lastTimestamp) lastTimestamp = timestamp |
| | | const deltaTime = timestamp - lastTimestamp |
| | | lastTimestamp = timestamp |
| | | |
| | | if (!isPaused) { |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) // æ åå为60fpsçé度 |
| | | |
| | | // 彿»å¨è¶
è¿åå§å
容é¿åº¦æ¶ï¼éç½®ä½ç½®å®ç°æ ç¼æ»å¨ |
| | | const maxScroll = Math.max(todoList.scrollHeight - containerHeight - cloneCount * itemHeight, itemHeight * scrollItems.length) |
| | | if (scrollPosition >= maxScroll) { |
| | | scrollPosition = 0 |
| | | todoList.scrollTop = 0 |
| | | } else { |
| | | todoList.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | |
| | | todoList._animationFrame = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | |
| | | // å¯å¨æ»å¨å¨ç» |
| | | todoList._animationFrame = requestAnimationFrame(scrollAnimation) |
| | | |
| | | // 设置æ»å¨-æå-æ»å¨çå¾ªç¯ææ |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | |
| | | // æ¸
ç宿¶å¨ |
| | | todoList._pauseTimer = pauseTimer |
| | | } |
| | | } |
| | | } |
| | | const getRandomColor = () => { |
| | | // çææµ
è²ï¼RãGãB åéé½å¨ 150-255 ä¹é´ |
| | | const r = Math.floor(Math.random() * 106) + 150; // 150-255 |
| | | const g = Math.floor(Math.random() * 106) + 150; // 150-255 |
| | | const b = Math.floor(Math.random() * 106) + 150; // 150-255 |
| | | // å° RGB 转æ¢ä¸ºåå
è¿å¶é¢è² |
| | | return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0'); |
| | | } |
| | | |
| | | // æ´æ°æ¶é´ |
| | | const updateTime = () => { |
| | | const now = new Date() |
| | | currentTime.value = now.toLocaleTimeString('zh-CN', { hour12: false }) |
| | | currentDate.value = now.toLocaleDateString('zh-CN', { |
| | | year: 'numeric', |
| | | month: '2-digit', |
| | | day: '2-digit', |
| | | weekday: 'long' |
| | | }) |
| | | } |
| | | |
| | | // åå§åæ¶é´ |
| | | const initTime = () => { |
| | | updateTime() |
| | | timer.value = setInterval(updateTime, 1000) |
| | | } |
| | | // å
¨å±åè½å®ç° - é对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 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() |
| | | } |
| | | } |
| | | } |
| | | |
| | | // çå¬å
¨å±ååäºä»¶ |
| | |
| | | document.webkitFullscreenElement || |
| | | document.msFullscreenElement |
| | | isFullscreen.value = fullscreenElement && fullscreenElement.classList.contains('scale-container') |
| | | |
| | | |
| | | // å
¨å±ç¶æååæ¶ï¼å»¶è¿éæ°è®¡ç®ç¼©æ¾æ¯ä¾ï¼ç¡®ä¿DOMæ´æ°å®æï¼ |
| | | setTimeout(() => { |
| | | calculateScale() |
| | |
| | | |
| | | // çå½å¨æé©å |
| | | onMounted(() => { |
| | | initTime() |
| | | // 使ç¨nextTickç¡®ä¿DOMå®å
¨æ¸²æåååå§åå¾è¡¨ |
| | | // 使ç¨nextTickç¡®ä¿DOMå®å
¨æ¸²æåååå§å |
| | | nextTick(() => { |
| | | // 计ç®åå§ç¼©æ¾æ¯ä¾ |
| | | calculateScale() |
| | | |
| | | // åå§åautofitèªéåºï¼å¦æéè¦ä¿çautofitï¼å¯ä»¥ä¿çï¼ä½ä¸»è¦ç¼©æ¾ç±scale-containeræ§å¶ï¼ |
| | | // autofit.init({ dh: 800, dw: 1280, el: '.data-dashboard', resize: true }, false) |
| | | |
| | | // æ·»å èªå¨æ»å¨å¨ç»ææ - 客æ·ä¿¡æ¯å表 |
| | | const contractList = refContractList.value |
| | | if (contractList && contractList.scrollHeight > contractList.clientHeight) { |
| | | // å建ä¸ä¸ªå
é项ï¼ç¨äºå®ç°æ ç¼æ»å¨ |
| | | const scrollItems = Array.from(contractList.querySelectorAll('li')) |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 0 |
| | | const containerHeight = contractList.clientHeight |
| | | const cloneCount = Math.ceil(containerHeight / itemHeight) + 2 |
| | | |
| | | // å
éåå 个项ç®å¹¶æ·»å å°å表æ«å°¾ï¼å®ç°æ ç¼æ»å¨ |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % scrollItems.length].cloneNode(true) |
| | | contractList.appendChild(clone) |
| | | } |
| | | |
| | | let scrollPosition = 0 |
| | | const scrollSpeed = 1.5 // å¢å æ»å¨é度ï¼ä½¿æ»å¨æ´å ææ¾ |
| | | const pauseTime = 3000 // æ»å¨æåæ¶é´ |
| | | let isPaused = false |
| | | let lastTimestamp = 0 |
| | | |
| | | // è¿ç»æ»å¨å¨ç»å½æ° |
| | | function scrollAnimation(timestamp) { |
| | | if (!lastTimestamp) lastTimestamp = timestamp |
| | | const deltaTime = timestamp - lastTimestamp |
| | | lastTimestamp = timestamp |
| | | |
| | | if (!isPaused) { |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) // æ åå为60fpsçé度 |
| | | |
| | | // 彿»å¨è¶
è¿åå§å
容é¿åº¦æ¶ï¼éç½®ä½ç½®å®ç°æ ç¼æ»å¨ |
| | | if (scrollPosition >= contractList.scrollHeight - containerHeight - cloneCount * itemHeight) { |
| | | scrollPosition = 0 |
| | | contractList.scrollTop = 0 |
| | | } else { |
| | | contractList.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | |
| | | timerScroll.value = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | |
| | | // å¯å¨æ»å¨å¨ç» |
| | | timerScroll.value = requestAnimationFrame(scrollAnimation) |
| | | |
| | | // 设置æ»å¨-æå-æ»å¨çå¾ªç¯ææ |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | |
| | | // æ¸
ç宿¶å¨ |
| | | contractList._pauseTimer = pauseTimer |
| | | } |
| | | |
| | | // å¾
åäºé¡¹å表æ»å¨åè½å·²ç§»è³todoInfoS彿°ä¸ï¼å¨è·åæ°æ®ååå§å |
| | | }) |
| | | |
| | | |
| | | window.addEventListener('resize', handleResize) |
| | | window.addEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | analysisCustomer() |
| | | workInProcessTurnoverInfo() |
| | | qualityStatisticsInfo() |
| | | // accountStatisticsInfo() |
| | | progressStatisticsInfo() |
| | | getNum() |
| | | getLedgerNum() |
| | | todoInfoS() |
| | | statisticsReceivable() |
| | | getAmountHalfYearNum() |
| | | |
| | | // 设置èªå¨è½®æ¢å¨ãæãå£åº¦ç宿¶å¨ï¼æ¯10ç§åæ¢ä¸æ¬¡ |
| | | autoSwitchTimer.value = setInterval(() => { |
| | | // 循ç¯åæ¢ï¼1(å¨) -> 2(æ) -> 3(å£åº¦) -> 1(å¨) |
| | | radio1.value = radio1.value === 3 ? 1 : radio1.value + 1 |
| | | statisticsReceivable() |
| | | }, 10000) // 10ç§åæ¢ä¸æ¬¡ |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (timer.value) { |
| | | clearInterval(timer.value) |
| | | } |
| | | if (timerScroll.value) { |
| | | cancelAnimationFrame(timerScroll.value) |
| | | } |
| | | // æ¸
çæ»å¨å表çæå宿¶å¨ |
| | | const contractList = refContractList.value |
| | | if (contractList && contractList._pauseTimer) { |
| | | clearInterval(contractList._pauseTimer) |
| | | } |
| | | |
| | | // æ¸
çå¾
åäºé¡¹å表çå¨ç»å宿¶å¨ |
| | | const todoList = refTodoList.value |
| | | if (todoList) { |
| | | if (todoList._animationFrame) { |
| | | cancelAnimationFrame(todoList._animationFrame) |
| | | todoList._animationFrame = null |
| | | } |
| | | if (todoList._pauseTimer) { |
| | | clearInterval(todoList._pauseTimer) |
| | | todoList._pauseTimer = null |
| | | } |
| | | } |
| | | |
| | | // æ¸
çç产订åè¿åº¦è¡¨æ ¼çå¨ç»å宿¶å¨ |
| | | const progressTable = progressTableRef.value |
| | | if (progressTable) { |
| | | if (progressTableScrollTimer.value) { |
| | | cancelAnimationFrame(progressTableScrollTimer.value) |
| | | progressTableScrollTimer.value = null |
| | | } |
| | | if (progressTable._pauseTimer) { |
| | | clearInterval(progressTable._pauseTimer) |
| | | progressTable._pauseTimer = null |
| | | } |
| | | } |
| | | |
| | | // æ¸
çè¡¨æ ¼æ»å¨å®æ¶å¨ |
| | | if (tableScrollTimeout.value) { |
| | | clearTimeout(tableScrollTimeout.value) |
| | | tableScrollTimeout.value = null |
| | | } |
| | | |
| | | // æ¸
çèªå¨è½®æ¢å¨ãæãå£åº¦ç宿¶å¨ |
| | | if (autoSwitchTimer.value) { |
| | | clearInterval(autoSwitchTimer.value) |
| | | autoSwitchTimer.value = null |
| | | } |
| | | |
| | | window.removeEventListener('resize', handleResize) |
| | | window.removeEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | |
| | | window.removeEventListener('resize', window._autofitUpdateHandler) |
| | | delete window._autofitUpdateHandler |
| | | } |
| | | disposeCharts() |
| | | // å
³éautofit |
| | | autofit.off() |
| | | }) |
| | |
| | | <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; |
| | | 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; |
| | | 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; |
| | | 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; |
| | | 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; |
| | | 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; |
| | | 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-weight: 600; |
| | | font-size: 52px; |
| | | color: #FFFFFF; |
| | | top: 16px; |
| | |
| | | } |
| | | |
| | | .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; |
| | | 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); |
| | | 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; |
| | | 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; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .left-panel, |
| | | .right-panel { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 24px; |
| | | width: 520px; |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 24px; |
| | | width: 520px; |
| | | } |
| | | |
| | | .center-panel { |
| | | flex: 1.5; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | .panel-item-customers { |
| | | border: 1px solid #1A58B0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 540px; |
| | | } |
| | | .panel-title-second { |
| | | height: 60px; |
| | | display: flex; |
| | | gap: 12px; |
| | | margin-bottom: 20px; |
| | | align-items: center; |
| | | } |
| | | .quality-cards { |
| | | display: flex; |
| | | gap: 12px; |
| | | width: 100%; |
| | | height: 54px; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .quality-cardSec { |
| | | display: flex; |
| | | } |
| | | .quality-cardTitle { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | display: flex; |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | } |
| | | .quality-card { |
| | | width: 80px; |
| | | height: 60px; |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | .quality-card.one { |
| | | background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png"); |
| | | } |
| | | .quality-card.two { |
| | | background-image: url("@/assets/BI/guochengyijianicon@2x.png"); |
| | | } |
| | | .quality-card.three { |
| | | background-image: url("@/assets/BI/chuchangyijianicon@2x.png"); |
| | | flex: 1.5; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | /* 订åç»è®¡å¡çæ ·å¼ */ |
| | | .order-statistics-cards { |
| | | display: flex; |
| | | gap: 12px; |
| | | width: 100%; |
| | | height: 94px; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .quality-card.four { |
| | | background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png"); |
| | | } |
| | | |
| | | .quality-card.five { |
| | | background-image: url("@/assets/BI/guochengyijianicon@2x.png"); |
| | | } |
| | | |
| | | .quality-card.six { |
| | | background-image: url("@/assets/BI/chuchangyijianicon@2x.png"); |
| | | } |
| | | |
| | | .quality-card.seven { |
| | | background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png"); |
| | | } |
| | | .panel-title-icon { |
| | | width: 60px; |
| | | height: 60px; |
| | | background-image: url("@/assets/BI/hetongicon.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | .total-customers { |
| | | background-image: url("@/assets/BI/hetongjineback@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | width: 90%; |
| | | height: 60px; |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 0 20px; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .total-customers .label { |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .total-customers .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; |
| | | } |
| | | |
| | | .contract-list { |
| | | margin-top: 16px; |
| | | font-size: 14px; |
| | | color: #666; |
| | | list-style: none; |
| | | padding: 0; |
| | | height: 82%; |
| | | overflow-y: auto; |
| | | width: 460px; |
| | | /* éèæ»å¨æ¡ä½ä¿çæ»å¨åè½ */ |
| | | scrollbar-width: none; /* Firefox */ |
| | | -ms-overflow-style: none; /* IEåEdge */ |
| | | } |
| | | |
| | | /* ChromeãSafariåOpera */ |
| | | .contract-list::-webkit-scrollbar { |
| | | display: none; |
| | | } |
| | | .line { |
| | | position: relative; |
| | | width: 230px; |
| | | } |
| | | .line::after { |
| | | content: ''; |
| | | position: absolute; |
| | | right: 2px; |
| | | top: 0; |
| | | bottom: 0; |
| | | width: 1px; |
| | | background-color: #C9C5C5; |
| | | border-radius: 2px; |
| | | } |
| | | .contract-list li { |
| | | margin-top: 10px; |
| | | } |
| | | .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); |
| | | } |
| | | |
| | | .equipment-stats { |
| | | border: 1px solid #1A58B0; |
| | | padding: 18px; |
| | | height: 240px; |
| | | } |
| | | .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: 21px; |
| | | 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; |
| | | } |
| | | .equipment-items { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .equipment-item { |
| | | text-align: center; |
| | | } |
| | | |
| | | .equipment-value { |
| | | display: block; |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | color: #FFFFFF; |
| | | width: 120px; |
| | | height: 110px; |
| | | line-height: 110px; |
| | | background-image: url("@/assets/BI/shujutongji@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .equipment-label { |
| | | font-weight: 500; |
| | | font-size: 21px; |
| | | color: #FFFFFE; |
| | | } |
| | | |
| | | .event-info { |
| | | background-image: url("@/assets/BI/shijianmingchengbeijing@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | padding: 20px; |
| | | height: 186px; |
| | | } |
| | | .event-header { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | .event-icon { |
| | | width: 40px; |
| | | height: 40px; |
| | | } |
| | | .event-title { |
| | | font-weight: 500; |
| | | font-size: 24px; |
| | | color: #FFFFFE; |
| | | line-height: 30px; |
| | | } |
| | | .todo-list { |
| | | list-style: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | height: 120px; /* æç¨æ·è¦æ±è°æ´é«åº¦ */ |
| | | overflow: hidden; |
| | | font-size: 15px; |
| | | } |
| | | .todo-list li { |
| | | border-radius: 8px; |
| | | margin-bottom: 12px; |
| | | padding: 12px 40px; |
| | | height: 74px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .todo-title { |
| | | font-weight: 400; |
| | | font-size: 20px; |
| | | color: #FFFFFE; |
| | | position: relative; |
| | | } |
| | | .todo-title::before { |
| | | content: ''; /* å¿
éï¼è¡¨ç¤ºè¿éæä¸ä¸ªå
容 */ |
| | | position: absolute; |
| | | left: -10px; /* å®ä½å°å·¦ä¾§ */ |
| | | top: 50%; /* åç´å±
ä¸ */ |
| | | transform: translateY(-50%); /* å¾®è°åç´å±
ä¸ */ |
| | | width: 6px; /* åçç´å¾ */ |
| | | height: 6px; /* åçç´å¾ */ |
| | | background: #498CEB; |
| | | border-radius: 50%; /* 让å
¶åæåå½¢ */ |
| | | } |
| | | .todo-division { |
| | | font-weight: 400; |
| | | font-size: 20px; |
| | | color: #FFFFFE; |
| | | } |
| | | .todo-time { |
| | | font-weight: 400; |
| | | font-size: 20px; |
| | | color: #FFFFFE; |
| | | } |
| | | .financial-header { |
| | | background-image: url("@/assets/BI/caiwufenxiback@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | .financial-title { |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #D9ECFF; |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | } |
| | | |
| | | /* èªå®ä¹åéæé®ç»æ ·å¼ */ |
| | | .custom-radio-group :deep(.el-radio-button__inner) { |
| | | background-color: transparent; |
| | | color: white; |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | } |
| | | |
| | | .custom-radio-group :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background-color: rgba(255, 255, 255, 0.2); |
| | | color: white; |
| | | border-color: rgba(255, 255, 255, 0.5); |
| | | box-shadow: -1px 0 0 0 rgba(255, 255, 255, 0.5); |
| | | } |
| | | |
| | | /* ç产订åè¿åº¦è¡¨æ ¼æ ·å¼ */ |
| | | .progress-table-container { |
| | | height: 200px; |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | margin-top: 10px; |
| | | scrollbar-width: none; /* Firefox */ |
| | | -ms-overflow-style: none; /* IEåEdge */ |
| | | } |
| | | |
| | | .progress-table-container::-webkit-scrollbar { |
| | | display: none; /* ChromeãSafariåOpera */ |
| | | } |
| | | |
| | | .progress-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | color: #B8C8E0; |
| | | font-size: 12px; |
| | | table-layout: fixed; |
| | | } |
| | | |
| | | .progress-table thead { |
| | | position: sticky; |
| | | top: 0; |
| | | background-color: rgba(26, 88, 176, 0.9); |
| | | z-index: 10; |
| | | } |
| | | |
| | | .progress-table th { |
| | | padding: 8px 6px; |
| | | text-align: left; |
| | | font-weight: 500; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.3); |
| | | color: #B8C8E0; |
| | | font-size: 12px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .progress-table th:nth-child(1) { width: 15%; } /* ç产订åå· */ |
| | | .progress-table th:nth-child(2) { width: 15%; } /* 产ååç§° */ |
| | | .progress-table th:nth-child(3) { width: 15%; } /* è§æ ¼ */ |
| | | .progress-table th:nth-child(4) { width: 12%; } /* éæ±æ°é */ |
| | | .progress-table th:nth-child(5) { width: 12%; } /* 宿æ°é */ |
| | | .progress-table th:nth-child(6) { width: 31%; } /* 宿è¿åº¦ */ |
| | | |
| | | .progress-table td { |
| | | padding: 8px 6px; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.1); |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | font-size: 12px; |
| | | transition: opacity 0.3s ease; |
| | | } |
| | | |
| | | .progress-table tbody tr:hover { |
| | | background-color: rgba(184, 200, 224, 0.1); |
| | | } |
| | | |
| | | .progress-table tbody tr.row-under-header { |
| | | opacity: 0.5; |
| | | } |
| | | |
| | | /* el-progress ç»ä»¶æ ·å¼è°æ´ */ |
| | | .progress-table :deep(.el-progress) { |
| | | width: 100%; |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress-bar__outer) { |
| | | background-color: rgba(184, 200, 224, 0.2); |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress__text) { |
| | | color: #B8C8E0; |
| | | font-size: 11px; |
| | | } |
| | | </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">{{ userStore.currentFactoryName }}</div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | | <div class="dashboard-content"> |
| | | <!-- 左侧åºå --> |
| | | <div class="left-panel"> |
| | | <!-- 客æ·ä¿¡æ¯ç»è®¡åæ --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">å¨å¶åç»è®¡åæ</span> |
| | | </div> |
| | | <div class="panel-item-customers"> |
| | | <div class="quality-cards"> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card one"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>æ»å¨å¶æ°é</div> |
| | | <div>{{workInProcessStatistics.totalQuantity}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card two"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>å¹³åå¨è½¬å¤©æ°</div> |
| | | <div>{{workInProcessStatistics.avgTurnoverDays}}天</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card three"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>å¨è½¬æç</div> |
| | | <div>{{workInProcessStatistics.turnoverEfficiency}}%</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- å·¥åºå¨å¶åæ°éæ±ç¶å¾ --> |
| | | <div style="height: 70%"> |
| | | <Echarts ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="workInProcessBarLegend" |
| | | :series="workInProcessBarSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="workInProcessXAxis" |
| | | :yAxis="workInProcessYAxis" |
| | | :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}" |
| | | style="height: 100%"></Echarts> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è´¨éç»è®¡ --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">è¿4æè´¨éç»è®¡</span> |
| | | </div> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | | <div class="quality-cards"> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card one"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>åæææ£æ°</div> |
| | | <div>{{qualityStatisticsObject.supplierNum}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card two"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>è¿ç¨æ£æ°</div> |
| | | <div>{{qualityStatisticsObject.processNum}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card three"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>åºåæ£æ°</div> |
| | | <div>{{qualityStatisticsObject.factoryNum}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | </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"></Echarts> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ä¸é´åºå --> |
| | | <div class="center-panel"> |
| | | <!-- é¡¶é¨ç»è®¡å¡ç --> |
| | | <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> |
| | | </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> |
| | | </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> |
| | | </div> |
| | | </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> |
| | | <div class="equipment-items"> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{equipmentNum}}</span> |
| | | <span class="equipment-label">è®¾å¤æ»æ°</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{equipmentRepair}}</span> |
| | | <span class="equipment-label">å¾
维修设å¤</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{equipmentMaintain}}</span> |
| | | <span class="equipment-label">å¾
ä¿å
»è®¾å¤</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{totalMeasuring}}</span> |
| | | <span class="equipment-label">计éå¨å
·æ»æ°</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- äºä»¶åç§° --> |
| | | <div class="event-info"> |
| | | <div class="event-header"> |
| | | <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="徿 " class="event-icon" /> |
| | | <span class="event-title">äºä»¶åç§°</span> |
| | | </div> |
| | | <div class="event-content"> |
| | | <ul class="todo-list" v-if="todoList.length > 0" ref="refTodoList"> |
| | | <li v-for="item in todoList" :key="item.id"> |
| | | <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px"> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;"> |
| | | <div class="todo-title">å¾
åç¼å·ï¼{{item.approveId}}</div> |
| | | <div class="todo-division">é¨é¨ï¼{{item.approveDeptName}}</div> |
| | | <div class="todo-time">{{item.approveTime}}</div> |
| | | </div> |
| | | <div class="todo-division">å¾
åäºç±ï¼{{item.approveReason}}</div> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <div v-else style="text-align: center"> |
| | | ææ æ°æ® |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="financial-header"> |
| | | <span class="financial-title">åç产订åç宿è¿åº¦ç»è®¡</span> |
| | | </div> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | | <div class="order-statistics-cards" style="margin-bottom: 0px;"> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card four"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>æ»è®¢åæ°</div> |
| | | <div>{{orderStatisticsObject.totalOrderCount}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card five"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>æªå®æè®¢åæ°</div> |
| | | <div>{{orderStatisticsObject.uncompletedOrderCount}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card six"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>é¨åå®æè®¢åæ°</div> |
| | | <div>{{orderStatisticsObject.partialCompletedOrderCount}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card seven"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>å·²å®æè®¢åæ°</div> |
| | | <div>{{orderStatisticsObject.completedOrderCount}}ä»¶</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="progress-table-container" ref="progressTableRef" style="margin-top: 0px;" @scroll="handleTableScroll"> |
| | | <table class="progress-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>ç产订åå·</th> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼</th> |
| | | <th>éæ±æ°é</th> |
| | | <th>宿æ°é</th> |
| | | <th>宿è¿åº¦</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr |
| | | v-for="(item, index) in progressTableData" |
| | | :key="index" |
| | | :ref="el => setRowRef(el, index)" |
| | | :class="{ 'row-under-header': isRowUnderHeader(index) }" |
| | | > |
| | | <td>{{ item.npsNo || '-' }}</td> |
| | | <td>{{ item.productCategory || '-' }}</td> |
| | | <td>{{ item.specificationModel || '-' }}</td> |
| | | <td>{{ item.quantity || 0 }}</td> |
| | | <td>{{ item.completeQuantity || 0 }}</td> |
| | | <td> |
| | | <el-progress |
| | | :percentage="calculateProgress(item)" |
| | | :color="progressColor(calculateProgress(item))" |
| | | :status="calculateProgress(item) >= 100 ? 'success' : ''" |
| | | :stroke-width="8" |
| | | /> |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å³ä¾§åºå --> |
| | | <div class="right-panel"> |
| | | <!-- åºæ¶åºä»ç»è®¡ --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">åºæ¶åºä»ç»è®¡</span> |
| | | </div> |
| | | <div class="panel-item-customers"> |
| | | <div style="display: flex;justify-content: space-between;margin-bottom: 20px;"> |
| | | <div class="section-title">åºæ¶åºä»ç»è®¡</div> |
| | | <!-- <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable" class="custom-radio-group">--> |
| | | <!-- <el-radio-button label="æå¨" :value="1" />--> |
| | | <!-- <el-radio-button label="ææ" :value="2" />--> |
| | | <!-- <el-radio-button label="æå£åº¦" :value="3" />--> |
| | | <!-- </el-radio-group>--> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :color="barColors2" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="barLegend2" |
| | | :series="barSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}" |
| | | style="height: 260px"></Echarts> |
| | | </div> |
| | | |
| | | <!-- 忬¾ä¸å¼ç¥¨åæ --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">è¿ä¸æå款ä¸å¼ç¥¨åæ</span> |
| | | </div> |
| | | <div class="panel-item-customers" style="padding-top: 60px;"> |
| | | <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries" |
| | | :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" :options="{backgroundColor: 'transparent', textStyle: {color: '#FFFFFF'}}" style="height: 270px;"></Echarts> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import * as echarts from 'echarts' |
| | | import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import useUserStore from '@/store/modules/user' |
| | | import { |
| | | analysisCustomerContractAmounts, getAmountHalfYear, |
| | | homeTodos, |
| | | qualityStatistics, |
| | | statisticsReceivablePayable, |
| | | getProgressStatistics, |
| | | getWorkInProcessTurnover |
| | | } from "@/api/viewIndex.js"; |
| | | import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js"; |
| | | import {listCustomer} from "@/api/basicData/customerFile.js"; |
| | | import {listSupplier} from "@/api/basicData/supplierManageFile.js"; |
| | | import {getLedgerPage} from "@/api/equipmentManagement/ledger.js"; |
| | | import {getRepairPage} from "@/api/equipmentManagement/repair.js"; |
| | | import {getUpkeepPage} from "@/api/equipmentManagement/upkeep.js"; |
| | | import {measuringInstrumentListPage} from "@/api/equipmentManagement/measurementEquipment.js"; |
| | | import {listPageAnalysis} from "@/api/financialManagement/expenseManagement.js"; |
| | | import {productOrderListPage} from "@/api/productionManagement/productionOrder.js"; |
| | | |
| | | // å
¨å±ç¸å
³ç¶æ |
| | | const isFullscreen = ref(false); |
| | | |
| | | // ç¼©æ¾æ¯ä¾ |
| | | const scaleRatio = ref(1) |
| | | // 设计尺寸ï¼åºå尺寸ï¼- æ ¹æ®å®é
è®¾è®¡ç¨¿è°æ´ |
| | | const designWidth = 1920 |
| | | const designHeight = 1080 |
| | | |
| | | // ç¨æ·store |
| | | const userStore = useUserStore() |
| | | |
| | | // ååºå¼æ°æ® |
| | | const currentTime = ref('') |
| | | const currentDate = ref('') |
| | | const timer = ref(null) |
| | | const charts = ref([]) |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const customerPieChartRef = ref(null) |
| | | const salesBarChartRef = ref(null) |
| | | const dataBarChartRef = ref(null) |
| | | const financialAreaChartRef = ref(null) |
| | | const realtimeLineChartRef = ref(null) |
| | | const refContractList = ref(null) |
| | | const refTodoList = ref(null) |
| | | const progressTableRef = ref(null) |
| | | const timerScroll = ref(null) |
| | | const progressTableScrollTimer = ref(null) |
| | | const isTableScrolling = ref(false) |
| | | const tableScrollTimeout = ref(null) |
| | | const tableRowRefs = ref([]) |
| | | const rowsUnderHeader = ref(new Set()) |
| | | |
| | | const chartStylePie = { |
| | | width: '100%', |
| | | height: '100%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | } |
| | | const materialPieSeries = ref([ |
| | | { |
| | | type: 'pie', |
| | | radius: ['0%', '90%'], |
| | | avoidLabelOverlap: false, |
| | | itemStyle: { |
| | | borderColor: '#fff', |
| | | borderWidth: 0 |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | data: [] |
| | | } |
| | | ]) |
| | | const pieLegend = reactive({ |
| | | show: false, |
| | | }) |
| | | const sum = ref(0) |
| | | const totalStaff = ref(0) |
| | | const totalCustomers = ref(0) |
| | | const totalSuppliers = ref(0) |
| | | const yny = ref(0) |
| | | const chain = ref(0) |
| | | const equipmentNum = ref(0) |
| | | const equipmentRepair = ref(0) |
| | | const equipmentMaintain = ref(0) |
| | | const totalMeasuring = ref(0) |
| | | const pieTooltip = reactive({ |
| | | trigger: 'item', |
| | | formatter: function (params) { |
| | | // å¨æçææç¤ºä¿¡æ¯ï¼åºäºæ°æ®é¡¹ç name 屿§ |
| | | const description = params.name === 'æ¬æåæ¬¾éé¢' ? 'æ¬æåæ¬¾éé¢' : 'åºæ¶æ¬¾éé¢'; |
| | | return `<div style="color: #B8C8E0">${description} ${params.value}å
${params.percent}%</div>`; |
| | | }, |
| | | position: 'right' |
| | | }) |
| | | |
| | | const qualityStatisticsObject = ref({ |
| | | supplierNum: 0, |
| | | processNum: 0, |
| | | factoryNum: 0, |
| | | }) |
| | | |
| | | // 订åç»è®¡å¯¹è±¡ |
| | | const orderStatisticsObject = ref({ |
| | | totalOrderCount: 0, |
| | | uncompletedOrderCount: 0, |
| | | partialCompletedOrderCount: 0, |
| | | completedOrderCount: 0, |
| | | }) |
| | | |
| | | // å¨å¶åå¨è½¬ç»è®¡å¯¹è±¡ |
| | | const workInProcessStatistics = ref({ |
| | | totalQuantity: 0, |
| | | avgTurnoverDays: 0, |
| | | turnoverEfficiency: 0, |
| | | }) |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '150%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | } |
| | | const barSeries = ref([ |
| | | { |
| | | name: 'åºä»éé¢', |
| | | type: 'bar', |
| | | data: [], |
| | | label: { |
| | | show: true, |
| | | }, |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: '#00A4ED' }, |
| | | { offset: 1, color: '#4EE4FF' } |
| | | ]) |
| | | } |
| | | }, |
| | | { |
| | | name: 'åºæ¶éé¢', |
| | | type: 'bar', |
| | | data: [], |
| | | label: { |
| | | show: true, |
| | | }, |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: '#537EF5' }, |
| | | { offset: 1, color: '#9061F8' } |
| | | ]) |
| | | } |
| | | } |
| | | ]) |
| | | const radio1 = ref(1) |
| | | const barColors2 = ['#5181DB', '#D369E0', '#F2CA6D', '#60CCA8'] |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | } |
| | | const lineLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['å¼ç¥¨', '忬¾'] |
| | | } |
| | | const lineSeries = ref([ |
| | | { |
| | | type: 'line', |
| | | data: [], |
| | | label: { |
| | | show: true |
| | | }, |
| | | showSymbol: true, // æ¾ç¤ºåç¹ |
| | | }, |
| | | ]) |
| | | const tooltipLine = { |
| | | trigger: 'axis', |
| | | } |
| | | const yAxis2 = ref([ |
| | | { |
| | | type: 'value', |
| | | } |
| | | ]) |
| | | const xAxis2 = ref([ |
| | | { |
| | | type: 'category', |
| | | data: [], |
| | | axisLabel: { |
| | | interval: 0, |
| | | formatter: function(value) { |
| | | return value.replace(/~/g, '\n'); |
| | | }, |
| | | } |
| | | } |
| | | ]) |
| | | const barLegend2 = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['åºä»éé¢', 'åºæ¶éé¢'] |
| | | } |
| | | const barLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['åææåæ ¼æ°', 'è¿ç¨åæ ¼æ°', 'åºä¸åæ ¼æ°'] |
| | | } |
| | | const barLegend1 = { |
| | | show: false, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: [] |
| | | } |
| | | const barSeries11 = ref([ |
| | | { |
| | | name: 'ç产订åç»è®¡', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | // 使ç¨å½æ°æ ¹æ®æ°æ®ç´¢å¼è¿åä¸åé¢è² |
| | | color: function(params) { |
| | | const colorStops = [ |
| | | [ |
| | | { offset: 1, color: '#00A4ED' }, |
| | | { offset: 0, color: '#4EE4FF' } |
| | | ], |
| | | [ |
| | | { offset: 1, color: '#3378FF' }, |
| | | { offset: 0, color: '#4E8AFF' } |
| | | ], |
| | | [ |
| | | { offset: 1, color: '#FF6B6B' }, |
| | | { offset: 0, color: '#FF8E8E' } |
| | | ], |
| | | [ |
| | | { offset: 1, color: '#537EF5' }, |
| | | { offset: 0, color: '#9061F8' } |
| | | ] |
| | | ] |
| | | const stops = colorStops[params.dataIndex] || colorStops[0] |
| | | return { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: stops |
| | | } |
| | | } |
| | | }, |
| | | data: [] |
| | | } |
| | | ]) |
| | | const barSeries1 = ref([ |
| | | { |
| | | name: 'åææåæ ¼æ°', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: '#00A4ED' }, |
| | | { offset: 0, color: '#4EE4FF' } |
| | | ] |
| | | } |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'è¿ç¨åæ ¼æ°', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: '#3378FF' }, |
| | | { offset: 0, color: '#4E8AFF' } |
| | | ] |
| | | } |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: 'åºååæ ¼æ°', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: '#537EF5' }, |
| | | { offset: 0, color: '#9061F8' } |
| | | ] |
| | | } |
| | | }, |
| | | data: [] |
| | | }, |
| | | ]) |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | }, |
| | | formatter: function (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 xAxis = [{ |
| | | type: 'value', |
| | | }] |
| | | const yAxis = [{ |
| | | type: 'category', |
| | | data: ['åºæ¶åºä»ç»è®¡'] |
| | | }] |
| | | const xAxis1 = ref([{ |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | data: [] |
| | | }]) |
| | | const yAxis1 = [{ |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' } |
| | | }] |
| | | const xAxis3 = ref([{ |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | data: [] |
| | | }]) |
| | | const yAxis3 = [{ |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' } |
| | | }] |
| | | |
| | | // å¨å¶åå·¥åºæ±ç¶å¾é
ç½® |
| | | const workInProcessXAxis = ref([{ |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | data: [] |
| | | }]) |
| | | const workInProcessYAxis = [{ |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | name: '' |
| | | }] |
| | | const workInProcessBarLegend = { |
| | | show: false, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: [] |
| | | } |
| | | const workInProcessBarSeries = ref([ |
| | | { |
| | | name: 'å¨å¶åæ°é', |
| | | type: 'bar', |
| | | barWidth: 25, // åºå®æ±ç¶å¾å®½åº¦ä¸º40px |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 0, color: '#4EE4FF' }, |
| | | { offset: 1, color: '#00A4ED' } |
| | | ] |
| | | } |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | color: '#B8C8E0' |
| | | }, |
| | | data: [] |
| | | } |
| | | ]) |
| | | |
| | | // å¾
åäºé¡¹ |
| | | const todoList = ref([]) |
| | | |
| | | // ç产订å宿è¿åº¦è¡¨æ ¼æ°æ® |
| | | const progressTableData = ref([]) |
| | | |
| | | // 计ç®å®æè¿åº¦ç¾åæ¯ |
| | | const calculateProgress = (item) => { |
| | | if (!item) return 0 |
| | | // ä¼å
使ç¨completionStatusåæ®µ |
| | | if (item.completionStatus !== undefined && item.completionStatus !== null) { |
| | | const percentage = Number(item.completionStatus) |
| | | if (isNaN(percentage)) return 0 |
| | | return Math.min(Math.max(Math.round(percentage), 0), 100) |
| | | } |
| | | // å¦ææ²¡æcompletionStatusï¼åæ ¹æ®å®ææ°éåéæ±æ°éè®¡ç® |
| | | if (!item.quantity || item.quantity === 0) return 0 |
| | | const percentage = (item.completeQuantity || 0) / item.quantity * 100 |
| | | return Math.min(Math.max(Math.round(percentage), 0), 100) |
| | | } |
| | | |
| | | // æ ¹æ®è¿åº¦ç¾åæ¯è¿åé¢è² |
| | | const progressColor = (percentage) => { |
| | | const p = percentage || 0 |
| | | if (p < 30) return "#f56c6c" |
| | | if (p < 50) return "#e6a23c" |
| | | if (p < 80) return "#409eff" |
| | | return "#67c23a" |
| | | } |
| | | |
| | | // 计ç®ç¼©æ¾æ¯ä¾ |
| | | 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) |
| | | |
| | | // 触åå¾è¡¨resize |
| | | charts.value.forEach(chart => { |
| | | if (chart && chart.resize) { |
| | | chart.resize() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // çªå£å¤§å°ååå¤ç |
| | | const handleResize = () => { |
| | | // å»¶è¿æ§è¡ï¼ç¡®ä¿DOMæ´æ°å®æ |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 100) |
| | | } |
| | | |
| | | // 鿝å¾è¡¨å®ä¾ |
| | | const disposeCharts = () => { |
| | | charts.value.forEach(chart => { |
| | | if (chart && chart.dispose) { |
| | | chart.dispose() |
| | | } |
| | | }) |
| | | charts.value = [] |
| | | } |
| | | // ååéé¢ |
| | | const analysisCustomer = () => { |
| | | analysisCustomerContractAmounts().then((res) => { |
| | | sum.value = res.data.sum |
| | | yny.value = res.data.yny |
| | | chain.value = res.data.chain |
| | | // 为æ¯ä¸ªæ°æ®é¡¹åé
éæºé¢è² |
| | | materialPieSeries.value[0].data = res.data.item.map(item => ({ |
| | | ...item, |
| | | itemStyle: { color: getRandomColor() } |
| | | })) |
| | | }) |
| | | } |
| | | // å¨å¶åå¨è½¬ç»è®¡ |
| | | const workInProcessTurnoverInfo = () => { |
| | | getWorkInProcessTurnover().then((res) => { |
| | | console.log("å¨å¶åå¨è½¬ç»è®¡æ°æ®:", res) |
| | | |
| | | if (!res || !res.data) { |
| | | console.warn('å¨å¶åå¨è½¬ç»è®¡æ°æ®ä¸ºç©º') |
| | | return |
| | | } |
| | | |
| | | // 仿¥å£è·åç»è®¡æ°æ® |
| | | workInProcessStatistics.value = { |
| | | totalQuantity: res.data.totalOrderCount || 0, |
| | | avgTurnoverDays: res.data.averageTurnoverDays || 0, |
| | | turnoverEfficiency: res.data.turnoverEfficiency || 0, |
| | | } |
| | | |
| | | // è®¾ç½®å·¥åºæ±ç¶å¾æ°æ® |
| | | // Xè½´ï¼processDetails (å·¥åºè¯¦æ
æ°ç») |
| | | // Yè½´ï¼processQuantityDetails (å·¥åºæ°é详æ
æ°ç») |
| | | if (res.data.processDetails && Array.isArray(res.data.processDetails)) { |
| | | // 设置Xè½´æ°æ®ï¼å·¥åºåç§°ï¼ |
| | | workInProcessXAxis.value[0].data = res.data.processDetails |
| | | } else { |
| | | workInProcessXAxis.value[0].data = [] |
| | | } |
| | | |
| | | if (res.data.processQuantityDetails && Array.isArray(res.data.processQuantityDetails)) { |
| | | // 设置Yè½´æ°æ®ï¼å¨å¶åæ°éï¼ |
| | | workInProcessBarSeries.value[0].data = res.data.processQuantityDetails |
| | | } else { |
| | | workInProcessBarSeries.value[0].data = [] |
| | | } |
| | | }).catch((error) => { |
| | | console.error('è·åå¨å¶åå¨è½¬ç»è®¡å¤±è´¥:', error) |
| | | }) |
| | | } |
| | | // è´¨æ£ç»è®¡ |
| | | const qualityStatisticsInfo = () => { |
| | | qualityStatistics().then((res) => { |
| | | res.data.item.forEach(item => { |
| | | xAxis1.value[0].data.push(item.date) |
| | | barSeries1.value[0].data.push(item.supplierNum) |
| | | barSeries1.value[1].data.push(item.processNum) |
| | | barSeries1.value[2].data.push(item.factoryNum) |
| | | }) |
| | | qualityStatisticsObject.value.supplierNum = res.data.supplierNum |
| | | qualityStatisticsObject.value.processNum = res.data.processNum |
| | | qualityStatisticsObject.value.factoryNum = res.data.factoryNum |
| | | }) |
| | | } |
| | | // åç产订åç宿è¿åº¦ç»è®¡ |
| | | const progressStatisticsInfo = () => { |
| | | // ä»ç»è®¡æ¥å£è·åç»è®¡æ°æ® |
| | | getProgressStatistics().then((res) => { |
| | | console.log("ç产订å宿è¿åº¦ç»è®¡æ°æ®:", res) |
| | | |
| | | if (!res || !res.data) { |
| | | console.warn('ç产订å宿è¿åº¦ç»è®¡æ°æ®ä¸ºç©º') |
| | | return |
| | | } |
| | | |
| | | // 仿¥å£è·åç»è®¡æ°æ® |
| | | orderStatisticsObject.value = { |
| | | totalOrderCount: res.data.totalOrderCount || 0, |
| | | uncompletedOrderCount: res.data.uncompletedOrderCount || 0, |
| | | partialCompletedOrderCount: res.data.partialCompletedOrderCount || 0, |
| | | completedOrderCount: res.data.completedOrderCount || 0 |
| | | } |
| | | progressTableData.value = res.data.completedOrderDetails || [] |
| | | // éç½®è¡å¼ç¨ |
| | | tableRowRefs.value = [] |
| | | rowsUnderHeader.value.clear() |
| | | |
| | | // å¨è·åå°æ°æ®åï¼åå§åæ»å¨åè½ |
| | | nextTick(() => { |
| | | initProgressTableScroll() |
| | | }) |
| | | }).catch((error) => { |
| | | console.error('è·åç产订å宿è¿åº¦ç»è®¡å¤±è´¥:', error) |
| | | }) |
| | | } |
| | | // è´¢å¡ç»è®¡ |
| | | // const accountStatisticsInfo = () => { |
| | | // listPageAnalysis().then((res) => { |
| | | // xAxis3.value[0].data = res.data.days |
| | | // barSeries11.value[0].data = res.data.totalIncome |
| | | // }) |
| | | // } |
| | | const getNum = () => { |
| | | const params = { |
| | | pageNum: -1, |
| | | pageSize: -1, |
| | | } |
| | | staffOnJobListPage({...params, staffState: 1}).then(res => { |
| | | totalStaff.value = res.data.total |
| | | }) |
| | | listCustomer(params).then((res) => { |
| | | totalCustomers.value = res.total; |
| | | }); |
| | | listSupplier(params).then((res) => { |
| | | totalSuppliers.value = res.data.total |
| | | }); |
| | | } |
| | | const getLedgerNum = () => { |
| | | const params = { |
| | | pageNum: -1, |
| | | pageSize: -1, |
| | | } |
| | | getLedgerPage(params).then((res) => { |
| | | equipmentNum.value = res.data.total |
| | | }); |
| | | getRepairPage({...params, status:0}).then((res) => { |
| | | equipmentRepair.value = res.data.total |
| | | }); |
| | | getUpkeepPage({...params, status:0}).then((res) => { |
| | | equipmentMaintain.value = res.data.total |
| | | }); |
| | | measuringInstrumentListPage(params).then((res) => { |
| | | totalMeasuring.value = res.data.total |
| | | }); |
| | | } |
| | | // å¾
åäºé¡¹ |
| | | const todoInfoS = () => { |
| | | homeTodos().then((res) => { |
| | | todoList.value = res.data |
| | | // å¨è·åå°å¾
åäºé¡¹æ°æ®åï¼åå§åæ»å¨åè½ |
| | | nextTick(() => { |
| | | initTodoListScroll() |
| | | }) |
| | | }) |
| | | } |
| | | // åºä»åºæ¶ç»è®¡ |
| | | const statisticsReceivable = (type) => { |
| | | statisticsReceivablePayable({type: radio1.value}).then((res) => { |
| | | // 设置åºä»é颿°æ® |
| | | barSeries.value[0].data = [ |
| | | { value: res.data.payableMoney } |
| | | ] |
| | | // è®¾ç½®åºæ¶é颿°æ® |
| | | barSeries.value[1].data = [ |
| | | { value: res.data.receivableMoney } |
| | | ] |
| | | }) |
| | | } |
| | | const getAmountHalfYearNum = async () => { |
| | | const res = await getAmountHalfYear() |
| | | console.log(res) |
| | | const monthName = [] |
| | | const receiptAmount = [] |
| | | const invoiceAmount = [] |
| | | res.data.forEach(item => { |
| | | monthName.push(item.month) |
| | | receiptAmount.push(item.receiptAmount) |
| | | invoiceAmount.push(item.invoiceAmount) |
| | | }) |
| | | // æ£ç¡®ååºå¼èµå¼ï¼å建æ°ç xAxis å series 对象 |
| | | xAxis2.value[0].data = monthName |
| | | xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~')); |
| | | lineSeries.value = [ |
| | | { |
| | | name: 'å¼ç¥¨', |
| | | type: 'line', |
| | | data: receiptAmount, |
| | | stack: 'Total', |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(131, 207, 255, 1)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(186, 228, 255, 1)' |
| | | } |
| | | ]) |
| | | }, |
| | | itemStyle: { |
| | | color: '#2D99FF', |
| | | borderColor: '#2D99FF' |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | showSymbol: true, |
| | | }, |
| | | { |
| | | name: '忬¾', |
| | | type: 'line', |
| | | data: invoiceAmount, |
| | | stack: 'Total', |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | itemStyle: { |
| | | color: '#83CFFF', |
| | | borderColor: '#83CFFF' |
| | | }, |
| | | showSymbol: true, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(54, 153, 255, 1)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(89, 169, 254, 1)' |
| | | } |
| | | ]) |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | } |
| | | ] |
| | | } |
| | | |
| | | // èªå¨è½®æ¢å¨ãæãå£åº¦ç宿¶å¨ |
| | | const autoSwitchTimer = ref(null) |
| | | |
| | | // 设置è¡å¼ç¨ |
| | | const setRowRef = (el, index) => { |
| | | if (el) { |
| | | tableRowRefs.value[index] = el |
| | | } |
| | | } |
| | | |
| | | // å¤æè¡æ¯å¦å¨è¡¨å¤´ä¸æ¹ |
| | | const isRowUnderHeader = (index) => { |
| | | return rowsUnderHeader.value.has(index) |
| | | } |
| | | |
| | | // å¤çè¡¨æ ¼æ»å¨äºä»¶ |
| | | const handleTableScroll = () => { |
| | | const tableContainer = progressTableRef.value |
| | | if (!tableContainer) return |
| | | |
| | | const thead = tableContainer.querySelector('thead') |
| | | if (!thead) return |
| | | |
| | | const theadHeight = thead.offsetHeight |
| | | const containerRect = tableContainer.getBoundingClientRect() |
| | | const containerTop = containerRect.top |
| | | const theadBottom = containerTop + theadHeight |
| | | |
| | | // æ¸
空ä¹åçè®°å½ |
| | | rowsUnderHeader.value.clear() |
| | | |
| | | // æ£æ¥æ¯ä¸è¡æ¯å¦å¨è¡¨å¤´ä¸æ¹ï¼è¢«è¡¨å¤´é®æ¡ï¼ |
| | | tableRowRefs.value.forEach((row, index) => { |
| | | if (row) { |
| | | const rowRect = row.getBoundingClientRect() |
| | | const rowTop = rowRect.top |
| | | const rowBottom = rowRect.bottom |
| | | |
| | | // 妿è¡ä¸è¡¨å¤´æéå ï¼è¡å¨è¡¨å¤´ä¸æ¹è¢«é®æ¡ï¼ |
| | | // è¡çé¡¶é¨å¨è¡¨å¤´åºé¨ä¸æ¹ï¼ä½è¡çåºé¨å¨è¡¨å¤´åºé¨ä¸æ¹ï¼è¯´æè¢«é®æ¡ |
| | | if (rowTop < theadBottom && rowBottom > containerTop) { |
| | | rowsUnderHeader.value.add(index) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // æ¸
é¤ä¹åç宿¶å¨ |
| | | if (tableScrollTimeout.value) { |
| | | clearTimeout(tableScrollTimeout.value) |
| | | } |
| | | |
| | | // æ»å¨åæ¢åæ¸
ç©ºæ·¡åæ è®° |
| | | tableScrollTimeout.value = setTimeout(() => { |
| | | rowsUnderHeader.value.clear() |
| | | }, 150) |
| | | } |
| | | |
| | | // åå§åç产订åè¿åº¦è¡¨æ ¼æ»å¨åè½ |
| | | const initProgressTableScroll = () => { |
| | | const tableContainer = progressTableRef.value |
| | | if (!tableContainer) return |
| | | |
| | | // æ¸
çä¹åçæ»å¨å¨ç»å宿¶å¨ |
| | | if (progressTableScrollTimer.value) { |
| | | cancelAnimationFrame(progressTableScrollTimer.value) |
| | | progressTableScrollTimer.value = null |
| | | } |
| | | if (tableContainer._pauseTimer) { |
| | | clearInterval(tableContainer._pauseTimer) |
| | | tableContainer._pauseTimer = null |
| | | } |
| | | |
| | | const tbody = tableContainer.querySelector('tbody') |
| | | if (!tbody) return |
| | | |
| | | // æ¸
çä¹åå¯è½åå¨çå
éè¡ï¼ä¿çåå§æ°æ®è¡ï¼ |
| | | // åå§æ°æ®è¡çæ°éåºè¯¥çäº progressTableData.value.length |
| | | const originalCount = progressTableData.value.length |
| | | const allRows = Array.from(tbody.querySelectorAll('tr')) |
| | | if (allRows.length > originalCount) { |
| | | // ç§»é¤ææè¶
è¿åå§æ°éçè¡ï¼è¿äºæ¯å
éçè¡ï¼ |
| | | for (let i = originalCount; i < allRows.length; i++) { |
| | | allRows[i].remove() |
| | | } |
| | | } |
| | | |
| | | const scrollItems = Array.from(tbody.querySelectorAll('tr')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | // è·ååå§æ°æ®é¡¹æ°é |
| | | const originalItemCount = scrollItems.length |
| | | |
| | | // 计ç®å®¹å¨é«åº¦å表头é«åº¦ |
| | | const thead = tableContainer.querySelector('thead') |
| | | const theadHeight = thead ? thead.offsetHeight : 40 |
| | | const containerHeight = tableContainer.clientHeight |
| | | const visibleHeight = containerHeight - theadHeight |
| | | |
| | | // 计ç®åå§æ°æ®çæ»é«åº¦ |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 40 |
| | | const totalContentHeight = itemHeight * originalItemCount |
| | | |
| | | // å¦ææ°æ®éä¸å¤ï¼å®¹å¨å¯ä»¥å®å
¨æ¾ç¤ºæææ°æ®ï¼å°±ä¸éè¦æ»å¨åå
é |
| | | if (totalContentHeight <= visibleHeight) { |
| | | // æ°æ®éå°ï¼ä¸éè¦æ»å¨ï¼ç´æ¥è¿å |
| | | return |
| | | } |
| | | |
| | | // æ°æ®éè¶³å¤ï¼éè¦æ»å¨ï¼è¿è¡å
é以å®ç°æ ç¼æ»å¨ |
| | | const cloneCount = Math.ceil(visibleHeight / itemHeight) + 2 |
| | | |
| | | // å
éåå 个项ç®å¹¶æ·»å å°å表æ«å°¾ï¼å®ç°æ ç¼æ»å¨ |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % originalItemCount].cloneNode(true) |
| | | tbody.appendChild(clone) |
| | | } |
| | | |
| | | let scrollPosition = 0 |
| | | const scrollSpeed = 1.5 |
| | | const pauseTime = 3000 |
| | | let isPaused = false |
| | | let lastTimestamp = 0 |
| | | |
| | | // è¿ç»æ»å¨å¨ç»å½æ° |
| | | function scrollAnimation(timestamp) { |
| | | if (!lastTimestamp) lastTimestamp = timestamp |
| | | const deltaTime = timestamp - lastTimestamp |
| | | lastTimestamp = timestamp |
| | | |
| | | if (!isPaused) { |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) |
| | | |
| | | // è®¡ç®æå¤§æ»å¨ä½ç½®ï¼åå§å
容çé«åº¦ï¼ |
| | | const maxScroll = itemHeight * originalItemCount |
| | | |
| | | // 彿»å¨è¶
è¿åå§å
容é¿åº¦æ¶ï¼éç½®ä½ç½®å®ç°æ ç¼æ»å¨ |
| | | if (scrollPosition >= maxScroll) { |
| | | scrollPosition = 0 |
| | | tableContainer.scrollTop = 0 |
| | | } else { |
| | | tableContainer.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | |
| | | progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | |
| | | // å¯å¨æ»å¨å¨ç» |
| | | progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation) |
| | | |
| | | // 设置æ»å¨-æå-æ»å¨çå¾ªç¯ææ |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | |
| | | // æ¸
ç宿¶å¨ |
| | | tableContainer._pauseTimer = pauseTimer |
| | | } |
| | | |
| | | // åå§åå¾
åäºé¡¹å表æ»å¨åè½ |
| | | const initTodoListScroll = () => { |
| | | const todoList = refTodoList.value |
| | | // 强å¶å¯ç¨æ»å¨ï¼ä¸æ£æ¥ä»»ä½æ¡ä»¶ |
| | | if (todoList) { |
| | | // å建ä¸ä¸ªå
é项ï¼ç¨äºå®ç°æ ç¼æ»å¨ |
| | | const scrollItems = Array.from(todoList.querySelectorAll('li')) |
| | | if (scrollItems.length > 0) { |
| | | // ç¡®ä¿æè¶³å¤ç项ç®ç¨äºæ»å¨ |
| | | // 妿项ç®å¤ªå°ï¼å¤å¤å¶å æ¬¡ä»¥ç¡®ä¿æ»å¨ææ |
| | | if (scrollItems.length < 4) { |
| | | const originalItems = [...scrollItems] |
| | | for (let i = 0; i < 4; i++) { |
| | | originalItems.forEach(item => { |
| | | const clone = item.cloneNode(true) |
| | | todoList.appendChild(clone) |
| | | }) |
| | | } |
| | | // éæ°è·åææé¡¹ç® |
| | | scrollItems.push(...Array.from(todoList.querySelectorAll('li')).slice(scrollItems.length)); |
| | | } |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 0 |
| | | const containerHeight = todoList.clientHeight |
| | | const cloneCount = Math.ceil(containerHeight / itemHeight) + 2 |
| | | |
| | | // å
éåå 个项ç®å¹¶æ·»å å°å表æ«å°¾ï¼å®ç°æ ç¼æ»å¨ |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % scrollItems.length].cloneNode(true) |
| | | todoList.appendChild(clone) |
| | | } |
| | | |
| | | let scrollPosition = 0 |
| | | const scrollSpeed = 1.5 // å¢å æ»å¨é度ï¼ä½¿æ»å¨æ´å ææ¾ |
| | | const pauseTime = 3000 // æ»å¨æåæ¶é´ |
| | | let isPaused = false |
| | | let lastTimestamp = 0 |
| | | |
| | | // è¿ç»æ»å¨å¨ç»å½æ° |
| | | function scrollAnimation(timestamp) { |
| | | if (!lastTimestamp) lastTimestamp = timestamp |
| | | const deltaTime = timestamp - lastTimestamp |
| | | lastTimestamp = timestamp |
| | | |
| | | if (!isPaused) { |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) // æ åå为60fpsçé度 |
| | | |
| | | // 彿»å¨è¶
è¿åå§å
容é¿åº¦æ¶ï¼éç½®ä½ç½®å®ç°æ ç¼æ»å¨ |
| | | const maxScroll = Math.max(todoList.scrollHeight - containerHeight - cloneCount * itemHeight, itemHeight * scrollItems.length) |
| | | if (scrollPosition >= maxScroll) { |
| | | scrollPosition = 0 |
| | | todoList.scrollTop = 0 |
| | | } else { |
| | | todoList.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | |
| | | todoList._animationFrame = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | |
| | | // å¯å¨æ»å¨å¨ç» |
| | | todoList._animationFrame = requestAnimationFrame(scrollAnimation) |
| | | |
| | | // 设置æ»å¨-æå-æ»å¨çå¾ªç¯ææ |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | |
| | | // æ¸
ç宿¶å¨ |
| | | todoList._pauseTimer = pauseTimer |
| | | } |
| | | } |
| | | } |
| | | const getRandomColor = () => { |
| | | // çææµ
è²ï¼RãGãB åéé½å¨ 150-255 ä¹é´ |
| | | const r = Math.floor(Math.random() * 106) + 150; // 150-255 |
| | | const g = Math.floor(Math.random() * 106) + 150; // 150-255 |
| | | const b = Math.floor(Math.random() * 106) + 150; // 150-255 |
| | | // å° RGB 转æ¢ä¸ºåå
è¿å¶é¢è² |
| | | return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0'); |
| | | } |
| | | |
| | | // æ´æ°æ¶é´ |
| | | const updateTime = () => { |
| | | const now = new Date() |
| | | currentTime.value = now.toLocaleTimeString('zh-CN', { hour12: false }) |
| | | currentDate.value = now.toLocaleDateString('zh-CN', { |
| | | year: 'numeric', |
| | | month: '2-digit', |
| | | day: '2-digit', |
| | | weekday: 'long' |
| | | }) |
| | | } |
| | | |
| | | // åå§åæ¶é´ |
| | | const initTime = () => { |
| | | updateTime() |
| | | timer.value = setInterval(updateTime, 1000) |
| | | } |
| | | // å
¨å±åè½å®ç° - é对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(() => { |
| | | initTime() |
| | | // 使ç¨nextTickç¡®ä¿DOMå®å
¨æ¸²æåååå§åå¾è¡¨ |
| | | nextTick(() => { |
| | | // 计ç®åå§ç¼©æ¾æ¯ä¾ |
| | | calculateScale() |
| | | |
| | | // åå§åautofitèªéåºï¼å¦æéè¦ä¿çautofitï¼å¯ä»¥ä¿çï¼ä½ä¸»è¦ç¼©æ¾ç±scale-containeræ§å¶ï¼ |
| | | // autofit.init({ dh: 800, dw: 1280, el: '.data-dashboard', resize: true }, false) |
| | | |
| | | // æ·»å èªå¨æ»å¨å¨ç»ææ - 客æ·ä¿¡æ¯å表 |
| | | const contractList = refContractList.value |
| | | if (contractList && contractList.scrollHeight > contractList.clientHeight) { |
| | | // å建ä¸ä¸ªå
é项ï¼ç¨äºå®ç°æ ç¼æ»å¨ |
| | | const scrollItems = Array.from(contractList.querySelectorAll('li')) |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 0 |
| | | const containerHeight = contractList.clientHeight |
| | | const cloneCount = Math.ceil(containerHeight / itemHeight) + 2 |
| | | |
| | | // å
éåå 个项ç®å¹¶æ·»å å°å表æ«å°¾ï¼å®ç°æ ç¼æ»å¨ |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % scrollItems.length].cloneNode(true) |
| | | contractList.appendChild(clone) |
| | | } |
| | | |
| | | let scrollPosition = 0 |
| | | const scrollSpeed = 1.5 // å¢å æ»å¨é度ï¼ä½¿æ»å¨æ´å ææ¾ |
| | | const pauseTime = 3000 // æ»å¨æåæ¶é´ |
| | | let isPaused = false |
| | | let lastTimestamp = 0 |
| | | |
| | | // è¿ç»æ»å¨å¨ç»å½æ° |
| | | function scrollAnimation(timestamp) { |
| | | if (!lastTimestamp) lastTimestamp = timestamp |
| | | const deltaTime = timestamp - lastTimestamp |
| | | lastTimestamp = timestamp |
| | | |
| | | if (!isPaused) { |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) // æ åå为60fpsçé度 |
| | | |
| | | // 彿»å¨è¶
è¿åå§å
容é¿åº¦æ¶ï¼éç½®ä½ç½®å®ç°æ ç¼æ»å¨ |
| | | if (scrollPosition >= contractList.scrollHeight - containerHeight - cloneCount * itemHeight) { |
| | | scrollPosition = 0 |
| | | contractList.scrollTop = 0 |
| | | } else { |
| | | contractList.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | |
| | | timerScroll.value = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | |
| | | // å¯å¨æ»å¨å¨ç» |
| | | timerScroll.value = requestAnimationFrame(scrollAnimation) |
| | | |
| | | // 设置æ»å¨-æå-æ»å¨çå¾ªç¯ææ |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | |
| | | // æ¸
ç宿¶å¨ |
| | | contractList._pauseTimer = pauseTimer |
| | | } |
| | | |
| | | // å¾
åäºé¡¹å表æ»å¨åè½å·²ç§»è³todoInfoS彿°ä¸ï¼å¨è·åæ°æ®ååå§å |
| | | }) |
| | | |
| | | window.addEventListener('resize', handleResize) |
| | | window.addEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | analysisCustomer() |
| | | workInProcessTurnoverInfo() |
| | | qualityStatisticsInfo() |
| | | // accountStatisticsInfo() |
| | | progressStatisticsInfo() |
| | | getNum() |
| | | getLedgerNum() |
| | | todoInfoS() |
| | | statisticsReceivable() |
| | | getAmountHalfYearNum() |
| | | |
| | | // 设置èªå¨è½®æ¢å¨ãæãå£åº¦ç宿¶å¨ï¼æ¯10ç§åæ¢ä¸æ¬¡ |
| | | autoSwitchTimer.value = setInterval(() => { |
| | | // 循ç¯åæ¢ï¼1(å¨) -> 2(æ) -> 3(å£åº¦) -> 1(å¨) |
| | | radio1.value = radio1.value === 3 ? 1 : radio1.value + 1 |
| | | statisticsReceivable() |
| | | }, 10000) // 10ç§åæ¢ä¸æ¬¡ |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (timer.value) { |
| | | clearInterval(timer.value) |
| | | } |
| | | if (timerScroll.value) { |
| | | cancelAnimationFrame(timerScroll.value) |
| | | } |
| | | // æ¸
çæ»å¨å表çæå宿¶å¨ |
| | | const contractList = refContractList.value |
| | | if (contractList && contractList._pauseTimer) { |
| | | clearInterval(contractList._pauseTimer) |
| | | } |
| | | |
| | | // æ¸
çå¾
åäºé¡¹å表çå¨ç»å宿¶å¨ |
| | | const todoList = refTodoList.value |
| | | if (todoList) { |
| | | if (todoList._animationFrame) { |
| | | cancelAnimationFrame(todoList._animationFrame) |
| | | todoList._animationFrame = null |
| | | } |
| | | if (todoList._pauseTimer) { |
| | | clearInterval(todoList._pauseTimer) |
| | | todoList._pauseTimer = null |
| | | } |
| | | } |
| | | |
| | | // æ¸
çç产订åè¿åº¦è¡¨æ ¼çå¨ç»å宿¶å¨ |
| | | const progressTable = progressTableRef.value |
| | | if (progressTable) { |
| | | if (progressTableScrollTimer.value) { |
| | | cancelAnimationFrame(progressTableScrollTimer.value) |
| | | progressTableScrollTimer.value = null |
| | | } |
| | | if (progressTable._pauseTimer) { |
| | | clearInterval(progressTable._pauseTimer) |
| | | progressTable._pauseTimer = null |
| | | } |
| | | } |
| | | |
| | | // æ¸
çè¡¨æ ¼æ»å¨å®æ¶å¨ |
| | | if (tableScrollTimeout.value) { |
| | | clearTimeout(tableScrollTimeout.value) |
| | | tableScrollTimeout.value = null |
| | | } |
| | | |
| | | // æ¸
çèªå¨è½®æ¢å¨ãæãå£åº¦ç宿¶å¨ |
| | | if (autoSwitchTimer.value) { |
| | | clearInterval(autoSwitchTimer.value) |
| | | autoSwitchTimer.value = null |
| | | } |
| | | |
| | | 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 |
| | | } |
| | | disposeCharts() |
| | | // å
³é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; |
| | | } |
| | | .panel-item-customers { |
| | | border: 1px solid #1A58B0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 540px; |
| | | } |
| | | .panel-title-second { |
| | | height: 60px; |
| | | display: flex; |
| | | gap: 12px; |
| | | margin-bottom: 20px; |
| | | align-items: center; |
| | | } |
| | | .quality-cards { |
| | | display: flex; |
| | | gap: 12px; |
| | | width: 100%; |
| | | height: 54px; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .quality-cardSec { |
| | | display: flex; |
| | | } |
| | | .quality-cardTitle { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | display: flex; |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | } |
| | | .quality-card { |
| | | width: 80px; |
| | | height: 60px; |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | .quality-card.one { |
| | | background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png"); |
| | | } |
| | | .quality-card.two { |
| | | background-image: url("@/assets/BI/guochengyijianicon@2x.png"); |
| | | } |
| | | .quality-card.three { |
| | | background-image: url("@/assets/BI/chuchangyijianicon@2x.png"); |
| | | } |
| | | |
| | | /* 订åç»è®¡å¡çæ ·å¼ */ |
| | | .order-statistics-cards { |
| | | display: flex; |
| | | gap: 12px; |
| | | width: 100%; |
| | | height: 94px; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .quality-card.four { |
| | | background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png"); |
| | | } |
| | | |
| | | .quality-card.five { |
| | | background-image: url("@/assets/BI/guochengyijianicon@2x.png"); |
| | | } |
| | | |
| | | .quality-card.six { |
| | | background-image: url("@/assets/BI/chuchangyijianicon@2x.png"); |
| | | } |
| | | |
| | | .quality-card.seven { |
| | | background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png"); |
| | | } |
| | | .panel-title-icon { |
| | | width: 60px; |
| | | height: 60px; |
| | | background-image: url("@/assets/BI/hetongicon.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | .total-customers { |
| | | background-image: url("@/assets/BI/hetongjineback@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | width: 90%; |
| | | height: 60px; |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 0 20px; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .total-customers .label { |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .total-customers .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; |
| | | } |
| | | |
| | | .contract-list { |
| | | margin-top: 16px; |
| | | font-size: 14px; |
| | | color: #666; |
| | | list-style: none; |
| | | padding: 0; |
| | | height: 82%; |
| | | overflow-y: auto; |
| | | width: 460px; |
| | | /* éèæ»å¨æ¡ä½ä¿çæ»å¨åè½ */ |
| | | scrollbar-width: none; /* Firefox */ |
| | | -ms-overflow-style: none; /* IEåEdge */ |
| | | } |
| | | |
| | | /* ChromeãSafariåOpera */ |
| | | .contract-list::-webkit-scrollbar { |
| | | display: none; |
| | | } |
| | | .line { |
| | | position: relative; |
| | | width: 230px; |
| | | } |
| | | .line::after { |
| | | content: ''; |
| | | position: absolute; |
| | | right: 2px; |
| | | top: 0; |
| | | bottom: 0; |
| | | width: 1px; |
| | | background-color: #C9C5C5; |
| | | border-radius: 2px; |
| | | } |
| | | .contract-list li { |
| | | margin-top: 10px; |
| | | } |
| | | .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); |
| | | } |
| | | |
| | | .equipment-stats { |
| | | border: 1px solid #1A58B0; |
| | | padding: 18px; |
| | | height: 240px; |
| | | } |
| | | .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: 21px; |
| | | 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; |
| | | } |
| | | .equipment-items { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .equipment-item { |
| | | text-align: center; |
| | | } |
| | | |
| | | .equipment-value { |
| | | display: block; |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | color: #FFFFFF; |
| | | width: 120px; |
| | | height: 110px; |
| | | line-height: 110px; |
| | | background-image: url("@/assets/BI/shujutongji@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .equipment-label { |
| | | font-weight: 500; |
| | | font-size: 21px; |
| | | color: #FFFFFE; |
| | | } |
| | | |
| | | .event-info { |
| | | background-image: url("@/assets/BI/shijianmingchengbeijing@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | padding: 20px; |
| | | height: 186px; |
| | | } |
| | | .event-header { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | .event-icon { |
| | | width: 40px; |
| | | height: 40px; |
| | | } |
| | | .event-title { |
| | | font-weight: 500; |
| | | font-size: 24px; |
| | | color: #FFFFFE; |
| | | line-height: 30px; |
| | | } |
| | | .todo-list { |
| | | list-style: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | height: 120px; /* æç¨æ·è¦æ±è°æ´é«åº¦ */ |
| | | overflow: hidden; |
| | | font-size: 15px; |
| | | } |
| | | .todo-list li { |
| | | border-radius: 8px; |
| | | margin-bottom: 12px; |
| | | padding: 12px 40px; |
| | | height: 74px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .todo-title { |
| | | font-weight: 400; |
| | | font-size: 20px; |
| | | color: #FFFFFE; |
| | | position: relative; |
| | | } |
| | | .todo-title::before { |
| | | content: ''; /* å¿
éï¼è¡¨ç¤ºè¿éæä¸ä¸ªå
容 */ |
| | | position: absolute; |
| | | left: -10px; /* å®ä½å°å·¦ä¾§ */ |
| | | top: 50%; /* åç´å±
ä¸ */ |
| | | transform: translateY(-50%); /* å¾®è°åç´å±
ä¸ */ |
| | | width: 6px; /* åçç´å¾ */ |
| | | height: 6px; /* åçç´å¾ */ |
| | | background: #498CEB; |
| | | border-radius: 50%; /* 让å
¶åæåå½¢ */ |
| | | } |
| | | .todo-division { |
| | | font-weight: 400; |
| | | font-size: 20px; |
| | | color: #FFFFFE; |
| | | } |
| | | .todo-time { |
| | | font-weight: 400; |
| | | font-size: 20px; |
| | | color: #FFFFFE; |
| | | } |
| | | .financial-header { |
| | | background-image: url("@/assets/BI/caiwufenxiback@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | .financial-title { |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #D9ECFF; |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | } |
| | | |
| | | /* èªå®ä¹åéæé®ç»æ ·å¼ */ |
| | | .custom-radio-group :deep(.el-radio-button__inner) { |
| | | background-color: transparent; |
| | | color: white; |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | } |
| | | |
| | | .custom-radio-group :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background-color: rgba(255, 255, 255, 0.2); |
| | | color: white; |
| | | border-color: rgba(255, 255, 255, 0.5); |
| | | box-shadow: -1px 0 0 0 rgba(255, 255, 255, 0.5); |
| | | } |
| | | |
| | | /* ç产订åè¿åº¦è¡¨æ ¼æ ·å¼ */ |
| | | .progress-table-container { |
| | | height: 200px; |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | margin-top: 10px; |
| | | scrollbar-width: none; /* Firefox */ |
| | | -ms-overflow-style: none; /* IEåEdge */ |
| | | } |
| | | |
| | | .progress-table-container::-webkit-scrollbar { |
| | | display: none; /* ChromeãSafariåOpera */ |
| | | } |
| | | |
| | | .progress-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | color: #B8C8E0; |
| | | font-size: 12px; |
| | | table-layout: fixed; |
| | | } |
| | | |
| | | .progress-table thead { |
| | | position: sticky; |
| | | top: 0; |
| | | background-color: rgba(26, 88, 176, 0.9); |
| | | z-index: 10; |
| | | } |
| | | |
| | | .progress-table th { |
| | | padding: 8px 6px; |
| | | text-align: left; |
| | | font-weight: 500; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.3); |
| | | color: #B8C8E0; |
| | | font-size: 12px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .progress-table th:nth-child(1) { width: 15%; } /* ç产订åå· */ |
| | | .progress-table th:nth-child(2) { width: 15%; } /* 产ååç§° */ |
| | | .progress-table th:nth-child(3) { width: 15%; } /* è§æ ¼ */ |
| | | .progress-table th:nth-child(4) { width: 12%; } /* éæ±æ°é */ |
| | | .progress-table th:nth-child(5) { width: 12%; } /* 宿æ°é */ |
| | | .progress-table th:nth-child(6) { width: 31%; } /* 宿è¿åº¦ */ |
| | | |
| | | .progress-table td { |
| | | padding: 8px 6px; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.1); |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | font-size: 12px; |
| | | transition: opacity 0.3s ease; |
| | | } |
| | | |
| | | .progress-table tbody tr:hover { |
| | | background-color: rgba(184, 200, 224, 0.1); |
| | | } |
| | | |
| | | .progress-table tbody tr.row-under-header { |
| | | opacity: 0.5; |
| | | } |
| | | |
| | | /* el-progress ç»ä»¶æ ·å¼è°æ´ */ |
| | | .progress-table :deep(.el-progress) { |
| | | width: 100%; |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress-bar__outer) { |
| | | background-color: rgba(184, 200, 224, 0.2); |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress__text) { |
| | | color: #B8C8E0; |
| | | font-size: 11px; |
| | | } |
| | | </style> |
| | |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="!isApproved(scope.row.status)" |
| | | :disabled="isApproving(scope.row.status)" |
| | | @click="openForm('edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | :disabled="!isApproved(scope.row.status)" |
| | | :disabled="isApproving(scope.row.status)" |
| | | @click="handleDeleteSingle(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = async (type, row) => { |
| | | // ç¼è¾æ¶æ£æ¥å®¡æ ¸ç¶æ |
| | | if (type === 'edit' && row && !isApproved(row.status)) { |
| | | proxy.$modal.msgWarning("åªè½ç¼è¾å®¡æ ¸éè¿çæ°æ®"); |
| | | // ç¼è¾æ¶æ£æ¥å®¡æ ¸ç¶æï¼åªæå®¡æ ¸ä¸ä¸è½ç¼è¾ |
| | | if (type === 'edit' && row && isApproving(row.status)) { |
| | | proxy.$modal.msgWarning("å®¡æ ¸ä¸çæ°æ®ä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | |
| | |
| | | return; |
| | | } |
| | | |
| | | // æ£æ¥éä¸çè¡æ¯å¦é½æ¯"å®¡æ ¸éè¿"ç¶æ |
| | | const notApprovedRows = selectedRows.value.filter(row => !isApproved(row.status)); |
| | | if (notApprovedRows.length > 0) { |
| | | proxy.$modal.msgWarning("åªè½å é¤å®¡æ ¸éè¿çæ°æ®"); |
| | | // æ£æ¥éä¸çè¡æ¯å¦æ"å®¡æ ¸ä¸"ç¶æ |
| | | const approvingRows = selectedRows.value.filter(row => isApproving(row.status)); |
| | | if (approvingRows.length > 0) { |
| | | proxy.$modal.msgWarning("å®¡æ ¸ä¸çæ°æ®ä¸è½å é¤"); |
| | | return; |
| | | } |
| | | |
| | |
| | | |
| | | // å个å é¤ |
| | | const handleDeleteSingle = (row) => { |
| | | // æ£æ¥æ¯å¦ä¸º"å®¡æ ¸éè¿"ç¶æ |
| | | if (!isApproved(row.deliveryLedger)) { |
| | | proxy.$modal.msgWarning("åªè½å é¤å®¡æ ¸éè¿çæ°æ®"); |
| | | // æ£æ¥æ¯å¦ä¸º"å®¡æ ¸ä¸"ç¶æ |
| | | if (isApproving(row.status)) { |
| | | proxy.$modal.msgWarning("å®¡æ ¸ä¸çæ°æ®ä¸è½å é¤"); |
| | | return; |
| | | } |
| | | |
| | |
| | | return statusStr === 'å®¡æ ¸éè¿' || statusStr === '3'; |
| | | }; |
| | | |
| | | // æ£æ¥å®¡æ ¸ç¶ææ¯å¦ä¸º"å®¡æ ¸ä¸" |
| | | const isApproving = (status) => { |
| | | if (status === null || status === undefined || status === '') { |
| | | return false; |
| | | } |
| | | // å¦ææ¯æ°åï¼1 è¡¨ç¤ºå®¡æ ¸ä¸ |
| | | if (typeof status === 'number') { |
| | | return status === 1; |
| | | } |
| | | // 妿æ¯å符串 |
| | | const statusStr = String(status).trim(); |
| | | return statusStr === 'å®¡æ ¸ä¸' || statusStr === '1'; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | |
| | | <template> |
| | | <div class="app-container indicator-stats"> |
| | | <el-card class="box-card"> |
| | | <!-- KPI æ±æ» --> |
| | | <el-row :gutter="20" class="stats-row"> |
| | | <el-col :span="8"> |
| | | <div class="stat-card"> |
| | | <div class="stat-icon" style="background: #ecf5ff;"> |
| | | <el-icon :size="30" color="#409eff"><Document /></el-icon> |
| | | </div> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">{{ indicatorKpis.orderCount.toLocaleString() }}</div> |
| | | <div class="stat-label">è®¢åæ°é</div> |
| | | <!-- KPI æ±æ» --> |
| | | <el-row :gutter="20" class="stats-row"> |
| | | <el-col :xs="24" :sm="12" :md="8"> |
| | | <div class="stat-card stat-card-blue"> |
| | | <div class="stat-icon-wrapper"> |
| | | <div class="stat-icon"> |
| | | <el-icon :size="32"><Document /></el-icon> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="stat-card"> |
| | | <div class="stat-icon" style="background: #f0f9ff;"> |
| | | <el-icon :size="30" color="#67c23a"><Tickets /></el-icon> |
| | | </div> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">Â¥{{ indicatorKpis.salesAmount.toLocaleString() }}</div> |
| | | <div class="stat-label">éå®é¢</div> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">{{ indicatorKpis.orderCount.toLocaleString() }}</div> |
| | | <div class="stat-label">è®¢åæ°é</div> |
| | | </div> |
| | | <div class="stat-bg-decoration"></div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8"> |
| | | <div class="stat-card stat-card-green"> |
| | | <div class="stat-icon-wrapper"> |
| | | <div class="stat-icon"> |
| | | <el-icon :size="32"><Tickets /></el-icon> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="stat-card"> |
| | | <div class="stat-icon" style="background: #fef0f0;"> |
| | | <el-icon :size="30" color="#e6a23c"><Van /></el-icon> |
| | | </div> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">{{ indicatorKpis.shipRate }}%</div> |
| | | <div class="stat-label">åè´§ç</div> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">Â¥{{ indicatorKpis.salesAmount.toLocaleString() }}</div> |
| | | <div class="stat-label">éå®é¢</div> |
| | | </div> |
| | | <div class="stat-bg-decoration"></div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8"> |
| | | <div class="stat-card stat-card-orange"> |
| | | <div class="stat-icon-wrapper"> |
| | | <div class="stat-icon"> |
| | | <el-icon :size="32"><Van /></el-icon> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">{{ indicatorKpis.shipRate }}%</div> |
| | | <div class="stat-label">åè´§ç</div> |
| | | </div> |
| | | <div class="stat-bg-decoration"></div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 维度çé --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-tree-select v-model="indicatorFilter.productCategory" placeholder="产åç±»å«" clearable check-strictly |
| | | :data="productOptions" :render-after-expand="false" style="width: 100%" /> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="indicatorFilter.customerName" placeholder="客æ·" clearable filterable> |
| | | <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName" /> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-date-picker v-model="indicatorFilter.dateRange" type="daterange" range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" end-placeholder="ç»ææ¥æ" value-format="YYYY-MM-DD" style="width: 100%" /> |
| | | </el-col> |
| | | <el-col :span="6" style="text-align: right;"> |
| | | <el-button type="primary" @click="applyIndicatorFilter">æ¥è¯¢</el-button> |
| | | <el-button @click="resetIndicatorFilter">éç½®</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- å¾è¡¨åº --> |
| | | <div class="chart-container"> |
| | | <div ref="indicatorChartRef" class="chart-wrapper"></div> |
| | | <!-- å¾è¡¨åºï¼å
å«çéæ¡ä»¶ï¼ --> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <div class="header-left"> |
| | | <span class="card-title">éå®è¶å¿åæ</span> |
| | | <span class="card-subtitle">ç鿡件ä»
å½±å䏿¹å¾è¡¨æ°æ®</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- å¾è¡¨ç鿡件 --> |
| | | <div class="chart-filter-section"> |
| | | <el-row :gutter="16" class="search-row"> |
| | | <el-col :xs="24" :sm="12" :md="6"> |
| | | <div class="filter-item"> |
| | | <label class="filter-label">产åç±»å«</label> |
| | | <el-tree-select |
| | | v-model="indicatorFilter.productCategory" |
| | | placeholder="è¯·éæ©äº§åç±»å«" |
| | | clearable |
| | | check-strictly |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | /> |
| | | </div> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="6"> |
| | | <div class="filter-item"> |
| | | <label class="filter-label">客æ·</label> |
| | | <el-select |
| | | v-model="indicatorFilter.customerName" |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in customerOption" |
| | | :key="item.id" |
| | | :label="item.customerName" |
| | | :value="item.customerName" |
| | | /> |
| | | </el-select> |
| | | </div> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="6"> |
| | | <div class="filter-item"> |
| | | <label class="filter-label">æ¥æèå´</label> |
| | | <el-date-picker |
| | | v-model="indicatorFilter.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | </div> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="6"> |
| | | <div class="filter-item filter-buttons"> |
| | | <el-button type="primary" :loading="loading" @click="applyIndicatorFilter"> |
| | | <el-icon><Search /></el-icon> |
| | | æ¥è¯¢å¾è¡¨ |
| | | </el-button> |
| | | <el-button @click="resetIndicatorFilter"> |
| | | <el-icon><Refresh /></el-icon> |
| | | éç½® |
| | | </el-button> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- ä¸ç»©ç»è®¡ï¼å¢éç»´åº¦ï¼æ 个人å§åï¼ --> |
| | | <el-table v-if="showTeamPerformance" :data="teamPerformanceList" border stripe style="margin-top: 20px;"> |
| | | <el-table-column prop="team" label="éå®å¢é"/> |
| | | <el-table-column prop="orderCount" label="è®¢åæ°"/> |
| | | <el-table-column prop="salesAmount" label="éå®é¢"> |
| | | <!-- å¾è¡¨å±ç¤ºåº --> |
| | | <div class="chart-container" v-loading="loading"> |
| | | <div ref="indicatorChartRef" class="chart-wrapper"></div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ä¸ç»©ç»è®¡ï¼å¢éç»´åº¦ï¼æ 个人å§åï¼ --> |
| | | <el-card v-if="showTeamPerformance" class="table-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">å¢éä¸ç»©ç»è®¡</span> |
| | | </div> |
| | | </template> |
| | | <el-table |
| | | :data="teamPerformanceList" |
| | | border |
| | | stripe |
| | | style="width: 100%" |
| | | :header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }" |
| | | > |
| | | <el-table-column prop="team" label="éå®å¢é" min-width="120"/> |
| | | <el-table-column prop="orderCount" label="è®¢åæ°" align="right" min-width="100"/> |
| | | <el-table-column prop="salesAmount" label="éå®é¢" align="right" min-width="140"> |
| | | <template #default="scope">Â¥{{ scope.row.salesAmount.toLocaleString() }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="shipRate" label="åè´§ç"> |
| | | <template #default="scope">{{ scope.row.shipRate }}</template> |
| | | <el-table-column prop="shipRate" label="åè´§ç" align="right" min-width="100"> |
| | | <template #default="scope">{{ scope.row.shipRate }}%</template> |
| | | </el-table-column> |
| | | <el-table-column prop="attainment" label="ç®æ è¾¾æç"> |
| | | <el-table-column prop="attainment" label="ç®æ è¾¾æç" align="center" min-width="120"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.attainment >= 100 ? 'success' : scope.row.attainment >= 80 ? 'warning' : 'danger'"> |
| | | <el-tag |
| | | :type="scope.row.attainment >= 100 ? 'success' : scope.row.attainment >= 80 ? 'warning' : 'danger'" |
| | | effect="dark" |
| | | > |
| | | {{ scope.row.attainment }}% |
| | | </el-tag> |
| | | </template> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue' |
| | | import { Document, Van, Tickets } from '@element-plus/icons-vue' |
| | | import { Document, Van, Tickets, Search, Refresh } from '@element-plus/icons-vue' |
| | | import * as echarts from 'echarts' |
| | | import { getTotalStatistics, getStatisticsTable } from '@/api/salesManagement/indicatorStats' |
| | | import { productTreeList } from '@/api/basicData/product.js' |
| | |
| | | } |
| | | |
| | | const applyIndicatorFilter = async () => { |
| | | await Promise.all([ |
| | | fetchTotalStatistics(), |
| | | fetchStatisticsTable() |
| | | ]) |
| | | // ç鿡件åªå½±åå¾è¡¨æ°æ®ï¼ä¸å½±åKPIæ±æ» |
| | | await fetchStatisticsTable() |
| | | } |
| | | |
| | | const resetIndicatorFilter = () => { |
| | |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | <style scoped lang="scss"> |
| | | .indicator-stats { |
| | | padding: 0; |
| | | padding: 20px; |
| | | background: #f5f7fa; |
| | | min-height: calc(100vh - 84px); |
| | | } |
| | | .box-card { border: none; box-shadow: none; } |
| | | .search-row { margin-bottom: 20px; } |
| | | .stats-row { margin-bottom: 24px; } |
| | | .stat-card { display: flex; align-items: center; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } |
| | | .stat-icon { width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; border-radius: 8px; margin-right: 16px; } |
| | | .stat-content { flex: 1; } |
| | | .stat-value { font-size: 28px; font-weight: bold; color: #303133; margin-bottom: 4px; } |
| | | .stat-label { font-size: 14px; color: #909399; } |
| | | .chart-container { |
| | | margin: 20px 0; |
| | | padding: 20px; |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | |
| | | .page-header { |
| | | margin-bottom: 24px; |
| | | padding: 20px 0; |
| | | |
| | | .page-title { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin: 0 0 8px 0; |
| | | } |
| | | |
| | | .page-desc { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin: 0; |
| | | } |
| | | } |
| | | |
| | | .stats-row { |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .stat-card { |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 24px; |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); |
| | | transition: all 0.3s ease; |
| | | overflow: hidden; |
| | | |
| | | &:hover { |
| | | transform: translateY(-4px); |
| | | box-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.12); |
| | | } |
| | | |
| | | .stat-icon-wrapper { |
| | | margin-right: 20px; |
| | | |
| | | .stat-icon { |
| | | width: 64px; |
| | | height: 64px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 12px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | } |
| | | |
| | | .stat-content { |
| | | flex: 1; |
| | | z-index: 1; |
| | | |
| | | .stat-value { |
| | | font-size: 32px; |
| | | font-weight: 700; |
| | | color: #303133; |
| | | margin-bottom: 8px; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | |
| | | .stat-bg-decoration { |
| | | position: absolute; |
| | | right: -20px; |
| | | top: -20px; |
| | | width: 120px; |
| | | height: 120px; |
| | | border-radius: 50%; |
| | | opacity: 0.1; |
| | | z-index: 0; |
| | | } |
| | | |
| | | &.stat-card-blue { |
| | | .stat-icon { |
| | | background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | .stat-bg-decoration { |
| | | background: #409eff; |
| | | } |
| | | } |
| | | |
| | | &.stat-card-green { |
| | | .stat-icon { |
| | | background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | .stat-bg-decoration { |
| | | background: #67c23a; |
| | | } |
| | | } |
| | | |
| | | &.stat-card-orange { |
| | | .stat-icon { |
| | | background: linear-gradient(135deg, #e6a23c 0%, #ebb563 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | .stat-bg-decoration { |
| | | background: #e6a23c; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chart-card, |
| | | .table-card { |
| | | margin-bottom: 20px; |
| | | border-radius: 12px; |
| | | border: none; |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 18px 20px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%); |
| | | } |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 0; |
| | | } |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | |
| | | .header-left { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .card-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .card-subtitle { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | font-weight: normal; |
| | | } |
| | | } |
| | | |
| | | .chart-filter-section { |
| | | padding: 20px; |
| | | background: #fafbfc; |
| | | border-bottom: 1px solid #ebeef5; |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .search-row { |
| | | .filter-item { |
| | | margin-bottom: 0; |
| | | |
| | | .filter-label { |
| | | display: block; |
| | | font-size: 13px; |
| | | color: #606266; |
| | | margin-bottom: 8px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | &.filter-buttons { |
| | | display: flex; |
| | | align-items: flex-end; |
| | | gap: 10px; |
| | | padding-top: 28px; |
| | | |
| | | .el-button { |
| | | flex: 1; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chart-container { |
| | | width: 100%; |
| | | overflow: hidden; |
| | | position: relative; |
| | | padding: 20px; |
| | | background: #fff; |
| | | |
| | | .chart-wrapper { |
| | | width: 100%; |
| | | height: 420px; |
| | | min-width: 0; |
| | | } |
| | | } |
| | | .chart-wrapper { |
| | | width: 100%; |
| | | height: 360px; |
| | | min-width: 0; |
| | | |
| | | .table-card { |
| | | :deep(.el-table) { |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | :deep(.el-table__header-wrapper) { |
| | | .el-table__header { |
| | | th { |
| | | background: #f5f7fa; |
| | | color: #606266; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | } |
| | | |
| | | :deep(.el-table__body-wrapper) { |
| | | .el-table__body { |
| | | tr:hover { |
| | | background-color: #f5f7fa; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ååºå¼è®¾è®¡ |
| | | @media (max-width: 768px) { |
| | | .indicator-stats { |
| | | padding: 12px; |
| | | } |
| | | |
| | | .stat-card { |
| | | padding: 20px; |
| | | |
| | | .stat-content .stat-value { |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .stat-icon-wrapper .stat-icon { |
| | | width: 56px; |
| | | height: 56px; |
| | | } |
| | | } |
| | | |
| | | .chart-filter-section { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .search-row { |
| | | .filter-item.filter-buttons { |
| | | padding-top: 0; |
| | | margin-top: 12px; |
| | | } |
| | | } |
| | | |
| | | .chart-container { |
| | | padding: 16px; |
| | | |
| | | .chart-wrapper { |
| | | height: 320px; |
| | | } |
| | | } |
| | | |
| | | .card-header { |
| | | .header-left { |
| | | .card-title { |
| | | font-size: 15px; |
| | | } |
| | | |
| | | .card-subtitle { |
| | | font-size: 11px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 576px) { |
| | | .page-header { |
| | | .page-title { |
| | | font-size: 20px; |
| | | } |
| | | |
| | | .page-desc { |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | |
| | | .stat-card { |
| | | flex-direction: column; |
| | | text-align: center; |
| | | |
| | | .stat-icon-wrapper { |
| | | margin-right: 0; |
| | | margin-bottom: 12px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | |
| | |
| | | <el-input v-model="searchForm.customerName" placeholder="请è¾å
¥" clearable prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·ååå·ï¼"> |
| | | <el-input v-model="searchForm.customerContractNo" placeholder="请è¾å
¥" clearable prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="éå®ååå·ï¼"> |
| | | <el-input v-model="searchForm.salesContractNo" placeholder="请è¾å
¥" clearable prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | |
| | | type="danger">ä¸è¶³</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åè´§ç¶æ" prop="shippingStatus" width="140" align="center" show-overflow-tooltip /> |
| | | <el-table-column label="åè´§ç¶æ" width="140" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getShippingStatusType(scope.row)" size="small"> |
| | | {{ getShippingStatusText(scope.row) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å¿«éå
¬å¸" prop="expressCompany" show-overflow-tooltip /> |
| | | <el-table-column label="å¿«éåå·" prop="expressNumber" show-overflow-tooltip /> |
| | | <el-table-column label="å货车ç" minWidth="100px" align="center"> |
| | | <template #default="scope"> |
| | | <div> |
| | | <el-tag type="success" v-if="scope.row.shippingCarNumber">{{ scope.row.shippingCarNumber }}</el-tag> |
| | | <el-tag v-else type="info">æªåè´§</el-tag> |
| | | <el-tag v-else type="info">-</el-tag> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="scope.row.approveStatus !== 1 || !!scope.row.shippingDate || !!scope.row.shippingCarNumber" |
| | | size="small" |
| | | :disabled="!canShip(scope.row)" |
| | | @click="openDeliveryForm(scope.row)"> |
| | | åè´§ |
| | | </el-button> |
| | |
| | | </el-table-column> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="éå®ååå·" prop="salesContractNo" width="180" show-overflow-tooltip /> |
| | | <el-table-column label="客æ·ååå·" prop="customerContractNo" width="180" show-overflow-tooltip /> |
| | | <el-table-column label="客æ·åç§°" prop="customerName" width="300" show-overflow-tooltip /> |
| | | <el-table-column label="ä¸å¡å" prop="salesman" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="项ç®åç§°" prop="projectName" width="180" show-overflow-tooltip /> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·ååå·ï¼" prop="customerContractNo"> |
| | | <el-input v-model="form.customerContractNo" placeholder="请è¾å
¥" clearable :disabled="operationType === 'view'"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°ï¼" prop="customerId"> |
| | | <el-select v-model="form.customerId" placeholder="è¯·éæ©" clearable :disabled="operationType === 'view'"> |
| | | <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"> |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°ï¼" prop="projectName"> |
| | | <el-input v-model="form.projectName" placeholder="请è¾å
¥" clearable :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°ï¼" prop="projectName"> |
| | | <el-input v-model="form.projectName" placeholder="请è¾å
¥" clearable :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¾è®¢æ¥æï¼" prop="executionDate"> |
| | | <el-date-picker style="width: 100%" v-model="form.executionDate" 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="仿¬¾æ¹å¼"> |
| | | <el-input v-model="form.paymentMethod" placeholder="请è¾å
¥" clearable :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row> |
| | | <el-form-item label="产åä¿¡æ¯ï¼" prop="entryDate"> |
| | | <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">æ·»å </el-button> |
| | |
| | | // å 载失败æ¶ä¿æå¯ç¼è¾ï¼ä¸ä¸æå¼¹çª |
| | | console.error("å 载产åè§æ ¼åå·å¤±è´¥", e); |
| | | } |
| | | } else { |
| | | getProductOptions() |
| | | } |
| | | productFormVisible.value = true; |
| | | }; |
| | |
| | | isCalculating.value = false; |
| | | }; |
| | | /** |
| | | * è·ååè´§ç¶æææ¬ |
| | | * @param row è¡æ°æ® |
| | | */ |
| | | const getShippingStatusText = (row) => { |
| | | // 妿已åè´§ï¼æåè´§æ¥ææè½¦çå·ï¼ï¼æ¾ç¤º"å·²åè´§" |
| | | if (row.shippingDate || row.shippingCarNumber) { |
| | | return 'å·²åè´§'; |
| | | } |
| | | |
| | | // è·ååè´§ç¶æåæ®µ |
| | | const status = row.shippingStatus; |
| | | |
| | | // å¦æç¶æä¸ºç©ºææªå®ä¹ï¼é»è®¤ä¸º"å¾
åè´§" |
| | | if (status === null || status === undefined || status === '') { |
| | | return 'å¾
åè´§'; |
| | | } |
| | | |
| | | // ç¶ææ¯å符串 |
| | | const statusStr = String(status).trim(); |
| | | const statusTextMap = { |
| | | 'å¾
åè´§': 'å¾
åè´§', |
| | | 'å¾
å®¡æ ¸': 'å¾
å®¡æ ¸', |
| | | 'å®¡æ ¸ä¸': 'å®¡æ ¸ä¸', |
| | | 'å®¡æ ¸æç»': 'å®¡æ ¸æç»', |
| | | 'å®¡æ ¸éè¿': 'å®¡æ ¸éè¿', |
| | | 'å·²åè´§': 'å·²åè´§' |
| | | }; |
| | | return statusTextMap[statusStr] || 'å¾
åè´§'; |
| | | }; |
| | | |
| | | /** |
| | | * è·ååè´§ç¶ææ ç¾ç±»åï¼é¢è²ï¼ |
| | | * @param row è¡æ°æ® |
| | | */ |
| | | const getShippingStatusType = (row) => { |
| | | // 妿已åè´§ï¼æåè´§æ¥ææè½¦çå·ï¼ï¼æ¾ç¤ºç»¿è² |
| | | if (row.shippingDate || row.shippingCarNumber) { |
| | | return 'success'; |
| | | } |
| | | |
| | | // è·ååè´§ç¶æåæ®µ |
| | | const status = row.shippingStatus; |
| | | |
| | | // å¦æç¶æä¸ºç©ºææªå®ä¹ï¼é»è®¤ä¸ºç°è²ï¼å¾
åè´§ï¼ |
| | | if (status === null || status === undefined || status === '') { |
| | | return 'info'; |
| | | } |
| | | |
| | | // ç¶ææ¯å符串 |
| | | const statusStr = String(status).trim(); |
| | | const typeTextMap = { |
| | | 'å¾
åè´§': 'info', |
| | | 'å¾
å®¡æ ¸': 'info', |
| | | 'å®¡æ ¸ä¸': 'warning', |
| | | 'å®¡æ ¸æç»': 'danger', |
| | | 'å®¡æ ¸éè¿': 'success', |
| | | 'å·²åè´§': 'success' |
| | | }; |
| | | return typeTextMap[statusStr] || 'info'; |
| | | }; |
| | | |
| | | /** |
| | | * 夿æ¯å¦å¯ä»¥åè´§ |
| | | * åªæå¨äº§åç¶ææ¯å
è¶³ï¼åè´§ç¶ææ¯å¾
åè´§åå®¡æ ¸æç»çæ¶åæå¯ä»¥åè´§ |
| | | * @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 openDeliveryForm = (row) => { |
| | | // æ ¡éªï¼åªæäº§åç¶æä¸ºå
è¶³ä¸æªåè´§æ¶æè½åè´§ |
| | | if (row.approveStatus !== 1) { |
| | | proxy.$modal.msgWarning("产åç¶æä¸è¶³ï¼æ æ³åè´§"); |
| | | // æ£æ¥æ¯å¦å¯ä»¥åè´§ |
| | | if (!canShip(row)) { |
| | | proxy.$modal.msgWarning("åªæå¨äº§åç¶ææ¯å
è¶³ï¼åè´§ç¶ææ¯å¾
åè´§æå®¡æ ¸æç»çæ¶åæå¯ä»¥åè´§"); |
| | | return; |
| | | } |
| | | if (row.shippingDate || row.shippingCarNumber) { |
| | | proxy.$modal.msgWarning("该产åå·²åè´§ï¼æ æ³éå¤åè´§"); |
| | | return; |
| | | } |
| | | |
| | | currentDeliveryRow.value = row; |
| | | deliveryForm.value = { |
| | | type: "货车", |