Przeglądaj źródła

Merge branch 'master' into jg-master

# Conflicts:
#	src/router/modules/analysis.ts
#	src/store/modules/dictionary.ts
luoxiao 2 miesięcy temu
rodzic
commit
db2148f3ae
40 zmienionych plików z 16616 dodań i 1302 usunięć
  1. 1 1
      .env.development
  2. 3 0
      .vscode/settings.json
  3. 137 1
      src/api/analysis/index.js
  4. 22 2
      src/api/file/index.ts
  5. 2 0
      src/api/spc/index.js
  6. 234 0
      src/api/statistic/firstPassYieldMockData.ts
  7. 506 0
      src/api/statistic/reportMockData.ts
  8. 286 0
      src/components/ReportView/reportTemplate.vue
  9. 9 9
      src/components/Search/index.vue
  10. 2 1
      src/hooks/userCrud.ts
  11. 18 0
      src/router/modules/statistic.ts
  12. 2 1
      src/store/modules/dictionary.ts
  13. 349 0
      src/views/analysis/process/C.vue
  14. 1044 0
      src/views/analysis/process/CList.vue
  15. 316 0
      src/views/analysis/process/EWMA.vue
  16. 1044 0
      src/views/analysis/process/EWMAList.vue
  17. 566 0
      src/views/analysis/process/I-MR.vue
  18. 1044 0
      src/views/analysis/process/I-MRList.vue
  19. 350 0
      src/views/analysis/process/NP.vue
  20. 1044 0
      src/views/analysis/process/NPList.vue
  21. 328 0
      src/views/analysis/process/P.vue
  22. 1044 0
      src/views/analysis/process/PList.vue
  23. 319 0
      src/views/analysis/process/U.vue
  24. 1044 0
      src/views/analysis/process/UList.vue
  25. 546 0
      src/views/analysis/process/Xbar-R-2.vue
  26. 529 0
      src/views/analysis/process/Xbar-R.vue
  27. 816 0
      src/views/analysis/process/Xbar-RList.vue
  28. 552 0
      src/views/analysis/process/Xbar-S-2.vue
  29. 546 0
      src/views/analysis/process/Xbar-S.vue
  30. 780 0
      src/views/analysis/process/Xbar-SList.vue
  31. 1304 0
      src/views/analysis/process/index-1.vue
  32. 342 1017
      src/views/analysis/process/index.vue
  33. 125 43
      src/views/analysis/spc/index.vue
  34. 302 208
      src/views/analysis/target/index/index.vue
  35. 184 0
      src/views/charts/index.vue
  36. 2 2
      src/views/login/index.vue
  37. 468 0
      src/views/statistic/firstPassYield/index.vue
  38. 233 17
      src/views/statistic/report/index.vue
  39. 163 0
      src/views/statistic/report/operationForm.vue
  40. 10 0
      src/views/statistic/statistic/index.vue

+ 1 - 1
.env.development

@@ -8,7 +8,7 @@ VITE_APP_PORT = 3005
 VITE_APP_BASE_API = '/dev-api'
 
 # 上传文件接口地址
-VITE_APP_UPLOAD_URL = 'http://192.168.101.4:7105'
+VITE_APP_UPLOAD_URL = 'http://127.0.0.1:7105'
 # 开发接口地址
  VITE_APP_API_URL = 'http://127.0.0.1:7105'
 

+ 3 - 0
.vscode/settings.json

@@ -77,4 +77,7 @@
   "i18n-ally.localesPaths": [
     "src/lang"
   ],
+  "[vue]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
 }

+ 137 - 1
src/api/analysis/index.js

@@ -1,8 +1,137 @@
 import request from "@/utils/request";
+import axios from "axios";
 
+export function collectData(data) {
+  return request({
+    url: "/api/v1/spc/collectData",
+    method: "post",
+    data,
+  });
+}
+export function cpkCompute(params) {
+  return request({
+    url: "/api/v1/spc/cpkCompute",
+    method: "post",
+    params: params,
+  });
+}
+export function deleteQualityFile(data) {
+  return request({
+    url: "/api/v1/quality/file/del",
+    method: "post",
+    data,
+  });
+}
+export function getQualityFileList(data) {
+  return request({
+    url: "/api/v1/quality/file/list",
+    method: "post",
+    data,
+  });
+}
+export function EWMACompute(data) {
+  return request({
+    url: "/api/v1/spc/EWMACompute",
+    method: "post",
+    data,
+  });
+}
+export function EWMACompute2(params) {
+  return request({
+    url: "/api/v1/spc/EWMACompute2",
+    method: "get",
+    params: params,
+  });
+}
+export function IMRCompute(data) {
+  return request({
+    url: "/api/v1/spc/IMRCompute",
+    method: "post",
+    data,
+  });
+}
+export function IMRCompute2(params) {
+  return request({
+    url: "/api/v1/spc/IMRCompute2",
+    method: "get",
+    params: params,
+  });
+}
+export function UCompute(data) {
+  return request({
+    url: "/api/v1/spc/UCompute",
+    method: "post",
+    data,
+  });
+}
+export function UCompute2(params) {
+  return request({
+    url: "/api/v1/spc/UCompute2",
+    method: "get",
+    params: params,
+  });
+}
+export function CCompute(data) {
+  return request({
+    url: "/api/v1/spc/CCompute",
+    method: "post",
+    data,
+  });
+}
+export function CCompute2(params) {
+  return request({
+    url: "/api/v1/spc/CCompute2",
+    method: "get",
+    params: params,
+  });
+}
+export function NPCompute(data) {
+  return request({
+    url: "/api/v1/spc/NPCompute",
+    method: "post",
+    data,
+  });
+}
+export function NPCompute2(params) {
+  return request({
+    url: "/api/v1/spc/NPCompute2",
+    method: "get",
+    params: params,
+  });
+}
+
+export function PCompute(data) {
+  return request({
+    url: "/api/v1/spc/PCompute",
+    method: "post",
+    data,
+  });
+}
+export function PCompute2(params) {
+  return request({
+    url: "/api/v1/spc/PCompute2",
+    method: "get",
+    params: params,
+  });
+}
+
+export function XBarSCompute(data) {
+  return request({
+    url: "/api/v1/spc/XBarSCompute",
+    method: "post",
+    data,
+  });
+}
+export function XBarRCompute(data) {
+  return request({
+    url: "/api/v1/spc/XBarRCompute",
+    method: "post",
+    data,
+  });
+}
 export function getData(data) {
   return request({
-    url: "/api/v1/spc/page1",
+    url: "/api/v1/spc/page",
     method: "post",
     data,
   });
@@ -36,3 +165,10 @@ export function updateData(data) {
     data,
   });
 }
+export function getTaskCode(data) {
+  return request({
+    url: "/api/v1/quality/task/list",
+    method: "post",
+    data,
+  });
+}

+ 22 - 2
src/api/file/index.ts

@@ -2,12 +2,32 @@ import request from "@/utils/request";
 import { AxiosPromise } from "axios";
 import { FileInfo } from "./types";
 
+export function uploadFileApi(
+  file: File,
+  generatePdf: boolean = false
+): AxiosPromise<FileInfo> {
+  const formData = new FormData();
+  console.log(file);
+  formData.append("file", file);
+  formData.append("fileName", file.name);
+  formData.append("generatePdf", generatePdf);
+  return request({
+    url: "/api/v1/base/upload",
+    method: "post",
+    data: formData,
+    headers: {
+      "Content-Type": "multipart/form-data",
+    },
+  });
+}
+
+
 /**
  * 上传文件
  *
  * @param file
  */
-export function uploadFileApi(file: File): AxiosPromise<FileInfo> {
+/*export function uploadFileApi(file: File): AxiosPromise<FileInfo> {
   const formData = new FormData();
   formData.append("file", file);
   return request({
@@ -18,7 +38,7 @@ export function uploadFileApi(file: File): AxiosPromise<FileInfo> {
       "Content-Type": "multipart/form-data",
     },
   });
-}
+}*/
 
 /**
  * 删除文件

+ 2 - 0
src/api/spc/index.js

@@ -65,3 +65,5 @@ export function deleteRuleData(data) {
     data,
   });
 }
+
+

+ 234 - 0
src/api/statistic/firstPassYieldMockData.ts

@@ -0,0 +1,234 @@
+// 用于模拟数据,实际开发中应从后端获取数据
+
+export const records = [
+  {
+    id: 1,
+    productLineName: "微电子车间",
+    productName: "产品A",
+    productType: "类型1",
+    productCode: "CODE1",
+    timeStr: "2025-03-22",
+    batchNo: "2001",
+    goodRate: "95%",
+    badRate: "5%",
+  },
+  {
+    id: 2,
+    productLineName: "微电子车间",
+    productName: "产品A",
+    productType: "类型2",
+    productCode: "CODE2",
+    timeStr: "2025-03-22",
+    batchNo: "2002",
+    goodRate: "90%",
+    badRate: "10%",
+  },
+  {
+    id: 3,
+    productLineName: "微电子车间",
+    productName: "产品A",
+    productType: "类型1",
+    productCode: "CODE3",
+    timeStr: "2025-03-22",
+    batchNo: "2003",
+    goodRate: "85%",
+    badRate: "15%",
+  },
+  {
+    id: 4,
+    productLineName: "微电子车间",
+    productName: "产品A",
+    productType: "类型2",
+    productCode: "CODE4",
+    timeStr: "2025-03-22",
+    batchNo: "2004",
+    goodRate: "80%",
+    badRate: "20%",
+  },
+  {
+    id: 5,
+    productLineName: "微电子车间",
+    productName: "产品A",
+    productType: "类型1",
+    productCode: "CODE5",
+    timeStr: "2025-03-22",
+    batchNo: "2005",
+    goodRate: "75%",
+    badRate: "25%",
+  },
+  {
+    id: 6,
+    productLineName: "微电子车间",
+    productName: "产品B",
+    productType: "类型2",
+    productCode: "CODE6",
+    timeStr: "2025-03-22",
+    batchNo: "2006",
+    goodRate: "70%",
+    badRate: "30%",
+  },
+  {
+    id: 7,
+    productLineName: "微电子车间",
+    productName: "产品B",
+    productType: "类型1",
+    productCode: "CODE7",
+    timeStr: "2025-03-22",
+    batchNo: "2007",
+    goodRate: "65%",
+    badRate: "35%",
+  },
+  {
+    id: 8,
+    productLineName: "微电子车间",
+    productName: "产品B",
+    productType: "类型2",
+    productCode: "CODE8",
+    timeStr: "2025-03-22",
+    batchNo: "2008",
+    goodRate: "60%",
+    badRate: "40%",
+  },
+  {
+    id: 9,
+    productLineName: "微电子车间",
+    productName: "产品B",
+    productType: "类型1",
+    productCode: "CODE9",
+    timeStr: "2025-03-22",
+    batchNo: "2009",
+    goodRate: "55%",
+    badRate: "45%",
+  },
+  {
+    id: 10,
+    productLineName: "微电子车间",
+    productName: "产品B",
+    productType: "类型2",
+    productCode: "CODE10",
+    timeStr: "2025-03-22",
+    batchNo: "2010",
+    goodRate: "50%",
+    badRate: "50%",
+  },
+  {
+    id: 11,
+    productLineName: "微电子车间",
+    productName: "产品C",
+    productType: "类型1",
+    productCode: "CODE11",
+    timeStr: "2025-03-22",
+    batchNo: "2011",
+    goodRate: "45%",
+    badRate: "55%",
+  },
+  {
+    id: 12,
+    productLineName: "微电子车间",
+    productName: "产品C",
+    productType: "类型2",
+    productCode: "CODE12",
+    timeStr: "2025-03-22",
+    batchNo: "2012",
+    goodRate: "40%",
+    badRate: "60%",
+  },
+  {
+    id: 13,
+    productLineName: "微电子车间",
+    productName: "产品C",
+    productType: "类型1",
+    productCode: "CODE13",
+    timeStr: "2025-03-22",
+    batchNo: "2013",
+    goodRate: "35%",
+    badRate: "65%",
+  },
+  {
+    id: 14,
+    productLineName: "微电子车间",
+    productName: "产品C",
+    productType: "类型2",
+    productCode: "CODE14",
+    timeStr: "2025-03-22",
+    batchNo: "2014",
+    goodRate: "30%",
+    badRate: "70%",
+  },
+  {
+    id: 15,
+    productLineName: "微电子车间",
+    productName: "产品C",
+    productType: "类型1",
+    productCode: "CODE15",
+    timeStr: "2025-03-22",
+    batchNo: "2015",
+    goodRate: "25%",
+    badRate: "75%",
+  },
+  {
+    id: 16,
+    productLineName: "微电子车间",
+    productName: "产品D",
+    productType: "类型2",
+    productCode: "CODE16",
+    timeStr: "2025-03-22",
+    batchNo: "2016",
+    goodRate: "20%",
+    badRate: "80%",
+  },
+  {
+    id: 17,
+    productLineName: "微电子车间",
+    productName: "产品D",
+    productType: "类型1",
+    productCode: "CODE17",
+    timeStr: "2025-03-22",
+    batchNo: "2017",
+    goodRate: "15%",
+    badRate: "85%",
+  },
+  {
+    id: 18,
+    productLineName: "微电子车间",
+    productName: "产品D",
+    productType: "类型2",
+    productCode: "CODE18",
+    timeStr: "2025-03-22",
+    batchNo: "2018",
+    goodRate: "10%",
+    badRate: "90%",
+  },
+  {
+    id: 19,
+    productLineName: "微电子车间",
+    productName: "产品D",
+    productType: "类型1",
+    productCode: "CODE19",
+    timeStr: "2025-03-22",
+    batchNo: "2019",
+    goodRate: "5%",
+    badRate: "95%",
+  },
+  {
+    id: 20,
+    productLineName: "微电子车间",
+    productName: "产品D",
+    productType: "类型2",
+    productCode: "CODE20",
+    timeStr: "2025-03-22",
+    batchNo: "2020",
+    goodRate: "50%",
+    badRate: "50%",
+  },
+];
+export const getMockData = async ({ page, limit }) => {
+  const start = (page - 1) * limit;
+  const end = page * limit;
+  return {
+    data: {
+      records: records.slice(start, end),
+      totalCount: records.length,
+    },
+  };
+};

+ 506 - 0
src/api/statistic/reportMockData.ts

@@ -0,0 +1,506 @@
+import { da, id } from "element-plus/es/locale";
+
+export const getMockData = async () => {
+  const records = [
+    {
+      id: 1,
+      reportCode: "RC001",
+      reportName: "报告A",
+      reportType: "1",
+      creator: "张三",
+      created: "2025-03-22",
+      genDate: "2025-03-22",
+    },
+    {
+      id: 2,
+      reportCode: "RC002",
+      reportName: "报告B",
+      reportType: "2",
+      creator: "李四",
+      created: "2025-03-22",
+      genDate: "2025-03",
+    },
+    {
+      id: 3,
+      reportCode: "RC003",
+      reportName: "报告C",
+      reportType: "1",
+      creator: "王五",
+      created: "2025-03-22",
+      genDate: "2025-03-22",
+    },
+    {
+      id: 4,
+      reportCode: "RC004",
+      reportName: "报告D",
+      reportType: "2",
+      creator: "赵六",
+      created: "2025-03-22",
+      genDate: "2025-03",
+    },
+    {
+      id: 5,
+      reportCode: "RC005",
+      reportName: "报告E",
+      reportType: "1",
+      creator: "孙七",
+      created: "2025-03-22",
+      genDate: "2025-03-22",
+    },
+    {
+      id: 6,
+      reportCode: "RC006",
+      reportName: "报告F",
+      reportType: "2",
+      creator: "周八",
+      created: "2025-03-22",
+      genDate: "2025-03",
+    },
+    {
+      id: 7,
+      reportCode: "RC007",
+      reportName: "报告G",
+      reportType: "1",
+      creator: "吴九",
+      created: "2025-03-22",
+      genDate: "2025-03-22",
+    },
+    {
+      id: 8,
+      reportCode: "RC008",
+      reportName: "报告H",
+      reportType: "2",
+      creator: "郑十",
+      created: "2025-03-22",
+      genDate: "2025-03",
+    },
+    {
+      id: 9,
+      reportCode: "RC009",
+      reportName: "报告I",
+      reportType: "1",
+      creator: "冯十一",
+      created: "2025-03-22",
+      genDate: "2025-03-22",
+    },
+    {
+      id: 10,
+      reportCode: "RC010",
+      reportName: "报告J",
+      reportType: "2",
+      creator: "陈十二",
+      created: "2025-03-22",
+      genDate: "2025-03",
+    },
+    {
+      id: 11,
+      reportCode: "RC011",
+      reportName: "报告K",
+      reportType: "1",
+      creator: "褚十三",
+      created: "2025-03-22",
+      genDate: "2025-03-22",
+    },
+    {
+      id: 12,
+      reportCode: "RC012",
+      reportName: "报告L",
+      reportType: "2",
+      creator: "卫十四",
+      created: "2025-03-22",
+      genDate: "2025-03",
+    },
+  ];
+  return {
+    data: {
+      records: records,
+      totalCount: records.length,
+    },
+  };
+};
+
+export const moduleScreeningData = ref([
+  {
+    id: 1,
+    model: "TM157-C",
+    batch: "2401",
+    preInspection: { quantity: 2, rejects: 0 },
+    highTempStorage: { quantity: 2, rejects: 0 },
+    tempCycle: { quantity: 2, rejects: 0 },
+    randomVibration: { quantity: 2, rejects: 0 },
+    electricalTest: { quantity: 2, rejects: 0 },
+    aging: { quantity: 2, rejects: 0 },
+    finalElectricalTest: { quantity: 2, rejects: 0 },
+    externalInspection: { quantity: 2, rejects: 0 },
+  },
+  {
+    id: 2,
+    model: "HJ-195C",
+    batch: "2405",
+    preInspection: { quantity: 11, rejects: 0 },
+    highTempStorage: { quantity: 11, rejects: 0 },
+    tempCycle: { quantity: 11, rejects: 0 },
+    randomVibration: { quantity: 11, rejects: 0 },
+    electricalTest: { quantity: 11, rejects: 0 },
+    aging: { quantity: 11, rejects: 0 },
+    finalElectricalTest: { quantity: 11, rejects: 0 },
+    externalInspection: { quantity: 11, rejects: 0 },
+  },
+  {
+    id: 3,
+    model: "TM591",
+    batch: "2312",
+    preInspection: { quantity: 10, rejects: 0 },
+    highTempStorage: { quantity: 10, rejects: 0 },
+    tempCycle: { quantity: 10, rejects: 0 },
+    randomVibration: { quantity: 10, rejects: 0 },
+    electricalTest: { quantity: 10, rejects: 0 },
+    aging: { quantity: 10, rejects: 0 },
+    finalElectricalTest: { quantity: 10, rejects: 0 },
+    externalInspection: { quantity: 10, rejects: 0 },
+  },
+  {
+    id: 4,
+    model: "TM665",
+    batch: "2403",
+    preInspection: { quantity: 2, rejects: 0 },
+    highTempStorage: { quantity: 2, rejects: 0 },
+    tempCycle: { quantity: 2, rejects: 0 },
+    randomVibration: { quantity: 2, rejects: 0 },
+    electricalTest: { quantity: 2, rejects: 0 },
+    aging: { quantity: 2, rejects: 0 },
+    finalElectricalTest: { quantity: 2, rejects: 0 },
+    externalInspection: { quantity: 2, rejects: 0 },
+  },
+  {
+    id: 5,
+    model: "TP28DC3A",
+    batch: "2406",
+    preInspection: { quantity: 6, rejects: 0 },
+    highTempStorage: { quantity: 6, rejects: 0 },
+    tempCycle: { quantity: 6, rejects: 0 },
+    randomVibration: { quantity: 6, rejects: 0 },
+    electricalTest: { quantity: 6, rejects: 0 },
+    aging: { quantity: 6, rejects: 0 },
+    finalElectricalTest: { quantity: 6, rejects: 0 },
+    externalInspection: { quantity: 6, rejects: 0 },
+  },
+  {
+    id: 6,
+    model: "TS200DC28S28S(K)",
+    batch: "2406",
+    preInspection: { quantity: 5, rejects: 0 },
+    highTempStorage: { quantity: 5, rejects: 0 },
+    tempCycle: { quantity: 5, rejects: 0 },
+    randomVibration: { quantity: 5, rejects: 0 },
+    electricalTest: { quantity: 5, rejects: 0 },
+    aging: { quantity: 5, rejects: 0 },
+    finalElectricalTest: { quantity: 5, rejects: 0 },
+    externalInspection: { quantity: 5, rejects: 0 },
+  },
+  {
+    id: 7,
+    model: "TM535G",
+    batch: "2312",
+    preInspection: { quantity: 5, rejects: 0 },
+    highTempStorage: { quantity: 5, rejects: 0 },
+    tempCycle: { quantity: 5, rejects: 0 },
+    randomVibration: { quantity: 5, rejects: 0 },
+    electricalTest: { quantity: 5, rejects: 0 },
+    aging: { quantity: 5, rejects: 0 },
+    finalElectricalTest: { quantity: 5, rejects: 0 },
+    externalInspection: { quantity: 5, rejects: 0 },
+  },
+]);
+
+export const consistencyTestData = ref([
+  {
+    id: 1,
+    model: "JTH80DC28S12LB0G",
+    batch: "2404",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "13",
+  },
+  {
+    id: 2,
+    model: "JTPM75RSK060Mb",
+    batch: "2411",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "13",
+  },
+  {
+    id: 3,
+    model: "JTH121MC",
+    batch: "2415",
+    inspectionStatus: "已完成",
+    sampleQuantity: "10",
+  },
+  {
+    id: 4,
+    model: "JTH707MC",
+    batch: "2415",
+    inspectionStatus: "已完成",
+    sampleQuantity: "10",
+  },
+  {
+    id: 5,
+    model: "JTH120DC28S28LB0G",
+    batch: "2406",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "13",
+  },
+  {
+    id: 6,
+    model: "JTH20DC28D±18QL0G",
+    batch: "2413",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "16+2 只不合格品",
+  },
+  {
+    id: 7,
+    model: "JTH15DC28S5SQG",
+    batch: "2350",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "10",
+  },
+  {
+    id: 8,
+    model: "JTH40DC28D±15PL0G",
+    batch: "2324",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "10",
+  },
+  {
+    id: 9,
+    model: "TH329HMC",
+    batch: "2415",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "10",
+  },
+  {
+    id: 10,
+    model: "JTH163",
+    batch: "2416",
+    inspectionStatus: "已完成",
+    sampleQuantity: "3+2 只不合格品(周期性检验)",
+  },
+  {
+    id: 11,
+    model: "JTH437MC",
+    batch: "2418",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "10",
+  },
+  {
+    id: 12,
+    model: "JTH20DC28S5QL-B",
+    batch: "2417",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "13",
+  },
+  {
+    id: 13,
+    model: "JTH20DC28S12NHMC",
+    batch: "2418",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "3+2 只不合格品(周期性检验)",
+  },
+  {
+    id: 14,
+    model: "JTH121MC",
+    batch: "2420",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "3 只不合格品(周期性检验)",
+  },
+  {
+    id: 15,
+    model: "JTH20DC28S12NHMC",
+    batch: "2423",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "3(周期性检验)",
+  },
+  {
+    id: 16,
+    model: "JTPM75RSK060Mb",
+    batch: "2424",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "3+2 只不合格品(周期性检验)",
+  },
+  {
+    id: 17,
+    model: "JTH117MC",
+    batch: "2425",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "10",
+  },
+  {
+    id: 18,
+    model: "JTH422GMC",
+    batch: "2421",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "10",
+  },
+  {
+    id: 19,
+    model: "MTPM2209-P1(底座)",
+    batch: "2412",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "10(周期性检验)",
+  },
+  {
+    id: 20,
+    model: "MTPM2211-P1a(底座)",
+    batch: "2412",
+    inspectionStatus: "已完成",
+    sampleQuantity: "20",
+  },
+  {
+    id: 21,
+    model: "MTPM2211-P1a(盖板)",
+    batch: "2352",
+    inspectionStatus: "已完成",
+    sampleQuantity: "15",
+  },
+  {
+    id: 22,
+    model: "MSPM2620-P3(底座)",
+    batch: "2415",
+    inspectionStatus: "正在进行",
+    sampleQuantity: "20",
+  },
+]);
+
+export const productAcceptanceData = ref([
+  {
+    id: 1,
+    model: "JTH80DC28S12LB0G",
+    batch: "2404",
+    sampleQuantity: 13,
+  },
+  {
+    id: 2,
+    model: "JTPM75RSK060Mb",
+    batch: "2411",
+    sampleQuantity: 13,
+  },
+  {
+    id: 3,
+    model: "JTH121MC",
+    batch: "2415",
+    sampleQuantity: 10,
+  },
+  {
+    id: 4,
+    model: "JTH707MC",
+    batch: "2415",
+    sampleQuantity: 10,
+  },
+  {
+    id: 5,
+    model: "JTH120DC28S28LB0G",
+    batch: "2406",
+    sampleQuantity: 13,
+  },
+  {
+    id: 6,
+    model: "JTH20DC28D±18QL0G",
+    batch: "2413",
+    sampleQuantity: 18,
+  },
+  {
+    id: 7,
+    model: "JTH15DC28S5SQG",
+    batch: "2350",
+    sampleQuantity: 10,
+  },
+  {
+    id: 8,
+    model: "JTH40DC28D±15PL0G",
+    batch: "2324",
+    sampleQuantity: 10,
+  },
+  {
+    id: 9,
+    model: "TH329HMC",
+    batch: "2415",
+    sampleQuantity: 10,
+  },
+  {
+    id: 10,
+    model: "JTH163",
+    batch: "2416",
+    sampleQuantity: 5,
+  },
+  {
+    id: 11,
+    model: "JTH437MC",
+    batch: "2418",
+    sampleQuantity: 10,
+  },
+  {
+    id: 12,
+    model: "JTH20DC28S5QL-B",
+    batch: "2417",
+    sampleQuantity: 13,
+  },
+  {
+    id: 13,
+    model: "JTH20DC28S12NHMC",
+    batch: "2418",
+    sampleQuantity: 5,
+  },
+  {
+    id: 14,
+    model: "JTH121MC",
+    batch: "2420",
+    sampleQuantity: 3,
+  },
+  {
+    id: 15,
+    model: "JTH20DC28S12NHMC",
+    batch: "2423",
+    sampleQuantity: 3,
+  },
+  {
+    id: 16,
+    model: "JTPM75RSK060Mb",
+    batch: "2424",
+    sampleQuantity: 5,
+  },
+  {
+    id: 17,
+    model: "JTH117MC",
+    batch: "2425",
+    sampleQuantity: 10,
+  },
+  {
+    id: 18,
+    model: "JTH422GMC",
+    batch: "2421",
+    sampleQuantity: 10,
+  },
+  {
+    id: 19,
+    model: "MTPM2209-P1(底座)",
+    batch: "2412",
+    sampleQuantity: 10,
+  },
+  {
+    id: 20,
+    model: "MTPM2211-P1a(底座)",
+    batch: "2412",
+    sampleQuantity: 20,
+  },
+  {
+    id: 21,
+    model: "MTPM2211-P1a(盖板)",
+    batch: "2352",
+    sampleQuantity: 15,
+  },
+  {
+    id: 22,
+    model: "MSPM2620-P3(底座)",
+    batch: "2415",
+    sampleQuantity: 20,
+  },
+]);

+ 286 - 0
src/components/ReportView/reportTemplate.vue

@@ -0,0 +1,286 @@
+<template>
+  <div class="bgColor" v-if="modelValue" @click.stop="close">
+    <div class="body">
+      <div class="header">
+        <div class="text">报告预览</div>
+        <div class="delete">
+          <el-button
+            style="margin-right: 10px"
+            type="primary"
+            size="small"
+            class="btn"
+            v-print="'#print'"
+            >打印</el-button
+          >
+          <span style="padding: 20px" @click="close">X</span>
+        </div>
+      </div>
+      <div class="box">
+        <el-scrollbar>
+          <div id="print">
+            <div class="tableInfo">
+              <div style="page-break-after: always">
+                <div class="title" style="text-align: center">质量日报</div>
+                <div class="tableTitle">模块筛选情况</div>
+                <div class="info">
+                  <div class="text">产品型号:</div>
+                  <div class="text">产品批次:</div>
+                  <div class="text">报告日期:2025年03月23日</div>
+                </div>
+                <table>
+                  <thead>
+                    <tr>
+                      <th>序号</th>
+                      <th id="lineId" colspan="2">
+                        <span style="float: left; margin-top: 10px"
+                          >产品型号<br />检验批号</span
+                        >
+                        <span style="float: right; margin-top: 10px"
+                          >筛选项目</span
+                        >
+                      </th>
+                      <th>封前 目检</th>
+                      <th>高温 贮存</th>
+                      <th>温度 循环</th>
+                      <th>随机 振动</th>
+                      <th>电测 试</th>
+                      <th>老炼</th>
+                      <th>最终电测试</th>
+                      <th>外部 目检</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr
+                      v-for="(item, index) in moduleScreeningData"
+                      :key="index"
+                    >
+                      <td>{{ item.id }}</td>
+                      <td>{{ item.model }} {{ item.batch }}</td>
+                      <td>
+                        <div>数量</div>
+                        <div>剔除数</div>
+                      </td>
+                      <td>
+                        <div>{{ item.preInspection.quantity }}</div>
+                        <div>{{ item.preInspection.rejects }}</div>
+                      </td>
+                      <td>
+                        <div>{{ item.highTempStorage.quantity }}</div>
+                        <div>{{ item.highTempStorage.rejects }}</div>
+                      </td>
+                      <td>
+                        <div>{{ item.tempCycle.quantity }}</div>
+                        <div>{{ item.tempCycle.rejects }}</div>
+                      </td>
+                      <td>
+                        <div>{{ item.randomVibration.quantity }}</div>
+                        <div>{{ item.randomVibration.rejects }}</div>
+                      </td>
+                      <td>
+                        <div>{{ item.electricalTest.quantity }}</div>
+                        <div>{{ item.electricalTest.rejects }}</div>
+                      </td>
+                      <td>
+                        <div>{{ item.aging.quantity }}</div>
+                        <div>{{ item.aging.rejects }}</div>
+                      </td>
+                      <td>
+                        <div>{{ item.finalElectricalTest.quantity }}</div>
+                        <div>{{ item.finalElectricalTest.rejects }}</div>
+                      </td>
+                      <td>
+                        <div>{{ item.externalInspection.quantity }}</div>
+                        <div>{{ item.externalInspection.rejects }}</div>
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <div style="page-break-after: always">
+                <div class="tableTitle" style="margin-top: 100px">
+                  质量一致性检验
+                </div>
+                <div class="info">
+                  <div class="text">产品型号:</div>
+                  <div class="text">产品批次:</div>
+                  <div class="text">报告日期:2025年03月23日</div>
+                </div>
+                <table>
+                  <thead>
+                    <tr>
+                      <th>序号</th>
+                      <th>产品型号</th>
+                      <th>批号</th>
+                      <th>质量一致性检验情况</th>
+                      <th>留样数量</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr
+                      v-for="(item, index) in consistencyTestData"
+                      :key="index"
+                    >
+                      <td>{{ item.id }}</td>
+                      <td>{{ item.model }}</td>
+                      <td>{{ item.batch }}</td>
+                      <td>{{ item.inspectionStatus }}</td>
+                      <td>{{ item.sampleQuantity }}</td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <div class="tableTitle" style="margin-top: 100px">
+                产品代验收情况
+              </div>
+              <div class="info">
+                <div class="text">产品型号:</div>
+                <div class="text">产品批次:</div>
+                <div class="text">报告日期:2025年03月23日</div>
+              </div>
+              <table>
+                <thead>
+                  <tr>
+                    <th>序号</th>
+                    <th>产品型号</th>
+                    <th>批号</th>
+                    <th>数量</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  <tr
+                    v-for="(item, index) in productAcceptanceData"
+                    :key="index"
+                  >
+                    <td>{{ item.id }}</td>
+                    <td>{{ item.model }}</td>
+                    <td>{{ item.batch }}</td>
+                    <td>{{ item.sampleQuantity }}</td>
+                  </tr>
+                </tbody>
+              </table>
+            </div>
+          </div>
+        </el-scrollbar>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import {
+  moduleScreeningData,
+  consistencyTestData,
+  productAcceptanceData,
+} from "@/api/statistic/reportMockData.ts";
+
+const props = defineProps({
+  modelValue: {
+    type: [Boolean],
+  },
+  // tablesData: {
+  //   type: [Object],
+  // },
+});
+const emits = defineEmits(["update:modelValue"]);
+const close = () => {
+  emits("update:modelValue", false);
+};
+// import html2canvas from "html2canvas";
+
+// const downloadPNG = async () => {
+//   try {
+//     const canvas = await html2canvas(document.getElementById("print"));
+//     const imgData = canvas.toDataURL("image/png");
+//     const downloadLink = document.createElement("a");
+//     downloadLink.href = imgData;
+//     downloadLink.download = "download.png";
+//     downloadLink.click();
+//   } catch (error) {
+//     console.error("Error generating PNG:", error);
+//   }
+// };
+</script>
+
+<style lang="scss" scoped>
+#lineId {
+  background: #f2f2f2
+    url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxsaW5lIHgxPSIwIiB5MT0iMCIgeDI9IjEwMCUiIHkyPSIxMDAlIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjEiLz48L3N2Zz4=)
+    no-repeat 100% center;
+}
+@media print {
+  #print {
+    position: absolute; /* 或 absolute, fixed, 根据需要调整 */
+    top: 20px; /* 调整顶部位置 */
+    margin: 0; /* 重置边距 */
+    width: 700px;
+  }
+}
+table {
+  width: 100%;
+  border-collapse: collapse; /* 合并表格边框 */
+}
+th,
+td {
+  border: 1px solid rgba(0, 0, 0, 0.3); /* 设置所有单元格的边框 */
+  padding: 8px;
+  text-align: left;
+}
+th {
+  background-color: #f2f2f2; /* 设置表头的背景颜色 */
+}
+
+.bgColor {
+  position: fixed;
+  width: 100vw;
+  height: 100vh;
+  z-index: 99999;
+  background-color: rgba(0, 0, 0, 0.3);
+  top: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  left: 0;
+  .body {
+    width: 80%;
+    height: 85vh;
+    background-color: white;
+    display: flex;
+    flex-direction: column;
+    padding: 20px;
+    .header {
+      width: 100%;
+      height: 40px;
+      display: flex;
+      padding-bottom: 10px;
+      justify-content: space-between;
+      border-bottom: 1px solid rgba(0, 0, 0, 0.3);
+    }
+    .box {
+      height: calc(100% - 40px);
+    }
+  }
+}
+.tableInfo {
+  width: 100%;
+  height: 60px;
+
+  .tableTitle {
+    margin: 10px 0;
+    font-size: 20px;
+    font-weight: 600;
+    letter-spacing: 20px;
+    text-align: center;
+  }
+
+  .title {
+    margin: 10px 0;
+    font-size: 20px;
+    font-weight: 600;
+  }
+  .info {
+    display: flex;
+    padding: 0 20px;
+    justify-content: space-between;
+  }
+}
+</style>

