hsy
4 天以前 14079a56d94f176b27300043b737c764e9ada389
feat(quality): 新增质量管理-报告管理模块

- 数据库:新增 `qms_inspection_report` 质检报告表及配套 SQL 脚本。
- 后端:新增报告管理的实体类、Mapper、Service 及 Controller,实现列表查询、新增、修改与状态注销(软删除)接口,查询按到期时间正序排列。
- 前端:在报告分析下新增“报告管理”页面 (`inspectionReport`)。
- 采用 PIMTable 规范构建列表页,新增“到期前30天预警”的数据标红高亮功能。
- 新增表单弹窗,实现填写“报告日期”和“有效期(月)”后自动通过 dayjs 推算“到期时间”的联动逻辑。
- 接入标准多文件上传组件,规范化核心资产报告的管理流程。
已添加3个文件
462 ■■■■■ 文件已修改
src/api/reportAnalysis/inspectionReport.js 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/inspectionReport/components/formDia.vue 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/inspectionReport/index.vue 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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'
  })
}
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>
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="请选择">
            <!-- ä¸´æ—¶å­—典数据,后续可改为从字典接口获取 -->
            <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>