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

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

695593266@qq.com 4 дней назад
Родитель
Сommit
0a17da00e1

+ 52 - 0
src/api/system/externalConfig/index.js

@@ -0,0 +1,52 @@
+import request from '@/utils/request';
+
+/**
+ * 分页查询
+ * @param params 查询条件
+ */
+export async function externalSystemConfigPage(params) {
+  const res = await request.get('/main/externalSystemConfig/list', {
+    params
+  });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+
+/**
+ * 详情
+ * @param params 查询条件
+ */
+export async function externalSystemConfigDetail(id) {
+  const res = await request.get(`/main/externalSystemConfig/getById/${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 修改
+ * @param params 更新参数
+ */
+export async function externalSystemConfigUpdate(params) {
+  const res = await request.put(`/main/externalSystemConfig/update`, params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ *启用/禁用
+ * @param params 更新参数
+ */
+export async function externalSystemConfigEnableOrDisable(params) {
+  const res = await request.post(`/main/externalSystemConfig/enableOrDisable`, params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 25 - 1
src/components/CommomSelect/dept-select.vue

@@ -47,6 +47,14 @@
       isBindPlan: {
         type: Boolean,
         default: false
+      },
+      init: {
+        type: Boolean,
+        default: true
+      },
+      selectList: {
+        type: Array,
+        default: () => []
       }
     },
     data() {
@@ -55,7 +63,22 @@
       };
     },
     created() {
-      this.getData();
+      if (this.init) {
+        this.getData();
+      }
+    },
+    watch: {
+      selectList: {
+        handler(val) {
+          this.treeData = this.$util.toTreeData({
+            data: val || [],
+            idField: 'id',
+            parentIdField: 'parentId'
+          });
+          console.log(this.treeData, 'dsdsd');
+        },
+        deep: true
+      }
     },
     methods: {
       async getData(params = {}) {
@@ -66,6 +89,7 @@
           parentIdField: 'parentId'
         });
       },
+
       /* 更新选中数据 */
       updateValue(value) {
         this.$emit('input', value);

+ 13 - 0
src/components/CommomSelect/person-select.vue

@@ -18,6 +18,7 @@
 
 <script>
   import { getUserPage } from '@/api/system/organization';
+
   export default {
     model: {
       prop: 'value',
@@ -31,6 +32,10 @@
       init: {
         type: Boolean,
         default: true
+      },
+      selectList: {
+        type: Array,
+        default: () => []
       }
     },
     data() {
@@ -38,6 +43,14 @@
         dictList: []
       };
     },
+    watch: {
+      selectList: {
+        handler(val) {
+          this.dictList = val;
+        },
+        deep: true
+      }
+    },
     computed: {
       selectVal: {
         set(val) {

+ 38 - 14
src/views/rulesManagement/releaseRules/components/experimentationProcessNew.vue

@@ -87,6 +87,13 @@
               @change="editInputChange"
             ></el-input>
           </el-form-item>
+          <el-form-item label="标题:" prop="label">
+            <el-input
+              v-model="domObj.label"
+              placeholder="请输入标题"
+              @change="editInputChange"
+            ></el-input>
+          </el-form-item>
           <el-form-item label="宽度:" prop="width" v-if="!domObj.isNoWidth">
             <el-input
               v-model="domObj.width"
@@ -95,7 +102,11 @@
               @change="editInputChange"
             ></el-input>
           </el-form-item>
-          <el-form-item label="字段类型:" prop="fieldType" v-if="domObj.mode=='person'">
+          <el-form-item
+            label="字段类型:"
+            prop="fieldType"
+            v-if="['person', 'dept'].includes(domObj.mode)"
+          >
             <el-select
               v-model="domObj.fieldType"
               placeholder="请选择"
@@ -103,7 +114,7 @@
               clearable
             >
               <el-option
-                v-for="opt in fieldTypeOptions"
+                v-for="opt in filteredFieldTypeOptions()"
                 :key="opt.value"
                 :label="opt.label"
                 :value="opt.value"
@@ -293,7 +304,14 @@
         type: Boolean
       }
     },
-    computed: {},
+    computed: {
+      filteredFieldTypeOptions() {
+        return (val) => {
+          const mode = val || this.domObj?.mode || 'text';
+          return this.allFieldTypeOptions[mode] || [];
+        };
+      }
+    },
     data() {
       return {
         list: [],
@@ -302,13 +320,16 @@
         templateDivRef: '',
         domObj: { units: {} },
         idList: [],
-        fieldTypeOptions: [
-          { label: '作业负责人', value: 'work_leader' },
-          { label: '监护人', value: 'guardian' },
-          { label: '作业人', value: 'worker' },
-          { label: '安全交底人', value: 'safety_briefer' },
-          { label: '接受交底人', value: 'safety_receiver' }
-        ],
+        allFieldTypeOptions: {
+          person: [
+            { label: '作业负责人', value: 'work_leader' },
+            { label: '监护人', value: 'guardian' },
+            { label: '作业人', value: 'worker' },
+            { label: '安全交底人', value: 'safety_briefer' },
+            { label: '接受交底人', value: 'safety_receiver' }
+          ],
+          dept: [{ label: '申请单位', value: 'apply_dept' }]
+        },
         opSelectOptions: ['+', '-', '*', '/', '%', '(', ')'],
         equationUnit: {
           equation: [],
@@ -402,7 +423,7 @@
             this.$message.warning(
               '该字段类型在当前表格中已被使用,请选择其他类型'
             );
-            this.domObj.fieldType =  '';
+            this.domObj.fieldType = '';
           } else {
             // 唯一,允许修改
             this.domObj.id = val;
@@ -553,9 +574,12 @@
         if (!this.edit) return;
         this.templateDivRef = templateDivRef;
         // 根据 id 匹配类型(若 id 为预设值则回显,否则置空)
-        const found = this.fieldTypeOptions.find(
-          (opt) => opt.value === domObj.id
-        );
+
+        const found = this.filteredFieldTypeOptions(domObj.mode).find((opt) => {
+          console.log(opt);
+          return opt.value === domObj.id;
+        });
+        console.log(found, 'found');
         domObj.fieldType = found ? found.value : '';
         // 确保 domObj 有 textAlign 默认值(若没有则设置)
         if (!domObj.textAlign) domObj.textAlign = 'center';

+ 270 - 174
src/views/rulesManagement/releaseRules/components/templateDiv/customTableNew.vue

@@ -40,82 +40,127 @@
                 <!-- 表头操作图标(已注释) -->
                 <!-- <i ... ></i> -->
 
-                <!-- ========== 复选框模式 ========== -->
-                <template v-if="cell.mode === 'checkbox'">
-                  <div class="checkbox-group">
+                <div style="display: flex; align-items: center">
+                  <span>{{ cell.label }}</span>
+                  <template v-if="cell.mode === 'checkbox'">
+                    <div class="checkbox-group" style="flex: 1">
+                      <div
+                        v-for="cb in cell.checkboxes"
+                        :key="cb.id"
+                        class="checkbox-item"
+                      >
+                        <input type="checkbox" v-model="cb.checked" />
+                        <input
+                          v-if="edit"
+                          type="text"
+                          v-model="cb.label"
+                          class="checkbox-label-input"
+                          @input="
+                            onCheckboxLabelInput(rowIndex, colIndex, cb.id)
+                          "
+                        />
+                        <span v-else>{{ cb.label }}</span>
+                        <i
+                          v-if="edit"
+                          class="el-icon-delete"
+                          @click.stop="
+                            removeCheckbox(rowIndex, colIndex, cb.id)
+                          "
+                        ></i>
+                      </div>
+                      <div
+                        v-if="edit"
+                        class="add-checkbox"
+                        @click="addCheckbox(rowIndex, colIndex)"
+                      >
+                        <i class="el-icon-circle-plus-outline"></i> 新增选项
+                      </div>
+                    </div>
+                  </template>
+
+                  <!-- ========== 🆕 人员选择模式 ========== -->
+                  <template v-else-if="cell.mode === 'person'">
                     <div
-                      v-for="cb in cell.checkboxes"
-                      :key="cb.id"
-                      class="checkbox-item"
+                      style="
+                        width: 100%;
+                        position: relative;
+                        min-height: 30px;
+                        flex: 1;
+                      "
                     >
-                      <input type="checkbox" v-model="cb.checked" />
-                      <input
-                        v-if="edit"
-                        type="text"
-                        v-model="cb.label"
-                        class="checkbox-label-input"
-                        @input="onCheckboxLabelInput(rowIndex, colIndex, cb.id)"
+                      <!-- 人员选择组件 -->
+                      <personSelect
+                        v-model="cell.value"
+                          :disabled="readonly || cell.readonly === 2"
+                        :readonly="readonly || cell.readonly === 2"
+                        @change="onPersonChange(rowIndex, colIndex)"
+                        style="width: 95%"
+                        :init="false"
+                        :selectList="userPage"
                       />
-                      <span v-else>{{ cb.label }}</span>
+                      <!-- 编辑图标(仅在编辑模式下显示) -->
                       <i
                         v-if="edit"
-                        class="el-icon-delete"
-                        @click.stop="removeCheckbox(rowIndex, colIndex, cb.id)"
+                        class="el-icon-edit person-edit-icon"
+                        @click.stop="openPersonConfig(cell, rowIndex, colIndex)"
                       ></i>
                     </div>
+                  </template>
+                  <!-- ========== 🆕 部门选择模式 ========== -->
+                  <template v-else-if="cell.mode === 'dept'">
                     <div
-                      v-if="edit"
-                      class="add-checkbox"
-                      @click="addCheckbox(rowIndex, colIndex)"
+                      style="
+                        width: 100%;
+                        position: relative;
+                        min-height: 30px;
+                        flex: 1;
+                      "
                     >
-                      <i class="el-icon-circle-plus-outline"></i> 新增选项
+                      <deptSelect
+                        v-model="cell.value"
+                        :disabled="readonly || cell.readonly === 2"
+                        :isBindPlan="readonly || cell.readonly === 2"
+                        @change="onDeptChange(rowIndex, colIndex)"
+                        style="width: 95%"
+                        placeholder="请选择部门"
+                        :init="false"
+                        :selectList="deptList"
+                      />
+                      <i
+                        v-if="edit"
+                        class="el-icon-edit person-edit-icon"
+                        @click.stop="openPersonConfig(cell, rowIndex, colIndex)"
+                      ></i>
                     </div>
-                  </div>
-                </template>
-
-                <!-- ========== 🆕 人员选择模式 ========== -->
-                <template v-else-if="cell.mode === 'person'">
-                  <div
-                    style="width: 100%; position: relative; min-height: 30px"
-                  >
-                    <!-- 人员选择组件 -->
-                    <personSelect
+                  </template>
+
+                  <!-- ========== 文本模式 ========== -->
+                  <template v-else>
+                    <textarea
                       v-model="cell.value"
-                      :disabled="!edit"
-                      :readonly="readonly || cell.readonly === 2"
-                      @change="onPersonChange(rowIndex, colIndex)"
-                      style="width: 95%"
-                    />
-                    <!-- 编辑图标(仅在编辑模式下显示) -->
-                    <i
-                      v-if="edit"
-                      class="el-icon-edit person-edit-icon"
-                      @click.stop="openPersonConfig(cell, rowIndex, colIndex)"
-                    ></i>
-                  </div>
-                </template>
-
-                <!-- ========== 文本模式 ========== -->
-                <template v-else>
-                  <textarea
-                    v-model="cell.value"
-                    class="templateInput"
-                    :style="{ textAlign: cell.textAlign || 'center' }"
-                    :id="cell.id"
-                    :ref="cell.id + 'ref'"
-                    :readonly="cell.readonly === 2 || readonly"
-                    @mousedown="onCellMouseDown"
-                    @click="
-                      inputClick(
-                        cell,
-                        rowIndex === 0 ? 'columns' : null,
-                        $event
-                      )
-                    "
-                    @input="onInput($event)"
-                    autocomplete="off"
-                  ></textarea>
-                </template>
+                      class="templateInput"
+                      :style="{
+                        textAlign: cell.textAlign || 'left',
+                        flex: 1
+                      }"
+                      :id="cell.id"
+                      :ref="cell.id + 'ref'"
+                      :readonly="cell.readonly === 2 || readonly"
+                      @mousedown="onCellMouseDown"
+                      @click="
+                        inputClick(
+                          cell,
+                          rowIndex === 0 ? 'columns' : null,
+                          $event
+                        )
+                      "
+                      @input="onInput($event)"
+                      autocomplete="off"
+                    ></textarea>
+                  </template>
+                </div>
+
+                <!-- ========== 复选框模式 ========== -->
               </td>
             </template>
           </tr>
@@ -127,13 +172,13 @@
 
 <script>
   import { generateRandomString } from '@/utils/util';
-  // 🆕 导入人员选择组件
   import personSelect from '@/components/CommomSelect/person-select.vue';
-  // 🆕 导入人员列表接口
+  import deptSelect from '@/components/CommomSelect/dept-select.vue'; // 🆕 导入部门选择组件
   import { getUserPage } from '@/api/system/organization';
+  import { listOrganizations } from '@/api/system/organization'; // 🆕 导入部门列表接口
   export default {
     // 🆕 注册人员选择组件
-    components: { personSelect },
+    components: { personSelect, deptSelect },
     props: {
       id: { type: String, default: '' },
       edit: { type: Boolean, default: true },
@@ -141,6 +186,8 @@
     },
     data() {
       return {
+        userPage: [],
+        deptList: [],
         form: null,
         valueObj: {},
         tableData: [],
@@ -238,7 +285,36 @@
         this.menuCloseHandler = null;
       }
     },
+    created() {
+      getUserPage({ pageNum: 1, size: -1 }).then((res) => {
+        this.userPage = res.list;
+      });
+      listOrganizations({ pageNum: 1, size: -1 }).then((res) => {
+        this.deptList = res
+      });
+    },
     methods: {
+      getTransitions(key) {
+        return [
+          { target: 'text', label: '转为文本模式', method: 'disableDeptMode' },
+          {
+            target: 'checkbox',
+            label: '转为复选框模式',
+            method: 'enableCheckboxMode'
+          },
+          {
+            target: 'person',
+            label: '转为人员选择模式',
+            method: 'enablePersonMode'
+          },
+          {
+            target: 'dept',
+            label: '转为部门选择模式',
+            method: 'enableDeptMode'
+          }
+        ].filter((item) => item.target !== key);
+      },
+
       // ========== 工具 ==========
       getCellStyle(cell) {
         let width = parseFloat(cell.style?.width || cell.width || 100);
@@ -246,7 +322,7 @@
         return {
           ...cell.style,
           width: width + 'px',
-          'text-align': cell.textAlign || 'center' // 默认居中
+          'text-align': cell.textAlign || 'left' // 默认居中
         };
       },
 
@@ -258,11 +334,12 @@
           value: '',
           rowspan: 1,
           colspan: 1,
+          label: '',
           width: safeWidth,
           style: { width: safeWidth },
           readonly: 1,
           mode: 'text', // 'text' | 'checkbox' | 'person'
-          textAlign: 'center',
+          textAlign: 'left',
           checkboxes: []
         };
       },
@@ -549,28 +626,30 @@
         this.currentRowIndex = rowIndex;
         this.currentColumnIndex = colIndex;
 
+        // 移除旧菜单和监听
         const existingMenu = document.querySelector('.custom-context-menu');
         if (existingMenu) existingMenu.remove();
-
         if (this.menuCloseHandler) {
           document.removeEventListener('click', this.menuCloseHandler);
           this.menuCloseHandler = null;
         }
 
+        // 创建菜单容器
         const menu = document.createElement('div');
         menu.className = 'custom-context-menu';
         menu.style.cssText = `
-      position: fixed;
-      left: ${event.clientX}px;
-      top: ${event.clientY}px;
-      background: white;
-      border: 1px solid #ccc;
-      box-shadow: 2px 2px 8px rgba(0,0,0,0.2);
-      z-index: 10000;
-      padding: 5px 0;
-      min-width: 120px;
-    `;
-
+    position: fixed;
+    left: ${event.clientX}px;
+    top: ${event.clientY}px;
+    background: white;
+    border: 1px solid #ccc;
+    box-shadow: 2px 2px 8px rgba(0,0,0,0.2);
+    z-index: 10000;
+    padding: 5px 0;
+    min-width: 120px;
+  `;
+
+        // 菜单项添加函数
         const addItem = (text, onClick, isSeparator = false) => {
           if (isSeparator) {
             const hr = document.createElement('hr');
@@ -595,6 +674,7 @@
           menu.appendChild(item);
         };
 
+        // 关闭菜单的全局监听
         this.menuCloseHandler = (e) => {
           if (!menu.contains(e.target)) {
             menu.remove();
@@ -603,59 +683,31 @@
           }
         };
 
-        // ===== 根据行号区分菜单项 =====
+        // ---- 行操作菜单 ----
         if (rowIndex === 0) {
-          // 表头行:插入/删除列
           addItem('插入列', () => this.addColumn(colIndex));
           addItem('删除列', () => this.removeColumn(colIndex));
-          addItem('', null, true); // 分隔线
+          addItem('', null, true);
         } else {
-          // 普通行:插入/删除行
           addItem('插入行', () => this.addRow(rowIndex));
           addItem('删除行', () => this.removeRow(rowIndex));
           addItem('', null, true);
         }
 
-        // ===== 模式切换(所有行通用) =====
+        // ---- 模式切换
         const cell = this.tableData[rowIndex][colIndex];
-        if (!cell) {
-          document.body.appendChild(menu);
-          setTimeout(
-            () => document.addEventListener('click', this.menuCloseHandler),
-            0
-          );
-          return;
-        }
-
-        // 根据当前模式显示可用切换项
-        if (cell.mode === 'text' || !cell.mode) {
-          addItem('转为复选框模式', () =>
-            this.enableCheckboxMode(rowIndex, colIndex)
-          );
-          addItem('转为人员选择模式', () =>
-            this.enablePersonMode(rowIndex, colIndex)
-          );
-        } else if (cell.mode === 'checkbox') {
-          addItem('转为文本模式', () =>
-            this.disableCheckboxMode(rowIndex, colIndex)
-          );
-          addItem('转为人员选择模式', () =>
-            this.enablePersonMode(rowIndex, colIndex)
-          );
-        } else if (cell.mode === 'person') {
-          addItem('转为文本模式', () =>
-            this.disablePersonMode(rowIndex, colIndex)
-          );
-          addItem('转为复选框模式', () =>
-            this.enableCheckboxMode(rowIndex, colIndex)
-          );
-        }
-
-        // 当为复选框模式时,额外显示“添加复选框”
-        if (cell.mode === 'checkbox') {
-          addItem('添加复选框', () => this.addCheckbox(rowIndex, colIndex));
+        if (cell) {
+          const currentMode = cell.mode || 'text';
+          this.getTransitions(currentMode).forEach(({ label, method }) => {
+            addItem(label, () => this[method](rowIndex, colIndex));
+          });
+          // 复选框模式额外选项
+          if (currentMode === 'checkbox') {
+            addItem('添加复选框', () => this.addCheckbox(rowIndex, colIndex));
+          }
         }
 
+        // 渲染菜单
         document.body.appendChild(menu);
         setTimeout(() => {
           document.addEventListener('click', this.menuCloseHandler);
@@ -713,6 +765,20 @@
 
       onCheckboxLabelInput() {},
 
+      enableDeptMode(rowIndex, colIndex) {
+        const cell = this.tableData[rowIndex][colIndex];
+        if (!cell) return;
+        this.$set(cell, 'mode', 'dept');
+        this.$forceUpdate();
+      },
+
+      disableDeptMode(rowIndex, colIndex) {
+        const cell = this.tableData[rowIndex][colIndex];
+        if (!cell) return;
+        this.$set(cell, 'mode', 'text');
+        this.$forceUpdate();
+      },
+      onDeptChange() {},
       // ========== 🆕 人员选择 ==========
       enablePersonMode(rowIndex, colIndex) {
         const cell = this.tableData[rowIndex][colIndex];
@@ -862,6 +928,34 @@
           valueObj: { tableData }
         });
       },
+
+      async fetchDeptMap() {
+        if (this.deptMapLoaded) return;
+        try {
+          const res = await listOrganizations({ pageNum: 1, size: -1 });
+          // 递归展平树形数据
+          const flatten = (nodes) => {
+            let result = [];
+            nodes.forEach((node) => {
+              result.push({ id: node.id, name: node.name });
+              if (node.children && node.children.length) {
+                result = result.concat(flatten(node.children));
+              }
+            });
+            return result;
+          };
+          const flatList = flatten(res || []);
+          const map = {};
+          flatList.forEach((dept) => {
+            map[dept.id] = dept.name || '未知部门';
+          });
+          this.deptMap = map;
+          this.deptMapLoaded = true;
+        } catch (error) {
+          console.warn('获取部门列表失败,打印时将显示ID', error);
+        }
+      },
+
       // ========== 🆕 获取用户映射(供打印使用) ==========
       async fetchUserMap() {
         if (this.userMapLoaded) return;
@@ -882,9 +976,8 @@
       },
       // ========== 🆕 打印(异步 + 人员名称映射) ==========
       async printTable() {
-        // 1. 先获取用户映射(如果未加载)
         await this.fetchUserMap();
-
+        await this.fetchDeptMap();
         const tableEl = this.$el.querySelector('.custom-table');
         if (!tableEl) return;
 
@@ -895,16 +988,7 @@
           )
           .forEach((el) => el.remove());
 
-        // 处理 textarea
-        clone.querySelectorAll('textarea').forEach((ta) => {
-          const div = document.createElement('div');
-          div.textContent = ta.value;
-          div.style.cssText =
-            'text-align:center;word-break:break-all;padding:4px;white-space:pre-wrap;';
-          ta.parentNode.replaceChild(div, ta);
-        });
-
-        // 处理复选框和人员选择
+        // 遍历所有单元格,根据模式重新构建显示内容(保留 label)
         for (let r = 0; r < this.tableData.length; r++) {
           for (let c = 0; c < this.tableData[r].length; c++) {
             const cell = this.tableData[r][c];
@@ -913,56 +997,68 @@
             );
             if (!td) continue;
 
+            const label = cell.label || '';
+            let displayHtml = '';
+            td.style.padding = '3px';
             if (cell.mode === 'checkbox' && Array.isArray(cell.checkboxes)) {
-              const container = document.createElement('div');
-              container.style.cssText =
-                'display:flex; flex-wrap:wrap; gap:5px; justify-content:center;';
-              cell.checkboxes.forEach((cb) => {
-                const item = document.createElement('div');
-                item.style.cssText =
-                  'display:inline-flex; align-items:center; gap:5px; white-space:nowrap;';
-                const symbolSpan = document.createElement('span');
-                symbolSpan.textContent = cb.checked ? '☑' : '☐';
-                symbolSpan.style.fontSize = '14px';
-                const labelSpan = document.createElement('span');
-                labelSpan.textContent = cb.label || '';
-                item.appendChild(symbolSpan);
-                item.appendChild(labelSpan);
-                container.appendChild(item);
-              });
-              td.innerHTML = '';
-              td.appendChild(container);
+              const items = cell.checkboxes
+                .map((cb) => {
+                  const symbol = cb.checked ? '☑' : '☐';
+                  return `<span style="margin:0 4px;">${symbol} ${
+                    cb.label || ''
+                  }</span>`;
+                })
+                .join('');
+              displayHtml = `<div style="display:flex; align-items:center; justify-content:center; gap:5px; flex-wrap:wrap;">${
+                label ? `<span >${label}</span>` : ''
+              }${items}</div>`;
             } else if (cell.mode === 'person') {
-              // 🆕 使用映射显示人员名称
-              const userId = cell.value;
-              const userName = this.userMap[userId] || userId || '';
-              const div = document.createElement('div');
-              div.textContent = userName;
-              div.style.cssText = 'text-align:center;padding:4px;';
-              td.innerHTML = '';
-              td.appendChild(div);
+              const userName = this.userMap[cell.value] || cell.value || '';
+              displayHtml = `<div style="display:flex; align-items:center; justify-content:center; gap:5px;">${
+                label ? `<span >${label}</span>` : ''
+              }<span style="flex:1">${userName}</span></div>`;
+            } else if (cell.mode === 'dept') {
+              const deptName = this.deptMap[cell.value] || cell.value || '';
+              displayHtml = `<div style="display:flex; align-items:center; justify-content:center; gap:5px;">${
+                label ? `<span >${label}</span>` : ''
+              }<span style="flex:1">${deptName}</span></div>`;
+            } else {
+              console.log(cell.textAlign);
+              // 文本模式
+              displayHtml = `<div style="display:flex; align-items:center; justify-content:${
+                cell.textAlign == 'left'
+                  ? 'flex-start'
+                  : cell.textAlign == 'right'
+                  ? 'flex-end'
+                  : 'center'
+              };">${label ? `<span >${label}</span>` : ''}<span>${
+                cell.value || ''
+              }</span></div>`;
             }
+
+            td.innerHTML = displayHtml;
           }
         }
 
+        // 打印 iframe(保持不变)
         const iframe = document.createElement('iframe');
         iframe.style.cssText = 'position:absolute;width:0;height:0;border:0;';
         document.body.appendChild(iframe);
         const doc = iframe.contentWindow.document;
         doc.write(`
-          <!DOCTYPE html>
-          <html>
-            <head>
-              <meta charset="utf-8">
-              <style>
-                body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
-                table { width: 100%; border-collapse: collapse; table-layout: auto; }
-                td { border: 1px solid #000; vertical-align: middle; text-align: center; padding: 1px; font-size: 12px; padding:0px; }
-              </style>
-            </head>
-            <body>${clone.outerHTML}</body>
-          </html>
-        `);
+    <!DOCTYPE html>
+    <html>
+      <head>
+        <meta charset="utf-8">
+        <style>
+          body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
+          table { width: 100%; border-collapse: collapse; table-layout: auto; }
+          td { border: 1px solid #000; vertical-align: middle;  padding: 1px; font-size: 12px; padding:0px; }
+        </style>
+      </head>
+      <body>${clone.outerHTML}</body>
+    </html>
+  `);
         doc.close();
         iframe.contentWindow.focus();
         iframe.contentWindow.print();

+ 524 - 0
src/views/system/externalConfig/index.vue

@@ -0,0 +1,524 @@
+<template>
+  <div class="ele-body">
+    <el-card shadow="never" v-loading="loading">
+      <ele-split-layout
+        width="266px"
+        allow-collapse
+        :right-style="{ overflow: 'hidden' }"
+      >
+        <div>
+          <!-- 操作按钮 -->
+          <ele-toolbar class="ele-toolbar-actions">
+            <div style="margin: 5px 0">
+              <el-input
+                v-model="searchValue"
+                placeholder="请输入"
+                clearable
+                @keyup.enter.native="query"
+              />
+            </div>
+          </ele-toolbar>
+          <div class="ele-border-lighter sys-organization-list">
+            <div class="list-title">系统列表</div>
+            <div class="list-body">
+              <div
+                v-for="item in systemList"
+                :key="item.id"
+                class="list-item"
+                :class="{ active: current && current.id === item.id }"
+                @click="onNodeClick(item)"
+              >
+                <!-- <i class="el-icon-folder list-item-icon"></i> -->
+                <span class="list-item-text">{{ item.systemName }}  {{ item.systemVersion }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+        <template v-slot:content>
+          <div class="external-config-content">
+            <el-form
+              ref="form"
+              :model="form"
+              :rules="rules"
+              label-width="110px"
+              class="el-form-box external-config-form"
+            >
+              <!-- 基本信息 -->
+              <HeaderTitle title="基本信息"></HeaderTitle>
+              <el-row :gutter="20">
+                <el-col :span="8">
+                  <el-form-item label="系统名称:" prop="systemName">
+                    <el-input disabled v-model="form.systemName" placeholder="请输入" />
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="系统唯一编码:" prop="systemCode">
+                    <el-input disabled v-model="form.systemCode" placeholder="请输入" />
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="系统版本号:" prop="systemVersion">
+                    <el-input v-model="form.systemVersion" :disabled="!editable" placeholder="请输入" />
+                  </el-form-item>
+                </el-col>
+              </el-row>
+              <el-row :gutter="20">
+                <el-col :span="8">
+                  <el-form-item label="对接业务类型:" prop="directionType">
+                    <el-select
+                      v-model="form.directionType"
+                      :disabled="!editable"
+                      placeholder="请选择"
+                      style="width: 100%"
+                    >
+                      <el-option
+                        v-for="item in directionTypeOptions"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="接口生效状态:" prop="effectStatus">
+                    <el-select
+                      v-model="form.effectStatus"
+                      :disabled="!editable"
+                      placeholder="请选择"
+                      style="width: 100%"
+                    >
+                      <el-option
+                        v-for="item in effectStatusOptions"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
+              <!-- 业务类型 -->
+              <HeaderTitle style="margin-top: 20px" title="业务类型" />
+              <el-row :gutter="20">
+                <!-- 推送业务 -->
+                <el-col :span="24">
+                  <div class="sub-title">推送</div>
+                  <el-checkbox-group v-model="form.pushSelected" :disabled="!editable" class="checkbox-block">
+                    <el-checkbox
+                      v-for="item in pushList"
+                      :key="item.code"
+                      :label="item.code"
+                    >{{ item.name }}</el-checkbox>
+                  </el-checkbox-group>
+                </el-col>
+                <!-- 回传业务 -->
+                <el-col :span="24" style="margin-top: 16px">
+                  <div class="sub-title">回传</div>
+                  <el-checkbox-group v-model="form.callbackSelected" :disabled="!editable" class="checkbox-block">
+                    <el-checkbox
+                      v-for="item in callbackList"
+                      :key="item.code"
+                      :label="item.code"
+                    >{{ item.name }}</el-checkbox>
+                  </el-checkbox-group>
+                </el-col>
+              </el-row>
+
+              <!-- 授权规则 -->
+              <HeaderTitle style="margin-top: 20px" title="授权规则" />
+              <el-row :gutter="20">
+                <el-col :span="24">
+                  <div class="rule-tip">
+                    勾选业务类型后,需本公司授权通过,接口才生效启用
+                  </div>
+                </el-col>
+              </el-row>
+            </el-form>
+
+            <!-- 操作按钮 -->
+            <div class="external-config-footer">
+              <template v-if="$hasPermission('main:externalSystemConfig:update')">
+                <template v-if="!editable">
+                    <el-button type="primary" plain @click="editable = true">编辑</el-button>
+                </template>
+                <template v-else>
+                    <el-button @click="cancelEdit">取消</el-button>
+                    <el-button type="primary" :loading="saveLoading" @click="handleSave">保存配置</el-button>
+                </template>
+              </template>
+              <el-button v-if="!editable && $hasPermission('main:externalSystemConfig:enableOrDisable')" :type="form.effectStatus == 0 ? 'success' : 'danger'" :loading="enableLoading" @click="handleEnable">{{form.effectStatus == 0 ? '启用接口' : '禁用接口'}}</el-button>
+              <!-- <el-button type="warning" @click="handleApplyAuth">申请授权</el-button> -->
+              <!-- <el-button type="danger" plain @click="handleDelete">删除</el-button> -->
+            </div>
+          </div>
+        </template>
+      </ele-split-layout>
+    </el-card>
+ 
+  </div>
+</template>
+
+<script>
+import { externalSystemConfigPage, externalSystemConfigDetail, externalSystemConfigUpdate, externalSystemConfigEnableOrDisable } from '@/api/system/externalConfig';
+
+
+  export default {
+    name: 'ExternalConfig',
+    components: { },
+    data() {
+      return {
+        // 加载状态
+        loading: false,
+        // 保存/启用按钮防抖锁
+        saveLoading: false,
+        enableLoading: false,
+        // 编辑模式(默认非编辑,右侧只读)
+        editable: false,
+        // 左侧系统列表(示例数据)
+        systemList: [],
+        // 选中数据
+        current: null,
+        // 是否显示表单弹窗
+        showEdit: false,
+        // 编辑回显数据
+        editData: null,
+        // 上级id
+        parentId: null,
+
+        // ========== 外部对接配置 ==========
+        // 对接业务类型
+        directionTypeOptions: [
+          { value: 'EX', label: '对外' },
+          { value: 'IN', label: '对内' }
+        ],
+        // 接口生效状态
+        effectStatusOptions: [
+          { value: 0, label: '未生效' },
+          { value: 1, label: '已生效' }
+        ],
+        // 推送业务子项(由详情 bizInterfaceList 中 buisType=PUSH 生成)
+        pushList: [],
+        // 回传业务子项(由详情 bizInterfaceList 中 buisType=PULL 生成)
+        callbackList: [],
+
+        // 表单数据
+        form: {
+          systemName: '',
+          systemCode: '',
+          systemVersion: '',
+          directionType: '',
+          effectStatus: '',
+          businessType: '',
+          pushSelected: [],
+          callbackSelected: []
+        },
+        searchValue: '',
+
+        // 校验规则
+        rules: {
+        //   systemName: [
+        //     { required: true, message: '请输入系统名称', trigger: 'blur' }
+        //   ],
+        //   systemCode: [
+        //     { required: true, message: '请输入系统唯一编码', trigger: 'blur' }
+        //   ],
+        //   systemVersion: [
+        //     { required: true, message: '请输入系统版本号', trigger: 'blur' }
+        //   ],
+        //   directionType: [
+        //     { required: true, message: '请选择对接业务类型', trigger: 'change' }
+        //   ]
+        }
+      };
+    },
+    created() {
+      this.query();
+    },
+    methods: {
+      /* 查询 */
+      query() {
+        this.loading = true;
+        externalSystemConfigPage({
+          systemName: this.searchValue
+        })
+          .then((res) => {
+            console.log(res);
+            this.loading = false;
+            this.systemList = res;
+            console.log(this.systemList);
+            // 默认选中第一条并显示详情
+            if (this.systemList && this.systemList.length) {
+              this.onNodeClick(this.systemList[0]);
+            }
+          })
+          .catch((e) => {
+            this.loading = false;
+            // this.$message.error(e.message);
+          });
+      },
+      /* 选择数据 */
+      onNodeClick(row) {
+        this.current = row;
+        // 切换系统时回到只读模式
+        this.editable = false;
+        this.getDetail(row.id);
+      },
+      /* 取消编辑,还原为只读并重新加载详情 */
+      cancelEdit() {
+        this.editable = false;
+        if (this.current) {
+          this.getDetail(this.current.id);
+        }
+      },
+      async getDetail(id) {
+        const detail = await externalSystemConfigDetail(id);
+        // 合并保留表单默认字段(如多选数组),避免接口未返回时丢失
+        this.form = { ...this.form, ...detail };
+
+        // 根据 buisType 分组业务接口列表:PUSH=推送,PULL=回传
+        const bizList = detail.bizInterfaceList || [];
+        const pushList = [];
+        const callbackList = [];
+        const pushSelected = [];
+        const callbackSelected = [];
+        bizList.forEach((item) => {
+          if (item.buisType === 'PUSH') {
+            pushList.push({
+              code: item.bizInterfaceCode,
+              name: item.bizInterfaceName
+            });
+            if (item.isChecked === 1) {
+              pushSelected.push(item.bizInterfaceCode);
+            }
+          } else if (item.buisType === 'PULL') {
+            callbackList.push({
+              code: item.bizInterfaceCode,
+              name: item.bizInterfaceName
+            });
+            if (item.isChecked === 1) {
+              callbackSelected.push(item.bizInterfaceCode);
+            }
+          }
+        });
+        this.pushList = pushList;
+        this.callbackList = callbackList;
+        this.form.pushSelected = pushSelected;
+        this.form.callbackSelected = callbackSelected;
+
+        console.log(this.form);
+      },
+      /* 显示编辑 */
+      openEdit(item) {
+        this.editData = item;
+        this.showEdit = true;
+      },
+      /* 删除 */
+      remove() {
+        this.$confirm('确定要删除选中的机构吗?', '提示', {
+          type: 'warning'
+        })
+          .then(() => {
+            const loading = this.$loading({ lock: true });
+            removeOrganization([this.current.id])
+              .then((msg) => {
+                loading.close();
+                this.$message.success(msg);
+                this.query();
+              })
+              .catch((e) => {
+                loading.close();
+                // this.$message.error(e.message);
+              });
+          })
+          .catch(() => {});
+      },
+
+      /* 保存配置 */
+      handleSave() {
+        if (this.saveLoading) {
+          return;
+        }
+        this.$refs.form.validate((valid) => {
+          if (!valid) {
+            this.$message.warning('有必填项未填,请检查');
+            return;
+          }
+          this.saveLoading = true;
+          // 保持接口返回格式,仅根据勾选状态更新 isChecked(勾选=1,未勾选=0)
+          const bizInterfaceList = (this.form.bizInterfaceList || []).map(
+            (item) => {
+              const checked =
+                (item.buisType === 'PUSH' &&
+                  this.form.pushSelected.includes(item.bizInterfaceCode)) ||
+                (item.buisType === 'PULL' &&
+                  this.form.callbackSelected.includes(item.bizInterfaceCode));
+              return { ...item, isChecked: checked ? 1 : 0 };
+            }
+          );
+          const submitData = { ...this.form, bizInterfaceList };
+          console.log('保存配置', submitData);
+          externalSystemConfigUpdate(submitData)
+            .then((res) => {
+              this.$message.success('保存配置成功');
+              this.editable = false;
+              this.query();
+            })
+            .finally(() => {
+              this.saveLoading = false;
+            });
+        });
+      },
+      /* 申请授权 */
+      handleApplyAuth() {
+        console.log('申请授权', this.form);
+        this.$message.info('申请授权');
+      },
+      /* 启用接口 */
+      handleEnable() {
+        if (this.enableLoading) {
+          return;
+        }
+        this.enableLoading = true;
+        console.log('启用接口', this.form);
+        externalSystemConfigEnableOrDisable({
+          id: this.form.id,
+          effectStatus: this.form.effectStatus == 0 ? 1 : 0
+        })
+          .then((res) => {
+            this.$message.success(this.form.effectStatus == 0 ? '接口已启用' : '接口已禁用');
+            this.query();
+          })
+          .finally(() => {
+            this.enableLoading = false;
+          });
+      },
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  .sys-organization-list {
+    height: calc(100vh - 264px);
+    box-sizing: border-box;
+    border-width: 1px;
+    border-style: solid;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .sys-organization-list .list-title {
+    flex-shrink: 0;
+    font-size: 14px;
+    font-weight: 600;
+    color: #303133;
+    padding: 12px 16px;
+    border-bottom: 1px solid #ebeef5;
+    background: #fafafa;
+  }
+
+  .sys-organization-list .list-body {
+    flex: 1;
+    overflow-y: auto;
+    padding: 8px;
+  }
+
+  .sys-organization-list .list-item {
+    display: flex;
+    align-items: center;
+    height: 38px;
+    padding: 0 12px;
+    border-radius: 4px;
+    cursor: pointer;
+    color: #606266;
+    transition: background-color 0.2s, color 0.2s;
+  }
+
+  .sys-organization-list .list-item:hover {
+    background: #ecf5ff;
+    color: #409eff;
+  }
+
+  .sys-organization-list .list-item.active {
+    background: #409eff;
+    color: #fff;
+  }
+
+  .sys-organization-list .list-item-icon {
+    margin-right: 8px;
+    font-size: 15px;
+  }
+
+  .sys-organization-list .list-item-text {
+    flex: 1;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    font-size: 14px;
+  }
+
+  .external-config-content {
+    padding: 16px 20px;
+    height: 100%;
+    overflow-y: auto;
+    box-sizing: border-box;
+  }
+
+  .external-config-form {
+    :deep(.el-checkbox-group) {
+      display: flex;
+      flex-wrap: wrap;
+    }
+    :deep(.el-checkbox) {
+      margin-right: 24px;
+      margin-bottom: 8px;
+    }
+  }
+
+  .rule-tip {
+    color: #909399;
+    font-size: 13px;
+    padding: 6px 0 4px;
+  }
+
+  .sub-title {
+    font-size: 14px;
+    font-weight: 600;
+    color: #303133;
+    position: relative;
+    padding-left: 10px;
+    margin-bottom: 10px;
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 50%;
+      transform: translateY(-50%);
+      width: 3px;
+      height: 14px;
+      background: #409eff;
+      border-radius: 2px;
+    }
+  }
+
+  .checkbox-block {
+    display: flex;
+    flex-wrap: wrap;
+    padding-left: 10px;
+
+    :deep(.el-checkbox) {
+      margin-right: 24px;
+      margin-bottom: 8px;
+    }
+  }
+
+  .external-config-footer {
+    margin-top: 24px;
+    padding-top: 16px;
+    border-top: 1px solid #ebeef5;
+    text-align: left;
+  }
+</style>