+ 9 - 9
src/components/Search/index.vue

@@ -23,7 +23,7 @@
             placeholder="Select"
           >
             <el-option
-              v-for="item in options"
+              v-for="item in option.options"
               :key="item.value"
               :label="item.label"
               :value="item.value"
@@ -61,23 +61,23 @@ const props = defineProps({
     type: Array,
   },
 });
-const emit = defineEmits(["dataList"]);
+const emit = defineEmits(["resetList", "dataList"]);
 const searchForm = ref({});
 const getData = () => {
   emit("dataList");
 };
 const reset = () => {
-  console.log(searchForm.value, "222");
+  emit("resetList");
 };
 const setSearchFrom = () => {
   props.searchOptions.forEach((option) => {});
 };
-const options = [
-  {
-    lable: "a",
-    value: "a",
-  },
-];
+// const options = [
+//   {
+//     lable: "a",
+//     value: "a",
+//   },
+// ];
 defineExpose({ searchForm });
 </script>
 <style lang="scss" scoped>

+ 2 - 1
src/hooks/userCrud.ts

@@ -364,12 +364,13 @@ export const useCrud = (config?: UseCrudConfig) => {
     /**
      * 根据搜索项导出数据
      */
-    exportData: async (urlStr: string) => {
+    exportData: async (urlStr: string, filePath: string) => {
       handleSearchData();
       const response = await request({
         url: urlStr,
         method: "post",
         data: search.value,
+        params: { filePath: filePath },
         responseType: "arraybuffer",
       });
       Utils.downloadFile(response);

+ 18 - 0
src/router/modules/statistic.ts

@@ -20,6 +20,15 @@ export default {
       },
     },
     {
+      path: "first-pass-yield",
+      component: () => import("@/views/statistic/firstPassYield/index.vue"),
+      name: "FirstPassYield",
+      meta: {
+        title: "一次性合格率",
+        icon: "CreditCard",
+      },
+    },
+    {
       path: "report",
       component: () => import("@/views/statistic/report/index.vue"),
       name: "StatisticReport",
@@ -28,5 +37,14 @@ export default {
         icon: "CreditCard",
       },
     },
+    {
+      path: "sales",
+      component: () => import("@/views/sales/index.vue"),
+      name: "Sales",
+      meta: {
+        title: "销售反馈",
+        icon: "Guide",
+      },
+    },
   ],
 };

+ 2 - 1
src/store/modules/dictionary.ts

@@ -33,7 +33,8 @@ export const useDictionaryStore = defineStore("dictionaryStore", () => {
     "excel_states",
     "spc_operation",
     "sales_info_type",
-      "unqualified_audit_type",
+    "unqualified_audit_type",
+    "spc_control_chart",
   ];
   const dicts = ref<{ [key: string]: any[] }>({});
 

+ 349 - 0
src/views/analysis/process/C.vue

@@ -0,0 +1,349 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box" v-show="!addStatus">
+          <div
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+            "
+          >
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              控制图绘制
+            </div>
+
+            <el-button
+              type="primary"
+              v-print="'#print'"
+              style="margin-left: 10px; height: 25px"
+              >打 印</el-button
+            >
+          </div>
+          <div class="info">
+            <div id="print">
+              <div id="charts" :style="{ height: maxHeight / 2 + 'px' }"></div>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from "echarts";
+import { useDictionaryStore } from "@/store";
+import { CCompute } from "@/api/analysis";
+
+const tableData = ref([]);
+const chartData = ref({});
+const param = ref([
+  7, 5, 6, 8, 4, 9, 5, 6, 7, 4, 5, 10, 3, 6, 5, 7, 12, 5, 6, 4,
+]);
+const getTableData = async (data) => {
+  // const { data } = await CCompute(data);
+  data.CL = data.CL.toFixed(4);
+  data.LCL = data.LCL.toFixed(4);
+  data.UCL = data.UCL.toFixed(4);
+  data.DataPoints = data.DataPoints.map((num) => {
+    return parseFloat(num.y.toFixed(4));
+  });
+  chartData.value = data;
+
+  chartsOption1.value.title[0].text = `C控制图`;
+  setChart1();
+};
+
+const { dicts } = useDictionaryStore();
+const opOptions = ref([...dicts.spc_operation]);
+const value = ref(opOptions.value[0].remark);
+const Y1value = ref([]);
+const X1array = ref([]);
+const setY1value = () => {
+  Y1value.value = [];
+  chartData.value.DataPoints.forEach((item) => {
+    Y1value.value.push(item);
+  });
+  Y1value.value.unshift("");
+  Y1value.value.push("");
+
+  chartsOption1.value.series[0].data = Y1value.value;
+};
+
+const setX1array = async () => {
+  X1array.value = await [];
+  chartData.value.DataPoints.forEach((item, index) => {
+    X1array.value.push(index + 1);
+  });
+  X1array.value.unshift("");
+  X1array.value.push("");
+  chartsOption1.value.xAxis[0].data = X1array.value;
+};
+
+const setChart1Info = () => {
+  // chartsOption1.value.title[0].text = `上限=${showData.value.avgMax ? showData.value.avgMax : "-"}`;
+  // chartsOption1.value.title[0].text = `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`;
+  chartsOption1.value.series[0].markLine.data[0].yAxis = chartData.value.UCL;
+  chartsOption1.value.series[0].markLine.data[0].label.formatter = `上限=${
+    chartData.value.UCL
+  }`;
+  chartsOption1.value.series[0].markLine.data[1].yAxis = chartData.value.LCL;
+  chartsOption1.value.series[0].markLine.data[1].label.formatter = `下限=${chartData.value.LCL}`;
+  chartsOption1.value.series[0].markLine.data[2].yAxis = chartData.value.CL;
+  chartsOption1.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    chartData.value.CL
+  }`;
+  // chartsOption1.value.title[2].text = `下限=${showData.value.avgMin ? showData.value.avgMin : "0"}`;
+};
+const setChart1 = () => {
+  setChart1Info();
+  setY1value();
+  setX1array();
+  charts1.value.setOption(chartsOption1.value, true);
+};
+
+const title = ref("调阻精度");
+const showLable = ref("调阻");
+const changeSelect = () => {
+  setTimeout(async () => {
+    // showLable.value = selectRef.value.currentPlaceholder;
+    // opOptions.value.forEach((item) => {
+    //   if (item.dictLabel == showLable.value) {
+    //     lableValue.value = item.dictValue;
+    //   }
+    // });
+    // switch (showLable.value) {
+    //   case "调阻":
+    //     title.value = "调阻精度";
+    //     break;
+    //   case "粘片":
+    //     title.value = "剪切强度";
+    //     break;
+    //   case "键合":
+    //     title.value = "键合强度";
+    //     break;
+    //   default:
+    //     title.value = "调阻精度";
+    //     break;
+    // }
+    // await getTableData();
+    // setChart2();
+  }, 0);
+};
+
+const maxHeight = ref(null);
+const maxWidth = ref(null);
+const charts1 = shallowRef(null);
+const chartsOption1 = ref({
+  title: [
+    {
+      text: `C控制图`,
+      left: "40%",
+    },
+  ],
+  grid: {
+    right: "15%",
+  },
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  tooltip: {
+    show: true,
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+      // scale: true, // 开启自适应缩放
+    },
+  ],
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(chartData.value.UCL) &&
+            paramValue >= Number(chartData.value.LCL)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${chartData.value.UCL}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${chartData.value.LCL}`,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const Height = ref(0);
+const setHeight = () => {
+  Height.value = document.querySelector(".databox").clientHeight;
+  maxHeight.value = document.querySelector(".info").clientHeight;
+  maxWidth.value = document.querySelector(".info").clientWidth;
+};
+
+const setView = () => {
+  setHeight();
+  charts1.value = echarts.init(document.getElementById("charts"));
+  charts1.value.setOption(chartsOption1.value, true);
+};
+onMounted(() => {
+  // init();
+});
+const init = async (data) => {
+  tableData.value = data;
+
+  setHeight();
+
+  if (data) {
+    charts1.value = echarts.init(document.getElementById("charts"));
+    window.addEventListener("resize", setView);
+    await getTableData(data);
+    // changeSelect();
+    charts1.value.setOption(chartsOption1.value, true);
+  }
+};
+onBeforeUnmount(() => {
+  window.removeEventListener("resize", setView);
+});
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+@media print {
+  #print {
+    margin-left: -18%;
+  }
+}
+.formStyle {
+  width: 400px;
+  margin: 20px auto;
+}
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

Plik diff jest za duży
+ 1044 - 0
src/views/analysis/process/CList.vue


+ 316 - 0
src/views/analysis/process/EWMA.vue

@@ -0,0 +1,316 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box" v-show="!addStatus">
+          <div
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+            "
+          >
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              控制图绘制
+            </div>
+
+            <el-button
+              type="primary"
+              v-print="'#print'"
+              style="margin-left: 10px; height: 25px"
+              >打 印</el-button
+            >
+          </div>
+          <div class="info">
+            <div id="print">
+              <div ref="chartRef" style="width: 100%; height: 400px"></div>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, watch } from "vue";
+import * as echarts from "echarts";
+import { EWMACompute } from "@/api/analysis";
+
+// 假设的数据
+var chartData = ref([]);
+
+const param = {
+  ProcessA: [50.1, 50.3, 49.8],
+};
+
+const getTableData = (resultData) => {
+  // 转换函数
+  const transformData = (data) => {
+    const result = [];
+    let sampleId = 1;
+
+    // 遍历原始数据,排除 total 属性
+    for (const key in data) {
+      const arr = data[key];
+      for (const item in arr) {
+        result.push({
+          sampleId: sampleId.toString(),
+          value: arr[item].EWMA,
+          ewma: arr[item].CL,
+          ucl: arr[item].UCL,
+          lcl: arr[item].LCL,
+        });
+        sampleId++;
+      }
+    }
+
+    return result;
+  };
+
+  // 转换后的数据
+  const formattedData = ref(transformData(resultData));
+
+  chartData.value = formattedData.value;
+};
+
+const chartRef = ref(null);
+const chartInstance = ref(null);
+
+const prepareData = () => {
+  let sampleIds = chartData.value.map((item) => item.sampleId);
+  let values = chartData.value.map((item) => item.value);
+  let ewmaValues = chartData.value.map((item) => item.ewma);
+  let uclValues = chartData.value.map((item) => item.ucl);
+  let lclValues = chartData.value.map((item) => item.lcl);
+
+  // 保留四位小数
+  values = values.map((num) => parseFloat(num.toFixed(4)));
+  ewmaValues = ewmaValues.map((num) => parseFloat(num.toFixed(4)));
+  uclValues = uclValues.map((num) => parseFloat(num.toFixed(4)));
+  lclValues = lclValues.map((num) => parseFloat(num.toFixed(4)));
+
+  return {
+    sampleIds,
+    values,
+    ewmaValues,
+    uclValues,
+    lclValues,
+  };
+};
+
+const initChart = (chartDom) => {
+  const myChart = echarts.init(chartDom);
+  const { sampleIds, values, ewmaValues, uclValues, lclValues } = prepareData();
+
+  const option = {
+    title: {
+      text: "EWMA控制图",
+      left: "center",
+    },
+    tooltip: {
+      trigger: "axis",
+    },
+    xAxis: {
+      type: "category",
+      boundaryGap: true, // 不留白,从原点开始
+      data: sampleIds,
+      axisLine: {
+        show: true,
+      },
+    },
+    yAxis: {
+      type: "value",
+      scale: true,
+      // name: "EWMA值",
+    },
+    series: [
+      {
+        name: "数值",
+        type: "line",
+        // step: "start",
+        symbol: "circle", //将小圆点改成实心 不写symbol默认空心
+        symbolSize: 8,
+        data: values.map((value, index) => ({
+          value,
+          itemStyle: {
+            color:
+              value > uclValues[index] || value < lclValues[index]
+                ? "red"
+                : "blue",
+          },
+        })),
+      },
+      {
+        name: "均值",
+        type: "line",
+        showSymbol: false,
+        data: ewmaValues,
+        lineStyle: {
+          color: "green",
+        },
+      },
+      {
+        name: "UCL",
+        type: "line",
+        data: uclValues,
+        showSymbol: false,
+        lineStyle: {
+          color: "red",
+          type: "dashed",
+        },
+      },
+      {
+        name: "LCL",
+        type: "line",
+        data: lclValues,
+        showSymbol: false,
+        lineStyle: {
+          color: "red",
+          type: "dashed",
+        },
+      },
+    ],
+    color: ["blue", "green", "red", "red", "black"],
+  };
+
+  myChart.setOption(option);
+  chartInstance.value = myChart;
+};
+
+const resizeChart = () => {
+  if (chartInstance.value) {
+    chartInstance.value.resize();
+  }
+};
+
+onMounted(() => {
+  window.addEventListener("resize", resizeChart);
+});
+
+// watch(chartData, () => {
+//   if (chartInstance.value) {
+//     const { sampleIds, values, ewmaValues, uclValues, lclValues, meanValue } =
+//       prepareData();
+//     const option = {
+//       title: [
+//         {
+//           text: `EWMA控制图`,
+//           left: "40%",
+//         },
+//       ],
+//       xAxis: {
+//         data: sampleIds,
+//       },
+//       series: [
+//         {
+//           data: values.map((value, index) => ({
+//             value,
+//             itemStyle: {
+//               color:
+//                 value > uclValues[index] || value < lclValues[index]
+//                   ? "red"
+//                   : "blue",
+//             },
+//           })),
+//         },
+//         {
+//           data: ewmaValues,
+//         },
+//         {
+//           data: uclValues,
+//         },
+//         {
+//           data: lclValues,
+//         },
+//         {
+//           data: Array(sampleIds.length).fill(meanValue),
+//         },
+//       ],
+//     };
+//     chartInstance.value.setOption(option);
+//   }
+// });
+
+const init = (data) => {
+  if (data) {
+    nextTick(async () => {
+      await getTableData(data);
+      await initChart(chartRef.value);
+    });
+  }
+};
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+/* 样式可以根据需要进行调整 */
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

Plik diff jest za duży
+ 1044 - 0
src/views/analysis/process/EWMAList.vue


+ 566 - 0
src/views/analysis/process/I-MR.vue

