From 077ab59c700b85efdd057265bf752ad5942395b2 Mon Sep 17 00:00:00 2001
From: ZN <zhang_12370@163.com>
Date: 星期二, 17 三月 2026 17:36:13 +0800
Subject: [PATCH] feat(quality): 新增质量管理模块的API接口和移动端页面

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

diff --git a/src/pages/qualityManagement/visualization/qualityDashboard.vue b/src/pages/qualityManagement/visualization/qualityDashboard.vue
new file mode 100644
index 0000000..d39e256
--- /dev/null
+++ b/src/pages/qualityManagement/visualization/qualityDashboard.vue
@@ -0,0 +1,276 @@
+<template>
+  <view class="quality-dashboard-page">
+    <PageHeader title="璐ㄩ噺鐪嬫澘" @back="goBack" />
+    
+    <scroll-view scroll-y class="dashboard-scroll">
+      <!-- 鏍峰搧鐘舵�佸垪琛� -->
+      <view class="dashboard-card">
+        <view class="card-header">
+          <text class="card-title">妫�娴嬫牱鍝佸姩鎬佺姸鎬�</text>
+          <up-switch v-model="voiceEnabled" size="18" activeText="璇煶" inactiveText="闈欓煶"></up-switch>
+        </view>
+        <view class="status-list">
+          <view v-for="item in sampleStatus" :key="item.id" class="status-item">
+            <view class="status-left">
+              <view class="status-dot" :class="item.status"></view>
+              <text class="sample-name">{{ item.name }}</text>
+            </view>
+            <view class="status-right">
+              <up-tag :text="statusLabel(item.status)" :type="statusTagType(item.status)" size="mini"></up-tag>
+              <text class="sample-time">{{ item.time }}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 鍚堟牸鐜囧垎鏋� (浠〃鐩�) -->
+      <view class="dashboard-card">
+        <view class="card-header">
+          <text class="card-title">鍚堟牸鐜囧垎鏋�</text>
+        </view>
+        <view class="chart-box">
+          <qiun-data-charts
+            type="gauge"
+            :opts="gaugeOpts"
+            :chartData="gaugeData"
+          />
+        </view>
+        <view class="passrate-summary">
+          <text>褰撳墠鍚堟牸鐜囷細</text>
+          <text class="highlight">{{ (passRate * 100).toFixed(1) }}%</text>
+        </view>
+      </view>
+
+      <!-- 浠诲姟鎺掕 (鏌辩姸鍥�) -->
+      <view class="dashboard-card">
+        <view class="card-header">
+          <text class="card-title">浠诲姟鎺掕 (Top 10)</text>
+        </view>
+        <view class="chart-box">
+          <qiun-data-charts
+            type="column"
+            :opts="columnOpts"
+            :chartData="columnData"
+          />
+        </view>
+      </view>
+
+      <!-- 鍘嗗彶瓒嬪娍 (鎶樼嚎鍥�) -->
+      <view class="dashboard-card">
+        <view class="card-header">
+          <text class="card-title">鍘嗗彶瓒嬪娍</text>
+        </view>
+        <view class="chart-box">
+          <qiun-data-charts
+            type="line"
+            :opts="lineOpts"
+            :chartData="lineData"
+          />
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
+
+const voiceEnabled = ref(false);
+let dataTimer = null;
+
+// 1) 鏍峰搧鍔ㄦ�佺姸鎬�
+const sampleStatus = ref([]);
+const statusPool = ['processing', 'warning', 'error', 'success'];
+
+const statusLabel = (s) => {
+  const labels = { 'processing': '妫�娴嬩腑', 'warning': '棰勮', 'error': '涓嶅悎鏍�', 'success': '鍚堟牸' };
+  return labels[s] || '鏈煡';
+};
+
+const statusTagType = (s) => {
+  const types = { 'processing': 'primary', 'warning': 'warning', 'error': 'error', 'success': 'success' };
+  return types[s] || 'info';
+};
+
+const 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 passRate = ref(0.92);
+const gaugeData = ref({
+  categories: [{ value: 0.92, color: "#2fc25b" }],
+  series: [{ name: "鍚堟牸鐜�", data: 0.92 }]
+});
+const gaugeOpts = {
+  title: { name: "鍚堟牸鐜�", color: "#2fc25b", fontSize: 16 },
+  subtitle: { name: "92%", color: "#666666", fontSize: 12 },
+  extra: { gauge: { type: "default", width: 15, labelColor: "#666666", splitLine: { fixRadius: -10 } } }
+};
+
+// 3) 浠诲姟鎺掕 (鏌辩姸鍥�)
+const columnData = ref({
+  categories: ["浠诲姟1", "浠诲姟2", "浠诲姟3", "浠诲姟4", "浠诲姟5"],
+  series: [{ name: "瀹屾垚鏁�", data: [35, 36, 31, 33, 13] }]
+});
+const columnOpts = {
+  color: ["#1890FF"],
+  padding: [15, 15, 0, 5],
+  enableScroll: false,
+  xAxis: { disableGrid: true },
+  yAxis: { data: [{ min: 0 }] },
+  extra: { column: { type: "group", width: 30, activeBgColor: "#000000", activeBgOpacity: 0.08 } }
+};
+
+// 4) 鍘嗗彶瓒嬪娍 (鎶樼嚎鍥�)
+const lineData = ref({
+  categories: ["10:00", "10:05", "10:10", "10:15", "10:20"],
+  series: [
+    { name: "鏉ユ牱鏁�", data: [35, 8, 25, 37, 4, 20] },
+    { name: "瀹屾垚鏁�", data: [70, 40, 65, 100, 44, 68] }
+  ]
+});
+const lineOpts = {
+  color: ["#1890FF", "#91CB74"],
+  padding: [15, 10, 0, 15],
+  enableScroll: false,
+  xAxis: { disableGrid: true },
+  yAxis: { gridType: "dash", dashLength: 2 },
+  legend: { position: "top" },
+  extra: { line: { type: "straight", width: 2 } }
+};
+
+const refreshData = () => {
+  // 妯℃嫙鏁版嵁鏇存柊
+  const next = randomSample();
+  sampleStatus.value = [next, ...sampleStatus.value].slice(0, 5);
+  
+  const delta = (Math.random() - 0.5) * 0.02;
+  passRate.value = Math.min(0.99, Math.max(0.6, passRate.value + delta));
+  gaugeData.value = {
+    series: [{ name: "鍚堟牸鐜�", data: passRate.value }]
+  };
+  gaugeOpts.subtitle.name = (passRate.value * 100).toFixed(1) + '%';
+};
+
+const goBack = () => {
+  uni.navigateBack();
+};
+
+onMounted(() => {
+  for (let i = 0; i < 5; i++) {
+    sampleStatus.value.push(randomSample());
+  }
+  dataTimer = setInterval(refreshData, 3000);
+});
+
+onBeforeUnmount(() => {
+  if (dataTimer) clearInterval(dataTimer);
+});
+</script>
+
+<style lang="scss" scoped>
+.quality-dashboard-page {
+  background-color: #f5f7fa;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.dashboard-scroll {
+  flex: 1;
+  padding: 20rpx;
+}
+
+.dashboard-card {
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30rpx;
+  border-left: 8rpx solid #3c9cff;
+  padding-left: 20rpx;
+}
+
+.card-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #303133;
+}
+
+.status-list {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+
+.status-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10rpx 0;
+}
+
+.status-left {
+  display: flex;
+  align-items: center;
+  gap: 15rpx;
+}
+
+.status-dot {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  
+  &.processing { background-color: #3c9cff; }
+  &.warning { background-color: #f9ae3d; }
+  &.error { background-color: #f56c6c; }
+  &.success { background-color: #5ac725; }
+}
+
+.sample-name {
+  font-size: 28rpx;
+  color: #303133;
+}
+
+.status-right {
+  display: flex;
+  align-items: center;
+  gap: 20rpx;
+}
+
+.sample-time {
+  font-size: 24rpx;
+  color: #909399;
+}
+
+.chart-box {
+  width: 100%;
+  height: 400rpx;
+}
+
+.passrate-summary {
+  text-align: center;
+  margin-top: 20rpx;
+  font-size: 28rpx;
+  color: #606266;
+  
+  .highlight {
+    font-size: 36rpx;
+    font-weight: bold;
+    color: #3c9cff;
+    margin-left: 10rpx;
+  }
+}
+</style>

--
Gitblit v1.9.3