Просмотр исходного кода

Merge branch 'master' of http://110.41.163.243:9980/kd-aiot/kd-aiot-frontend-wt into dengfei

695593266@qq.com 2 недель назад
Родитель
Сommit
2e3247f8e5

+ 67 - 0
src/BIZComponents/processSubmitDialog/components/blank_table_headers.json

@@ -0,0 +1,67 @@
+{
+  "blank_business_trip": [
+    {"prop": "transportation", "label": "交通工具"},
+    {"prop": "isRoundTrip", "label": "单程往返"},
+    {"prop": "departureCity", "label": "出发城市"},
+    {"prop": "destinationCity", "label": "目的城市"},
+    {"prop": "startTime", "label": "开始时间"},
+    {"prop": "endTime", "label": "结束时间"},
+    {"prop": "duration", "label": "时长"}
+  ],
+  "blank_business_component": [
+    {"prop": "startDate", "label": "出发日期"},
+    {"prop": "endDate", "label": "到达日期"},
+    {"prop": "startPlace", "label": "出发地"},
+    {"prop": "endPlace", "label": "到达地"},
+    {"prop": "transportationWay", "label": "交通方式"},
+    {"prop": "price", "label": "交通费用"}
+  ],
+  "blank_application_component": [
+    {"prop": "productName", "label": "产品名称"},
+    {"prop": "quantity", "label": "数量"},
+    {"prop": "specification", "label": "规格型号"}
+  ],
+  "blank_product_specification": [
+    {"prop": "specification", "label": "产品规格型号"},
+    {"prop": "num", "label": "数量"},
+    {"prop": "batchNo", "label": "批号"}
+  ],
+  "blank_purchase_requisition": [
+    {"prop": "materialName", "label": "物料名称"},
+    {"prop": "specification", "label": "规格型号"},
+    {"prop": "unit", "label": "单位"},
+    {"prop": "quantity", "label": "数量"},
+    {"prop": "purpose", "label": "用途"},
+    {"prop": "expectedDeliveryDate", "label": "期望交付日期"},
+    {"prop": "remark", "label": "备注"}
+  ]
+  ,
+  "blank_use_seal": [
+    {"prop": "useSealType", "label": "印章使用类型"},
+    {"prop": "useSealRemark", "label": "印章使用事由"},
+    {"prop": "useSealName", "label": "印章名称"}
+  ],
+  "blank_use_qualification": [
+    {"prop": "useQualificationType", "label": "资质类型"},
+    {"prop": "useQualificationName", "label": "使用资质名称"},
+    {"prop": "useQualificationRemark", "label": "资质使用事由"}
+  ],
+  "blank_event_component": [
+    {"prop": "eventTime", "label": "事件发生时间"},
+    {"prop": "eventHospital", "label": "事件发生的医院及科室"},
+    {"prop": "eventDesc", "label": "事件详细描述"},
+    {"prop": "eventImage", "label": "图片上传"},
+    {"prop": "eventOpinion", "label": "其他意见"}
+  ],
+  "blank_reissue_component": [
+    {"prop": "reissueType", "label": "补发型号规格"},
+    {"prop": "reissueOtherType", "label": "补发其他规格型号"},
+    {"prop": "reissueContact", "label": "收件地址/联系人/电话"},
+    {"prop": "reissueOtherReq", "label": "其他要求"}
+  ],
+  "blank_adopzrdd": [
+    {"prop": "remark", "label": "报销事项"},
+    {"prop": "price", "label": "金额"},
+    {"prop": "file", "label": "附件"}
+  ]
+}

+ 36 - 0
src/api/bpm/components/qualityReportApproval/qualityReportApproval.js

@@ -92,4 +92,40 @@ export async function generateReport(data) {
     return res.data.data;
   }
   return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 分页