@@ -0,0 +1,566 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box" v-show="!addStatus">
+          <div
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+            "
+          >
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              控制图绘制
+            </div>
+
+            <el-button
+              type="primary"
+              v-print="'#print'"
+              style="margin-left: 10px; height: 25px"
+              >打 印</el-button
+            >
+          </div>
+          <div class="info">
+            <div id="print">
+              <div id="charts" :style="{ height: maxHeight / 2 + 'px' }"></div>
+              <div id="charts1" :style="{ height: maxHeight / 2 + 'px' }"></div>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from "echarts";
+import { useDictionaryStore } from "@/store";
+import { IMRCompute } from "@/api/analysis";
+
+const tableData = ref([]);
+const chartData = ref({
+  MR_CHART: {
+    LCL: 0,
+    CL: 0,
+    points: [],
+    UCL: 0,
+  },
+  I_CHART: {
+    LCL: 0,
+    CL: 0,
+    points: [],
+    UCL: 0,
+  },
+});
+const param = ref({
+  dataList: [7, 10.02, 9, 8.04, 11, 9.23, 10.1, 10.07],
+  subgroupSize: 2,
+});
+const getTableData = async (data) => {
+  data.MR_CHART.LCL = data.MR_CHART.LCL.toFixed(4);
+  data.MR_CHART.UCL = data.MR_CHART.UCL.toFixed(4);
+  data.MR_CHART.CL = data.MR_CHART.CL.toFixed(4);
+  data.MR_CHART.points = data.MR_CHART.points.map((num) =>
+    parseFloat(num.y.toFixed(4))
+  );
+  data.I_CHART.LCL = data.I_CHART.LCL.toFixed(4);
+  data.I_CHART.UCL = data.I_CHART.UCL.toFixed(4);
+  data.I_CHART.CL = data.I_CHART.CL.toFixed(4);
+  data.I_CHART.points = data.I_CHART.points.map((num) =>
+    parseFloat(num.y.toFixed(4))
+  );
+  chartData.value = data;
+
+  chartsOption1.value.title[0].text = `I-MR控制图`;
+  setChart1();
+  setChart2();
+};
+
+const { dicts } = useDictionaryStore();
+const opOptions = ref([...dicts.spc_operation]);
+const value = ref(opOptions.value[0].remark);
+const Y1value = ref([]);
+const X1array = ref([]);
+const setY1value = () => {
+  Y1value.value = [];
+  chartData.value.I_CHART.points.forEach((item) => {
+    Y1value.value.push(item);
+  });
+  Y1value.value.unshift("");
+  Y1value.value.push("");
+
+  chartsOption1.value.series[0].data = Y1value.value;
+};
+
+const setX1array = async () => {
+  X1array.value = await [];
+  chartData.value.I_CHART.points.forEach((item, index) => {
+    X1array.value.push(index + 1);
+  });
+  X1array.value.unshift("");
+  X1array.value.push("");
+  chartsOption1.value.xAxis[0].data = X1array.value;
+};
+
+const setChart1Info = () => {
+  // chartsOption1.value.title[0].text = `上限=${showData.value.avgMax ? showData.value.avgMax : "-"}`;
+  // chartsOption1.value.title[0].text = `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`;
+  chartsOption1.value.series[0].markLine.data[0].yAxis =
+    chartData.value.I_CHART.UCL;
+  chartsOption1.value.series[0].markLine.data[0].label.formatter = `上限=${
+    chartData.value.I_CHART.UCL
+  }`;
+  chartsOption1.value.series[0].markLine.data[1].yAxis =
+    chartData.value.I_CHART.LCL;
+  chartsOption1.value.series[0].markLine.data[1].label.formatter = `下限=${
+    chartData.value.I_CHART.LCL
+  }`;
+  chartsOption1.value.series[0].markLine.data[2].yAxis =
+    chartData.value.I_CHART.CL;
+  chartsOption1.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    chartData.value.I_CHART.CL
+  }`;
+  // chartsOption1.value.title[2].text = `下限=${showData.value.avgMin ? showData.value.avgMin : "0"}`;
+};
+const setChart2Info = () => {
+  // chartsOption2.value.title[0].text = `上限=${showData.value.rangeMax ? showData.value.rangeMax : "-"}`;
+  // chartsOption2.value.title[0].text = `R=${showData.value.rangeAvg ? showData.value.rangeAvg : "-"}`;
+  chartsOption2.value.series[0].markLine.data[0].yAxis =
+    chartData.value.MR_CHART.UCL;
+  chartsOption2.value.series[0].markLine.data[0].label.formatter = `上限=${
+    chartData.value.MR_CHART.UCL
+  }`;
+  chartsOption2.value.series[0].markLine.data[1].yAxis =
+    chartData.value.MR_CHART.LCL;
+  chartsOption2.value.series[0].markLine.data[1].label.formatter = `下限=${
+    chartData.value.MR_CHART.LCL
+  }`;
+  chartsOption2.value.series[0].markLine.data[2].yAxis =
+    chartData.value.MR_CHART.CL;
+  chartsOption2.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    chartData.value.MR_CHART.CL
+  }`;
+  // chartsOption2.value.title[2].text = `下限=${showData.value.rangeMin ? showData.value.rangeMin : "0"}`;
+};
+const setChart1 = () => {
+  setChart1Info();
+  setY1value();
+  setX1array();
+  charts1.value.setOption(chartsOption1.value, true);
+};
+const setChart2 = () => {
+  setChart2Info();
+  setY2value();
+  setX2array();
+  charts2.value.setOption(chartsOption2.value, true);
+};
+
+const title = ref("调阻精度");
+const showLable = ref("调阻");
+const changeSelect = () => {
+  setTimeout(async () => {
+    // showLable.value = selectRef.value.currentPlaceholder;
+    // opOptions.value.forEach((item) => {
+    //   if (item.dictLabel == showLable.value) {
+    //     lableValue.value = item.dictValue;
+    //   }
+    // });
+    // switch (showLable.value) {
+    //   case "调阻":
+    //     title.value = "调阻精度";
+    //     break;
+    //   case "粘片":
+    //     title.value = "剪切强度";
+    //     break;
+    //   case "键合":
+    //     title.value = "键合强度";
+    //     break;
+    //   default:
+    //     title.value = "调阻精度";
+    //     break;
+    // }
+    // await getTableData();
+
+  }, 0);
+};
+
+const maxHeight = ref(null);
+const maxWidth = ref(null);
+const charts1 = shallowRef(null);
+const charts2 = shallowRef(null);
+const chartsOption1 = ref({
+  title: [
+    // {
+    //   text: `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`,
+    //   right: "5%",
+    //   top: "42%",
+    //   textStyle: {
+    //     fontSize: 15,
+    //     color: "#333",
+    //     fontWeight: 100,
+    //   },
+    // },
+    {
+      text: `I-MR控制图`,
+      left: "40%",
+    },
+    {
+      text: "单",
+      left: "4%",
+      top: "42%",
+    },
+    {
+      text: "值",
+      left: "4%",
+      top: "49%",
+    },
+  ],
+  grid: {
+    right: "15%",
+  },
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  tooltip: {
+    show: true,
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+      scale: true, // 开启自适应缩放
+    },
+  ],
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(chartData.value.I_CHART.UCL) &&
+            paramValue >= Number(chartData.value.I_CHART.LCL)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${chartData.value.I_CHART.UCL}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${chartData.value.I_CHART.LCL}`,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const chartsOption2 = ref({
+  title: [
+    // {
+    //   text: `R=${showData.value.rangeAvg ? showData.value.rangeAvg : "-"}`,
+    //   right: "5%",
+    //   top: "42%",
+    //   textStyle: {
+    //     fontSize: 15,
+    //     color: "#333",
+    //     fontWeight: 100,
+    //   },
+    // },
+    {
+      text: "移",
+      left: "4%",
+      top: "28%",
+    },
+    {
+      text: "动",
+      left: "4%",
+      top: "35%",
+    },
+    {
+      text: "极",
+      left: "4%",
+      top: "42%",
+    },
+    {
+      text: "差",
+      left: "4%",
+      top: "49%",
+    },
+  ],
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  grid: {
+    right: "15%",
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+      scale: true, // 开启自适应缩放
+    },
+  ],
+  tooltip: {
+    show: true,
+  },
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(chartData.value.MR_CHART.UCL) &&
+            paramValue >= Number(chartData.value.MR_CHART.LCL)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${chartData.value.MR_CHART.UCL}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${chartData.value.MR_CHART.LCL}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const Height = ref(0);
+const setHeight = () => {
+  Height.value = document.querySelector(".databox").clientHeight;
+  maxHeight.value = document.querySelector(".info").clientHeight;
+  maxWidth.value = document.querySelector(".info").clientWidth;
+};
+const Y2value = ref([]);
+const X2array = ref([]);
+const setY2value = () => {
+  Y2value.value = [];
+  chartData.value.MR_CHART.points.forEach((item) => {
+    Y2value.value.push(item);
+  });
+  Y2value.value.unshift("");
+  Y2value.value.push("");
+  chartsOption2.value.series[0].data = Y2value.value;
+};
+const setX2array = () => {
+  X2array.value = [];
+  chartData.value.MR_CHART.points.forEach((item, index) => {
+    X2array.value.push(index + 1);
+  });
+  X2array.value.unshift("");
+  X2array.value.push("");
+  chartsOption2.value.xAxis[0].data = X2array.value;
+};
+
+const setView = () => {
+  setHeight();
+  charts1.value = echarts.init(document.getElementById("charts"));
+  charts2.value = echarts.init(document.getElementById("charts1"));
+  charts1.value.setOption(chartsOption1.value, true);
+  charts2.value.setOption(chartsOption2.value, true);
+};
+onMounted(() => {
+  // init();
+});
+const init = async (data) => {
+  tableData.value = data;
+
+  setHeight();
+
+  if (data) {
+    charts1.value = echarts.init(document.getElementById("charts"));
+    charts2.value = echarts.init(document.getElementById("charts1"));
+    window.addEventListener("resize", setView);
+    await getTableData(data);
+    // changeSelect();
+    charts1.value.setOption(chartsOption1.value, true);
+    charts2.value.setOption(chartsOption2.value, true);
+  }
+};
+onBeforeUnmount(() => {
+  window.removeEventListener("resize", setView);
+});
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+@media print {
+  #print {
+    margin-left: -18%;
+  }
+}
+.formStyle {
+  width: 400px;
+  margin: 20px auto;
+}
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

Plik diff jest za duży
+ 1044 - 0
src/views/analysis/process/I-MRList.vue


+ 350 - 0
src/views/analysis/process/NP.vue

@@ -0,0 +1,350 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box" v-show="!addStatus">
+          <div
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+            "
+          >
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              控制图绘制
+            </div>
+
+            <el-button
+              type="primary"
+              v-print="'#print'"
+              style="margin-left: 10px; height: 25px"
+              >打 印</el-button
+            >
+          </div>
+          <div class="info">
+            <div id="print">
+              <div id="charts" :style="{ height: maxHeight / 2 + 'px' }"></div>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from "echarts";
+import { useDictionaryStore } from "@/store";
+import { NPCompute } from "@/api/analysis";
+
+const tableData = ref([]);
+const chartData = ref({});
+const param = ref({
+  dataList: [3, 7, 5],
+  subgroupSize: 150,
+  scale: 4,
+});
+const getTableData = async (data) => {
+  data.cl = data.cl.toFixed(4);
+  data.lcl = data.lcl.toFixed(4);
+  data.ucl = data.ucl.toFixed(4);
+  data.DataPoints = data.DataPoints.map((num) => {
+    return parseFloat(num.y.toFixed(4));
+  });
+  chartData.value = data;
+
+  chartsOption1.value.title[0].text = `NP控制图`;
+  setChart1();
+};
+
+const { dicts } = useDictionaryStore();
+const opOptions = ref([...dicts.spc_operation]);
+const value = ref(opOptions.value[0].remark);
+const Y1value = ref([]);
+const X1array = ref([]);
+const setY1value = () => {
+  Y1value.value = [];
+  chartData.value.DataPoints.forEach((item) => {
+    Y1value.value.push(item);
+  });
+  Y1value.value.unshift("");
+  Y1value.value.push("");
+
+  chartsOption1.value.series[0].data = Y1value.value;
+};
+
+const setX1array = async () => {
+  X1array.value = await [];
+  chartData.value.DataPoints.forEach((item, index) => {
+    X1array.value.push(index + 1);
+  });
+  X1array.value.unshift("");
+  X1array.value.push("");
+  chartsOption1.value.xAxis[0].data = X1array.value;
+};
+
+const setChart1Info = () => {
+  // chartsOption1.value.title[0].text = `上限=${showData.value.avgMax ? showData.value.avgMax : "-"}`;
+  // chartsOption1.value.title[0].text = `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`;
+  chartsOption1.value.series[0].markLine.data[0].yAxis = chartData.value.ucl;
+  chartsOption1.value.series[0].markLine.data[0].label.formatter = `上限=${
+    chartData.value.ucl
+  }`;
+  chartsOption1.value.series[0].markLine.data[1].yAxis = chartData.value.lcl;
+  chartsOption1.value.series[0].markLine.data[1].label.formatter = `下限=${chartData.value.lcl}`;
+  chartsOption1.value.series[0].markLine.data[2].yAxis = chartData.value.cl;
+  chartsOption1.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    chartData.value.cl
+  }`;
+  // chartsOption1.value.title[2].text = `下限=${showData.value.avgMin ? showData.value.avgMin : "0"}`;
+};
+const setChart1 = () => {
+  setChart1Info();
+  setY1value();
+  setX1array();
+  charts1.value.setOption(chartsOption1.value, true);
+};
+
+const title = ref("调阻精度");
+const showLable = ref("调阻");
+const changeSelect = () => {
+  setTimeout(async () => {
+    // showLable.value = selectRef.value.currentPlaceholder;
+    // opOptions.value.forEach((item) => {
+    //   if (item.dictLabel == showLable.value) {
+    //     lableValue.value = item.dictValue;
+    //   }
+    // });
+    // switch (showLable.value) {
+    //   case "调阻":
+    //     title.value = "调阻精度";
+    //     break;
+    //   case "粘片":
+    //     title.value = "剪切强度";
+    //     break;
+    //   case "键合":
+    //     title.value = "键合强度";
+    //     break;
+    //   default:
+    //     title.value = "调阻精度";
+    //     break;
+    // }
+    // await getTableData();
+    // setChart2();
+  }, 0);
+};
+
+const maxHeight = ref(null);
+const maxWidth = ref(null);
+const charts1 = shallowRef(null);
+const chartsOption1 = ref({
+  title: [
+    {
+      text: `NP控制图`,
+      left: "40%",
+    },
+  ],
+  grid: {
+    right: "15%",
+  },
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  tooltip: {
+    show: true,
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+      scale: true, // 开启自适应缩放
+    },
+  ],
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(chartData.value.ucl) &&
+            paramValue >= Number(chartData.value.lcl)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${chartData.value.ucl}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${chartData.value.lcl}`,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const Height = ref(0);
+const setHeight = () => {
+  Height.value = document.querySelector(".databox").clientHeight;
+  maxHeight.value = document.querySelector(".info").clientHeight;
+  maxWidth.value = document.querySelector(".info").clientWidth;
+};
+
+const setView = () => {
+  setHeight();
+  charts1.value = echarts.init(document.getElementById("charts"));
+  charts1.value.setOption(chartsOption1.value, true);
+};
+onMounted(() => {
+  // init();
+});
+const init = async (data) => {
+  tableData.value = data;
+
+  setHeight();
+
+  if (data) {
+    charts1.value = echarts.init(document.getElementById("charts"));
+    window.addEventListener("resize", setView);
+    await getTableData(data);
+    // changeSelect();
+    charts1.value.setOption(chartsOption1.value, true);
+  }
+};
+onBeforeUnmount(() => {
+  window.removeEventListener("resize", setView);
+});
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+@media print {
+  #print {
+    margin-left: -18%;
+  }
+}
+.formStyle {
+  width: 400px;
+  margin: 20px auto;
+}
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

Plik diff jest za duży
+ 1044 - 0
src/views/analysis/process/NPList.vue


+ 328 - 0
src/views/analysis/process/P.vue

@@ -0,0 +1,328 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box" v-show="!addStatus">
+          <div
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+            "
+          >
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              控制图绘制
+            </div>
+
+            <el-button
+              type="primary"
+              v-print="'#print'"
+              style="margin-left: 10px; height: 25px"
+              >打 印</el-button
+            >
+          </div>
+          <div class="info">
+            <div id="print">
+              <div ref="chartRef" style="width: 100%; height: 400px"></div>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, watch } from "vue";
+import * as echarts from "echarts";
+import { PCompute } from "@/api/analysis";
+
+// 假设的数据
+var chartData = ref([]);
+
+const param = {
+  x: {
+    defectCount: 30,
+    sampleSize: 500,
+  },
+  x2: {
+    defectCount: 50,
+    sampleSize: 600,
+  },
+};
+
+const getTableData = async (resultData) => {
+  // 转换函数
+  const transformData = (data) => {
+    const result = [];
+    let sampleId = 1;
+
+    // 遍历原始数据,排除 total 属性
+    for (const key in data) {
+      if (key !== "total") {
+        const item = data[key];
+        result.push({
+          sampleId: sampleId.toString(),
+          value: item.p,
+          ewma: item.cl,
+          ucl: item.ucl,
+          lcl: item.lcl,
+        });
+        sampleId++;
+      }
+    }
+
+    return result;
+  };
+
+  // 转换后的数据
+  const formattedData = ref(transformData(resultData));
+  chartData.value = formattedData.value;
+};
+
+const chartRef = ref(null);
+const chartInstance = ref(null);
+
+const prepareData = () => {
+  let sampleIds = chartData.value.map((item) => item.sampleId);
+  let values = chartData.value.map((item) => item.value);
+  let ewmaValues = chartData.value.map((item) => item.ewma);
+  let uclValues = chartData.value.map((item) => item.ucl);
+  let lclValues = chartData.value.map((item) => item.lcl);
+
+  // 保留四位小数
+  values = values.map((num) => parseFloat(num.toFixed(4)));
+  ewmaValues = ewmaValues.map((num) => parseFloat(num.toFixed(4)));
+  uclValues = uclValues.map((num) => parseFloat(num.toFixed(4)));
+  lclValues = lclValues.map((num) => parseFloat(num.toFixed(4)));
+
+  return {
+    sampleIds,
+    values,
+    ewmaValues,
+    uclValues,
+    lclValues,
+  };
+};
+
+const initChart = (chartDom) => {
+  const myChart = echarts.init(chartDom);
+  const { sampleIds, values, ewmaValues, uclValues, lclValues } = prepareData();
+
+  const option = {
+    title: {
+      text: "P控制图",
+      left: "center",
+    },
+    tooltip: {
+      trigger: "axis",
+    },
+    xAxis: {
+      type: "category",
+      boundaryGap: true, // 不留白,从原点开始
+      data: sampleIds,
+    },
+    yAxis: {
+      type: "value",
+      // name: "EWMA值",
+    },
+    series: [
+      {
+        name: "数值",
+        type: "line",
+        // step: "start",
+        symbol: "circle", //将小圆点改成实心 不写symbol默认空心
+        symbolSize: 8,
+        data: values.map((value, index) => ({
+          value,
+          itemStyle: {
+            color:
+              value > uclValues[index] || value < lclValues[index]
+                ? "red"
+                : "blue",
+          },
+        })),
+      },
+      {
+        name: "均值",
+        type: "line",
+        showSymbol: false,
+        data: ewmaValues,
+        lineStyle: {
+          color: "green",
+        },
+      },
+      // {
+      //   name: "均值",
+      //   type: "line",
+      //   data: Array(sampleIds.length).fill(meanValue),
+      //   showSymbol: false,
+      //   lineStyle: {
+      //     color: "green",
+      //     type: "solid",
+      //   },
+      // },
+      {
+        name: "UCL",
+        type: "line",
+        data: uclValues,
+        showSymbol: false,
+        lineStyle: {
+          color: "red",
+          type: "dashed",
+        },
+      },
+      {
+        name: "LCL",
+        type: "line",
+        data: lclValues,
+        showSymbol: false,
+        lineStyle: {
+          color: "red",
+          type: "dashed",
+        },
+      },
+    ],
+    color: ["blue", "green", "red", "red", "black"],
+  };
+
+  myChart.setOption(option);
+  chartInstance.value = myChart;
+};
+
+const resizeChart = () => {
+  if (chartInstance.value) {
+    chartInstance.value.resize();
+  }
+};
+
+onMounted(() => {
+  window.addEventListener("resize", resizeChart);
+});
+
+// watch(chartData, () => {
+//   if (chartInstance.value) {
+//     const { sampleIds, values, ewmaValues, uclValues, lclValues, meanValue } =
+//       prepareData();
+//     const option = {
+//       title: [
+//         {
+//           text: `P控制图`,
+//           left: "40%",
+//         },
+//       ],
+//       xAxis: {
+//         data: sampleIds,
+//       },
+//       series: [
+//         {
+//           data: values.map((value, index) => ({
+//             value,
+//             itemStyle: {
+//               color:
+//                 value > uclValues[index] || value < lclValues[index]
+//                   ? "red"
+//                   : "blue",
+//             },
+//           })),
+//         },
+//         {
+//           data: ewmaValues,
+//         },
+//         {
+//           data: uclValues,
+//         },
+//         {
+//           data: lclValues,
+//         },
+//         {
+//           data: Array(sampleIds.length).fill(meanValue),
+//         },
+//       ],
+//     };
+//     chartInstance.value.setOption(option);
+//   }
+// });
+
+const init = (data) => {
+  if (data) {
+    nextTick(async () => {
+      await getTableData(data);
+      await initChart(chartRef.value);
+    });
+  }
+};
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+/* 样式可以根据需要进行调整 */
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

Plik diff jest za duży
+ 1044 - 0
src/views/analysis/process/PList.vue


+ 319 - 0
src/views/analysis/process/U.vue

@@ -0,0 +1,319 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box" v-show="!addStatus">
+          <div
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+            "
+          >
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              控制图绘制
+            </div>
+
+            <el-button
+              type="primary"
+              v-print="'#print'"
+              style="margin-left: 10px; height: 25px"
+              >打 印</el-button
+            >
+          </div>
+          <div class="info">
+            <div id="print">
+              <div ref="chartRef" style="width: 100%; height: 400px"></div>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, watch } from "vue";
+import * as echarts from "echarts";
+import { UCompute } from "@/api/analysis";
+
+// 假设的数据
+var chartData = ref([]);
+
+const param = {
+  x: {
+    defectCount: 30,
+    sampleSize: 100,
+  },
+  x2: {
+    defectCount: 50,
+    sampleSize: 200,
+  },
+};
+
+const getTableData = async (resultData) => {
+  // 转换函数
+  const transformData = (data) => {
+    const result = [];
+    let sampleId = 1;
+
+    // 遍历原始数据,排除 total 属性
+    for (const key in data) {
+      if (key !== "total") {
+        const item = data[key];
+        result.push({
+          sampleId: sampleId.toString(),
+          value: item.u,
+          ewma: item.cl,
+          ucl: item.ucl,
+          lcl: item.lcl,
+        });
+        sampleId++;
+      }
+    }
+
+    return result;
+  };
+
+  // 转换后的数据
+  const formattedData = ref(transformData(resultData));
+
+  chartData.value = formattedData.value;
+};
+
+const chartRef = ref(null);
+const chartInstance = ref(null);
+
+const prepareData = () => {
+  let sampleIds = chartData.value.map((item) => item.sampleId);
+  let values = chartData.value.map((item) => item.value);
+  let ewmaValues = chartData.value.map((item) => item.ewma);
+  let uclValues = chartData.value.map((item) => item.ucl);
+  let lclValues = chartData.value.map((item) => item.lcl);
+
+  // 保留四位小数
+  values = values.map((num) => parseFloat(num.toFixed(4)));
+  ewmaValues = ewmaValues.map((num) => parseFloat(num.toFixed(4)));
+  uclValues = uclValues.map((num) => parseFloat(num.toFixed(4)));
+  lclValues = lclValues.map((num) => parseFloat(num.toFixed(4)));
+
+  return {
+    sampleIds,
+    values,
+    ewmaValues,
+    uclValues,
+    lclValues,
+  };
+};
+
+const initChart = (chartDom) => {
+  const myChart = echarts.init(chartDom);
+  const { sampleIds, values, ewmaValues, uclValues, lclValues } = prepareData();
+
+  const option = {
+    title: {
+      text: "U控制图",
+      left: "center",
+    },
+    tooltip: {
+      trigger: "axis",
+    },
+    xAxis: {
+      type: "category",
+      boundaryGap: true, // 不留白,从原点开始
+      data: sampleIds,
+    },
+    yAxis: {
+      type: "value",
+      // name: "EWMA值",
+    },
+    series: [
+      {
+        name: "数值",
+        type: "line",
+        // step: "start",
+        symbol: "circle", //将小圆点改成实心 不写symbol默认空心
+        symbolSize: 8,
+        data: values.map((value, index) => ({
+          value,
+          itemStyle: {
+            color:
+              value > uclValues[index] || value < lclValues[index]
+                ? "red"
+                : "blue",
+          },
+        })),
+      },
+      {
+        name: "均值",
+        type: "line",
+        showSymbol: false,
+        data: ewmaValues,
+        lineStyle: {
+          color: "green",
+        },
+      },
+      {
+        name: "UCL",
+        type: "line",
+        data: uclValues,
+        showSymbol: false,
+        lineStyle: {
+          color: "red",
+          type: "dashed",
+        },
+      },
+      {
+        name: "LCL",
+        type: "line",
+        data: lclValues,
+        showSymbol: false,
+        lineStyle: {
+          color: "red",
+          type: "dashed",
+        },
+      },
+    ],
+    color: ["blue", "green", "red", "red", "black"],
+  };
+
+  myChart.setOption(option);
+  chartInstance.value = myChart;
+};
+
+const resizeChart = () => {
+  if (chartInstance.value) {
+    chartInstance.value.resize();
+  }
+};
+
+onMounted(() => {
+  window.addEventListener("resize", resizeChart);
+});
+
+// watch(chartData, () => {
+//   if (chartInstance.value) {
+//     const { sampleIds, values, ewmaValues, uclValues, lclValues, meanValue } =
+//       prepareData();
+//     const option = {
+//       title: [
+//         {
+//           text: `U控制图`,
+//           left: "40%",
+//         },
+//       ],
+//       xAxis: {
+//         data: sampleIds,
+//       },
+//       series: [
+//         {
+//           data: values.map((value, index) => ({
+//             value,
+//             itemStyle: {
+//               color:
+//                 value > uclValues[index] || value < lclValues[index]
+//                   ? "red"
+//                   : "blue",
+//             },
+//           })),
+//         },
+//         {
+//           data: ewmaValues,
+//         },
+//         {
+//           data: uclValues,
+//         },
+//         {
+//           data: lclValues,
+//         },
+//         {
+//           data: Array(sampleIds.length).fill(meanValue),
+//         },
+//       ],
+//     };
+//     chartInstance.value.setOption(option);
+//   }
+// });
+
+const init = (data) => {
+  if (data) {
+    nextTick(async () => {
+      await getTableData(data);
+      await initChart(chartRef.value);
+    });
+  }
+};
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+/* 样式可以根据需要进行调整 */
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

Plik diff jest za duży
+ 1044 - 0
src/views/analysis/process/UList.vue


+ 546 - 0
src/views/analysis/process/Xbar-R-2.vue

@@ -0,0 +1,546 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box" v-show="!addStatus">
+          <div
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+            "
+          >
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              控制图绘制
+            </div>
+
+            <el-button
+              type="primary"
+              v-print="'#print'"
+              style="margin-left: 10px; height: 25px"
+              >打 印</el-button
+            >
+          </div>
+          <div class="info">
+            <div id="print">
+              <div id="charts" :style="{ height: maxHeight / 2 + 'px' }"></div>
+              <div id="charts1" :style="{ height: maxHeight / 2 + 'px' }"></div>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from "echarts";
+import { useDictionaryStore } from "@/store";
+
+const tableData = ref([]);
+const chartData = ref([]);
+const getTableData = async (data) => {
+  data.lclX = data.lclX.toFixed(4);
+  data.uclR = data.uclR.toFixed(4);
+  data.rBar = data.rBar.toFixed(4);
+  data.xDoubleBar = data.xDoubleBar.toFixed(4);
+  data.uclX = data.uclX.toFixed(4);
+  data.lclR = data.lclR.toFixed(4);
+  data.subgroupRanges = data.subgroupRanges.map((num) =>
+    parseFloat(num.toFixed(4))
+  );
+  data.subgroupMeans = data.subgroupMeans.map((num) =>
+    parseFloat(num.toFixed(4))
+  );
+  chartData.value = data;
+
+  chartsOption1.value.title[0].text = `${title.value}的Xbar-R控制图`;
+  setChart1();
+  setChart2();
+};
+
+const { dicts } = useDictionaryStore();
+const opOptions = ref([...dicts.spc_operation]);
+const value = ref(opOptions.value[0].remark);
+const Y1value = ref([]);
+const X1array = ref([]);
+const setY1value = () => {
+  Y1value.value = [];
+  chartData.value.subgroupMeans.forEach((item) => {
+    Y1value.value.push(item);
+  });
+  Y1value.value.unshift("");
+  Y1value.value.push("");
+
+  chartsOption1.value.series[0].data = Y1value.value;
+};
+
+const setX1array = async () => {
+  X1array.value = await [];
+  chartData.value.subgroupMeans.forEach((item, index) => {
+    X1array.value.push(index + 1);
+  });
+  X1array.value.unshift("");
+  X1array.value.push("");
+  chartsOption1.value.xAxis[0].data = X1array.value;
+};
+
+const setChart1Info = () => {
+  // chartsOption1.value.title[0].text = `上限=${showData.value.avgMax ? showData.value.avgMax : "-"}`;
+  // chartsOption1.value.title[0].text = `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`;
+  chartsOption1.value.series[0].markLine.data[0].yAxis = chartData.value.uclX;
+  chartsOption1.value.series[0].markLine.data[0].label.formatter = `上限=${
+    chartData.value.uclX
+  }`;
+  chartsOption1.value.series[0].markLine.data[1].yAxis = chartData.value.lclX;
+  chartsOption1.value.series[0].markLine.data[1].label.formatter = `下限=${chartData.value.lclX}`;
+  chartsOption1.value.series[0].markLine.data[2].yAxis =
+    chartData.value.xDoubleBar;
+  chartsOption1.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    chartData.value.xDoubleBar
+  }`;
+  // chartsOption1.value.title[2].text = `下限=${showData.value.avgMin ? showData.value.avgMin : "0"}`;
+};
+const setChart2Info = () => {
+  // chartsOption2.value.title[0].text = `上限=${showData.value.rangeMax ? showData.value.rangeMax : "-"}`;
+  // chartsOption2.value.title[0].text = `R=${showData.value.rangeAvg ? showData.value.rangeAvg : "-"}`;
+  chartsOption2.value.series[0].markLine.data[0].yAxis = chartData.value.uclR;
+  chartsOption2.value.series[0].markLine.data[0].label.formatter = `上限=${
+    chartData.value.uclR
+  }`;
+  chartsOption2.value.series[0].markLine.data[1].yAxis = chartData.value.lclR;
+  chartsOption2.value.series[0].markLine.data[1].label.formatter = `下限=${
+    chartData.value.lclR
+  }`;
+  chartsOption2.value.series[0].markLine.data[2].yAxis = chartData.value.rBar;
+  chartsOption2.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    chartData.value.rBar
+  }`;
+  // chartsOption2.value.title[2].text = `下限=${showData.value.rangeMin ? showData.value.rangeMin : "0"}`;
+};
+const setChart1 = () => {
+  setChart1Info();
+  setY1value();
+  setX1array();
+  charts1.value.setOption(chartsOption1.value, true);
+};
+const setChart2 = () => {
+  setChart2Info();
+  setY2value();
+  setX2array();
+  charts2.value.setOption(chartsOption2.value, true);
+};
+
+const title = ref("调阻精度");
+const showLable = ref("调阻");
+const changeSelect = () => {
+  setTimeout(() => {
+    // showLable.value = selectRef.value.currentPlaceholder;
+    // opOptions.value.forEach((item) => {
+    //   if (item.dictLabel == showLable.value) {
+    //     lableValue.value = item.dictValue;
+    //   }
+    // });
+    // switch (showLable.value) {
+    //   case "调阻":
+    //     title.value = "调阻精度";
+    //     break;
+    //   case "粘片":
+    //     title.value = "剪切强度";
+    //     break;
+    //   case "键合":
+    //     title.value = "键合强度";
+    //     break;
+    //   default:
+    //     title.value = "调阻精度";
+    //     break;
+    // }
+    // await getTableData();
+    chartsOption1.value.title[0].text = `${title.value}的Xbar-R控制图`;
+    setChart1();
+    setChart2();
+  }, 0);
+};
+
+const maxHeight = ref(null);
+const maxWidth = ref(null);
+const charts1 = shallowRef(null);
+const charts2 = shallowRef(null);
+const chartsOption1 = ref({
+  title: [
+    // {
+    //   text: `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`,
+    //   right: "5%",
+    //   top: "42%",
+    //   textStyle: {
+    //     fontSize: 15,
+    //     color: "#333",
+    //     fontWeight: 100,
+    //   },
+    // },
+    {
+      text: `${title.value}的Xbar-R控制图`,
+      left: "40%",
+    },
+    {
+      text: "均",
+      left: "4%",
+      top: "42%",
+    },
+    {
+      text: "值",
+      left: "4%",
+      top: "49%",
+    },
+  ],
+  grid: {
+    right: "15%",
+  },
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  tooltip: {
+    show: true,
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+      scale: true, // 开启自适应缩放
+    },
+  ],
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(chartData.value.uclX) &&
+            paramValue >= Number(chartData.value.lclX)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${chartData.value.uclX}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${chartData.value.lclX}`,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const chartsOption2 = ref({
+  title: [
+    // {
+    //   text: `R=${showData.value.rangeAvg ? showData.value.rangeAvg : "-"}`,
+    //   right: "5%",
+    //   top: "42%",
+    //   textStyle: {
+    //     fontSize: 15,
+    //     color: "#333",
+    //     fontWeight: 100,
+    //   },
+    // },
+    {
+      text: "极",
+      left: "4%",
+      top: "35%",
+    },
+    {
+      text: "差",
+      left: "4%",
+      top: "42%",
+    },
+  ],
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  grid: {
+    right: "15%",
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+      scale: true, // 开启自适应缩放
+    },
+  ],
+  tooltip: {
+    show: true,
+  },
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(chartData.value.uclR) &&
+            paramValue >= Number(chartData.value.lclR)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${chartData.value.uclR}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${chartData.value.lclR}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const Height = ref(0);
+const setHeight = () => {
+  Height.value = document.querySelector(".databox").clientHeight;
+  maxHeight.value = document.querySelector(".info").clientHeight;
+  maxWidth.value = document.querySelector(".info").clientWidth;
+};
+const Y2value = ref([]);
+const X2array = ref([]);
+const setY2value = () => {
+  Y2value.value = [];
+  chartData.value.subgroupRanges.forEach((item) => {
+    Y2value.value.push(item);
+  });
+  Y2value.value.unshift("");
+  Y2value.value.push("");
+  chartsOption2.value.series[0].data = Y2value.value;
+};
+const setX2array = () => {
+  X2array.value = [];
+  chartData.value.subgroupRanges.forEach((item, index) => {
+    X2array.value.push(index + 1);
+  });
+  X2array.value.unshift("");
+  X2array.value.push("");
+  chartsOption2.value.xAxis[0].data = X2array.value;
+};
+
+const setView = () => {
+  setHeight();
+  charts1.value = echarts.init(document.getElementById("charts"));
+  charts2.value = echarts.init(document.getElementById("charts1"));
+  charts1.value.setOption(chartsOption1.value, true);
+  charts2.value.setOption(chartsOption2.value, true);
+};
+onMounted(() => {
+  init();
+});
+const init = async (data) => {
+  tableData.value = data;
+
+  setHeight();
+
+  if (data) {
+    charts1.value = echarts.init(document.getElementById("charts"));
+    charts2.value = echarts.init(document.getElementById("charts1"));
+    window.addEventListener("resize", setView);
+    await getTableData(data);
+    // changeSelect();
+    charts1.value.setOption(chartsOption1.value, true);
+    charts2.value.setOption(chartsOption2.value, true);
+  }
+
+  // nextTick(() => {
+  //   if (tableData.value != undefined && tableData.value.length > 0) {
+  //     getTableData();
+  //     // changeSelect();
+  //   }
+  //
+  //   charts1.value = echarts.init(document.getElementById("charts"));
+  //   charts2.value = echarts.init(document.getElementById("charts1"));
+  //   charts1.value.setOption(chartsOption1.value, true);
+  //   charts2.value.setOption(chartsOption2.value, true);
+  // });
+  // window.addEventListener("resize", setView);
+};
+onBeforeUnmount(() => {
+  window.removeEventListener("resize", setView);
+});
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+@media print {
+  #print {
+    margin-left: -18%;
+  }
+}
+.formStyle {
+  width: 400px;
+  margin: 20px auto;
+}
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

