Prechádzať zdrojové kódy

Merge branch 'master' of http://113.44.0.55:8014/jiaxiaoqiang/qingban-SPC

lupeng 4 dní pred
rodič
commit
82bd487fc0

+ 76 - 0
src/api/statistic/consistencyInspection.ts

@@ -0,0 +1,76 @@
+import request from "@/utils/request";
+
+export function getFirstLevel() {
+  return request({
+    url: "/api/v1/quality/inspect/opts/get/firstLevel",
+    method: "get",
+  });
+}
+
+export function getSecondLevel(firstLevelId) {
+  return request({
+    url: `/api/v1/quality/inspect/opts/get/secondLevel/${firstLevelId}`,
+    method: "get",
+  });
+}
+
+export function getById(id) {
+  return request({
+    url: `/api/v1/quality/inspect/opts/get/${id}`,
+    method: "get",
+  });
+}
+
+export function getAllOpts() {
+  return request({
+    url: "/api/v1/quality/inspect/opts/list/all",
+    method: "get",
+  });
+}
+
+export function addBatch(data) {
+  return request({
+    url: "/api/v1/quality/batch/items/add",
+    method: "post",
+    data: data,
+  });
+}
+
+export function getBatchPage(data) {
+  return request({
+    url: `/api/v1/quality/batch/items/page`,
+    method: "post",
+    data: data,
+  });
+}
+
+export function delSingleBatch(id) {
+  return request({
+    url: `/api/v1/quality/batch/items/delete/${id}`,
+    method: "post",
+  });
+}
+
+export function getBatchOptionPage(data) {
+  return request({
+    url: `/api/v1/quality/batch/items/options/page`,
+    method: "post",
+    data: data,
+  });
+}
+
+export function updateStatus(data) {
+  return request({
+    url: `/api/v1/quality/batch/items/update`,
+    method: "post",
+    data: data,
+  });
+}
+
+export function deleteBatchRow(data) {
+  return request({
+    url: `/api/v1/quality/batch/items/del`,
+    method: "post",
+    data: data,
+  });
+}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 9 - 0
src/assets/icons/pdm-logo.svg


+ 27 - 7
src/components/CrudTable/configs/tableConfig.ts

@@ -2,7 +2,6 @@ import { useDictionaryStore } from "@/store";
 const { dicts } = useDictionaryStore();
 import dictDataUtil from "@/common/configs/dictDataUtil";
 export const tableConfig = {
-
   WORK_ORDER: {
     url: "/api/v1/quality/stat/workOrderInfo",
     column: [
@@ -39,12 +38,33 @@ export const tableConfig = {
         prop: "created",
         width: 120,
       },
-
     ],
   },
-
-
-
-
-
+  BASE_MATERIAL: {
+    url: "/api/v1/material/info/getMaterialInfo",
+    column: [
+      {
+        label: "产品编码",
+        prop: "materialCode",
+        width: 120,
+        search: true,
+      },
+      {
+        label: "产品名称",
+        prop: "materialName",
+        width: 120,
+        search: true,
+      },
+      {
+        label: "产品规格",
+        prop: "spec",
+        search: true,
+      },
+      {
+        label: "创建时间",
+        prop: "created",
+        width: 120,
+      },
+    ],
+  },
 };

+ 13 - 0
src/hooks/userCrud.ts

@@ -404,6 +404,19 @@ export const useCrud = (config?: UseCrudConfig) => {
     },
 
     /**
+     * 下载模版 传入url
+     */
+    downloadSPCTemplate: async (urlStr: string, filePath: string) => {
+      const response = await request({
+        url: urlStr,
+        method: "get",
+        params: {filePath},
+        responseType: "arraybuffer",
+      });
+      Utils.downloadFile(response);
+    },
+
+    /**
      * 根据搜索项导出数据
      */
     exportData: async (urlStr: string) => {

+ 1 - 1
src/layout/components/Sidebar/components/SidebarMixTopMenu.vue

@@ -8,7 +8,7 @@
       :text-color="variables['menu-text']"
       :active-text-color="variables['menu-active-text']"
       @select="handleMenuSelect"
-      style="display: flex; justify-content: center"
+      style="display: flex; justify-content: center; width: 80%;"
     >
       <el-menu-item
         v-for="route in mixTopMenus"

+ 6 - 0
src/layout/components/Sidebar/index.vue

@@ -3,6 +3,12 @@
     <!--混合布局-->
     <div class="flex w-full" v-if="layout == 'mix'">
       <!-- <SidebarLogo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" /> -->
+      <svg-icon
+        icon-class="pdm-logo"
+        size="52"
+        style="height: 52px; width: 95px"
+      />
+      <h2 style="margin: 10px; color: #e6e6e6">质量分析系统</h2>
       <SidebarMixTopMenu class="flex-1" />
       <NavbarRight style="position: absolute; right: 0; top: 0" />
     </div>

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

@@ -73,5 +73,15 @@ export default {
         icon: "Guide",
       },
     },
