From de59371f50991a0dbee997eb4a13fd3f5f415ffd Mon Sep 17 00:00:00 2001
From: baoshiwei <baoshiwei@shlanbao.cn>
Date: 星期五, 21 三月 2025 09:45:21 +0800
Subject: [PATCH] feat(login): 添加 Keycloak 登录支持

---
 src/views/qms/trend/quality.vue                |  535 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 /dev/null                                      |    6 
 src/lang/zh_CN.ts                              |    3 
 src/views/login.vue                            |   32 +-
 src/layout/components/SocialCallback/index.vue |    2 
 src/views/qms/sensorRetest/index.vue           |    2 
 src/views/qms/sensor/index.vue                 |    3 
 7 files changed, 556 insertions(+), 27 deletions(-)

diff --git a/src/lang/zh_CN.ts b/src/lang/zh_CN.ts
index 3cc9872..a94a307 100644
--- a/src/lang/zh_CN.ts
+++ b/src/lang/zh_CN.ts
@@ -33,7 +33,8 @@
       maxkey: 'MaxKey鐧诲綍',
       topiam: 'TopIam鐧诲綍',
       gitee: 'Gitee鐧诲綍',
-      github: 'Github鐧诲綍'
+      github: 'Github鐧诲綍',
+      keycloak: 'Keycloak鐧诲綍'
     }
   },
   // 娉ㄥ唽椤甸潰鍥介檯鍖�
diff --git a/src/layout/components/SocialCallback/index.vue b/src/layout/components/SocialCallback/index.vue
index 746de20..84baf34 100644
--- a/src/layout/components/SocialCallback/index.vue
+++ b/src/layout/components/SocialCallback/index.vue
@@ -75,7 +75,7 @@
     socialCode: code,
     socialState: state,
     tenantId: tenantId,
-    source: source,
+    source: 'keycloak',
     clientId: import.meta.env.VITE_APP_CLIENT_ID,
     grantType: 'social'
   };
diff --git a/src/views/login.vue b/src/views/login.vue
index e5d7c23..50d0cb7 100644
--- a/src/views/login.vue
+++ b/src/views/login.vue
@@ -45,21 +45,21 @@
       </el-form-item>
       <el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">{{ proxy.$t('login.rememberPassword') }}</el-checkbox>
       <el-form-item style="float: right">
-        <el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">
-          <svg-icon icon-class="wechat" />
+<!--        <el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">-->
+<!--          <svg-icon icon-class="wechat" />-->
+<!--        </el-button>-->
+        <el-button circle :title="proxy.$t('login.social.keycloak')" @click="doSocialLogin('keycloak')">
+          <svg-icon icon-class="keycloak" />
         </el-button>
-        <el-button circle :title="proxy.$t('login.social.maxkey')" @click="doSocialLogin('maxkey')">
-          <svg-icon icon-class="maxkey" />
-        </el-button>
-        <el-button circle :title="proxy.$t('login.social.topiam')" @click="doSocialLogin('topiam')">
-          <svg-icon icon-class="topiam" />
-        </el-button>
-        <el-button circle :title="proxy.$t('login.social.gitee')" @click="doSocialLogin('gitee')">
-          <svg-icon icon-class="gitee" />
-        </el-button>
-        <el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">
-          <svg-icon icon-class="github" />
-        </el-button>
+<!--        <el-button circle :title="proxy.$t('login.social.topiam')" @click="doSocialLogin('topiam')">-->
+<!--          <svg-icon icon-class="topiam" />-->
+<!--        </el-button>-->
+<!--        <el-button circle :title="proxy.$t('login.social.gitee')" @click="doSocialLogin('gitee')">-->
+<!--          <svg-icon icon-class="gitee" />-->
+<!--        </el-button>-->
+<!--        <el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">-->
+<!--          <svg-icon icon-class="github" />-->
+<!--        </el-button>-->
       </el-form-item>
       <el-form-item style="width: 100%">
         <el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
@@ -223,9 +223,7 @@
 };
 
 onMounted(() => {
-  getCode();
-  initTenantList();
-  getLoginData();
+  doSocialLogin('keycloak')
 });
 </script>
 