+ 529 - 0
src/views/analysis/process/Xbar-R.vue

@@ -0,0 +1,529 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box" v-show="!addStatus">
+          <div
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+            "
+          >
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              控制图绘制
+            </div>
+
+            <el-button
+              type="primary"
+              v-print="'#print'"
+              style="margin-left: 10px; height: 25px"
+              >打 印</el-button
+            >
+          </div>
+          <div class="info">
+            <div id="print">
+              <div id="charts" :style="{ height: maxHeight / 2 + 'px' }"></div>
+              <div id="charts1" :style="{ height: maxHeight / 2 + 'px' }"></div>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from "echarts";
+import { useDictionaryStore } from "@/store";
+
+const props = defineProps({
+  process: {
+    type: String,
+    required: true,
+  },
+});
+const { dicts } = useDictionaryStore();
+const opOptions = ref([...dicts.spc_operation]);
+const value = ref(opOptions.value[0].remark);
+const Y1value = ref([]);
+const X1array = ref([]);
+const setY1value = () => {
+  Y1value.value = [];
+  tableData.value.forEach((item) => {
+    Y1value.value.push(item.avg);
+  });
+  Y1value.value.unshift("");
+  Y1value.value.push("");
+
+  chartsOption1.value.series[0].data = Y1value.value;
+};
+const tableData = ref([]);
+const setX1array = async () => {
+  X1array.value = await [];
+  tableData.value.forEach((item, index) => {
+    X1array.value.push(index + 1);
+  });
+  X1array.value.unshift("");
+  X1array.value.push("");
+  chartsOption1.value.xAxis[0].data = X1array.value;
+};
+
+const setChart1Info = () => {
+  // chartsOption1.value.title[0].text = `上限=${showData.value.avgMax ? showData.value.avgMax : "-"}`;
+  // chartsOption1.value.title[0].text = `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`;
+  chartsOption1.value.series[0].markLine.data[0].yAxis = JSON.parse(
+    value.value
+  ).avgMax;
+  chartsOption1.value.series[0].markLine.data[0].label.formatter = `上限=${
+    JSON.parse(value.value).avgMax ? JSON.parse(value.value).avgMax : "0"
+  }`;
+  chartsOption1.value.series[0].markLine.data[1].yAxis = JSON.parse(value.value)
+    .avgMin
+    ? JSON.parse(value.value).avgMin
+    : 0;
+  chartsOption1.value.series[0].markLine.data[1].label.formatter = `下限=${JSON.parse(value.value).avgMin ? JSON.parse(value.value).avgMin : "0"}`;
+  chartsOption1.value.series[0].markLine.data[2].yAxis = JSON.parse(
+    value.value
+  ).avgMid;
+  chartsOption1.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    JSON.parse(value.value).avgMid
+  }`;
+  // chartsOption1.value.title[2].text = `下限=${showData.value.avgMin ? showData.value.avgMin : "0"}`;
+};
+const setChart2Info = () => {
+  // chartsOption2.value.title[0].text = `上限=${showData.value.rangeMax ? showData.value.rangeMax : "-"}`;
+  // chartsOption2.value.title[0].text = `R=${showData.value.rangeAvg ? showData.value.rangeAvg : "-"}`;
+  chartsOption2.value.series[0].markLine.data[0].yAxis = JSON.parse(
+    value.value
+  ).rangeMax;
+  chartsOption2.value.series[0].markLine.data[0].label.formatter = `上限=${
+    JSON.parse(value.value).rangeMax ? JSON.parse(value.value).rangeMax : "0"
+  }`;
+  chartsOption2.value.series[0].markLine.data[1].yAxis = JSON.parse(value.value)
+    .rangeMin
+    ? JSON.parse(value.value).rangeMin
+    : 0;
+  chartsOption2.value.series[0].markLine.data[1].label.formatter = `下限=${
+    JSON.parse(value.value).rangeMin ? JSON.parse(value.value).rangeMin : "0"
+  }`;
+  chartsOption2.value.series[0].markLine.data[2].yAxis = JSON.parse(
+    value.value
+  ).rangeMid;
+  chartsOption2.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    JSON.parse(value.value).rangeMid
+  }`;
+  // chartsOption2.value.title[2].text = `下限=${showData.value.rangeMin ? showData.value.rangeMin : "0"}`;
+};
+const setChart1 = () => {
+  setChart1Info();
+  setY1value();
+  setX1array();
+  charts1.value.setOption(chartsOption1.value, true);
+};
+const setChart2 = () => {
+  setChart2Info();
+  setY2value();
+  setX2array();
+  charts2.value.setOption(chartsOption2.value, true);
+};
+
+const title = ref("调阻精度");
+const showLable = ref("调阻");
+const changeSelect = () => {
+  setTimeout(async () => {
+    // showLable.value = selectRef.value.currentPlaceholder;
+    // opOptions.value.forEach((item) => {
+    //   if (item.dictLabel == showLable.value) {
+    //     lableValue.value = item.dictValue;
+    //   }
+    // });
+    switch (process) {
+      case "调阻":
+        title.value = "调阻精度";
+        break;
+      case "粘片":
+        title.value = "剪切强度";
+        break;
+      case "键合":
+        title.value = "键合强度";
+        break;
+      default:
+        title.value = "调阻精度";
+        break;
+    }
+    // await getTableData();
+    chartsOption1.value.title[0].text = `${title.value}的Xbar-R控制图`;
+    setChart1();
+    setChart2();
+  }, 0);
+};
+
+const maxHeight = ref(null);
+const maxWidth = ref(null);
+const charts1 = shallowRef(null);
+const charts2 = shallowRef(null);
+const chartsOption1 = ref({
+  title: [
+    // {
+    //   text: `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`,
+    //   right: "5%",
+    //   top: "42%",
+    //   textStyle: {
+    //     fontSize: 15,
+    //     color: "#333",
+    //     fontWeight: 100,
+    //   },
+    // },
+    {
+      text: `${title.value}的Xbar-R控制图`,
+      left: "40%",
+    },
+    {
+      text: "均",
+      left: "4%",
+      top: "42%",
+    },
+    {
+      text: "值",
+      left: "4%",
+      top: "49%",
+    },
+  ],
+  grid: {
+    right: "15%",
+  },
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  tooltip: {
+    show: true,
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+    },
+  ],
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const avg = JSON.parse(value.value);
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(avg.avgMax) &&
+            paramValue >= Number(avg.avgMin)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${JSON.parse(value.value).avgMax ? JSON.parse(value.value).avgMax : "-"}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${JSON.parse(value.value).avgMin ? JSON.parse(value.value).avgMin : "-"}`,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const chartsOption2 = ref({
+  title: [
+    // {
+    //   text: `R=${showData.value.rangeAvg ? showData.value.rangeAvg : "-"}`,
+    //   right: "5%",
+    //   top: "42%",
+    //   textStyle: {
+    //     fontSize: 15,
+    //     color: "#333",
+    //     fontWeight: 100,
+    //   },
+    // },
+    {
+      text: "极",
+      left: "4%",
+      top: "42%",
+    },
+    {
+      text: "值",
+      left: "4%",
+      top: "49%",
+    },
+  ],
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  grid: {
+    right: "15%",
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+    },
+  ],
+  tooltip: {
+    show: true,
+  },
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const range = JSON.parse(value.value);
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(range.rangeMax) &&
+            paramValue >= Number(range.rangeMin)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${JSON.parse(value.value).rangeMax ? JSON.parse(value.value).rangeMax : "-"}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${JSON.parse(value.value).rangeMin ? JSON.parse(value.value).rangeMin : "-"}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const Height = ref(0);
+const setHeight = () => {
+  Height.value = document.querySelector(".databox").clientHeight;
+  maxHeight.value = document.querySelector(".info").clientHeight;
+  maxWidth.value = document.querySelector(".info").clientWidth;
+};
+const Y2value = ref([]);
+const X2array = ref([]);
+const setY2value = () => {
+  Y2value.value = [];
+  tableData.value.forEach((item) => {
+    Y2value.value.push(item.range);
+  });
+  Y2value.value.unshift("");
+  Y2value.value.push("");
+  chartsOption2.value.series[0].data = Y2value.value;
+};
+const setX2array = () => {
+  X2array.value = [];
+  tableData.value.forEach((item, index) => {
+    X2array.value.push(index + 1);
+  });
+  X2array.value.unshift("");
+  X2array.value.push("");
+  chartsOption2.value.xAxis[0].data = X2array.value;
+};
+
+const setView = () => {
+  setHeight();
+  charts1.value = echarts.init(document.getElementById("charts"));
+  charts2.value = echarts.init(document.getElementById("charts1"));
+  charts1.value.setOption(chartsOption1.value, true);
+  charts2.value.setOption(chartsOption2.value, true);
+};
+onMounted(() => {
+  init();
+});
+const init = (data) => {
+
+  tableData.value = data;
+  changeSelect();
+  setHeight();
+  nextTick(() => {
+    charts1.value = echarts.init(document.getElementById("charts"));
+    charts2.value = echarts.init(document.getElementById("charts1"));
+    charts1.value.setOption(chartsOption1.value, true);
+    charts2.value.setOption(chartsOption2.value, true);
+  });
+  window.addEventListener("resize", setView);
+};
+
+onBeforeUnmount(() => {
+  window.removeEventListener("resize", setView);
+});
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+@media print {
+  #print {
+    margin-left: -18%;
+  }
+}
+.formStyle {
+  width: 400px;
+  margin: 20px auto;
+}
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

+ 816 - 0
src/views/analysis/process/Xbar-RList.vue

@@ -0,0 +1,816 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box">
+          <div class="title">
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              样本数据录入
+            </div>
+            <div class="header" v-show="!addStatus && !editStatus">
+              <Search
+                :searchOptions="searchForm"
+                ref="searchRef"
+                @data-list="getTableData"
+                @reset-list="reset"
+              />
+            </div>
+            <div class="btns">
+              <el-upload
+                v-if="!addStatus && !editStatus"
+                style="float: left"
+                :action="uploadUrl"
+                :on-success="handleSuccess"
+                :before-upload="beforeUpload"
+                :limit="1"
+                accept=".xlsx, .xls"
+                :show-file-list="false"
+                ref="uploadRef"
+              >
+                <el-button size="small" type="primary">Excel导入</el-button>
+              </el-upload>
+              <el-button
+                v-if="!addStatus && !editStatus"
+                style="margin-left: 15px"
+                type="primary"
+                size="small"
+                class="btn"
+                @click="
+                  exportData(
+                    '/api/v1/spc/downloadTemplate',
+                    '/spc/template/XBarR控制图数据导入模版.xlsx'
+                  )
+                "
+                >模版下载</el-button
+              >
+              <el-button
+                style="margin-left: 15px"
+                v-if="!addStatus && !editStatus"
+                type="primary"
+                size="small"
+                class="btn"
+                @click="changeaddstatus"
+                >新增</el-button
+              >
+              <el-button
+                style="margin-left: 15px"
+                v-if="!addStatus && !editStatus"
+                type="primary"
+                size="small"
+                class="btn"
+                @click="compute"
+                >计算</el-button
+              >
+
+              <el-button
+                v-if="editStatus || addStatus"
+                type="success"
+                size="small"
+                class="btn"
+                @click="submit"
+                >确定</el-button
+              >
+              <el-button
+                v-if="editStatus || addStatus"
+                type="info"
+                size="small"
+                class="btn"
+                @click="canceleOp"
+                >取消</el-button
+              >
+            </div>
+          </div>
+          <div class="info" v-if="!addStatus">
+            <el-table
+              :data="tableData"
+              border
+              :style="{
+                height: maxHeight - 150 + 'px',
+                width: maxWidth + 'px',
+              }"
+              :show-overflow-tooltip="true"
+              :row-class-name="tableRowClassName"
+            >
+              <el-table-column
+                align="center"
+                prop="dateStr"
+                sortable
+                label="日期"
+              >
+                <template #default="{ row }"
+                  ><span>{{ row.dateStr }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column align="center" prop="model" label="产品型号">
+                <template #default="{ row }"
+                  ><span>{{ row.model }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column align="center" prop="batchNo" label="生产批号">
+                <template #default="{ row }"
+                  ><span>{{ row.batchNo }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column align="center" prop="source" label="数据来源">
+                <template #default="{ row }"
+                  ><span>{{ row.source }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column
+                align="center"
+                prop="accuracys"
+                label="数据"
+                :show-overflow-tooltip="false"
+              >
+                <template #default="{ row }">
+                  <el-tooltip placement="top">
+                    <template #content>
+                      <div v-for="(item, index) in row.accuracys" :key="index">
+                        <span>数值{{ index + 1 }}: {{ item }}</span>
+                      </div>
+                    </template>
+                    <div class="ellipsis-text">
+                      {{ row.accuracys.join(", ") }}
+                    </div>
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+              <!--              <el-table-column
+                align="center"
+                prop="avg"
+                label="平均值"
+                width="60"
+              /><el-table-column
+                align="center"
+                prop="range"
+                label="极差"
+                width="60"
+              />--><!--<el-table-column
+                align="center"
+                prop="checkUser"
+                label="检测人"
+              >
+                <template #default="{ row }"
+                  ><span>{{ row.checkUser }}</span>
+                </template> </el-table-column
+              ><el-table-column
+                align="center"
+                prop="checkDeviceNo"
+                label="检查设备编号"
+              >
+                <template #default="{ row }"
+                  ><span>{{ row.checkDeviceNo }}</span>
+                </template>
+              </el-table-column>-->
+              <el-table-column align="center" prop="abnormal" label="是否异常">
+                <template #default="{ row }"
+                  ><span>{{ row.abnormal }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column
+                align="center"
+                prop="abnormal1"
+                label="异常原因"
+                :show-overflow-tooltip="false"
+              >
+                <template #default="{ row }">
+                  <el-tooltip placement="top">
+                    <template #content>
+                      <div
+                        v-for="(item, index) in row.abnormal1.split(',')"
+                        :key="index"
+                      >
+                        <span>{{ item }}</span>
+                      </div>
+                    </template>
+                    <div class="ellipsis-text">
+                      {{ row.abnormal1 }}
+                    </div>
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+              <!--              <el-table-column align="center" prop="analyseUser" label="分析人">
+                <template #default="{ row }"
+                  ><span>{{ row.analyseUser }}</span>
+                </template>
+              </el-table-column>-->
+              <el-table-column align="center" prop="measure" label="处置措施">
+                <template #default="{ row }"
+                  ><span>{{ row.measure }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column align="center" width="240" prop="" label="操作">
+                <template #default="{ row }">
+                  <el-button
+                    v-if="row.source === '自动'"
+                    type="primary"
+                    size="small"
+                    class="btn"
+                    @click="dataAcquisition(row)"
+                    style="height: 25px"
+                    >采集数据</el-button
+                  >
+                  <el-button
+                    type="primary"
+                    size="small"
+                    class="btn"
+                    @click="updataItem(row)"
+                    style="height: 25px"
+                    >修改</el-button
+                  >
+                  <el-button
+                    type="info"
+                    size="small"
+                    class="btn"
+                    style="height: 25px"
+                    @click="deleteSubmit(row.id)"
+                    >删除</el-button
+                  >
+                </template>
+              </el-table-column>
+            </el-table>
+            <Pagination
+              :total="currentOption.total"
+              :page="currentOption.page"
+              :limit="currentOption.limit"
+              :pageSizes="currentOption.pageSizes"
+              v-model:page="currentOption.page"
+              v-model:limit="currentOption.limit"
+              @pagination="getTableData"
+            />
+          </div>
+          <div class="info" v-else>
+            <el-form
+              ref="ruleFormRef"
+              :model="addData"
+              :rules="rules"
+              label-width="auto"
+              class="formStyle"
+            >
+              <el-form-item label="日期" prop="dateStr">
+                <el-date-picker
+                  v-model="addData.dateStr"
+                  type="date"
+                  aria-label="Pick a date"
+                  placeholder="Pick a date"
+                  style="width: 100%"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                />
+              </el-form-item>
+              <el-form-item label="任务编号" prop="qualityTaskId">
+                <el-select
+                  v-model="addData.qualityTaskId"
+                  @change="
+                    (value) => {
+                      taskChange(value);
+                    }
+                  "
+                >
+                  <el-option
+                    v-for="(item, index) in taskOption"
+                    :key="index"
+                    :label="item.taskCode"
+                    :value="Number(item.id)"
+                  />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="产品型号" prop="model">
+                <el-input :disabled="true" v-model="addData.model" />
+              </el-form-item>
+              <el-form-item label="数据来源" prop="source">
+                <el-input :disabled="true" v-model="addData.source" />
+              </el-form-item>
+              <el-form-item label="生产批号" prop="batchNo">
+                <el-input v-model="addData.batchNo" />
+              </el-form-item>
+              <el-form-item
+                v-for="(item, index) in addData.accuracys"
+                :label="'数值' + (index + 1)"
+                :key="index"
+                :rules="[
+                  {
+                    required: true,
+                    trigger: 'change',
+                  },
+                ]"
+              >
+                <el-input-number
+                  :precision="2"
+                  :step="0.01"
+                  style="width: 100%"
+                  v-model="addData.accuracys[index]"
+                />
+              </el-form-item>
+              <!-- <el-form-item label="平均值" prop="avg">
+                <el-input v-model="addData.avg" />
+              </el-form-item>
+              <el-form-item label="极差" prop="range">
+                <el-input v-model="addData.range" />
+              </el-form-item> -->
+              <!--              <el-form-item label="检测人" prop="checkUser">
+                <el-input v-model="addData.checkUser" />
+              </el-form-item>
+              <el-form-item label="检查设备编号" prop="checkDeviceNo">
+                <el-input v-model="addData.checkDeviceNo" />
+              </el-form-item>-->
+              <!-- <el-form-item label="是否异常" prop="abnormal">
+                <el-input v-model="addData.abnormal" />
+              </el-form-item> -->
+              <!--              <el-form-item label="分析人" prop="analyseUser">
+                <el-input v-model="addData.analyseUser" />
+              </el-form-item>-->
+              <el-form-item label="处置措施" prop="measure">
+                <el-input v-model="addData.measure" />
+              </el-form-item>
+            </el-form>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import * as echarts from "echarts";
+import { useDictionaryStore } from "@/store";
+import {
+  getData,
+  addDatas,
+  deleteData,
+  updateData,
+  getTaskCode,
+} from "@/api/analysis";
+import Search from "@/components/Search/index.vue";
+import { XBarRCompute, collectData } from "@/api/analysis";
+import { useCrud } from "@/hooks/userCrud";
+
+const { Utils } = useCrud({
+  src: "/api/v1/spc/pDownloadTemplate",
+});
+const { exportData } = Utils;
+const emit = defineEmits(["tableData"]);
+
+const dataAcquisition = async (row) => {
+  const { data } = await collectData({
+    qualitySpcRecordId: row.id,
+    model: row.model,
+    batchNo: row.batchNo,
+    operation: lableValue.value,
+  });
+  emit("tableData", data);
+  getTableData();
+};
+
+const compute = async () => {
+  const accuracysList = ref([]);
+  for (const item of tableData.value) {
+    if (item.accuracys?.length > 0) {
+      accuracysList.value.push(item.accuracys);
+    } else {
+      ElMessage.error(`产品 "${item.model}" 的 "${item.batchNo}" 批次数据为空`);
+      return;
+    }
+  }
+  const { data } = await XBarRCompute({
+    dataList: accuracysList.value,
+    scale: 4,
+  });
+  emit("tableData", data);
+};
+
+const tableRowClassName = ({ row }) => {
+  if (row.abnormal === "是") {
+    return "warning-row";
+  }
+  return "";
+};
+
+const { dicts } = useDictionaryStore();
+
+const year = ref("0");
+const currentOption = reactive({
+  total: 0,
+  page: 1,
+  limit: 10,
+  pageSizes: [10, 20, 30, 40, 50],
+});
+const lableValue = ref("");
+const searchRef = ref(null);
+const getTaskOption = async () => {
+  const { data } = await getTaskCode({
+    operationCode: lableValue.value,
+  });
+  taskOption.value = data;
+};
+const getTableData = async () => {
+  const { data, code, msg } = await getData({
+    ...searchRef.value.searchForm,
+    pageNo: currentOption.page,
+    pageSize: currentOption.limit,
+    // yearStr: year.value,
+    operation: lableValue.value,
+  });
+  if (code == "200") {
+    tableData.value = data.records;
+    showData.value = { ...data, list: null };
+    currentOption.total = data.totalCount;
+    currentOption.page = data.pageNo;
+    oldDataJSON.value = JSON.stringify(data.records);
+  }
+  disabled.value = false;
+};
+const searchForm = [
+  {
+    label: "日期",
+    prop: "createds",
+    type: "daterange",
+  },
+  {
+    label: "产品型号",
+    prop: "model",
+    type: "input",
+  },
+  {
+    label: "生产批号",
+    prop: "batchNo",
+    type: "input",
+  },
+];
+//编辑状态
+const editStatus = ref(false);
+const addStatus = ref(false);
+const changeEditstatus = () => {
+  editStatus.value = !changeEditstatus.value;
+  addStatus.value = false;
+};
+const changeaddstatus = () => {
+  addStatus.value = !addStatus.value;
+  editStatus.value = false;
+};
+const canceleOp = () => {
+  addStatus.value = false;
+  editStatus.value = false;
+  reset();
+};
+
+const disabled = ref(false);
+const tableData = ref([]);
+//Form
+const ruleFormRef = ref(null);
+const rules = {
+  qualityTaskId: [
+    {
+      required: true,
+      trigger: "change",
+    },
+  ],
+  dateStr: [
+    {
+      required: true,
+      trigger: "change",
+    },
+  ],
+  model: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  batchNo: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  accuracy1: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  accuracy2: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  accuracy3: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  accuracy4: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  accuracy5: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  checkUser: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  checkDeviceNo: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  // abnormal: [
+  //   {
+  //     required: true,
+
+  //     trigger: "change",
+  //   },
+  // ],
+  analyseUser: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  measure: [
+    {
+      required: false,
+
+      trigger: "change",
+    },
+  ],
+};
+const resItem = {
+  // abnormal: "",
+  analyseUser: "",
+  batchNo: "",
+  checkDeviceNo: "",
+  checkUser: "",
+  dateStr: "",
+  measure: "",
+  model: "",
+};
+const addData = ref({
+  // abnormal: "",
+  analyseUser: "",
+  batchNo: "",
+  checkDeviceNo: "",
+  checkUser: "",
+  dateStr: "",
+  measure: "",
+  model: "",
+});
+const accuracysSum = ref(0);
+const taskChange = (value) => {
+  taskOption.value.forEach((item) => {
+    if (item.id == value) {
+      addData.value.model = item.prodtModel;
+      addData.value.source = item.source;
+      if (item.source === "手动") {
+        accuracysSum.value = Number(item.processCount);
+        addData.value.accuracys = [];
+        let array = [];
+        for (let i = 0; i < accuracysSum.value; i++) {
+          array.push(0);
+        }
+        addData.value.accuracys = array;
+      } else {
+        addData.value.accuracys = [];
+      }
+    }
+  });
+};
+const oldDataJSON = ref("");
+const showData = ref({});
+const opOptions = ref([...dicts.spc_operation]);
+
+//修改
+const updataItem = (row) => {
+  editStatus.value = true;
+  addData.value = row;
+  addStatus.value = true;
+};
+const taskOption = ref([]);
+const value = ref(opOptions.value[0].remark);
+const showLable = ref("调阻");
+const maxHeight = ref(null);
+const maxWidth = ref(null);
+const Height = ref(0);
+const setHeight = () => {
+  Height.value = document.querySelector(".databox").clientHeight;
+  maxHeight.value = document.querySelector(".info").clientHeight;
+  maxWidth.value = document.querySelector(".info").clientWidth;
+};
+
+//当新增或者编辑的确定操作
+const submit = () => {
+  if (editStatus.value == true) {
+    updateSubmit();
+  } else {
+    addSubmit();
+  }
+};
+const addSubmit = async () => {
+  await ruleFormRef.value.validate(async (valid, fields) => {
+    if (valid) {
+      const { data, code } = await addDatas({
+        ...addData.value,
+        yearStr: year.value,
+        operation: lableValue.value,
+      });
+      if (code == "200") {
+        ElMessage.success("添加成功!");
+        reset();
+        getTableData();
+      }
+    } else {
+      ElMessage.error("请检查表单信息");
+    }
+  });
+};
+const deleteSubmit = async (id) => {
+  const { data, code } = await deleteData({
+    id,
+  });
+  if (code == "200") {
+    ElMessage.success("删除成功!");
+    getTableData();
+  }
+};
+const updateSubmit = async () => {
+  const { data, code } = await updateData({
+    ...addData.value,
+  });
+  if (code == "200") {
+    ElMessage.success("更新成功!");
+    reset();
+    getTableData();
+  }
+};
+const reset = () => {
+  addStatus.value = false;
+  editStatus.value = false;
+  addData.value = { ...resItem };
+  searchRef.value.searchForm = {};
+  currentOption.value = {
+    total: 0,
+    page: 0,
+    limit: 12,
+    pageSizes: [12],
+    operation: value.value,
+  };
+  getTableData();
+};
+
+const uploadUrl = ref("");
+const beforeUpload = (file) => {
+  const isExcel =
+    file.type === "application/vnd.ms-excel" ||
+    file.type ===
+      "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+  if (!isExcel) {
+    ElMessage.error("只能上传Excel文件!");
+  }
+  return isExcel;
+};
+const uploadRef = ref("uploadRef");
+const handleSuccess = (response) => {
+  if (response.code === "200") {
+    ElMessage.success("Excel导入成功!");
+    uploadRef.value.clearFiles();
+    getTableData();
+  } else {
+    ElMessage.error("Excel导入失败!");
+  }
+};
+
+onMounted(async () => {
+  setHeight();
+  year.value = new Date().getFullYear() + "";
+  opOptions.value.forEach((item) => {
+    if (item.dictLabel == showLable.value) {
+      lableValue.value = item.dictValue;
+    }
+  });
+
+  await getTableData();
+  uploadUrl.value =
+    import.meta.env.VITE_APP_BASE_API +
+    "/api/v1/spc/xBarRUpload?operation=" +
+    lableValue.value;
+});
+onBeforeUnmount(() => {});
+
+const init = (data) => {
+  lableValue.value = data;
+  getTaskOption();
+  getTableData();
+};
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+@media print {
+  #print {
+    margin-left: -18%;
+  }
+}
+:deep(.el-table .warning-row) {
+  background-color: rgb(241, 142, 142) !important;
+}
+.ellipsis-text {
+  white-space: nowrap; /* 禁止换行 */
+  overflow: hidden; /* 隐藏超出部分 */
+  text-overflow: ellipsis; /* 显示省略号 */
+  width: 100%; /* 宽度占满单元格 */
+}
+.formStyle {
+  width: 400px;
+  margin: 20px auto;
+}
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

+ 552 - 0
src/views/analysis/process/Xbar-S-2.vue

@@ -0,0 +1,552 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box" v-show="!addStatus">
+          <div
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+            "
+          >
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              控制图绘制
+            </div>
+
+            <el-button
+              type="primary"
+              v-print="'#print'"
+              style="margin-left: 10px; height: 25px"
+              >打 印</el-button
+            >
+          </div>
+          <div class="info">
+            <div id="print">
+              <div id="charts" :style="{ height: maxHeight / 2 + 'px' }"></div>
+              <div id="charts1" :style="{ height: maxHeight / 2 + 'px' }"></div>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from "echarts";
+import { useDictionaryStore } from "@/store";
+import { XBarSCompute } from "@/api/analysis";
+
+const tableData = ref([]);
+const chartData = ref([]);
+const getTableData = async (data) => {
+  data.lclX = data.lclX.toFixed(4);
+  data.uclS = data.uclS.toFixed(4);
+  data.sBar = data.sBar.toFixed(4);
+  data.xDoubleBar = data.xDoubleBar.toFixed(4);
+  data.uclX = data.uclX.toFixed(4);
+  data.lclS = data.lclS.toFixed(4);
+  data.subgroupStdDevs = data.subgroupStdDevs.map((num) =>
+    parseFloat(num.toFixed(4))
+  );
+  data.subgroupMeans = data.subgroupMeans.map((num) =>
+    parseFloat(num.toFixed(4))
+  );
+  chartData.value = data;
+
+  chartsOption1.value.title[0].text = `${title.value}的Xbar-S控制图`;
+  setChart1();
+  setChart2();
+};
+
+const { dicts } = useDictionaryStore();
+const opOptions = ref([...dicts.spc_operation]);
+const value = ref(opOptions.value[0].remark);
+const Y1value = ref([]);
+const X1array = ref([]);
+const setY1value = () => {
+  Y1value.value = [];
+  chartData.value.subgroupMeans.forEach((item) => {
+    Y1value.value.push(item);
+  });
+  Y1value.value.unshift("");
+  Y1value.value.push("");
+
+  chartsOption1.value.series[0].data = Y1value.value;
+};
+
+const setX1array = async () => {
+  X1array.value = await [];
+  chartData.value.subgroupMeans.forEach((item, index) => {
+    X1array.value.push(index + 1);
+  });
+  X1array.value.unshift("");
+  X1array.value.push("");
+  chartsOption1.value.xAxis[0].data = X1array.value;
+};
+
+const setChart1Info = () => {
+  // chartsOption1.value.title[0].text = `上限=${showData.value.avgMax ? showData.value.avgMax : "-"}`;
+  // chartsOption1.value.title[0].text = `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`;
+  chartsOption1.value.series[0].markLine.data[0].yAxis = chartData.value.uclX;
+  chartsOption1.value.series[0].markLine.data[0].label.formatter = `上限=${
+    chartData.value.uclX
+  }`;
+  chartsOption1.value.series[0].markLine.data[1].yAxis = chartData.value.lclX;
+  chartsOption1.value.series[0].markLine.data[1].label.formatter = `下限=${chartData.value.lclX}`;
+  chartsOption1.value.series[0].markLine.data[2].yAxis =
+    chartData.value.xDoubleBar;
+  chartsOption1.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    chartData.value.xDoubleBar
+  }`;
+  // chartsOption1.value.title[2].text = `下限=${showData.value.avgMin ? showData.value.avgMin : "0"}`;
+};
+const setChart2Info = () => {
+  // chartsOption2.value.title[0].text = `上限=${showData.value.rangeMax ? showData.value.rangeMax : "-"}`;
+  // chartsOption2.value.title[0].text = `R=${showData.value.rangeAvg ? showData.value.rangeAvg : "-"}`;
+  chartsOption2.value.series[0].markLine.data[0].yAxis = chartData.value.uclS;
+  chartsOption2.value.series[0].markLine.data[0].label.formatter = `上限=${
+    chartData.value.uclS
+  }`;
+  chartsOption2.value.series[0].markLine.data[1].yAxis = chartData.value.lclS;
+  chartsOption2.value.series[0].markLine.data[1].label.formatter = `下限=${
+    chartData.value.lclS
+  }`;
+  chartsOption2.value.series[0].markLine.data[2].yAxis = chartData.value.sBar;
+  chartsOption2.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    chartData.value.sBar
+  }`;
+  // chartsOption2.value.title[2].text = `下限=${showData.value.rangeMin ? showData.value.rangeMin : "0"}`;
+};
+const setChart1 = () => {
+  setChart1Info();
+  setY1value();
+  setX1array();
+  charts1.value.setOption(chartsOption1.value, true);
+};
+const setChart2 = () => {
+  setChart2Info();
+  setY2value();
+  setX2array();
+  charts2.value.setOption(chartsOption2.value, true);
+};
+
+const title = ref("调阻精度");
+const showLable = ref("调阻");
+const changeSelect = () => {
+  setTimeout(async () => {
+    // showLable.value = selectRef.value.currentPlaceholder;
+    // opOptions.value.forEach((item) => {
+    //   if (item.dictLabel == showLable.value) {
+    //     lableValue.value = item.dictValue;
+    //   }
+    // });
+    // switch (showLable.value) {
+    //   case "调阻":
+    //     title.value = "调阻精度";
+    //     break;
+    //   case "粘片":
+    //     title.value = "剪切强度";
+    //     break;
+    //   case "键合":
+    //     title.value = "键合强度";
+    //     break;
+    //   default:
+    //     title.value = "调阻精度";
+    //     break;
+    // }
+    // await getTableData();
+    chartsOption1.value.title[0].text = `${title.value}的Xbar-S控制图`;
+    setChart1();
+    setChart2();
+  }, 0);
+};
+
+const maxHeight = ref(null);
+const maxWidth = ref(null);
+const charts1 = shallowRef(null);
+const charts2 = shallowRef(null);
+const chartsOption1 = ref({
+  title: [
+    // {
+    //   text: `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`,
+    //   right: "5%",
+    //   top: "42%",
+    //   textStyle: {
+    //     fontSize: 15,
+    //     color: "#333",
+    //     fontWeight: 100,
+    //   },
+    // },
+    {
+      text: `${title.value}的Xbar-S控制图`,
+      left: "40%",
+    },
+    {
+      text: "均",
+      left: "4%",
+      top: "42%",
+    },
+    {
+      text: "值",
+      left: "4%",
+      top: "49%",
+    },
+  ],
+  grid: {
+    right: "15%",
+  },
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  tooltip: {
+    show: true,
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+      scale: true, // 开启自适应缩放
+    },
+  ],
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(chartData.value.uclX) &&
+            paramValue >= Number(chartData.value.lclX)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${chartData.value.uclX}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${chartData.value.lclX}`,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const chartsOption2 = ref({
+  title: [
+    // {
+    //   text: `R=${showData.value.rangeAvg ? showData.value.rangeAvg : "-"}`,
+    //   right: "5%",
+    //   top: "42%",
+    //   textStyle: {
+    //     fontSize: 15,
+    //     color: "#333",
+    //     fontWeight: 100,
+    //   },
+    // },
+    {
+      text: "标",
+      left: "4%",
+      top: "35%",
+    },
+    {
+      text: "准",
+      left: "4%",
+      top: "42%",
+    },
+    {
+      text: "差",
+      left: "4%",
+      top: "49%",
+    },
+  ],
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  grid: {
+    right: "15%",
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+      scale: true, // 开启自适应缩放
+    },
+  ],
+  tooltip: {
+    show: true,
+  },
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(chartData.value.uclS) &&
+            paramValue >= Number(chartData.value.lclS)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${chartData.value.uclS}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${chartData.value.lclS}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const Height = ref(0);
+const setHeight = () => {
+  Height.value = document.querySelector(".databox").clientHeight;
+  maxHeight.value = document.querySelector(".info").clientHeight;
+  maxWidth.value = document.querySelector(".info").clientWidth;
+};
+const Y2value = ref([]);
+const X2array = ref([]);
+const setY2value = () => {
+  Y2value.value = [];
+  chartData.value.subgroupStdDevs.forEach((item) => {
+    Y2value.value.push(item);
+  });
+  Y2value.value.unshift("");
+  Y2value.value.push("");
+  chartsOption2.value.series[0].data = Y2value.value;
+};
+const setX2array = () => {
+  X2array.value = [];
+  chartData.value.subgroupStdDevs.forEach((item, index) => {
+    X2array.value.push(index + 1);
+  });
+  X2array.value.unshift("");
+  X2array.value.push("");
+  chartsOption2.value.xAxis[0].data = X2array.value;
+};
+
+const setView = () => {
+  setHeight();
+  charts1.value = echarts.init(document.getElementById("charts"));
+  charts2.value = echarts.init(document.getElementById("charts1"));
+  charts1.value.setOption(chartsOption1.value, true);
+  charts2.value.setOption(chartsOption2.value, true);
+};
+onMounted(() => {
+  init();
+});
+const init = async (data) => {
+  tableData.value = data;
+
+  setHeight();
+
+  if (data) {
+    charts1.value = echarts.init(document.getElementById("charts"));
+    charts2.value = echarts.init(document.getElementById("charts1"));
+    window.addEventListener("resize", setView);
+    await getTableData(data);
+    // changeSelect();
+    charts1.value.setOption(chartsOption1.value, true);
+    charts2.value.setOption(chartsOption2.value, true);
+  }
+
+  // nextTick(() => {
+  //   if (tableData.value != undefined && tableData.value.length > 0) {
+  //     getTableData();
+  //     // changeSelect();
+  //   }
+  //
+  //   charts1.value = echarts.init(document.getElementById("charts"));
+  //   charts2.value = echarts.init(document.getElementById("charts1"));
+  //   charts1.value.setOption(chartsOption1.value, true);
+  //   charts2.value.setOption(chartsOption2.value, true);
+  // });
+  // window.addEventListener("resize", setView);
+};
+onBeforeUnmount(() => {
+  window.removeEventListener("resize", setView);
+});
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+@media print {
+  #print {
+    margin-left: -18%;
+  }
+}
+.formStyle {
+  width: 400px;
+  margin: 20px auto;
+}
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

