| | |
| | | }) |
| | | } |
| | | |
| | | // æ¥è¯¢è¿ç¨æ£å表 |
| | | export function qualityInspectProcessPage(query) { |
| | | return request({ |
| | | url: '/quality/qualityInspect/processPage', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | // æ¥è¯¢è¿ç¨æ£è¯¦æ
|
| | | export function qualityInspectProcessDetails(query) { |
| | | return request({ |
| | | url: '/quality/qualityInspect/processDetails', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | // æ¥è¯¢æåæ£æ£å表 |
| | | export function qualityInspectFinishedListPage(query) { |
| | | return request({ |
| | | url: '/quality/qualityInspect/finishedPage', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | // æåæ£-æ¥çæ åæå
¥äº§åºæ¯ä¾ |
| | | export function qualityInspectFinishedRatio(query) { |
| | | return request({ |
| | | url: '/quality/qualityInspect/finishedRatio', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | |
| | | |
| | | // ç»è®¡ç»´åº¦åæ¢ |
| | | const handleTypeChange = () => { |
| | | // éç½®æ¶é´èå´ |
| | | searchForm.dateRange = []; |
| | | searchForm.monthRange = []; |
| | | searchForm.year = new Date().getFullYear(); |
| | | // éç½®æ¶é´èå´å¹¶è®¾ç½®é»è®¤å¼ |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | |
| | | if (statisticsType.value === "day") { |
| | | // é»è®¤æè¿7天 |
| | | start.setDate(start.getDate() - 6); |
| | | searchForm.dateRange = [ |
| | | start.toISOString().split("T")[0], |
| | | end.toISOString().split("T")[0], |
| | | ]; |
| | | } else if (statisticsType.value === "month") { |
| | | // é»è®¤æè¿3个æ |
| | | start.setMonth(start.getMonth() - 2); |
| | | searchForm.monthRange = [ |
| | | start.toISOString().slice(0, 7), |
| | | end.toISOString().slice(0, 7), |
| | | ]; |
| | | } else if (statisticsType.value === "year") { |
| | | // é»è®¤å½å年份 |
| | | searchForm.year = new Date().getFullYear(); |
| | | } |
| | | |
| | | page.current = 1; |
| | | handleQuery(); |
| | | }; |
| | |
| | | <div class="home-page"> |
| | | <div class="top-bar"> |
| | | <div class="user-box"> |
| | | <img :src="userStore.avatar" class="avatar" alt="" /> |
| | | <!-- <img :src="userStore.avatar" |
| | | class="avatar" |
| | | alt="" /> --> |
| | | <div> |
| | | <div class="hello">{{ userStore.roleName || "ç³»ç»ç®¡çå" }}ï¼ä½ 好</div> |
| | | <div class="sub">ç»å½æ¶é´ï¼{{ userStore.currentLoginTime }}</div> |
| | |
| | | </div> |
| | | <div class="top-actions"> |
| | | <span class="refresh-time">æ°æ®æ´æ°æ¶é´ï¼{{ lastUpdatedAt || "-" }}</span> |
| | | <el-button size="small" type="primary" plain @click="refreshDashboardData">å·æ°æ°æ®</el-button> |
| | | <el-button size="small" plain @click="configDialogVisible = true">é¦é¡µé
ç½®</el-button> |
| | | <el-button size="small" |
| | | type="primary" |
| | | plain |
| | | @click="refreshDashboardData">å·æ°æ°æ®</el-button> |
| | | <el-button size="small" |
| | | plain |
| | | @click="configDialogVisible = true">é¦é¡µé
ç½®</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="content-grid"> |
| | | <div class="left-col"> |
| | | <section class="section-card"> |
| | | <div class="section-title-row"> |
| | | <div class="section-title">å¿«æ·æä½</div> |
| | | <el-button size="small" type="primary" link @click="openShortcutDialog">æ·»å å¿«æ·å
¥å£</el-button> |
| | | <el-button size="small" |
| | | type="primary" |
| | | link |
| | | @click="openShortcutDialog">æ·»å å¿«æ·å
¥å£</el-button> |
| | | </div> |
| | | <div class="quick-grid"> |
| | | <el-button |
| | | v-for="item in shortcuts" |
| | | :key="`${item.label}-${item.path}`" |
| | | :type="item.invalid ? 'danger' : 'default'" |
| | | @click="goTo(item.path)" |
| | | > |
| | | <el-button v-for="item in shortcuts" |
| | | :key="`${item.label}-${item.path}`" |
| | | :type="item.invalid ? 'danger' : 'default'" |
| | | @click="goTo(item.path)"> |
| | | {{ item.label }} |
| | | </el-button> |
| | | </div> |
| | | </section> |
| | | |
| | | <section class="section-card"> |
| | | <div class="section-title">éç¹å¾
å</div> |
| | | <div class="todo-row" v-for="todo in todos" :key="todo.title"> |
| | | <el-tag size="small" :type="todo.type">{{ todo.level }}</el-tag> |
| | | <div class="todo-row" |
| | | v-for="todo in todos" |
| | | :key="todo.title"> |
| | | <el-tag size="small" |
| | | :type="todo.type">{{ todo.level }}</el-tag> |
| | | <span>{{ todo.title }}</span> |
| | | </div> |
| | | </section> |
| | | |
| | | <section class="section-card"> |
| | | <div class="section-title">ç»è¥å
³æ³¨</div> |
| | | <div class="focus-row" v-for="item in businessFocus" :key="item.name"> |
| | | <div class="focus-row" |
| | | v-for="item in businessFocus" |
| | | :key="item.name"> |
| | | <span class="focus-name">{{ item.name }}</span> |
| | | <span class="focus-value">{{ item.value }}</span> |
| | | </div> |
| | | </section> |
| | | |
| | | <section class="section-card flex-fill-card"> |
| | | <div class="section-title-row"> |
| | | <div class="section-title">仿¥å¾
å¤ç</div> |
| | | <el-radio-group v-model="pendingFilter" size="small"> |
| | | <el-radio-group v-model="pendingFilter" |
| | | size="small"> |
| | | <el-radio-button label="all">å
¨é¨</el-radio-button> |
| | | <el-radio-button label="mine">æç</el-radio-button> |
| | | <el-radio-button label="high">é«ä¼å
</el-radio-button> |
| | | </el-radio-group> |
| | | </div> |
| | | <div class="task-row" v-for="task in filteredPendingTasks" :key="task.id"> |
| | | <div class="task-row" |
| | | v-for="task in filteredPendingTasks" |
| | | :key="task.id"> |
| | | <div class="task-left"> |
| | | <el-tag size="small" :type="task.type">{{ task.level }}</el-tag> |
| | | <el-tag size="small" |
| | | :type="task.type">{{ task.level }}</el-tag> |
| | | <span class="task-title">{{ task.title }}</span> |
| | | </div> |
| | | <el-button link type="primary" @click="goTo(task.path)">å»å¤ç</el-button> |
| | | <el-button link |
| | | type="primary" |
| | | @click="goTo(task.path)">å»å¤ç</el-button> |
| | | </div> |
| | | <el-empty v-if="filteredPendingTasks.length === 0" description="ææ å¾
å¤çäºé¡¹" :image-size="80" /> |
| | | <el-empty v-if="filteredPendingTasks.length === 0" |
| | | description="ææ å¾
å¤çäºé¡¹" |
| | | :image-size="80" /> |
| | | </section> |
| | | </div> |
| | | |
| | | <div class="right-col"> |
| | | <section class="section-card" v-if="isSectionVisible('trendCards')"> |
| | | <section class="section-card" |
| | | v-if="isSectionVisible('trendCards')"> |
| | | <div class="section-title">æè¿7天å
³é®ææ è¶å¿</div> |
| | | <div class="trend-cards"> |
| | | <div class="trend-card clickable" v-for="card in recentTrendCards" :key="card.key" @click="handleTrendCardClick(card)"> |
| | | <div class="trend-card clickable" |
| | | v-for="card in recentTrendCards" |
| | | :key="card.key" |
| | | @click="handleTrendCardClick(card)"> |
| | | <div class="trend-head"> |
| | | <span class="trend-label">{{ card.label }}</span> |
| | | <span class="trend-rate" :class="trendClass(card.change)"> |
| | | <span class="trend-rate" |
| | | :class="trendClass(card.change)"> |
| | | {{ card.change > 0 ? "+" : "" }}{{ card.change.toFixed(1) }}% |
| | | </span> |
| | | </div> |
| | | <div class="trend-value">{{ card.latest }} {{ card.unit }}</div> |
| | | <div class="sparkline"> |
| | | <span |
| | | v-for="(v, idx) in card.values" |
| | | :key="`${card.key}-${idx}`" |
| | | class="sparkline-bar" |
| | | :style="{ height: `${calcBarHeight(v, card.values)}%` }" |
| | | /> |
| | | <span v-for="(v, idx) in card.values" |
| | | :key="`${card.key}-${idx}`" |
| | | class="sparkline-bar" |
| | | :style="{ height: `${calcBarHeight(v, card.values)}%` }" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </section> |
| | | |
| | | <section class="section-card" v-if="isSectionVisible('planTrend')"> |
| | | <section class="section-card" |
| | | v-if="isSectionVisible('planTrend')"> |
| | | <div class="section-title-row"> |
| | | <div class="section-title">计åä¸ç产è¶å¿</div> |
| | | <el-radio-group v-model="chartRangePlan" size="small" @change="loadPlanTrend"> |
| | | <el-radio-group v-model="chartRangePlan" |
| | | size="small" |
| | | @change="loadPlanTrend"> |
| | | <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> |
| | | </div> |
| | | <Echarts |
| | | :chartStyle="chartStyle" |
| | | :legend="planLegend" |
| | | :grid="grid" |
| | | :tooltip="lineTooltip" |
| | | :xAxis="planXAxis" |
| | | :yAxis="valueYAxis" |
| | | :series="planSeries" |
| | | style="height: 300px" |
| | | /> |
| | | <Echarts :chartStyle="chartStyle" |
| | | :legend="planLegend" |
| | | :grid="grid" |
| | | :tooltip="lineTooltip" |
| | | :xAxis="planXAxis" |
| | | :yAxis="valueYAxis" |
| | | :series="planSeries" |
| | | style="height: 300px" /> |
| | | </section> |
| | | |
| | | <div class="row-two" v-if="isSectionVisible('qualityChart') || isSectionVisible('costChart')"> |
| | | <section class="section-card" v-if="isSectionVisible('qualityChart')"> |
| | | <div class="row-two" |
| | | v-if="isSectionVisible('qualityChart') || isSectionVisible('costChart')"> |
| | | <section class="section-card" |
| | | v-if="isSectionVisible('qualityChart')"> |
| | | <div class="section-title-row"> |
| | | <div class="section-title">è´¨æ£å¼å¸¸åå¸</div> |
| | | <el-radio-group v-model="chartRangeQuality" size="small" @change="loadQualityData"> |
| | | <el-radio-group v-model="chartRangeQuality" |
| | | size="small" |
| | | @change="loadQualityData"> |
| | | <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> |
| | | </div> |
| | | <Echarts |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :tooltip="barTooltip" |
| | | :xAxis="qualityXAxis" |
| | | :yAxis="valueYAxis" |
| | | :series="qualitySeries" |
| | | style="height: 260px" |
| | | /> |
| | | <Echarts :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :tooltip="barTooltip" |
| | | :xAxis="qualityXAxis" |
| | | :yAxis="valueYAxis" |
| | | :series="qualitySeries" |
| | | style="height: 260px" /> |
| | | </section> |
| | | <section class="section-card" v-if="isSectionVisible('costChart')"> |
| | | <section class="section-card" |
| | | v-if="isSectionVisible('costChart')"> |
| | | <div class="section-title">è½è䏿æ¬ç»æ</div> |
| | | <Echarts |
| | | :chartStyle="chartStyle" |
| | | :legend="costLegend" |
| | | :tooltip="pieTooltip" |
| | | :series="costSeries" |
| | | style="height: 260px" |
| | | /> |
| | | <Echarts :chartStyle="chartStyle" |
| | | :legend="costLegend" |
| | | :tooltip="pieTooltip" |
| | | :series="costSeries" |
| | | style="height: 260px" /> |
| | | </section> |
| | | </div> |
| | | |
| | | <section class="section-card" v-if="isSectionVisible('warningCenter')"> |
| | | <section class="section-card" |
| | | v-if="isSectionVisible('warningCenter')"> |
| | | <div class="section-title">å¼å¸¸é¢è¦ä¸å¿</div> |
| | | <div class="warning-row" v-for="item in warningList" :key="item.id"> |
| | | <div class="warning-row" |
| | | v-for="item in warningList" |
| | | :key="item.id"> |
| | | <div class="warning-left"> |
| | | <el-tag size="small" :type="item.levelType">{{ item.levelText }}</el-tag> |
| | | <el-tag size="small" |
| | | :type="item.levelType">{{ item.levelText }}</el-tag> |
| | | <span class="warning-title">{{ item.title }}</span> |
| | | </div> |
| | | <el-button link type="danger" @click="goTo(item.path)">å¤ç</el-button> |
| | | <el-button link |
| | | type="danger" |
| | | @click="goTo(item.path)">å¤ç</el-button> |
| | | </div> |
| | | <el-empty v-if="warningList.length === 0" description="ææ å¼å¸¸é¢è¦" :image-size="80" /> |
| | | <el-empty v-if="warningList.length === 0" |
| | | description="ææ å¼å¸¸é¢è¦" |
| | | :image-size="80" /> |
| | | </section> |
| | | |
| | | <section class="section-card mini-table-wrap" v-if="isSectionVisible('planTable')"> |
| | | <section class="section-card mini-table-wrap" |
| | | v-if="isSectionVisible('planTable')"> |
| | | <div class="section-title">çäº§è®¡åæ§è¡æç»</div> |
| | | <el-table :data="planTable" size="small" stripe> |
| | | <el-table-column prop="planNo" label="计ååå·" min-width="150" /> |
| | | <el-table-column prop="product" label="产å" min-width="120" /> |
| | | <el-table-column prop="qty" label="计åé" min-width="90" /> |
| | | <el-table-column prop="issued" label="å·²ä¸å" min-width="90" /> |
| | | <el-table-column prop="status" label="ç¶æ" min-width="100" /> |
| | | <el-table-column label="æä½" min-width="120"> |
| | | <el-table :data="planTable" |
| | | size="small" |
| | | stripe> |
| | | <el-table-column prop="planNo" |
| | | label="计ååå·" |
| | | min-width="150" /> |
| | | <el-table-column prop="product" |
| | | label="产å" |
| | | min-width="120" /> |
| | | <el-table-column prop="qty" |
| | | label="计åé" |
| | | min-width="90" /> |
| | | <el-table-column prop="issued" |
| | | label="å·²ä¸å" |
| | | min-width="90" /> |
| | | <el-table-column prop="status" |
| | | label="ç¶æ" |
| | | min-width="100" /> |
| | | <el-table-column label="æä½" |
| | | min-width="120"> |
| | | <template #default="{ row }"> |
| | | <el-button link type="primary" @click="goTo(routePathMap.plan)">æ¥ç</el-button> |
| | | <el-button |
| | | v-if="row.status !== '已宿'" |
| | | link |
| | | type="success" |
| | | @click="goTo(routePathMap.dispatch)" |
| | | > |
| | | <el-button link |
| | | type="primary" |
| | | @click="goTo(routePathMap.plan)">æ¥ç</el-button> |
| | | <el-button v-if="row.status !== '已宿'" |
| | | link |
| | | type="success" |
| | | @click="goTo(routePathMap.dispatch)"> |
| | | ä¸å |
| | | </el-button> |
| | | </template> |
| | |
| | | </section> |
| | | </div> |
| | | </div> |
| | | |
| | | <el-dialog v-model="shortcutDialogVisible" title="æ·»å å¿«æ·å
¥å£ï¼æå¤6个ï¼" width="680px"> |
| | | <el-dialog v-model="shortcutDialogVisible" |
| | | title="æ·»å å¿«æ·å
¥å£ï¼æå¤6个ï¼" |
| | | width="680px"> |
| | | <div class="shortcut-form-row"> |
| | | <el-tree-select |
| | | v-model="selectedPagePath" |
| | | placeholder="è¯·éæ©é¡µé¢ï¼ç®å½ä¸å¯éï¼" |
| | | filterable |
| | | clearable |
| | | check-strictly |
| | | node-key="value" |
| | | :data="menuTreeOptions" |
| | | :props="{ label: 'label', value: 'value', children: 'children', disabled: 'disabled' }" |
| | | style="grid-column: span 2" |
| | | /> |
| | | <el-button type="success" @click="addShortcutBySelect">éæ©æ·»å </el-button> |
| | | <el-tree-select v-model="selectedPagePath" |
| | | placeholder="è¯·éæ©é¡µé¢ï¼ç®å½ä¸å¯éï¼" |
| | | filterable |
| | | clearable |
| | | check-strictly |
| | | node-key="value" |
| | | :data="menuTreeOptions" |
| | | :props="{ label: 'label', value: 'value', children: 'children', disabled: 'disabled' }" |
| | | style="grid-column: span 2" /> |
| | | <el-button type="success" |
| | | @click="addShortcutBySelect">éæ©æ·»å </el-button> |
| | | </div> |
| | | <el-table :data="shortcuts" size="small" border> |
| | | <el-table-column prop="label" label="åç§°" min-width="220" /> |
| | | <el-table-column label="ç¶æ" min-width="80"> |
| | | <el-table :data="shortcuts" |
| | | size="small" |
| | | border> |
| | | <el-table-column prop="label" |
| | | label="åç§°" |
| | | min-width="220" /> |
| | | <el-table-column label="ç¶æ" |
| | | min-width="80"> |
| | | <template #default="{ row }"> |
| | | <el-tag size="small" :type="row.invalid ? 'danger' : 'success'">{{ row.invalid ? "æ æ" : "ææ" }}</el-tag> |
| | | <el-tag size="small" |
| | | :type="row.invalid ? 'danger' : 'success'">{{ row.invalid ? "æ æ" : "ææ" }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" min-width="90" align="center"> |
| | | <el-table-column label="æä½" |
| | | min-width="90" |
| | | align="center"> |
| | | <template #default="{ $index }"> |
| | | <el-button link type="danger" @click="removeShortcut($index)">å é¤</el-button> |
| | | <el-button link |
| | | type="danger" |
| | | @click="removeShortcut($index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | |
| | | <el-dialog v-model="configDialogVisible" title="é¦é¡µæ¨¡åé
ç½®" width="520px"> |
| | | <el-checkbox-group v-model="enabledSectionKeys" class="config-check-group"> |
| | | <el-checkbox v-for="item in sectionConfigOptions" :key="item.key" :label="item.key"> |
| | | <el-dialog v-model="configDialogVisible" |
| | | title="é¦é¡µæ¨¡åé
ç½®" |
| | | width="520px"> |
| | | <el-checkbox-group v-model="enabledSectionKeys" |
| | | class="config-check-group"> |
| | | <el-checkbox v-for="item in sectionConfigOptions" |
| | | :key="item.key" |
| | | :label="item.key"> |
| | | {{ item.label }} |
| | | </el-checkbox> |
| | | </el-checkbox-group> |
| | | <template #footer> |
| | | <el-button @click="configDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="saveSectionConfig">ä¿å</el-button> |
| | | <el-button type="primary" |
| | | @click="saveSectionConfig">ä¿å</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, onMounted, reactive, ref } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import { ElMessage } from "element-plus"; |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import usePermissionStore from "@/store/modules/permission"; |
| | | import { |
| | | expenseCompositionAnalysis, |
| | | getProgressStatistics, |
| | | homeTodos, |
| | | orderCount, |
| | | processDataProductionStatistics, |
| | | qualityInspectionStatistics, |
| | | nonComplianceWarning, |
| | | } from "@/api/viewIndex.js"; |
| | | import { computed, onMounted, reactive, ref } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import { ElMessage } from "element-plus"; |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import usePermissionStore from "@/store/modules/permission"; |
| | | import { |
| | | expenseCompositionAnalysis, |
| | | getProgressStatistics, |
| | | homeTodos, |
| | | orderCount, |
| | | processDataProductionStatistics, |
| | | qualityInspectionStatistics, |
| | | nonComplianceWarning, |
| | | } from "@/api/viewIndex.js"; |
| | | |
| | | const router = useRouter(); |
| | | const userStore = useUserStore(); |
| | | const permissionStore = usePermissionStore(); |
| | | const router = useRouter(); |
| | | const userStore = useUserStore(); |
| | | const permissionStore = usePermissionStore(); |
| | | |
| | | const SHORTCUT_STORAGE_KEY = "home-shortcuts-v1"; |
| | | const SHORTCUT_STORAGE_KEY = "home-shortcuts-v1"; |
| | | |
| | | const defaultShortcuts = [ |
| | | { label: "主ç产计å", path: "/productionManagement/productionPlan" }, |
| | | { label: "ç产订å", path: "/productionManagement/productionOrder" }, |
| | | { label: "ç产æ¥å·¥", path: "/productionManagement/productionReporting" }, |
| | | { label: "è¿ç¨æ£", path: "/qualityManagement/processInspection" }, |
| | | { label: "ç产è½è", path: "/energyManagement/productionEnergyConsumption" }, |
| | | { label: "çäº§ææ¬", path: "/costAccounting/productionCostAccounting" }, |
| | | { label: "æ åvså®é
", path: "/costAccounting/stdVsActCostAnalysis" }, |
| | | { label: "å³çåæ", path: "/reportAnalysis/dataDashboard" }, |
| | | ]; |
| | | const defaultShortcuts = [ |
| | | { label: "主ç产计å", path: "/productionManagement/productionPlan" }, |
| | | { label: "ç产订å", path: "/productionManagement/productionOrder" }, |
| | | { label: "ç产æ¥å·¥", path: "/productionManagement/productionReporting" }, |
| | | { label: "è¿ç¨æ£", path: "/qualityManagement/processInspection" }, |
| | | { label: "ç产è½è", path: "/energyManagement/productionEnergyConsumption" }, |
| | | { label: "çäº§ææ¬", path: "/costAccounting/productionCostAccounting" }, |
| | | { label: "æ åvså®é
", path: "/costAccounting/stdVsActCostAnalysis" }, |
| | | { label: "å³çåæ", path: "/reportAnalysis/dataDashboard" }, |
| | | ]; |
| | | |
| | | const isRouteValid = (path) => { |
| | | if (!path || !path.startsWith("/")) return false; |
| | | const resolved = router.resolve(path); |
| | | return resolved.matched && resolved.matched.length > 0; |
| | | }; |
| | | const isRouteValid = path => { |
| | | if (!path || !path.startsWith("/")) return false; |
| | | const resolved = router.resolve(path); |
| | | return resolved.matched && resolved.matched.length > 0; |
| | | }; |
| | | |
| | | const withValidFlag = (list) => |
| | | list.map((item) => ({ |
| | | ...item, |
| | | invalid: !isRouteValid(item.path), |
| | | })); |
| | | const withValidFlag = list => |
| | | list.map(item => ({ |
| | | ...item, |
| | | invalid: !isRouteValid(item.path), |
| | | })); |
| | | |
| | | const pageOptions = router |
| | | .getRoutes() |
| | | .filter((route) => { |
| | | const hasTitle = Boolean(route.meta?.title); |
| | | const validPath = route.path && route.path.startsWith("/") && !route.path.includes(":"); |
| | | const isVisibleMenu = !route.meta?.hidden && route.path !== "/index"; |
| | | const notSpecial = |
| | | !route.path.includes("redirect") && |
| | | route.path !== "/login" && |
| | | route.path !== "/register" && |
| | | route.path !== "/401" && |
| | | !route.path.includes(":pathMatch"); |
| | | return hasTitle && validPath && isVisibleMenu && notSpecial; |
| | | }) |
| | | .map((route) => ({ |
| | | title: route.meta.title, |
| | | path: route.path, |
| | | })) |
| | | .sort((a, b) => a.path.localeCompare(b.path)); |
| | | const pageOptions = router |
| | | .getRoutes() |
| | | .filter(route => { |
| | | const hasTitle = Boolean(route.meta?.title); |
| | | const validPath = |
| | | route.path && route.path.startsWith("/") && !route.path.includes(":"); |
| | | const isVisibleMenu = !route.meta?.hidden && route.path !== "/index"; |
| | | const notSpecial = |
| | | !route.path.includes("redirect") && |
| | | route.path !== "/login" && |
| | | route.path !== "/register" && |
| | | route.path !== "/401" && |
| | | !route.path.includes(":pathMatch"); |
| | | return hasTitle && validPath && isVisibleMenu && notSpecial; |
| | | }) |
| | | .map(route => ({ |
| | | title: route.meta.title, |
| | | path: route.path, |
| | | })) |
| | | .sort((a, b) => a.path.localeCompare(b.path)); |
| | | |
| | | const normalizePath = (path) => String(path || "").replace(/\/+/g, "/"); |
| | | const joinPath = (parentPath, childPath) => { |
| | | if (!childPath) return normalizePath(parentPath || "/"); |
| | | if (String(childPath).startsWith("/")) return normalizePath(childPath); |
| | | return normalizePath(`${parentPath || ""}/${childPath}`); |
| | | }; |
| | | const normalizePath = path => String(path || "").replace(/\/+/g, "/"); |
| | | const joinPath = (parentPath, childPath) => { |
| | | if (!childPath) return normalizePath(parentPath || "/"); |
| | | if (String(childPath).startsWith("/")) return normalizePath(childPath); |
| | | return normalizePath(`${parentPath || ""}/${childPath}`); |
| | | }; |
| | | |
| | | const buildMenuTreeOptions = (routes = [], parentPath = "") => { |
| | | const result = []; |
| | | routes.forEach((route) => { |
| | | if (!route || route.hidden) return; |
| | | const fullPath = joinPath(parentPath, route.path); |
| | | const children = buildMenuTreeOptions(route.children || [], fullPath); |
| | | const title = route.meta?.title; |
| | | if (!title && children.length > 0) { |
| | | result.push(...children); |
| | | const buildMenuTreeOptions = (routes = [], parentPath = "") => { |
| | | const result = []; |
| | | routes.forEach(route => { |
| | | if (!route || route.hidden) return; |
| | | const fullPath = joinPath(parentPath, route.path); |
| | | const children = buildMenuTreeOptions(route.children || [], fullPath); |
| | | const title = route.meta?.title; |
| | | if (!title && children.length > 0) { |
| | | result.push(...children); |
| | | return; |
| | | } |
| | | if (!title) return; |
| | | result.push({ |
| | | label: title, |
| | | value: fullPath, |
| | | disabled: children.length > 0, |
| | | children, |
| | | }); |
| | | }); |
| | | return result; |
| | | }; |
| | | |
| | | const menuTreeOptions = computed(() => |
| | | buildMenuTreeOptions(permissionStore.sidebarRouters || []) |
| | | ); |
| | | const selectableMenuMap = computed(() => { |
| | | const map = new Map(); |
| | | const walk = (list = []) => { |
| | | list.forEach(item => { |
| | | if (!item.disabled) map.set(item.value, item.label); |
| | | if (item.children?.length) walk(item.children); |
| | | }); |
| | | }; |
| | | walk(menuTreeOptions.value); |
| | | return map; |
| | | }); |
| | | |
| | | const keywordMap = { |
| | | 主ç产计å: ["ç产计å", "productionPlan"], |
| | | ç产订å: ["ç产订å", "productionOrder"], |
| | | ç产æ¥å·¥: ["æ¥å·¥", "productionReporting"], |
| | | è¿ç¨æ£: ["è¿ç¨æ£", "processInspection"], |
| | | ç产è½è: ["ç产è½è", "productionEnergyConsumption"], |
| | | çäº§ææ¬: ["çäº§ææ¬", "productionCostAccounting"], |
| | | æ åvså®é
: ["æ å", "å®é
", "stdVsActCostAnalysis"], |
| | | å³çåæ: ["å³ç", "çæ¿", "dataDashboard"], |
| | | }; |
| | | |
| | | const findRouteByKeywords = (keywords = []) => { |
| | | const lowerKeywords = keywords.map(k => String(k).toLowerCase()); |
| | | return pageOptions.find(item => { |
| | | const title = String(item.title || "").toLowerCase(); |
| | | const path = String(item.path || "").toLowerCase(); |
| | | return lowerKeywords.some(k => title.includes(k) || path.includes(k)); |
| | | }); |
| | | }; |
| | | |
| | | const getPathByKeywords = (keywords = []) => |
| | | findRouteByKeywords(keywords)?.path || ""; |
| | | |
| | | const getRecommendedShortcuts = () => { |
| | | const list = defaultShortcuts |
| | | .map(item => { |
| | | const matched = findRouteByKeywords( |
| | | keywordMap[item.label] || [item.label] |
| | | ); |
| | | return matched ? { label: item.label, path: matched.path } : null; |
| | | }) |
| | | .filter(Boolean); |
| | | return list.length > 0 ? list : defaultShortcuts; |
| | | }; |
| | | |
| | | const tryRepairSavedShortcut = item => { |
| | | const matched = findRouteByKeywords(keywordMap[item.label] || [item.label]); |
| | | if (matched) return { label: item.label, path: matched.path }; |
| | | return item; |
| | | }; |
| | | |
| | | const getSavedShortcuts = () => { |
| | | const recommended = getRecommendedShortcuts(); |
| | | try { |
| | | const saved = localStorage.getItem(SHORTCUT_STORAGE_KEY); |
| | | if (!saved) return recommended; |
| | | const parsed = JSON.parse(saved); |
| | | if (!Array.isArray(parsed) || parsed.length === 0) return recommended; |
| | | return parsed.map(item => tryRepairSavedShortcut(item)); |
| | | } catch (error) { |
| | | return recommended; |
| | | } |
| | | }; |
| | | |
| | | const shortcuts = reactive(withValidFlag(getSavedShortcuts().slice(0, 6))); |
| | | const shortcutDialogVisible = ref(false); |
| | | const configDialogVisible = ref(false); |
| | | const selectedPagePath = ref(""); |
| | | const lastUpdatedAt = ref(""); |
| | | const pendingFilter = ref("all"); |
| | | const chartRangePlan = ref(3); |
| | | const chartRangeQuality = ref(2); |
| | | |
| | | const routePathMap = { |
| | | plan: getPathByKeywords(["ç产计å", "productionPlan"]), |
| | | order: getPathByKeywords(["ç产订å", "productionOrder"]), |
| | | processInspection: getPathByKeywords(["è¿ç¨æ£", "processInspection"]), |
| | | meter: getPathByKeywords(["æè¡¨", "meterCollection", "è½è"]), |
| | | dispatch: getPathByKeywords(["ç产è°åº¦", "productionDispatching"]), |
| | | }; |
| | | |
| | | const persistShortcuts = () => { |
| | | localStorage.setItem( |
| | | SHORTCUT_STORAGE_KEY, |
| | | JSON.stringify( |
| | | shortcuts.slice(0, 6).map(({ label, path }) => ({ label, path })) |
| | | ) |
| | | ); |
| | | }; |
| | | |
| | | const todos = reactive([]); |
| | | |
| | | const businessFocus = reactive([ |
| | | { name: "çäº§è®¢åæ»æ°", value: "-" }, |
| | | { name: "å·²å®æè®¢åæ°", value: "-" }, |
| | | { name: "æªå®æè®¢åæ°", value: "-" }, |
| | | { name: "é¨åå®æè®¢åæ°", value: "-" }, |
| | | { name: "è´¨æ£æ»æ°", value: "-" }, |
| | | { name: "è¿ç¨æ£æ»æ°", value: "-" }, |
| | | ]); |
| | | |
| | | const pendingTasks = reactive([]); |
| | | const warningList = reactive([]); |
| | | const SECTION_CONFIG_KEY = "home-sections-v1"; |
| | | const sectionConfigOptions = [ |
| | | { key: "trendCards", label: "æè¿7天è¶å¿å¡" }, |
| | | { key: "planTrend", label: "计åä¸ç产è¶å¿å¾" }, |
| | | { key: "qualityChart", label: "è´¨æ£å¼å¸¸åå¸å¾" }, |
| | | { key: "costChart", label: "è½è䏿æ¬ç»æå¾" }, |
| | | { key: "warningCenter", label: "å¼å¸¸é¢è¦ä¸å¿" }, |
| | | { key: "planTable", label: "çäº§è®¡åæ§è¡æç»è¡¨" }, |
| | | ]; |
| | | const enabledSectionKeys = ref(sectionConfigOptions.map(i => i.key)); |
| | | |
| | | const chartStyle = { width: "100%", height: "100%" }; |
| | | const grid = { left: "3%", right: "4%", bottom: "3%", containLabel: true }; |
| | | const lineTooltip = { trigger: "axis" }; |
| | | const barTooltip = { trigger: "axis", axisPointer: { type: "shadow" } }; |
| | | const pieTooltip = { trigger: "item" }; |
| | | |
| | | const valueYAxis = [{ type: "value" }]; |
| | | const planXAxis = [{ type: "category", data: [] }]; |
| | | const qualityXAxis = [{ type: "category", data: [] }]; |
| | | |
| | | const planLegend = { show: true, data: ["计åé", "ä¸åé", "宿é"] }; |
| | | const costLegend = { |
| | | show: true, |
| | | orient: "vertical", |
| | | right: 10, |
| | | top: "center", |
| | | data: ["è½èææ¬", "çäº§ææ¬", "è´¨éæå¤±ææ¬", "å
¶ä»ææ¬"], |
| | | }; |
| | | |
| | | const planSeries = reactive([ |
| | | { name: "计åé", type: "line", smooth: true, data: [] }, |
| | | { name: "ä¸åé", type: "line", smooth: true, data: [] }, |
| | | { name: "宿é", type: "line", smooth: true, data: [] }, |
| | | ]); |
| | | |
| | | const qualitySeries = reactive([ |
| | | { |
| | | name: "å¼å¸¸æ°", |
| | | type: "bar", |
| | | barWidth: 26, |
| | | itemStyle: { color: "#e67e22", borderRadius: [6, 6, 0, 0] }, |
| | | data: [], |
| | | }, |
| | | ]); |
| | | |
| | | const costSeries = reactive([ |
| | | { |
| | | type: "pie", |
| | | radius: ["45%", "68%"], |
| | | center: ["35%", "50%"], |
| | | label: { formatter: "{b}: {d}%" }, |
| | | data: [], |
| | | }, |
| | | ]); |
| | | |
| | | const planTable = reactive([]); |
| | | const recentTrendCards = reactive([ |
| | | { |
| | | key: "planIssued", |
| | | label: "计åä¸åé", |
| | | unit: "å", |
| | | values: [0, 0, 0, 0, 0, 0, 0], |
| | | latest: 0, |
| | | change: 0, |
| | | }, |
| | | { |
| | | key: "qualityRaw", |
| | | label: "æ¥ææ£æ°é", |
| | | unit: "æ¡", |
| | | values: [0, 0, 0, 0, 0, 0, 0], |
| | | latest: 0, |
| | | change: 0, |
| | | }, |
| | | { |
| | | key: "qualityProcess", |
| | | label: "è¿ç¨æ£æ°é", |
| | | unit: "æ¡", |
| | | values: [0, 0, 0, 0, 0, 0, 0], |
| | | latest: 0, |
| | | change: 0, |
| | | }, |
| | | { |
| | | key: "qualityFactory", |
| | | label: "æåæ£æ°é", |
| | | unit: "æ¡", |
| | | values: [0, 0, 0, 0, 0, 0, 0], |
| | | latest: 0, |
| | | change: 0, |
| | | }, |
| | | ]); |
| | | |
| | | const toNumber = value => { |
| | | const num = Number(value); |
| | | return Number.isFinite(num) ? num : 0; |
| | | }; |
| | | |
| | | const pickFirstNumber = (obj, keys = []) => { |
| | | for (const key of keys) { |
| | | if (obj && obj[key] !== undefined && obj[key] !== null) |
| | | return toNumber(obj[key]); |
| | | } |
| | | return 0; |
| | | }; |
| | | |
| | | const updateArray = (target, list) => { |
| | | target.splice(0, target.length, ...list); |
| | | }; |
| | | |
| | | const toFixedOne = num => Number(num || 0).toFixed(1); |
| | | |
| | | const normalizeSeven = (list = []) => { |
| | | const nums = list.map(i => toNumber(i)); |
| | | if (nums.length >= 7) return nums.slice(-7); |
| | | return [...Array(7 - nums.length).fill(0), ...nums]; |
| | | }; |
| | | |
| | | const calcTrend = (list = []) => { |
| | | if (!Array.isArray(list) || list.length === 0) |
| | | return { latest: 0, change: 0 }; |
| | | const first = toNumber(list[0]); |
| | | const latest = toNumber(list[list.length - 1]); |
| | | if (first === 0) return { latest, change: latest > 0 ? 100 : 0 }; |
| | | return { latest, change: ((latest - first) / first) * 100 }; |
| | | }; |
| | | |
| | | const setTrendCard = (key, values) => { |
| | | const target = recentTrendCards.find(i => i.key === key); |
| | | if (!target) return; |
| | | const series = normalizeSeven(values); |
| | | const { latest, change } = calcTrend(series); |
| | | target.values = series; |
| | | target.latest = latest; |
| | | target.change = Number(toFixedOne(change)); |
| | | }; |
| | | |
| | | const trendClass = change => (change > 0 ? "up" : change < 0 ? "down" : "flat"); |
| | | |
| | | const calcBarHeight = (value, list) => { |
| | | const max = Math.max(...list, 1); |
| | | return Math.max(18, Math.round((toNumber(value) / max) * 100)); |
| | | }; |
| | | |
| | | const filteredPendingTasks = computed(() => { |
| | | if (pendingFilter.value === "high") |
| | | return pendingTasks.filter(i => i.level === "é«"); |
| | | if (pendingFilter.value === "mine") { |
| | | const currentUserName = String(userStore?.name || "").toLowerCase(); |
| | | const currentUserId = String(userStore?.userId || ""); |
| | | return pendingTasks.filter(i => { |
| | | const ownerName = String(i.ownerName || "").toLowerCase(); |
| | | const ownerId = String(i.ownerId || ""); |
| | | return ( |
| | | (currentUserName && ownerName && ownerName.includes(currentUserName)) || |
| | | (currentUserId && ownerId === currentUserId) |
| | | ); |
| | | }); |
| | | } |
| | | return pendingTasks; |
| | | }); |
| | | |
| | | const isSectionVisible = key => enabledSectionKeys.value.includes(key); |
| | | |
| | | const goTo = path => { |
| | | if (!isRouteValid(path)) { |
| | | ElMessage.warning("å½åèåæªé
ç½®è¯¥é¡µé¢ææ è®¿é®æé"); |
| | | return; |
| | | } |
| | | if (!title) return; |
| | | result.push({ |
| | | label: title, |
| | | value: fullPath, |
| | | disabled: children.length > 0, |
| | | children, |
| | | }); |
| | | }); |
| | | return result; |
| | | }; |
| | | |
| | | const menuTreeOptions = computed(() => buildMenuTreeOptions(permissionStore.sidebarRouters || [])); |
| | | const selectableMenuMap = computed(() => { |
| | | const map = new Map(); |
| | | const walk = (list = []) => { |
| | | list.forEach((item) => { |
| | | if (!item.disabled) map.set(item.value, item.label); |
| | | if (item.children?.length) walk(item.children); |
| | | }); |
| | | router.push(path); |
| | | }; |
| | | walk(menuTreeOptions.value); |
| | | return map; |
| | | }); |
| | | |
| | | const keywordMap = { |
| | | "主ç产计å": ["ç产计å", "productionPlan"], |
| | | "ç产订å": ["ç产订å", "productionOrder"], |
| | | "ç产æ¥å·¥": ["æ¥å·¥", "productionReporting"], |
| | | "è¿ç¨æ£": ["è¿ç¨æ£", "processInspection"], |
| | | "ç产è½è": ["ç产è½è", "productionEnergyConsumption"], |
| | | "çäº§ææ¬": ["çäº§ææ¬", "productionCostAccounting"], |
| | | "æ åvså®é
": ["æ å", "å®é
", "stdVsActCostAnalysis"], |
| | | "å³çåæ": ["å³ç", "çæ¿", "dataDashboard"], |
| | | }; |
| | | |
| | | const findRouteByKeywords = (keywords = []) => { |
| | | const lowerKeywords = keywords.map((k) => String(k).toLowerCase()); |
| | | return pageOptions.find((item) => { |
| | | const title = String(item.title || "").toLowerCase(); |
| | | const path = String(item.path || "").toLowerCase(); |
| | | return lowerKeywords.some((k) => title.includes(k) || path.includes(k)); |
| | | }); |
| | | }; |
| | | |
| | | const getPathByKeywords = (keywords = []) => findRouteByKeywords(keywords)?.path || ""; |
| | | |
| | | const getRecommendedShortcuts = () => { |
| | | const list = defaultShortcuts |
| | | .map((item) => { |
| | | const matched = findRouteByKeywords(keywordMap[item.label] || [item.label]); |
| | | return matched ? { label: item.label, path: matched.path } : null; |
| | | }) |
| | | .filter(Boolean); |
| | | return list.length > 0 ? list : defaultShortcuts; |
| | | }; |
| | | |
| | | const tryRepairSavedShortcut = (item) => { |
| | | const matched = findRouteByKeywords(keywordMap[item.label] || [item.label]); |
| | | if (matched) return { label: item.label, path: matched.path }; |
| | | return item; |
| | | }; |
| | | |
| | | const getSavedShortcuts = () => { |
| | | const recommended = getRecommendedShortcuts(); |
| | | try { |
| | | const saved = localStorage.getItem(SHORTCUT_STORAGE_KEY); |
| | | if (!saved) return recommended; |
| | | const parsed = JSON.parse(saved); |
| | | if (!Array.isArray(parsed) || parsed.length === 0) return recommended; |
| | | return parsed.map((item) => tryRepairSavedShortcut(item)); |
| | | } catch (error) { |
| | | return recommended; |
| | | } |
| | | }; |
| | | |
| | | const shortcuts = reactive(withValidFlag(getSavedShortcuts().slice(0, 6))); |
| | | const shortcutDialogVisible = ref(false); |
| | | const configDialogVisible = ref(false); |
| | | const selectedPagePath = ref(""); |
| | | const lastUpdatedAt = ref(""); |
| | | const pendingFilter = ref("all"); |
| | | const chartRangePlan = ref(3); |
| | | const chartRangeQuality = ref(2); |
| | | |
| | | const routePathMap = { |
| | | plan: getPathByKeywords(["ç产计å", "productionPlan"]), |
| | | order: getPathByKeywords(["ç产订å", "productionOrder"]), |
| | | processInspection: getPathByKeywords(["è¿ç¨æ£", "processInspection"]), |
| | | meter: getPathByKeywords(["æè¡¨", "meterCollection", "è½è"]), |
| | | dispatch: getPathByKeywords(["ç产è°åº¦", "productionDispatching"]), |
| | | }; |
| | | |
| | | const persistShortcuts = () => { |
| | | localStorage.setItem( |
| | | SHORTCUT_STORAGE_KEY, |
| | | JSON.stringify(shortcuts.slice(0, 6).map(({ label, path }) => ({ label, path }))) |
| | | ); |
| | | }; |
| | | |
| | | const todos = reactive([]); |
| | | |
| | | const businessFocus = reactive([ |
| | | { name: "çäº§è®¢åæ»æ°", value: "-" }, |
| | | { name: "å·²å®æè®¢åæ°", value: "-" }, |
| | | { name: "æªå®æè®¢åæ°", value: "-" }, |
| | | { name: "é¨åå®æè®¢åæ°", value: "-" }, |
| | | { name: "è´¨æ£æ»æ°", value: "-" }, |
| | | { name: "è¿ç¨æ£æ»æ°", value: "-" }, |
| | | ]); |
| | | |
| | | const pendingTasks = reactive([]); |
| | | const warningList = reactive([]); |
| | | const SECTION_CONFIG_KEY = "home-sections-v1"; |
| | | const sectionConfigOptions = [ |
| | | { key: "trendCards", label: "æè¿7天è¶å¿å¡" }, |
| | | { key: "planTrend", label: "计åä¸ç产è¶å¿å¾" }, |
| | | { key: "qualityChart", label: "è´¨æ£å¼å¸¸åå¸å¾" }, |
| | | { key: "costChart", label: "è½è䏿æ¬ç»æå¾" }, |
| | | { key: "warningCenter", label: "å¼å¸¸é¢è¦ä¸å¿" }, |
| | | { key: "planTable", label: "çäº§è®¡åæ§è¡æç»è¡¨" }, |
| | | ]; |
| | | const enabledSectionKeys = ref(sectionConfigOptions.map((i) => i.key)); |
| | | |
| | | const chartStyle = { width: "100%", height: "100%" }; |
| | | const grid = { left: "3%", right: "4%", bottom: "3%", containLabel: true }; |
| | | const lineTooltip = { trigger: "axis" }; |
| | | const barTooltip = { trigger: "axis", axisPointer: { type: "shadow" } }; |
| | | const pieTooltip = { trigger: "item" }; |
| | | |
| | | const valueYAxis = [{ type: "value" }]; |
| | | const planXAxis = [{ type: "category", data: [] }]; |
| | | const qualityXAxis = [{ type: "category", data: [] }]; |
| | | |
| | | const planLegend = { show: true, data: ["计åé", "ä¸åé", "宿é"] }; |
| | | const costLegend = { |
| | | show: true, |
| | | orient: "vertical", |
| | | right: 10, |
| | | top: "center", |
| | | data: ["è½èææ¬", "çäº§ææ¬", "è´¨éæå¤±ææ¬", "å
¶ä»ææ¬"], |
| | | }; |
| | | |
| | | const planSeries = reactive([ |
| | | { name: "计åé", type: "line", smooth: true, data: [] }, |
| | | { name: "ä¸åé", type: "line", smooth: true, data: [] }, |
| | | { name: "宿é", type: "line", smooth: true, data: [] }, |
| | | ]); |
| | | |
| | | const qualitySeries = reactive([ |
| | | { |
| | | name: "å¼å¸¸æ°", |
| | | type: "bar", |
| | | barWidth: 26, |
| | | itemStyle: { color: "#e67e22", borderRadius: [6, 6, 0, 0] }, |
| | | data: [], |
| | | }, |
| | | ]); |
| | | |
| | | const costSeries = reactive([ |
| | | { |
| | | type: "pie", |
| | | radius: ["45%", "68%"], |
| | | center: ["35%", "50%"], |
| | | label: { formatter: "{b}: {d}%" }, |
| | | data: [], |
| | | }, |
| | | ]); |
| | | |
| | | const planTable = reactive([]); |
| | | const recentTrendCards = reactive([ |
| | | { key: "planIssued", label: "计åä¸åé", unit: "å", values: [0, 0, 0, 0, 0, 0, 0], latest: 0, change: 0 }, |
| | | { key: "qualityRaw", label: "æ¥ææ£æ°é", unit: "æ¡", values: [0, 0, 0, 0, 0, 0, 0], latest: 0, change: 0 }, |
| | | { key: "qualityProcess", label: "è¿ç¨æ£æ°é", unit: "æ¡", values: [0, 0, 0, 0, 0, 0, 0], latest: 0, change: 0 }, |
| | | { key: "qualityFactory", label: "æåæ£æ°é", unit: "æ¡", values: [0, 0, 0, 0, 0, 0, 0], latest: 0, change: 0 }, |
| | | ]); |
| | | |
| | | const toNumber = (value) => { |
| | | const num = Number(value); |
| | | return Number.isFinite(num) ? num : 0; |
| | | }; |
| | | |
| | | const pickFirstNumber = (obj, keys = []) => { |
| | | for (const key of keys) { |
| | | if (obj && obj[key] !== undefined && obj[key] !== null) return toNumber(obj[key]); |
| | | } |
| | | return 0; |
| | | }; |
| | | |
| | | const updateArray = (target, list) => { |
| | | target.splice(0, target.length, ...list); |
| | | }; |
| | | |
| | | const toFixedOne = (num) => Number(num || 0).toFixed(1); |
| | | |
| | | const normalizeSeven = (list = []) => { |
| | | const nums = list.map((i) => toNumber(i)); |
| | | if (nums.length >= 7) return nums.slice(-7); |
| | | return [...Array(7 - nums.length).fill(0), ...nums]; |
| | | }; |
| | | |
| | | const calcTrend = (list = []) => { |
| | | if (!Array.isArray(list) || list.length === 0) return { latest: 0, change: 0 }; |
| | | const first = toNumber(list[0]); |
| | | const latest = toNumber(list[list.length - 1]); |
| | | if (first === 0) return { latest, change: latest > 0 ? 100 : 0 }; |
| | | return { latest, change: ((latest - first) / first) * 100 }; |
| | | }; |
| | | |
| | | const setTrendCard = (key, values) => { |
| | | const target = recentTrendCards.find((i) => i.key === key); |
| | | if (!target) return; |
| | | const series = normalizeSeven(values); |
| | | const { latest, change } = calcTrend(series); |
| | | target.values = series; |
| | | target.latest = latest; |
| | | target.change = Number(toFixedOne(change)); |
| | | }; |
| | | |
| | | const trendClass = (change) => (change > 0 ? "up" : change < 0 ? "down" : "flat"); |
| | | |
| | | const calcBarHeight = (value, list) => { |
| | | const max = Math.max(...list, 1); |
| | | return Math.max(18, Math.round((toNumber(value) / max) * 100)); |
| | | }; |
| | | |
| | | const filteredPendingTasks = computed(() => { |
| | | if (pendingFilter.value === "high") return pendingTasks.filter((i) => i.level === "é«"); |
| | | if (pendingFilter.value === "mine") { |
| | | const currentUserName = String(userStore?.name || "").toLowerCase(); |
| | | const currentUserId = String(userStore?.userId || ""); |
| | | return pendingTasks.filter((i) => { |
| | | const ownerName = String(i.ownerName || "").toLowerCase(); |
| | | const ownerId = String(i.ownerId || ""); |
| | | return (currentUserName && ownerName && ownerName.includes(currentUserName)) || (currentUserId && ownerId === currentUserId); |
| | | }); |
| | | } |
| | | return pendingTasks; |
| | | }); |
| | | |
| | | const isSectionVisible = (key) => enabledSectionKeys.value.includes(key); |
| | | |
| | | const goTo = (path) => { |
| | | if (!isRouteValid(path)) { |
| | | ElMessage.warning("å½åèåæªé
ç½®è¯¥é¡µé¢ææ è®¿é®æé"); |
| | | return; |
| | | } |
| | | router.push(path); |
| | | }; |
| | | |
| | | const handleTrendCardClick = (card) => { |
| | | const mapping = { |
| | | planIssued: routePathMap.plan || routePathMap.order, |
| | | qualityRaw: routePathMap.processInspection, |
| | | qualityProcess: routePathMap.processInspection, |
| | | qualityFactory: routePathMap.processInspection, |
| | | }; |
| | | const target = mapping[card.key]; |
| | | if (!target) { |
| | | ElMessage.warning("æªé
ç½®å¯è·³è½¬é¡µé¢"); |
| | | return; |
| | | } |
| | | const query = |
| | | card.key === "planIssued" |
| | | ? { dateType: String(chartRangePlan.value), source: "homeTrend" } |
| | | : { dateType: String(chartRangeQuality.value), source: "homeTrend" }; |
| | | router.push({ path: target, query }); |
| | | }; |
| | | |
| | | const openShortcutDialog = () => { |
| | | shortcutDialogVisible.value = true; |
| | | }; |
| | | |
| | | const addShortcutBySelect = () => { |
| | | if (shortcuts.length >= 6) { |
| | | ElMessage.warning("å¿«æ·å
¥å£æå¤åªè½æ·»å 6个"); |
| | | return; |
| | | } |
| | | if (!selectedPagePath.value) { |
| | | ElMessage.warning("请å
鿩页é¢"); |
| | | return; |
| | | } |
| | | if (shortcuts.some((item) => item.path === selectedPagePath.value)) { |
| | | ElMessage.warning("该快æ·å
¥å£å·²åå¨"); |
| | | return; |
| | | } |
| | | const label = selectableMenuMap.value.get(selectedPagePath.value); |
| | | if (!label) { |
| | | ElMessage.warning("è¯·éæ©å¯æ·»å ç页é¢ï¼ç®å½èç¹ä¸å¯é"); |
| | | return; |
| | | } |
| | | shortcuts.push({ |
| | | label, |
| | | path: selectedPagePath.value, |
| | | invalid: !isRouteValid(selectedPagePath.value), |
| | | }); |
| | | persistShortcuts(); |
| | | selectedPagePath.value = ""; |
| | | }; |
| | | |
| | | const removeShortcut = (index) => { |
| | | shortcuts.splice(index, 1); |
| | | persistShortcuts(); |
| | | ElMessage.success("å·²å é¤å¿«æ·å
¥å£"); |
| | | }; |
| | | |
| | | const loadHomeTodos = async () => { |
| | | try { |
| | | const res = await homeTodos(); |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | const mapped = list.slice(0, 4).map((item, idx) => { |
| | | const text = item?.approveReason || item?.approveTypeName || `å¾
å¤çäºé¡¹ ${idx + 1}`; |
| | | const levelType = idx === 0 ? "danger" : idx <= 2 ? "warning" : "success"; |
| | | const level = idx === 0 ? "é«" : idx <= 2 ? "ä¸" : "ä½"; |
| | | return { level, title: text, type: levelType }; |
| | | }); |
| | | updateArray(todos, mapped); |
| | | const pendingMapped = list.slice(0, 4).map((item, idx) => { |
| | | const title = item?.approveReason || item?.approveTypeName || `å¾
å¤çäºé¡¹ ${idx + 1}`; |
| | | const path = inferTodoPath(item); |
| | | return { |
| | | id: item?.id || `${idx}-${title}`, |
| | | title, |
| | | level: idx === 0 ? "é«" : idx <= 2 ? "ä¸" : "ä½", |
| | | type: idx === 0 ? "danger" : idx <= 2 ? "warning" : "success", |
| | | path, |
| | | ownerId: item?.approveUserId || item?.userId || "", |
| | | ownerName: item?.approveUserName || item?.userName || "", |
| | | }; |
| | | }); |
| | | updateArray(pendingTasks, pendingMapped); |
| | | } catch (error) { |
| | | console.error("homeTodosæ¥å£è·å失败:", error); |
| | | } |
| | | }; |
| | | |
| | | const loadOrderAndProgress = async () => { |
| | | try { |
| | | const [orderRes, progressRes] = await Promise.allSettled([orderCount(), getProgressStatistics()]); |
| | | |
| | | if (orderRes.status === "fulfilled") { |
| | | const items = Array.isArray(orderRes.value?.data) ? orderRes.value.data : []; |
| | | const byName = Object.fromEntries( |
| | | items.map((i) => [String(i?.name || "").replace(/\s/g, ""), i?.value]) |
| | | ); |
| | | businessFocus[0].value = `${pickFirstNumber(byName, ["çäº§è®¢åæ°", "çäº§è®¢åæ»æ°", "æ»è®¢åæ°"]) || 0} å`; |
| | | businessFocus[1].value = `${pickFirstNumber(byName, ["å·²å®æè®¢åæ°"]) || 0} å`; |
| | | businessFocus[2].value = `${pickFirstNumber(byName, ["å¾
çäº§è®¢åæ°", "æªå®æè®¢åæ°"]) || 0} å`; |
| | | businessFocus[3].value = `${pickFirstNumber(byName, ["é¨åå®æè®¢åæ°"]) || 0} å`; |
| | | const handleTrendCardClick = card => { |
| | | const mapping = { |
| | | planIssued: routePathMap.plan || routePathMap.order, |
| | | qualityRaw: routePathMap.processInspection, |
| | | qualityProcess: routePathMap.processInspection, |
| | | qualityFactory: routePathMap.processInspection, |
| | | }; |
| | | const target = mapping[card.key]; |
| | | if (!target) { |
| | | ElMessage.warning("æªé
ç½®å¯è·³è½¬é¡µé¢"); |
| | | return; |
| | | } |
| | | const query = |
| | | card.key === "planIssued" |
| | | ? { dateType: String(chartRangePlan.value), source: "homeTrend" } |
| | | : { dateType: String(chartRangeQuality.value), source: "homeTrend" }; |
| | | router.push({ path: target, query }); |
| | | }; |
| | | |
| | | if (progressRes.status === "fulfilled") { |
| | | const p = progressRes.value?.data || {}; |
| | | const detail = Array.isArray(p.completedOrderDetails) ? p.completedOrderDetails : []; |
| | | const rows = detail.slice(0, 6).map((item, index) => { |
| | | const qty = pickFirstNumber(item, ["quantity", "planQuantity"]); |
| | | const done = pickFirstNumber(item, ["completeQuantity", "completedQuantity"]); |
| | | const openShortcutDialog = () => { |
| | | shortcutDialogVisible.value = true; |
| | | }; |
| | | |
| | | const addShortcutBySelect = () => { |
| | | if (shortcuts.length >= 6) { |
| | | ElMessage.warning("å¿«æ·å
¥å£æå¤åªè½æ·»å 6个"); |
| | | return; |
| | | } |
| | | if (!selectedPagePath.value) { |
| | | ElMessage.warning("请å
鿩页é¢"); |
| | | return; |
| | | } |
| | | if (shortcuts.some(item => item.path === selectedPagePath.value)) { |
| | | ElMessage.warning("该快æ·å
¥å£å·²åå¨"); |
| | | return; |
| | | } |
| | | const label = selectableMenuMap.value.get(selectedPagePath.value); |
| | | if (!label) { |
| | | ElMessage.warning("è¯·éæ©å¯æ·»å ç页é¢ï¼ç®å½èç¹ä¸å¯é"); |
| | | return; |
| | | } |
| | | shortcuts.push({ |
| | | label, |
| | | path: selectedPagePath.value, |
| | | invalid: !isRouteValid(selectedPagePath.value), |
| | | }); |
| | | persistShortcuts(); |
| | | selectedPagePath.value = ""; |
| | | }; |
| | | |
| | | const removeShortcut = index => { |
| | | shortcuts.splice(index, 1); |
| | | persistShortcuts(); |
| | | ElMessage.success("å·²å é¤å¿«æ·å
¥å£"); |
| | | }; |
| | | |
| | | const loadHomeTodos = async () => { |
| | | try { |
| | | const res = await homeTodos(); |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | const mapped = list.slice(0, 4).map((item, idx) => { |
| | | const text = |
| | | item?.approveReason || item?.approveTypeName || `å¾
å¤çäºé¡¹ ${idx + 1}`; |
| | | const levelType = idx === 0 ? "danger" : idx <= 2 ? "warning" : "success"; |
| | | const level = idx === 0 ? "é«" : idx <= 2 ? "ä¸" : "ä½"; |
| | | return { level, title: text, type: levelType }; |
| | | }); |
| | | updateArray(todos, mapped); |
| | | const pendingMapped = list.slice(0, 4).map((item, idx) => { |
| | | const title = |
| | | item?.approveReason || item?.approveTypeName || `å¾
å¤çäºé¡¹ ${idx + 1}`; |
| | | const path = inferTodoPath(item); |
| | | return { |
| | | planNo: item.npsNo || item.productionPlanNo || `NO-${index + 1}`, |
| | | product: item.productCategory || item.productName || "-", |
| | | qty, |
| | | issued: done, |
| | | status: qty > 0 && done >= qty ? "已宿" : done > 0 ? "æ§è¡ä¸" : "å¾
ä¸å", |
| | | id: item?.id || `${idx}-${title}`, |
| | | title, |
| | | level: idx === 0 ? "é«" : idx <= 2 ? "ä¸" : "ä½", |
| | | type: idx === 0 ? "danger" : idx <= 2 ? "warning" : "success", |
| | | path, |
| | | ownerId: item?.approveUserId || item?.userId || "", |
| | | ownerName: item?.approveUserName || item?.userName || "", |
| | | }; |
| | | }); |
| | | updateArray(planTable, rows); |
| | | setTrendCard( |
| | | "planIssued", |
| | | detail.slice(-7).map((i) => pickFirstNumber(i, ["completeQuantity", "completedQuantity", "issueNum"])) |
| | | ); |
| | | |
| | | updateArray(pendingTasks, pendingMapped); |
| | | } catch (error) { |
| | | console.error("homeTodosæ¥å£è·å失败:", error); |
| | | } |
| | | } catch (error) { |
| | | console.error("orderCount/getProgressStatisticsæ¥å£è·å失败:", error); |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | const inferTodoPath = (todo) => { |
| | | const text = `${todo?.approveTypeName || ""}${todo?.approveReason || ""}`.toLowerCase(); |
| | | if (text.includes("计å")) return routePathMap.plan || routePathMap.order; |
| | | if (text.includes("订å")) return routePathMap.order || routePathMap.plan; |
| | | if (text.includes("è¿ç¨æ£") || text.includes("è´¨æ£")) return routePathMap.processInspection || routePathMap.plan; |
| | | if (text.includes("è½è") || text.includes("æè¡¨")) return routePathMap.meter || routePathMap.plan; |
| | | return routePathMap.plan || routePathMap.order || ""; |
| | | }; |
| | | const loadOrderAndProgress = async () => { |
| | | try { |
| | | const [orderRes, progressRes] = await Promise.allSettled([ |
| | | orderCount(), |
| | | getProgressStatistics(), |
| | | ]); |
| | | |
| | | const loadPlanTrend = async () => { |
| | | try { |
| | | const res = await processDataProductionStatistics({ type: chartRangePlan.value }); |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | planXAxis[0].data = list.map((i, index) => i.processName || `å·¥åº${index + 1}`); |
| | | planSeries[0].data = list.map((i) => pickFirstNumber(i, ["totalInput", "input", "planNum"])); |
| | | planSeries[1].data = list.map((i) => pickFirstNumber(i, ["totalOutput", "output", "issueNum"])); |
| | | planSeries[2].data = list.map((i) => pickFirstNumber(i, ["totalScrap", "scrap", "completeNum"])); |
| | | } catch (error) { |
| | | console.error("processDataProductionStatisticsæ¥å£è·å失败:", error); |
| | | } |
| | | }; |
| | | if (orderRes.status === "fulfilled") { |
| | | const items = Array.isArray(orderRes.value?.data) |
| | | ? orderRes.value.data |
| | | : []; |
| | | const byName = Object.fromEntries( |
| | | items.map(i => [String(i?.name || "").replace(/\s/g, ""), i?.value]) |
| | | ); |
| | | businessFocus[0].value = `${ |
| | | pickFirstNumber(byName, ["çäº§è®¢åæ°", "çäº§è®¢åæ»æ°", "æ»è®¢åæ°"]) || 0 |
| | | } å`; |
| | | businessFocus[1].value = `${ |
| | | pickFirstNumber(byName, ["å·²å®æè®¢åæ°"]) || 0 |
| | | } å`; |
| | | businessFocus[2].value = `${ |
| | | pickFirstNumber(byName, ["å¾
çäº§è®¢åæ°", "æªå®æè®¢åæ°"]) || 0 |
| | | } å`; |
| | | businessFocus[3].value = `${ |
| | | pickFirstNumber(byName, ["é¨åå®æè®¢åæ°"]) || 0 |
| | | } å`; |
| | | } |
| | | |
| | | const loadQualityData = async () => { |
| | | try { |
| | | const res = await qualityInspectionStatistics({ type: chartRangeQuality.value }); |
| | | const data = res?.data || {}; |
| | | const items = Array.isArray(data.item) ? data.item : []; |
| | | if (items.length > 0) { |
| | | qualityXAxis[0].data = items.map((i) => i.date || i.name || "-"); |
| | | qualitySeries[0].data = items.map((i) => |
| | | pickFirstNumber(i, ["supplierNum", "processNum", "factoryNum", "totalNum"]) |
| | | ); |
| | | setTrendCard("qualityRaw", items.map((i) => pickFirstNumber(i, ["supplierNum"]))); |
| | | setTrendCard("qualityProcess", items.map((i) => pickFirstNumber(i, ["processNum"]))); |
| | | setTrendCard("qualityFactory", items.map((i) => pickFirstNumber(i, ["factoryNum"]))); |
| | | } else { |
| | | qualityXAxis[0].data = ["æ¥ææ£", "è¿ç¨æ£", "æåæ£"]; |
| | | qualitySeries[0].data = [ |
| | | pickFirstNumber(data, ["supplierNum"]), |
| | | pickFirstNumber(data, ["processNum"]), |
| | | pickFirstNumber(data, ["factoryNum"]), |
| | | ]; |
| | | setTrendCard("qualityRaw", [pickFirstNumber(data, ["supplierNum"])]); |
| | | setTrendCard("qualityProcess", [pickFirstNumber(data, ["processNum"])]); |
| | | setTrendCard("qualityFactory", [pickFirstNumber(data, ["factoryNum"])]); |
| | | if (progressRes.status === "fulfilled") { |
| | | const p = progressRes.value?.data || {}; |
| | | const detail = Array.isArray(p.completedOrderDetails) |
| | | ? p.completedOrderDetails |
| | | : []; |
| | | const rows = detail.slice(0, 6).map((item, index) => { |
| | | const qty = pickFirstNumber(item, ["quantity", "planQuantity"]); |
| | | const done = pickFirstNumber(item, [ |
| | | "completeQuantity", |
| | | "completedQuantity", |
| | | ]); |
| | | return { |
| | | planNo: item.npsNo || item.productionPlanNo || `NO-${index + 1}`, |
| | | product: item.productCategory || item.productName || "-", |
| | | qty, |
| | | issued: done, |
| | | status: |
| | | qty > 0 && done >= qty ? "已宿" : done > 0 ? "æ§è¡ä¸" : "å¾
ä¸å", |
| | | }; |
| | | }); |
| | | updateArray(planTable, rows); |
| | | setTrendCard( |
| | | "planIssued", |
| | | detail |
| | | .slice(-7) |
| | | .map(i => |
| | | pickFirstNumber(i, [ |
| | | "completeQuantity", |
| | | "completedQuantity", |
| | | "issueNum", |
| | | ]) |
| | | ) |
| | | ); |
| | | } |
| | | } catch (error) { |
| | | console.error("orderCount/getProgressStatisticsæ¥å£è·å失败:", error); |
| | | } |
| | | businessFocus[4].value = `${pickFirstNumber(data, ["supplierNum", "totalNum"])} æ¡`; |
| | | businessFocus[5].value = `${pickFirstNumber(data, ["processNum"])} æ¡`; |
| | | } catch (error) { |
| | | console.error("qualityInspectionStatisticsæ¥å£è·å失败:", error); |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | const loadWarningCenter = async () => { |
| | | try { |
| | | const res = await nonComplianceWarning(); |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | const mapped = list.slice(0, 6).map((item, idx) => { |
| | | const levelNum = toNumber(item.level ?? item.warningLevel ?? 2); |
| | | const levelType = levelNum >= 3 ? "danger" : levelNum === 2 ? "warning" : "info"; |
| | | const levelText = levelNum >= 3 ? "é«" : levelNum === 2 ? "ä¸" : "ä½"; |
| | | const title = item.name || item.title || item.paramName || `å¼å¸¸é¢è¦ ${idx + 1}`; |
| | | const text = `${title}${item.processName || ""}${item.orderNo || ""}`.toLowerCase(); |
| | | const path = text.includes("è´¨æ£") |
| | | ? routePathMap.processInspection |
| | | : text.includes("订å") |
| | | const inferTodoPath = todo => { |
| | | const text = `${todo?.approveTypeName || ""}${ |
| | | todo?.approveReason || "" |
| | | }`.toLowerCase(); |
| | | if (text.includes("计å")) return routePathMap.plan || routePathMap.order; |
| | | if (text.includes("订å")) return routePathMap.order || routePathMap.plan; |
| | | if (text.includes("è¿ç¨æ£") || text.includes("è´¨æ£")) |
| | | return routePathMap.processInspection || routePathMap.plan; |
| | | if (text.includes("è½è") || text.includes("æè¡¨")) |
| | | return routePathMap.meter || routePathMap.plan; |
| | | return routePathMap.plan || routePathMap.order || ""; |
| | | }; |
| | | |
| | | const loadPlanTrend = async () => { |
| | | try { |
| | | const res = await processDataProductionStatistics({ |
| | | type: chartRangePlan.value, |
| | | }); |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | planXAxis[0].data = list.map( |
| | | (i, index) => i.processName || `å·¥åº${index + 1}` |
| | | ); |
| | | planSeries[0].data = list.map(i => |
| | | pickFirstNumber(i, ["totalInput", "input", "planNum"]) |
| | | ); |
| | | planSeries[1].data = list.map(i => |
| | | pickFirstNumber(i, ["totalOutput", "output", "issueNum"]) |
| | | ); |
| | | planSeries[2].data = list.map(i => |
| | | pickFirstNumber(i, ["totalScrap", "scrap", "completeNum"]) |
| | | ); |
| | | } catch (error) { |
| | | console.error("processDataProductionStatisticsæ¥å£è·å失败:", error); |
| | | } |
| | | }; |
| | | |
| | | const loadQualityData = async () => { |
| | | try { |
| | | const res = await qualityInspectionStatistics({ |
| | | type: chartRangeQuality.value, |
| | | }); |
| | | const data = res?.data || {}; |
| | | const items = Array.isArray(data.item) ? data.item : []; |
| | | if (items.length > 0) { |
| | | qualityXAxis[0].data = items.map(i => i.date || i.name || "-"); |
| | | qualitySeries[0].data = items.map(i => |
| | | pickFirstNumber(i, [ |
| | | "supplierNum", |
| | | "processNum", |
| | | "factoryNum", |
| | | "totalNum", |
| | | ]) |
| | | ); |
| | | setTrendCard( |
| | | "qualityRaw", |
| | | items.map(i => pickFirstNumber(i, ["supplierNum"])) |
| | | ); |
| | | setTrendCard( |
| | | "qualityProcess", |
| | | items.map(i => pickFirstNumber(i, ["processNum"])) |
| | | ); |
| | | setTrendCard( |
| | | "qualityFactory", |
| | | items.map(i => pickFirstNumber(i, ["factoryNum"])) |
| | | ); |
| | | } else { |
| | | qualityXAxis[0].data = ["æ¥ææ£", "è¿ç¨æ£", "æåæ£"]; |
| | | qualitySeries[0].data = [ |
| | | pickFirstNumber(data, ["supplierNum"]), |
| | | pickFirstNumber(data, ["processNum"]), |
| | | pickFirstNumber(data, ["factoryNum"]), |
| | | ]; |
| | | setTrendCard("qualityRaw", [pickFirstNumber(data, ["supplierNum"])]); |
| | | setTrendCard("qualityProcess", [pickFirstNumber(data, ["processNum"])]); |
| | | setTrendCard("qualityFactory", [pickFirstNumber(data, ["factoryNum"])]); |
| | | } |
| | | businessFocus[4].value = `${pickFirstNumber(data, [ |
| | | "supplierNum", |
| | | "totalNum", |
| | | ])} æ¡`; |
| | | businessFocus[5].value = `${pickFirstNumber(data, ["processNum"])} æ¡`; |
| | | } catch (error) { |
| | | console.error("qualityInspectionStatisticsæ¥å£è·å失败:", error); |
| | | } |
| | | }; |
| | | |
| | | const loadWarningCenter = async () => { |
| | | try { |
| | | const res = await nonComplianceWarning(); |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | const mapped = list.slice(0, 6).map((item, idx) => { |
| | | const levelNum = toNumber(item.level ?? item.warningLevel ?? 2); |
| | | const levelType = |
| | | levelNum >= 3 ? "danger" : levelNum === 2 ? "warning" : "info"; |
| | | const levelText = levelNum >= 3 ? "é«" : levelNum === 2 ? "ä¸" : "ä½"; |
| | | const title = |
| | | item.name || item.title || item.paramName || `å¼å¸¸é¢è¦ ${idx + 1}`; |
| | | const text = `${title}${item.processName || ""}${ |
| | | item.orderNo || "" |
| | | }`.toLowerCase(); |
| | | const path = text.includes("è´¨æ£") |
| | | ? routePathMap.processInspection |
| | | : text.includes("订å") |
| | | ? routePathMap.order |
| | | : routePathMap.processInspection || routePathMap.order || routePathMap.plan; |
| | | return { id: item.id || `${idx}-${title}`, levelType, levelText, title, path }; |
| | | }); |
| | | updateArray(warningList, mapped); |
| | | } catch (error) { |
| | | console.error("nonComplianceWarningæ¥å£è·å失败:", error); |
| | | updateArray(warningList, []); |
| | | } |
| | | }; |
| | | |
| | | const initSectionConfig = () => { |
| | | try { |
| | | const raw = localStorage.getItem(SECTION_CONFIG_KEY); |
| | | if (!raw) return; |
| | | const parsed = JSON.parse(raw); |
| | | if (Array.isArray(parsed) && parsed.length > 0) { |
| | | enabledSectionKeys.value = parsed.filter((k) => sectionConfigOptions.some((i) => i.key === k)); |
| | | : routePathMap.processInspection || |
| | | routePathMap.order || |
| | | routePathMap.plan; |
| | | return { |
| | | id: item.id || `${idx}-${title}`, |
| | | levelType, |
| | | levelText, |
| | | title, |
| | | path, |
| | | }; |
| | | }); |
| | | updateArray(warningList, mapped); |
| | | } catch (error) { |
| | | console.error("nonComplianceWarningæ¥å£è·å失败:", error); |
| | | updateArray(warningList, []); |
| | | } |
| | | } catch (error) { |
| | | console.error("读åé¦é¡µé
置失败:", error); |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | const saveSectionConfig = () => { |
| | | if (enabledSectionKeys.value.length === 0) { |
| | | ElMessage.warning("è³å°ä¿çä¸ä¸ªæ¨¡å"); |
| | | return; |
| | | } |
| | | localStorage.setItem(SECTION_CONFIG_KEY, JSON.stringify(enabledSectionKeys.value)); |
| | | configDialogVisible.value = false; |
| | | ElMessage.success("é¦é¡µé
置已ä¿å"); |
| | | }; |
| | | const initSectionConfig = () => { |
| | | try { |
| | | const raw = localStorage.getItem(SECTION_CONFIG_KEY); |
| | | if (!raw) return; |
| | | const parsed = JSON.parse(raw); |
| | | if (Array.isArray(parsed) && parsed.length > 0) { |
| | | enabledSectionKeys.value = parsed.filter(k => |
| | | sectionConfigOptions.some(i => i.key === k) |
| | | ); |
| | | } |
| | | } catch (error) { |
| | | console.error("读åé¦é¡µé
置失败:", error); |
| | | } |
| | | }; |
| | | |
| | | const loadCostComposition = async () => { |
| | | try { |
| | | const res = await expenseCompositionAnalysis({ type: 1 }); |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | const mapped = list.map((i) => ({ |
| | | name: i.name || "æªå½å", |
| | | value: pickFirstNumber(i, ["value", "amount", "cost"]), |
| | | })); |
| | | costSeries[0].data = mapped; |
| | | } catch (error) { |
| | | console.error("expenseCompositionAnalysisæ¥å£è·å失败:", error); |
| | | } |
| | | }; |
| | | const saveSectionConfig = () => { |
| | | if (enabledSectionKeys.value.length === 0) { |
| | | ElMessage.warning("è³å°ä¿çä¸ä¸ªæ¨¡å"); |
| | | return; |
| | | } |
| | | localStorage.setItem( |
| | | SECTION_CONFIG_KEY, |
| | | JSON.stringify(enabledSectionKeys.value) |
| | | ); |
| | | configDialogVisible.value = false; |
| | | ElMessage.success("é¦é¡µé
置已ä¿å"); |
| | | }; |
| | | |
| | | const refreshDashboardData = () => { |
| | | loadHomeTodos(); |
| | | loadOrderAndProgress(); |
| | | loadPlanTrend(); |
| | | loadQualityData(); |
| | | loadCostComposition(); |
| | | loadWarningCenter(); |
| | | lastUpdatedAt.value = new Date().toLocaleString(); |
| | | }; |
| | | const loadCostComposition = async () => { |
| | | try { |
| | | const res = await expenseCompositionAnalysis({ type: 1 }); |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | const mapped = list.map(i => ({ |
| | | name: i.name || "æªå½å", |
| | | value: pickFirstNumber(i, ["value", "amount", "cost"]), |
| | | })); |
| | | costSeries[0].data = mapped; |
| | | } catch (error) { |
| | | console.error("expenseCompositionAnalysisæ¥å£è·å失败:", error); |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | initSectionConfig(); |
| | | refreshDashboardData(); |
| | | }); |
| | | const refreshDashboardData = () => { |
| | | loadHomeTodos(); |
| | | loadOrderAndProgress(); |
| | | loadPlanTrend(); |
| | | loadQualityData(); |
| | | loadCostComposition(); |
| | | loadWarningCenter(); |
| | | lastUpdatedAt.value = new Date().toLocaleString(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // initSectionConfig(); |
| | | // refreshDashboardData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .home-page { |
| | | min-height: 100vh; |
| | | background: #f5f7fb; |
| | | padding: 20px; |
| | | } |
| | | .home-page { |
| | | min-height: 100vh; |
| | | background: #f5f7fb; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .top-bar { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 16px; |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 16px; |
| | | margin-bottom: 16px; |
| | | } |
| | | .top-bar { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 16px; |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 16px; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .top-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | .top-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .refresh-time { |
| | | font-size: 12px; |
| | | color: #7b8794; |
| | | } |
| | | .refresh-time { |
| | | font-size: 12px; |
| | | color: #7b8794; |
| | | } |
| | | |
| | | .user-box { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | .user-box { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .avatar { |
| | | width: 54px; |
| | | height: 54px; |
| | | border-radius: 50%; |
| | | object-fit: cover; |
| | | } |
| | | .avatar { |
| | | width: 54px; |
| | | height: 54px; |
| | | border-radius: 50%; |
| | | object-fit: cover; |
| | | } |
| | | |
| | | .hello { |
| | | font-size: 18px; |
| | | font-weight: 700; |
| | | color: #1f2d3d; |
| | | } |
| | | .hello { |
| | | font-size: 18px; |
| | | font-weight: 700; |
| | | color: #1f2d3d; |
| | | } |
| | | |
| | | .sub { |
| | | margin-top: 4px; |
| | | color: #6b7785; |
| | | font-size: 13px; |
| | | } |
| | | .sub { |
| | | margin-top: 4px; |
| | | color: #6b7785; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .content-grid { |
| | | display: grid; |
| | | grid-template-columns: 320px 1fr; |
| | | gap: 16px; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .left-col, |
| | | .right-col { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .section-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 16px; |
| | | margin-bottom: 16px; |
| | | box-shadow: 0 2px 10px rgba(20, 35, 90, 0.06); |
| | | } |
| | | |
| | | .flex-fill-card { |
| | | flex: 1; |
| | | } |
| | | |
| | | .section-title { |
| | | position: relative; |
| | | padding-left: 10px; |
| | | margin-bottom: 14px; |
| | | font-size: 16px; |
| | | font-weight: 700; |
| | | color: #243447; |
| | | } |
| | | |
| | | .section-title-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .section-title::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 4px; |
| | | width: 4px; |
| | | height: 16px; |
| | | border-radius: 2px; |
| | | background: #409eff; |
| | | } |
| | | |
| | | .quick-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 10px; |
| | | } |
| | | |
| | | .quick-grid :deep(.el-button) { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .shortcut-form-row { |
| | | display: grid; |
| | | grid-template-columns: 1fr 1.5fr auto; |
| | | gap: 10px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .todo-row { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | margin-bottom: 10px; |
| | | font-size: 13px; |
| | | color: #3b4a5b; |
| | | } |
| | | |
| | | .focus-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 8px 0; |
| | | border-bottom: 1px dashed #e8edf5; |
| | | } |
| | | |
| | | .focus-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .focus-name { |
| | | font-size: 13px; |
| | | color: #516174; |
| | | } |
| | | |
| | | .focus-value { |
| | | font-weight: 700; |
| | | color: #1f2d3d; |
| | | } |
| | | |
| | | .task-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 10px; |
| | | padding: 8px 0; |
| | | border-bottom: 1px dashed #e8edf5; |
| | | } |
| | | |
| | | .task-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .task-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .task-title { |
| | | font-size: 13px; |
| | | color: #3d4d5f; |
| | | } |
| | | |
| | | .row-two { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 16px; |
| | | } |
| | | |
| | | .trend-cards { |
| | | display: grid; |
| | | grid-template-columns: repeat(4, minmax(0, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | |
| | | .trend-card { |
| | | border: 1px solid #e8edf5; |
| | | border-radius: 10px; |
| | | padding: 12px; |
| | | } |
| | | |
| | | .trend-card.clickable { |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | } |
| | | |
| | | .trend-card.clickable:hover { |
| | | border-color: #8eb8ff; |
| | | background: #f6f9ff; |
| | | } |
| | | |
| | | .trend-head { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .trend-label { |
| | | font-size: 13px; |
| | | color: #5f6b7a; |
| | | } |
| | | |
| | | .trend-rate { |
| | | font-size: 12px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .trend-rate.up { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .trend-rate.down { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .trend-rate.flat { |
| | | color: #909399; |
| | | } |
| | | |
| | | .trend-value { |
| | | margin-top: 6px; |
| | | font-size: 20px; |
| | | color: #1f2d3d; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .sparkline { |
| | | margin-top: 10px; |
| | | height: 48px; |
| | | display: flex; |
| | | align-items: flex-end; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .sparkline-bar { |
| | | flex: 1; |
| | | min-height: 6px; |
| | | border-radius: 3px 3px 0 0; |
| | | background: linear-gradient(180deg, #82b1ff 0%, #409eff 100%); |
| | | } |
| | | |
| | | .warning-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 10px; |
| | | padding: 8px 0; |
| | | border-bottom: 1px dashed #e8edf5; |
| | | } |
| | | |
| | | .warning-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .warning-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .warning-title { |
| | | font-size: 13px; |
| | | color: #3d4d5f; |
| | | } |
| | | |
| | | .config-check-group { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 10px 16px; |
| | | } |
| | | |
| | | .mini-table-wrap :deep(.el-table th) { |
| | | background: #f8fbff; |
| | | } |
| | | |
| | | @media (max-width: 1100px) { |
| | | .content-grid { |
| | | grid-template-columns: 1fr; |
| | | display: grid; |
| | | grid-template-columns: 320px 1fr; |
| | | gap: 16px; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .left-col, |
| | | .right-col { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .section-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 16px; |
| | | margin-bottom: 16px; |
| | | box-shadow: 0 2px 10px rgba(20, 35, 90, 0.06); |
| | | } |
| | | |
| | | .flex-fill-card { |
| | | flex: 1; |
| | | } |
| | | |
| | | .section-title { |
| | | position: relative; |
| | | padding-left: 10px; |
| | | margin-bottom: 14px; |
| | | font-size: 16px; |
| | | font-weight: 700; |
| | | color: #243447; |
| | | } |
| | | |
| | | .section-title-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .section-title::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 4px; |
| | | width: 4px; |
| | | height: 16px; |
| | | border-radius: 2px; |
| | | background: #409eff; |
| | | } |
| | | |
| | | .quick-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 10px; |
| | | } |
| | | |
| | | .quick-grid :deep(.el-button) { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .shortcut-form-row { |
| | | display: grid; |
| | | grid-template-columns: 1fr 1.5fr auto; |
| | | gap: 10px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .todo-row { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | margin-bottom: 10px; |
| | | font-size: 13px; |
| | | color: #3b4a5b; |
| | | } |
| | | |
| | | .focus-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 8px 0; |
| | | border-bottom: 1px dashed #e8edf5; |
| | | } |
| | | |
| | | .focus-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .focus-name { |
| | | font-size: 13px; |
| | | color: #516174; |
| | | } |
| | | |
| | | .focus-value { |
| | | font-weight: 700; |
| | | color: #1f2d3d; |
| | | } |
| | | |
| | | .task-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 10px; |
| | | padding: 8px 0; |
| | | border-bottom: 1px dashed #e8edf5; |
| | | } |
| | | |
| | | .task-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .task-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .task-title { |
| | | font-size: 13px; |
| | | color: #3d4d5f; |
| | | } |
| | | |
| | | .row-two { |
| | | grid-template-columns: 1fr; |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 16px; |
| | | } |
| | | |
| | | .trend-cards { |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | display: grid; |
| | | grid-template-columns: repeat(4, minmax(0, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | } |
| | | |
| | | .trend-card { |
| | | border: 1px solid #e8edf5; |
| | | border-radius: 10px; |
| | | padding: 12px; |
| | | } |
| | | |
| | | .trend-card.clickable { |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | } |
| | | |
| | | .trend-card.clickable:hover { |
| | | border-color: #8eb8ff; |
| | | background: #f6f9ff; |
| | | } |
| | | |
| | | .trend-head { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .trend-label { |
| | | font-size: 13px; |
| | | color: #5f6b7a; |
| | | } |
| | | |
| | | .trend-rate { |
| | | font-size: 12px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .trend-rate.up { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .trend-rate.down { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .trend-rate.flat { |
| | | color: #909399; |
| | | } |
| | | |
| | | .trend-value { |
| | | margin-top: 6px; |
| | | font-size: 20px; |
| | | color: #1f2d3d; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .sparkline { |
| | | margin-top: 10px; |
| | | height: 48px; |
| | | display: flex; |
| | | align-items: flex-end; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .sparkline-bar { |
| | | flex: 1; |
| | | min-height: 6px; |
| | | border-radius: 3px 3px 0 0; |
| | | background: linear-gradient(180deg, #82b1ff 0%, #409eff 100%); |
| | | } |
| | | |
| | | .warning-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 10px; |
| | | padding: 8px 0; |
| | | border-bottom: 1px dashed #e8edf5; |
| | | } |
| | | |
| | | .warning-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .warning-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .warning-title { |
| | | font-size: 13px; |
| | | color: #3d4d5f; |
| | | } |
| | | |
| | | .config-check-group { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 10px 16px; |
| | | } |
| | | |
| | | .mini-table-wrap :deep(.el-table th) { |
| | | background: #f8fbff; |
| | | } |
| | | |
| | | @media (max-width: 1100px) { |
| | | .content-grid { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | .row-two { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | .trend-cards { |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | } |
| | | } |
| | | </style> |
| | |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 追踪è¿åº¦å¼¹çª --> |
| | | <el-dialog v-model="showTrackProgressDialog" |
| | | destroy-on-close |
| | | :title="`追踪è¿åº¦ - ${trackProgressForm.materialCode || ''}`" |
| | | width="600px"> |
| | | <el-form :model="trackProgressForm" |
| | | label-width="120px"> |
| | | <el-form-item label="ç©æç¼ç "> |
| | | <el-input v-model="trackProgressForm.materialCode" |
| | | disabled /> |
| | | </el-form-item> |
| | | <el-form-item label="å½åç¶æ"> |
| | | <el-select v-model="trackProgressForm.currentStatus" |
| | | placeholder="è¯·éæ©ç¶æ"> |
| | | <el-option label="å¾
å¤ç" |
| | | value="pending" /> |
| | | <el-option label="è¿è¡ä¸" |
| | | value="processing" /> |
| | | <el-option label="已宿" |
| | | value="completed" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="宿è¿åº¦"> |
| | | <el-progress :percentage="trackProgressForm.completionRate" |
| | | :status="trackProgressForm.completionRate === 100 ? 'success' : ''" /> |
| | | </el-form-item> |
| | | <el-form-item label="è¿åº¦è¯¦æ
"> |
| | | <el-table :data="trackProgressForm.progressDetails" |
| | | border |
| | | style="width: 100%"> |
| | | <el-table-column prop="step" |
| | | label="æ¥éª¤" |
| | | align="center" |
| | | width="100" /> |
| | | <el-table-column prop="status" |
| | | label="ç¶æ" |
| | | align="center" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'completed' ? 'success' : scope.row.status === 'processing' ? 'warning' : 'info'"> |
| | | {{ scope.row.status === 'completed' ? '已宿' : scope.row.status === 'processing' ? 'è¿è¡ä¸' : 'å¾
å¼å§' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="startTime" |
| | | label="å¼å§æ¶é´" |
| | | align="center" |
| | | width="180" /> |
| | | <el-table-column prop="endTime" |
| | | label="ç»ææ¶é´" |
| | | align="center" |
| | | width="180" /> |
| | | </el-table> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="trackProgressForm.remark" |
| | | type="textarea" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="showTrackProgressDialog = false">å
³é</el-button> |
| | | <el-button type="primary" |
| | | @click="handleUpdateProgress">æ´æ°è¿åº¦</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 导å
¥å¼¹çª --> |
| | | <ImportDialog ref="importDialogRef" |
| | | v-model="importDialogVisible" |
| | |
| | | import ImportDialog from "@/components/Dialog/ImportDialog.vue"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import { useDict } from "@/utils/dict"; |
| | | import { useRouter } from "vue-router"; |
| | | import { |
| | | productionPlanListPage, |
| | | loadProdData, |
| | |
| | | } from "@/api/basicData/newProduct.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const router = useRouter(); |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | |
| | | strength: "", |
| | | }); |
| | | |
| | | // 追踪è¿åº¦å¼¹çªæ§å¶ |
| | | const showTrackProgressDialog = ref(false); |
| | | // 追踪è¿åº¦è¡¨åæ°æ® |
| | | const trackProgressForm = reactive({ |
| | | materialCode: "", |
| | | currentStatus: "", |
| | | completionRate: 0, |
| | | progressDetails: [], |
| | | remark: "", |
| | | }); |
| | | |
| | | // 导å
¥ç¸å
³ |
| | | const importDialogRef = ref(null); |
| | | const importDialogVisible = ref(false); |
| | |
| | | |
| | | // å¤ç追踪è¿åº¦æé®ç¹å» |
| | | const handleTrackProgress = row => { |
| | | // è®¾ç½®è¡¨åæ°æ® |
| | | trackProgressForm.materialCode = row.materialCode; |
| | | trackProgressForm.currentStatus = row.status; |
| | | |
| | | // çææ¨¡æè¿åº¦æ°æ® |
| | | trackProgressForm.progressDetails = generateProgressDetails(row.status); |
| | | |
| | | // 计ç®å®æç |
| | | trackProgressForm.completionRate = calculateCompletionRate( |
| | | trackProgressForm.progressDetails |
| | | ); |
| | | trackProgressForm.remark = ""; |
| | | |
| | | // æå¼å¼¹çª |
| | | showTrackProgressDialog.value = true; |
| | | // 跳转å°è¿½è¸ªè¿åº¦é¡µé¢ |
| | | router.push({ |
| | | path: "/productionPlan/trackProgress", |
| | | query: { |
| | | row: encodeURIComponent(JSON.stringify(row)), |
| | | }, |
| | | }); |
| | | }; |
| | | const onBlur = value => { |
| | | // éå¶åä½å°æ° |
| | |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // çææ¨¡æè¿åº¦è¯¦æ
æ°æ® |
| | | const generateProgressDetails = status => { |
| | | const details = [ |
| | | { |
| | | step: "计å确认", |
| | | status: "completed", |
| | | startTime: "2026-03-01 09:00:00", |
| | | endTime: "2026-03-01 10:00:00", |
| | | }, |
| | | { |
| | | step: "ç©æåå¤", |
| | | status: |
| | | status === "completed" |
| | | ? "completed" |
| | | : status === "processing" |
| | | ? "completed" |
| | | : "pending", |
| | | startTime: |
| | | status === "completed" || status === "processing" |
| | | ? "2026-03-01 10:30:00" |
| | | : "", |
| | | endTime: |
| | | status === "completed" || status === "processing" |
| | | ? "2026-03-02 16:00:00" |
| | | : "", |
| | | }, |
| | | { |
| | | step: "ç产å å·¥", |
| | | status: |
| | | status === "completed" |
| | | ? "completed" |
| | | : status === "processing" |
| | | ? "processing" |
| | | : "pending", |
| | | startTime: |
| | | status === "completed" || status === "processing" |
| | | ? "2026-03-03 08:00:00" |
| | | : "", |
| | | endTime: status === "completed" ? "2026-03-08 17:00:00" : "", |
| | | }, |
| | | { |
| | | step: "è´¨éæ£éª", |
| | | status: status === "completed" ? "completed" : "pending", |
| | | startTime: status === "completed" ? "2026-03-09 09:00:00" : "", |
| | | endTime: status === "completed" ? "2026-03-09 15:00:00" : "", |
| | | }, |
| | | { |
| | | step: "å
¥åº", |
| | | status: status === "completed" ? "completed" : "pending", |
| | | startTime: status === "completed" ? "2026-03-10 10:00:00" : "", |
| | | endTime: status === "completed" ? "2026-03-10 11:00:00" : "", |
| | | }, |
| | | ]; |
| | | return details; |
| | | }; |
| | | |
| | | // 计ç®å®æç |
| | | const calculateCompletionRate = details => { |
| | | const completedSteps = details.filter( |
| | | step => step.status === "completed" |
| | | ).length; |
| | | return Math.round((completedSteps / details.length) * 100); |
| | | }; |
| | | |
| | | // å¤çè¿åº¦æ´æ° |
| | | const handleUpdateProgress = () => { |
| | | // è¿éå¯ä»¥æ·»å æ´æ°è¿åº¦çé»è¾ |
| | | ElMessage.success("è¿åº¦æ´æ°æå"); |
| | | showTrackProgressDialog.value = false; |
| | | }; |
| | | |
| | | const data = reactive({ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="ç产计å追踪è¿åº¦"> |
| | | </PageHeader> |
| | | <el-card style="height:82vh;overflow:auto;"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ç³è¯·åç¼å· - {{ rowData.applyNo || '' }}</span> |
| | | </div> |
| | | </template> |
| | | <!-- åºç¡ä¿¡æ¯ --> |
| | | <div class="detail-section"> |
| | | <h3 class="section-title">åºç¡ä¿¡æ¯</h3> |
| | | <el-descriptions :column="3" |
| | | border> |
| | | <el-descriptions-item label="ç³è¯·åç¼å·">{{ rowData.applyNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="产ååç§°">{{ rowData.productName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="产åè§æ ¼">{{ rowData.model || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç©æç¼ç ">{{ rowData.materialCode || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸åæ°é">{{ rowData.assignedQuantity || 0 }} <span class="unit">æ¹</span></el-descriptions-item> |
| | | <el-descriptions-item label="å½åç¶æ"> |
| | | <el-tag :type="getStatusType(rowData.status)"> |
| | | {{ getStatusText(rowData.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | <div class="progress-container"> |
| | | <div class="progress-section"> |
| | | <h3 class="section-title">è¿åº¦ä¿¡æ¯</h3> |
| | | <div class="progress-item"> |
| | | <div class="progress-label">宿è¿åº¦ï¼</div> |
| | | <div class="progress-content"> |
| | | <el-progress :percentage="trackProgressForm.completionRate" |
| | | :color="customColors" |
| | | :status="trackProgressForm.completionRate === 100 ? 'success' : ''" /> |
| | | </div> |
| | | </div> |
| | | <div class="progress-item"> |
| | | <div class="progress-label">è¿åº¦è¯¦æ
ï¼</div> |
| | | <div class="progress-content"> |
| | | <el-table :data="trackProgressForm.progressDetails" |
| | | border |
| | | style="width: auto; height: 300px"> |
| | | <el-table-column prop="step" |
| | | label="æ¥éª¤ï¼ç¹å»æ¥ç详æ
ï¼" |
| | | align="center"> |
| | | <template #default="{ row, $index }"> |
| | | <el-link v-if="$index!=0" |
| | | @click="handleClickStep(row)" |
| | | type="primary">{{ row.step }}</el-link> |
| | | <span v-else |
| | | @click="handleClickStep(row)">{{ row.step }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" |
| | | label="ç¶æ" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'completed' ? 'success' : scope.row.status === 'processing' ? 'warning' : 'info'"> |
| | | {{ scope.row.status === 'completed' ? '已宿' : scope.row.status === 'processing' ? 'è¿è¡ä¸' : 'å¾
å¼å§' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="quantity" |
| | | label="æ°é" |
| | | align="center" /> |
| | | <el-table-column prop="startTime" |
| | | label="æ¶é´" |
| | | align="center" /> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="progress-section"> |
| | | <h3 class="section-title">订åä¿¡æ¯</h3> |
| | | <div v-for="item in rowData.orderList" |
| | | :key="item.orderNo" |
| | | class="order-item"> |
| | | <el-descriptions :column="3" |
| | | border> |
| | | <el-descriptions-item label="订åç¼å·">{{ item.orderNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="订åç¶æ"> |
| | | <el-tag :type="getStatusType(item.status)">{{ getStatusText(item.status) }}</el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å¼å§æ¥æ">{{ item.startTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="éæ±æ°é">{{ item.quantity || 0 }} <span class="unit">æ¹</span></el-descriptions-item> |
| | | <el-descriptions-item label="宿æ°é">{{ item.completeQuantity || 0 }} <span class="unit">æ¹</span></el-descriptions-item> |
| | | <el-descriptions-item label="宿è¿åº¦"> |
| | | <el-progress :percentage="item.completionRate" |
| | | :color="customColors(item.completionRate)" |
| | | :status="item.completionRate === 100 ? 'success' : ''" |
| | | style="width: 120px;" /> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { useRouter, useRoute } from "vue-router"; |
| | | |
| | | const router = useRouter(); |
| | | const route = useRoute(); |
| | | |
| | | // è·¯ç±åæ°æ°æ® |
| | | const rowData = reactive({}); |
| | | |
| | | // 追踪è¿åº¦è¡¨åæ°æ® |
| | | const trackProgressForm = reactive({ |
| | | materialCode: "", |
| | | currentStatus: "", |
| | | completionRate: 0, |
| | | progressDetails: [], |
| | | remark: "", |
| | | }); |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = status => { |
| | | const typeMap = { |
| | | 0: "warning", |
| | | 1: "primary", |
| | | 2: "info", |
| | | }; |
| | | return typeMap[status] || "info"; |
| | | }; |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = status => { |
| | | const statusMap = { |
| | | 0: "å¾
ä¸å", |
| | | 1: "é¨åä¸å", |
| | | 2: "å·²ä¸å", |
| | | }; |
| | | return statusMap[status] || ""; |
| | | }; |
| | | const customColors = percentage => { |
| | | if (Number(percentage) < 10) { |
| | | return "#909399"; |
| | | } |
| | | if (Number(percentage) < 70) { |
| | | return "#e6a23c"; |
| | | } |
| | | return "#67c23a"; |
| | | }; |
| | | |
| | | // çææ¨¡æè¿åº¦è¯¦æ
æ°æ® |
| | | const generateProgressDetails = status => { |
| | | const details = [ |
| | | { |
| | | step: "计å确认", |
| | | status: "completed", |
| | | quantity: 233, |
| | | startTime: "2026-03-01 09:00:00", |
| | | endTime: "2026-03-01 10:00:00", |
| | | }, |
| | | { |
| | | step: "ç¬¬ä¸æ¬¡æ¥å·¥", |
| | | status: "completed", |
| | | quantity: 233, |
| | | startTime: "2026-03-01 09:00:00", |
| | | endTime: "2026-03-01 10:00:00", |
| | | }, |
| | | { |
| | | step: "ç¬¬äºæ¬¡æ¥å·¥", |
| | | status: "completed", |
| | | quantity: 233, |
| | | startTime: "2026-03-01 09:00:00", |
| | | endTime: "2026-03-01 10:00:00", |
| | | }, |
| | | { |
| | | step: "ç¬¬ä¸æ¬¡æ¥å·¥", |
| | | status: "completed", |
| | | quantity: 233, |
| | | startTime: "2026-03-01 09:00:00", |
| | | endTime: "2026-03-01 10:00:00", |
| | | }, |
| | | { |
| | | step: "ç¬¬åæ¬¡æ¥å·¥", |
| | | status: "completed", |
| | | quantity: 233, |
| | | startTime: "2026-03-01 09:00:00", |
| | | endTime: "2026-03-01 10:00:00", |
| | | }, |
| | | { |
| | | step: "ç¬¬äºæ¬¡æ¥å·¥", |
| | | status: "completed", |
| | | quantity: 233, |
| | | startTime: "2026-03-01 09:00:00", |
| | | endTime: "2026-03-01 10:00:00", |
| | | }, |
| | | { |
| | | step: "第å
次æ¥å·¥", |
| | | status: "completed", |
| | | quantity: 233, |
| | | startTime: "2026-03-01 09:00:00", |
| | | endTime: "2026-03-01 10:00:00", |
| | | }, |
| | | { |
| | | step: "ç¬¬ä¸æ¬¡æ¥å·¥", |
| | | status: "completed", |
| | | quantity: 233, |
| | | startTime: "2026-03-01 09:00:00", |
| | | endTime: "2026-03-01 10:00:00", |
| | | }, |
| | | ]; |
| | | return details; |
| | | }; |
| | | |
| | | // 计ç®å®æç |
| | | const calculateCompletionRate = details => { |
| | | const completedSteps = details.filter( |
| | | step => step.status === "completed" |
| | | ).length; |
| | | return Math.round((completedSteps / details.length) * 100); |
| | | }; |
| | | |
| | | // å¤çè¿åº¦æ´æ° |
| | | const handleUpdateProgress = () => { |
| | | // è¿éå¯ä»¥æ·»å æ´æ°è¿åº¦çé»è¾ |
| | | ElMessage.success("è¿åº¦æ´æ°æå"); |
| | | }; |
| | | |
| | | // å¤çè¿å |
| | | const handleBack = () => { |
| | | router.push("/productionPlan/productionPlan"); |
| | | }; |
| | | |
| | | // çææ¨¡æè®¢åæ°æ® |
| | | const generateOrderList = () => { |
| | | return [ |
| | | { |
| | | orderNo: "ORD-2026-001", |
| | | status: 1, |
| | | quantity: 233.28, |
| | | completeQuantity: 14, |
| | | completionRate: 6, |
| | | startTime: "2026-03-25", |
| | | }, |
| | | { |
| | | orderNo: "ORD-2026-002", |
| | | status: 2, |
| | | quantity: 150.5, |
| | | completeQuantity: 100, |
| | | completionRate: 67, |
| | | startTime: "2026-03-20", |
| | | }, |
| | | { |
| | | orderNo: "ORD-2026-003", |
| | | status: 0, |
| | | quantity: 80.0, |
| | | completeQuantity: 0, |
| | | completionRate: 0, |
| | | startTime: "2026-03-30", |
| | | }, |
| | | ]; |
| | | }; |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(() => { |
| | | // ä»è·¯ç±åæ°ä¸è·åæ°æ® |
| | | const data = route.query.row |
| | | ? JSON.parse(decodeURIComponent(route.query.row)) |
| | | : null; |
| | | if (data) { |
| | | // èµå¼ç»rowData |
| | | Object.assign(rowData, data); |
| | | // èµå¼ç»è¡¨åæ°æ® |
| | | trackProgressForm.materialCode = data.materialCode; |
| | | trackProgressForm.currentStatus = data.status; |
| | | trackProgressForm.progressDetails = generateProgressDetails(data.status); |
| | | trackProgressForm.completionRate = calculateCompletionRate( |
| | | trackProgressForm.progressDetails |
| | | ); |
| | | trackProgressForm.remark = ""; |
| | | } |
| | | // çææ¨¡æè®¢åæ°æ® |
| | | rowData.orderList = generateOrderList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 0 10px; |
| | | } |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-top: 20px; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .detail-section { |
| | | margin-bottom: 24px; |
| | | background-color: #ffffff; |
| | | border-radius: 10px; |
| | | padding: 24px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .detail-section:hover { |
| | | box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.12); |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | margin-bottom: 20px; |
| | | color: #1a1a1a; |
| | | border-bottom: 2px solid #409eff; |
| | | padding-bottom: 10px; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .section-title::before { |
| | | content: ""; |
| | | display: inline-block; |
| | | width: 4px; |
| | | height: 16px; |
| | | background-color: #409eff; |
| | | margin-right: 8px; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .unit { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | margin-left: 4px; |
| | | } |
| | | |
| | | :deep(.el-descriptions) { |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | :deep(.el-descriptions__row:nth-child(odd)) { |
| | | background-color: #f9f9f9; |
| | | } |
| | | |
| | | :deep(.el-descriptions__label) { |
| | | font-weight: 500; |
| | | color: #606266; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | :deep(.el-descriptions__content) { |
| | | color: #303133; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .progress-container { |
| | | display: flex; |
| | | gap: 24px; |
| | | } |
| | | |
| | | .progress-section { |
| | | margin-bottom: 24px; |
| | | background-color: #ffffff; |
| | | border-radius: 10px; |
| | | padding: 24px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); |
| | | flex: 1; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .progress-section:hover { |
| | | box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.12); |
| | | } |
| | | |
| | | .progress-item { |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .progress-label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #606266; |
| | | margin-bottom: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .progress-content { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .progress-content .el-table { |
| | | max-height: 400px; |
| | | overflow-y: auto; |
| | | border-radius: 8px; |
| | | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | :deep(.el-table th) { |
| | | background-color: #f5f7fa !important; |
| | | font-weight: 600; |
| | | color: #606266; |
| | | } |
| | | |
| | | :deep(.el-table tr:hover) { |
| | | background-color: #f5f7fa !important; |
| | | } |
| | | |
| | | .order-item { |
| | | margin-bottom: 20px; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .order-item:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | :deep(.el-progress-bar__inner) { |
| | | border-radius: 10px; |
| | | } |
| | | |
| | | :deep(.el-tag) { |
| | | border-radius: 12px; |
| | | padding: 2px 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog v-model="dialogVisible" |
| | | title="æ åæå
¥äº§åºæ¯ä¾" |
| | | width="1000px" |
| | | @close="closeDialog"> |
| | | <div class="ratio-cards"> |
| | | <el-card v-for="(item, index) in ratioData" |
| | | :key="index" |
| | | class="ratio-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>{{ item.productName }}<span v-if="item.model">-</span>{{ item.model || '' }}</span> |
| | | <span class="material-code">{{ item.materialCode }}</span> |
| | | </div> |
| | | </template> |
| | | <div class="ratio-info"> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">å®é
æå
¥éï¼</span> |
| | | <span class="info-value">{{ item.actualInputQuantity }} {{ item.unit }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">å®é
产åºéï¼</span> |
| | | <span class="info-value">{{ item.actualOutputQuantity }} {{ item.unit }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">å®é
æå
¥äº§åºæ¯ä¾ï¼</span> |
| | | <span class="info-value actual-ratio">{{ item.actualInputOutputRatio }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">æ åæå
¥äº§åºæ¯ä¾ï¼</span> |
| | | <span class="info-value standard-ratio">{{ item.standardInputOutputRatio }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">åå·®çï¼</span> |
| | | <span :class="['info-value', 'deviation-rate', item.deviationRate >= 0 ? 'positive' : 'negative']"> |
| | | {{ item.deviationRate >= 0 ? '+' : '' }}{{ (item.deviationRate * 100).toFixed(2) }}% |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <img v-if="item.deviationRate < 0" |
| | | src="@/assets/images/ratiodown.png" |
| | | class="chart-image" /> |
| | | <img v-else |
| | | src="@/assets/images/ratioup.png" |
| | | class="chart-imageUp" /> |
| | | </el-card> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDialog">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | import { qualityInspectFinishedRatio } from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | |
| | | const emit = defineEmits(["close"]); |
| | | const dialogVisible = ref(false); |
| | | const ratioData = ref([]); |
| | | const orderInfo = ref({}); |
| | | const loading = ref(false); |
| | | |
| | | const openDialog = row => { |
| | | dialogVisible.value = true; |
| | | orderInfo.value = row; |
| | | getRatioDetails(row); |
| | | }; |
| | | |
| | | const getRatioDetails = row => { |
| | | loading.value = true; |
| | | // æå»ºè¯·æ±åæ° |
| | | const params = { |
| | | productOrderId: row.productOrderId, |
| | | }; |
| | | |
| | | qualityInspectFinishedRatio(params) |
| | | .then(res => { |
| | | ratioData.value = res.data || []; |
| | | loading.value = false; |
| | | }) |
| | | .catch(err => { |
| | | loading.value = false; |
| | | console.error("è·åæ åæå
¥äº§åºæ¯ä¾å¤±è´¥:", err); |
| | | }); |
| | | }; |
| | | |
| | | const closeDialog = () => { |
| | | dialogVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .detail-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #333; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .material-code { |
| | | font-size: 14px; |
| | | font-weight: normal; |
| | | color: #909399; |
| | | } |
| | | |
| | | .detail-info { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .ratio-cards { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); |
| | | gap: 20px; |
| | | } |
| | | |
| | | .ratio-card { |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 4px; |
| | | overflow: hidden; |
| | | position: relative; |
| | | } |
| | | |
| | | .ratio-info { |
| | | padding: 15px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .info-row { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .info-item { |
| | | width: 50%; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .info-label { |
| | | display: inline-block; |
| | | width: 100%; |
| | | font-weight: bold; |
| | | color: #666; |
| | | } |
| | | |
| | | .info-value { |
| | | color: #333; |
| | | } |
| | | |
| | | .actual-ratio { |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .standard-ratio { |
| | | color: #f68f00; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .deviation-rate { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .deviation-rate.positive { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .deviation-rate.negative { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: center; |
| | | } |
| | | .chart-image { |
| | | position: absolute; |
| | | bottom: 30px; |
| | | right: 20px; |
| | | width: 60px; |
| | | height: 80px; |
| | | opacity: 0.8; |
| | | } |
| | | .chart-imageUp { |
| | | position: absolute; |
| | | bottom: 30px; |
| | | right: 20px; |
| | | width: 60px; |
| | | height: 80px; |
| | | opacity: 0.8; |
| | | transform: rotate(180deg); |
| | | } |
| | | </style> |
| | |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">产ååç§°ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.productName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥äº§ååç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span style="margin-left: 10px" class="search_title">æ£æµæ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | <span class="search_title">ç产订åå·ï¼</span> |
| | | <el-input v-model="searchForm.npsNo" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥ç产订åå·æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span style="margin-left: 20px" |
| | | class="search_title">产åç¼ç ï¼</span> |
| | | <el-input v-model="searchForm.materialCode" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥äº§åç¼ç æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span style="margin-left: 20px" |
| | | class="search_title">产ååç§°ï¼</span> |
| | | <el-input v-model="searchForm.productName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥äº§ååç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | <el-button type="info" |
| | | @click="handleReset" |
| | | style="margin-left: 10px">éç½®</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total"> |
| | | <template #needQuantity="{ row }"> |
| | | <span style="font-weight: bold;color: #f68f00;">{{ row.needQuantity }}</span><span style="margin-left: 5px;color: #909399;">æ¹</span> |
| | | </template> |
| | | <template #quantity="{ row }"> |
| | | <span style="font-weight: bold;color: #409eff;">{{ row.quantity }}</span><span style="margin-left: 5px;color: #909399;">æ¹</span> |
| | | </template> |
| | | <template #qualifiedQuantity="{ row }"> |
| | | <span style="font-weight: bold;color: #67c23a;">{{ row.qualifiedQuantity }}</span><span style="margin-left: 5px;color: #909399;">æ¹</span> |
| | | </template> |
| | | <template #unqualifiedQuantity="{ row }"> |
| | | <span style="font-weight: bold;color: #f56c6c;">{{ row.unqualifiedQuantity }}</span><span style="margin-left: 5px;color: #909399;">æ¹</span> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia> |
| | | <FormDia ref="formDia" @close="handleQuery"></FormDia> |
| | | <files-dia ref="filesDia" @close="handleQuery"></files-dia> |
| | | <el-dialog v-model="dialogFormVisible" title="ç¼è¾æ£éªå" width="30%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-form-item label="æ£éªåï¼" prop="checkName"> |
| | | <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <RatioDialog ref="ratioDialog" |
| | | @close="handleQuery"></RatioDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; |
| | | import InspectionFormDia from "@/views/qualityManagement/finalInspection/components/inspectionFormDia.vue"; |
| | | import FormDia from "@/views/qualityManagement/finalInspection/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import { |
| | | downloadQualityInspect, |
| | | qualityInspectDel, |
| | | qualityInspectListPage, qualityInspectUpdate, |
| | | submitQualityInspect |
| | | } from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import FilesDia from "@/views/qualityManagement/finalInspection/components/filesDia.vue"; |
| | | import dayjs from "dayjs"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, nextTick } from "vue"; |
| | | import RatioDialog from "@/views/qualityManagement/finalInspection/components/ratioDialog.vue"; |
| | | import { qualityInspectFinishedListPage } from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | productName: "", |
| | | entryDate: undefined, // å½å
¥æ¥æ |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | }, |
| | | rules: { |
| | | checkName: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æ£æµæ¥æ", |
| | | prop: "checkTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç产工åå·", |
| | | prop: "workOrderNo", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "æ£éªå", |
| | | prop: "checkName", |
| | | }, |
| | | { |
| | | label: "产ååç§°", |
| | | prop: "productName", |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "model", |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "æ°é", |
| | | prop: "quantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "æ£æµåä½", |
| | | prop: "checkCompany", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "æ£æµç»æ", |
| | | prop: "checkResult", |
| | | dataType: "tag", |
| | | formatType: (params) => { |
| | | if (params == 'ä¸åæ ¼') { |
| | | return "danger"; |
| | | } else if (params == 'åæ ¼') { |
| | | return "success"; |
| | | } else { |
| | | return null; |
| | | } |
| | | const data = reactive({ |
| | | searchForm: { |
| | | npsNo: "", |
| | | materialCode: "", |
| | | productName: "", |
| | | }, |
| | | }, |
| | | { |
| | | label: "æäº¤ç¶æ", |
| | | prop: "inspectState", |
| | | formatData: (params) => { |
| | | if (params) { |
| | | return "å·²æäº¤"; |
| | | } else { |
| | | return "æªæäº¤"; |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 280, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => { |
| | | // å·²æäº¤åç¦ç¨ |
| | | if (row.inspectState == 1) return true; |
| | | // 妿æ£éªåæå¼ï¼åªæå½åç»å½ç¨æ·è½ç¼è¾ |
| | | if (row.checkName) { |
| | | return row.checkName !== userStore.nickName; |
| | | } |
| | | return false; |
| | | } |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç产订åå·", |
| | | prop: "npsNo", |
| | | width: 140, |
| | | }, |
| | | { |
| | | label: "产åç¼ç ", |
| | | prop: "materialCode", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "产ååç§°", |
| | | prop: "productName", |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "model", |
| | | }, |
| | | { |
| | | label: "产åç±»å", |
| | | prop: "strength", |
| | | }, |
| | | { |
| | | label: "æéæ°é", |
| | | prop: "needQuantity", |
| | | dataType: "slot", |
| | | slot: "needQuantity", |
| | | }, |
| | | { |
| | | label: "äº§åºæ°é", |
| | | prop: "quantity", |
| | | dataType: "slot", |
| | | slot: "quantity", |
| | | }, |
| | | { |
| | | label: "åæ ¼æ°é", |
| | | prop: "qualifiedQuantity", |
| | | dataType: "slot", |
| | | slot: "qualifiedQuantity", |
| | | }, |
| | | { |
| | | label: "ä¸åæ ¼æ°é", |
| | | prop: "unqualifiedQuantity", |
| | | dataType: "slot", |
| | | slot: "unqualifiedQuantity", |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | formatType: params => { |
| | | const typeMap = { |
| | | 1: "primary", |
| | | 2: "warning", |
| | | 3: "success", |
| | | 4: "danger", |
| | | }; |
| | | return typeMap[params] || "default"; |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesFormDia(row); |
| | | }, |
| | | formatData: val => { |
| | | const labelMap = { |
| | | 1: "å¾
å¼å§", |
| | | 2: "è¿è¡ä¸", |
| | | 3: "已宿", |
| | | 4: "已忶", |
| | | }; |
| | | return labelMap[val] || val; |
| | | }, |
| | | { |
| | | name: "æäº¤", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | submit(row.id); |
| | | }, |
| | | disabled: (row) => { |
| | | // å·²æäº¤åç¦ç¨ |
| | | if (row.inspectState == 1) return true; |
| | | // 妿æ£éªåæå¼ï¼åªæå½åç»å½ç¨æ·è½æäº¤ |
| | | if (row.checkName) { |
| | | return row.checkName !== userStore.nickName; |
| | | } |
| | | return false; |
| | | } |
| | | }, |
| | | { |
| | | name: "åé
æ£éªå", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | if (!row.checkName) { |
| | | open(row) |
| | | } else { |
| | | proxy.$modal.msgError("æ£éªåå·²åå¨"); |
| | | } |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1 || row.checkName; |
| | | } |
| | | }, |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const currentRow = ref(null) |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0 |
| | | }); |
| | | const formDia = ref() |
| | | const filesDia = ref() |
| | | const inspectionFormDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | const userStore = useUserStore() |
| | | const userList = ref([]); |
| | | const form = ref({ |
| | | checkName: "" |
| | | }); |
| | | const dialogFormVisible = ref(false); |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 180, |
| | | operation: [ |
| | | { |
| | | name: "æå
¥äº§åºæ¯ä¾", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openFilesFormDia(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const ratioDialog = ref(); |
| | | /** éç½®æé®æä½ */ |
| | | const handleReset = () => { |
| | | searchForm.value = { |
| | | npsNo: "", |
| | | materialCode: "", |
| | | productName: "", |
| | | }; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | getList(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined |
| | | qualityInspectListPage({...params, inspectType: 2}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // æå¼æ°å¢æ£éªå¼¹æ¡ |
| | | const openInspectionForm = (type, row) => { |
| | | nextTick(() => { |
| | | inspectionFormDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (type, row) => { |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | qualityInspectDel(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined; |
| | | qualityInspectFinishedListPage({ ...params }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/quality/qualityInspect/export", {inspectType: 2}, "åºåæ£éª.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | }; |
| | | |
| | | // æä»· |
| | | const submit = async (id) => { |
| | | const res = await submitQualityInspect({id: id}) |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | getList(); |
| | | } |
| | | } |
| | | // æå¼æ åæå
¥äº§åºæ¯ä¾å¼¹æ¡ |
| | | const openFilesFormDia = row => { |
| | | nextTick(() => { |
| | | ratioDialog.value?.openDialog(row); |
| | | }); |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | if (currentRow.value) { |
| | | const data = { |
| | | ...form.value, |
| | | id: currentRow.value.id |
| | | } |
| | | qualityInspectUpdate(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }) |
| | | } |
| | | }; |
| | | |
| | | const open = async (row) => { |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | currentRow.value = row |
| | | dialogFormVisible.value = true |
| | | } |
| | | |
| | | const downLoadFile = (row) => { |
| | | downloadQualityInspect({ id: row.id }).then((blobData) => { |
| | | const blob = new Blob([blobData], { |
| | | type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
| | | }) |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = 'åæææ£éªæ¥å.docx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | |
| | | document.body.removeChild(link) |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | }) |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog v-model="dialogVisible" |
| | | title="æ£éªè¯¦æ
" |
| | | width="1000px" |
| | | @close="closeDialog"> |
| | | <el-card class="detail-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åºæ¬ä¿¡æ¯</span> |
| | | </div> |
| | | </template> |
| | | <div class="detail-info"> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">æ¥æï¼</span> |
| | | <span class="info-value">{{ dayjs(detailData.createTime) .format('YYYY-MM-DD') }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">ç产订åå·ï¼</span> |
| | | <span class="info-value">{{ detailData.npsNo }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">å·¥åºï¼</span> |
| | | <!-- <span class="info-value">{{ detailData.process }}</span> --> |
| | | <el-tag type="primary">{{ detailData.process }}</el-tag> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">产åç¼ç ï¼</span> |
| | | <span class="info-value">{{ detailData.materialCode }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">产åç±»åï¼</span> |
| | | <!-- <span class="info-value">{{ detailData.strength }}</span> --> |
| | | <el-tag type="info">{{ detailData.strength }}</el-tag> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">产ååç§°ï¼</span> |
| | | <span class="info-value">{{ detailData.productName }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">è§æ ¼ï¼</span> |
| | | <span class="info-value">{{ detailData.model }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">çæ¬¡ï¼</span> |
| | | <el-tag :type="detailData.schedule === 'ç½ç' ? 'primary' : 'warning'">{{ detailData.schedule }}</el-tag> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">å²ä½äººåï¼</span> |
| | | <span class="info-value">{{ detailData.postName }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <!-- <div class="info-item"> |
| | | <span class="info-label">æ¥å·¥åå·ï¼</span> |
| | | <span class="info-value">{{ detailData.productionProductRouteItemId }}</span> |
| | | </div> --> |
| | | <div class="info-item"> |
| | | <span class="info-label">äº§åºæ°éï¼</span> |
| | | <span class="info-value"><span style="font-weight: bold;color: #409eff;">{{ detailData.quantity }}</span> æ¹</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">åæ ¼æ°éï¼</span> |
| | | <span class="info-value"><span style="font-weight: bold;color: #28e431;">{{ detailData.qualifiedQuantity }}</span> æ¹</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">ä¸åæ ¼æ°éï¼</span> |
| | | <span class="info-value"><span style="font-weight: bold;color: #b43434;">{{ detailData.unqualifiedQuantity }}</span> æ¹</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">æ¥å·¥åå·ï¼</span> |
| | | <span class="info-value">{{ detailData.productNo }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | <el-card v-for="group in groupedInspectionData" |
| | | :key="group.sourceSort" |
| | | class="detail-card" |
| | | style="margin-top: 20px;"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span v-if="groupedInspectionData.length > 1">æ£éªzhiç» - {{ group.sourceSort }}</span> |
| | | <span v-else>æ£éªææ </span> |
| | | </div> |
| | | </template> |
| | | <el-table :data="group.items" |
| | | style="width: 100%" |
| | | :row-class-name="rowClassName"> |
| | | <el-table-column prop="paramName" |
| | | label="ææ " /> |
| | | <el-table-column prop="unit" |
| | | label="åä½" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.unit || "/" }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="standardText" |
| | | label="æ åå¼" /> |
| | | <el-table-column prop="paramValue" |
| | | label="å®é
å¼" /> |
| | | <el-table-column prop="result" |
| | | label="ç»æ" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.result === 'åæ ¼' ? 'success' : 'danger'"> |
| | | {{ scope.row.result }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDialog">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from "vue"; |
| | | import { qualityInspectProcessDetails } from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const emit = defineEmits(["close"]); |
| | | const dialogVisible = ref(false); |
| | | const detailData = ref({}); |
| | | const inspectionData = ref([]); |
| | | const groupedInspectionData = ref([]); |
| | | const loading = ref(false); |
| | | |
| | | const openDialog = row => { |
| | | dialogVisible.value = true; |
| | | detailData.value = { |
| | | ...row, |
| | | createTime: row.createTime |
| | | ? dayjs(row.createTime).format("YYYY-MM-DD HH:mm:ss") |
| | | : "", |
| | | }; |
| | | getInspectionDetails(row.productionProductRouteItemId); |
| | | }; |
| | | |
| | | const getInspectionDetails = id => { |
| | | loading.value = true; |
| | | qualityInspectProcessDetails({ productionProductRouteItemId: id }) |
| | | .then(res => { |
| | | const data = res.data || []; |
| | | // 计ç®ç»æ |
| | | const processedData = data.map(item => { |
| | | let result = "åæ ¼"; |
| | | let standardText = ""; |
| | | if (item.valueMode === 1) { |
| | | // å弿¯è¾ |
| | | if (item.standardValue !== null) { |
| | | standardText = item.standardValue; |
| | | if (item.paramValue !== item.standardValue) { |
| | | result = "ä¸åæ ¼"; |
| | | } |
| | | } else { |
| | | standardText = "-"; |
| | | result = "åæ ¼"; |
| | | } |
| | | } else if (item.valueMode === 2) { |
| | | // åºé´æ¯è¾ |
| | | if (item.minValue !== null || item.maxValue !== null) { |
| | | standardText = |
| | | (item.minValue ? item.minValue : "-â") + |
| | | "~" + |
| | | (item.maxValue ? item.maxValue : "+â"); |
| | | if ( |
| | | item.paramValue < item.minValue || |
| | | item.paramValue > item.maxValue |
| | | ) { |
| | | result = "ä¸åæ ¼"; |
| | | } |
| | | } else { |
| | | standardText = "-"; |
| | | result = "åæ ¼"; |
| | | } |
| | | } |
| | | return { |
| | | ...item, |
| | | standardText, |
| | | result, |
| | | }; |
| | | }); |
| | | |
| | | // æsourceSortåç» |
| | | const grouped = {}; |
| | | processedData.forEach(item => { |
| | | const key = item.sourceSort || "é»è®¤"; |
| | | if (!grouped[key]) { |
| | | grouped[key] = []; |
| | | } |
| | | grouped[key].push(item); |
| | | }); |
| | | |
| | | // 转æ¢ä¸ºæ°ç»æ ¼å¼ |
| | | groupedInspectionData.value = Object.entries(grouped).map( |
| | | ([key, items]) => ({ |
| | | sourceSort: key, |
| | | items, |
| | | }) |
| | | ); |
| | | |
| | | loading.value = false; |
| | | }) |
| | | .catch(err => { |
| | | loading.value = false; |
| | | console.error("è·åæ£éªè¯¦æ
失败:", err); |
| | | }); |
| | | }; |
| | | |
| | | const closeDialog = () => { |
| | | dialogVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | |
| | | // 为ä¸åæ ¼çè¡æ·»å æ ·å¼ |
| | | const rowClassName = ({ row }) => { |
| | | return row.result == "ä¸åæ ¼" ? "unqualified-row" : ""; |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .detail-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .detail-info { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .info-row { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .info-item { |
| | | width: 33%; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .info-label { |
| | | display: inline-block; |
| | | width: 100px; |
| | | font-weight: bold; |
| | | color: #666; |
| | | } |
| | | |
| | | .info-value { |
| | | color: #333; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: center; |
| | | } |
| | | |
| | | :deep(.unqualified-row) { |
| | | background-color: rgba(245, 108, 108, 0.05) !important; |
| | | } |
| | | |
| | | :deep(.unqualified-row .cell) { |
| | | color: #f56c6c !important; |
| | | } |
| | | </style> |
| | |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å·¥åºï¼</span> |
| | | <el-input |
| | | v-model="searchForm.process" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å·¥åºæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span style="margin-left: 10px" class="search_title">æ£æµæ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | <el-input v-model="searchForm.process" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥å·¥åºæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span style="margin-left: 10px" |
| | | class="search_title">ç产订åå·ï¼</span> |
| | | <el-input v-model="searchForm.npsNo" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥ç产订åå·æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span style="margin-left: 10px" |
| | | class="search_title">产åç¼ç ï¼</span> |
| | | <el-input v-model="searchForm.materialCode" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥äº§åç¼ç æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span style="margin-left: 10px" |
| | | class="search_title">产ååç§°ï¼</span> |
| | | <el-input v-model="searchForm.productName" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥äº§ååç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span style="margin-left: 10px" |
| | | class="search_title">æ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="daterange" |
| | | style="width: 240px" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="changeDaterange" /> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | <el-button type="info" |
| | | @click="resetForm" |
| | | style="margin-left: 10px">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> |
| | | <!-- <el-button type="primary" |
| | | @click="openForm('add')">æ°å¢</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">å é¤</el-button> --> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total"> |
| | | <template #qualifiedQuantity="{ row }"> |
| | | <span style="font-weight: bold;color: #409eff;">{{ row.qualifiedQuantity }}</span><span style="margin-left: 5px;color: #909399;">æ¹</span> |
| | | </template> |
| | | <template #unqualifiedQuantity="{ row }"> |
| | | <span style="font-weight: bold;color: #b43434;">{{ row.unqualifiedQuantity }}</span><span style="margin-left: 5px;color: #909399;">æ¹</span> |
| | | </template> |
| | | <template #quantity="{ row }"> |
| | | <span style="font-weight: bold;color: #28e431;">{{ row.quantity }}</span><span style="margin-left: 5px;color: #909399;">æ¹</span> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia> |
| | | <FormDia ref="formDia" @close="handleQuery"></FormDia> |
| | | <files-dia ref="filesDia" @close="handleQuery"></files-dia> |
| | | <el-dialog v-model="dialogFormVisible" title="ç¼è¾æ£éªå" width="30%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-form-item label="æ£éªåï¼" prop="checkName"> |
| | | <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <InspectionFormDia ref="inspectionFormDia" |
| | | @close="handleQuery"></InspectionFormDia> |
| | | <FormDia ref="formDia" |
| | | @close="handleQuery"></FormDia> |
| | | <files-dia ref="filesDia" |
| | | @close="handleQuery"></files-dia> |
| | | <DetailDialog ref="detailDialog" |
| | | @close="handleQuery"></DetailDialog> |
| | | <el-dialog v-model="dialogFormVisible" |
| | | title="ç¼è¾æ£éªå" |
| | | width="30%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef"> |
| | | <el-form-item label="æ£éªåï¼" |
| | | prop="checkName"> |
| | | <el-select v-model="form.checkName" |
| | | placeholder="è¯·éæ©" |
| | | clearable> |
| | | <el-option v-for="item in userList" |
| | | :key="item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.nickName" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; |
| | | import InspectionFormDia from "@/views/qualityManagement/processInspection/components/inspectionFormDia.vue"; |
| | | import FormDia from "@/views/qualityManagement/processInspection/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import { |
| | | downloadQualityInspect, |
| | | qualityInspectDel, |
| | | qualityInspectListPage, qualityInspectUpdate, |
| | | submitQualityInspect |
| | | } from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import FilesDia from "@/views/qualityManagement/processInspection/components/filesDia.vue"; |
| | | import dayjs from "dayjs"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { |
| | | onMounted, |
| | | ref, |
| | | reactive, |
| | | toRefs, |
| | | getCurrentInstance, |
| | | nextTick, |
| | | } from "vue"; |
| | | import InspectionFormDia from "@/views/qualityManagement/processInspection/components/inspectionFormDia.vue"; |
| | | import FormDia from "@/views/qualityManagement/processInspection/components/formDia.vue"; |
| | | import DetailDialog from "@/views/qualityManagement/processInspection/components/detailDialog.vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { |
| | | downloadQualityInspect, |
| | | qualityInspectDel, |
| | | qualityInspectListPage, |
| | | qualityInspectUpdate, |
| | | submitQualityInspect, |
| | | qualityInspectProcessPage, |
| | | qualityInspectProcessDetails, |
| | | } from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import FilesDia from "@/views/qualityManagement/processInspection/components/filesDia.vue"; |
| | | import dayjs from "dayjs"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | process: "", |
| | | entryDate: undefined, // å½å
¥æ¥æ |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | }, |
| | | rules: { |
| | | checkName: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æ£æµæ¥æ", |
| | | prop: "checkTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç产工åå·", |
| | | prop: "workOrderNo", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "å·¥åº", |
| | | prop: "process", |
| | | width: 230 |
| | | }, |
| | | { |
| | | label: "æ£éªå", |
| | | prop: "checkName", |
| | | }, |
| | | { |
| | | label: "产ååç§°", |
| | | prop: "productName", |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "model", |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "æ°é", |
| | | prop: "quantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "æ£æµåä½", |
| | | prop: "checkCompany", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "æ£æµç»æ", |
| | | prop: "checkResult", |
| | | dataType: "tag", |
| | | formatType: (params) => { |
| | | if (params == 'ä¸åæ ¼') { |
| | | return "danger"; |
| | | } else if (params == 'åæ ¼') { |
| | | return "success"; |
| | | } else { |
| | | return null; |
| | | } |
| | | const data = reactive({ |
| | | searchForm: { |
| | | process: "", |
| | | entryDate: undefined, // å½å
¥æ¥æ |
| | | startTime: undefined, |
| | | endTime: undefined, |
| | | materialCode: "", |
| | | productName: "", |
| | | npsNo: "", |
| | | }, |
| | | }, |
| | | { |
| | | label: "æäº¤ç¶æ", |
| | | prop: "inspectState", |
| | | formatData: (params) => { |
| | | if (params) { |
| | | return "å·²æäº¤"; |
| | | } else { |
| | | return "æªæäº¤"; |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 280, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => { |
| | | // å·²æäº¤åç¦ç¨ |
| | | if (row.inspectState == 1) return true; |
| | | // 妿æ£éªåæå¼ï¼åªæå½åç»å½ç¨æ·è½ç¼è¾ |
| | | if (row.checkName) { |
| | | return row.checkName !== userStore.nickName; |
| | | } |
| | | return false; |
| | | } |
| | | rules: { |
| | | checkName: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æ¥æ", |
| | | prop: "createTime", |
| | | width: "130", |
| | | formatData: val => { |
| | | return dayjs(val).format("YYYY-MM-DD"); |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesFormDia(row); |
| | | }, |
| | | }, |
| | | { |
| | | label: "ç产订åå·", |
| | | prop: "npsNo", |
| | | width: "130", |
| | | }, |
| | | { |
| | | label: "å·¥åº", |
| | | prop: "process", |
| | | dataType: "tag", |
| | | }, |
| | | { |
| | | label: "产åç¼ç ", |
| | | prop: "materialCode", |
| | | width: "130", |
| | | }, |
| | | { |
| | | label: "产åç±»å", |
| | | prop: "strength", |
| | | dataType: "tag", |
| | | formatType: params => { |
| | | if (params == "A3.5") { |
| | | return "success"; |
| | | } else if (params == "A4.5") { |
| | | return "warning"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | { |
| | | name: "æäº¤", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | submit(row.id); |
| | | }, |
| | | disabled: (row) => { |
| | | // å·²æäº¤åç¦ç¨ |
| | | if (row.inspectState == 1) return true; |
| | | // 妿æ£éªåæå¼ï¼åªæå½åç»å½ç¨æ·è½æäº¤ |
| | | if (row.checkName) { |
| | | return row.checkName !== userStore.nickName; |
| | | } |
| | | return false; |
| | | } |
| | | }, |
| | | { |
| | | name: "åé
æ£éªå", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | if (!row.checkName) { |
| | | open(row) |
| | | } else { |
| | | proxy.$modal.msgError("æ£éªåå·²åå¨"); |
| | | } |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1 || row.checkName; |
| | | } |
| | | }, |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const userList = ref([]); |
| | | const currentRow = ref(null) |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const dialogFormVisible = ref(false); |
| | | const form = ref({ |
| | | checkName: "" |
| | | }); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0 |
| | | }); |
| | | const formDia = ref() |
| | | const filesDia = ref() |
| | | const inspectionFormDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | const userStore = useUserStore() |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | getList(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined |
| | | qualityInspectListPage({...params, inspectType: 1}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | }, |
| | | { |
| | | label: "产ååç§°", |
| | | prop: "productName", |
| | | }, |
| | | { |
| | | label: "è§æ ¼", |
| | | prop: "model", |
| | | width: "130", |
| | | }, |
| | | { |
| | | label: "çæ¬¡", |
| | | prop: "schedule", |
| | | dataType: "tag", |
| | | formatType: params => { |
| | | if (params == "ç½ç") { |
| | | return "primary"; |
| | | } else if (params == "å¤ç") { |
| | | return "warning"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | label: "å²ä½äººå", |
| | | prop: "postName", |
| | | }, |
| | | { |
| | | label: "æ¥å·¥åå·", |
| | | prop: "productNo", |
| | | width: "130", |
| | | }, |
| | | { |
| | | label: "äº§åºæ°é", |
| | | prop: "quantity", |
| | | dataType: "slot", |
| | | slot: "quantity", |
| | | }, |
| | | { |
| | | label: "åæ ¼æ°é", |
| | | prop: "qualifiedQuantity", |
| | | dataType: "slot", |
| | | slot: "qualifiedQuantity", |
| | | }, |
| | | { |
| | | label: "ä¸åæ ¼æ°é", |
| | | prop: "unqualifiedQuantity", |
| | | dataType: "slot", |
| | | slot: "unqualifiedQuantity", |
| | | }, |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // æå¼æ°å¢æ£éªå¼¹æ¡ |
| | | const openInspectionForm = (type, row) => { |
| | | nextTick(() => { |
| | | inspectionFormDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (type, row) => { |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // æä»· |
| | | const submit = async (id) => { |
| | | const res = await submitQualityInspect({id: id}) |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | getList(); |
| | | } |
| | | } |
| | | const open = async (row) => { |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | currentRow.value = row |
| | | dialogFormVisible.value = true |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | const submitForm = () => { |
| | | if (currentRow.value) { |
| | | const data = { |
| | | ...form.value, |
| | | id: currentRow.value.id |
| | | } |
| | | qualityInspectUpdate(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }) |
| | | } |
| | | }; |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | | clickFun: row => { |
| | | openInspectionFormDia(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const userList = ref([]); |
| | | const currentRow = ref(null); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const dialogFormVisible = ref(false); |
| | | const form = ref({ |
| | | checkName: "", |
| | | }); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const formDia = ref(); |
| | | const filesDia = ref(); |
| | | const inspectionFormDia = ref(); |
| | | const detailDialog = ref(); |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | const changeDaterange = value => { |
| | | searchForm.value.startTime = undefined; |
| | | searchForm.value.endTime = undefined; |
| | | if (value) { |
| | | searchForm.value.startTime = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.endTime = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | getList(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | /** éç½®æé®æä½ */ |
| | | const resetForm = () => { |
| | | searchForm.value = { |
| | | process: "", |
| | | entryDate: undefined, |
| | | startTime: undefined, |
| | | endTime: undefined, |
| | | materialCode: "", |
| | | productName: "", |
| | | npsNo: "", |
| | | }; |
| | | getList(); |
| | | }; |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined; |
| | | qualityInspectProcessPage({ ...params, inspectType: 1 }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | // æå¼æ°å¢æ£éªå¼¹æ¡ |
| | | const openInspectionForm = (type, row) => { |
| | | nextTick(() => { |
| | | inspectionFormDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | |
| | | // æå¼è¯¦æ
å¼¹æ¡ |
| | | const openInspectionFormDia = row => { |
| | | nextTick(() => { |
| | | detailDialog.value?.openDialog(row); |
| | | }); |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (type, row) => { |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | // æä»· |
| | | const submit = async id => { |
| | | const res = await submitQualityInspect({ id: id }); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | getList(); |
| | | } |
| | | }; |
| | | const open = async row => { |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | currentRow.value = row; |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | const submitForm = () => { |
| | | if (currentRow.value) { |
| | | const data = { |
| | | ...form.value, |
| | | id: currentRow.value.id, |
| | | }; |
| | | qualityInspectUpdate(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | qualityInspectDel(ids).then((res) => { |
| | | qualityInspectDel(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | const downLoadFile = (row) => { |
| | | downloadQualityInspect({ id: row.id }).then((blobData) => { |
| | | const blob = new Blob([blobData], { |
| | | type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
| | | }) |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = 'è¿ç¨æ£éªæ¥å.docx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | |
| | | document.body.removeChild(link) |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | }) |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | }; |
| | | const downLoadFile = row => { |
| | | downloadQualityInspect({ id: row.id }).then(blobData => { |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", |
| | | }); |
| | | const downloadUrl = window.URL.createObjectURL(blob); |
| | | |
| | | const link = document.createElement("a"); |
| | | link.href = downloadUrl; |
| | | link.download = "è¿ç¨æ£éªæ¥å.docx"; |
| | | document.body.appendChild(link); |
| | | link.click(); |
| | | |
| | | document.body.removeChild(link); |
| | | window.URL.revokeObjectURL(downloadUrl); |
| | | }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/quality/qualityInspect/export", {inspectType: 1}, "è¿ç¨æ£éª.xlsx"); |
| | | proxy.download( |
| | | "/quality/qualityInspect/export", |
| | | { inspectType: 1 }, |
| | | "è¿ç¨æ£éª.xlsx" |
| | | ); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
| | |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | | // å
ç¡®ä¿äº§åæ å·²å è½½ï¼å¦åç¼è¾æ¶äº§å/è§æ ¼åå·æ æ³åæ¾ |
| | | await getProductOptions(); |
| | | // await getProductOptions(); |
| | | if (operationType.value === "edit") { |
| | | // å
ä¿å testStandardIdï¼é¿å
被æ¸
空 |
| | | const savedTestStandardId = row.testStandardId; |
| | |
| | | @click="handleBlockTimeDimensionChange('month')">æ</span> |
| | | </div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-filter-tabs"> |
| | | <div class="chart-filter-tabs"> |
| | | <span v-for="area in salesAreas" |
| | | :key="area" |
| | | class="cf-tab" |
| | |
| | | </div> |
| | | <div class="material-info-card"> |
| | | <div class="material-icon"> |
| | | <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <svg width="24" |
| | | height="24" |
| | | viewBox="0 0 24 24" |
| | | fill="none" |
| | | stroke="currentColor" |
| | | stroke-width="2"> |
| | | <path d="M20 7h-4V4c0-1.1-.9-2-2-2h-4c-1.1 0-2 .9-2 2v3H4c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2z" /> |
| | | <polyline points="22,7 12,13 2,7" /> |
| | | </svg> |
| | |
| | | @click="handleBoardTimeDimensionChange('month')">æ</span> |
| | | </div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-filter-tabs"> |
| | | <div class="chart-filter-tabs"> |
| | | <span v-for="area in salesAreas" |
| | | :key="area" |
| | | class="cf-tab" |
| | |
| | | </div> |
| | | <div class="material-info-card"> |
| | | <div class="material-icon"> |
| | | <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <svg width="24" |
| | | height="24" |
| | | viewBox="0 0 24 24" |
| | | fill="none" |
| | | stroke="currentColor" |
| | | stroke-width="2"> |
| | | <path d="M20 7h-4V4c0-1.1-.9-2-2-2h-4c-1.1 0-2 .9-2 2v3H4c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2z" /> |
| | | <polyline points="22,7 12,13 2,7" /> |
| | | </svg> |
| | |
| | | <!-- å³ä¸ï¼ééæååæ --> |
| | | <div class="bi-panel bi-panel-bottom-right"> |
| | | <PanelHeader :isFullscreen="true" |
| | | title="ç©æç产éç»è®¡" /> |
| | | title="è½èç»è®¡" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item" |
| | | :class="{ active: salesTimeDimension === 'year' }" |
| | |
| | | @click="handleSalesTimeDimensionChange('month')">æ</span> |
| | | </div> |
| | | <div class="bi-panel-body"> |
| | | <!-- <div class="chart-filter-tabs"> |
| | | <span v-for="area in salesAreas" |
| | | :key="area" |
| | | class="cf-tab" |
| | | :class="{ active: selectedArea === area }" |
| | | @click="handleAreaChange(area)">{{ area }}</span> |
| | | </div> |
| | | <div ref="salesRankingChart" |
| | | class="echart-fill"></div> --> |
| | | class="echart-fill"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | // æ¿æåèå¾è¡¨é
ç½® |
| | | const boardCostChartOption = computed(() => { |
| | | const materials = ["æ¶èé"]; |
| | | const colors = ["#00A4ED", "#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447", "#FF6B6B"]; |
| | | const colors = [ |
| | | "#00A4ED", |
| | | "#34D8F7", |
| | | "#4A8BFF", |
| | | "#8A6BFF", |
| | | "#C8C447", |
| | | "#FF6B6B", |
| | | ]; |
| | | const year = 2024; |
| | | const periodType = boardTimeDimension.value; |
| | | |
| | |
| | | // 产éåæå¾è¡¨é
ç½® |
| | | const productionChartOption = computed(() => { |
| | | const salesAreas = ["å
¨é¨", "ç å", "æ¿æ"]; |
| | | const colors = ["#00A4ED", "#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447", "#FF6B6B"]; |
| | | const colors = [ |
| | | "#00A4ED", |
| | | "#34D8F7", |
| | | "#4A8BFF", |
| | | "#8A6BFF", |
| | | "#C8C447", |
| | | "#FF6B6B", |
| | | ]; |
| | | const year = 2024; |
| | | const periodType = productionTimeDimension.value; |
| | | |
| | |
| | | }; |
| | | }); |
| | | |
| | | // ééæååæå¾è¡¨é
ç½® |
| | | // è½èç»è®¡å¾è¡¨é
ç½® |
| | | const salesRankingChartOption = computed(() => { |
| | | const customers = ["客æ·BB", "客æ·AA", "客æ·CC", "客æ·DD", "客æ·DD", "客æ·DD"]; |
| | | const values = [130, 120, 102, 90, 90, 70]; |
| | | const barColors = [ |
| | | "#34D8F7", |
| | | "#4A8BFF", |
| | | "#8A6BFF", |
| | | "#C8C447", |
| | | "#C8C447", |
| | | "#C8C447", |
| | | const energyTypes = ["æ°´", "çµ", "è¸æ±½"]; |
| | | const colors = ["#00A4ED", "#AC43C2", "#F5BC4A"]; |
| | | const year = 2024; |
| | | const periodType = salesTimeDimension.value; |
| | | |
| | | // çææ¶é´æ®µ |
| | | let periods = []; |
| | | if (periodType === "year") { |
| | | // å¹´åº¦æ°æ®ï¼6个æ |
| | | for (let month = 9; month <= 12; month++) { |
| | | periods.push(`${month}/${year.toString().slice(2)}`); |
| | | } |
| | | for (let month = 1; month <= 3; month++) { |
| | | periods.push(`${month}/${(year + 1).toString().slice(2)}`); |
| | | } |
| | | } else { |
| | | // æåº¦æ°æ®ï¼7天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 7; day++) { |
| | | periods.push(`${month}/${day}`); |
| | | } |
| | | } |
| | | |
| | | // 为æ¯ç§è½æºç±»åçææ°æ® |
| | | const waterData = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 300) + 400 |
| | | : Math.floor(Math.random() * 30) + 40; |
| | | }); |
| | | const steamData = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 400) + 500 |
| | | : Math.floor(Math.random() * 40) + 50; |
| | | }); |
| | | const electricityData = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 200) + 300 |
| | | : Math.floor(Math.random() * 20) + 30; |
| | | }); |
| | | |
| | | const series = [ |
| | | { |
| | | name: "æ°´", |
| | | type: "bar", |
| | | data: waterData, |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: "#00A4ED" }, |
| | | { offset: 1, color: "#0F285A" }, |
| | | ]), |
| | | borderRadius: [getResponsiveValue(4), getResponsiveValue(4), 0, 0], |
| | | }, |
| | | barWidth: getResponsiveValue(6), |
| | | }, |
| | | { |
| | | name: "çµ", |
| | | type: "line", |
| | | data: electricityData, |
| | | itemStyle: { |
| | | color: "#AC43C2", |
| | | }, |
| | | lineStyle: { |
| | | width: getResponsiveValue(1), |
| | | color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ |
| | | { offset: 0, color: "#AC43C2" }, |
| | | { offset: 1, color: "#AC43C2" }, |
| | | ]), |
| | | }, |
| | | symbol: "circle", |
| | | symbolSize: getResponsiveValue(8), |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: "#AC43C250" }, |
| | | { offset: 1, color: "#AC43C203" }, |
| | | ]), |
| | | }, |
| | | }, |
| | | { |
| | | name: "è¸æ±½", |
| | | type: "bar", |
| | | data: steamData, |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: "#F5BC4A" }, |
| | | { offset: 1, color: "#591C22" }, |
| | | ]), |
| | | borderRadius: [getResponsiveValue(4), getResponsiveValue(4), 0, 0], |
| | | }, |
| | | barWidth: getResponsiveValue(6), |
| | | }, |
| | | ]; |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | | tooltip: { |
| | | trigger: "axis", |
| | | axisPointer: { type: "shadow" }, |
| | | backgroundColor: "rgba(0,0,0,0.55)", |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | axisPointer: { type: "cross" }, |
| | | backgroundColor: "rgba(0,0,0,0.7)", |
| | | borderColor: "rgba(64,158,255,0.5)", |
| | | borderWidth: getResponsiveValue(1), |
| | | textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) }, |
| | | formatter: "{b}: {c} ç«æ¹ç±³", |
| | | textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(12) }, |
| | | formatter: function (params) { |
| | | let result = params[0].name + "<br/>"; |
| | | params.forEach(param => { |
| | | const unit = param.seriesName === "çµ" ? "度" : "å¨"; |
| | | result += `${param.marker}${param.seriesName}: ${param.value} ${unit}<br/>`; |
| | | }); |
| | | return result; |
| | | }, |
| | | }, |
| | | legend: { |
| | | data: energyTypes, |
| | | top: "5%", |
| | | right: "1%", |
| | | textStyle: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(10), |
| | | }, |
| | | itemWidth: getResponsiveValue(12), |
| | | itemHeight: getResponsiveValue(12), |
| | | itemGap: getResponsiveValue(15), |
| | | }, |
| | | grid: { |
| | | left: "14%", |
| | | right: "6%", |
| | | top: "16%", |
| | | bottom: "8%", |
| | | left: "1%", |
| | | right: "1%", |
| | | top: "25%", |
| | | bottom: "0%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | | type: "value", |
| | | axisLine: { show: false }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: getResponsiveValue(11) }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | yAxis: { |
| | | type: "category", |
| | | data: customers, |
| | | axisTick: { show: false }, |
| | | axisLine: { show: false }, |
| | | data: periods, |
| | | axisLabel: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(11), |
| | | margin: getResponsiveValue(8), |
| | | color: "#93B9FF", |
| | | interval: 0, |
| | | rotate: periodType === "month" ? 45 : 0, |
| | | }, |
| | | }, |
| | | series: [ |
| | | { |
| | | name: "ééï¼ç«æ¹ç±³ï¼", |
| | | type: "bar", |
| | | barWidth: getResponsiveValue(14), |
| | | data: values, |
| | | itemStyle: { |
| | | color: params => barColors[params.dataIndex] || "#00A4ED", |
| | | borderRadius: [ |
| | | getResponsiveValue(6), |
| | | getResponsiveValue(6), |
| | | getResponsiveValue(6), |
| | | getResponsiveValue(6), |
| | | ], |
| | | axisLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | width: getResponsiveValue(1), |
| | | color: "#305B9A", |
| | | }, |
| | | }, |
| | | ], |
| | | axisTick: { |
| | | show: false, |
| | | }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { |
| | | fontSize: getResponsiveValue(11), |
| | | color: "#93B9FF", |
| | | formatter: function (value) { |
| | | return value; |
| | | }, |
| | | }, |
| | | axisLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | color: "#305B9A", |
| | | }, |
| | | }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: "#0F2E60", |
| | | type: "dashed", |
| | | }, |
| | | }, |
| | | }, |
| | | series: series, |
| | | }; |
| | | }); |
| | | |
| | |
| | | } |
| | | |
| | | /* .scroll-table tbody tr:nth-child(odd) { |
| | | background-color: rgba(64, 158, 255, 0.05); |
| | | } |
| | | background-color: rgba(64, 158, 255, 0.05); |
| | | } |
| | | |
| | | .scroll-table tbody tr:nth-child(even) { |
| | | background-color: rgba(64, 158, 255, 0.1); |
| | | } */ |
| | | .scroll-table tbody tr:nth-child(even) { |
| | | background-color: rgba(64, 158, 255, 0.1); |
| | | } */ |
| | | .oddTableTr { |
| | | background-color: rgba(64, 158, 255, 0.05); |
| | | } |
| | |
| | | height: 24vh; |
| | | } |
| | | |
| | | |
| | | .bi-panel-bottom-right .echart-fill { |
| | | height: calc(100% - 2.8vh); |
| | | } |
| | |
| | | text-align: left; |
| | | color: #c3c3c3; |
| | | } |
| | | /* ææä¿¡æ¯å¡ç */ |
| | | /* ææä¿¡æ¯å¡ç */ |
| | | .material-info-card { |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | font-size: 1vh; |
| | | opacity: 0.7; |
| | | } |
| | | |
| | | </style> |
| | |
| | | const { VITE_APP_ENV } = env; |
| | | const baseUrl = |
| | | env.VITE_APP_ENV === "development" |
| | | ? "http://192.168.1.234:9019" |
| | | ? "http://192.168.1.107:7003" |
| | | : env.VITE_BASE_API; |
| | | const javaUrl = |
| | | env.VITE_APP_ENV === "development" |