From e1535c267711c7c8d560e8916437167bbcd3156b Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期一, 02 二月 2026 17:54:31 +0800
Subject: [PATCH] feat: 进销质量类分析接口对接
---
src/views/reportAnalysis/qualityAnalysis/components/center-bottom.vue | 182 ++++++++++---
src/views/reportAnalysis/qualityAnalysis/index.vue | 3
src/views/reportAnalysis/qualityAnalysis/components/right-top.vue | 121 +++++--
src/views/reportAnalysis/qualityAnalysis/components/left-top.vue | 91 ++++--
src/views/reportAnalysis/qualityAnalysis/components/center-center.vue | 101 +++---
src/views/reportAnalysis/qualityAnalysis/components/right-bottom.vue | 227 +++++----------
src/api/viewIndex.js | 67 ++++
src/views/reportAnalysis/qualityAnalysis/components/center-top.vue | 46 ++-
8 files changed, 511 insertions(+), 327 deletions(-)
diff --git a/src/api/viewIndex.js b/src/api/viewIndex.js
index 1479e0c..1ec8c49 100644
--- a/src/api/viewIndex.js
+++ b/src/api/viewIndex.js
@@ -1,6 +1,73 @@
// 棣栭〉鎺ュ彛
import request from "@/utils/request";
+// 鍘熸潗鏂欐娴�
+export const rawMaterialDetection = (query) => {
+ return request({
+ url: "/home/rawMaterialDetection",
+ method: "get",
+ params: query,
+ });
+};
+
+// 杩囩▼妫�娴�
+export const processDetection = (query) => {
+ return request({
+ url: "/home/processDetection",
+ method: "get",
+ params: query,
+ });
+};
+
+// 鎴愬搧鍑哄巶妫�娴�
+export const factoryDetection = (query) => {
+ return request({
+ url: "/home/factoryDetection",
+ method: "get",
+ params: query,
+ });
+};
+
+// 妫�楠屾暟閲�
+export const qualityInspectionCount = () => {
+ return request({
+ url: "/home/qualityInspectionCount",
+ method: "get",
+ });
+};
+
+// 涓嶅悎鏍奸璀�
+export const nonComplianceWarning = () => {
+ return request({
+ url: "/home/nonComplianceWarning",
+ method: "get",
+ });
+};
+
+// 瀹屾垚妫�楠屾暟
+export const completedInspectionCount = () => {
+ return request({
+ url: "/home/completedInspectionCount",
+ method: "get",
+ });
+};
+
+// 涓嶅悎鏍间骇鍝佹帓鍚�
+export const unqualifiedProductRanking = () => {
+ return request({
+ url: "/home/unqualifiedProductRanking",
+ method: "get",
+ });
+};
+
+// 涓嶅悎鏍兼鍝佸鐞嗗垎鏋�
+export const unqualifiedProductProcessingAnalysis = () => {
+ return request({
+ url: "/home/unqualifiedProductProcessingAnalysis",
+ method: "get",
+ });
+};
+
// 閿�鍞�-閲囪喘-搴撳瓨鏁版嵁
export const getBusiness = () => {
return request({
diff --git a/src/views/reportAnalysis/qualityAnalysis/components/center-bottom.vue b/src/views/reportAnalysis/qualityAnalysis/components/center-bottom.vue
index 2f2fb66..24d9552 100644
--- a/src/views/reportAnalysis/qualityAnalysis/components/center-bottom.vue
+++ b/src/views/reportAnalysis/qualityAnalysis/components/center-bottom.vue
@@ -1,6 +1,9 @@
<template>
<div>
- <PanelHeader title="宸ュ崟鎵ц鏁堢巼鍒嗘瀽" />
+ <div class="chart-header">
+ <PanelHeader title="瀹屾垚妫�楠屾暟" />
+ <div class="warn-range" @click="handleRangeClick">杩�7澶�</div>
+ </div>
<div class="main-panel panel-item-customers">
<Echarts
ref="chart"
@@ -20,7 +23,7 @@
<script setup>
import { ref, onMounted } from 'vue'
-import { qualityStatistics } from '@/api/viewIndex.js'
+import { completedInspectionCount } from '@/api/viewIndex.js'
import PanelHeader from './PanelHeader.vue'
import Echarts from '@/components/Echarts/echarts.vue'
@@ -29,21 +32,25 @@
height: '135%',
}
-const grid = { left: '3%', right: '4%', bottom: '3%', top: '10%', containLabel: true }
+const grid = { left: '8%', right: '8%', bottom: '8%', top: '15%', containLabel: true }
const barLegend = {
show: true,
- textStyle: { color: '#B8C8E0' },
- data: ['寮�宸�', '瀹屾垚'],
+ top: '5%',
+ left: 'center',
+ textStyle: { color: '#B8C8E0', fontSize: 14 },
+ itemGap: 30,
+ data: ['鍚堟牸', '涓嶅悎鏍�', '鍚堟牸鐜�'],
}
-// 鏌辩姸鍥撅細寮�宸ャ�佸畬鎴愶紱鎶樼嚎鍥撅細鑹搧鐜囷紙棰滆壊 rgba(90, 216, 166, 1)锛�
+// 鏌辩姸鍥撅細鍚堟牸锛堥粍鑹诧級銆佷笉鍚堟牸锛堢传鑹诧級锛涙姌绾垮浘锛氬悎鏍肩巼锛堣摑鑹诧級
const chartSeries = ref([
{
- name: '寮�宸�',
+ name: '鍚堟牸',
type: 'bar',
barWidth: 20,
- barGap: '40%',
+ barGap: '20%',
+ yAxisIndex: 0,
emphasis: { focus: 'series' },
itemStyle: {
color: {
@@ -53,18 +60,19 @@
x2: 0,
y2: 1,
colorStops: [
- { offset: 1, color: 'rgba(0, 164, 237, 0)' },
- { offset: 0, color: 'rgba(78, 228, 255, 1)' },
+ { offset: 0, color: 'rgba(255, 215, 0, 1)' }, // 閲戦粍鑹查《閮�
+ { offset: 1, color: 'rgba(255, 215, 0, 0.5)' }, // 鍗婇�忔槑搴曢儴
],
},
},
data: [],
},
{
- name: '瀹屾垚',
+ name: '涓嶅悎鏍�',
type: 'bar',
- barGap: '40%',
+ barGap: '20%',
barWidth: 20,
+ yAxisIndex: 0,
emphasis: { focus: 'series' },
itemStyle: {
color: {
@@ -74,9 +82,34 @@
x2: 0,
y2: 1,
colorStops: [
- { offset: 1, color: 'rgba(83, 126, 245, 0.19)' },
- { offset: 0, color: 'rgba(144, 97, 248, 1)' },
+ { offset: 0, color: 'rgba(144, 97, 248, 1)' }, // 绱壊椤堕儴
+ { offset: 1, color: 'rgba(144, 97, 248, 0.6)' }, // 鍗婇�忔槑搴曢儴
],
+ },
+ },
+ data: [],
+ },
+ {
+ name: '鍚堟牸鐜�',
+ type: 'line',
+ yAxisIndex: 1,
+ smooth: true,
+ symbol: 'circle',
+ symbolSize: 8,
+ lineStyle: {
+ color: 'rgba(78, 228, 255, 1)', // 闈掕壊
+ width: 2,
+ },
+ itemStyle: {
+ color: 'rgba(78, 228, 255, 1)',
+ borderWidth: 2,
+ borderColor: '#fff',
+ },
+ emphasis: {
+ focus: 'series',
+ itemStyle: {
+ shadowBlur: 10,
+ shadowColor: 'rgba(78, 228, 255, 0.8)',
},
},
data: [],
@@ -86,53 +119,87 @@
const tooltip = {
trigger: 'axis',
axisPointer: { type: 'cross' },
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
+ borderColor: 'rgba(78, 228, 255, 0.5)',
+ borderWidth: 1,
+ textStyle: { color: '#B8C8E0' },
formatter(params) {
let result = params[0].axisValueLabel + '<br/>'
params.forEach((item) => {
- const unit = item.seriesName === '杩�7澶�'
- result += `<div>${item.marker} ${item.seriesName}: ${item.value}${unit}</div>`
+ let unit = ''
+ if (item.seriesName === '鍚堟牸鐜�') {
+ unit = '%'
+ } else {
+ unit = '浠�'
+ }
+ result += `<div style="margin: 4px 0;">${item.marker} ${item.seriesName}: ${item.value}${unit}</div>`
})
return result
},
}
const xAxis1 = ref([
- { type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] },
+ {
+ type: 'category',
+ axisTick: { show: false },
+ axisLabel: { color: '#B8C8E0', fontSize: 12 },
+ axisLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.3)' } },
+ data: [],
+ },
])
+
const yAxis1 = [
- { type: 'value', name: '浠�', axisLabel: { color: '#B8C8E0' }, nameTextStyle: { color: '#B8C8E0' } },
{
type: 'value',
- name: '杩�7澶�',
+ name: '鍗曚綅: 浠�',
+ nameLocation: 'start',
+ nameTextStyle: { color: '#B8C8E0', fontSize: 12, padding: [0, 0, 0, 10] },
+ axisLabel: { color: '#B8C8E0', fontSize: 12 },
+ axisLine: { show: false },
+ splitLine: {
+ show: true,
+ lineStyle: { color: 'rgba(184, 200, 224, 0.2)', type: 'dashed' },
+ },
+ },
+ {
+ type: 'value',
+ name: '鍗曚綅: %',
+ nameLocation: 'end',
+ nameTextStyle: { color: '#B8C8E0', fontSize: 12, padding: [0, 0, 0, 10] },
min: 0,
max: 100,
- axisLabel: { color: '#B8C8E0', formatter: '{value}%' },
- nameTextStyle: { color: '#B8C8E0' },
- splitLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.2)' } },
+ axisLabel: { color: '#B8C8E0', fontSize: 12, formatter: '{value}' },
+ axisLine: { show: false },
+ splitLine: {
+ show: true,
+ lineStyle: { color: 'rgba(184, 200, 224, 0.2)', type: 'dashed' },
+ },
},
]
const fetchData = () => {
- qualityStatistics()
- .then((res) => {
- if (!res?.data?.item || !Array.isArray(res.data.item)) return
- const items = res.data.item
- xAxis1.value[0].data = items.map((d) => d.date)
- // 寮�宸ワ細杩囩▼妫�楠屾暟
- chartSeries.value[0].data = items.map((d) => Number(d.processNum) || 0)
- // 瀹屾垚锛氬嚭鍘傛暟
- chartSeries.value[1].data = items.map((d) => Number(d.factoryNum) || 0)
- // 鑹搧鐜囷細鍑哄巶鏁�/杩囩▼鏁�*100锛堟棤鍗曠嫭鎺ュ彛鏃剁敤姝ゅ崰浣嶏級
- chartSeries.value[2].data = items.map((d) => {
- const processNum = Number(d.processNum) || 0
- const factoryNum = Number(d.factoryNum) || 0
- if (processNum <= 0) return 0
- return Math.min(100, Math.round((factoryNum / processNum) * 100))
- })
- })
- .catch((err) => {
- console.error('鑾峰彇寮�宸ヤ笌鑹搧鐜囨暟鎹け璐�:', err)
- })
+ completedInspectionCount()
+ .then((res) => {
+ if (res?.code === 200 && Array.isArray(res?.data)) {
+ const items = res.data
+ // 鏇存柊X杞存棩鏈熸暟鎹�
+ xAxis1.value[0].data = items.map((d) => d.dateStr || '')
+ // 鏇存柊鍚堟牸鏁帮紙榛勮壊鏌辩姸鍥撅級
+ chartSeries.value[0].data = items.map((d) => Number(d.qualifiedCount) || 0)
+ // 鏇存柊涓嶅悎鏍兼暟锛堢传鑹叉煴鐘跺浘锛�
+ chartSeries.value[1].data = items.map((d) => Number(d.unqualifiedCount) || 0)
+ // 鏇存柊鍚堟牸鐜囷紙钃濊壊鎶樼嚎鍥撅級
+ chartSeries.value[2].data = items.map((d) => Number(d.passRate) || 0)
+ }
+ })
+ .catch((err) => {
+ console.error('鑾峰彇瀹屾垚妫�楠屾暟鏁版嵁澶辫触:', err)
+ })
+}
+
+const handleRangeClick = () => {
+ // 鍏堟寜鎴浘鍋氶潤鎬�"杩�7澶�"锛屽悗缁湁鐪熷疄绛涢�夐渶姹傚啀鎺ュ叆
+ fetchData()
}
onMounted(() => {
@@ -141,6 +208,35 @@
</script>
<style scoped>
+.chart-header {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.warn-range {
+ position: absolute;
+ right: 0;
+ top: 0;
+ height: 32px;
+ padding: 0 14px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ color: #ffffff;
+ font-weight: 600;
+ font-size: 14px;
+ background: linear-gradient(180deg, rgba(51, 120, 255, 1) 0%, rgba(0, 164, 237, 1) 100%);
+ border: 1px solid rgba(78, 228, 255, 0.25);
+ cursor: pointer;
+ z-index: 10;
+}
+
+.warn-range:hover {
+ background: linear-gradient(180deg, rgba(51, 140, 255, 1) 0%, rgba(0, 184, 237, 1) 100%);
+}
+
.main-panel {
display: flex;
flex-direction: column;
@@ -152,5 +248,7 @@
padding: 18px;
width: 100%;
height: 449px;
+ position: relative;
+ background: radial-gradient(circle at 50% 50%, rgba(78, 228, 255, 0.05) 0%, rgba(0, 0, 0, 0) 70%);
}
</style>
diff --git a/src/views/reportAnalysis/qualityAnalysis/components/center-center.vue b/src/views/reportAnalysis/qualityAnalysis/components/center-center.vue
index 8024092..8d28f7a 100644
--- a/src/views/reportAnalysis/qualityAnalysis/components/center-center.vue
+++ b/src/views/reportAnalysis/qualityAnalysis/components/center-center.vue
@@ -12,7 +12,8 @@
<div class="warn-body">
<div class="warn-list" role="list">
<div v-for="item in warnings" :key="item.id" class="warn-item" role="listitem" @click="openWarning(item)">
- <div class="warn-tag" :class="tagClass(item.type)">{{ item.typeText }}</div>
+ <div class="warn-tag" :class="tagClass(item.type)">{{ item.parentProductTitle }}-{{ item.productTitle }}
+ </div>
<div class="warn-text" :title="item.title">{{ item.title }}</div>
<div class="warn-action" @click.stop="openWarning(item)">鏌ョ湅</div>
<div class="warn-date">{{ item.date }}</div>
@@ -26,18 +27,18 @@
<script setup>
import { computed, getCurrentInstance, ref, onMounted } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
-import { qualityUnqualifiedListPage } from '@/api/qualityManagement/nonconformingManagement.js'
+import { nonComplianceWarning } from '@/api/viewIndex.js'
const { proxy } = getCurrentInstance() || {}
-const warnings = ref([
- { id: '1', type: 'raw', typeText: '鍘熸潗鏂�', title: '鍏充簬浼佷笟鍘熸潗鏂欒皟鏁撮�氱煡', date: '2024.08.24' },
- { id: '2', type: 'raw', typeText: '鍘熸潗鏂�', title: '鍏充簬鍘熸潗鏂欐秷鑰楁柟妗堝缓璁剧殑閫氱煡', date: '2024.08.24' },
- { id: '3', type: 'final', typeText: '鎴愬搧', title: '鎴愬搧宸ヤ綔鍙扮郴缁熺淮鎶よ鍒掑畨鎺�', date: '2024.08.24' },
- { id: '4', type: 'final', typeText: '鎴愬搧', title: '鎴愬搧宸ヤ綔鍙扮郴缁熺淮鎶よ鍒掑畨鎺�', date: '2024.08.24' },
- { id: '5', type: 'semi', typeText: '鍗婃垚鍝�', title: 'HRM绯荤粺瀹夊叏鍗囩骇鍏憡锛氬姞寮鸿闂帶鍒垛��', date: '2024.08.24' },
- { id: '6', type: 'semi', typeText: '鍗婃垚鍝�', title: 'HRM绯荤粺瀹夊叏鍗囩骇鍏憡锛氬姞寮鸿闂帶鍒垛��', date: '2024.08.24' },
-])
+const warnings = ref([])
+
+// 鍗犳瘮鏁版嵁
+const ratios = ref({
+ rawMaterialRatio: 0,
+ semiFinishedProductRatio: 0,
+ finishedProductRatio: 0,
+})
const TAG_COLORS = {
raw: '#7C4DFF',
@@ -51,6 +52,14 @@
return 'tag-semi'
}
+// 鏍规嵁productTitle鏄犲皠绫诲瀷
+const mapProductTitleToType = (productTitle) => {
+ if (productTitle === '鍘熸潗鏂�') return 'raw'
+ if (productTitle === '鍗婃垚鍝�') return 'semi'
+ if (productTitle === '鎴愬搧') return 'final'
+ return 'raw' // 榛樿鍊�
+}
+
const pieChartStyle = { width: '100%', height: '100%' }
const pieOptions = {
@@ -60,19 +69,14 @@
const pieTooltip = {
trigger: 'item',
- formatter: (p) => `${p.name}锛�${p.value}`,
+ formatter: (p) => `${p.name}锛�${p.value}%`,
}
const pieData = computed(() => {
- const counts = { raw: 0, final: 0, semi: 0 }
- warnings.value.forEach((w) => {
- const key = w.type in counts ? w.type : 'raw'
- counts[key] += 1
- })
return [
- { name: '鍘熸潗鏂�', value: counts.raw, itemStyle: { color: TAG_COLORS.raw } },
- { name: '鍗婃垚鍝�', value: counts.semi, itemStyle: { color: TAG_COLORS.semi } },
- { name: '鎴愬搧', value: counts.final, itemStyle: { color: TAG_COLORS.final } },
+ { name: '鍘熸潗鏂�', value: ratios.value.rawMaterialRatio, itemStyle: { color: TAG_COLORS.raw } },
+ { name: '鍗婃垚鍝�', value: ratios.value.semiFinishedProductRatio, itemStyle: { color: TAG_COLORS.semi } },
+ { name: '鎴愬搧', value: ratios.value.finishedProductRatio, itemStyle: { color: TAG_COLORS.final } },
]
})
@@ -111,34 +115,42 @@
const fetchWarnings = async () => {
try {
- const res = await qualityUnqualifiedListPage({ pageNum: 1, pageSize: 6 })
- const rows = res?.rows || res?.data?.rows || res?.data || []
- if (!Array.isArray(rows) || rows.length === 0) return
+ const res = await nonComplianceWarning()
+ if (res?.code === 200 && res?.data) {
+ const data = res.data
- warnings.value = rows.slice(0, 6).map((r, idx) => {
- const typeCode = r.inspectType ?? r.modelType ?? r.type
- const mappedType = typeCode === 0 || typeCode === '0' ? 'raw' : typeCode === 1 || typeCode === '1' ? 'semi' : 'final'
- const title = r.title || r.unqualifiedTitle || r.remark || r.unqualifiedReason || '涓嶅悎鏍奸璀�'
- const date = (r.warningTime || r.createTime || r.updateTime || '').slice(0, 10).replace(/-/g, '.') || '2024.08.24'
- return {
- id: r.id ?? r.unqualifiedId ?? `${idx}`,
- type: mappedType,
- typeText: mappedType === 'raw' ? '鍘熸潗鏂�' : mappedType === 'semi' ? '鍗婃垚鍝�' : '鎴愬搧',
- title,
- date,
+ // 鏇存柊鍗犳瘮鏁版嵁
+ ratios.value = {
+ rawMaterialRatio: data.rawMaterialRatio ?? 0,
+ semiFinishedProductRatio: data.semiFinishedProductRatio ?? 0,
+ finishedProductRatio: data.finishedProductRatio ?? 0,
}
- })
+
+ // 鏇存柊璀﹀憡鍒楄〃
+ const children = data.children || []
+ warnings.value = children.map((item, idx) => {
+ const type = mapProductTitleToType(item.parentProductTitle)
+ const date = item.date ? item.date.replace(/-/g, '.') : ''
+ return {
+ id: item.id ?? `warning-${idx}`,
+ type,
+ parentProductTitle: item.parentProductTitle || '鍘熸潗鏂�',
+ productTitle: item.productTitle || '鍘熸潗鏂�',
+ title: item.description || '涓嶅悎鏍奸璀�',
+ date,
+ }
+ })
+ }
} catch (e) {
- // 鎺ュ彛澶辫触鍒欎繚鎸� mock
+ // 鎺ュ彛澶辫触鍒欎繚鎸佺┖鏁版嵁
console.error('鑾峰彇涓嶅悎鏍奸璀﹀け璐�:', e)
}
}
const openWarning = (item) => {
- const title = `銆�${item.typeText}銆�${item.title}`
- const content = `${title}鏃堕棿锛�${item.date}`
+ const title = `銆�${item.parentProductTitle}-${item.productTitle}銆�${item.title}`
if (proxy?.$modal?.alert) {
- proxy.$modal.alert(content)
+ proxy.$modal.alert(title)
return
}
// 鍏滃簳锛氭病鏈夊叏灞� modal 鏃剁敤 console
@@ -170,14 +182,11 @@
align-items: center;
justify-content: space-between;
border-bottom: 1px solid;
- border-image: linear-gradient(
- 270deg,
+ border-image: linear-gradient(270deg,
rgba(0, 126, 255, 0) 0%,
rgba(0, 126, 255, 0.4549) 35%,
#007eff 78%,
- #007eff 100%
- )
- 1;
+ #007eff 100%) 1;
padding: 10px 0 6px;
}
@@ -235,13 +244,13 @@
.warn-item {
display: grid;
- grid-template-columns: 88px 1fr auto 110px;
+ grid-template-columns: 130px 1fr auto 110px;
align-items: center;
gap: 12px;
color: #b8c8e0;
font-size: 14px;
- line-height: 1;
- padding: 6px 0;
+ line-height: 1.2;
+ padding: 8px 0;
border-radius: 4px;
transition: background-color 0.2s, color 0.2s;
}
diff --git a/src/views/reportAnalysis/qualityAnalysis/components/center-top.vue b/src/views/reportAnalysis/qualityAnalysis/components/center-top.vue
index 0937b32..3ecf799 100644
--- a/src/views/reportAnalysis/qualityAnalysis/components/center-top.vue
+++ b/src/views/reportAnalysis/qualityAnalysis/components/center-top.vue
@@ -2,11 +2,7 @@
<div>
<!-- 椤堕儴缁熻鍗$墖 -->
<div class="stats-cards">
- <div
- v-for="item in statItems"
- :key="item.name"
- class="stat-card"
- >
+ <div v-for="item in statItems" :key="item.name" class="stat-card">
<img src="@/assets/BI/icon@2x.png" alt="鍥炬爣" class="card-icon" />
<div class="card-content">
<span class="card-label">{{ item.name }}</span>
@@ -25,7 +21,7 @@
<script setup>
import { ref, onMounted } from 'vue'
-import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js'
+import { qualityInspectionCount } from '@/api/viewIndex.js'
const statItems = ref([])
@@ -37,18 +33,32 @@
const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down')
const fetchData = () => {
- salesPurchaseStorageProductCount()
+ qualityInspectionCount()
.then((res) => {
- if (res.code === 200 && Array.isArray(res.data)) {
- statItems.value = res.data.map((item) => ({
- name: item.name,
- value: item.value,
- rate: item.rate,
- }))
+ if (res.code === 200 && res.data) {
+ const data = res.data
+
+ statItems.value = [
+ {
+ name: '鎬绘楠屾暟',
+ value: data.totalCount ?? 0,
+ rate: data.totalCountGrowthRate ?? 0,
+ },
+ {
+ name: '浠婃棩寰呭畬鎴愭暟',
+ value: data.todayPendingCount ?? 0,
+ rate: data.todayPendingCountGrowthRate ?? 0,
+ },
+ {
+ name: '浠婃棩宸插畬鎴愭暟',
+ value: data.todayCompletedCount ?? 0,
+ rate: data.todayCompletedCountGrowthRate ?? 0,
+ },
+ ]
}
})
.catch((err) => {
- console.error('鑾峰彇閿�鍞�/閲囪喘/鍌ㄥ瓨浜у搧鏁板け璐�:', err)
+ console.error('鑾峰彇璐ㄩ噺妫�楠岀粺璁″け璐�:', err)
})
}
@@ -97,7 +107,7 @@
.card-label {
font-weight: 400;
- font-size: 19px;
+ font-size: 16px;
color: rgba(208, 231, 255, 0.7);
}
@@ -109,7 +119,7 @@
color: #d0e7ff;
}
-.card-compare > span:first-child {
+.card-compare>span:first-child {
font-size: 13px;
opacity: 0.8;
}
@@ -121,7 +131,8 @@
.compare-icon {
font-size: 14px;
position: relative;
- top: -1px; /* 杞诲井涓婄Щ锛岃绠ご涓庢枃瀛楀瀭鐩村眳涓榻� */
+ top: -1px;
+ /* 杞诲井涓婄Щ锛岃绠ご涓庢枃瀛楀瀭鐩村眳涓榻� */
}
.compare-up .compare-value,
@@ -133,5 +144,4 @@
.compare-down .compare-icon {
color: #ff5252;
}
-
</style>
diff --git a/src/views/reportAnalysis/qualityAnalysis/components/left-top.vue b/src/views/reportAnalysis/qualityAnalysis/components/left-top.vue
index 7debef5..8237a3f 100644
--- a/src/views/reportAnalysis/qualityAnalysis/components/left-top.vue
+++ b/src/views/reportAnalysis/qualityAnalysis/components/left-top.vue
@@ -13,13 +13,8 @@
<div class="inspect-body">
<div class="ring">
- <Echarts
- :chartStyle="ringChartStyle"
- :series="buildRingSeries(section)"
- :tooltip="ringTooltip"
- :legend="{ show: false }"
- :options="ringOptions"
- />
+ <Echarts :chartStyle="ringChartStyle" :series="buildRingSeries(section)" :tooltip="ringTooltip"
+ :legend="{ show: false }" :options="ringOptions" />
</div>
<div class="stats">
@@ -51,42 +46,72 @@
</template>
<script setup>
-import { reactive } from 'vue'
+import { reactive, onMounted } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from './PanelHeader.vue'
import DateTypeSwitch from './DateTypeSwitch.vue'
+import { rawMaterialDetection, processDetection, factoryDetection } from '@/api/viewIndex.js'
const QUALIFIED_COLOR = '#4EE4FF'
const UNQUALIFIED_COLOR = '#3378FF'
const TRACK_COLOR = 'rgba(78, 228, 255, 0.12)'
+
+const apiMap = {
+ raw: rawMaterialDetection,
+ process: processDetection,
+ final: factoryDetection,
+}
+
+
+const fetchSectionData = async (section) => {
+ const api = apiMap[section.key]
+ if (!api) return
+
+ try {
+ const res = await api({
+ type: section.dateType,
+ })
+
+ if (res?.code === 200 && res?.data) {
+ const data = res.data
+ section.qualifiedCount = Number(data.qualifiedCount || 0)
+ section.unqualifiedCount = Number(data.unqualifiedCount || 0)
+ section.qualifiedRate = Number(data.qualifiedRate || 0)
+ section.unqualifiedRate = Number(data.unqualifiedRate || 0)
+ }
+ } catch (err) {
+ console.error(`${section.key} 鎺ュ彛璇锋眰澶辫触`, err)
+ }
+}
+
const sections = reactive([
{
key: 'raw',
title: '鍘熸潗鏂欐娴�',
dateType: 1,
- qualifiedCount: 199,
- unqualifiedCount: 99,
- qualifiedRate: 90,
- unqualifiedRate: 10,
+ qualifiedCount: 0,
+ unqualifiedCount: 0,
+ qualifiedRate: 0,
+ unqualifiedRate: 0,
},
{
key: 'process',
title: '杩囩▼妫�娴�',
dateType: 1,
- qualifiedCount: 199,
- unqualifiedCount: 99,
- qualifiedRate: 90,
- unqualifiedRate: 10,
+ qualifiedCount: 0,
+ unqualifiedCount: 0,
+ qualifiedRate: 0,
+ unqualifiedRate: 0,
},
{
key: 'final',
title: '鎴愬搧鍑哄巶妫�娴�',
dateType: 1,
- qualifiedCount: 199,
- unqualifiedCount: 99,
- qualifiedRate: 90,
- unqualifiedRate: 10,
+ qualifiedCount: 0,
+ unqualifiedCount: 0,
+ qualifiedRate: 0,
+ unqualifiedRate: 0,
},
])
@@ -183,15 +208,15 @@
const section = sections.find((s) => s.key === key)
if (!section) return
section.dateType = dateType
- const rates = calcRates(section.qualifiedCount, section.unqualifiedCount)
- section.qualifiedRate = rates.qualifiedRate
- section.unqualifiedRate = rates.unqualifiedRate
+ // 鍒囨崲鏃ユ湡绫诲瀷鏃堕噸鏂拌幏鍙栨暟鎹�
+ fetchSectionData(section)
}
-sections.forEach((s) => {
- const rates = calcRates(s.qualifiedCount, s.unqualifiedCount)
- s.qualifiedRate = rates.qualifiedRate
- s.unqualifiedRate = rates.unqualifiedRate
+// 缁勪欢鎸傝浇鏃惰幏鍙栨墍鏈塻ection鐨勬暟鎹�
+onMounted(() => {
+ sections.forEach((section) => {
+ fetchSectionData(section)
+ })
})
</script>
@@ -248,7 +273,7 @@
width: 18px;
height: 7px;
border-radius: 8px;
- background: linear-gradient(360deg, rgba(33,133,255,0.4) 0%, rgba(33,221,255,0) 100%);
+ background: linear-gradient(360deg, rgba(33, 133, 255, 0.4) 0%, rgba(33, 221, 255, 0) 100%);
position: absolute;
top: 50%;
left: -1px;
@@ -308,7 +333,7 @@
flex: 1 1 auto;
min-height: 0;
display: flex;
- justify-content:space-around;
+ justify-content: space-around;
align-items: center;
gap: 18px;
}
@@ -329,11 +354,9 @@
position: absolute;
inset: -8px;
border-radius: 50%;
- background: repeating-conic-gradient(
- from 0deg,
- rgba(78, 228, 255, 0.75) 0 1deg,
- rgba(78, 228, 255, 0) 1deg 9deg
- );
+ background: repeating-conic-gradient(from 0deg,
+ rgba(78, 228, 255, 0.75) 0 1deg,
+ rgba(78, 228, 255, 0) 1deg 9deg);
-webkit-mask: radial-gradient(circle, transparent 62%, #000 63%);
mask: radial-gradient(circle, transparent 62%, #000 63%);
opacity: 0.35;
diff --git a/src/views/reportAnalysis/qualityAnalysis/components/right-bottom.vue b/src/views/reportAnalysis/qualityAnalysis/components/right-bottom.vue
index cd22d56..49621f3 100644
--- a/src/views/reportAnalysis/qualityAnalysis/components/right-bottom.vue
+++ b/src/views/reportAnalysis/qualityAnalysis/components/right-bottom.vue
@@ -1,43 +1,34 @@
<template>
<div>
- <PanelHeader title="浜у搧澶х被" />
+ <PanelHeader title="涓嶅悎鏍兼鍝佸鐞嗗垎鏋�" />
<div class="panel-item-customers">
<div class="pie-chart-wrapper" ref="pieWrapperRef">
<div class="pie-background" ref="pieBackgroundRef"></div>
- <Echarts
- ref="chart"
- :chartStyle="chartStyle"
- :legend="landLegend"
- :series="landSeries"
- :tooltip="landTooltip"
- :color="landColors"
- :options="pieOptions"
- style="height: 100%"
- class="land-chart"
- />
+ <Echarts ref="chart" :chartStyle="chartStyle" :legend="landLegend" :series="computedSeries"
+ :tooltip="landTooltip" :color="landColors" :options="pieOptions" style="height: 100%" class="land-chart" />
</div>
</div>
</div>
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount } from 'vue'
+import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from './PanelHeader.vue'
-import { productCategoryDistribution } from '@/api/viewIndex.js'
+import { unqualifiedProductProcessingAnalysis } from '@/api/viewIndex.js'
import { useChartBackground } from '@/hooks/useChartBackground.js'
const pieWrapperRef = ref(null)
const pieBackgroundRef = ref(null)
const chart = ref(null)
-// 鏁版嵁鍒楄〃锛堟潵鑷帴鍙o級
+// 鏁版嵁鍒楄〃
const dataList = ref([])
-// 棰滆壊鍒楄〃
+// 棰滆壊鍒楄〃
const landColors = ['#26FFCB', '#24CBFF', '#35FBF4', '#2651FF', '#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF']
-// label 瀵屾枃鏈細涓烘瘡涓鑹茬敓鎴愪竴涓皬鍦嗙偣鏍峰紡锛堢‘淇濆湪 label 涓彲瑙侊級
+// label 瀵屾枃鏈牱寮�
const dotRich = landColors.reduce((acc, color, idx) => {
acc[`dot${idx}`] = {
width: 8,
@@ -49,163 +40,113 @@
return acc
}, {})
-// 鍥句緥閰嶇疆锛堝彸渚х珫鎺掞級
-const landLegend = {
+// 鍥句緥閰嶇疆
+const landLegend = ref({
show: false,
icon: 'circle',
data: [],
right: '8%',
top: '40%',
orient: 'vertical',
- itemGap: 14,
- itemWidth: 6,
- itemHeight: 6,
textStyle: {
- fontSize: 12,
+ color: '#fff',
rich: {
- unit: {
- color: '#fff',
- fontSize: 12,
- padding: [0, 10, 0, 0],
- },
- text: {
- width: 60,
- color: '#fff',
- fontSize: 12,
- },
- },
- },
- formatter: function (name) {
- const list = dataList.value || []
- const item = list.find((d) => d.name === name)
- if (!item) return name
- const val = Number(item.value || 0)
- const totalValue = list.reduce((sum, it) => sum + Number(it.value || 0), 0)
- const percent = totalValue ? ((val / totalValue) * 100).toFixed(2) : '0.00'
- return `{text|${name}}${val}{unit| 鍏》}${percent}{unit|%}`
- },
-}
+ unit: { color: '#fff', fontSize: 12, padding: [0, 10, 0, 0] },
+ text: { width: 60, color: '#fff', fontSize: 12 },
+ }
+ }
+})
-// 鎻愮ず妗�
+// 鎻愮ず妗嗛厤缃�
const landTooltip = {
- // triggerOn: 'hover',
- alwaysShowContent: true,
+ trigger: 'item',
+ alwaysShowContent: false,
position: function (pt) {
return [pt[0], 130]
},
formatter: function (params) {
- return `${params.name} (${params.value}绫�)`
+ // 纭繚 params.data 瀛樺湪
+ if (!params.data) return ''
+ const { name, value, rate } = params.data
+ return `${name}<br/>鏁伴噺锛�${value}涓�<br/>鍗犳瘮锛�${rate}%`
},
}
-// 鍙屽眰鐜舰楗煎浘
-const landSeries = ref([
- {
- name: '浜у搧澶х被',
- type: 'pie',
- radius: ['35%', '55%'],
- center: ['50%', '50%'],
- label: {
- show: true,
- position: 'outside',
- color: '#fff',
- fontSize: 12,
- lineHeight: 18,
- rich: {
- ...dotRich,
- parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20, overflow: 'break' },
- child: { fontSize: 12, color: '#fff', lineHeight: 18 },
+// 浣跨敤璁$畻灞炴�у鐞� Series
+const computedSeries = computed(() => {
+ return [
+ {
+ name: '涓嶅悎鏍兼鍝佸鐞嗗垎鏋�',
+ type: 'pie',
+ radius: ['35%', '55%'],
+ center: ['50%', '50%'],
+ label: {
+ show: true,
+ position: 'outside',
+ color: '#fff',
+ rich: {
+ ...dotRich,
+ parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20 },
+ child: { fontSize: 12, color: '#fff', lineHeight: 18 },
+ },
+ formatter: function (params) {
+ if (!params.data) return ''
+ const dotKey = `dot${params.dataIndex % landColors.length}`
+ return `{${dotKey}|} {parent|${params.data.name} (${params.data.value}涓�)}`
+ },
},
- formatter: function (params) {
- const children = params?.data?.children || []
- const parentName = params?.data?.name || ''
- const rawVal = params?.data?.value
- const parentValue = typeof rawVal === 'number' && !Number.isNaN(rawVal) ? rawVal : (Number(rawVal) || 0)
- const dotKey = `dot${(params?.dataIndex || 0) % landColors.length}`
- const dot = `{${dotKey}|} `
- const parentLine = `${dot}{parent|${parentName} (${parentValue}绫�)}`
- if (!children.length) return parentLine
- // 鐖剁骇鍏ㄩ儴鏄剧ず锛涘瓙绾ф渶澶� 5 涓紝瓒呭嚭鏄剧ず鐪佺暐鍙�
- const displayed = children.slice(0, 5).map((c) => `{child|${c.name}}`)
- if (children.length > 5) displayed.push('{child|鈥')
- return [parentLine, ...displayed].join('\n')
+ labelLine: {
+ show: true,
+ length: 20,
+ lineStyle: { color: '#B8C8E0' },
},
+ data: dataList.value,
},
- labelLine: {
- show: true,
- length: 20,
- length2: 20,
- lineStyle: {
- color: '#B8C8E0',
- },
+ {
+ // 鍐呭湀瑁呴グ
+ type: 'pie',
+ radius: ['35%', '40%'],
+ center: ['50%', '50%'],
+ silent: true,
+ label: { show: false },
+ itemStyle: { color: 'rgba(0, 127, 255, 0.25)' },
+ data: [1],
},
- itemStyle: {
- color: function (params) {
- return landColors[params.dataIndex % landColors.length]
- },
- },
- data: dataList.value,
- },
- {
- // 鍐呭湀
- type: 'pie',
- radius: ['35%', '40%'],
- center: ['50%', '50%'],
- silent: true,
- label: {
- show: false,
- },
- labelLine: {
- show: false,
- },
- itemStyle: {
- color: 'rgba(0, 127, 255, 0.25)',
- },
- data: [1],
- },
-])
+ ]
+})
-const chartStyle = {
- width: '100%',
- height: '126%',
-}
+const chartStyle = { width: '100%', height: '126%' }
+const pieOptions = { backgroundColor: 'transparent' }
-const pieOptions = {
- backgroundColor: 'transparent',
- textStyle: { color: '#B8C8E0' },
-}
-
-// 浣跨敤灏佽鐨勮儗鏅綅缃皟鏁存柟娉曪紝鍙嚜瀹氫箟鍋忕Щ鍊�
+// 鑳屾櫙澶勭悊閽╁瓙
const { adjustBackgroundPosition, init: initBackground, cleanup: cleanupBackground } = useChartBackground({
wrapperRef: pieWrapperRef,
backgroundRef: pieBackgroundRef,
- offsetX: '-51.5%', // X 杞村亸绉伙紝鍙姩鎬佽皟鏁�
- offsetY: '-39%', // Y 杞村亸绉伙紝鍙姩鎬佽皟鏁�
- watchData: dataList // 鐩戝惉鏁版嵁鍙樺寲锛岃嚜鍔ㄨ皟鏁翠綅缃�
+ offsetX: '-51.5%',
+ offsetY: '-39%',
+ watchData: dataList
})
const loadData = async () => {
try {
- const res = await productCategoryDistribution()
- const items = res?.data?.items || []
- dataList.value = items.map((it) => ({
- name: it.name,
- value: Number(it.value || 0),
- rate: it.rate,
- children: Array.isArray(it.children) ? it.children : [],
- }))
- landLegend.data = dataList.value.map((d) => d.name)
- landSeries.value[0].data = dataList.value
- // 鏁版嵁鍔犺浇瀹屾垚鍚庤皟鏁磋儗鏅綅缃�
- adjustBackgroundPosition()
+ const res = await unqualifiedProductProcessingAnalysis()
+ if (res && res.code === 200) {
+ dataList.value = (res.data || []).map((it) => ({
+ name: it.name,
+ value: Number(it.value || 0),
+ rate: it.rate,
+ }))
+ landLegend.value.data = dataList.value.map((d) => d.name)
+
+ // 鏁版嵁鏇存柊鍚庡井璋冭儗鏅�
+ setTimeout(() => {
+ adjustBackgroundPosition()
+ }, 100)
+ }
} catch (e) {
- console.error('鑾峰彇浜у搧澶х被鍒嗗竷澶辫触:', e)
- dataList.value = []
- landLegend.data = []
- landSeries.value[0].data = []
+ console.error('鑾峰彇鏁版嵁澶辫触:', e)
}
}
-
onMounted(() => {
loadData()
@@ -229,7 +170,6 @@
position: relative;
width: 100%;
height: 320px;
- background: transparent;
}
.pie-background {
@@ -242,9 +182,8 @@
background-repeat: no-repeat;
z-index: 1;
pointer-events: none;
- /* 榛樿灞呬腑锛屼細鍦� JS 涓姩鎬佽皟鏁� */
left: 50%;
top: 50%;
transform: translate(-51.5%, -39%);
}
-</style>
+</style>
\ No newline at end of file
diff --git a/src/views/reportAnalysis/qualityAnalysis/components/right-top.vue b/src/views/reportAnalysis/qualityAnalysis/components/right-top.vue
index 1400cb2..890e99a 100644
--- a/src/views/reportAnalysis/qualityAnalysis/components/right-top.vue
+++ b/src/views/reportAnalysis/qualityAnalysis/components/right-top.vue
@@ -1,83 +1,121 @@
<template>
<div>
- <PanelHeader title="宸ュ崟鎵ц鏁堢巼鍒嗘瀽" />
+ <PanelHeader title="涓嶅悎鏍间骇鍝佹帓鍚�" />
<div class="main-panel panel-item-customers">
<div class="main-panel-container">
- <div
- style="color: white"
- class="main-panel-box"
- v-for="(item, index) in panelList"
- :key="index"
- >
- <div style="flex: 1" class="main-panel-box-left">Top{{ index + 1 }}</div>
- <div style="flex: 3" class="main-panel-box-right">
- <div class="main-panel-box-right-text">
- <span>鎬绘暟閲忥細{{ item.total }}</span>
- <span>宸插畬鎴愶細{{ item.finished }}</span>
- <span>鍚堟牸鐜囷細{{ item.qualifiedRate }}</span>
- </div>
- <div class="main-panel-box-right-progress">
- <el-progress :percentage="item.percentage" :format="format" />
+ <div style="color: white" class="main-panel-box" v-for="(item, index) in panelList" :key="index">
+ <!-- <div style="flex: 1" class="main-panel-box-left">{{ item.rank }}</div> -->
+ <div style="flex: 1" class="main-panel-box-left">{{ item.productName }}</div>
+ <div style="flex: 3" class="main-panel-box-right">
+ <!-- <div class="main-panel-box-right-title">{{ item.productName }}</div> -->
+ <div class="main-panel-box-right-text">
+ <span>鎬绘暟閲忥細{{ item.total }}</span>
+ <span>宸插畬鎴愶細{{ item.finished }}</span>
+ <span>鍚堟牸鐜囷細{{ item.qualifiedRate }}%</span>
+ </div>
+ <div class="main-panel-box-right-progress">
+ <el-progress :percentage="item.percentage" :format="format" />
+ </div>
+ </div>
</div>
</div>
- </div>
- </div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
+import { unqualifiedProductRanking } from '@/api/viewIndex.js'
import PanelHeader from './PanelHeader.vue'
-const panelList = [
- { total: 100, finished: 100, qualifiedRate: 100, percentage: 100 }, // Top1
- { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2
- { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2
- { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2
- { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2
- { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2
- { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2
- { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2
- { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2
- { total: 150, finished: 120, qualifiedRate: 80, percentage: 80 } // Top3
- ]
- const format = (percentage) => {
- return `${percentage}%`;
- }
+
+const panelList = ref([])
+
+const format = (percentage) => {
+ return `${percentage}%`
+}
+
+const fetchData = () => {
+ unqualifiedProductRanking()
+ .then((res) => {
+ if (res?.code === 200 && Array.isArray(res?.data)) {
+ const data = res.data
+ panelList.value = data.map((item, index) => {
+ const total = Number(item.totalCount) || 0
+ const finished = Number(item.completedCount) || 0
+ const passRate = Number(item.passRate) || 0
+
+ return {
+ rank: `Top${index + 1}`,
+ productName: item.productName || `浜у搧${index + 1}`,
+ total: total.toFixed(2),
+ finished: finished.toFixed(2),
+ qualifiedRate: passRate.toFixed(2),
+ percentage: Math.min(100, Math.max(0, passRate)), // 纭繚鐧惧垎姣斿湪0-100涔嬮棿
+ }
+ })
+ }
+ })
+ .catch((err) => {
+ console.error('鑾峰彇宸ュ崟鎵ц鏁堢巼鍒嗘瀽鏁版嵁澶辫触:', err)
+ })
+}
+
onMounted(() => {
- // fetchData()
+ fetchData()
})
</script>
<style scoped>
-.main-panel-box{
+.main-panel-box {
display: flex;
flex-direction: row;
align-items: center;
height: 40px;
- .main-panel-box-left{
+
+ .main-panel-box-left {
background: red;
border-radius: 20px;
text-align: center;
line-height: 32px;
- margin: 0 20px;
+ margin: 0 20px;
}
- .main-panel-box-right{
+
+ .main-panel-box-right {
display: flex;
flex-direction: column;
- .main-panel-box-right-text{
+ flex: 1;
+
+ .main-panel-box-right-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #ffffff;
+ margin-bottom: 6px;
+ }
+
+ .main-panel-box-right-text {
font-size: 12px;
display: flex;
justify-content: space-between;
padding-right: 60px;
+ margin-bottom: 4px;
}
- .main-panel-box-right-progress{
- :deep(.el-progress__text){
+
+ .main-panel-box-right-progress {
+ :deep(.el-progress__text) {
color: white !important;
}
}
}
}
+
+.main-panel-container {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ height: 100%;
+ overflow-y: auto;
+}
+
.main-panel {
display: flex;
flex-direction: column;
@@ -89,5 +127,6 @@
padding: 18px;
width: 100%;
height: 449px;
+ overflow: hidden;
}
</style>
diff --git a/src/views/reportAnalysis/qualityAnalysis/index.vue b/src/views/reportAnalysis/qualityAnalysis/index.vue
index 759bf03..1163154 100644
--- a/src/views/reportAnalysis/qualityAnalysis/index.vue
+++ b/src/views/reportAnalysis/qualityAnalysis/index.vue
@@ -13,7 +13,7 @@
<!-- 椤堕儴鏍囬鏍� -->
<div class="dashboard-header">
- <div class="factory-name">鐢熶骇鏁版嵁鍒嗘瀽</div>
+ <div class="factory-name">杩涢攢璐ㄩ噺绫诲垎鏋�</div>
</div>
<!-- 涓昏鍐呭鍖哄煙 -->
@@ -32,7 +32,6 @@
<!-- 鍙充晶鍖哄煙 -->
<div class="right-panel">
-
<RightTop />
<RightBottom />
</div>
--
Gitblit v1.9.3