From c7b4b9a2f4c0f05aeb60a9e3f5fba5d9a3676f3f Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 18 八月 2025 16:22:42 +0800
Subject: [PATCH] 中强恒兴设备管理页面添加

---
 src/views/qualityManagement/visualization/qualityDashboard.vue |  307 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 307 insertions(+), 0 deletions(-)

diff --git a/src/views/qualityManagement/visualization/qualityDashboard.vue b/src/views/qualityManagement/visualization/qualityDashboard.vue
new file mode 100644
index 0000000..57462b7
--- /dev/null
+++ b/src/views/qualityManagement/visualization/qualityDashboard.vue
@@ -0,0 +1,307 @@
+<template>
+  <div class="quality-dashboard">
+    <el-row :gutter="16">
+      <el-col :xs="24" :sm="12">
+        <el-card shadow="hover" class="panel">
+          <template #header>
+            <div class="panel-title">
+              妫�娴嬫牱鍝佸姩鎬佺姸鎬�
+              <div class="actions">
+                <el-switch v-model="voiceEnabled" active-text="璇煶棰勮" inactive-text="闈欓煶" />
+              </div>
+            </div>
+          </template>
+          <div class="status-list">
+            <div v-for="item in sampleStatus" :key="item.id" class="status-item" :class="item.status">
+              <div class="left">
+                <span class="dot" :class="item.status"></span>
+                <span class="name">{{ item.name }}</span>
+              </div>
+              <div class="right">
+                <el-tag :type="statusTagType(item.status)" size="small">{{ statusLabel(item.status) }}</el-tag>
+                <span class="time">{{ item.time }}</span>
+              </div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :xs="24" :sm="12">
+        <el-card shadow="hover" class="panel">
+          <template #header>
+            <div class="panel-title">浠诲姟鎺掕锛圱op 10锛�</div>
+          </template>
+          <EChart :xAxis="tasksXAxis" :yAxis="[{ type: 'value' }]" :series="tasksSeries" :grid="{ left: 40, right: 20, top: 20, bottom: 40 }" :tooltip="{ trigger: 'axis' }" :barColors="['#3b82f6']" :chartStyle="{ height: '320px', width: '100%' }" />
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="16" style="margin-top: 16px;">
+      <el-col :xs="24" :sm="14">
+        <el-card shadow="hover" class="panel">
+          <template #header>
+            <div class="panel-title">鍘嗗彶瓒嬪娍</div>
+          </template>
+          <EChart :xAxis="[{ type: 'category', data: trendXAxis }]" :yAxis="[{ type: 'value', name: '鏁伴噺' }]" :series="trendSeries" :tooltip="{ trigger: 'axis' }" :legend="{ top: 0 }" :lineColors="['#10b981', '#f59e0b']" :chartStyle="{ height: '340px', width: '100%' }" />
+        </el-card>
+      </el-col>
+      <el-col :xs="24" :sm="10">
+        <el-card shadow="hover" class="panel">
+          <template #header>
+            <div class="panel-title">鍚堟牸鐜囧垎鏋�</div>
+          </template>
+          <EChart :series="passRateSeries" :legend="{ show: false }" :chartStyle="{ height: '340px', width: '100%' }" />
+          <div class="passrate-text">
+            褰撳墠鍚堟牸鐜囷細<b>{{ (passRate * 100).toFixed(1) }}%</b>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="16" style="margin-top: 16px;">
+      <el-col :xs="24">
+        <el-card shadow="hover" class="panel">
+          <template #header>
+            <div class="panel-title">SPC 鎺у埗鍥撅紙Xbar锛�</div>
+          </template>
+          <EChart :xAxis="[{ type: 'category', data: spcXAxis }]" :yAxis="[{ type: 'value', name: '娴嬮噺鍊�' }]" :series="spcSeries" :legend="{ top: 0 }" :tooltip="{ trigger: 'axis' }" :lineColors="['#2563eb', '#ef4444', '#f97316', '#22c55e']" :chartStyle="{ height: '380px', width: '100%' }" />
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+  
+</template>
+
+<script setup>
+import { onMounted, onBeforeUnmount, reactive, ref } from 'vue'
+import EChart from '@/components/Echarts/echarts.vue'
+
+const voiceEnabled = ref(true)
+let dataTimer = null
+
+// 1) 鏍峰搧鍔ㄦ�佺姸鎬侊紙婊氬姩鏇存柊锛�
+const sampleStatus = ref([])
+const statusPool = ['processing', 'warning', 'error', 'success']
+function statusLabel(s) {
+  return s === 'processing' ? '妫�娴嬩腑' : s === 'warning' ? '棰勮' : s === 'error' ? '涓嶅悎鏍�' : '鍚堟牸'
+}
+function statusTagType(s) {
+  return s === 'processing' ? 'info' : s === 'warning' ? 'warning' : s === 'error' ? 'danger' : 'success'
+}
+function randomSample() {
+  const id = Math.random().toString(36).slice(2, 8)
+  const status = statusPool[Math.floor(Math.random() * statusPool.length)]
+  const name = `鏍峰搧-${Math.floor(Math.random() * 900 + 100)}`
+  const time = new Date().toLocaleTimeString('zh-CN', { hour12: false })
+  return { id, name, status, time }
+}
+
+// 2) 浠诲姟鎺掕锛堟煴鐘跺浘锛�
+const tasksXAxis = reactive([{ type: 'category', data: [] }])
+const tasksSeries = ref([
+  {
+    type: 'bar',
+    data: [],
+    label: { show: true, position: 'inside', align: 'center', verticalAlign: 'middle', color: '#fff' },
+    encode: undefined,
+  },
+])
+
+// 3) 鍘嗗彶瓒嬪娍锛堟姌绾匡級
+const trendXAxis = ref([])
+const trendSeries = ref([
+  { name: '鏉ユ牱鏁�', type: 'line', smooth: true, data: [] },
+  { name: '瀹屾垚鏁�', type: 'line', smooth: true, data: [] },
+])
+
+// 4) 鍚堟牸鐜囧垎鏋愶紙浠〃鐩橈級
+const passRate = ref(0.92)
+const passRateSeries = ref([
+  {
+    type: 'gauge',
+    progress: { show: true, width: 12 },
+    axisLine: { lineStyle: { width: 12 } },
+    pointer: { show: true },
+    detail: { valueAnimation: true, formatter: (v) => `${(v * 100).toFixed(1)}%` },
+    data: [{ value: passRate.value }],
+  },
+])
+
+// 5) SPC 鎺у埗鍥�
+const spcXAxis = ref([])
+const spcData = ref([]) // 瀹為檯娴嬮噺鍊�
+const CL = ref(50)
+const UCL = ref(55)
+const LCL = ref(45)
+const spcSeries = ref([
+  {
+    name: '娴嬮噺鍧囧��',
+    type: 'line',
+    smooth: false,
+    symbol: 'circle',
+    data: [],
+    markLine: {
+      symbol: 'none',
+      lineStyle: { type: 'dashed', color: '#999' },
+      data: [
+        { yAxis: () => UCL.value, name: 'UCL' },
+        { yAxis: () => CL.value, name: 'CL' },
+        { yAxis: () => LCL.value, name: 'LCL' },
+      ],
+      label: { formatter: ({ name }) => name },
+    },
+  },
+  { name: 'UCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#ef4444' } },
+  { name: 'CL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#f97316' } },
+  { name: 'LCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#22c55e' } },
+])
+
+// 璇煶鎾姤
+function speak(text) {
+  if (!voiceEnabled.value) return
+  if (!('speechSynthesis' in window)) return
+  const utter = new SpeechSynthesisUtterance(text)
+  utter.lang = 'zh-CN'
+  try {
+    window.speechSynthesis.cancel()
+    window.speechSynthesis.speak(utter)
+  } catch (e) {
+    // ignore
+  }
+}
+
+function refreshFakeData() {
+  // 鏍峰搧鐘舵�佹粴鍔�
+  const next = randomSample()
+  sampleStatus.value = [next, ...sampleStatus.value].slice(0, 8)
+
+  // 浠诲姟鎺掕
+  const tasks = Array.from({ length: 10 }).map((_, i) => ({ name: `浠诲姟-${i + 1}`, count: Math.floor(Math.random() * 100 + 20) }))
+  tasks.sort((a, b) => a.count - b.count)
+  tasksXAxis.data = tasks.map(t => t.name)
+  tasksSeries.value[0].data = tasks.map(t => t.count)
+
+  // 鍘嗗彶瓒嬪娍锛堣拷鍔犵偣锛�
+  const nowLabel = new Date().toLocaleTimeString('zh-CN', { minute: '2-digit', second: '2-digit' })
+  if (trendXAxis.value.length > 15) {
+    trendXAxis.value.shift()
+    trendSeries.value[0].data.shift()
+    trendSeries.value[1].data.shift()
+  }
+  trendXAxis.value.push(nowLabel)
+  const incoming = Math.floor(Math.random() * 30 + 20)
+  const finished = Math.max(0, incoming - Math.floor(Math.random() * 10))
+  trendSeries.value[0].data.push(incoming)
+  trendSeries.value[1].data.push(finished)
+
+  // 鍚堟牸鐜囷紙杞诲井娉㈠姩锛�
+  const delta = (Math.random() - 0.5) * 0.02
+  passRate.value = Math.min(0.99, Math.max(0.6, passRate.value + delta))
+  passRateSeries.value[0].data[0].value = passRate.value
+
+  // SPC 鏁版嵁锛堢獥鍙gЩ鍔級
+  const nextVal = CL.value + (Math.random() - 0.5) * 8 // 娉㈠姩
+  if (spcXAxis.value.length > 30) {
+    spcXAxis.value.shift()
+    spcData.value.shift()
+  }
+  spcXAxis.value.push(`${spcXAxis.value.length + 1}`)
+  spcData.value.push(parseFloat(nextVal.toFixed(2)))
+  spcSeries.value[0].data = [...spcData.value]
+  spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value)
+  spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value)
+  spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value)
+
+  // 瑙﹀彂鎾姤锛氬悎鏍肩巼杩囦綆鎴� SPC 瓒呴檺
+  if (passRate.value < 0.8) {
+    speak(`棰勮锛屽綋鍓嶅悎鏍肩巼涓� ${(passRate.value * 100).toFixed(0)}%锛屼綆浜� 80% 闃堝�糮)
+  }
+  const last = spcData.value[spcData.value.length - 1]
+  if (last > UCL.value) {
+    speak(`棰勮锛屾渶鏂版祴閲忓�� ${last.toFixed(2)} 瓒呰繃涓婇檺`)
+  }
+  if (last < LCL.value) {
+    speak(`棰勮锛屾渶鏂版祴閲忓�� ${last.toFixed(2)} 浣庝簬涓嬮檺`)
+  }
+}
+
+onMounted(() => {
+  // 鍒濆鍖栧嚑鏉″亣鏁版嵁
+  sampleStatus.value = Array.from({ length: 5 }).map(() => randomSample())
+  for (let i = 0; i < 10; i++) {
+    trendXAxis.value.push(`T-${i}`)
+    trendSeries.value[0].data.push(Math.floor(Math.random() * 30 + 20))
+    trendSeries.value[1].data.push(Math.floor(Math.random() * 25 + 15))
+  }
+  for (let i = 0; i < 20; i++) {
+    spcXAxis.value.push(`${i + 1}`)
+    const v = CL.value + (Math.random() - 0.5) * 6
+    spcData.value.push(parseFloat(v.toFixed(2)))
+  }
+  spcSeries.value[0].data = [...spcData.value]
+  spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value)
+  spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value)
+  spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value)
+
+  dataTimer = setInterval(refreshFakeData, 10000)
+})
+
+onBeforeUnmount(() => {
+  if (dataTimer) clearInterval(dataTimer)
+  try { window.speechSynthesis && window.speechSynthesis.cancel() } catch (e) {}
+})
+</script>
+
+<style scoped>
+.quality-dashboard {
+  padding: 8px;
+}
+.panel-title {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-weight: 600;
+}
+.status-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  max-height: 320px;
+  overflow: auto;
+}
+.status-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 10px;
+  border-radius: 6px;
+  background: var(--el-fill-color-light);
+}
+.status-item .left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.status-item .right {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+.status-item .name { font-weight: 500; }
+.status-item .time { color: var(--el-text-color-secondary); font-size: 12px; }
+.dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  display: inline-block;
+}
+.dot.processing { background: #60a5fa; }
+.dot.warning { background: #f59e0b; }
+.dot.error { background: #ef4444; }
+.dot.success { background: #10b981; }
+.passrate-text {
+  text-align: center;
+  margin-top: 8px;
+}
+</style>
+
+

--
Gitblit v1.9.3