Merge remote-tracking branch 'origin/dev_pro_河南鹤壁' into dev_pro_河南鹤壁_泽淇实业
# Conflicts:
# src/views/productionManagement/productionReporting/index.vue
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <FormDialog |
| | | v-model="dialogVisitable" |
| | | title="ä¿å
»è®°å½è¯¦æ
" |
| | | width="800px" |
| | | operation-type="detail" |
| | | @close="cancel" |
| | | > |
| | | <el-descriptions border :column="2"> |
| | | <el-descriptions-item label="设å¤åç§°"> |
| | | {{ detailData.deviceName || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="è§æ ¼åå·"> |
| | | {{ detailData.deviceModel || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="计åä¿å
»æ¥æ"> |
| | | {{ formatDate(detailData.maintenancePlanTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å½å
¥äºº"> |
| | | {{ detailData.createUserName || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¿å
»é¡¹ç®" :span="2"> |
| | | {{ detailData.machineryCategory || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å®é
ä¿å
»äºº"> |
| | | {{ detailData.maintenanceActuallyName || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å®é
ä¿å
»æ¥æ"> |
| | | {{ formatDate(detailData.maintenanceActuallyTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¿å
ȍȾ" :span="2"> |
| | | {{ detailData.maintenanceResult || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç¶æ" :span="2"> |
| | | <el-tag v-if="detailData.status === 2" type="danger">失败</el-tag> |
| | | <el-tag v-else-if="detailData.status === 1" type="success">å®ç»</el-tag> |
| | | <el-tag v-else type="warning">å¾
ä¿å
»</el-tag> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div v-if="fileList && fileList.length > 0" class="image-section"> |
| | | <div class="image-title">ä¿å
»éä»¶(å¾ç)ï¼</div> |
| | | <AttachmentUploadImage |
| | | v-model:fileList="fileList" |
| | | :disabled="true" |
| | | /> |
| | | </div> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { ref } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { attachmentList } from "@/api/basicData/storageAttachment.js"; |
| | | import AttachmentUploadImage from '@/components/AttachmentUpload/image/index.vue'; |
| | | |
| | | const dialogVisitable = ref(false); |
| | | const detailData = ref({}); |
| | | const fileList = ref([]); |
| | | |
| | | const formatDate = (date) => { |
| | | return date ? dayjs(date).format("YYYY-MM-DD") : "--"; |
| | | }; |
| | | |
| | | const openDialog = (row) => { |
| | | dialogVisitable.value = true; |
| | | detailData.value = { ...row }; |
| | | fileList.value = []; |
| | | |
| | | if (row.id) { |
| | | attachmentList({ |
| | | recordType: 'device_maintenance', |
| | | recordId: row.id, |
| | | }).then(res => { |
| | | if (res && res.data) { |
| | | fileList.value = res.data || []; |
| | | } |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .image-section { |
| | | margin-top: 20px; |
| | | } |
| | | .image-title { |
| | | font-weight: bold; |
| | | margin-bottom: 10px; |
| | | color: #606266; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <FormDialog |
| | | v-model="dialogVisitable" |
| | | title="ä¿å
»ä»»å¡è¯¦æ
" |
| | | width="800px" |
| | | operation-type="detail" |
| | | @close="cancel" |
| | | > |
| | | <el-descriptions border :column="2"> |
| | | <el-descriptions-item label="设å¤åç§°"> |
| | | {{ detailData.taskName || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="è§æ ¼åå·"> |
| | | {{ detailData.deviceModel || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å½å
¥äºº"> |
| | | {{ detailData.registrant || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç»è®°æ¶é´"> |
| | | {{ detailData.registrationDate || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¿å
»é¡¹ç®" :span="2"> |
| | | {{ detailData.machineryCategory || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¿å
»äºº"> |
| | | {{ detailData.maintenancePerson || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä»»å¡é¢ç"> |
| | | {{ formatFrequencyType(detailData.frequencyType) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æ§è¡æ¶é´"> |
| | | {{ formatFrequencyDetail(detailData.frequencyDetail) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="宿¶ä»»å¡"> |
| | | <el-tag :type="detailData.isActive === 1 ? 'success' : 'info'"> |
| | | {{ detailData.isActive === 1 ? 'å¼å¯' : 'å
³é' }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="夿³¨" :span="2"> |
| | | {{ detailData.remarks || '--' }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div v-if="fileList && fileList.length > 0" class="image-section"> |
| | | <div class="image-title">设å¤å¾çï¼</div> |
| | | <AttachmentUploadImage |
| | | v-model:fileList="fileList" |
| | | :disabled="true" |
| | | /> |
| | | </div> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { ref } from "vue"; |
| | | import { getLedgerById } from "@/api/equipmentManagement/ledger"; |
| | | import AttachmentUploadImage from '@/components/AttachmentUpload/image/index.vue'; |
| | | |
| | | const dialogVisitable = ref(false); |
| | | const detailData = ref({}); |
| | | const fileList = ref([]); |
| | | |
| | | const formatFrequencyType = (type) => { |
| | | const map = { |
| | | DAILY: "æ¯æ¥", |
| | | WEEKLY: "æ¯å¨", |
| | | MONTHLY: "æ¯æ", |
| | | QUARTERLY: "å£åº¦", |
| | | }; |
| | | return map[type] || "--"; |
| | | }; |
| | | |
| | | const formatFrequencyDetail = (detail) => { |
| | | if (!detail) return "--"; |
| | | const replacements = { |
| | | MON: "å¨ä¸", |
| | | TUE: "å¨äº", |
| | | WED: "å¨ä¸", |
| | | THU: "å¨å", |
| | | FRI: "å¨äº", |
| | | SAT: "å¨å
", |
| | | SUN: "卿¥", |
| | | }; |
| | | return detail.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]); |
| | | }; |
| | | |
| | | const openDialog = (row) => { |
| | | dialogVisitable.value = true; |
| | | detailData.value = { ...row }; |
| | | fileList.value = []; |
| | | |
| | | if (row.taskId) { |
| | | getLedgerById(row.taskId).then(res => { |
| | | if (res.code === 200 && res.data) { |
| | | fileList.value = res.data.storageBlobVOs || []; |
| | | } |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .image-section { |
| | | margin-top: 20px; |
| | | } |
| | | .image-title { |
| | | font-weight: bold; |
| | | margin-bottom: 10px; |
| | | color: #606266; |
| | | } |
| | | </style> |
| | |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="handleDetail(row)"> |
| | | 详æ
|
| | | </el-button> |
| | | <el-button type="primary" |
| | | link |
| | | @click="editScheduledTask(row)"> |
| | | ç¼è¾ |
| | | </el-button> |
| | |
| | | type="warning">å¾
ä¿å
»</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="handleRecordDetail(row)"> |
| | | 详æ
|
| | | </el-button> |
| | | <!-- è¿ä¸ªåè½è·æ°å¢ä¿å
»åè½ä¸æ¨¡ä¸æ ·ï¼æå¥æä¹ï¼ --> |
| | | <!-- <el-button |
| | | type="primary" |
| | |
| | | @ok="getTableData" /> |
| | | <FormDia ref="formDiaRef" |
| | | @closeDia="getScheduledTableData" /> |
| | | <DetailDia ref="detailDiaRef" /> |
| | | <RecordDetailDia ref="recordDetailDiaRef" /> |
| | | <FileList v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | :record-type="'device_maintenance'" |
| | |
| | | import PlanModal from "./Form/PlanModal.vue"; |
| | | import MaintenanceModal from "./Form/MaintenanceModal.vue"; |
| | | import FormDia from "./Form/formDia.vue"; |
| | | import DetailDia from "./Form/detailDia.vue"; |
| | | import RecordDetailDia from "./Form/RecordDetailDia.vue"; |
| | | import { |
| | | getUpkeepPage, |
| | | delUpkeep, |
| | |
| | | const maintainModalRef = ref(); |
| | | // 宿¶ä»»å¡å¼¹çªæ§å¶å¨ |
| | | const formDiaRef = ref(); |
| | | // 宿¶ä»»å¡è¯¦æ
å¼¹çªæ§å¶å¨ |
| | | const detailDiaRef = ref(); |
| | | // ä¿å
»è®°å½è¯¦æ
å¼¹çªæ§å¶å¨ |
| | | const recordDetailDiaRef = ref(); |
| | | // éä»¶å¼¹çª |
| | | const fileListDialogRef = ref(null); |
| | | const fileDialogVisible = ref(false); |
| | |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | | minWidth: 50, |
| | | minWidth: 80, |
| | | // PIMTable 使ç¨çæ¯ formatDataï¼è䏿¯ Element-Plus ç formatter |
| | | formatData: cell => |
| | | ({ |
| | |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "150px", |
| | | width: "160px", |
| | | }, |
| | | ]); |
| | | |
| | |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "350px", |
| | | width: "250px", |
| | | }, |
| | | ]); |
| | | |
| | |
| | | if (row) { |
| | | nextTick(() => { |
| | | formDiaRef.value?.openDialog("edit", row); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const handleDetail = row => { |
| | | if (row) { |
| | | nextTick(() => { |
| | | detailDiaRef.value?.openDialog(row); |
| | | }); |
| | | } |
| | | }; |
| | |
| | | maintainModalRef.value.open(row.id, row); |
| | | }; |
| | | |
| | | const handleRecordDetail = row => { |
| | | if (row) { |
| | | nextTick(() => { |
| | | recordDetailDiaRef.value?.openDialog(row); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const addPlan = () => { |
| | | planModalRef.value.openModal(); |
| | | }; |
| | |
| | | { |
| | | label: "æ¥å·¥åå·", |
| | | prop: "productNo", |
| | | width: 120, |
| | | width: 140, |
| | | }, |
| | | { |
| | | label: "æ¥å·¥äººå", |
| | | prop: "nickName", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "å·¥æ¶ï¼hï¼", |
| | | width: 100, |
| | | prop: "workHour", |
| | | }, |
| | | // { |
| | | // label: "å·¥æ¶ï¼hï¼", |
| | | // width: 100, |
| | | // prop: "workHour", |
| | | // }, |
| | | { |
| | | label: "å·¥åº", |
| | | prop: "process", |
| | | width: 120, |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "å·¥åç¼å·", |
| | | prop: "workOrderNo", |
| | | width: 120, |
| | | width: 140, |
| | | }, |
| | | { |
| | | label: "éå®ååå·", |
| | |
| | | <template> |
| | | <div class="panel-header"> |
| | | <div |
| | | class="panel-header" |
| | | :class="{ clickable: !!to }" |
| | | @click="handleClick" |
| | | > |
| | | <span class="panel-title">{{ title }}</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | defineProps({ |
| | | import { useRouter } from 'vue-router' |
| | | |
| | | const props = defineProps({ |
| | | title: { |
| | | type: String, |
| | | required: true, |
| | | default: '' |
| | | }, |
| | | to: { |
| | | type: String, |
| | | default: '' |
| | | } |
| | | }) |
| | | |
| | | const router = useRouter() |
| | | |
| | | const handleClick = () => { |
| | | if (props.to) { |
| | | router.push(props.to) |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | } |
| | | |
| | | .panel-header.clickable { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .panel-header.clickable:hover .panel-title { |
| | | color: #43e8fc; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="åºå
¥åºè¶å¿" /> |
| | | <PanelHeader title="åºå
¥åºè¶å¿" to="/inventoryManagement/receiptManagement" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="filters-row"> |
| | | |
| | |
| | | <div> |
| | | <!-- 设å¤ç»è®¡ --> |
| | | <div class="equipment-stats"> |
| | | <div class="equipment-header"> |
| | | <div class="equipment-header clickable" @click="handleNavigate"> |
| | | <img |
| | | src="@/assets/BI/shujutongjiicon@2x.png" |
| | | alt="徿 " |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { productTurnoverDays } from '@/api/viewIndex.js' |
| | | import { getPsiRoute } from '../psiNavigation.js' |
| | | |
| | | const router = useRouter() |
| | | |
| | | const handleNavigate = () => { |
| | | const path = getPsiRoute('产åå¨è½¬å¤©æ°') |
| | | if (path) { |
| | | router.push(path) |
| | | } |
| | | } |
| | | |
| | | const chartStyle = { width: '100%', height: '100%' } |
| | | const grid = { left: '3%', right: '4%', bottom: '3%', top: '4%', containLabel: true } |
| | |
| | | padding-bottom: 2px; |
| | | } |
| | | |
| | | .equipment-header.clickable { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .equipment-header.clickable:hover .equipment-title { |
| | | opacity: 0.85; |
| | | } |
| | | |
| | | .equipment-title { |
| | | font-weight: 500; |
| | | font-size: 18px; |
| | |
| | | v-for="item in statItems" |
| | | :key="item.name" |
| | | class="stat-card" |
| | | :class="{ clickable: !!getStatRoute(item.name) }" |
| | | @click="handleStatClick(item.name)" |
| | | > |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js' |
| | | import { getPsiRoute } from '../psiNavigation.js' |
| | | |
| | | const router = useRouter() |
| | | const statItems = ref([]) |
| | | |
| | | const getStatRoute = (name) => getPsiRoute(name) |
| | | |
| | | const handleStatClick = (name) => { |
| | | const path = getStatRoute(name) |
| | | if (path) { |
| | | router.push(path) |
| | | } |
| | | } |
| | | |
| | | const formatPercent = (val) => { |
| | | const num = Number(val) || 0 |
| | |
| | | height: 142px; |
| | | } |
| | | |
| | | .stat-card.clickable { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .stat-card.clickable:hover .card-label { |
| | | color: #43e8fc; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="éè´ååå¸" /> |
| | | <PanelHeader title="éè´ååå¸" to="/procurementManagement/procurementLedger" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="éå®ååå¸" /> |
| | | <PanelHeader title="éå®ååå¸" to="/salesManagement/salesLedger" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | |
| | | |
| | | <!-- å³ä¾§åºå --> |
| | | <div class="right-panel"> |
| | | <RightBottom /> |
| | | <RightTop /> |
| | | <RightBottom header-to="/salesManagement/receiptPaymentLedger" /> |
| | | <RightTop header-to="/procurementManagement/paymentLedger" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | export const PSI_ROUTE_MAP = { |
| | | éå®ååå¸: '/salesManagement/salesLedger', |
| | | éè´ååå¸: '/procurementManagement/procurementLedger', |
| | | éå®äº§åæ°: '/salesManagement/salesLedger', |
| | | éè´äº§åæ°: '/procurementManagement/procurementLedger', |
| | | å¨åäº§åæ°: '/inventoryManagement/stockManagement', |
| | | 客æ·è´¡ç®æå: '/salesManagement/receiptPaymentLedger', |
| | | ä¾åºåéè´æå: '/procurementManagement/paymentLedger', |
| | | åºå
¥åºè¶å¿: '/inventoryManagement/receiptManagement', |
| | | 产åå¨è½¬å¤©æ°: '/inventoryManagement/stockManagement', |
| | | } |
| | | |
| | | export function getPsiRoute(name) { |
| | | return PSI_ROUTE_MAP[name] || '' |
| | | } |
| | |
| | | <template> |
| | | <div class="panel-header"> |
| | | <div |
| | | class="panel-header" |
| | | :class="{ clickable: !!to }" |
| | | @click="handleClick" |
| | | > |
| | | <span class="panel-title">{{ title }}</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | defineProps({ |
| | | import { useRouter } from 'vue-router' |
| | | |
| | | const props = defineProps({ |
| | | title: { |
| | | type: String, |
| | | required: true, |
| | | default: '' |
| | | }, |
| | | to: { |
| | | type: String, |
| | | default: '' |
| | | } |
| | | }) |
| | | |
| | | const router = useRouter() |
| | | |
| | | const handleClick = () => { |
| | | if (props.to) { |
| | | router.push(props.to) |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | } |
| | | |
| | | .panel-header.clickable { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .panel-header.clickable:hover .panel-title { |
| | | color: #43e8fc; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="客æ·è´¡ç®æå" /> |
| | | <PanelHeader title="客æ·è´¡ç®æå" :to="headerTo" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="switch-container"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | | import { customerContributionRanking } from '@/api/viewIndex.js' |
| | | |
| | | defineProps({ |
| | | headerTo: { |
| | | type: String, |
| | | default: '' |
| | | } |
| | | }) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="ä¾åºåéè´æå" /> |
| | | <PanelHeader title="ä¾åºåéè´æå" :to="headerTo" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="switch-container"> |
| | | <DateTypeSwitch v-model="radio1" @change="handleDateTypeChange" /> |
| | |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | | import { supplierPurchaseRanking } from '@/api/viewIndex.js' |
| | | |
| | | defineProps({ |
| | | headerTo: { |
| | | type: String, |
| | | default: '' |
| | | } |
| | | }) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | |
| | | <template> |
| | | <div class="panel-header"> |
| | | <div |
| | | class="panel-header" |
| | | :class="{ clickable: !!to }" |
| | | @click="handleClick" |
| | | > |
| | | <span class="panel-title">{{ title }}</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | defineProps({ |
| | | import { useRouter } from 'vue-router' |
| | | |
| | | const props = defineProps({ |
| | | title: { |
| | | type: String, |
| | | required: true, |
| | | default: '' |
| | | }, |
| | | to: { |
| | | type: String, |
| | | default: '' |
| | | } |
| | | }) |
| | | |
| | | const router = useRouter() |
| | | |
| | | const handleClick = () => { |
| | | if (props.to) { |
| | | router.push(props.to) |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | } |
| | | |
| | | .panel-header.clickable { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .panel-header.clickable:hover .panel-title { |
| | | color: #43e8fc; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="ç产订å宿è¿åº¦" /> |
| | | <PanelHeader title="ç产订å宿è¿åº¦" to="/productionManagement/productionOrder" /> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="4" /> |
| | |
| | | <div> |
| | | <!-- 设å¤ç»è®¡ --> |
| | | <div class="equipment-stats"> |
| | | <div class="equipment-header"> |
| | | <div class="equipment-header clickable" @click="handleNavigate"> |
| | | <img |
| | | src="@/assets/BI/shujutongjiicon@2x.png" |
| | | alt="徿 " |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, inject, watch, nextTick } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import * as echarts from 'echarts' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { inputOutputAnalysis } from '@/api/viewIndex.js' |
| | | import DateTypeSwitch from "@/views/reportAnalysis/productionAnalysis/components/DateTypeSwitch.vue"; |
| | | import { getProductionRoute } from '../productionNavigation.js' |
| | | |
| | | const router = useRouter() |
| | | |
| | | const handleNavigate = () => { |
| | | const path = getProductionRoute('æå
¥äº§åºåæ') |
| | | if (path) { |
| | | router.push(path) |
| | | } |
| | | } |
| | | |
| | | const dateType = ref(3) // 1=å¨ 2=æ 3=å£åº¦ |
| | | const chartRef = ref(null) |
| | |
| | | padding-bottom: 2px; |
| | | } |
| | | |
| | | .equipment-header.clickable { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .equipment-header.clickable:hover .equipment-title { |
| | | opacity: 0.85; |
| | | } |
| | | |
| | | .equipment-title { |
| | | font-weight: 500; |
| | | font-size: 18px; |
| | |
| | | v-for="item in statItems" |
| | | :key="item.name" |
| | | class="stat-card" |
| | | :class="{ clickable: !!getStatRoute(item.name) }" |
| | | @click="handleStatClick(item.name)" |
| | | > |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { orderCount } from '@/api/viewIndex.js' |
| | | import { getProductionRoute } from '../productionNavigation.js' |
| | | |
| | | const router = useRouter() |
| | | const statItems = ref([]) |
| | | |
| | | const getStatRoute = (name) => getProductionRoute(name) |
| | | |
| | | const handleStatClick = (name) => { |
| | | const path = getStatRoute(name) |
| | | if (path) { |
| | | router.push(path) |
| | | } |
| | | } |
| | | |
| | | const formatPercent = (val) => { |
| | | const num = Number(val) || 0 |
| | |
| | | height: 142px; |
| | | } |
| | | |
| | | .stat-card.clickable { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .stat-card.clickable:hover .card-label { |
| | | color: #43e8fc; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å¨å¶åç»è®¡åæ" /> |
| | | <PanelHeader title="å¨å¶åç»è®¡åæ" to="/productionManagement/productionOrder" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="chart-wrapper"> |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å·¥åºäº§åºåæ" /> |
| | | <PanelHeader title="å·¥åºäº§åºåæ" to="/productionManagement/processStatistics" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="filters-row"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="çäº§æ ¸ç®åæ" /> |
| | | <PanelHeader title="çäº§æ ¸ç®åæ" to="/productionManagement/productionCosting" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="filters-row"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å·¥åæ§è¡æçåæ" /> |
| | | <PanelHeader title="å·¥åæ§è¡æçåæ" to="/productionManagement/productionManagement/workOrderEdit/index" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="filters-row"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | const PRODUCTION_ORDER_ROUTE = '/productionManagement/productionOrder' |
| | | |
| | | export const PRODUCTION_ROUTE_MAP = { |
| | | å·¥åºäº§åºåæ: '/productionManagement/processStatistics', |
| | | å¨å¶åç»è®¡åæ: PRODUCTION_ORDER_ROUTE, |
| | | çäº§è®¢åæ°: PRODUCTION_ORDER_ROUTE, |
| | | å·²å®æè®¢åæ°: PRODUCTION_ORDER_ROUTE, |
| | | å¾
ç产订å: PRODUCTION_ORDER_ROUTE, |
| | | å¾
çäº§è®¢åæ°: PRODUCTION_ORDER_ROUTE, |
| | | ç产订å宿è¿åº¦: PRODUCTION_ORDER_ROUTE, |
| | | ç产订å宿è¿åº¦æ°: PRODUCTION_ORDER_ROUTE, |
| | | æå
¥äº§åºåæ: '/productionManagement/productionReporting', |
| | | å·¥åæ§è¡æçåæ: '/productionManagement/productionManagement/workOrderEdit/index', |
| | | çäº§æ ¸ç®åæ: '/productionManagement/productionCosting', |
| | | } |
| | | |
| | | export function getProductionRoute(name) { |
| | | return PRODUCTION_ROUTE_MAP[name] || '' |
| | | } |
| | |
| | | <div class="app-container indicator-stats"> |
| | | <!-- KPI æ±æ» --> |
| | | <el-row :gutter="20" class="stats-row"> |
| | | <el-col :xs="24" :sm="12" :md="8"> |
| | | <el-col :xs="24" :sm="12" :md="6"> |
| | | <div class="stat-card stat-card-blue"> |
| | | <div class="stat-icon-wrapper"> |
| | | <div class="stat-icon"> |
| | |
| | | <div class="stat-bg-decoration"></div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8"> |
| | | <el-col :xs="24" :sm="12" :md="6"> |
| | | <div class="stat-card stat-card-green"> |
| | | <div class="stat-icon-wrapper"> |
| | | <div class="stat-icon"> |
| | |
| | | <div class="stat-bg-decoration"></div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8"> |
| | | <el-col :xs="24" :sm="12" :md="6"> |
| | | <div class="stat-card stat-card-purple"> |
| | | <div class="stat-icon-wrapper"> |
| | | <div class="stat-icon"> |
| | | <el-icon :size="32"><Goods /></el-icon> |
| | | </div> |
| | | </div> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">{{ formatQuantity(indicatorKpis.productSalesQuantity) }}</div> |
| | | <div class="stat-label">产åé宿°é</div> |
| | | </div> |
| | | <div class="stat-bg-decoration"></div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="6"> |
| | | <div class="stat-card stat-card-orange"> |
| | | <div class="stat-icon-wrapper"> |
| | | <div class="stat-icon"> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue' |
| | | import { Document, Van, Tickets, Search, Refresh } from '@element-plus/icons-vue' |
| | | import { Document, Van, Tickets, Search, Refresh, Goods } 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 indicatorKpis = reactive({ |
| | | orderCount: 0, |
| | | salesAmount: 0, |
| | | productSalesQuantity: 0, |
| | | shipRate: 0 |
| | | }) |
| | | |
| | |
| | | const productOptions = ref([]) |
| | | const customerOption = ref([]) |
| | | |
| | | const formatQuantity = (value) => { |
| | | const num = Number(value) |
| | | if (Number.isNaN(num)) return '0' |
| | | return num.toLocaleString(undefined, { maximumFractionDigits: 2 }) |
| | | } |
| | | // 转æ¢äº§åæ æ°æ®ï¼å° id æ¹ä¸º value |
| | | function convertIdToValue(data) { |
| | | return data.map((item) => { |
| | |
| | | if (res && res.data) { |
| | | indicatorKpis.orderCount = res.data.total || 0 |
| | | indicatorKpis.salesAmount = res.data.contractAmountTotal || 0 |
| | | // åè´§ç妿æ¥å£æ²¡æè¿åï¼ä¿æåå¼æè®¾ä¸º0 |
| | | indicatorKpis.productSalesQuantity = res.data.productQuantityTotal || 0 |
| | | indicatorKpis.shipRate = res.data.shipRate || 0 |
| | | } |
| | | } catch (error) { |
| | |
| | | if (indicatorChart) indicatorChart.dispose() |
| | | indicatorChart = echarts.init(indicatorChartRef.value) |
| | | |
| | | // æ ¹æ®æ¥å£è¿åçæ°æ®ç»ææ´æ°å¾è¡¨ |
| | | // æ¥å£è¿å: dateList, orderCountList, salesAmountList |
| | | // æ¥å£è¿å: dateList, orderCountList, salesAmountList, productQuantityList |
| | | const option = { |
| | | title: { text: 'å¤ç»´åº¦é宿æ è¶å¿', left: 'center' }, |
| | | tooltip: { trigger: 'axis' }, |
| | | legend: { data: ['è®¢åæ°', 'éå®é¢'], top: 30 }, |
| | | legend: { |
| | | data: ['è®¢åæ°', 'éå®é¢', '产åé宿°é'], |
| | | top: 30, |
| | | selected: { |
| | | 'è®¢åæ°': false, |
| | | 'éå®é¢': true, |
| | | '产åé宿°é': true |
| | | } |
| | | }, |
| | | grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true }, |
| | | xAxis: { |
| | | type: 'category', |
| | |
| | | yAxisIndex: 0, |
| | | data: chartData.salesAmountList || [], |
| | | itemStyle: { color: '#67c23a' } |
| | | }, |
| | | { |
| | | name: '产åé宿°é', |
| | | type: 'line', |
| | | yAxisIndex: 1, |
| | | data: chartData.productQuantityList || [], |
| | | itemStyle: { color: '#f56c6c' } |
| | | } |
| | | ] |
| | | } |
| | |
| | | const option = { |
| | | title: { text: 'å¤ç»´åº¦é宿æ è¶å¿', left: 'center' }, |
| | | tooltip: { trigger: 'axis' }, |
| | | legend: { data: ['è®¢åæ°', 'éå®é¢'], top: 30 }, |
| | | legend: { |
| | | data: ['è®¢åæ°', 'éå®é¢', '产åé宿°é'], |
| | | top: 30, |
| | | selected: { |
| | | 'è®¢åæ°': false, |
| | | 'éå®é¢': true, |
| | | '产åé宿°é': true |
| | | } |
| | | }, |
| | | grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true }, |
| | | xAxis: { type: 'category', data: [] }, |
| | | yAxis: [ |
| | |
| | | ], |
| | | series: [ |
| | | { name: 'è®¢åæ°', type: 'line', yAxisIndex: 1, data: [], itemStyle: { color: '#409eff' } }, |
| | | { name: 'éå®é¢', type: 'bar', yAxisIndex: 0, data: [], itemStyle: { color: '#67c23a' } } |
| | | { name: 'éå®é¢', type: 'bar', yAxisIndex: 0, data: [], itemStyle: { color: '#67c23a' } }, |
| | | { name: '产åé宿°é', type: 'line', yAxisIndex: 1, data: [], itemStyle: { color: '#f56c6c' } } |
| | | ] |
| | | } |
| | | indicatorChart.setOption(option) |
| | |
| | | background: #e6a23c; |
| | | } |
| | | } |
| | | |
| | | &.stat-card-purple { |
| | | .stat-icon { |
| | | background: linear-gradient(135deg, #9b59b6 0%, #b37fcc 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | .stat-bg-decoration { |
| | | background: #9b59b6; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chart-card, |