feat(order): 优化订单管理功能并增加设备绑定支持
- 修改生产日期字段支持清除和显示默认值
- 在标准库中增加设备绑定功能,支持模板与设备关联
- 新增价格字段用于检验项目定价管理
- 增强模板绑定界面,支持多选和设备分配操作
已修改12个文件
171 ■■■■■ 文件已修改
.env.development 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/standard/standardLibrary.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Onlyoffice/onlyoffice.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/costStatistics/components/viewInfoDia.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/materialOrderComponents/materialOrder/printDialog.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/productOrder/components/addOrder.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/productOrder/components/addView.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/productOrder/components/printDialog.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/standard/model/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/standard/standardLibrary/index.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -7,8 +7,8 @@
# LIMS实验室管理系统/开发环境
VUE_APP_BASE_API = '/dev-api'
# 本地环境ip+端口
VUE_APP_BASE_URL = 'http://127.0.0.1:8001/lims'
# 文件访问与普通接口统一走本地开发代理
VUE_APP_BASE_URL = '/dev-api'
# OnlyOffice 文档服务地址(预览 pdf 用,没有 http:// 前缀会当成相对路径)
VUE_APP_ONLYOFFICE_API = 'http://192.168.21.53:9001'
src/api/standard/standardLibrary.js
@@ -140,6 +140,15 @@
  return request({ url: "/standardTree/saveTemplateBindings", method: "post", data });
}
// 查询可绑定到原始记录模板的设备
export function searchDevices(query) {
  return request({
    url: "/deviceScope/search",
    method: "get",
    params: query,
  });
}
// 获取标准方法枚举
export function selectStandardMethods(query) {
  return request({
src/components/Onlyoffice/onlyoffice.vue
@@ -1,6 +1,15 @@
<!--onlyoffice 编辑器-->
<template>
  <div id="vabOnlyOffice"></div>
  <div class="document-preview">
    <iframe
      v-if="isPdf"
      :key="option.url"
      :src="option.url"
      class="native-pdf-preview"
      title="PDF预览"
    ></iframe>
    <div v-else id="vabOnlyOffice"></div>
  </div>
</template>
<script>
@@ -55,6 +64,11 @@
      }
    };
  },
  computed: {
    isPdf() {
      return this.isPdfFile(this.option);
    }
  },
  created() {
    if(this.options){
      const option = this.options
@@ -106,10 +120,12 @@
    },
    async setEditor(option) {
      this.destroyEditor();
      // 没有文档地址就不初始化编辑器,避免访问尚未加载的 DocsAPI
      if (!option || !option.url) {
      // PDF 使用浏览器原生预览;其他文档才初始化 OnlyOffice。
      if (!option || !option.url || this.isPdfFile(option)) {
        return;
      }
      // 等待 v-else 中的 OnlyOffice 容器完成渲染。
      await this.$nextTick();
      this.doctype = this.getFileType(option.fileType);
      let config = {
        document: {
@@ -162,6 +178,14 @@
        return;
      }
      this.docEditor = new window.DocsAPI.DocEditor("vabOnlyOffice", config);
    },
    isPdfFile(option) {
      if (!option) {
        return false;
      }
      const fileType = String(option.fileType || "").toLowerCase();
      const url = String(option.url || "").split(/[?#]/)[0].toLowerCase();
      return fileType === "pdf" || url.endsWith(".pdf");
    },
    getFileType(fileType) {
      let docType = "";
@@ -227,6 +251,16 @@
</script>
<style scoped>
.document-preview,
.native-pdf-preview,
#vabOnlyOffice {
  width: 100%;
  height: 100%;
}
.native-pdf-preview {
  display: block;
  border: 0;
}
html,
body {
  height: 100%;
src/main.js
@@ -65,10 +65,10 @@
Vue.prototype.HaveJson = (val) => {
  return JSON.parse(JSON.stringify(val));
};
// Vue.prototype.javaApi = process.env.NODE_ENV === "production"?"http://192.168.21.53:8001/lims":"http://127.0.0.1:8001/lims";
// Vue.prototype.javaApi = process.env.VUE_APP_BASE_URL && process.env.NODE_ENV==='production'?process.env.VUE_APP_BASE_URL : "http://127.0.0.1:8001/lims";
Vue.prototype.javaApi =
  process.env.VUE_APP_BASE_URL || process.env.VUE_APP_BASE_API;
const apiBase = process.env.VUE_APP_BASE_URL || process.env.VUE_APP_BASE_API;
Vue.prototype.javaApi = /^https?:\/\//i.test(apiBase)
  ? apiBase.replace(/\/$/, "")
  : window.location.origin + "/" + apiBase.replace(/^\/+|\/+$/g, "");
Vue.prototype.checkPermi = checkPermi;
Vue.prototype.uploadHeader = {
  Authorization: "Bearer " + getToken(),
src/views/business/costStatistics/components/viewInfoDia.vue
@@ -20,12 +20,9 @@
    return {
      tableData: [],
      column: [
        { label: '委托编号', prop: 'entrustCode', width: 160 },
        {label: '检验项分类', prop: 'inspectionItemClass', width: 160},
        {label: '检验项', prop: 'inspectionItem', width: 160},
        {label: '检验子项', prop: 'inspectionItemSubclass', width: 160},
        { label: '收费标准(元/次)', prop: 'rates' },
        { label: '分组系数', prop: 'manHourGroup' },
        { label: '检验项目', prop: 'number', width: 160 },
        { label: '模板名称', prop: 'name', width: 200 },
        { label: '价格(元)', prop: 'price' },
      ],
      viewInfoDia: false,
      tableLoading: false,
src/views/business/materialOrderComponents/materialOrder/printDialog.vue
@@ -30,7 +30,7 @@
                    <span class="info">{{ item.partDesc }}</span>
                  </div>
                  <div class="item">
                    <span class="full-title">委托日期</span>:
                    <span class="full-title">送样日期</span>:
                    <span class="info">{{ item.sendTime }}</span>
                  </div>
                  <div class="item">
@@ -93,7 +93,7 @@
                <span class="info4">{{ item.partDesc }}</span>
              </div>
              <div class="item">
                <span class="full-title2">委托日期</span>:
                <span class="full-title2">送样日期</span>:
                <span class="info">{{ item.sendTime }}</span>
              </div>
              <div class="item">
src/views/business/productOrder/components/addOrder.vue
@@ -126,7 +126,7 @@
            <el-col :span="6">
              <el-form-item label="生产日期">
                <el-date-picker v-model="addObj.productionDate" type="date" value-format="yyyy-MM-dd"
                                :disabled="active>1" size="small"/>
                                :disabled="active>1" clearable placeholder="不选择时报告显示/" size="small"/>
              </el-form-item>
            </el-col>
            <el-col :span="6">
@@ -853,6 +853,8 @@
        quarterItemId: '',
        sampleView: '', // 样品名称(报告展示字段)
        sampleViewEn: '', // 样品名称英文(报告展示字段)
        productionDate: null,
        sampleDate: null,
      },
      addObjRules: { // 表单校验规则
        orderType: [
@@ -1567,9 +1569,15 @@
    },
    saveData(sampleList) {
      this.saveLoad = true
      const insOrder = {
        ...this.addObj,
        productionDate: this.addObj.productionDate === '/' || !this.addObj.productionDate
          ? null : this.addObj.productionDate,
        typeSource: -1,
      }
      if (this.tabIndex == 4 && this.active == 2) {
        // 退回后提交
        updateInsOrder({insOrder: this.addObj, sampleProduct: sampleList}).then(res => {
        updateInsOrder({insOrder, sampleProduct: sampleList}).then(res => {
          this.saveLoad = false
          this.$message.success('已提交')
          this.bsm3Dia = false;
@@ -1579,7 +1587,7 @@
        })
      } else {
        // 常规提交
        const newJuOrder = {...this.addObj, typeSource: -1}
        const newJuOrder = {...insOrder}
        delete newJuOrder.temperature
        delete newJuOrder.humidity
        const payload = this.addObj.standardTreeId
@@ -1589,7 +1597,7 @@
            sampleCode: this.sampleCode,
            templateList: this.boundTemplateList.map((item, index) => ({templateId: item.templateId, sort: index}))
          }
          : {insOrder: {...this.addObj, typeSource: -1}, sampleList: sampleList}
          : {insOrder, sampleList: sampleList}
        addInsOrder(payload).then(res => {
          this.saveLoad = false
          this.$message.success('已提交')
src/views/business/productOrder/components/addView.vue
@@ -57,7 +57,7 @@
          <el-descriptions-item label="样品编号">{{ sampleCode || '-' }}</el-descriptions-item>
          <el-descriptions-item label="样品外观">{{ addObj.sampleStatus || '-' }}</el-descriptions-item>
          <el-descriptions-item label="送样人">{{ addObj.prepareUser || '-' }}</el-descriptions-item>
          <el-descriptions-item label="生产日期">{{ addObj.productionDate || '-' }}</el-descriptions-item>
          <el-descriptions-item label="生产日期">{{ addObj.productionDate || '/' }}</el-descriptions-item>
          <el-descriptions-item label="生产批号">{{ addObj.productionBatch || '-' }}</el-descriptions-item>
          <el-descriptions-item label="送样日期">{{ addObj.sampleDate || '-' }}</el-descriptions-item>
          <el-descriptions-item label="联系方式">{{ addObj.phone || '-' }}</el-descriptions-item>
src/views/business/productOrder/components/printDialog.vue
@@ -30,7 +30,7 @@
                    <span class="info">{{ item.sampleModel }}</span>
                  </div>
                  <div class="item">
                    <span class="full-title">委托日期</span>:
                    <span class="full-title">送样日期</span>:
                    <span class="info">{{ item.sendTime }}</span>
                  </div>
                  <div class="item">
@@ -91,7 +91,7 @@
                <span class="info4">{{ item.sampleModel }}</span>
              </div>
              <div class="item">
                <span class="full-title2">委托日期</span>:
                <span class="full-title2">送样日期</span>:
                <span class="info">{{ item.sendTime }}</span>
              </div>
              <div class="item">
src/views/standard/model/index.vue
@@ -27,7 +27,7 @@
    <el-dialog :before-close="closeCopyTem" :close-on-click-modal="false" :close-on-press-escape="false"
      :visible.sync="isShowCopyTem" :title="title" width="35%" :modal-append-to-body="false">
      <el-form ref="copyForm" :model="copyForm" :rules="copyFormRules" label-position="right" label-width="80px">
        <el-form-item label="模版编号" prop="number">
        <el-form-item label="检验项目" prop="number">
          <el-input v-model="copyForm.number" clearable size="small"></el-input>
        </el-form-item>
        <el-form-item label="模版名称" prop="name">
@@ -35,6 +35,9 @@
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="copyForm.remark" clearable size="small"></el-input>
        </el-form-item>
        <el-form-item label="价格(元)" prop="price">
          <el-input v-model="copyForm.price" clearable size="small"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
@@ -79,6 +82,7 @@
        number: "",
        name: "",
        remark: "",
        price: "",
        id: "",
      },
      copyFormRules: {
@@ -89,9 +93,10 @@
      },
      tableData: [],
      column: [
        { label: "模板编号", prop: "number" },
        { label: "检验项目", prop: "number" },
        { label: "模板名称", prop: "name" },
        { label: "备注", prop: "remark" },
        { label: "价格(元)", prop: "price" },
        { label: "创建用户", prop: "createUserName" },
        { label: "创建时间", prop: "createTime", width: "160" },
        { label: "更新用户", prop: "updateUserName" },
@@ -216,6 +221,7 @@
            name: this.copyForm.name,
            number: this.copyForm.number,
            remark: this.copyForm.remark,
            price: this.copyForm.price === "" || this.copyForm.price == null ? null : this.copyForm.price,
          };
          switch (this.title) {
            case "新增":
@@ -225,7 +231,7 @@
                  this.isShowCopyTem = false;
                  this.submitCopyInfoLoading = false;
                  this.$message.success("新增成功");
                  this.refreshTable("page");
                  this.getList();
                })
                .catch((err) => {
                  console.log("copyTemplate----", err);
src/views/standard/standardLibrary/index.vue
@@ -48,9 +48,21 @@
            <p v-if="!templateBindings.length" class="binding-tip">请为当前标准绑定原始记录模板。</p>
            <el-table :data="templateBindings" border size="small">
              <el-table-column type="index" label="顺序" width="70" /><el-table-column label="模板编号" prop="number" width="180" /><el-table-column label="模板名称" prop="name" /><el-table-column label="检测依据(模板备注)" prop="remark" />
              <el-table-column label="设备绑定" min-width="360">
                <template slot-scope="scope">
                  <el-select v-model="scope.row.deviceIds"
                             :disabled="!checkPermi(['standard:standardLibrary:upStandardProduct'])"
                             multiple filterable clearable size="small"
                             placeholder="请选择该模板的默认设备" style="width:100%">
                    <el-option v-for="device in deviceOptions" :key="device.id"
                               :label="device.label" :value="device.id" />
                  </el-select>
                </template>
              </el-table-column>
              <el-table-column v-if="checkPermi(['standard:standardLibrary:upStandardProduct'])" label="操作" width="150"><template slot-scope="scope"><el-button type="text" :disabled="scope.$index === 0" @click="moveBinding(scope.$index, -1)">上移</el-button><el-button type="text" :disabled="scope.$index === templateBindings.length - 1" @click="moveBinding(scope.$index, 1)">下移</el-button><el-button type="text" @click="removeBinding(scope.$index)">移除</el-button></template></el-table-column>
            </el-table>
            <el-select v-if="checkPermi(['standard:standardLibrary:upStandardProduct'])" v-model="templateToBind" filterable placeholder="选择原始记录模板" size="small" style="width:320px;margin-top:16px" @change="addBinding"><el-option v-for="template in templateList.filter(item => !templateBindings.some(binding => binding.templateId === item.id))" :key="template.id" :label="template.number ? template.number + ' - ' + template.name : template.name" :value="template.id" /></el-select>
            <el-select v-if="checkPermi(['standard:standardLibrary:upStandardProduct'])" v-model="templateToBind" multiple filterable placeholder="选择原始记录模板" size="small" style="width:320px;margin-top:16px"><el-option v-for="template in templateList.filter(item => !templateBindings.some(binding => binding.templateId === item.id))" :key="template.id" :label="template.number ? template.number + ' - ' + template.name : template.name" :value="template.id" /></el-select>
            <el-button v-if="checkPermi(['standard:standardLibrary:upStandardProduct'])" type="primary" size="small" style="margin-top:16px;margin-left:8px" @click="confirmAddBindings">确认</el-button>
          </el-card>
          <el-empty v-else description="请选择二级标准,维护其原始记录模板" />
          <div v-if="false">
@@ -322,6 +334,7 @@
  getStandardTemplate,
  selectTemplateBindings,
  saveTemplateBindings,
  searchDevices,
} from "@/api/standard/standardLibrary";
import bindSupplierDensityDialogAsk from "./components/bindSupplierDensityDialogAsk.vue";
import BatchCopy from "./components/BatchCopy.vue";
@@ -393,7 +406,8 @@
      sectionList: [],
      templateList: [],
      templateBindings: [],
      templateToBind: null,
      templateToBind: [],
      deviceOptions: [],
      total: 0,
      currentPage: 1,
      standardId: 0,
@@ -419,6 +433,7 @@
    this.selectEnumByCategoryForSonLaboratory();
    this.selectEnumByCategoryForsampleType();
    this.getStandardTemplate();
    this.loadDeviceOptions();
    this.selectStandardMethodsSec();
    this.token = {
      token: sessionStorage.getItem("token"),
@@ -977,18 +992,32 @@
        this.templateList = res.data;
      });
    },
    loadTemplateBindings(standardTreeId) {
      selectTemplateBindings({ standardTreeId }).then((res) => {
        this.templateBindings = res.data || [];
        this.templateToBind = null;
    loadDeviceOptions() {
      searchDevices({ status: 0 }).then((res) => {
        this.deviceOptions = (res.data || []).map((device) => ({
          id: device.id,
          label: `${device.deviceName}--${device.managementNumber}`,
        }));
      });
    },
    addBinding(templateId) {
      const template = this.templateList.find((item) => item.id === templateId);
      if (template && !this.templateBindings.some((item) => item.templateId === templateId)) {
        this.templateBindings.push({ ...template, templateId, sort: this.templateBindings.length });
      }
      this.templateToBind = null;
    loadTemplateBindings(standardTreeId) {
      selectTemplateBindings({ standardTreeId }).then((res) => {
        this.templateBindings = (res.data || []).map((item) => ({
          ...item,
          deviceIds: Array.isArray(item.deviceIds) ? [...item.deviceIds] : [],
        }));
        this.templateToBind = [];
      });
    },
    confirmAddBindings() {
      const selectedIds = Array.isArray(this.templateToBind) ? this.templateToBind : [];
      selectedIds.forEach((templateId) => {
        const template = this.templateList.find((item) => item.id === templateId);
        if (template && !this.templateBindings.some((item) => item.templateId === templateId)) {
          this.templateBindings.push({ ...template, templateId, sort: this.templateBindings.length, deviceIds: [] });
        }
      });
      this.templateToBind = [];
    },
    removeBinding(index) {
      this.templateBindings.splice(index, 1);
@@ -1000,7 +1029,11 @@
    },
    saveBindings() {
      saveTemplateBindings({ standardTreeId: this.selected.id,
        templates: this.templateBindings.map((item, index) => ({ templateId: item.templateId, sort: index }))
        templates: this.templateBindings.map((item, index) => ({
          templateId: item.templateId,
          sort: index,
          deviceIds: Array.isArray(item.deviceIds) ? [...item.deviceIds] : [],
        }))
      }).then(() => {
        this.$message.success("模板绑定已保存");
        this.loadTemplateBindings(this.selected.id);
vue.config.js
@@ -36,8 +36,8 @@
    proxy: {
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
        // target: `http://36.213.90.123:9015/lims`,
        target: `http://192.168.21.53:9001/lims`,
        // 本地开发统一访问本机后端,报告文件读取本机 wordUrl 目录。
        target: `http://127.0.0.1:9509/lims`,
        changeOrigin: true,
        pathRewrite: {
          ["^" + process.env.VUE_APP_BASE_API]: "",