+    {
+      path: "consistencyInspection",
+      component: () =>
+        import("@/views/statistic/consistencyInspection/index.vue"),
+      name: "consistencyInspection",
+      meta: {
+        title: "质量一致性检验",
+        icon: "Guide",
+      },
+    },
   ],
 };

+ 283 - 0
src/views/statistic/consistencyInspection/BatchItemDialog.vue

@@ -0,0 +1,283 @@
+<template>
+  <el-dialog
+    v-model="batchsVisible"
+    :title="`${productCode} - 批号管理`"
+    size="900px"
+    :append-to-body="true"
+    @closed="batchsVisible = false"
+  >
+    <div class="batch-input-container">
+      <el-input
+        v-model="newBatchCode"
+        placeholder="输入批号"
+        class="batch-input"
+      />
+      <el-button type="primary" @click="addBatch" class="batch-add-btn">
+        新增
+      </el-button>
+    </div>
+
+    <el-divider class="batch-divider" />
+
+    <el-table :data="data" style="width: 100%" class="batch-table">
+      <el-table-column prop="batchCode" label="产品批号" align="center" />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="danger"
+            size="small"
+            @click="handleDelete(scope.row)"
+          >
+            删除
+          </el-button>
+          <el-button
+            link
+            type="primary"
+            size="small"
+            @click="handleProgress(scope.row)"
+          >
+            项目进度
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页组件 -->
+    <el-pagination
+      v-model:current-page="page.currentPage"
+      v-model:page-size="page.pageSize"
+      :total="page.total"
+      layout="total, sizes, prev, pager, next, jumper"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </el-dialog>
+  <el-drawer
+    v-model="batchsDrawerVisible"
+    :title="`${currentBatchCode} - 检验进度`"
+    size="900px"
+    direction="rtl"
+    :append-to-body="true"
+    @closed="batchsDrawerVisible = false"
+  >
+    <!-- <el-button type="primary" style="width: 75px"> 批量完成</el-button> -->
+    <el-table :data="drawerData" style="width: 100%" class="batch-table">
+      <!-- <el-table-column type="selection" width="55" /> -->
+      <el-table-column prop="name" label="检验项目" align="center" />
+      <el-table-column prop="duration" label="周期(小时)" align="center" />
+      <el-table-column prop="status" label="状态" align="center">
+        <template #default="scope">
+          <el-tag :type="scope.row.status == '1' ? 'success' : 'info'">
+            {{ scope.row.status == "1" ? "已完成" : "未完成" }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            v-if="scope.row.status == 0"
+            link
+            type="primary"
+            size="small"
+            @click="handleFinishOne(scope.row)"
+          >
+            完成
+          </el-button>
+          <el-button
+            v-if="scope.row.status == 1"
+            link
+            type="danger"
+            size="small"
+            @click="handleCancelOne(scope.row)"
+          >
+            取消
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      v-model:current-page="drawerPage.currentPage"
+      v-model:page-size="drawerPage.pageSize"
+      :total="drawerPage.total"
+      layout="total, sizes, prev, pager, next, jumper"
+      @size-change="handleDrawerSizeChange"
+      @current-change="handleDrawerCurrentChange"
+      style="margin-top: 20px; padding: 10px"
+    />
+  </el-drawer>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import * as Api from "@/api/statistic/consistencyInspection";
+
+const batchsVisible = ref(false);
+const batchsDrawerVisible = ref(false);
+const newBatchCode = ref("");
+const data = ref([]); // 表格数据
+const productId = ref(null);
+const productCode = ref("");
+const drawerData = ref([]);
+const currentBatchCode = ref(""); // 当前批号
+
+// 分页配置
+const page = ref({
+  currentPage: 1,
+  pageSize: 10,
+  total: 0,
+});
+const drawerPage = ref({
+  currentPage: 1,
+  pageSize: 10,
+  total: 0,
+});
+
+const fetchDrawerData = (params = {}) => {
+  const query = {
+    pageNum: drawerPage.value.currentPage,
+    pageSize: drawerPage.value.pageSize,
+    batchCode: currentBatchCode.value,
+    productId: productId.value,
+    ...params,
+  };
+
+  Api.getBatchOptionPage(query).then((res) => {
+    drawerData.value = res.data.records || [];
+    drawerPage.value.total = res.data.totalCount || 0;
+  });
+};
+
+// 获取数据
+const fetchData = (params = {}) => {
+  const query = {
+    pageNum: page.value.currentPage,
+    pageSize: page.value.pageSize,
+    productId: productId.value,
+    ...params,
+  };
+
+  Api.getBatchPage(query).then((res) => {
+    data.value = res.data.records || [];
+    page.value.total = res.data.totalCount || 0;
+  });
+};
+
+const openBatchs = (row) => {
+  productId.value = row.id;
+  productCode.value = row.productCode;
+  fetchData();
+  batchsVisible.value = true;
+};
+
+const addBatch = () => {
+  if (!newBatchCode.value) {
+    return ElMessage.error("批号不能为空");
+  }
+
+  const newBatch = {
+    batchCode: newBatchCode.value,
+    productId: productId.value,
+  };
+
+  Api.addBatch(newBatch).then(() => {
+    newBatchCode.value = "";
+    ElMessage.success("批号添加成功");
+    fetchData();
+  });
+};
+
+// 删除操作
+const handleDelete = (row) => {
+  ElMessageBox.confirm("确认删除此批号吗?", "提示", {
+    type: "warning",
+  }).then(() => {
+    Api.deleteBatchRow({
+      batchCode: row.batchCode,
+      productId: productId.value,
+    }).then(() => {
+      ElMessage.success("删除成功");
+      fetchData();
+    });
+  });
+};
+
+// 项目进度操作
+const handleProgress = (row) => {
+  currentBatchCode.value = row.batchCode;
+  fetchDrawerData();
+  batchsDrawerVisible.value = true;
+};
+
+// 分页事件
+const handleSizeChange = (val) => {
+  page.value.pageSize = val;
+  fetchData();
+};
+
+const handleCurrentChange = (val) => {
+  page.value.currentPage = val;
+  fetchData();
+};
+
+const handleDrawerSizeChange = (val) => {
+  drawerPage.value.pageSize = val;
+  fetchDrawerData();
+};
+
+const handleDrawerCurrentChange = (val) => {
+  drawerPage.value.currentPage = val;
+  fetchDrawerData();
+};
+
+const handleFinishOne = (row) => {
+  Api.updateStatus({
+    id: row.id,
+    status: 1,
+  }).then(() => {
+    ElMessage.success("检验项目已完成");
+    fetchDrawerData();
+  });
+};
+
+const handleCancelOne = (row) => {
+  Api.updateStatus({
+    id: row.id,
+    status: 0,
+  }).then(() => {
+    ElMessage.success("检验项目已取消");
+    fetchDrawerData();
+  });
+};
+
+defineExpose({
+  openBatchs,
+});
+</script>
+
+<style scoped>
+.batch-input-container {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+  margin-top: 10px;
+}
+
+.batch-input {
+  width: 300px;
+  margin-right: 10px;
+}
+
+.batch-divider {
+  margin: 10px 0;
+}
+
+.batch-table {
+  margin-top: 10px;
+}
+
+.el-pagination {
+  margin-top: 20px;
+  justify-content: flex-end;
+}
+</style>

+ 120 - 0
src/views/statistic/consistencyInspection/InspectItemDialog.vue

@@ -0,0 +1,120 @@
+<template>
+  <el-drawer
+    v-model="drawerVisible"
+    title="选择检验项目"
+    size="900px"
+    direction="rtl"
+    :append-to-body="true"
+    @closed="handleClose"
+  >
+    <el-table
+      ref="tableRef"
+      :data="inspectionItems"
+      style="width: 100%"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" />
+      <el-table-column prop="name" label="检验项目" />
+      <el-table-column label="周期(小时)">
+        <template #default="{ row }">
+          <el-input-number
+            v-model="row.duration"
+            min="0"
+            controls-position="right"
+          />
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <template #footer>
+      <div class="drawer-footer">
+        <el-button @click="drawerVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleConfirm">确定</el-button>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import * as Api from "@/api/statistic/consistencyInspection";
+
+const drawerVisible = ref(false);
+const inspectionItems = ref([]);
+const selectedItems = ref([]);
+const tableRef = ref(null);
+const currentInspectType = ref(null);
+
+// 打开侧边栏
+const openDrawer = (inspectTypeId, selectedIds = [], durations = []) => {
+  currentInspectType.value = inspectTypeId;
+  drawerVisible.value = true;
+
+  loadInspectionItems(inspectTypeId).then(() => {
+    nextTick(() => {
+      inspectionItems.value.forEach((item) => {
+        const index = selectedIds.indexOf(item.id);
+        if (index !== -1) {
+          tableRef.value?.toggleRowSelection(item, true);
+          item.duration = durations[index] || 0;
+        }
+      });
+    });
+  });
+};
+// 加载检验项目(保持不变)
+const loadInspectionItems = async (inspectTypeId) => {
+  try {
+    const response = await Api.getSecondLevel(inspectTypeId);
+    inspectionItems.value = response.data;
+  } catch (error) {
+    console.error("加载检验项目失败:", error);
+  }
+};
+
+// 处理选择变化(保持不变)
+const handleSelectionChange = (selection) => {
+  selectedItems.value = selection;
+};
+
+// 确认选择(保持不变)
+const handleConfirm = () => {
+  // 检查是否有未填写duration的选中项
+  const invalidItems = selectedItems.value.filter(
+    (item) =>
+      item.duration === undefined ||
+      item.duration === null ||
+      item.duration === ""
+  );
+
+  if (invalidItems.length > 0) {
+    // 显示错误提示
+    ElMessage.error(
+      `请为 ${invalidItems.map((i) => i.name).join("、")} 填写周期`
+    );
+    return;
+  }
+  emit("confirm", selectedItems.value);
+  drawerVisible.value = false;
+};
+
+// 关闭侧边栏(保持不变)
+const handleClose = () => {
+  tableRef.value?.clearSelection();
+  selectedItems.value = [];
+};
+
+const emit = defineEmits(["confirm"]);
+
+defineExpose({
+  openDrawer: openDrawer, // 修改方法名
+});
+</script>
+
+<style scoped>
+.drawer-footer {
+  display: flex;
+  justify-content: flex-end;
+  padding: 16px;
+}
+</style>

+ 234 - 0
src/views/statistic/consistencyInspection/index.vue

@@ -0,0 +1,234 @@
+<template>
+  <div class="mainContentBox">
+    <avue-crud
+      ref="crudRef"
+      v-model:search="search"
+      v-model="form"
+      :data="data"
+      :option="option"
+      v-model:page="page"
+      @row-save="createRow"
+      @row-update="updateRow"
+      @row-del="deleteRow"
+      @search-change="searchChange"
+      @search-reset="resetChange"
+      @size-change="dataList"
+      @current-change="dataList"
+      @selection-change="selectionChange"
+    >
+      <template #menu="{ row, index, type }">
+        <el-button link type="primary" size="small" @click="openBatchs(row)"
+          >生成批号</el-button
+        ></template
+      >
+      <!--      <template #menu-right="{}">-->
+      <!--        <el-button-->
+      <!--          class="ml-3"-->
+      <!--          @click="exportData('/api/v1/quality/report/export')"-->
+      <!--        >-->
+      <!--          <template #icon> <i-ep-download /> </template>导出-->
+      <!--        </el-button>-->
+      <!--      </template>-->
+    </avue-crud>
+    <InspectItemDialog
+      ref="inspectItemRef"
+      @confirm="handleInspectItemsConfirm"
+    />
+    <CrudTable
+      ref="ctableRef"
+      tableTitle="选择统计产品"
+      tableType="BASE_MATERIAL"
+      @selected-sure="handleConfirm"
+    />
+    <BatchItemDialog ref="batchItemRef" />
+  </div>
+</template>
+
+<script setup>
+import { useCrud } from "@/hooks/userCrud";
+import dictDataUtil from "@/common/configs/dictDataUtil";
+import * as Api from "@/api/statistic/consistencyInspection";
+import InspectItemDialog from "./InspectItemDialog.vue";
+import BatchItemDialog from "./BatchItemDialog.vue";
+
+// 传入一个url,后面不带/
+const { form, data, option, search, page, Methords, Utils } = useCrud({
+  src: "/api/v1/quality/prod/inspect",
+});
+const { dataList, createRow, updateRow, deleteRow, searchChange, resetChange } =
+  Methords; //增删改查
+const { selectionChange, multipleDelete } = Methords; //选中和批量删除事件
+const { checkBtnPerm, downloadTemplate, exportData } = Utils; //按钮权限等工具
+const crudRef = ref(null); //crudRef.value 获取avue-crud对象
+
+onMounted(async () => {
+  // 初始化菜单
+  await fetchFirstLevel();
+  await fetchAllOpts();
+  dataList();
+});
+
+const ctableRef = ref(null);
+const inspectItemRef = ref(null); // 检验项目弹窗
+
+//一二级菜单
+const firstLevel = ref([]);
+const secondLevel = ref([]);
+// 所有检验项目选项
+const allOpts = ref([]);
+const batchItemRef = ref(null); // 批号弹窗
+const isTypeChange = ref(false);
+
+// 设置表格列或者其他自定义的option
+option.value = Object.assign(option.value, {
+  searchEnter: true,
+  viewBtn: false,
+  column: [
+    {
+      label: "产品编码",
+      prop: "productCode",
+      type: "input",
+      search: "true",
+      rules: [
+        {
+          required: true,
+          message: "请选择产品编码",
+          trigger: "blur",
+        },
+      ],
+      click: ({ value, column }) => {
+        if (column.boxType) {
+          ctableRef.value && ctableRef.value.startSelect();
+        }
+      },
+    },
+    {
+      label: "产品名称",
+      prop: "productName",
+      type: "input",
+      disabled: true,
+      rules: [
+        {
+          required: true,
+          message: "请选择产品名称",
+          trigger: "blur",
+        },
+      ],
+    },
+    {
+      label: "产品型号",
+      prop: "productModel",
+      type: "input",
+      search: "true",
+      disabled: true,
+      rules: [
+        {
+          required: true,
+          message: "请填写产品型号",
+          trigger: "blur",
+        },
+      ],
+    },
+    {
+      label: "检验类型",
+      prop: "inspectType",
+      type: "select",
+      dicData: firstLevel,
+      rules: [
+        {
+          required: true,
+          message: "请选择检验类型",
+          trigger: "blur",
+        },
+      ],
+      click: () => {
+        isTypeChange.value = true;
+      },
+      change: () => {
+        if (isTypeChange.value) {
+          form.value.inspectItems = [];
+        }
+        isTypeChange.value = false;
+      },
+    },
+    {
+      label: "检验项目",
+      prop: "inspectItems",
+      type: "select",
+      dicData: allOpts,
+      multiple: true,
+      disabled: computed(() => !form.value.inspectType),
+      rules: [
+        {
+          required: true,
+          message: "请选择检验项目",
+          trigger: "blur",
+        },
+      ],
+      click: () => {
+        if (form.value.inspectType) {
+          inspectItemRef.value.openDrawer(
+            form.value.inspectType,
+            form.value.inspectItems || [],
+            form.value.durations || []
+          );
+        } else {
+          ElMessage.error("请先选择检验类型");
+        }
+      },
+    },
+    {
+      label: "周期",
+      prop: "durations",
+      hide: true,
+      addDisplay: false,
+      editDisplay: false,
+      valueFormat: "array",
+      display: false,
+    },
+    {
+      label: "备注",
+      prop: "remark",
+      type: "textarea",
+      span: 24,
+    },
+  ],
+});
+
+const handleConfirm = (row) => {
+  if (row) {
+    form.value.productCode = row.materialCode;
+    form.value.productName = row.materialName;
+    form.value.productModel = row.spec;
+  }
+};
+
+// 获取检验类型
+const fetchFirstLevel = async () => {
+  const response = await Api.getFirstLevel();
+  firstLevel.value = response.data.map((item) => ({
+    label: item.name,
+    value: item.id,
+  }));
+};
+
+// 获取所有检验项目
+const fetchAllOpts = async () => {
+  const response = await Api.getAllOpts();
+  allOpts.value = response.data.map((item) => ({
+    label: item.name,
+    value: item.id,
+  }));
+};
+
+// 处理检验项目选择
+const handleInspectItemsConfirm = (selectedItems) => {
+  form.value.inspectItems = selectedItems.map((item) => item.id);
+  form.value.durations = selectedItems.map((item) => item.duration);
+};
+
+// 创建批号
+const openBatchs = (row) => {
+  batchItemRef.value.openBatchs(row);
+};
+</script>