+ 546 - 0
src/views/analysis/process/Xbar-S.vue

@@ -0,0 +1,546 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box" v-show="!addStatus">
+          <div
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+            "
+          >
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              控制图绘制
+            </div>
+
+            <el-button
+              type="primary"
+              v-print="'#print'"
+              style="margin-left: 10px; height: 25px"
+              >打 印</el-button
+            >
+          </div>
+          <div class="info">
+            <div id="print">
+              <div id="charts" :style="{ height: maxHeight / 2 + 'px' }"></div>
+              <div id="charts1" :style="{ height: maxHeight / 2 + 'px' }"></div>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from "echarts";
+import { useDictionaryStore } from "@/store";
+
+const { dicts } = useDictionaryStore();
+const opOptions = ref([...dicts.spc_operation]);
+const value = ref(opOptions.value[0].remark);
+const Y1value = ref([]);
+const X1array = ref([]);
+const setY1value = () => {
+  Y1value.value = [];
+  tableData.value.forEach((item) => {
+    Y1value.value.push(item.avg);
+  });
+  Y1value.value.unshift("");
+  Y1value.value.push("");
+
+  chartsOption1.value.series[0].data = Y1value.value;
+};
+const tableData = ref([]);
+const setX1array = async () => {
+  X1array.value = await [];
+  tableData.value.forEach((item, index) => {
+    X1array.value.push(index + 1);
+  });
+  X1array.value.unshift("");
+  X1array.value.push("");
+  chartsOption1.value.xAxis[0].data = X1array.value;
+};
+
+const setChart1Info = () => {
+  // chartsOption1.value.title[0].text = `上限=${showData.value.avgMax ? showData.value.avgMax : "-"}`;
+  // chartsOption1.value.title[0].text = `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`;
+  chartsOption1.value.series[0].markLine.data[0].yAxis = JSON.parse(
+    value.value
+  ).avgMax;
+  chartsOption1.value.series[0].markLine.data[0].label.formatter = `                          上限=${
+    JSON.parse(value.value).avgMax ? JSON.parse(value.value).avgMax : "0"
+  }`;
+  chartsOption1.value.series[0].markLine.data[1].yAxis = JSON.parse(value.value)
+    .avgMin
+    ? JSON.parse(value.value).avgMin
+    : 0;
+  chartsOption1.value.series[0].markLine.data[1].label.formatter = `           下限=${JSON.parse(value.value).avgMin ? JSON.parse(value.value).avgMin : "0"}`;
+  chartsOption1.value.series[0].markLine.data[2].yAxis = JSON.parse(
+    value.value
+  ).avgMid;
+  chartsOption1.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    JSON.parse(value.value).avgMid
+  }`;
+  // chartsOption1.value.title[2].text = `下限=${showData.value.avgMin ? showData.value.avgMin : "0"}`;
+};
+const setChart2Info = () => {
+  // chartsOption2.value.title[0].text = `上限=${showData.value.rangeMax ? showData.value.rangeMax : "-"}`;
+  // chartsOption2.value.title[0].text = `R=${showData.value.rangeAvg ? showData.value.rangeAvg : "-"}`;
+  chartsOption2.value.series[0].markLine.data[0].yAxis = JSON.parse(
+    value.value
+  ).rangeMax;
+  chartsOption2.value.series[0].markLine.data[0].label.formatter = `                          上限=${
+    JSON.parse(value.value).rangeMax ? JSON.parse(value.value).rangeMax : "0"
+  }`;
+  chartsOption2.value.series[0].markLine.data[1].yAxis = JSON.parse(value.value)
+    .rangeMin
+    ? JSON.parse(value.value).rangeMin
+    : 0;
+  chartsOption2.value.series[0].markLine.data[1].label.formatter = `           下限=${
+    JSON.parse(value.value).rangeMin ? JSON.parse(value.value).rangeMin : "0"
+  }`;
+  chartsOption2.value.series[0].markLine.data[2].yAxis = JSON.parse(
+    value.value
+  ).rangeMid;
+  chartsOption2.value.series[0].markLine.data[2].label.formatter = `x̄=${
+    JSON.parse(value.value).rangeMid
+  }`;
+  // chartsOption2.value.title[2].text = `下限=${showData.value.rangeMin ? showData.value.rangeMin : "0"}`;
+};
+const setChart1 = () => {
+  setChart1Info();
+  setY1value();
+  setX1array();
+  charts1.value.setOption(chartsOption1.value, true);
+};
+const setChart2 = () => {
+  setChart2Info();
+  setY2value();
+  setX2array();
+  charts2.value.setOption(chartsOption2.value, true);
+};
+
+const title = ref("调阻精度");
+const showLable = ref("调阻");
+const changeSelect = () => {
+  setTimeout(async () => {
+    // showLable.value = selectRef.value.currentPlaceholder;
+    // opOptions.value.forEach((item) => {
+    //   if (item.dictLabel == showLable.value) {
+    //     lableValue.value = item.dictValue;
+    //   }
+    // });
+    // switch (showLable.value) {
+    //   case "调阻":
+    //     title.value = "调阻精度";
+    //     break;
+    //   case "粘片":
+    //     title.value = "剪切强度";
+    //     break;
+    //   case "键合":
+    //     title.value = "键合强度";
+    //     break;
+    //   default:
+    //     title.value = "调阻精度";
+    //     break;
+    // }
+    // await getTableData();
+    chartsOption1.value.title[0].text = `${title.value}的Xbar-S控制图`;
+    setChart1();
+    setChart2();
+  }, 0);
+};
+
+const maxHeight = ref(null);
+const maxWidth = ref(null);
+const charts1 = shallowRef(null);
+const charts2 = shallowRef(null);
+const chartsOption1 = ref({
+  title: [
+    // {
+    //   text: `x̄=${showData.value.avgAvg ? showData.value.avgAvg : "-"}`,
+    //   right: "5%",
+    //   top: "42%",
+    //   textStyle: {
+    //     fontSize: 15,
+    //     color: "#333",
+    //     fontWeight: 100,
+    //   },
+    // },
+    {
+      text: `${title.value}的Xbar-S控制图`,
+      left: "40%",
+    },
+    {
+      text: "样",
+      left: "4%",
+      top: "28%",
+    },
+    {
+      text: "本",
+      left: "4%",
+      top: "35%",
+    },
+    {
+      text: "均",
+      left: "4%",
+      top: "42%",
+    },
+    {
+      text: "值",
+      left: "4%",
+      top: "49%",
+    },
+  ],
+  grid: {
+    right: "15%",
+  },
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  tooltip: {
+    show: true,
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+    },
+  ],
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const avg = JSON.parse(value.value);
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(avg.avgMax) &&
+            paramValue >= Number(avg.avgMin)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${JSON.parse(value.value).avgMax ? JSON.parse(value.value).avgMax : "-"}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${JSON.parse(value.value).avgMin ? JSON.parse(value.value).avgMin : "-"}`,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const chartsOption2 = ref({
+  title: [
+    // {
+    //   text: `R=${showData.value.rangeAvg ? showData.value.rangeAvg : "-"}`,
+    //   right: "5%",
+    //   top: "42%",
+    //   textStyle: {
+    //     fontSize: 15,
+    //     color: "#333",
+    //     fontWeight: 100,
+    //   },
+    // },
+    {
+      text: "样",
+      left: "4%",
+      top: "28%",
+    },
+    {
+      text: "本",
+      left: "4%",
+      top: "35%",
+    },
+    {
+      text: "标",
+      left: "4%",
+      top: "42%",
+    },
+    {
+      text: "准",
+      left: "4%",
+      top: "49%",
+    },
+    {
+      text: "差",
+      left: "4%",
+      top: "56%",
+    },
+  ],
+  toolbox: {
+    feature: {
+      saveAsImage: {},
+    },
+  },
+  grid: {
+    right: "15%",
+  },
+  xAxis: [
+    {
+      type: "category",
+      boundaryGap: false,
+      data: [],
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+    },
+  ],
+  tooltip: {
+    show: true,
+  },
+  series: [
+    {
+      type: "line",
+      lineStyle: {
+        color: "rgb(26, 122, 240)",
+      },
+      symbolSize: 13,
+      symbol: "circle",
+      itemStyle: {
+        color: (params) => {
+          const range = JSON.parse(value.value);
+          const paramValue = Number(params.value);
+          if (
+            paramValue <= Number(range.rangeMax) &&
+            paramValue >= Number(range.rangeMin)
+          ) {
+            return "rgb(26, 122, 240)";
+          } else {
+            return "red";
+          }
+        },
+      },
+      markLine: {
+        silent: true,
+        data: [
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `上限=${JSON.parse(value.value).rangeMax ? JSON.parse(value.value).rangeMax : "-"}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            silent: false,
+            yAxis: 0,
+            label: {
+              position: "end",
+              formatter: `下限=${JSON.parse(value.value).rangeMin ? JSON.parse(value.value).rangeMin : "-"}`,
+              color: "#333",
+            },
+            lineStyle: { type: "solid", color: "#333", width: 2 },
+          },
+          {
+            yAxis: 0,
+            silent: false,
+            label: {
+              position: "end",
+              formatter: ``,
+              color: "#333",
+            },
+            lineStyle: {
+              type: "solid",
+              color: "#333",
+              width: 2,
+            },
+          },
+        ],
+      },
+    },
+  ],
+});
+const Height = ref(0);
+const setHeight = () => {
+  Height.value = document.querySelector(".databox").clientHeight;
+  maxHeight.value = document.querySelector(".info").clientHeight;
+  maxWidth.value = document.querySelector(".info").clientWidth;
+};
+const Y2value = ref([]);
+const X2array = ref([]);
+const setY2value = () => {
+  Y2value.value = [];
+  tableData.value.forEach((item) => {
+    Y2value.value.push(item.range);
+  });
+  Y2value.value.unshift("");
+  Y2value.value.push("");
+  chartsOption2.value.series[0].data = Y2value.value;
+};
+const setX2array = () => {
+  X2array.value = [];
+  tableData.value.forEach((item, index) => {
+    X2array.value.push(index + 1);
+  });
+  X2array.value.unshift("");
+  X2array.value.push("");
+  chartsOption2.value.xAxis[0].data = X2array.value;
+};
+
+const setView = () => {
+  setHeight();
+  charts1.value = echarts.init(document.getElementById("charts"));
+  charts2.value = echarts.init(document.getElementById("charts1"));
+  charts1.value.setOption(chartsOption1.value, true);
+  charts2.value.setOption(chartsOption2.value, true);
+};
+onMounted(() => {
+  init();
+});
+const init = (data, str) => {
+  tableData.value = data;
+  changeSelect();
+  setHeight();
+  nextTick(() => {
+    charts1.value = echarts.init(document.getElementById("charts"));
+    charts2.value = echarts.init(document.getElementById("charts1"));
+    charts1.value.setOption(chartsOption1.value, true);
+    charts2.value.setOption(chartsOption2.value, true);
+  });
+  window.addEventListener("resize", setView);
+};
+onBeforeUnmount(() => {
+  window.removeEventListener("resize", setView);
+});
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+@media print {
+  #print {
+    margin-left: -18%;
+  }
+}
+.formStyle {
+  width: 400px;
+  margin: 20px auto;
+}
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

+ 780 - 0
src/views/analysis/process/Xbar-SList.vue

@@ -0,0 +1,780 @@
+<template>
+  <div class="container1">
+    <div class="databox">
+      <el-scrollbar :style="{ height: Height + 'px' }">
+        <div class="box">
+          <div class="title">
+            <div style="display: flex; align-items: center">
+              <div class="bg"></div>
+              样本数据录入
+            </div>
+            <div class="header" v-show="!addStatus && !editStatus">
+              <Search
+                :searchOptions="searchForm"
+                ref="searchRef"
+                @data-list="getTableData"
+                @reset-list="reset"
+              />
+            </div>
+            <div class="btns">
+              <el-upload
+                v-if="!addStatus && !editStatus"
+                style="float: left"
+                :action="uploadUrl"
+                :on-success="handleSuccess"
+                :before-upload="beforeUpload"
+                :limit="1"
+                accept=".xlsx, .xls"
+                :show-file-list="false"
+                ref="uploadRef"
+              >
+                <el-button size="small" type="primary">Excel导入</el-button>
+              </el-upload>
+              <el-button
+                v-if="!addStatus && !editStatus"
+                style="margin-left: 15px"
+                type="primary"
+                size="small"
+                class="btn"
+                @click="
+                  exportData(
+                    '/api/v1/spc/downloadTemplate',
+                    '/spc/template/XBarS控制图数据导入模版.xlsx'
+                  )
+                "
+                >模版下载</el-button
+              >
+              <el-button
+                style="margin-left: 15px"
+                v-if="!addStatus && !editStatus"
+                type="primary"
+                size="small"
+                class="btn"
+                @click="changeaddstatus"
+                >新增</el-button
+              >
+              <el-button
+                style="margin-left: 15px"
+                v-if="!addStatus && !editStatus"
+                type="primary"
+                size="small"
+                class="btn"
+                @click="compute"
+                >计算</el-button
+              >
+              <el-button
+                v-if="editStatus || addStatus"
+                type="success"
+                size="small"
+                class="btn"
+                @click="submit"
+                >确定</el-button
+              >
+              <el-button
+                v-if="editStatus || addStatus"
+                type="info"
+                size="small"
+                class="btn"
+                @click="canceleOp"
+                >取消</el-button
+              >
+            </div>
+          </div>
+          <div class="info" v-if="!addStatus">
+            <el-table
+              :data="tableData"
+              border
+              :style="{
+                height: maxHeight - 150 + 'px',
+                width: maxWidth + 'px',
+              }"
+              :show-overflow-tooltip="true"
+              :row-class-name="tableRowClassName"
+            >
+              <el-table-column
+                align="center"
+                prop="dateStr"
+                sortable
+                label="日期"
+              >
+                <template #default="{ row }"
+                  ><span>{{ row.dateStr }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column align="center" prop="model" label="产品型号">
+                <template #default="{ row }"
+                  ><span>{{ row.model }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column align="center" prop="batchNo" label="生产批号">
+                <template #default="{ row }"
+                  ><span>{{ row.batchNo }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column
+                align="center"
+                prop="accuracys"
+                label="数据"
+                :show-overflow-tooltip="false"
+              >
+                <template #default="{ row }">
+                  <el-tooltip placement="top">
+                    <template #content>
+                      <div v-for="(item, index) in row.accuracys" :key="index">
+                        <span>数值{{ index + 1 }}: {{ item }}</span>
+                      </div>
+                    </template>
+                    <div class="ellipsis-text">
+                      {{ row.accuracys.join(", ") }}
+                    </div>
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+              <!--              <el-table-column
+                align="center"
+                prop="avg"
+                label="平均值"
+                width="60"
+              /><el-table-column
+                align="center"
+                prop="range"
+                label="极差"
+                width="60"
+              />--><!--<el-table-column
+                align="center"
+                prop="checkUser"
+                label="检测人"
+              >
+                <template #default="{ row }"
+                  ><span>{{ row.checkUser }}</span>
+                </template> </el-table-column
+              ><el-table-column
+                align="center"
+                prop="checkDeviceNo"
+                label="检查设备编号"
+              >
+                <template #default="{ row }"
+                  ><span>{{ row.checkDeviceNo }}</span>
+                </template>
+              </el-table-column>-->
+              <el-table-column align="center" prop="abnormal" label="是否异常">
+                <template #default="{ row }"
+                  ><span>{{ row.abnormal }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column
+                align="center"
+                prop="abnormal1"
+                label="异常原因"
+                :show-overflow-tooltip="false"
+              >
+                <template #default="{ row }">
+                  <el-tooltip placement="top">
+                    <template #content>
+                      <div
+                        v-for="(item, index) in row.abnormal1.split(',')"
+                        :key="index"
+                      >
+                        <span>{{ item }}</span>
+                      </div>
+                    </template>
+                    <div class="ellipsis-text">
+                      {{ row.abnormal1 }}
+                    </div>
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+              <!--              <el-table-column align="center" prop="analyseUser" label="分析人">
+                <template #default="{ row }"
+                  ><span>{{ row.analyseUser }}</span>
+                </template>
+              </el-table-column>-->
+              <el-table-column align="center" prop="measure" label="处置措施">
+                <template #default="{ row }"
+                  ><span>{{ row.measure }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column align="center" width="160" prop="" label="操作">
+                <template #default="{ row }">
+                  <el-button
+                    type="primary"
+                    size="small"
+                    class="btn"
+                    @click="updataItem(row)"
+                    style="height: 25px"
+                    >修改</el-button
+                  >
+                  <el-button
+                    type="info"
+                    size="small"
+                    class="btn"
+                    style="height: 25px"
+                    @click="deleteSubmit(row.id)"
+                    >删除</el-button
+                  >
+                </template>
+              </el-table-column>
+            </el-table>
+            <Pagination
+              :total="currentOption.total"
+              :page="currentOption.page"
+              :limit="currentOption.limit"
+              :pageSizes="currentOption.pageSizes"
+              v-model:page="currentOption.page"
+              v-model:limit="currentOption.limit"
+              @pagination="getTableData"
+            />
+          </div>
+          <div class="info" v-else>
+            <el-form
+              ref="ruleFormRef"
+              :model="addData"
+              :rules="rules"
+              label-width="auto"
+              class="formStyle"
+            >
+              <el-form-item label="日期" prop="dateStr">
+                <el-date-picker
+                  v-model="addData.dateStr"
+                  type="date"
+                  aria-label="Pick a date"
+                  placeholder="Pick a date"
+                  style="width: 100%"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                />
+              </el-form-item>
+              <el-form-item label="任务编号" prop="qualityTaskId">
+                <el-select
+                  v-model="addData.qualityTaskId"
+                  @change="
+                    (value) => {
+                      taskChange(value);
+                    }
+                  "
+                >
+                  <el-option
+                    v-for="(item, index) in taskOption"
+                    :key="index"
+                    :label="item.taskCode"
+                    :value="item.id"
+                  />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="产品型号" prop="model">
+                <el-input :disabled="true" v-model="addData.model" />
+              </el-form-item>
+              <el-form-item label="生产批号" prop="batchNo">
+                <el-input v-model="addData.batchNo" />
+              </el-form-item>
+              <el-form-item
+                v-for="(item, index) in addData.accuracys"
+                :label="'数值' + (index + 1)"
+                :key="index"
+                :rules="[
+                  {
+                    required: true,
+                    trigger: 'change',
+                  },
+                ]"
+              >
+                <el-input-number
+                  :precision="2"
+                  :step="0.01"
+                  style="width: 100%"
+                  v-model="addData.accuracys[index]"
+                />
+              </el-form-item>
+              <!-- <el-form-item label="平均值" prop="avg">
+                <el-input v-model="addData.avg" />
+              </el-form-item>
+              <el-form-item label="极差" prop="range">
+                <el-input v-model="addData.range" />
+              </el-form-item> -->
+              <!--              <el-form-item label="检测人" prop="checkUser">
+                <el-input v-model="addData.checkUser" />
+              </el-form-item>
+              <el-form-item label="检查设备编号" prop="checkDeviceNo">
+                <el-input v-model="addData.checkDeviceNo" />
+              </el-form-item>-->
+              <!-- <el-form-item label="是否异常" prop="abnormal">
+                <el-input v-model="addData.abnormal" />
+              </el-form-item> -->
+              <!--              <el-form-item label="分析人" prop="analyseUser">-->
+              <!--                <el-input v-model="addData.analyseUser" />-->
+              <!--              </el-form-item>-->
+              <el-form-item label="处置措施" prop="measure">
+                <el-input v-model="addData.measure" />
+              </el-form-item>
+            </el-form>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useDictionaryStore } from "@/store";
+import {
+  getData,
+  addDatas,
+  deleteData,
+  updateData,
+  getTaskCode,
+} from "@/api/analysis";
+import Search from "@/components/Search/index.vue";
+import { XBarSCompute } from "@/api/analysis";
+import { useCrud } from "@/hooks/userCrud";
+
+const { Utils } = useCrud({
+  src: "/api/v1/spc/pDownloadTemplate",
+});
+const { exportData } = Utils;
+
+const emit = defineEmits(["tableData"]);
+const compute = async () => {
+  // 存储提取的 accuracys 数据
+  const accuracysList = ref([]);
+
+  // 提取 accuracys 数据
+  tableData.value.forEach((v) => {
+    accuracysList.value.push(v.accuracys);
+  });
+
+  const { data } = await XBarSCompute({
+    dataList: accuracysList.value,
+    scale: 4,
+  });
+  emit("tableData", data);
+};
+
+const tableRowClassName = ({ row }) => {
+  if (row.abnormal === "是") {
+    return "warning-row";
+  }
+  return "";
+};
+
+const { dicts } = useDictionaryStore();
+
+const year = ref("0");
+const currentOption = reactive({
+  total: 0,
+  page: 1,
+  limit: 10,
+  pageSizes: [10, 20, 30, 40, 50],
+});
+const lableValue = ref("");
+const searchRef = ref(null);
+const getTaskOption = async () => {
+  const { data } = await getTaskCode({
+    operationCode: lableValue.value,
+  });
+  taskOption.value = data;
+};
+const getTableData = async () => {
+  const { data, code, msg } = await getData({
+    ...searchRef.value.searchForm,
+    pageNo: currentOption.page,
+    pageSize: currentOption.limit,
+    // yearStr: year.value,
+    operation: lableValue.value,
+  });
+  if (code == "200") {
+    tableData.value = data.records;
+    showData.value = { ...data, list: null };
+    currentOption.total = data.totalCount;
+    currentOption.page = data.pageNo;
+    oldDataJSON.value = JSON.stringify(data.records);
+  }
+  disabled.value = false;
+};
+const searchForm = [
+  {
+    label: "日期",
+    prop: "createds",
+    type: "daterange",
+  },
+  {
+    label: "产品型号",
+    prop: "model",
+    type: "input",
+  },
+  {
+    label: "生产批号",
+    prop: "batchNo",
+    type: "input",
+  },
+];
+//编辑状态
+const editStatus = ref(false);
+const addStatus = ref(false);
+const changeEditstatus = () => {
+  editStatus.value = !changeEditstatus.value;
+  addStatus.value = false;
+};
+const changeaddstatus = () => {
+  addStatus.value = !addStatus.value;
+  editStatus.value = false;
+};
+const canceleOp = () => {
+  addStatus.value = false;
+  editStatus.value = false;
+  reset();
+};
+const disabled = ref(false);
+const tableData = ref([]);
+//Form
+const ruleFormRef = ref(null);
+
+const rules = {
+  qualityTaskId: [
+    {
+      required: true,
+      trigger: "change",
+    },
+  ],
+  dateStr: [
+    {
+      required: true,
+      trigger: "change",
+    },
+  ],
+  model: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  batchNo: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  accuracy1: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  accuracy2: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  accuracy3: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  accuracy4: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  accuracy5: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  checkUser: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  checkDeviceNo: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  // abnormal: [
+  //   {
+  //     required: true,
+
+  //     trigger: "change",
+  //   },
+  // ],
+  analyseUser: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  measure: [
+    {
+      required: false,
+
+      trigger: "change",
+    },
+  ],
+};
+const resItem = {
+  // abnormal: "",
+  analyseUser: "",
+  batchNo: "",
+  checkDeviceNo: "",
+  checkUser: "",
+  dateStr: "",
+  measure: "",
+  model: "",
+};
+const addData = ref({
+  // abnormal: "",
+  analyseUser: "",
+  batchNo: "",
+  checkDeviceNo: "",
+  checkUser: "",
+  dateStr: "",
+  measure: "",
+  model: "",
+});
+const accuracysSum = ref(0);
+const taskChange = (value) => {
+  taskOption.value.forEach((item) => {
+    if (item.id == value) {
+      addData.value.model = item.prodtModel;
+      accuracysSum.value = Number(item.processCount);
+      addData.value.accuracys = [];
+      let array = [];
+      for (let i = 0; i < accuracysSum.value; i++) {
+        array.push(0);
+      }
+      addData.value.accuracys = array;
+    }
+  });
+};
+const oldDataJSON = ref("");
+const showData = ref({});
+const opOptions = ref([...dicts.spc_operation]);
+
+//修改
+const updataItem = (row) => {
+  editStatus.value = true;
+  addData.value = row;
+  addStatus.value = true;
+};
+const taskOption = ref([]);
+const value = ref(opOptions.value[0].remark);
+const showLable = ref("调阻");
+const maxHeight = ref(null);
+const maxWidth = ref(null);
+const Height = ref(0);
+const setHeight = () => {
+  Height.value = document.querySelector(".databox").clientHeight;
+  maxHeight.value = document.querySelector(".info").clientHeight;
+  maxWidth.value = document.querySelector(".info").clientWidth;
+};
+
+//当新增或者编辑的确定操作
+const submit = () => {
+  if (editStatus.value == true) {
+    updateSubmit();
+  } else {
+    addSubmit();
+  }
+};
+const addSubmit = async () => {
+  await ruleFormRef.value.validate(async (valid, fields) => {
+    if (valid) {
+      const { data, code } = await addDatas({
+        ...addData.value,
+        yearStr: year.value,
+        operation: lableValue.value,
+      });
+      if (code == "200") {
+        ElMessage.success("添加成功!");
+        reset();
+        getTableData();
+      }
+    } else {
+      ElMessage.error("请检查表单信息");
+    }
+  });
+};
+const deleteSubmit = async (id) => {
+  const { data, code } = await deleteData({
+    id,
+  });
+  if (code == "200") {
+    ElMessage.success("删除成功!");
+    getTableData();
+  }
+};
+const updateSubmit = async () => {
+  const { data, code } = await updateData({
+    ...addData.value,
+  });
+  if (code == "200") {
+    ElMessage.success("更新成功!");
+    reset();
+    getTableData();
+  }
+};
+const reset = () => {
+  addStatus.value = false;
+  editStatus.value = false;
+  addData.value = { ...resItem };
+  searchRef.value.searchForm = {};
+  currentOption.value = {
+    total: 0,
+    page: 0,
+    limit: 12,
+    pageSizes: [12],
+    operation: value.value,
+  };
+  getTableData();
+};
+
+const uploadUrl = ref("");
+const beforeUpload = (file) => {
+  const isExcel =
+    file.type === "application/vnd.ms-excel" ||
+    file.type ===
+      "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+  if (!isExcel) {
+    ElMessage.error("只能上传Excel文件!");
+  }
+  return isExcel;
+};
+const uploadRef = ref("uploadRef");
+const handleSuccess = (response) => {
+  if (response.code === "200") {
+    ElMessage.success("Excel导入成功!");
+    uploadRef.value.clearFiles();
+    getTableData();
+  } else {
+    ElMessage.error("Excel导入失败!");
+  }
+};
+
+onMounted(async () => {
+  setHeight();
+  year.value = new Date().getFullYear() + "";
+  opOptions.value.forEach((item) => {
+    if (item.dictLabel == showLable.value) {
+      lableValue.value = item.dictValue;
+    }
+  });
+
+  await getTableData();
+  uploadUrl.value =
+    import.meta.env.VITE_APP_BASE_API +
+    "/api/v1/spc/xBarRUpload?operation=" +
+    lableValue.value;
+});
+onBeforeUnmount(() => {});
+
+const init = (data) => {
+  lableValue.value = data;
+  getTaskOption();
+  getTableData();
+};
+
+// 暴露 init 方法
+defineExpose({
+  init,
+});
+</script>
+
+<style lang="scss" scoped>
+@media print {
+  #print {
+    margin-left: -18%;
+  }
+}
+:deep(.el-table .warning-row) {
+  background-color: rgb(241, 142, 142) !important;
+}
+.ellipsis-text {
+  white-space: nowrap; /* 禁止换行 */
+  overflow: hidden; /* 隐藏超出部分 */
+  text-overflow: ellipsis; /* 显示省略号 */
+  width: 100%; /* 宽度占满单元格 */
+}
+.formStyle {
+  width: 400px;
+  margin: 20px auto;
+}
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 200px;
+    .header {
+      height: 120px;
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    flex: 1;
+    border-left: 2px solid #00000010;
+    .box {
+      height: 710px;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .illustrate {
+        padding: 20px 60px;
+      }
+      .tableTitle {
+        text-align: center;
+        margin: 10px 0;
+        padding-right: 40px;
+      }
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
+        display: flex;
+        width: 100%;
+        height: auto;
+      }
+      //.title {
+      //  height: 50px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
+      .info {
+        margin-top: 20px;
+        flex: 1;
+        height: 300px;
+      }
+    }
+  }
+}
+</style>

Plik diff jest za duży
+ 1304 - 0
src/views/analysis/process/index-1.vue


Plik diff jest za duży
+ 342 - 1017
src/views/analysis/process/index.vue


+ 125 - 43
src/views/analysis/spc/index.vue

@@ -1,7 +1,12 @@
 <template>
   <div class="container1">
     <div class="header" v-show="!addStatus && !editStatus">
-      <Search :searchOptions="searchForm" ref="searchRef" />
+      <Search
+        :searchOptions="searchForm"
+        ref="searchRef"
+        @data-list="getData"
+        @reset-list="resetData"
+      />
     </div>
     <div class="table" v-if="!addStatus && !editStatus">
       <el-button
@@ -15,14 +20,14 @@
       <el-table
         :data="tableData"
         border
-        :style="{ height: maxHeight - 80 + 'px' }"
+
       >
         <el-table-column prop="taskCode" label="任务编号" />
         <el-table-column prop="prodtModel" label="产品型号" />
         <el-table-column prop="operationName" label="工序名称" />
         <el-table-column prop="source" label="采集数据源" />
-        <el-table-column prop="param" label="控制参数" />
-        <el-table-column prop="chart" label="控制图" />
+        <!--        <el-table-column prop="param" label="控制参数" />-->
+        <!--        <el-table-column prop="chart" label="控制图" />-->
         <el-table-column prop="address" label="预警规则">
           <template #default="{ row }">
             <el-button
@@ -35,6 +40,10 @@
             >
           </template>
         </el-table-column>
+        <el-table-column prop="processCount" label="工序采集点数" />
+        <!--        <el-table-column prop="centralValue" label="中心值" />-->
+        <!--        <el-table-column prop="upperLimit" label="上限" />-->
+        <!--        <el-table-column prop="floor" label="下限" />-->
         <el-table-column prop="created" label="创建时间" />
         <el-table-column
           align="center"
@@ -85,6 +94,9 @@
           label-width="auto"
           class="formStyle"
         >
+          <el-form-item label="任务编号" prop="taskCode">
+            <el-input v-model="formData.taskCode" />
+          </el-form-item>
           <el-form-item label="产品型号" prop="Index1">
             <el-select v-model="formData.Index1">
               <el-option
@@ -98,9 +110,9 @@
           <el-form-item label="工序名称" prop="Index2">
             <el-select v-model="formData.Index2">
               <el-option
-                v-for="(item, index) in opInfoData2"
+                v-for="(item, index) in opOptions"
                 :key="index"
-                :label="item.name"
+                :label="item.dictLabel"
                 :value="index"
               />
             </el-select>
@@ -111,12 +123,32 @@
               <el-option label="手动" value="手动" />
             </el-select>
           </el-form-item>
-          <el-form-item label="控制参数" prop="param">
-            <el-input v-model="formData.param" />
-          </el-form-item>
-          <el-form-item label="控制图" prop="chart">
-            <el-input :disabled="true" v-model="formData.chart" />
+          <!--          <el-form-item label="控制参数" prop="param">-->
+          <!--            <el-input v-model="formData.param" />-->
+          <!--          </el-form-item>-->
+          <!--          <el-form-item label="控制图" prop="chart">-->
+          <!--            <el-input :disabled="true" v-model="formData.chart" />-->
+          <!--          </el-form-item>-->
+          <el-form-item label="工序采集点数" prop="processCount">
+            <el-input-number
+              v-model="formData.processCount"
+              :min="1"
+              :step="1"
+            />
           </el-form-item>
+          <!--          <el-form-item label="上限" prop="upperLimit">-->
+          <!--            <el-input-number v-model="formData.upperLimit" />-->
+          <!--          </el-form-item>-->
+          <!--          <el-form-item label="下限" prop="floor">-->
+          <!--            <el-input-number v-model="formData.floor" />-->
+          <!--          </el-form-item>-->
+          <!--          <el-form-item label="中心值" prop="centralValue">-->
+          <!--            <el-input-number-->
+          <!--              :min="formData.floor"-->
+          <!--              :max="formData.upperLimit"-->
+          <!--              v-model="formData.centralValue"-->
+          <!--            />-->
+          <!--          </el-form-item>-->
         </el-form>
         <div style="display: flex; justify-content: space-evenly">
           <el-button
@@ -143,7 +175,7 @@
 
 <script setup>
 import Search from "@/components/Search/index.vue";
-import { useSpcStore } from "@/store";
+import { useDictionaryStore, useSpcStore } from "@/store";
 import {
   getTableData,
   getBaseData,
@@ -166,7 +198,7 @@ const currentOption = reactive({
   total: 0,
   page: 1,
   limit: 10,
-
+  pageSizes: [10, 20, 30, 40, 50],
 });
 const showStatus = ref(true);
 const tableData = ref([]);
@@ -174,7 +206,7 @@ const searchRef = ref(null);
 const searchForm = [
   {
     label: "创建时间",
-    prop: "created",
+    prop: "createds",
     type: "daterange",
   },
   {
@@ -187,12 +219,8 @@ const searchForm = [
     prop: "prodtModel",
     type: "input",
   },
-  // {
-  //   label: "工序名称",
-  //   prop: "sss",
-  //   type: "select",
-  // },
 ];
+
 const maxHeight = ref(null);
 const setHeight = () => {
   maxHeight.value = document.querySelector(".table").clientHeight;
@@ -212,7 +240,7 @@ const toRuler = (row) => {
   router.push({ name: "SPCrules" });
 };
 const getData = async (obj) => {
-
+  console.log("222222222222222222")
   const { data, code } = await getTableData({
     ...searchRef.value.searchForm,
     pageNo: currentOption.page,
@@ -224,13 +252,22 @@ const getData = async (obj) => {
   }
 };
 const ruleFormRef = ref(null);
+const { dicts } = useDictionaryStore();
+const opOptions = ref([...dicts.spc_operation]);
 
 const rules = {
-  Index1: [
+  taskCode: [
     {
       required: true,
       trigger: "change",
+      message: "请输入任务编号",
+    },
+  ],
+  Index1: [
+    {
+      required: true,
       message: "请选择产品型号",
+      trigger: "change",
     },
   ],
   Index2: [
@@ -240,6 +277,13 @@ const rules = {
       trigger: "change",
     },
   ],
+  operationName: [
+    {
+      required: true,
+      trigger: "change",
+      message: "请选择工序名称",
+    },
+  ],
   source: [
     {
       required: true,
@@ -268,6 +312,34 @@ const rules = {
       trigger: "change",
     },
   ],
+  processCount: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  centralValue: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  upperLimit: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
+  floor: [
+    {
+      required: true,
+
+      trigger: "change",
+    },
+  ],
 };
 const opInfoData = ref([]);
 const toAdd = () => {
@@ -279,8 +351,8 @@ const setEditFormData = (row) => {
       formData.value.Index1 = index;
     }
   });
-  opInfoData.value[formData.value.Index1].operations.forEach((item, index) => {
-    if (item.code == row.operationCode) {
+  opOptions.value.forEach((item, index) => {
+    if (item.dictLabel == row.operationName) {
       formData.value.Index2 = index;
     }
   });
@@ -305,33 +377,44 @@ const reset = () => {
   };
   addStatus.value = false;
   editStatus.value = false;
+  currentOption.value = {
+    total: 0,
+    page: 1,
+    limit: 10,
+    pageSizes: [10, 20, 30, 40, 50],
+  };
+  console.log(currentOption.value, "currentOption.value")
+  console.log("11111111111111111")
 };
+
+const resetData = () => {
+  searchRef.value.searchForm = {};
+  getData();
+};
+
 const toSubmit = async () => {
   if (addStatus.value) {
     await ruleFormRef.value.validate(async (valid, fields) => {
       if (valid) {
         const { data, code } = await add({
           chart: formData.value.chart,
-          operationCode:
-            opInfoData.value[formData.value.Index1].operations[
-              formData.value.Index2
-            ].code,
-          operationName:
-            opInfoData.value[formData.value.Index1].operations[
-              formData.value.Index2
-            ].name,
-
+          operationCode: opOptions.value[formData.value.Index2].dictValue,
+          operationName: opOptions.value[formData.value.Index2].dictLabel,
           prodtCode: opInfoData.value[formData.value.Index1].prodtCode,
           prodtModel: opInfoData.value[formData.value.Index1].prodtModel,
           prodtName: opInfoData.value[formData.value.Index1].prodtName,
           unit: opInfoData.value[formData.value.Index1].unit,
           param: formData.value.param,
-
           source: formData.value.source,
+          processCount: formData.value.processCount,
+          centralValue: formData.value.centralValue,
+          upperLimit: formData.value.upperLimit,
+          floor: formData.value.floor,
+          taskCode: formData.value.taskCode,
         });
         if (code == "200") {
           ElMessage.success("添加成功!");
-          reset();
+          await reset();
           getData();
         }
       } else {
@@ -343,14 +426,8 @@ const toSubmit = async () => {
       if (valid) {
         const { data, code } = await updateData({
           ...formData.value,
-          operationCode:
-            opInfoData.value[formData.value.Index1].operations[
-              formData.value.Index2
-            ].code,
-          operationName:
-            opInfoData.value[formData.value.Index1].operations[
-              formData.value.Index2
-            ].name,
+          operationCode: opOptions.value[formData.value.Index2].dictValue,
+          operationName: opOptions.value[formData.value.Index2].dictLabel,
           prodtCode: opInfoData.value[formData.value.Index1].prodtCode,
           prodtModel: opInfoData.value[formData.value.Index1].prodtModel,
           prodtName: opInfoData.value[formData.value.Index1].prodtName,
@@ -358,7 +435,7 @@ const toSubmit = async () => {
         });
         if (code == "200") {
           ElMessage.success("修改成功!");
-          reset();
+          await reset();
           getData();
         }
       } else {
@@ -386,25 +463,30 @@ onMounted(() => {
   padding: 20px;
   display: flex;
   flex-direction: column;
+
   .header {
     width: 100%;
     height: auto;
   }
+
   .table {
     flex: 1;
     padding-bottom: 20px;
   }
+
   .formView {
     width: 100%;
     height: 100%;
     display: flex;
     flex-direction: column;
     align-items: center;
+
     .formTitle {
       text-align: center;
       font-size: 20px;
       margin-bottom: 20px;
     }
+
     .formStyle {
       width: 400px;
     }

+ 302 - 208
src/views/analysis/target/index/index.vue

@@ -18,15 +18,11 @@
         </el-select>
       </div>
       <div class="body">
-        <div class="text">
-          规格上限:{{ value ? JSON.parse(value).up : "-" }}
-        </div>
-        <div class="text">
-          规格下限:{{ value ? JSON.parse(value).down : "-" }}
-        </div>
-        <div class="text">
-          计量单位:{{ value ? JSON.parse(value).unit : "-" }}
-        </div>
+        <div class="text">上限:{{ value ? JSON.parse(value).up : "-" }}</div>
+        <div class="text">下限:{{ value ? JSON.parse(value).down : "-" }}</div>
+        <!--        <div class="text">-->
+        <!--          计量单位:{{ value ? JSON.parse(value).unit : "-" }}-->
+        <!--        </div>-->
       </div>
     </div>
     <div class="databox">
@@ -37,6 +33,14 @@
               <div class="bg"></div>
               样本数据录入
             </div>
+            <div class="header" v-show="!addStatus && !editStatus">
+              <Search
+                :searchOptions="searchForm"
+                ref="searchRef"
+                @data-list="getTableData"
+                @reset-list="resetData"
+              />
+            </div>
             <div class="btns">
               <!-- <el-button
                 v-if="!editStatus && !addStatus"
@@ -45,19 +49,21 @@
                 @click="changeEditstatus"
                 >编辑</el-button
               > -->
-              <el-button
-                v-if="!addStatus && !editStatus"
-                type="primary"
-                class="btn"
-                v-print="'#print'"
-                @click="printFnc"
-                >打印</el-button
-              >
+              <!--              <el-button-->
+              <!--                v-if="!addStatus && !editStatus"-->
+              <!--                type="primary"-->
+              <!--                class="btn"-->
+              <!--                v-print="'#print'"-->
+              <!--                @click="printFnc"-->
+              <!--                size="small"-->
+              <!--                >打印</el-button-->
+              <!--              >-->
               <el-button
                 v-if="!addStatus && !editStatus"
                 type="primary"
                 class="btn"
                 @click="changeaddstatus"
+                size="small"
                 >新增</el-button
               >
               <el-button
@@ -75,7 +81,8 @@
                 @click="canceleOp"
                 id="cancel"
                 >取消</el-button
-              ><span v-if="!addStatus && !editStatus" style="margin: 10px"
+              >
+              <!--              <span v-if="!addStatus && !editStatus" style="margin: 10px"
                 >年份:</span
               >
               <el-date-picker
@@ -88,7 +95,7 @@
                 placeholder="Pick a year"
                 :clear-icon="''"
                 @change="getTableData"
-              />
+              />-->
             </div>
             <!-- 导入代码 -->
             <!-- <div class="btns">
@@ -128,23 +135,20 @@
               <div class="tableTitle">
                 <div></div>
                 <div>{{ title }}一致性检测表</div>
-                <div>{{ year }}年</div>
+                <div></div>
               </div>
               <el-table
                 v-if="!printStatus"
                 :data="showTableData"
-                :span-method="objectSpanMethod"
-                id="table"
                 border
                 :style="{
-                  height: Height - 230 + 'px',
+                  height: Height - 50 + 'px',
                   width: maxWidth + 'px',
                 }"
                 :show-overflow-tooltip="true"
               >
                 <el-table-column
                   align="center"
-                  width="100"
                   prop="dateStr"
                   sortable
                   label="日期"
@@ -153,128 +157,51 @@
                     ><span>{{ row.dateStr }}</span>
                   </template>
                 </el-table-column>
-                <el-table-column
-                  align="center"
-                  width="110"
-                  prop="model"
-                  label="产品型号"
-                >
+                <el-table-column align="center" prop="model" label="产品型号">
                   <template #default="{ row }"
                     ><span>{{ row.model }}</span>
                   </template>
                 </el-table-column>
+                <el-table-column align="center" prop="batchNo" label="生产批号">
+                  <template #default="{ row }"
+                    ><span>{{ row.batchNo }}</span>
+                  </template>
+                </el-table-column>
                 <el-table-column
                   align="center"
-                  width="100"
-                  prop="batchNo"
-                  label="生产批号"
+                  prop="accuracys"
+                  label="数据"
+                  :show-overflow-tooltip="false"
                 >
-                  <template #default="{ row }"
-                    ><span>{{ row.batchNo }}</span>
+                  <template #default="{ row }">
+                    <el-tooltip placement="top">
+                      <template #content>
+                        <div
+                          v-for="(item, index) in row.accuracys"
+                          :key="index"
+                        >
+                          <span>数值{{ index + 1 }}: {{ item }}</span>
+                        </div>
+                      </template>
+                      <div class="ellipsis-text">
+                        {{ row.accuracys.join(", ") }}
+                      </div>
+                    </el-tooltip>
                   </template>
                 </el-table-column>
-                <el-table-column align="center" :label="title">
-                  <el-table-column
-                    align="center"
-                    prop="accuracy1"
-                    width="60"
-                    label="数值1"
-                  >
-                    <template #default="{ row }"
-                      ><span>{{ row.accuracy1 }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column
-                    align="center"
-                    prop="accuracy2"
-                    width="60"
-                    label="数值2"
-                  >
-                    <template #default="{ row }"
-                      ><span>{{ row.accuracy2 }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column
-                    align="center"
-                    prop="accuracy3"
-                    width="60"
-                    label="数值3"
-                  >
-                    <template #default="{ row }"
-                      ><span>{{ row.accuracy3 }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column
-                    align="center"
-                    prop="accuracy4"
-                    width="60"
-                    label="数值4"
-                  >
-                    <template #default="{ row }"
-                      ><span>{{ row.accuracy4 }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column
-                    align="center"
-                    prop="accuracy5"
-                    width="60"
-                    label="数值5"
-                  >
-                    <template #default="{ row }"
-                      ><span>{{ row.accuracy5 }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column
-                    align="center"
-                    prop="accuracy6"
-                    width="60"
-                    label="数值6"
-                  >
-                    <template #default="{ row }"
-                      ><span>{{ row.accuracy6 }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column
-                    align="center"
-                    prop="accuracy7"
-                    width="60"
-                    label="数值7"
-                  >
-                    <template #default="{ row }"
-                      ><span>{{ row.accuracy7 }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column
-                    align="center"
-                    prop="accuracy8"
-                    width="60"
-                    label="数值8"
-                  >
-                    <template #default="{ row }"
-                      ><span>{{ row.accuracy8 }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column
-                    align="center"
-                    prop="accuracy9"
-                    width="60"
-                    label="数值9"
-                  >
-                    <template #default="{ row }"
-                      ><span>{{ row.accuracy9 }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column
-                    align="center"
-                    prop="accuracy10"
-                    width="60"
-                    label="数值10"
-                  >
-                    <template #default="{ row }"
-                      ><span>{{ row.accuracy10 }}</span>
-                    </template>
-                  </el-table-column> </el-table-column
-                ><el-table-column
+                <!--                <el-table-column
+                  width="100"
+                  align="center"
+                  prop="accuracys"
+                  label="数据"
+                >
+                  <template #default="{ row }">
+                    <div v-for="(item, index) in row.accuracys" :key="index">
+                      <span>数值{{ index + 1 }}: {{ item }}</span>
+                    </div>
+                  </template>
+                </el-table-column>-->
+                <!--                                <el-table-column
                   align="center"
                   prop="Cpk"
                   label="Cpk值"
@@ -283,13 +210,8 @@
                   <template #default="{ row }"
                     ><span>{{ Number(row.cpk).toFixed(2) }}</span>
                   </template>
-                </el-table-column>
-                <el-table-column
-                  align="center"
-                  width="70"
-                  prop="checkUser"
-                  label="检测人"
-                >
+                </el-table-column>-->
+                <!--                <el-table-column align="center" prop="checkUser" label="检测人">
                   <template #default="{ row }"
                     ><span>{{ row.checkUser }}</span>
                   </template> </el-table-column
@@ -301,8 +223,8 @@
                   <template #default="{ row }"
                     ><span>{{ row.checkDeviceNo }}</span>
                   </template>
-                </el-table-column>
-                <el-table-column
+                </el-table-column>-->
+                <!--                <el-table-column
                   align="center"
                   prop="abnormal"
                   width="60"
@@ -325,7 +247,7 @@
                   <template #default="{ row }"
                     ><span>{{ row.measure }}</span>
                   </template>
-                </el-table-column>
+                </el-table-column>-->
                 <el-table-column align="center" prop="remark" label="备注">
                   <template #default="{ row }"
                     ><span>{{ row.remark }}</span>
@@ -333,7 +255,7 @@
                 </el-table-column>
                 <el-table-column
                   align="center"
-                  width="160"
+                  width="260"
                   prop=""
                   label="操作"
                   id="opear"
@@ -342,6 +264,13 @@
                     <el-button
                       type="primary"
                       class="btn"
+                      @click="computeCPK(row.id)"
+                      style="height: 25px"
+                      >计算Cpk</el-button
+                    >
+                    <el-button
+                      type="primary"
+                      class="btn"
                       @click="updataItem(row.Index)"
                       style="height: 25px"
                       >修改</el-button
@@ -511,7 +440,7 @@
                     ><span>{{ Number(row.cpk).toFixed(2) }}</span>
                   </template>
                 </el-table-column>
-                <el-table-column
+                <!--                <el-table-column
                   align="center"
                   width="60"
                   prop="checkUser"
@@ -529,7 +458,7 @@
                   <template #default="{ row }"
                     ><span>{{ row.checkDeviceNo }}</span>
                   </template>
-                </el-table-column>
+                </el-table-column>-->
                 <el-table-column
                   align="center"
                   prop="abnormal"
@@ -571,12 +500,12 @@
                   </template>
                 </el-table-column>
               </el-table>
-              <div class="illustrate">
-                <div>
-                  1)Cpx值为表征内引线键合强度一致性的参数,通过相应的软件计算得出,内引线键合强度一致性是否符合要求的判据:Cpk值≥1.33。
-                </div>
-                <div>2)“备注”栏填写被测内引线的材料和规格。</div>
-              </div>
+              <!--              <div class="illustrate">-->
+              <!--                <div>-->
+              <!--                  1)Cpk值为表征内引线键合强度一致性的参数,通过相应的软件计算得出,内引线键合强度一致性是否符合要求的判据:Cpk值≥1.33。-->
+              <!--                </div>-->
+              <!--                <div>2)“备注”栏填写被测内引线的材料和规格。</div>-->
+              <!--              </div>-->
             </div>
             <Pagination
               :total="currentOption.total"
@@ -584,6 +513,7 @@
               :limit="currentOption.limit"
               :pageSizes="currentOption.pageSizes"
               v-model:page="currentOption.page"
+              v-model:limit="currentOption.limit"
               @pagination="getTableData"
             />
           </div>
@@ -603,7 +533,7 @@
                 <template #default="{ row }"
                   >
                   <el-input-number
-           
+
                     v-model="row.accuracy1"
                     :precision="2"
                     :step="0.01"
@@ -620,7 +550,7 @@
                 <template #default="{ row }"
                   >
                   <el-input-number
-        
+
                     v-model="row.accuracy1"
                     :precision="2"
                     :step="0.01"
@@ -637,7 +567,7 @@
                 <template #default="{ row }"
                   >
                   <el-input-number
-     
+
                     v-model="row.accuracy1"
                     :precision="2"
                     :step="0.01"
@@ -655,7 +585,7 @@
                   <template #default="{ row }"
                     >
                     <el-input-number
-       
+
                       v-model="row.accuracy1"
                       :precision="2"
                       :step="0.01"
@@ -672,7 +602,7 @@
                   <template #default="{ row }"
                     >
                     <el-input-number
-          
+
                       v-model="row.accuracy2"
                       :precision="2"
                       :step="0.01"
@@ -688,7 +618,7 @@
                   <template #default="{ row }"
                     >
                     <el-input-number
-              
+
                       v-model="row.accuracy3"
                       :precision="2"
                       :step="0.01"
@@ -704,7 +634,7 @@
                   <template #default="{ row }"
                     >
                     <el-input-number
-          
+
                       v-model="row.accuracy4"
                       :precision="2"
                       :step="0.01"
@@ -720,7 +650,7 @@
                   <template #default="{ row }"
                     >
                     <el-input-number
-          
+
                       v-model="row.accuracy5"
                       :precision="2"
                       :step="0.01"
@@ -731,7 +661,7 @@
                 <template #default="{ row }"
                   >
                   <el-input-number
-            
+
                     v-model="row.accuracy1"
                     :precision="2"
                     :step="0.01"
@@ -742,7 +672,7 @@
                 <template #default="{ row }"
                   >
                   <el-input-number
-             
+
                     v-model="row.accuracy1"
                     :precision="2"
                     :step="0.01"
@@ -753,7 +683,7 @@
                 <template #default="{ row }"
                   >
                   <el-input-number
-              
+
                     v-model="row.accuracy1"
                     :precision="2"
                     :step="0.01"
@@ -768,7 +698,7 @@
                 <template #default="{ row }"
                   >
                   <el-input-number
-           
+
                     v-model="row.accuracy1"
                     :precision="2"
                     :step="0.01"
@@ -780,7 +710,7 @@
                 <template #default="{ row }"
                   >
                   <el-input-number
-    
+
                     v-model="row.accuracy1"
                     :precision="2"
                     :step="0.01"
@@ -792,7 +722,7 @@
                 <template #default="{ row }"
                   >
                   <el-input-number
-              
+
                     v-model="row.accuracy1"
                     :precision="2"
                     :step="0.01"
@@ -828,21 +758,41 @@
                     value-format="YYYY-MM-DD"
                   />
                 </el-form-item>
+                <el-form-item label="任务编号" prop="qualityTaskId">
+                  <el-select
+                    v-model="addData.qualityTaskId"
+                    @change="
+                      (value) => {
+                        taskChange(value);
+                      }
+                    "
+                  >
+                    <el-option
+                      v-for="(item, index) in taskOption"
+                      :key="index"
+                      :label="item.taskCode"
+                      :value="item.id"
+                    />
+                  </el-select>
+                </el-form-item>
                 <el-form-item label="产品型号" prop="model">
-                  <!-- <el-select  v-model="addData.model">
+                  <el-input :disabled="true" v-model="addData.model" />
+                </el-form-item>
+                <!--                <el-form-item label="产品型号" prop="model">
+                  &lt;!&ndash; <el-select  v-model="addData.model">
                     <el-option
                       v-for="item in modelOptions"
                       :key="item.dictLabel"
                       :label="item.dictLabel"
                       :value="item.dictValue"
                     />
-                  </el-select> -->
+                  </el-select> &ndash;&gt;
                   <el-input v-model="addData.model" />
-                </el-form-item>
+                </el-form-item>-->
                 <el-form-item label="生产批号" prop="batchNo">
                   <el-input v-model="addData.batchNo" />
                 </el-form-item>
-                <template v-for="(item, index) in 30" :key="index">
+                <!--                <template v-for="(item, index) in 30" :key="index">
                   <el-form-item
                     :label="'数值' + addIndex(index)"
                     :prop="'accuracy' + addIndex(index)"
@@ -860,20 +810,38 @@
                       v-model="addData[`accuracy${addIndex(index)}`]"
                     />
                   </el-form-item>
-                </template>
+                </template>-->
+                <el-form-item
+                  v-for="(item, index) in addData.accuracys"
+                  :label="'数值' + (index + 1)"
+                  :key="index"
+                  :rules="[
+                    {
+                      required: true,
+                      trigger: 'change',
+                    },
+                  ]"
+                >
+                  <el-input-number
+                    :precision="2"
+                    :step="0.01"
+                    style="width: 100%"
+                    v-model="addData.accuracys[index]"
+                  />
+                </el-form-item>
                 <!-- <el-form-item label="平均值" prop="avg">
                 <el-input v-model="addData.avg" />
               </el-form-item>
               <el-form-item label="极差" prop="range">
                 <el-input v-model="addData.range" />
               </el-form-item> -->
-                <el-form-item label="检测人" prop="checkUser">
+                <!--                <el-form-item label="检测人" prop="checkUser">
                   <el-input v-model="addData.checkUser" />
                 </el-form-item>
                 <el-form-item label="检查设备编号" prop="checkDeviceNo">
                   <el-input v-model="addData.checkDeviceNo" />
-                </el-form-item>
-                <el-form-item label="是否异常" prop="abnormal">
+                </el-form-item>-->
+                <!--                <el-form-item label="是否异常" prop="abnormal">
                   <el-input v-model="addData.abnormal" />
                 </el-form-item>
                 <el-form-item label="分析人" prop="analyseUser">
@@ -881,7 +849,7 @@
                 </el-form-item>
                 <el-form-item label="处置措施" prop="measure">
                   <el-input v-model="addData.measure" />
-                </el-form-item>
+                </el-form-item>-->
                 <el-form-item label="备注" prop="remark">
                   <el-input
                     maxlength="40"
@@ -926,10 +894,95 @@
 
 <script setup>
 import { useDictionaryStore } from "@/store";
-import { getData2, addDatas, deleteData, updateData } from "@/api/analysis";
+import {
+  getData2,
+  addDatas,
+  deleteData,
+  updateData,
+  getTaskCode,
+  cpkCompute,
+} from "@/api/analysis";
+import Search from "@/components/Search/index.vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+// 模拟后台接口
+const cpkApi = async (id) => {
+  const { data } = await cpkCompute({
+    spcRecordId: id,
+  });
+
+  return data;
+};
+
+const computeCPK = async (id) => {
+  try {
+    // 显示加载中的提示
+    ElMessageBox.alert("计算中...", "提示", {
+      confirmButtonText: "确定",
+      showClose: false,
+      closeOnClickModal: false,
+      closeOnPressEscape: false,
+    });
+
+    // 调用后台接口
+    const response = await cpkApi(id);
+
+    // 关闭加载中的提示
+    ElMessageBox.close();
+
+    const result = ref("");
+    if ("error" in response.data) {
+      result.value = response.data.error;
+    } else {
+      const { product, batch, process, cpk, status } = response.data;
+      result.value = `产品: ${product}<br>
+      批次: ${batch}<br>
+      工序: ${process}<br>
+      CPK: ${cpk}<br>
+      评级: ${status}`;
+    }
+
+    // 显示计算结果
+    ElMessageBox.alert(result.value, "计算结果", {
+      confirmButtonText: "确定",
+      dangerouslyUseHTMLString: true,
+    });
+  } catch (error) {
+    // 处理错误
+    ElMessageBox.close();
+  }
+};
+
 const tableData = ref([]);
 const printStatus = ref(false);
 const printLoading = ref(false);
+const taskOption = ref([]);
+const getTaskOption = async () => {
+  const { data } = await getTaskCode({
+    operationCode: JSON.parse(value.value).value,
+  });
+  taskOption.value = data;
+};
+const accuracysSum = ref(0);
+const taskChange = (value) => {
+  taskOption.value.forEach((item) => {
+    if (item.id == value) {
+      addData.value.model = item.prodtModel;
+      if (item.source === "手动") {
+        accuracysSum.value = Number(item.processCount);
+        addData.value.accuracys = [];
+        let array = [];
+        for (let i = 0; i < accuracysSum.value; i++) {
+          array.push(0);
+        }
+        addData.value.accuracys = array;
+      } else {
+        addData.value.accuracys = [];
+      }
+    }
+  });
+};
+
 //打印功能
 const printFnc = () => {
   printLoading.value = true;
@@ -995,23 +1048,24 @@ const setTableData = (array) => {
   //★Index 为实际接口数据Index
   array.forEach((item, index) => {
     let obj1 = { ...item, Index: index };
-    let obj2 = { ...item, Index: index };
-    let obj3 = { ...item, Index: index };
-    for (let i = 1; i < 11; i++) {
-      obj2[`accuracy${i}`] = obj2[`accuracy${i + 10}`];
-      obj3[`accuracy${i}`] = obj3[`accuracy${i + 20}`];
-    }
+    // let obj2 = { ...item, Index: index };
+    // let obj3 = { ...item, Index: index };
+    // for (let i = 1; i < 11; i++) {
+    //   obj2[`accuracy${i}`] = obj2[`accuracy${i + 10}`];
+    //   obj3[`accuracy${i}`] = obj3[`accuracy${i + 20}`];
+    // }
     showTableData.value.push(obj1);
-    showTableData.value.push(obj2);
-    showTableData.value.push(obj3);
+    // showTableData.value.push(obj2);
+    // showTableData.value.push(obj3);
   });
 };
 //接口获取实际data
 const getTableData = async () => {
   const { data, code, msg } = await getData2({
+    ...searchRef.value.searchForm,
     pageNo: currentOption.page,
     pageSize: currentOption.limit,
-    yearStr: year.value,
+    // yearStr: year.value,
     operation: lableValue.value,
   });
   if (code == "200") {
@@ -1026,6 +1080,24 @@ const getTableData = async () => {
   }
   disabled.value = false;
 };
+const searchRef = ref(null);
+const searchForm = [
+  {
+    label: "日期",
+    prop: "createds",
+    type: "daterange",
+  },
+  {
+    label: "产品型号",
+    prop: "model",
+    type: "input",
+  },
+  {
+    label: "生产批号",
+    prop: "batchNo",
+    type: "input",
+  },
+];
 //编辑状态
 const editStatus = ref(false);
 //新增状态
@@ -1164,8 +1236,8 @@ const value = ref(opOptions.value[0].remark);
 const currentOption = reactive({
   total: 0,
   page: 1,
-  limit: 4,
-  pageSizes: [4],
+  limit: 10,
+  pageSizes: [10, 20, 30, 40, 50],
 });
 const modelOptions = ref([]);
 const setChart1Info = () => {
@@ -1236,6 +1308,7 @@ const showLable = ref("调阻");
 const lableValue = ref("");
 const changeSelect = () => {
   setTimeout(() => {
+    getTaskOption();
     showLable.value = selectRef.value.currentPlaceholder;
     opOptions.value.forEach((item) => {
       if (item.dictLabel == showLable.value) {
@@ -1565,6 +1638,7 @@ const addSubmit = async () => {
       const { data, code } = await addDatas({
         ...addData.value,
         yearStr: year.value,
+        operation: lableValue.value,
       });
       if (code == "200") {
         ElMessage.success("添加成功!");
@@ -1601,11 +1675,17 @@ const reset = () => {
   addData.value = { ...resItem };
   currentOption.value = {
     total: 0,
-    page: 0,
-    limit: 4,
-    pageSizes: [4],
+    page: 1,
+    limit: 10,
+    pageSizes: [10, 20, 30, 40, 50],
   };
 };
+
+const resetData = () => {
+  searchRef.value.searchForm = {};
+  getTableData();
+};
+
 watch(
   () => showTableData.value,
   (newVal, oldVal) => {
@@ -1616,6 +1696,7 @@ watch(
   }
 );
 onMounted(() => {
+  getTaskOption();
   setHeight();
   year.value = new Date().getFullYear() + "";
   opOptions.value.forEach((item) => {
@@ -1679,6 +1760,12 @@ onMounted(() => {
     alignitems: center;
   }
 }
+.ellipsis-text {
+  white-space: nowrap; /* 禁止换行 */
+  overflow: hidden; /* 隐藏超出部分 */
+  text-overflow: ellipsis; /* 显示省略号 */
+  width: 100%; /* 宽度占满单元格 */
+}
 .formStyle {
   width: 400px;
   margin: 20px auto;
@@ -1716,22 +1803,29 @@ onMounted(() => {
         margin: 10px 0;
         padding-right: 40px;
       }
-      .title {
-        height: 30px;
+      .header {
+        margin-top: 20px;
+        //margin-left: 100px;
         display: flex;
-        align-items: center;
-        margin-bottom: 10px;
-        justify-content: space-between;
-        .btns {
-          display: flex;
-          align-items: center;
-          .btn {
-            height: 24px;
-            font-size: 14px;
-            margin: 0 5px;
-          }
-        }
+        width: 100%;
+        height: auto;
       }
+      //.title {
+      //  height: 30px;
+      //  display: flex;
+      //  align-items: center;
+      //  margin-bottom: 10px;
+      //  justify-content: space-between;
+      //  .btns {
+      //    display: flex;
+      //    align-items: center;
+      //    .btn {
+      //      height: 24px;
+      //      font-size: 14px;
+      //      margin: 0 5px;
+      //    }
+      //  }
+      //}
       .info {
         flex: 1;
         .tableTitle {

+ 184 - 0
src/views/charts/index.vue

@@ -0,0 +1,184 @@
+<template>
+  <div ref="chartRef" style="width: 100%; height: 400px"></div>
+</template>
+
+<script setup>
+import { ref, onMounted, watch } from "vue";
+import * as echarts from "echarts";
+
+// 假设的数据
+const data = [
+  { sampleId: "1", value: 10, ewma: 10.5, ucl: 15, lcl: 2 },
+  { sampleId: "2", value: 9, ewma: 11.0, ucl: 16, lcl: 3 },
+  { sampleId: "3", value: 13, ewma: 11.5, ucl: 17, lcl: 4 },
+  { sampleId: "4", value: 28, ewma: 12.0, ucl: 18, lcl: 5 },
+  { sampleId: "5", value: 7, ewma: 12.5, ucl: 22, lcl: 6 },
+  { sampleId: "6", value: 15, ewma: 13.0, ucl: 34, lcl: 8 },
+  { sampleId: "7", value: 12, ewma: 13.5, ucl: 34, lcl: 8 },
+  { sampleId: "8", value: 14, ewma: 14.0, ucl: 34, lcl: 8 },
+  { sampleId: "9", value: 16, ewma: 14.5, ucl: 34, lcl: 8 },
+  { sampleId: "10", value: 4, ewma: 15.0, ucl: 34, lcl: 8 },
+];
+
+const chartRef = ref(null);
+const chartInstance = ref(null);
+
+const prepareData = () => {
+  const sampleIds = data.map((item) => item.sampleId);
+  const values = data.map((item) => item.value);
+  const ewmaValues = data.map((item) => item.ewma);
+  const uclValues = data.map((item) => item.ucl);
+  const lclValues = data.map((item) => item.lcl);
+  const meanValue =
+    data.reduce((acc, item) => acc + item.value, 0) / data.length;
+
+  return {
+    sampleIds,
+    values,
+    ewmaValues,
+    uclValues,
+    lclValues,
+    meanValue,
+  };
+};
+
+const initChart = (chartDom) => {
+  const myChart = echarts.init(chartDom);
+  const { sampleIds, values, ewmaValues, uclValues, lclValues, meanValue } =
+    prepareData();
+
+  const option = {
+    title: {
+      text: "EWMA 控制图",
+    },
+    tooltip: {
+      trigger: "axis",
+    },
+    xAxis: {
+      type: "category",
+      boundaryGap: false, // 不留白,从原点开始
+      data: sampleIds,
+    },
+    yAxis: {
+      type: "value",
+      name: "EWMA值",
+    },
+    series: [
+      {
+        name: "数值",
+        type: "line",
+        // step: "start",
+        symbol: "circle", //将小圆点改成实心 不写symbol默认空心
+        symbolSize: 8,
+        data: values.map((value, index) => ({
+          value,
+          itemStyle: {
+            color:
+              value > uclValues[index] || value < lclValues[index]
+                ? "red"
+                : "blue",
+          },
+        })),
+      },
+      // {
+      //   name: "EWMA",
+      //   type: "line",
+      //   step: "start",
+      //   data: ewmaValues,
+      //   lineStyle: {
+      //     color: "green",
+      //   },
+      // },
+      {
+        name: "均值",
+        type: "line",
+        data: Array(sampleIds.length).fill(meanValue),
+        showSymbol: false,
+        lineStyle: {
+          color: "green",
+          type: "solid",
+        },
+      },
+      {
+        name: "UCL",
+        type: "line",
+        step: "start",
+        data: uclValues,
+        showSymbol: false,
+        lineStyle: {
+          color: "red",
+          type: "dashed",
+        },
+      },
+      {
+        name: "LCL",
+        type: "line",
+        step: "start",
+        data: lclValues,
+        showSymbol: false,
+        lineStyle: {
+          color: "red",
+          type: "dashed",
+        },
+      },
+    ],
+    color: ["blue", "green", "red", "red", "black"],
+  };
+
+  myChart.setOption(option);
+  chartInstance.value = myChart;
+};
+
+const resizeChart = () => {
+  if (chartInstance.value) {
+    chartInstance.value.resize();
+  }
+};
+
+onMounted(() => {
+  initChart(chartRef.value);
+  window.addEventListener("resize", resizeChart);
+});
+
+watch(data, () => {
+  if (chartInstance.value) {
+    const { sampleIds, values, ewmaValues, uclValues, lclValues, meanValue } =
+      prepareData();
+    const option = {
+      xAxis: {
+        data: sampleIds,
+      },
+      series: [
+        {
+          data: values.map((value, index) => ({
+            value,
+            itemStyle: {
+              color:
+                value > uclValues[index] || value < lclValues[index]
+                  ? "red"
+                  : "blue",
+            },
+          })),
+        },
+        {
+          data: ewmaValues,
+        },
+        {
+          data: uclValues,
+        },
+        {
+          data: lclValues,
+        },
+        {
+          data: Array(sampleIds.length).fill(meanValue),
+        },
+      ],
+    };
+    chartInstance.value.setOption(option);
+  }
+});
+</script>
+
+<style scoped>
+/* 样式可以根据需要进行调整 */
+</style>

+ 2 - 2
src/views/login/index.vue

@@ -137,8 +137,8 @@ const loginFormRef = ref(ElForm); // 登录表单ref
 const { height } = useWindowSize();
 
 const loginData = ref<LoginData>({
-  userName: "admin",
-  password: "admin@123",
+  userName: "",
+  password: "",
 });
 
 const loginRules = computed?.(() => {

+ 468 - 0
src/views/statistic/firstPassYield/index.vue

@@ -0,0 +1,468 @@
+<template>
+  <div class="container1">
+    <div class="infobox">
+      <el-scrollbar>
+        <div class="header">
+          <div class="text">选择统计时间段:</div>
+          <div style="display: flex">
+            <el-date-picker
+              v-model="value1"
+              type="daterange"
+              range-separator="-"
+              start-placeholder="起始时间"
+              end-placeholder="截止时间"
+              format="YYYY-MM-DD"
+              value-format="YYYY-MM-DD"
+              size="small"
+            />
+          </div>
+        </div>
+        <div class="header">
+          <div class="text">选择统计维度:</div>
+          <div>
+            <el-radio-group v-model="radio" style="display: flex">
+              <el-radio size="small" :value="1">天</el-radio>
+              <el-radio size="small" :value="2">周</el-radio>
+              <el-radio size="small" :value="3">月</el-radio>
+              <el-radio size="small" :value="4">年</el-radio>
+            </el-radio-group>
+          </div>
+        </div>
+        <div class="header">
+          <div class="text">选择统计车间:</div>
+          <el-tree
+            ref="treeRef1"
+            :data="statProductionLineData"
+            show-checkbox
+            default-expand-all
+            :default-checked-keys="['line']"
+            node-key="key"
+          />
+        </div>
+        <div class="header" style="border-bottom: 0px">
+          <div class="text">选择统计产品:</div>
+          <el-input
+            size="small"
+            placeholder="请输入产品名称"
+            @click="openProdtForm"
+          />
+          <el-tree
+            ref="treeRef2"
+            :data="treeData"
+            default-expand-all
+            node-key="productName"
+            highlight-current
+            style="margin-top: 20px"
+          />
+          <!-- <el-tree
+            v-if="prodt.prodtName"
+            ref="treeRef2"
+            :data="statProductTypeData"
+            show-checkbox
+            default-expand-all
+            :default-checked-keys="['productType1']"
+            node-key="key"
+            style="margin-top: 20px"
+          /> -->
+        </div>
+      </el-scrollbar>
+    </div>
+    <div class="databox">
+      <div class="box">
+        <div class="boxheader">
+          <div>
+            <el-button type="primary" class="btn" @click="getTableDataSearch"
+              >统计查询</el-button
+            >
+            <el-button class="btn" @click="reset">重置</el-button>
+          </div>
+          <el-button class="btn" @click="exportFnc">导出</el-button>
+        </div>
+        <div class="info">
+          <el-table
+            :data="tableData"
+            border
+            :style="{ height: maxHeight + 'px' }"
+            style="width: calc(100% - 50)"
+          >
+            <!-- <el-table-column
+              v-if="queryIndexs.includes('productLine')"
+              prop="productLineName"
+              label="产线名称"
+            />
+            <el-table-column
+              v-if="queryIndexs.includes('prodict')"
+              prop="materialName"
+              label="产品名称"
+            />
+            <el-table-column
+              v-if="queryIndexs.includes('operation')"
+              prop="operationName"
+              label="工序名称"
+            /> -->
+            <el-table-column prop="productLineName" label="车间名称" />
+            <el-table-column prop="productName" label="产品名称" />
+            <el-table-column prop="productCode" label="产品编码" />
+            <el-table-column prop="batchNo" label="产品批次" />
+            <el-table-column prop="goodRate" label="良率" />
+            <el-table-column prop="badRate" label="不良率" />
+            <el-table-column prop="timeStr" label="日期" />
+          </el-table>
+        </div>
+        <div class="footer">
+          <Pagination
+            :total="currentOption.total"
+            :page="currentOption.page"
+            :limit="currentOption.limit"
+            :pageSizes="currentOption.pageSizes"
+            v-model:page="currentOption.page"
+            v-model:limit="currentOption.limit"
+            @pagination="getTableData"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+  <el-dialog title="选择统计产品" v-model="dialogVisible" width="60%" center>
+    <div>
+      <Search :searchOptions="searchForm" ref="searchRef" />
+    </div>
+    <div>
+      <el-table
+        :data="tableProdtData"
+        row-key="id"
+        border
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column type="selection" width="55" />
+        <el-table-column prop="productLineName" label="车间名称" />
+        <el-table-column prop="productName" label="产品名称" />
+        <el-table-column prop="productCode" label="产品编码" />
+        <el-table-column prop="batchNo" label="产品批次" />
+        <el-table-column prop="productType" label="产品类型" />
+        <el-table-column prop="timeStr" label="创建日期" />
+      </el-table>
+      <Pagination
+        :total="currentProdtOption.total"
+        :page="currentProdtOption.page"
+        :limit="currentProdtOption.limit"
+        :pageSizes="currentProdtOption.pageSizes"
+        v-model:page="currentProdtOption.page"
+        v-model:limit="currentProdtOption.limit"
+        @pagination="getProdtData"
+      />
+    </div>
+    <div>
+      <el-button type="primary" class="btn" @click="handleConfirm"
+        >确定</el-button
+      >
+      <el-button class="btn" @click="dialogVisible = false">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup>
+import { getData, getStatLevel, getExport } from "@/api/statistic";
+import { getMockData } from "@/api/statistic/firstPassYieldMockData";
+import Pagination from "@/components/Pagination/index.vue";
+import { downFile } from "@/utils/common";
+import Search from "@/components/Search/index.vue";
+
+defineOptions({
+  name: "Dashboard",
+  inheritAttrs: false,
+});
+const exportFnc = async () => {
+  await getExport({
+    endTime: value1.value ? value1.value[1] : "",
+    startTime: value1.value ? value1.value[0] : "",
+    queryTypes: getValue(treeRef1.value.getCheckedNodes()),
+    queryIndex: getValue(treeRef2.value.getCheckedNodes()),
+    timeType: radio.value,
+  }).then((res) => downFile(res));
+};
+const treeRef1 = ref(null);
+const treeRef2 = ref(null);
+const currentOption = ref({
+  total: 0,
+  page: 1,
+  limit: 10,
+  pageSizes: [10, 20, 30, 40, 50, 100],
+});
+
+const maxHeight = ref(null);
+const maxWidth = ref(null);
+
+const value1 = ref(null);
+const radio = ref(1);
+const tableData = ref([]);
+
+const setHeight = () => {
+  maxHeight.value = document.querySelector(".info").clientHeight - 30;
+  maxWidth.value = document.querySelector(".info").clientWidth - 120;
+};
+const getCurrentMonthStartAndEndDates = () => {
+  // 获取当前日期
+  let now = new Date();
+
+  // 获取当前月份的第一天4r
+  let startDate = new Date(now.getFullYear(), now.getMonth(), 1);
+
+  // 获取当前月份的最后一天
+  let endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0);
+
+  // 格式化日期为'YYYY-MM-DD'格式
+  function formatDate(date) {
+    let year = date.getFullYear();
+    let month = String(date.getMonth() + 1).padStart(2, "0");
+    let day = String(date.getDate()).padStart(2, "0");
+    return `${year}-${month}-${day}`;
+  }
+
+  // 返回包含开始和结束日期的数组
+  return [formatDate(startDate), formatDate(endDate)];
+};
+const getValue = (array) => {
+  let resarray = [];
+  for (let i = 0; i < array.length; i++) {
+    resarray.push(array[i].value);
+  }
+  return resarray;
+};
+const queryIndexs = ref([]);
+// const getTableData = async () => {
+//   queryIndexs.value = getValue(treeRef1.value.getCheckedNodes());
+//   const { data } = await getData({
+//     endTime: value1.value ? value1.value[1] : "",
+//     startTime: value1.value ? value1.value[0] : "",
+//     pageNo: currentOption.value.page,
+//     pageSize: currentOption.value.limit,
+//     queryTypes: getValue(treeRef1.value.getCheckedNodes()),
+//     queryIndex: getValue(treeRef2.value.getCheckedNodes()),
+//     timeType: radio.value,
+//   });
+//   tableData.value = data.records;
+//   currentOption.value.total = data.totalCount;
+// };
+
+// mock数据
+const getTableData = async () => {
+  queryIndexs.value = getValue(treeRef1.value.getCheckedNodes());
+  const { data } = await getMockData({
+    page: currentOption.value.page,
+    limit: currentOption.value.limit,
+  }); // 使用假数据函数
+  tableData.value = data.records;
+  currentOption.value.total = data.totalCount;
+};
+// -------
+
+const reset = () => {
+  value1.value = getCurrentMonthStartAndEndDates();
+  // treeRef1.value.setCheckedKeys([statProductionLineData[0].key], true, true);
+  // treeRef2.value.setCheckedKeys([statProductTypeData[0].key], true, true);
+  radio.value = 1;
+  currentOption.value.page = 1;
+  treeData.value = [];
+  selectedRows.value = [];
+  tableData.value = [];
+  getTableData();
+};
+
+// mock数据
+const statProductionLineData = ref([
+  {
+    key: "line",
+    label: "微电子车间",
+    children: [],
+  },
+]);
+
+const tableProdtData = ref([]);
+const dialogVisible = ref(false);
+const searchRef = ref(null);
+const openProdtForm = async () => {
+  dialogVisible.value = true;
+  getProdtData();
+};
+
+const getProdtData = async () => {
+  const { data } = await getMockData({
+    page: currentProdtOption.value.page,
+    limit: currentProdtOption.value.limit,
+  }); // 使用假数据函数
+  tableProdtData.value = data.records;
+  currentProdtOption.value.total = data.totalCount;
+};
+const searchForm = [
+  {
+    label: "产品名称",
+    type: "input",
+    key: "prodtName",
+  },
+  {
+    label: "产品编码",
+    type: "input",
+    key: "prodtCode",
+  },
+  {
+    label: "产品类型",
+    type: "select",
+    key: "prodtType",
+    options: [
+      {
+        label: "类型1",
+        value: "1",
+      },
+      {
+        label: "类型2",
+        value: "2",
+      },
+    ],
+  },
+];
+
+const currentProdtOption = ref({
+  total: 0,
+  page: 1,
+  limit: 10,
+  pageSizes: [10, 20, 30, 40, 50, 100],
+});
+// --------------------------------------------
+const statProductTypeData = ref([
+  {
+    key: "id",
+    label: "产品类型",
+    children: [
+      {
+        key: "productType1",
+        label: "类型1",
+      },
+      {
+        key: "productType2",
+        label: "类型2",
+      },
+    ],
+  },
+]);
+const selectedRows = ref([]); // 存储选中的行数据
+
+const handleSelectionChange = (rows) => {
+  selectedRows.value = rows; // 更新选中的行数据
+};
+
+const treeData = ref([]); // 存储转换后的 el-tree 数据
+const handleConfirm = () => {
+  const map = {};
+  const tree = [];
+
+  selectedRows.value.forEach((row) => {
+    const { productName, batchNo } = row;
+
+    // 如果父级节点不存在,创建父级节点
+    if (!map[productName]) {
+      const parentNode = {
+        label: productName, // 父级名称
+        key: productName, // 父级唯一标识
+        children: [], // 子级数组
+      };
+      map[productName] = parentNode;
+      tree.push(parentNode);
+    }
+
+    // 添加子级节点
+    map[productName].children.push({
+      label: batchNo, // 子级名称
+      key: batchNo, // 子级唯一标识
+    });
+  });
+
+  treeData.value = tree; // 更新 el-tree 数据源
+  dialogVisible.value = false; // 关闭对话框
+};
+
+const getTableDataSearch = () => {
+  if (treeData.value.length === 0) {
+    ElMessage.warning("请选择产品");
+    return;
+  }
+  // 提取 treeData 中的 productName 和 batchNo
+  const selectedProductMap = {};
+  treeData.value.forEach((parent) => {
+    selectedProductMap[parent.label] = parent.children.map(
+      (child) => child.label
+    );
+  });
+
+  // 筛选 tableProdtData 中符合条件的数据
+  const filteredData = tableProdtData.value.filter((item) => {
+    const productNameMatches = selectedProductMap[item.productName];
+    return productNameMatches && productNameMatches.includes(item.batchNo);
+  });
+
+  // 更新表格数据
+  tableData.value = filteredData;
+};
+//-------------------
+
+const checkedKeys = computed(() => {
+  let array = [];
+  for (let i = 0; i < statProductionLineData.value.length; i++) {
+    array.push(statProductionLineData.value[i].key);
+  }
+  return array;
+});
+
+onMounted(async () => {
+  window.addEventListener("resize", setHeight);
+  value1.value = getCurrentMonthStartAndEndDates();
+  setHeight();
+  // await getStatLevelData();
+  getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.btn {
+  margin-left: 10px;
+  height: 25px;
+}
+.container1 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  background-color: white;
+  .infobox {
+    width: 260px;
+    .header {
+      border-bottom: 2px solid #00000010;
+      padding: 20px;
+    }
+    .body {
+      padding: 20px;
+    }
+  }
+  .databox {
+    width: calc(100% - 260px);
+    border-left: 2px solid #00000010;
+    .box {
+      height: 100%;
+      padding: 5px 20px;
+      display: flex;
+      flex-direction: column;
+      .boxheader {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin: 10px 0px 10px -10px;
+      }
+      .info {
+        flex: 1;
+      }
+    }
+  }
+}
+.el-radio {
+  margin-right: 8px;
+}
+</style>

+ 233 - 17
src/views/statistic/report/index.vue

@@ -1,13 +1,19 @@
 <template>
   <div class="container1">
-    <div class="header">
+    <div class="header" v-show="!addStatus && !editStatus">
       <Search
         ref="searchref"
         :searchOptions="searchOptons"
         @data-list="getTableData"
       />
     </div>
-    <div class="table">
+    <div class="table" v-if="!addStatus && !editStatus">
+      <div class="tableheader">
+        <el-button type="primary" size="small" class="btn" @click="toAdd"
+          >新增</el-button
+        >
+        <el-button class="btn" @click="exportFnc">导出</el-button>
+      </div>
       <!-- <el-button class="btn" style="margin-bottom: 5px" @click="showPrint"
         >打印</el-button
       > -->
@@ -18,6 +24,12 @@
       >
         <el-table-column prop="reportCode" label="报告编码" />
         <el-table-column prop="reportName" label="报告名称" />
+        <el-table-column label="报告类型">
+          <template #default="{ row }">
+            {{ row.reportType === "1" ? "日报" : "月报" }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="genDate" label="生成日期" />
         <el-table-column prop="creator" label="创建人" />
         <el-table-column prop="created" label="创建时间" />
         <el-table-column
@@ -32,25 +44,130 @@
               type="primary"
               class="btn"
               style="height: 25px"
-              @click="toLook(row)"
+              @click="toEdit(row)"
+              link
+              >编辑</el-button
+            >
+            <el-button
+              type="primary"
+              class="btn"
+              style="height: 25px"
+              @click="showPrint(row)"
               link
               >查看</el-button
             >
+            <el-button
+              type="info"
+              class="btn"
+              style="height: 25px"
+              link
+              @click="toDelete(row.id)"
+              >删除</el-button
+            >
           </template>
         </el-table-column>
       </el-table>
       <Pagination />
     </div>
-    <ReportView v-model="showStatus" :tableData="tableData" />
+    <div v-if="addStatus || editStatus" class="formView">
+      <div class="formTitle">
+        {{ addStatus ? "新增报告" : "编辑报告" }}
+      </div>
+      <el-scrollbar style="height: calc(100% - 60px)">
+        <el-form
+          ref="ruleFormRef"
+          :model="formData"
+          :rules="rules"
+          label-width="auto"
+          class="formStyle"
+        >
+          <el-form-item label="报告编码" prop="reportCode">
+            <el-input v-model="formData.reportCode" />
+          </el-form-item>
+          <el-form-item label="报告名称" prop="reportName">
+            <el-input v-model="formData.reportName" />
+          </el-form-item>
+          <el-form-item label="报告类型" prop="reportType">
+            <el-select v-model="formData.reportType">
+              <el-option
+                v-for="(item, index) in reportTypeOptions"
+                :key="index"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="选择生成日期" prop="genDate">
+            <el-date-picker
+              v-model="formData.genDate"
+              type="date"
+              placeholder="选择日期"
+              style="width: 100%"
+              v-if="formData.reportType == 1"
+            />
+            <el-date-picker
+              v-model="formData.genDate"
+              type="month"
+              placeholder="选择日期"
+              style="width: 100%"
+              v-if="formData.reportType == 2"
+            />
+          </el-form-item>
+          <el-form-item label="产品型号" prop="prodtCode">
+            <el-input
+              v-model="formData.prodtCode"
+              @click="openChoseForm('prodt')"
+            />
+          </el-form-item>
+          <el-form-item label="产品批次" prop="batchNo">
+            <el-input
+              v-model="formData.batchNo"
+              @click="openChoseForm('batch')"
+            />
+          </el-form-item>
+          <el-form-item label="产品工序" prop="operationCode">
+            <el-input
+              v-model="formData.operationCode"
+              @click="openChoseForm('operation')"
+            />
+          </el-form-item>
+          <el-form-item label="创建人" prop="creator">
+            <el-input v-model="formData.creator" />
+          </el-form-item>
+        </el-form>
+        <div style="display: flex; justify-content: space-evenly">
+          <el-button
+            type="primary"
+            size="small"
+            style="margin-top: 10px"
+            class="btn"
+            @click="toSubmit"
+            >提交</el-button
+          >
+          <el-button
+            type="info"
+            size="small"
+            style="margin-top: 10px"
+            class="btn"
+            @click="toCancel"
+            >取消</el-button
+          >
+        </div>
+      </el-scrollbar>
+    </div>
+    <reportTemplate v-model="showStatus" :tablesData="tablesData" />
+    <operationForm ref="opRef" />
   </div>
 </template>
 
 <script setup>
 import Search from "@/components/Search/index.vue";
-import ReportView from "@/components/ReportView/index.vue";
+import reportTemplate from "@/components/ReportView/reportTemplate.vue";
 import { useDictionaryStore } from "@/store";
 import { getData } from "@/api/report";
 import Pagination from "@/components/Pagination/index.vue";
+import { getMockData } from "@/api/statistic/reportMockData";
+import operationForm from "./operationForm.vue";
 const searchref = ref(null);
 const showPrint = () => {
   showStatus.value = true;
@@ -90,22 +207,85 @@ const getCurrentMonthStartAndEndDates = () => {
   return [formatDate(startDate), formatDate(endDate)];
 };
 const tableData = ref([]);
+// const getTableData = async () => {
+//   const { data } = await getData({
+//     pageNo: currentOption.page,
+//     pageSize: currentOption.limit,
+//     startTime: searchref.value.searchForm.time
+//       ? searchref.value.searchForm.time[0]
+//       : null,
+//     endTime: searchref.value.searchForm.time
+//       ? searchref.value.searchForm.time[1]
+//       : null,
+//     reportCode: searchref.value.searchForm.reportCode,
+//     reportName: searchref.value.searchForm.reportName,
+//     reportType: searchref.value.searchForm.reportType,
+//   });
+//   tableData.value = data.records;
+//   currentOption.total = data.totalCount;
+// };
+
+// mock数据
 const getTableData = async () => {
-  const { data } = await getData({
-    pageNo: currentOption.page,
-    pageSize: currentOption.limit,
-    startTime: searchref.value.searchForm.time
-      ? searchref.value.searchForm.time[0]
-      : null,
-    endTime: searchref.value.searchForm.time
-      ? searchref.value.searchForm.time[1]
-      : null,
-    reportCode: searchref.value.searchForm.reportCode,
-    reportName: searchref.value.searchForm.reportName,
-  });
+  const { data } = await getMockData(); // 使用假数据函数
   tableData.value = data.records;
   currentOption.total = data.totalCount;
+  console.log(tableData.value);
+};
+
+const toCancel = () => {
+  addStatus.value = false;
+  editStatus.value = false;
+};
+
+const formData = reactive({
+  reportCode: "",
+  rePortName: "",
+  rePortType: "",
+  creator: "",
+  prodtCode: "",
+  batchNo: "",
+  operationCode: "",
+});
+
+const addStatus = ref(false);
+const toAdd = () => {
+  reset();
+  addStatus.value = true;
+};
+const editStatus = ref(false);
+const toEdit = (row) => {
+  console.log(row);
+  editStatus.value = true;
+  formData.reportCode = row.reportCode;
+  formData.reportName = row.reportName;
+  formData.reportType = row.reportType;
+  formData.genDate = row.genDate;
+  formData.creator = row.creator;
+  formData.prodtCode = row.prodtCode;
+  formData.batchNo = row.batchNo;
+  formData.operationCode = row.operationCode;
 };
+
+const reset = () => {
+  formData.reportCode = "";
+  formData.reportName = "";
+  formData.reportType = "";
+  formData.creator = "";
+  formData.prodtCode = "";
+  formData.batchNo = "";
+  formData.operationCode = "";
+};
+
+const toSubmit = () => {
+  addStatus.value = false;
+  editStatus.value = false;
+};
+
+const reportTypeOptions = ref([
+  { label: "日报", value: "1" },
+  { label: "月报", value: "2" },
+]);
 const showStatus = ref(false);
 const searchOptons = [
   {
@@ -123,7 +303,20 @@ const searchOptons = [
     prop: "reportName",
     type: "input",
   },
+  {
+    label: "报告类型",
+    prop: "reportType",
+    type: "select",
+    options: reportTypeOptions.value,
+  },
 ];
+
+const opRef = ref(null);
+const openChoseForm = (type) => {
+  opRef.value.open(type);
+};
+
+//------------------
 const maxHeight = ref(null);
 const setHeight = () => {
   maxHeight.value = document.querySelector(".table").clientHeight - 30;
@@ -153,6 +346,29 @@ onMounted(async () => {
   .table {
     flex: 1;
     padding-bottom: 20px;
+    .tableheader {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin: 10px 0px 10px 0px;
+    }
+  }
+}
+.formView {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  .formTitle {
+    text-align: center;
+    font-size: 20px;
+    margin-bottom: 20px;
+  }
+
+  .formStyle {
+    width: 400px;
   }
 }
 </style>

+ 163 - 0
src/views/statistic/report/operationForm.vue

@@ -0,0 +1,163 @@
+<template>
+  <el-dialog v-model="dialogvisable">
+    <el-form
+      label-position="right"
+      label-width="100px"
+      inline
+      class="search"
+      v-model="searchForm"
+    >
+      <el-form-item label="产品名称:">
+        <el-input v-model="searchForm.produdctName" />
+      </el-form-item>
+      <el-form-item label="产品编码:">
+        <el-input v-model="searchForm.produdctCode" />
+      </el-form-item>
+      <el-form-item
+        label="产品批次:"
+        v-if="type === 'batch' || type === 'operation'"
+      >
+        <el-input v-model="searchForm.produdctName" />
+      </el-form-item>
+      <el-form-item label="工序名称:" v-if="type === 'operation'">
+        <el-input v-model="searchForm.operationName" />
+      </el-form-item>
+      <el-form-item label="工序编码:" v-if="type === 'operation'">
+        <el-input v-model="searchForm.operationCode" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="getData">查询</el-button>
+        <el-button @click="reset">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-table :data="tableData">
+      <el-table-column type="selection" width="55" />
+      <el-table-column prop="produdctLineName" label="产线名称" />
+      <el-table-column prop="produdctName" label="产品名称" />
+      <el-table-column prop="produdctCode" label="产品编码" />
+      <el-table-column
+        prop="batchNo"
+        v-if="type === 'batch' || type === 'operation'"
+        label="产品批次"
+      />
+      <el-table-column
+        prop="operationName"
+        v-if="type === 'operation'"
+        label="工序名称"
+      />
+      <el-table-column
+        prop="operationCode"
+        v-if="type === 'operation'"
+        label="工序编码"
+      />
+      <el-table-column prop="created" label="创建时间" />
+    </el-table>
+    <Pagination
+      :total="currentOption.total"
+      :page="currentOption.page"
+      :limit="currentOption.limit"
+      :pageSizes="currentOption.pageSizes"
+      @pagination="getProdtData"
+    />
+    <div class="footer">
+      <el-button class="btn" type="primary" @click="dialogvisable = false"
+        >确定</el-button
+      >
+      <el-button class="btn" @click="dialogvisable = false">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+const searchForm = ref({
+  produdctName: "",
+  produdctCode: "",
+  batchNo: "",
+  operationName: "",
+  operationCode: "",
+});
+
+const currentOption = ref({
+  total: 0,
+  page: 1,
+  limit: 10,
+  pageSizes: [10, 20, 30, 40],
+});
+
+const dialogvisable = ref(false);
+const tableData = ref([
+  {
+    produdctLineName: "微电子车间产线",
+    produdctName: "产品A",
+    produdctCode: "CODE001",
+    batchNo: "BATCH001",
+    operationName: "工序1",
+    operationCode: "OP001",
+    created: "2025-03-27 10:00:00",
+  },
+  {
+    produdctLineName: "微电子车间产线",
+    produdctName: "产品A",
+    produdctCode: "CODE002",
+    batchNo: "BATCH002",
+    operationName: "工序2",
+    operationCode: "OP002",
+    created: "2025-03-27 11:00:00",
+  },
+  {
+    produdctLineName: "微电子车间产线",
+    produdctName: "产品B",
+    produdctCode: "CODE003",
+    batchNo: "BATCH003",
+    operationName: "工序3",
+    operationCode: "OP003",
+    created: "2025-03-27 12:00:00",
+  },
+  {
+    produdctLineName: "微电子车间产线",
+    produdctName: "产品B",
+    produdctCode: "CODE004",
+    batchNo: "BATCH004",
+    operationName: "工序4",
+    operationCode: "OP004",
+    created: "2025-03-27 13:00:00",
+  },
+  {
+    produdctLineName: "微电子车间产线",
+    produdctName: "产品C",
+    produdctCode: "CODE005",
+    batchNo: "BATCH005",
+    operationName: "工序5",
+    operationCode: "OP005",
+    created: "2025-03-27 14:00:00",
+  },
+]);
+const type = ref();
+
+const open = (openType: String) => {
+  dialogvisable.value = true;
+  type.value = openType;
+};
+
+const getData = () => {
+  console.log(searchForm.value);
+};
+
+const reset = () => {
+  searchForm.value = {
+    produdctName: "",
+    produdctCode: "",
+    batchNo: "",
+    operationName: "",
+    operationCode: "",
+  };
+};
+
+const getProdtData = () => {
+  console.log(tableData.value.length);
+};
+
+defineExpose({ open });
+</script>
+
+<css lang="scss" scope></css>

+ 10 - 0
src/views/statistic/statistic/index.vue

@@ -138,6 +138,16 @@ const data1 = [
     key: "合格率",
     label: "合格率",
   },
+  {
+    value: "oneTimePassRate",
+    key: "一次性送检合格率",
+    label: "一次性送检合格率",
+  },
+  {
+    value: "PPM",
+    key: "PPM",
+    label: "PPM",
+  },
 ];
 const maxHeight = ref(null);
 const maxWidth = ref(null);