From 14079a56d94f176b27300043b737c764e9ada389 Mon Sep 17 00:00:00 2001
From: hsy <1029150275@qq.com>
Date: 星期五, 28 八月 2026 15:57:20 +0800
Subject: [PATCH] feat(quality): 新增质量管理-报告管理模块

---
 src/views/reportAnalysis/inspectionReport/index.vue              |  235 +++++++++++++++++++++++++++++
 src/views/reportAnalysis/inspectionReport/components/formDia.vue |  183 ++++++++++++++++++++++
 src/api/reportAnalysis/inspectionReport.js                       |   44 +++++
 3 files changed, 462 insertions(+), 0 deletions(-)

diff --git a/src/api/reportAnalysis/inspectionReport.js b/src/api/reportAnalysis/inspectionReport.js
new file mode 100644
index 0000000..4c5cb90
--- /dev/null
+++ b/src/api/reportAnalysis/inspectionReport.js
@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鎶ュ憡鍒楄〃
+export function listInspectionReport(query) {
+  return request({
+    url: '/quality/inspectionReport/listPage',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ鎶ュ憡璇︾粏
+export function getInspectionReport(id) {
+  return request({
+    url: '/quality/inspectionReport/' + id,
+    method: 'get'
+  })
+}
+
+// 鏂板鎶ュ憡
+export function addInspectionReport(data) {
+  return request({
+    url: '/quality/inspectionReport',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼鎶ュ憡
+export function updateInspectionReport(data) {
+  return request({
+    url: '/quality/inspectionReport',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎鎶ュ憡
+export function delInspectionReport(id) {
+  return request({
+    url: '/quality/inspectionReport/' + id,
+    method: 'delete'
+  })
+}
diff --git a/src/views/reportAnalysis/inspectionReport/components/formDia.vue b/src/views/reportAnalysis/inspectionReport/components/formDia.vue
new file mode 100644
index 0000000..723c487
--- /dev/null
+++ b/src/views/reportAnalysis/inspectionReport/components/formDia.vue
@@ -0,0 +1,183 @@
+<template>
+  <el-dialog
+    :title="title"
+    v-model="visible"
+    width="600px"
+    append-to-body
+    :close-on-click-modal="false"
+  >
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+      <el-form-item label="浜у搧鍚嶇О" prop="productName">
+        <el-input v-model="form.productName" placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�" />
+      </el-form-item>
+      
+      <el-form-item label="浜у搧鍨嬪彿" prop="productModel">
+        <el-input v-model="form.productModel" placeholder="璇疯緭鍏ヤ骇鍝佸瀷鍙�" />
+      </el-form-item>
+      
+      <el-form-item label="鎶ュ憡缂栧彿" prop="reportNo">
+        <el-input v-model="form.reportNo" placeholder="璇疯緭鍏ユ姤鍛婄紪鍙�" />
+      </el-form-item>
+      
+      <el-form-item label="鎶ュ憡绫诲瀷" prop="reportType">
+        <el-select v-model="form.reportType" placeholder="璇烽�夋嫨鎶ュ憡绫诲瀷" style="width: 100%">
+          <el-option label="鍨嬪紡璇曢獙鎶ュ憡" value="鍨嬪紡璇曢獙鎶ュ憡" />
+          <el-option label="鍑哄巶妫�楠屾姤鍛�" value="鍑哄巶妫�楠屾姤鍛�" />
+          <el-option label="濮旀墭妫�楠屾姤鍛�" value="濮旀墭妫�楠屾姤鍛�" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="妫�娴嬪崟浣�" prop="testingAgency">
+        <el-input v-model="form.testingAgency" placeholder="璇疯緭鍏ユ娴嬪崟浣�" />
+      </el-form-item>
+      
+      <el-form-item label="鎶ュ憡鏃ユ湡" prop="reportDate">
+        <el-date-picker 
+          v-model="form.reportDate" 
+          type="date" 
+          value-format="YYYY-MM-DD"
+          placeholder="閫夋嫨鎶ュ憡鏃ユ湡" 
+          style="width: 100%"
+          @change="calculateExpiration"
+        />
+      </el-form-item>
+      
+      <el-form-item label="鏈夋晥鏈�(鏈�)" prop="validityPeriod">
+        <el-input-number 
+          v-model="form.validityPeriod" 
+          :min="1" 
+          :max="120"
+          controls-position="right"
+          style="width: 100%"
+          placeholder="璇疯緭鍏ユ湁鏁堟湡(鏈�)" 
+          @change="calculateExpiration"
+        />
+      </el-form-item>
+      
+      <el-form-item label="鍒版湡鏃堕棿" prop="expirationTime">
+        <el-date-picker 
+          v-model="form.expirationTime" 
+          type="date" 
+          value-format="YYYY-MM-DD"
+          disabled
+          placeholder="绯荤粺鑷姩璁$畻" 
+          style="width: 100%"
+        />
+      </el-form-item>
+
+      <el-form-item label="澶囨敞" prop="remark">
+        <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ娉ㄥ唴瀹�" />
+      </el-form-item>
+      
+      <el-form-item label="闄勪欢" prop="attachmentUrls">
+        <!-- 杩欓噷浣跨敤鏅�氱殑el-upload鍗犱綅锛屽疄闄呴」鐩腑鍙浛鎹负灏佽鐨� FileUpload -->
+        <FileUpload v-model="form.attachmentUrls" :limit="5" :fileSize="50" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+        <el-button @click="cancel">鍙� 娑�</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, getCurrentInstance } from "vue";
+import { addInspectionReport, updateInspectionReport, getInspectionReport } from "@/api/reportAnalysis/inspectionReport.js";
+import dayjs from "dayjs";
+
+const { proxy } = getCurrentInstance();
+const emits = defineEmits(["close"]);
+
+const visible = ref(false);
+const title = ref("");
+const formRef = ref(null);
+
+const data = reactive({
+  form: {},
+  rules: {
+    productName: [{ required: true, message: "浜у搧鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    reportNo: [{ required: true, message: "鎶ュ憡缂栧彿涓嶈兘涓虹┖", trigger: "blur" }],
+    reportDate: [{ required: true, message: "鎶ュ憡鏃ユ湡涓嶈兘涓虹┖", trigger: "change" }],
+    validityPeriod: [{ required: true, message: "鏈夋晥鏈熶笉鑳戒负绌�", trigger: "blur" }],
+    expirationTime: [{ required: true, message: "鍒版湡鏃堕棿涓嶈兘涓虹┖", trigger: "change" }],
+  }
+});
+
+const { form, rules } = toRefs(data);
+
+// 鑷姩璁$畻鍒版湡鏃堕棿
+const calculateExpiration = () => {
+  if (form.value.reportDate && form.value.validityPeriod) {
+    form.value.expirationTime = dayjs(form.value.reportDate)
+      .add(form.value.validityPeriod, 'month')
+      .format('YYYY-MM-DD');
+  } else {
+    form.value.expirationTime = null;
+  }
+};
+
+const openDialog = (type, row) => {
+  reset();
+  if (type === "add") {
+    title.value = "鏂板鎶ュ憡";
+    visible.value = true;
+  } else {
+    title.value = "淇敼鎶ュ憡";
+    getInspectionReport(row.id).then(response => {
+      form.value = response.data;
+      visible.value = true;
+    });
+  }
+};
+
+const reset = () => {
+  form.value = {
+    id: null,
+    productName: null,
+    productModel: null,
+    reportNo: null,
+    reportDate: null,
+    validityPeriod: 12, // 榛樿涓�骞�
+    expirationTime: null,
+    testingAgency: null,
+    reportType: null,
+    attachmentUrls: null,
+    remark: null
+  };
+  if (formRef.value) {
+    formRef.value.resetFields();
+  }
+};
+
+const submitForm = () => {
+  formRef.value.validate(valid => {
+    if (valid) {
+      if (form.value.id != null) {
+        updateInspectionReport(form.value).then(() => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          visible.value = false;
+          emits("close");
+        });
+      } else {
+        addInspectionReport(form.value).then(() => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          visible.value = false;
+          emits("close");
+        });
+      }
+    }
+  });
+};
+
+const cancel = () => {
+  visible.value = false;
+  reset();
+};
+
+defineExpose({
+  openDialog
+});
+</script>
diff --git a/src/views/reportAnalysis/inspectionReport/index.vue b/src/views/reportAnalysis/inspectionReport/index.vue
new file mode 100644
index 0000000..5ae2b13
--- /dev/null
+++ b/src/views/reportAnalysis/inspectionReport/index.vue
@@ -0,0 +1,235 @@
+<template>
+  <div class="app-container">
+    <div class="search_form">
+      <el-form :model="searchForm" inline style="margin-bottom: 0;">
+        <el-form-item label="浜у搧鍚嶇О锛�">
+          <el-input
+              v-model="searchForm.productName"
+              style="width: 200px"
+              placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+              @change="handleQuery"
+              clearable
+              :prefix-icon="Search"
+          />
+        </el-form-item>
+        <el-form-item label="鎶ュ憡缂栧彿锛�">
+          <el-input
+              v-model="searchForm.reportNo"
+              style="width: 200px"
+              placeholder="璇疯緭鍏ユ姤鍛婄紪鍙�"
+              @change="handleQuery"
+              clearable
+              :prefix-icon="Search"
+          />
+        </el-form-item>
+        <el-form-item label="鎶ュ憡绫诲瀷锛�">
+          <el-select v-model="searchForm.reportType" clearable style="width: 200px" @change="handleQuery" placeholder="璇烽�夋嫨">
+            <!-- 涓存椂瀛楀吀鏁版嵁锛屽悗缁彲鏀逛负浠庡瓧鍏告帴鍙h幏鍙� -->
+            <el-option label="鍨嬪紡璇曢獙鎶ュ憡" value="鍨嬪紡璇曢獙鎶ュ憡" />
+            <el-option label="鍑哄巶妫�楠屾姤鍛�" value="鍑哄巶妫�楠屾姤鍛�" />
+            <el-option label="濮旀墭妫�楠屾姤鍛�" value="濮旀墭妫�楠屾姤鍛�" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+          <el-button @click="resetQuery">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    
+    <div class="mb20" style="text-align: right;">
+      <el-button type="primary" @click="openForm('add')">鏂板</el-button>
+      <el-button type="danger" plain @click="handleDelete">娉ㄩ攢浣滃簾</el-button>
+    </div>
+    
+    <div class="table_list">
+      <PIMTable
+          rowKey="id"
+          :column="tableColumn"
+          :tableData="tableData"
+          :page="page"
+          :isSelection="true"
+          @selection-change="handleSelectionChange"
+          :tableLoading="tableLoading"
+          @pagination="pagination"
+          :total="page.total"
+      ></PIMTable>
+    </div>
+    
+    <FormDia ref="formDia" @close="handleQuery"></FormDia>
+  </div>
+</template>
+
+<script setup>
+import { Search } from "@element-plus/icons-vue";
+import { onMounted, ref, reactive, toRefs, nextTick, getCurrentInstance } from "vue";
+import FormDia from "./components/formDia.vue";
+import { ElMessageBox } from "element-plus";
+import { listInspectionReport, delInspectionReport } from "@/api/reportAnalysis/inspectionReport.js";
+import dayjs from "dayjs";
+
+const { proxy } = getCurrentInstance();
+
+const data = reactive({
+  searchForm: {
+    productName: "",
+    reportNo: "",
+    reportType: "",
+  },
+});
+const { searchForm } = toRefs(data);
+
+const tableColumn = ref([
+  {
+    label: "浜у搧鍚嶇О",
+    prop: "productName",
+  },
+  {
+    label: "浜у搧鍨嬪彿",
+    prop: "productModel",
+  },
+  {
+    label: "鎶ュ憡缂栧彿",
+    prop: "reportNo",
+  },
+  {
+    label: "鎶ュ憡鏃ユ湡",
+    prop: "reportDate",
+    width: 120
+  },
+  {
+    label: "鎶ュ憡绫诲瀷",
+    prop: "reportType",
+  },
+  {
+    label: "妫�娴嬪崟浣�",
+    prop: "testingAgency",
+  },
+  {
+    label: "鍒版湡鏃堕棿",
+    prop: "expirationTime",
+    dataType: "tag",
+    width: 120,
+    formatData: (val) => val, // 鍘熸牱杩斿洖鏃堕棿
+    formatType: (val) => {
+      // 棰勮閫昏緫锛氬皬浜庣瓑浜�30澶╁埌鏈燂紝杩斿洖 danger 鏍囩孩
+      if (!val) return 'info';
+      const days = dayjs(val).diff(dayjs(), 'day');
+      if (days <= 30 && days >= 0) {
+        return "danger";
+      } else if (days < 0) {
+        return "danger"; // 宸茶繃鏈�
+      }
+      return "success";
+    }
+  },
+  {
+    dataType: "action",
+    label: "鎿嶄綔",
+    align: "center",
+    fixed: "right",
+    width: 120,
+    operation: [
+      {
+        name: "缂栬緫",
+        type: "text",
+        clickFun: (row) => {
+          openForm("edit", row);
+        }
+      }
+    ]
+  }
+]);
+
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+  current: 1,
+  size: 10,
+  total: 0
+});
+const formDia = ref();
+
+// 鏌ヨ鍒楄〃
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const resetQuery = () => {
+  searchForm.value = {
+    productName: "",
+    reportNo: "",
+    reportType: "",
+  };
+  handleQuery();
+};
+
+const pagination = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  const params = { ...searchForm.value, current: page.current, size: page.size };
+  
+  listInspectionReport(params).then(res => {
+    tableLoading.value = false;
+    // PIMTable expected formats usually res.data.records or similar, if it uses IPage
+    if(res.data && res.data.records) {
+        tableData.value = res.data.records;
+        page.total = res.data.total;
+    } else {
+        // Fallback standard structure
+        tableData.value = res.data;
+        page.total = res.total || 0;
+    }
+  }).catch(err => {
+    tableLoading.value = false;
+  });
+};
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection;
+};
+
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+  nextTick(() => {
+    formDia.value?.openDialog(type, row);
+  });
+};
+
+// 鍒犻櫎(娉ㄩ攢浣滃簾)
+const handleDelete = () => {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("璇烽�夋嫨瑕佹敞閿�鐨勬暟鎹�");
+    return;
+  }
+  let ids = selectedRows.value.map((item) => item.id);
+  
+  ElMessageBox.confirm("閫変腑鐨勬姤鍛婂皢琚敞閿�浣滃簾锛屾槸鍚︾‘璁わ紵", "璀﹀憡", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      delInspectionReport(ids.join(',')).then(() => {
+        proxy.$modal.msgSuccess("娉ㄩ攢鎴愬姛");
+        getList();
+      });
+    })
+    .catch(() => {});
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped>
+</style>

--
Gitblit v1.9.3