diff --git a/src/views/qms/sensor/index.vue b/src/views/qms/sensor/index.vue
index c2a6750..815f919 100644
--- a/src/views/qms/sensor/index.vue
+++ b/src/views/qms/sensor/index.vue
@@ -171,7 +171,8 @@
               <template #append>V</template>
             </el-input>
           </el-form-item>
-        </el-row>
+        </el-col>
+      </el-row>
 
         <el-row>
           <el-col :xs="24" :sm="12">
diff --git a/src/views/qms/sensorRetest/index.vue b/src/views/qms/sensorRetest/index.vue
index eeaf1e7..28938b0 100644
--- a/src/views/qms/sensorRetest/index.vue
+++ b/src/views/qms/sensorRetest/index.vue
@@ -115,7 +115,7 @@
 </template>
 
 <script setup name="SensorRetest" lang="ts">
-import { listSensorRetest, getSensorRetest, delSensorRetest, addSensorRetest, updateSensorRetest } from 'src/api/qms/sensorRetest';
+import { listSensorRetest, getSensorRetest, delSensorRetest, addSensorRetest, updateSensorRetest } from '@/api/qms/sensorRetest';
 import { SensorRetestVO, SensorRetestQuery, SensorRetestForm } from '@/api/qms/sensorRetest/types';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