+ */
+export async function getBadnameList(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/qms/badname/page?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 分页
+ */
+export async function getBadtypeList(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/qms/badtype/page?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 分页
+ */
+export async function getReasontypeList(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/qms/reasontype/page?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
 }

+ 460 - 2
src/views/bpm/collaborative/detail.vue

@@ -1,4 +1,5 @@
 <template>
+  <div>
   <ele-modal
     :visible="formDetailDialogFlag"
     :width="modelWidth"
@@ -26,7 +27,7 @@
       </div>
     </template>
     <div style="display: flex; height: 100%; justify-content: space-between">
-      <div class="form-box" :style="fmStyle">
+      <div id="printSection" class="form-box" :style="fmStyle">
         <fm-generate-form
           v-if="Object.keys(form?.formJson || {}).length !== 0"
           :data="jsonData"
@@ -206,9 +207,21 @@
       :isInterior="true"
     ></outgoingDetails>
     <div slot="footer" class="footer">
+      <el-button type="primary" @click="print">打印</el-button>
+      <!-- <el-button @click="openPreview">预览</el-button> -->
       <el-button @click="cancel">返回</el-button>
     </div>
   </ele-modal>
+
+  <!-- 打印 HTML 预览弹窗 -->
+  <el-dialog :visible.sync="previewDialog" width="80%" top="20px" title="打印预览">
+    <div style="max-height:70vh; overflow:auto; background:#fff; padding:16px;" v-html="previewHtml"></div>
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="previewDialog = false">关闭</el-button>
+      <el-button type="primary" @click="printPreview">打印预览</el-button>
+    </span>
+  </el-dialog>
+  </div>
 </template>
 <script>
 import dictMixins from '@/mixins/dictMixins';
@@ -227,6 +240,10 @@ import Detail from '@/views/bpm/processInstance/detailNew.vue';
 import applicationComponent from '@/BIZComponents/processSubmitDialog/components/applicationComponent.vue';
 import purchaseRequisitionComponent from '@/BIZComponents/processSubmitDialog/components/purchaseRequisitionComponent.vue';
 import businessTripComponent from '@/BIZComponents/processSubmitDialog/components/businessTripComponent.vue';
+import { getGroupUserTree, getProduceTreeByCode } from '@/api/main';
+import { listOrganizations, getUserPage } from '@/api/system/organization';
+import { getTaskListByProcessInstanceId } from '@/api/bpm/task';
+import store from '@/store';
 
 export default {
   name: 'formDetailDialog',
@@ -259,7 +276,11 @@ export default {
       form: {},
       jsonData: {},
       outgoingData: {},
-      isRight: false
+      isRight: false,
+      previewDialog: false,
+      previewHtml: '',
+      remoteOptionsCache: {},
+      tasks: []
     };
   },
   computed: {
@@ -288,12 +309,23 @@ export default {
     }
   },
   methods: {
+    getChildren(data) {
+      const arr = [];
+      (data || []).forEach((item) => {
+        if (item.children && item.children.length > 0) {
+          arr.push(...this.getChildren(item.children));
+        }
+        arr.push(item);
+      });
+      return arr;
+    },
     open(row) {
       this.form = _.cloneDeep(row);
       if (this.form.id) {
         storageApi.getInfoBySourceBizNo(this.form.id).then((res) => {
           this.outgoingData = res;
         });
+        this.getTaskList();
       }
       this.jsonData = JSON.parse(this.form.formJson.makingJson);
       this.jsonData.config.dataSource &&
@@ -303,6 +335,432 @@ export default {
           };
           // item.url = item.url && item.url.replace('/api', this.APIUrl);
         });
+      // 预加载远程数据(例如 getGroupUserTree)以便打印时将 id 转为名称
+      this.ensureRemoteData(this.jsonData).catch((e) => {
+        console.warn('预加载远程数据失败', e);
+      });
+    },
+
+    // 获取流程审批记录
+    getTaskList() {
+      getTaskListByProcessInstanceId(this.form.id).then((response) => {
+          // 审批记录
+          this.tasks = [];
+          // console.log(response,'response')
+          // 移除已取消的审批
+          response.forEach((task) => {
+            // if (task.result !== 4) {
+            this.tasks.push(...this.getChildren([task]));
+            // }
+          });
+
+          // 需要审核的记录
+          const userId = store.getters.userId;
+          this.tasks.forEach((task) => {
+            if (task.result !== 1 && task.result !== 6) {
+              // 只有待处理才需要
+              return;
+            }
+            if (!task.assigneeUser || task.assigneeUser.id !== userId) {
+              // 自己不是处理人
+              return;
+            }
+          });
+          console.log(this.tasks,'this.tasks')
+        });
+    },
+
+    async ensureRemoteData(makingJson) {
+      if (!makingJson || !makingJson.list) return;
+      const tasks = [];
+      makingJson.list.forEach((item) => {
+        const remoteKey = item.options && item.options.remoteDataSource;
+        if (remoteKey && !this.remoteOptionsCache[remoteKey]) {
+          // 目前只实现 getGroupUserTree 的预加载
+          if (remoteKey === 'getGroupUserTree') {
+            tasks.push(
+              getGroupUserTree()
+                .then((res) => {
+                  // res 可能是树形,扁平化为 id->node 映射
+                  const map = {};
+                  const walk = (nodes) => {
+                    if (!nodes) return;
+                    nodes.forEach((n) => {
+                      if (n.id !== undefined) map[n.id] = n;
+                      if (n.children && n.children.length) walk(n.children);
+                    });
+                  };
+                  walk(res || []);
+                  this.remoteOptionsCache[remoteKey] = map;
+                })
+                .catch((e) => {
+                  console.warn('加载远程数据失败', remoteKey, e);
+                })
+            );
+          } else if (remoteKey === 'getUserPage') {
+            tasks.push(
+              getUserPage({ pageNum: 1, pageSize: -1 })
+                .then((res) => {
+                  const map = {};
+                  const list = res?.list || [];
+                  list.forEach((user) => {
+                    if (user && user.id !== undefined) {
+                      map[user.id] = user;
+                    }
+                  });
+                  this.remoteOptionsCache[remoteKey] = map;
+                })
+                .catch((e) => {
+                  console.warn('加载远程数据失败', remoteKey, e);
+                })
+            );
+          } else if (remoteKey === 'getDeptTree') {
+            tasks.push(
+              listOrganizations()
+                .then((res) => {
+                  const map = {};
+                  const list = Array.isArray(res) ? res : [];
+                  list.forEach((dept) => {
+                    if (dept && dept.id !== undefined) {
+                      map[dept.id] = dept;
+                    }
+                  });
+                  this.remoteOptionsCache[remoteKey] = map;
+                })
+                .catch((e) => {
+                  console.warn('加载远程数据失败', remoteKey, e);
+                })
+            );
+          }
+        }
+      });
+      await Promise.all(tasks);
+    },
+    print1() {
+      const printSection = document.getElementById('printSection');
+      // 创建打印任务
+      const printWindow = window.open('', '_blank');
+      printWindow.document.open();
+      printWindow.document.write('<html><head><title>打印预览</title>');
+      printWindow.document.write(
+        '<link rel="stylesheet" href="your-stylesheet-url.css" type="text/css" />'
+      );
+      printWindow.document.write('</head><body>');
+      printWindow.document.write(printSection.innerHTML);
+      printWindow.document.write('</body></html>');
+      printWindow.document.close();
+      printWindow.onload = function () {
+        printWindow.print();
+      };
+    },
+    async print() {
+      // 将 formJson.makingJson 与 valueJson 合并生成 HTML,再打开新窗口打印
+      try {
+        const making = this.jsonData || (this.form.formJson && JSON.parse(this.form.formJson.makingJson));
+        // 确保远程数据加载完成(用于 id->name 映射)
+        await this.ensureRemoteData(making);
+        const values = this.form.valueJson || {};
+        const html = this.generatePrintHtml(making, values, this.form);
+        const w = window.open('', '_blank');
+        if (!w) {
+          this.$message && this.$message.error && this.$message.error('弹窗被拦截,无法打开预览窗口');
+          return;
+        }
+        w.document.open();
+        w.document.write(html);
+        w.document.close();
+        w.focus();
+        setTimeout(() => {
+          w.print();
+        }, 300);
+      } catch (e) {
+        console.error('打印失败', e);
+        console.log('打印 JSON 数据:', this.jsonData, this.form.valueJson);
+      }
+    },
+
+    async openPreview() {
+      try {
+        const making = this.jsonData || (this.form.formJson && JSON.parse(this.form.formJson.makingJson));
+        // 确保远程数据加载完成(用于 id->name 映射)
+        await this.ensureRemoteData(making);
+        const values = this.form.valueJson || {};
+        const full = this.generatePrintHtml(making, values, this.form);
+        // 提取 <style> 和 .pf-container,用相同样式渲染预览
+        const styleMatch = full.match(/<style>([\s\S]*?)<\/style>/);
+        const containerMatch = full.match(/<div class="pf-container">[\s\S]*<\/div>/);
+        const styleTag = styleMatch ? `<style>${styleMatch[1]}</style>` : '';
+        this.previewHtml = styleTag + (containerMatch ? containerMatch[0] : full);
+        this.previewDialog = true;
+      } catch (e) {
+        console.error('生成预览失败', e);
+        this.$message && this.$message.error && this.$message.error('生成预览失败');
+      }
+    },
+
+    async printPreview() {
+      try {
+        const making = this.jsonData || (this.form.formJson && JSON.parse(this.form.formJson.makingJson));
+        await this.ensureRemoteData(making);
+        const values = this.form.valueJson || {};
+        const full = this.generatePrintHtml(making, values, this.form);
+        const w = window.open('', '_blank');
+        if (!w) {
+          this.$message && this.$message.error && this.$message.error('弹窗被拦截,无法打开预览窗口');
+          return;
+        }
+        w.document.open();
+        w.document.write(full);
+        w.document.close();
+        w.focus();
+        setTimeout(() => w.print(), 300);
+      } catch (e) {
+        console.error('打印预览失败', e);
+        this.$message && this.$message.error && this.$message.error('打印预览失败');
+      }
+    },
+
+    generatePrintHtml(makingJson, valueJson, formMeta = {}) {
+      // 加载自定义区域表头映射(如果存在)
+      let headerMap = {};
+      try {
+        // eslint-disable-next-line no-undef
+        headerMap = require('@/BIZComponents/processSubmitDialog/components/blank_table_headers.json');
+      } catch (e) {
+        headerMap = {};
+      }
+
+      const title = this.escapeHtml(formMeta.name || formMeta.formName || '表单打印');
+      const list = (makingJson && makingJson.list) || [];
+
+      const rows = [];
+      const isBlankField = (item) => item.type === 'blank' || (item.model && String(item.model).indexOf('blank_') === 0);
+      const renderBlankTable = (item, rawRows = []) => {
+        const mapKey = item.model || item.key || '';
+        let cols = [];
+        if (headerMap && headerMap[mapKey]) {
+          cols = headerMap[mapKey].map(c => c.prop);
+        } else {
+          const set = new Set();
+          rawRows.forEach((r) => {
+            if (r && typeof r === 'object') {
+              Object.keys(r).forEach(k => set.add(k));
+            }
+          });
+          cols = Array.from(set);
+        }
+
+        const ths = cols.map(c => {
+          let headLabel = c;
+          if (headerMap && headerMap[mapKey]) {
+            const found = headerMap[mapKey].find(h => h.prop === c);
+            if (found && found.label) headLabel = found.label;
+          }
+          return '<th>' + this.escapeHtml(headLabel) + '</th>';
+        }).join('');
+
+        const trs = rawRows.map(r => {
+          const tds = cols.map(c => {
+            const v = (r && r[c] !== undefined && r[c] !== null) ? r[c] : '';
+            if (typeof v === 'object') return '<td>' + this.escapeHtml(JSON.stringify(v)) + '</td>';
+            return '<td>' + this.escapeHtml(String(v)) + '</td>';
+          }).join('');
+          return '<tr>' + tds + '</tr>';
+        }).join('');
+
+        return '<table class="pf-table"><thead><tr>' + ths + '</tr></thead><tbody>' + trs + '</tbody></table>';
+      };
+
+      for (let i = 0; i < list.length; i++) {
+        const item = list[i];
+        // 标题优先取 name/label,若不存在则回退到 model 的可读化,最终回退到 ''
+        const rawLabel = item.name || item.label || (item.model ? String(item.model).replace(/^blank_/, '').replace(/_/g, ' ') : '') || '';
+        const label = this.escapeHtml(rawLabel);
+        const key = item.model || item.key || '';
+        let val = '';
+        try {
+          const raw = valueJson && (valueJson[key] !== undefined ? valueJson[key] : valueJson[item.model]);
+          if (raw === null || raw === undefined || raw === '') {
+            if (isBlankField(item)) {
+              val = renderBlankTable(item, []);
+            } else {
+              val = '';
+            }
+          } else if (typeof raw === 'string' || typeof raw === 'number') {
+            const remoteKey = item.options && item.options.remoteDataSource;
+            if (remoteKey && this.remoteOptionsCache[remoteKey]) {
+              const map = this.remoteOptionsCache[remoteKey];
+              val = map && map[raw] ? (map[raw].name || map[raw]) : String(raw);
+            } else {
+              val = this.escapeHtml(String(raw));
+            }
+          } else if (Array.isArray(raw)) {
+            if (raw.length === 0) {
+              if (isBlankField(item)) {
+                val = renderBlankTable(item, []);
+              } else {
+                val = '';
+              }
+            } else {
+              const first = raw[0];
+              // 先检测是否为附件数组(对象形式或 id 列表)
+              const isAttachmentObjectArray = raw.every(r => r && (r.id !== undefined || r.fileId !== undefined || r.uid !== undefined));
+              const looksLikeAttachmentByMeta = (item.type && String(item.type).toLowerCase().includes('upload')) || (item.model && String(item.model).toLowerCase().includes('file')) || (label && label.includes('附件'));
+              if (isAttachmentObjectArray) {
+                val = raw.length + ' 个附件';
+              } else if ((typeof first === 'string' || typeof first === 'number') && looksLikeAttachmentByMeta) {
+                // 原始为 id 列表,并且通过字段/label 推断为附件,显示数量
+                val = raw.length + ' 个附件';
+              } else if ((typeof first === 'string' || typeof first === 'number')) {
+                const remoteKey = item.options && item.options.remoteDataSource;
+                if (remoteKey && this.remoteOptionsCache[remoteKey]) {
+                  const map = this.remoteOptionsCache[remoteKey];
+                  val = raw.map(id => (map[id] ? (map[id].name || map[id]) : id)).join(', ');
+                } else {
+                  val = raw.map(r => this.escapeHtml(String(r))).join(', ');
+                }
+              } else if (item.model === 'manage_workorder' && typeof first === 'object' && first !== null) {
+                val = raw
+                  .map(r => (r && r.code !== undefined ? this.escapeHtml(String(r.code)) : this.escapeHtml(JSON.stringify(r))))
+                  .join(', ');
+              } else if (typeof first === 'object' && first !== null && (item.type === 'blank' || (item.model && item.model.indexOf('blank_') === 0))) {
+                // 自定义区域渲染为表格
+                const mapKey = item.model || item.key || '';
+                let cols = [];
+                if (headerMap && headerMap[mapKey]) {
+                  cols = headerMap[mapKey].map(c => c.prop);
+                } else {
+                  const set = new Set();
+                  for (let r of raw) {
+                    if (r && typeof r === 'object') {
+                      Object.keys(r).forEach(k => set.add(k));
+                    }
+                  }
+                  cols = Array.from(set);
+                }
+
+                // 表头
+                const ths = cols.map(c => {
+                  let headLabel = c;
+                  if (headerMap && headerMap[mapKey]) {
+                    const found = headerMap[mapKey].find(h => h.prop === c);
+                    if (found && found.label) headLabel = found.label;
+                  }
+                  return '<th>' + this.escapeHtml(headLabel) + '</th>';
+                }).join('');
+
+                // 行
+                const trs = raw.map(r => {
+                  const tds = cols.map(c => {
+                    const v = (r && r[c] !== undefined && r[c] !== null) ? r[c] : '';
+                    if (typeof v === 'object') return '<td>' + this.escapeHtml(JSON.stringify(v)) + '</td>';
+                    return '<td>' + this.escapeHtml(String(v)) + '</td>';
+                  }).join('');
+                  return '<tr>' + tds + '</tr>';
+                }).join('');
+
+                val = '<table class="pf-table"><thead><tr>' + ths + '</tr></thead><tbody>' + trs + '</tbody></table>';
+              } else {
+                // 其他对象数组,简要渲染
+                val = raw.map(r => {
+                  if (r === null || r === undefined) return '';
+                  if (typeof r === 'string' || typeof r === 'number') return this.escapeHtml(String(r));
+                  if (r && (r.name || r.fileName || r.label)) return this.escapeHtml(r.name || r.fileName || r.label);
+                  if (r && (r.url || r.path)) return '<a href="' + this.escapeHtml(r.url || r.path) + '" target="_blank">文件</a>';
+                  return this.escapeHtml(JSON.stringify(r));
+                }).join(', ');
+              }
+            }
+          } else if (typeof raw === 'object') {
+            if (item.model === 'manage_workorder' && raw.code !== undefined) {
+              val = this.escapeHtml(String(raw.code));
+            } else if (raw.name) {
+              val = this.escapeHtml(String(raw.name));
+            } else if (raw.label) {
+              val = this.escapeHtml(String(raw.label));
+            } else {
+              val = this.escapeHtml(JSON.stringify(raw));
+            }
+          } else {
+            val = this.escapeHtml(String(raw));
+          }
+        } catch (e) {
+          val = '';
+        }
+
+        // 如果是自定义区域(blank)并且渲染结果为表格,则用整行显示区域名称 + 表格
+        const isBlankArea = (item.type === 'blank' || (item.model && String(item.model).indexOf('blank_') === 0));
+        if (isBlankArea && val && String(val).indexOf('<table') !== -1) {
+          // 不显示自定义区域标题,仅渲染表格
+          rows.push('<div class="pf-blank-area">' + val + '</div>');
+        } else {
+          rows.push('<div class="pf-row"><div class="pf-label">' + label + '</div><div class="pf-value">' + val + '</div></div>');
+        }
+      }
+
+      const style = 'body{font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial; color:#333} '
+        + '.pf-container{padding:20px;max-width:900px;margin:0 auto} '
+        + '.pf-title{font-size:18px;margin-bottom:12px} '
+        + '.pf-row{display:flex;padding:8px 0;border-bottom:1px solid #eee} '
+        + '.pf-label{width:160px;color:#666} '
+        + '.pf-value{flex:1} '
+        + '.pf-blank-area{margin-top:12px;margin-bottom:12px;overflow:auto} '
+        + '.pf-blank-title{font-weight:600;margin-bottom:6px;color:#333;font-size:14px;word-break:break-word;white-space:normal} '
+        + 'a{color:#1890ff} '
+        + '.pf-table{width:100%;border-collapse:collapse;margin-top:6px} '
+        + '.pf-table th,.pf-table td{border:1px solid #e8e8e8;padding:6px;text-align:left} '
+        + '.pf-tasks-title{font-size:16px;font-weight:600;margin-top:24px;margin-bottom:8px} '
+        + '@media print{.pf-table thead{display:table-row-group}.pf-table tr{page-break-inside:avoid}}';
+
+      return '<!doctype html><html><head><meta charset="utf-8" /><title>' + title + '</title><style>' + style + '</style></head><body>'
+        + '<div class="pf-container"><div class="pf-title">' + title + '</div>' + rows.join('\n') + this.generateTasksPrintHtml() + '</div></body></html>';
+    },
+
+    // 将流程审批记录(tasks)拼接为可打印的表格 HTML
+    generateTasksPrintHtml() {
+      if (!this.tasks || this.tasks.length === 0) return '';
+      const trs = this.tasks
+        .map((item, index) => {
+          const nickname =
+            item.assigneeUser && item.assigneeUser.nickname
+              ? item.assigneeUser.nickname
+              : '';
+          const deptName =
+            item.assigneeUser && item.assigneeUser.deptName
+              ? item.assigneeUser.deptName
+              : '';
+          return '<tr>'
+            + '<td style="text-align:center">' + (index + 1) + '</td>'
+            + '<td>' + this.escapeHtml(item.name) + '</td>'
+            + '<td>' + this.escapeHtml(nickname) + '</td>'
+            + '<td>' + this.escapeHtml(deptName) + '</td>'
+            + '<td>' + this.escapeHtml(item.createTime) + '</td>'
+            + '<td>' + this.escapeHtml(item.endTime) + '</td>'
+            + '<td>' + this.escapeHtml(item.reason) + '</td>'
+            + '</tr>';
+        })
+        .join('');
+
+      return '<div class="pf-tasks-title">审批记录</div>'
+        + '<table class="pf-table"><thead><tr>'
+        + '<th style="text-align:center">序号</th>'
+        + '<th>任务</th>'
+        + '<th>发起人/审批人</th>'
+        + '<th>部门</th>'
+        + '<th>创建时间</th>'
+        + '<th>审批时间</th>'
+        + '<th>审批意见</th>'
+        + '</tr></thead><tbody>' + trs + '</tbody></table>';
+    },
+
+    escapeHtml(str) {
+      if (str === null || str === undefined) return '';
+      return String(str)
+        .replace(/&/g, '&amp;')
+        .replace(/</g, '&lt;')
+        .replace(/>/g, '&gt;')
+        .replace(/"/g, '&quot;')
+        .replace(/'/g, '&#39;');
     },
     getTimelineItemType(result) {
       if (result === '通过') {

+ 7 - 0
src/views/bpm/handleTask/components/financialManage/invoiceManage/submit.vue

@@ -23,6 +23,7 @@
         type="success"
         size="mini"
         v-click-once
+        :loading="loading"
         @click="handleAudit(1)"
         >通过
       </el-button>
@@ -31,6 +32,7 @@
         type="danger"
         size="mini"
         v-click-once
+        :loading="loading"
         @click="_approveTaskWithVariables(0)"
         >驳回
       </el-button>
@@ -77,6 +79,7 @@
           technicianId: '',
           reason: '同意'
         },
+        loading: false,
         tabOptions: [
           { key: 'starter', permissionType: 'update', name: '发起人申请' },
           {
@@ -113,18 +116,22 @@
         let variables = {
           pass: !!status
         };
+        this.loading = true;
         let API = !!status ? approveTaskWithVariables : rejectTask;
         API({
           id: this.taskId,
           reason: this.form.reason,
           variables
         }).then((res) => {
+          this.loading = false;
           if (res.data.code != '-1') {
             this.$emit('handleAudit', {
               status,
               title: status === 0 ? '驳回' : ''
             });
           }
+        }).finally(() => {
+          this.loading = false;
         });
       },
 

+ 6 - 1
src/views/bpm/handleTask/components/productionWarehousing/components/WarehousingDialog.vue

@@ -69,6 +69,11 @@ export default {
       measureQuantity: 0
     };
   },
+  computed: {
+    userInfo() {
+      return this.$store.getters.user.info;
+    },
+  },
   methods: {
     changeWarehouse() {
       this.tableDate = []
@@ -120,7 +125,7 @@ export default {
       this.tableDate.splice(index, 1);
     },
     async getWarehouseList(categoryType) {
-      const res = await warehouseDefinition.list({ inventoryType: categoryType });
+      const res = await warehouseDefinition.list({ inventoryType: categoryType, ownerId: this.userInfo.userId });
       console.log(res);
       this.warehouseList = res.map((item) => {
         return { ...item, name: item.name };

+ 57 - 6
src/views/bpm/handleTask/components/qualityReportApproval/detailDialog.vue

@@ -30,6 +30,7 @@
         v-show="activeComp == 'unqualifiedList'"
         @setQualifiedNumber="setQualifiedNumber"
         :total="basicInfoData.total"
+        @setLossNumber="setLossNumber"
       ></unqualifiedProduct>
 
       <div style="margin-top: 10px" v-if="businessType?.includes('检测报告单')">
@@ -37,7 +38,7 @@
         <el-select
           v-model="basicInfoData.qualityResults"
           placeholder="请选择"
-          style="width: 140px"
+          style="width: 120px"
           @change="selectResult"
         >
           <el-option label="整单合格" :value="1" />
@@ -50,7 +51,7 @@
         <el-input
           v-model="basicInfoData.qualifiedNumber"
           placeholder="合格数"
-          style="width: 140px"
+          style="width: 110px"
           disabled
         ></el-input>
 
@@ -59,7 +60,35 @@
           v-model="basicInfoData.noQualifiedNumber"
           placeholder="不合格数"
           disabled
-          style="width: 140px"
+          style="width: 110px"
+        ></el-input>
+        留样数(合格):
+        <el-input
+          v-model="basicInfoData.retainedSampleQuantity"
+          placeholder=" "
+          disabled
+          style="width: 80px"
+        ></el-input>
+        留样数(不合格):
+        <el-input
+          v-model="basicInfoData.retainedSampleUnqualified"
+          placeholder=" "
+          disabled
+          style="width: 80px"
+        ></el-input>
+        消耗数(合格):
+        <el-input
+          v-model="basicInfoData.lossNumber"
+          placeholder=" "
+          disabled
+          style="width: 80px"
+        ></el-input>
+        消耗数(不合格):
+        <el-input
+          v-model="basicInfoData.lossNumberUnqualified"
+          placeholder=" "
+          disabled
+          style="width: 80px"
         ></el-input>
       </div>
     </div>
@@ -209,7 +238,29 @@
           this.fileUrl = `${url}?token=${getToken()}&id=${this.businessId}`;
         }
       },
-
+      setLossNumber({
+        lossNumber,
+        lossNumberUnqualified,
+        retainedSampleQuantity,
+        retainedSampleUnqualified
+      }) {
+        this.$set(
+          this.basicInfoData,
+          'retainedSampleQuantity',
+          retainedSampleQuantity
+        );
+        this.$set(
+          this.basicInfoData,
+          'retainedSampleUnqualified',
+          retainedSampleUnqualified
+        );
+        this.$set(this.basicInfoData, 'lossNumber', lossNumber);
+        this.$set(
+          this.basicInfoData,
+          'lossNumberUnqualified',
+          lossNumberUnqualified
+        );
+      },
       async initUnqualifiedProduct() {
         const data = await getList({
           sourceCode: this.basicInfoData.code
@@ -240,7 +291,7 @@
             this.basicInfoData.qualityResults = 4;
           }
         }
-        if (data.noQualifiedNumber == this.basicInfoData.total) {
+        if (!data.qualifiedNumber ) {
           this.basicInfoData.qualityResults = 2;
         }
         if (data.qualifiedNumber && data.noQualifiedNumber) {
@@ -256,7 +307,7 @@
           document.getElementById('conclusionInput').value;
         const params = {
           id: this.businessId,
-          type:0,
+          type: 0,
           reportTemplateId: this.form.reportTemplateId,
           reportTemplateCode: this.form.reportTemplateCode,
           reportTemplateName: this.form.reportTemplateName,

+ 7 - 4
src/views/bpm/handleTask/components/qualityReportApproval/submit.vue

@@ -121,10 +121,13 @@
             // userId: userInfo?.id,
             pass: !!status
           }
-        });
-        this.$emit('handleAudit', {
-          status,
-          title: status === 0 ? '驳回' : ''
+        }).then((res) => {
+          if (res.data.code == 0) {
+            this.$emit('handleAudit', {
+              status,
+              title: status === 0 ? '驳回' : ''
+            });
+          }
         });
       },
       async rejectTask(status) {

+ 207 - 15
src/views/bpm/handleTask/components/qualityReportApproval/unqualifiedProduct.vue

@@ -4,7 +4,7 @@
       <ele-pro-table
         ref="table"
         :columns="columns"
-        :datasource="poList"
+        :datasource="poList.filter((item) => item.disposalStatus != 2)"
         :selection.sync="selection"
         :needPage="false"
         row-key="id"
@@ -35,6 +35,71 @@
             </template>
           </el-popconfirm>
         </template>
+
+        <template v-slot:badTypeName="{ row, $index }">
+          <el-select
+            v-model="row.badTypeId"
+            placeholder="请选择不良类型"
+            size="small"
+            style="width: 100%"
+            remote
+            filterable
+            clearable
+          >
+            <el-option
+              v-for="item in badTypeList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+              @click="row.badTypeName = item.name"
+            >
+            </el-option>
+          </el-select>
+        </template>
+
+        <template v-slot:badNameName="{ row, $index }">
+          <el-select
+            v-model="row.badNameId"
+            placeholder="请选择不良名称"
+            size="small"
+            style="width: 100%"
+            remote
+            filterable
+            clearable
+          >
+            <el-option
+              v-for="item in badNameList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+              @click="row.badNameName = item.name"
+            >
+            </el-option>
+          </el-select>
+        </template>
+        <template v-slot:reasonTypeName="{ row, $index }">
+          <el-select
+            v-model="row.reasonTypeId"
+            placeholder="请选择原因类型"
+            size="small"
+            style="width: 100%"
+            remote
+            filterable
+            clearable
+          >
+            <el-option
+              v-for="item in reasonTypeList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+              @click="row.reasonTypeName = item.name"
+            >
+            </el-option>
+          </el-select>
+        </template>
+        <template v-slot:unqualifiedReason="{ row, $index }">
+          <el-input v-model="row.unqualifiedReason"></el-input>
+        </template>
       </ele-pro-table>
     </el-card>
 
@@ -284,7 +349,7 @@
               <el-input-number
                 v-model="singleQuantity"
                 :min="0"
-                :max="total"
+                :max="availableTotal"
                 style="width: 120px"
               ></el-input-number>
             </template>
@@ -315,7 +380,10 @@
   import {
     queryQualitySamplContent,
     queryQualityInventory,
-    getById
+    getById,
+    getBadnameList,
+    getBadtypeList,
+    getReasontypeList
   } from '@/api/bpm/components/qualityReportApproval/qualityReportApproval';
   export default {
     components: {
@@ -449,7 +517,14 @@
         sampleList: [],
         inventoryList: [],
         oldList: [],
-        addSelection: []
+        addSelection: [],
+        retainedSampleQuantity: 0,
+        retainedSampleUnqualified: 0,
+        lossNumber: 0,
+        lossNumberUnqualified: 0,
+        badTypeList: [],
+        badNameList: [],
+        reasonTypeList: []
       };
     },
     computed: {
@@ -471,6 +546,14 @@
           return this.allList;
         }
       },
+      // 可用总数 = 总数 - 留样数 - 消耗数
+      availableTotal() {
+        if (!this.sampleList.length) return this.total;
+        const usedQuantity = this.sampleList
+          .filter((item) => item.disposeType == 6 || item.disposeType == 7)
+          .reduce((sum, item) => sum + (item.measureQuantity || 0), 0);
+        return this.total - usedQuantity;
+      },
       // 表格列配置
       columns() {
         const arr = [
@@ -585,22 +668,26 @@
           {
             prop: 'badTypeName',
             label: '不良类型',
+            slot: 'badTypeName',
             width: '180',
             align: 'center'
           },
           {
             prop: 'badNameName',
+            slot: 'badNameName',
             label: '不良名称',
             width: '180',
             align: 'center'
           },
           {
+            slot: 'reasonTypeName',
             prop: 'reasonTypeName',
             label: '原因类型',
             width: '180',
             align: 'center'
           },
           {
+            slot: 'unqualifiedReason',
             prop: 'unqualifiedReason',
             label: '原因',
             width: '180',
@@ -643,8 +730,36 @@
         return { 0: '待处置', 1: '处置中', 2: '处置完成' };
       }
     },
-
+    created() {
+      this.getBadTypeList();
+      this.getBadNameList();
+      this.getReasonTypeList();
+    },
     methods: {
+      // 查询不良类型
+      async getBadTypeList() {
+        const res = await getBadtypeList({
+          pageNum: 1,
+          size: 999
+        });
+        this.badTypeList = res.list;
+      },
+      // 查询不良名称
+      async getBadNameList() {
+        const res = await getBadnameList({
+          pageNum: 1,
+          size: 999
+        });
+        this.badNameList = res.list;
+      },
+      // 查询原因类型
+      async getReasonTypeList() {
+        const res = await getReasontypeList({
+          pageNum: 1,
+          size: 999
+        });
+        this.reasonTypeList = res.list;
+      },
       async init(row) {
         this.qualityType = row.qualityType;
         this.id = row.id;
@@ -662,19 +777,20 @@
       setQualifiedNumber() {
         let isQualifiedNumber = true;
         let noQualifiedNumber = this.poList
-          .filter((item) => item.disposeType != 5)
+          .filter((item) => item.disposeType != 5 && item.disposalStatus != 2)
           .reduce((acc, cur) => acc + cur.measureQuantity, 0);
 
         if (
           this.poList
             .filter((item) => item.disposeType == 5)
-            .reduce((acc, cur) => acc + cur.measureQuantity, 0) == this.total
+            .reduce((acc, cur) => acc + cur.measureQuantity, 0) ==
+          this.availableTotal
         ) {
           isQualifiedNumber = false;
         }
         this.$emit('setQualifiedNumber', {
           noQualifiedNumber,
-          qualifiedNumber: this.total - noQualifiedNumber,
+          qualifiedNumber: this.availableTotal - noQualifiedNumber,
           isQualifiedNumber
         });
       },
@@ -682,21 +798,39 @@
         if (qualityResults == 1) {
           this.poList = JSON.parse(JSON.stringify(this.oldList));
           this.poList = this.poList.map((item) => {
-            item.disposalStatus = 1;
-            item.disposeType = 5;
+            if (item.disposalStatus != 2) {
+              item.disposalStatus = 1;
+              item.disposeType = 5;
+            }
             return item;
           });
         }
         if (qualityResults == 4) {
-          this.poList = JSON.parse(JSON.stringify(this.inventoryList));
+          this.poList = [
+            ...JSON.parse(JSON.stringify(this.inventoryList)),
+            ...JSON.parse(
+              JSON.stringify(
+                this.oldList.filter((item) => item.disposalStatus == 2)
+              )
+            )
+          ];
           this.poList = this.poList.map((item) => {
-            item.disposalStatus = 1;
-            item.disposeType = 5;
+            if (item.disposalStatus != 2) {
+              item.disposalStatus = 1;
+              item.disposeType = 5;
+            }
             return item;
           });
         }
         if (qualityResults == 2) {
-          this.poList = JSON.parse(JSON.stringify(this.inventoryList));
+          this.poList = [
+            ...JSON.parse(JSON.stringify(this.inventoryList)),
+            ...JSON.parse(
+              JSON.stringify(
+                this.oldList.filter((item) => item.disposalStatus == 2)
+              )
+            )
+          ];
         }
         if (qualityResults == 3) {
           this.poList = JSON.parse(JSON.stringify(this.oldList));
@@ -732,7 +866,7 @@
             0
           );
         }
-        if (poListTotal + selectedTotal > this.total) {
+        if (poListTotal + selectedTotal > this.availableTotal) {
           this.$message.error('选中的计量数量之和加上已有数量不能超过总数');
           return;
         }
@@ -759,6 +893,7 @@
         const arr = await getById(this.id);
         this.oldList = JSON.parse(JSON.stringify(arr.poList));
         this.poList = arr.poList;
+        this.setQualifiedNumber();
       },
 
       //   async getRefluxTask() {
@@ -802,6 +937,7 @@
         if (res.list.length > 0) {
           this.inventoryList = res.list;
         }
+        return;
       },
 
       async queryQualitySamplContent() {
@@ -810,6 +946,62 @@
           size: 1000
         });
         this.sampleList = res.list;
+        [
+          'retainedSampleQuantity',
+          'retainedSampleUnqualified',
+          'lossNumber',
+          'lossNumberUnqualified'
+        ].forEach((key) => {
+          this[key] = 0;
+        });
+        if (this.sampleList.length > 0 && this.inventoryList.length > 0) {
+          const processedList = this.sampleList.filter(
+            (item) => item.disposeType == 6 || item.disposeType == 7
+          );
+          console.log(
+            processedList.filter((item) => item.disposeType == 6),
+            'qwewqe'
+          );
+          console.log(
+            processedList.filter((item) => item.disposeType == 7),
+            '123123'
+          );
+          processedList.forEach((sampleItem) => {
+            if (sampleItem.disposeType == 6) {
+              sampleItem.qualityResults == 2
+                ? (this.retainedSampleUnqualified += sampleItem.measureQuantity)
+                : (this.retainedSampleQuantity += sampleItem.measureQuantity);
+            } else {
+              sampleItem.qualityResults == 2
+                ? (this.lossNumberUnqualified += sampleItem.measureQuantity)
+                : (this.lossNumber += sampleItem.measureQuantity);
+            }
+            const inventoryItem = this.inventoryList.find(
+              (item) => item.sourceId === sampleItem.sourceId
+            );
+            if (inventoryItem) {
+              inventoryItem.measureQuantity = Math.max(
+                0,
+                (inventoryItem.measureQuantity || 0) -
+                  (sampleItem.measureQuantity || 0)
+              );
+            }
+          });
+        }
+        this.$emit('setLossNumber', {
+          lossNumber: this.lossNumber,
+          lossNumberUnqualified: this.lossNumberUnqualified,
+          retainedSampleQuantity: this.retainedSampleQuantity,
+          retainedSampleUnqualified: this.retainedSampleUnqualified
+        });
+        if (this.inventoryList.length == 1 && !this.inventoryList[0].sourceId) {
+          this.inventoryList[0].measureQuantity =
+            this.inventoryList[0].measureQuantity -
+            this.lossNumber -
+            this.lossNumberUnqualified -
+            this.retainedSampleQuantity -
+            this.retainedSampleUnqualified;
+        }
       },
 
       async handleDispose() {

+ 7 - 2
src/views/bpm/stockManagement/components/WarehousingDialog.vue

@@ -69,6 +69,11 @@ export default {
       measureQuantity: 0
     };
   },
+  computed: {
+    userInfo() {
+      return this.$store.getters.user.info;
+    },
+  },
   methods: {
     changeWarehouse() {
       this.tableDate = []
@@ -120,8 +125,8 @@ export default {
       this.tableDate.splice(index, 1);
     },
     async getWarehouseList(categoryType) {
-      const res = await warehouseDefinition.list({ inventoryType: categoryType });
-      console.log(res);
+      const res = await warehouseDefinition.list({ inventoryType: categoryType, ownerId: this.userInfo.userId });
+      console.log('仓库列表', res);
       this.warehouseList = res.map((item) => {
         return { ...item, name: item.name };
       });