diff --git a/src/views/qms/trend/quality.vue b/src/views/qms/trend/quality.vue
new file mode 100644
index 0000000..e4560ec
--- /dev/null
+++ b/src/views/qms/trend/quality.vue
@@ -0,0 +1,535 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="浜у搧鍨嬪彿" prop="prodModel">
+              <el-select v-model="queryParams.prodModel" placeholder="璇烽�夋嫨浜у搧鍨嬪彿" clearable @keyup.enter="handleQuery">
+                <el-option v-for="item in prodModelOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="娴嬭瘯椤圭洰" prop="testItem">
+              <el-select v-model="selectedTestItems" :multiple="false" placeholder="璇烽�夋嫨娴嬭瘯椤圭洰" clearable @keyup.enter="handleQuery">
+                <el-option v-for="item in testItemsOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鎵规鍙�" prop="batchCode">
+              <el-input v-model="queryParams.batchCode" placeholder="璇疯緭鍏ユ壒娆″彿" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="鍒涘缓鏃堕棿" style="width: 308px">
+              <el-date-picker
+                v-model="dateRangeCreateTime"
+                value-format="YYYY-MM-DD"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="寮�濮嬫棩鏈�"
+                end-placeholder="缁撴潫鏃ユ湡"
+                unlink-panels
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['qms:testResult:add']">鏂板</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['qms:testResult:edit']">淇敼</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['qms:testResult:remove']">鍒犻櫎</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['qms:testResult:export']">瀵煎嚭</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="handleQuery"></right-toolbar>
+        </el-row>
+      </template>
+
+
+      <div ref="chartRef" style="width: 100%; height: 660px;"></div>
+
+      <!--      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />-->
+    </el-card>
+    <!-- 娣诲姞鎴栦慨鏀规祴璇曠粨鏋滃璇濇 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="testResultFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="鎵规鍙�" prop="batchCode">
+          <el-input v-model="form.batchCode" placeholder="璇疯緭鍏ユ壒娆″彿" />
+        </el-form-item>
+        <el-form-item label="娴嬭瘯搴忓彿" prop="testNum">
+          <el-input v-model="form.testNum" placeholder="璇疯緭鍏ユ祴璇曞簭鍙�" />
+        </el-form-item>
+        <el-form-item label="娴嬭瘯椤圭洰" prop="testItem">
+          <el-select v-model="form.testItem" placeholder="璇烽�夋嫨娴嬭瘯椤圭洰">
+            <el-option v-for="item in testItemsOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="瀹為檯鐢靛帇" prop="voltage">
+          <el-input v-model="form.voltage" placeholder="璇疯緭鍏ュ疄闄呯數鍘�" />
+        </el-form-item>
+        <el-form-item label="瀹為檯鐢垫祦" prop="loadCurrent">
+          <el-input v-model="form.loadCurrent" placeholder="璇疯緭鍏ュ疄闄呯數娴�" />
+        </el-form-item>
+        <el-form-item label="鏍囧噯璺濈" prop="stdDistance">
+          <el-input v-model="form.stdDistance" placeholder="璇疯緭鍏ユ爣鍑嗚窛绂�" />
+        </el-form-item>
+        <el-form-item label="鎰熷簲鐗�" prop="inductor">
+          <el-input v-model="form.inductor" placeholder="璇疯緭鍏ユ劅搴旂墿" />
+        </el-form-item>
+        <el-form-item label="杈撳嚭寮曡剼" prop="output">
+          <el-input v-model="form.output" placeholder="璇疯緭鍏ヨ緭鍑哄紩鑴�" />
+        </el-form-item>
+        <el-form-item label="娴嬭瘯鏁版嵁" prop="testValue">
+          <el-input v-model="form.testValue" placeholder="璇疯緭鍏ユ祴璇曟暟鎹�" />
+        </el-form-item>
+        <el-form-item label="鍒ゆ柇鏉′欢" prop="judgeDetail">
+          <el-input v-model="form.judgeDetail" placeholder="璇疯緭鍏ュ垽鏂潯浠�" />
+        </el-form-item>
+        <el-form-item label="娴嬭瘯缁撴灉" prop="testResult">
+          <el-input v-model="form.testResult" placeholder="璇疯緭鍏ユ祴璇曠粨鏋�" />
+        </el-form-item>
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input v-model="form.remark" placeholder="璇疯緭鍏ュ娉�" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="TestResult" lang="ts">
+import { listTestResultAll, getTestResult, delTestResult, addTestResult, updateTestResult, listTestItem } from '@/api/qms/testResult';
+import { TestResultVO, TestResultQuery, TestResultForm } from '@/api/qms/testResult/types';
+import * as echarts from 'echarts';
+import { listProdModels} from '@/api/qms/batch';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const testResultList = ref<TestResultVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+// 璁剧疆榛樿鏃堕棿涓烘渶杩戜竴涓湀
+const now = new Date();
+const dateRangeCreateTime = ref<[DateModelType, DateModelType]>([]);
+const oneMonthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());
+dateRangeCreateTime.value = [oneMonthAgo.toISOString().split('T')[0], now.toISOString().split('T')[0]] as [DateModelType, DateModelType];
+
+const queryFormRef = ref<ElFormInstance>();
+const testResultFormRef = ref<ElFormInstance>();
+const chartRef = ref<HTMLDivElement | null>(null); // 鏂板 chartRef 寮曠敤
+
+const testItemsOptions = ref<{ label: string, value: string }[]>([]); // 鏂板娴嬭瘯椤圭洰閫夐」
+const selectedTestItems = ref<string | string[]>([]); // 淇敼涓哄吋瀹瑰崟閫夌殑绫诲瀷
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: TestResultForm = {
+  id: undefined,
+  batchCode: undefined,
+  testNum: undefined,
+  testItem: undefined,
+  voltage: undefined,
+  loadCurrent: undefined,
+  stdDistance: undefined,
+  inductor: undefined,
+  output: undefined,
+  testValue: undefined,
+  judgeDetail: undefined,
+  testResult: undefined,
+  remark: undefined
+}
+const prodModelOptions = ref<{ label: string, value: string }[]>([]);
+
+const data = reactive<PageData<TestResultForm, TestResultQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    batchCode: undefined,
+    testNum: undefined,
+    testItem: undefined,
+    voltage: undefined,
+    loadCurrent: undefined,
+    stdDistance: undefined,
+    inductor: undefined,
+    output: undefined,
+    testValue: undefined,
+    judgeDetail: undefined,
+    testResult: undefined,
+    startTime: undefined, // 鏂板 startTime 瀛楁
+    endTime: undefined, // 鏂板 endTime 瀛楁
+    prodModel: undefined, // 鏂板浜у搧鍨嬪彿瀛楁
+    params: {
+      createTime: undefined,
+    }
+  },
+  rules: {
+    id: [
+      { required: true, message: "涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    batchCode: [
+      { required: true, message: "鎵规鍙蜂笉鑳戒负绌�", trigger: "blur" }
+    ],
+    testNum: [
+      { required: true, message: "娴嬭瘯搴忓彿涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    testItem: [
+      { required: true, message: "娴嬭瘯椤圭洰涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    voltage: [
+      { required: true, message: "瀹為檯鐢靛帇涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    loadCurrent: [
+      { required: true, message: "瀹為檯鐢垫祦涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    stdDistance: [
+      { required: true, message: "鏍囧噯璺濈涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    inductor: [
+      { required: true, message: "鎰熷簲鐗╀笉鑳戒负绌�", trigger: "blur" }
+    ],
+    output: [
+      { required: true, message: "杈撳嚭寮曡剼涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    testValue: [
+      { required: true, message: "娴嬭瘯鏁版嵁涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    judgeDetail: [
+      { required: true, message: "鍒ゆ柇鏉′欢涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    testResult: [
+      { required: true, message: "娴嬭瘯缁撴灉涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    prodModel: [
+      { required: true, message: "浜у搧鍨嬪彿涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+// 鑾峰彇鎵�鏈夋祴璇曢」鐩�
+const fetchTestItems = async () => {
+  const res = await listTestItem();
+  testItemsOptions.value = res.data.map(item => ({ label: item, value: item }));
+}
+// 鑾峰彇鎵�鏈変骇鍝佸瀷鍙�
+const fetchProdModels = async () => {
+  const res = await listProdModels();
+  prodModelOptions.value = res.data.map(item => ({ label: item, value: item }));
+}
+
+/** 鏌ヨ娴嬭瘯缁撴灉鍒楄〃 */
+const getList = async () => {
+  loading.value = true;
+  queryParams.value.params = {};
+  // 澶勭悊 selectedTestItems 鐨勫�硷紝鍏煎鍗曢�夊拰澶氶��
+  if (Array.isArray(selectedTestItems.value)) {
+    queryParams.value.testItem = selectedTestItems.value.join(','); // 澶氶�夋ā寮�
+  } else {
+    queryParams.value.testItem = selectedTestItems.value; // 鍗曢�夋ā寮�
+  }
+  proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, 'CreateTime');
+  const res = await listTestResultAll(queryParams.value);
+  testResultList.value = res.data;
+  loading.value = false;
+  updateChart(); // 鏇存柊鍥捐〃
+}
+
+// 鐩戝惉 testResultList 鐨勫彉鍖栵紝鍔ㄦ�佹洿鏂板浘琛�
+watch(testResultList, () => {
+  updateChart();
+});
+
+/** 鍙栨秷鎸夐挳 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 琛ㄥ崟閲嶇疆 */
+const reset = () => {
+  form.value = {...initFormData};
+
+  testResultFormRef.value?.resetFields();
+}
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  // 鏍¢獙鏃堕棿鑼冨洿鏄惁瓒呰繃涓変釜鏈�
+  if (dateRangeCreateTime.value[0] && dateRangeCreateTime.value[1]) {
+    const startDate = new Date(dateRangeCreateTime.value[0]);
+    const endDate = new Date(dateRangeCreateTime.value[1]);
+    const timeDiff = endDate.getTime() - startDate.getTime();
+    const maxDuration = 90 * 24 * 60 * 60 * 1000; // 涓変釜鏈堢殑姣鏁�
+
+    if (timeDiff > maxDuration) {
+      proxy?.$modal.msgWarning("鏃堕棿鑼冨洿涓嶈兘瓒呰繃涓変釜鏈堬紝璇烽噸鏂伴�夋嫨锛�");
+      return;
+    }
+  }
+
+  // 鏍¢獙浜у搧鍨嬪彿鏄惁閫夋嫨
+  if (!queryParams.value.prodModel) {
+    proxy?.$modal.msgWarning("璇烽�夋嫨浜у搧鍨嬪彿锛�");
+    return;
+  }
+
+  getList();
+}
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  dateRangeCreateTime.value = ['', ''];
+  queryFormRef.value?.resetFields();
+  selectedTestItems.value = []; // 閲嶇疆閫変腑鐨勬祴璇曢」鐩�
+  reset(); // 璋冪敤閲嶇疆鏂规硶浠ユ竻绌烘椂闂存瀛楁
+}
+
+/** 澶氶�夋閫変腑鏁版嵁 */
+const handleSelectionChange = (selection: TestResultVO[]) => {
+  ids.value = selection.map(item => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 鏂板鎸夐挳鎿嶄綔 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = "娣诲姞娴嬭瘯缁撴灉";
+}
+
+/** 淇敼鎸夐挳鎿嶄綔 */
+const handleUpdate = async (row?: TestResultVO) => {
+  reset();
+  const _id = row?.id || ids.value[0]
+  const res = await getTestResult(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = "淇敼娴嬭瘯缁撴灉";
+}
+
+/** 鎻愪氦鎸夐挳 */
+const submitForm = () => {
+  testResultFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateTestResult(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addTestResult(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("鎿嶄綔鎴愬姛");
+      dialog.visible = false;
+      await handleQuery();
+    }
+  });
+}
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (row?: TestResultVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('鏄惁纭鍒犻櫎娴嬭瘯缁撴灉缂栧彿涓�"' + _ids + '"鐨勬暟鎹」锛�').finally(() => loading.value = false);
+  await delTestResult(_ids);
+  proxy?.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  await handleQuery();
+}
+
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+const handleExport = () => {
+  proxy?.download('qms/testResult/export', {
+    ...queryParams.value
+  }, `testResult_${new Date().getTime()}.xlsx`)
+}
+
+// 鏇存柊鍥捐〃
+const updateChart = () => {
+  if (!chartRef.value) return;
+  // 閿�姣佸凡鏈夌殑鍥捐〃瀹炰緥
+  const existingChart = echarts.getInstanceByDom(chartRef.value);
+  if (existingChart) {
+    existingChart.dispose();
+  }
+  const chart = echarts.init(chartRef.value);
+
+  // 瀹氫箟20涓笉閲嶅鐨勯鑹叉睜
+  const colorPool = [
+    '#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de',
+    '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc', '#5e7ce0',
+    '#f0c757', '#ff9f7f', '#d48265', '#7fb80e', '#b37feb',
+    '#4dc9b0', '#f47920', '#65b581', '#f08585', '#4a90e2',
+    // 鏂板棰滆壊浠ユ彁楂樺姣斿害
+    '#ff6f61', '#ffd700', '#00ced1', '#da70d6', '#adff2f'
+  ];
+
+  // 浣跨敤Map鏇夸唬Object鏇撮珮鏁�
+  const seriesMap = new Map<string, [string, number][]>();
+  testResultList.value.forEach(item => {
+    const key = item.testItem;
+    if (!seriesMap.has(key)) {
+      seriesMap.set(key, []);
+    }
+    seriesMap.get(key)?.push([item.createTime, item.testValue]);
+  });
+
+  // 瑙f瀽鏈�鍚庝竴鏉℃暟鎹殑鍒ゆ柇鏉′欢瀛楁
+  let upperLimit: number | null = null;
+  let lowerLimit: number | null = null;
+  if (testResultList.value.length > 0) {
+    const lastItem = testResultList.value[testResultList.value.length - 1];
+    const judgeDetail = lastItem.judgeDetail;
+    console.log('judgeDetail:', judgeDetail)
+    const regex = /(\d+\.\d+)\s*(V|mA|uA)\s*鈮s*result\s*鈮s*(\d+\.\d+)\s*(V|mA|uA)/;
+    const match = judgeDetail.match(regex);
+    console.log('match:', match)
+    if (match) {
+      lowerLimit = parseFloat(match[1]);
+      upperLimit = parseFloat(match[3]);
+    }
+  }
+
+  console.log('upperLimit:', upperLimit, 'lowerLimit:', lowerLimit)
+  // 璁$畻鏂扮殑涓婁笅闄愬�硷紝澧炲姞20%
+  const paddingPercentage = 0.1; // 20%鐨刾adding
+  // 璁$畻涓婇檺鐨�20%鏄灏�
+  let padding = lowerLimit * paddingPercentage;
+
+  console.log("padding",padding)
+
+  let newLowerLimit = lowerLimit !== null ? (lowerLimit - padding) : undefined;
+  let newUpperLimit = upperLimit !== null ? (upperLimit + padding) : undefined;
+
+
+  const option = {
+    grid: {
+      top: '15%',
+      left: '5%',
+      right: '5%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'time',
+      name: '鏃堕棿'
+    },
+    yAxis: {
+      type: 'value',
+      name: '娴嬭瘯鏁版嵁',
+      min: newLowerLimit, // 浣跨敤鏂扮殑涓嬮檺鍊�
+      max: newUpperLimit, // 浣跨敤鏂扮殑涓婇檺鍊�
+      axisLabel: {
+        formatter: function (value: number) {
+          return value;
+        }
+      }
+    },
+    legend: {
+      data: Array.from(seriesMap.keys())
+    },
+    series: Array.from(seriesMap).map(([name, data], index) => ({
+      name: name,
+      data: data,
+      type: 'line',
+      animationDuration: 1000,
+      lineStyle: {
+        color: colorPool[index % colorPool.length],
+        width: 2 // 澧炲姞绾挎潯瀹藉害
+      },
+      itemStyle: {
+        color: colorPool[index % colorPool.length],
+        opacity: 0.8 // 娣诲姞閫忔槑搴�
+      },
+      markLine: {
+        symbol: ['none', 'none'],
+        label: {
+          position: 'end',
+          align: 'center', // 娣诲姞 align 灞炴�т互瀹炵幇灞呬腑瀵归綈
+          formatter: function (params: any) {
+            const name = params.name || ''; // 涓婇檺鎴栦笅闄愭爣璇�
+            const value = params.value || ''; // 瀵瑰簲鐨勬暟鍊�
+            return `${name}\n${value}`; // 鎹㈣鏄剧ず锛岀‘淇濅笂涓嬭鍐呭娓呮櫚
+          }
+        },
+        lineStyle: {
+          color: 'red',
+          type: 'dashed'
+        },
+        data: [
+          {
+            name: '涓嬮檺',
+            yAxis: lowerLimit !== null ? lowerLimit : undefined
+          },
+          {
+            name: '涓婇檺',
+            yAxis: upperLimit !== null ? upperLimit : undefined
+          }
+        ]
+      },
+      // markLine: {
+      //   data: [{ type: 'average', name: 'Avg' }]
+      // }
+    })),
+    // 鏂板 dataZoom 閰嶇疆
+    dataZoom: [
+      {
+        type: 'slider', // X杞存粦鍔ㄦ潯
+        xAxisIndex: 0,
+        start: 0, // 鍒濆鑼冨洿璧风偣鐧惧垎姣�
+        end: 100 // 鍒濆鑼冨洿缁堢偣鐧惧垎姣�
+      },
+      {
+        type: 'slider', // Y杞存粦鍔ㄦ潯
+        yAxisIndex: 0,
+        start: 0, // 鍒濆鑼冨洿璧风偣鐧惧垎姣�
+        end: 100 // 鍒濆鑼冨洿缁堢偣鐧惧垎姣�
+      },
+      {
+        type: 'inside', // X杞村唴缃嫋鍔ㄥ拰妗嗛��
+        xAxisIndex: 0,
+        start: 0,
+        end: 100
+      },
+      {
+        type: 'inside', // Y杞村唴缃嫋鍔ㄥ拰妗嗛��
+        yAxisIndex: 0,
+        start: 0,
+        end: 100
+      }
+    ]
+  };
+
+  chart.setOption(option, true);
+  window.addEventListener('resize', () => {
+    chart.resize();
+  });
+}
+
+onMounted(() => {
+  fetchTestItems(); // 璋冪敤鑾峰彇娴嬭瘯椤圭洰鎺ュ彛
+  fetchProdModels(); // 璋冪敤鑾峰彇浜у搧鍨嬪彿鎺ュ彛
+});
+</script>
diff --git a/vite/plugins/i18n.ts b/vite/plugins/i18n.ts
deleted file mode 100644
index 8777d1a..0000000
--- a/vite/plugins/i18n.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
-export default (path: any) => {
-  return VueI18nPlugin({
-    include: [path.resolve(__dirname, '../../src/lang/**.json')]
-  });
-};

--
Gitblit v1.9.3