Ver Fonte

暂时恢复为之前的查询结果页面,添加多选修改弹窗

chensibo há 3 dias atrás
pai
commit
336ec1ca76

+ 2 - 2
.env.dev

@@ -5,8 +5,8 @@ NODE_ENV = 'development'
 VUE_APP_TITLE = WMS开发环境
 
 # 芋道管理系统/开发环境
-VUE_APP_BASE_API = 'http://192.168.1.94:48080'
-# VUE_APP_BASE_API = 'http://192.168.1.26:48080'
+# VUE_APP_BASE_API = 'http://192.168.1.94:48080'
+VUE_APP_BASE_API = 'http://192.168.1.26:48080'
 # VUE_APP_BASE_API = 'http://113.105.183.190:48028'
 # VUE_APP_BASE_API = 'http://61.155.26.34:36936'
 # VUE_APP_BASE_API = 'http://127.0.0.1:48080'

+ 115 - 0
src/views/mes/queryManage/components/ParameterBatch.vue

@@ -0,0 +1,115 @@
+<template>
+  <el-dialog
+    title="批量修改"
+    :visible.sync="visible"
+    width="80%"
+    top="5vh"
+    append-to-body
+    @close="close"
+  >
+    <!-- 表格:每行就是原来的一条记录,列=所有字段 -->
+    <el-table :data="tableData" border size="mini" max-height="450">
+      <el-table-column
+        v-for="col in allCol"
+        :key="col.paramName"
+        :label="col.paramComment"
+        show-overflow-tooltip
+      >
+        <template slot-scope="{ row }">
+          <el-input
+            v-model="row[col.paramName]"
+            size="mini"
+            :disabled="!col.allowEdit"
+          />
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div slot="footer">
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" :loading="loading" @click="submit"
+        >确 定</el-button
+      >
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import request from "@/utils/request";
+
+export default {
+  name: "ParameterBatch",
+  data() {
+    return {
+      visible: false,
+      loading: false,
+      tableData: [], // 多条记录
+      allCol: [], // 所有字段(含只读)
+      buttonInfo: {},
+      masterId: "",
+    };
+  },
+  methods: {
+    /* 打开弹窗
+     * btn: 当前按钮配置
+     * masterId: 主表 id
+     * rows: 被勾选的原始行数据
+     * queryData: 父组件的列配置,用于把列注释映射到字段
+     */
+    open(btn, masterId, rows, queryData) {
+      this.buttonInfo = btn;
+      this.masterId = masterId;
+      // 1. 只保留“不是隐藏”的字段,和 Detail 保持一样
+      this.allCol = (btn.paramList || []).filter((p) => p.ifHide !== true);
+
+      // 2. 把 rows 转成"字段名→值"的 plain object
+      this.tableData = rows.map((r) => {
+        const obj = {};
+        this.allCol.forEach((p) => {
+          const commentKey = queryData?.find(
+            (q) => q.id === p.columnId
+          )?.columnComment;
+          obj[p.paramName] = commentKey ? r[commentKey] : "";
+        });
+        return obj;
+      });
+      this.visible = true;
+    },
+
+    close() {
+      this.visible = false;
+      this.tableData = [];
+    },
+
+    /* 一次性提交整包数组 */
+    async submit() {
+      this.loading = true;
+      try {
+        // 组装成后端需要的结构:List<Map<paramName, value>>
+        const payload = this.tableData.map((row) =>
+          this.allCol.reduce((acc, p) => {
+            acc[p.paramName] = row[p.paramName] ?? "";
+            return acc;
+          }, {})
+        );
+
+        // 如果后端只能单条,就循环;支持数组就直接发
+        const { restMethod = "post", interfaceUrl } = this.buttonInfo;
+        await request({
+          method: restMethod,
+          url: interfaceUrl,
+          data: payload, // 整包数组
+        });
+
+        this.$message.success("批量保存成功");
+        this.close();
+        this.$emit("success");
+      } catch (e) {
+        this.$message.error(e.message || "保存失败");
+      } finally {
+        this.loading = false;
+      }
+    },
+  },
+};
+</script>

+ 1157 - 0
src/views/mes/queryManage/query2.vue

@@ -0,0 +1,1157 @@
+<template>
+  <!-- 唯一根节点:el-card -->
+  <el-card class="el-content-wrap mb-3px" shadow="never">
+    <!-- 原 content-wrap 的 slot 内容 -->
+    <el-row class="mb-10px">
+      <template v-for="btn in dynamicButtons">
+        <el-button
+          v-if="!btn.ifHide"
+          :key="btn.id"
+          :type="getButtonType(btn)"
+          size="small"
+          @click="handleDynamicButtonClick(btn)"
+        >
+          <i
+            :class="getButtonIcon(btn)"
+            class="mr-5px"
+            style="font-size: 14px"
+          />
+          {{ btn.buttonName }}
+        </el-button>
+      </template>
+
+      <el-button size="small" type="primary" @click="Export">
+        <i class="el-icon-download mr-5px" style="font-size: 14px" />
+        导出当前页
+      </el-button>
+
+      <el-button
+        size="small"
+        type="success"
+        :loading="exportLoading"
+        @click="ExportAll"
+      >
+        <i class="el-icon-download mr-5px" style="font-size: 14px" />
+        导出全部
+      </el-button>
+
+      <el-button size="small" type="warning" @click="AllConditionReset">
+        <i class="el-icon-refresh mr-5px" style="font-size: 14px" />
+        全条件重置
+      </el-button>
+      <!-- <el-button size="small" type="warning" @click="handleRefresh">
+        <i class="el-icon-refresh mr-5px" style="font-size: 14px" />
+        全条件重置
+      </el-button> -->
+    </el-row>
+
+    <!-- 表格 -->
+    <el-table
+      id="table_excel"
+      ref="tableRef"
+      v-loading="loading"
+      :data="list"
+      border
+      show-overflow-tooltip
+      :row-style="rowStyle"
+      @selection-change="handleSelectionChange"
+      @header-dragend="handleResize"
+    >
+      <el-table-column
+        v-if="showMultipleList.length"
+        key="selection-column"
+        type="selection"
+        width="55"
+      />
+      <el-table-column type="index" width="55" align="center" />
+
+      <template v-for="(item, i) in queryData">
+        <el-table-column
+          v-if="!item.ifHide"
+          :key="item.id"
+          :label="item.columnComment"
+          :prop="item.columnComment"
+          align="center"
+          :min-width="item.javaField || ''"
+          :sortable="false"
+          show-overflow-tooltip
+        >
+          <template slot="header">
+            <div class="mr-5px flex items-center">
+              <!-- 排序图标 -->
+              <div v-show="item.ifSort" class="mr-5px flex flex-col gap-0">
+                <i
+                  class="el-icon-caret-top"
+                  style="font-size: 15px; cursor: pointer"
+                  :class="{
+                    'is-sort':
+                      currentSortField === item.columnComment &&
+                      currentSortDirection === 'asc',
+                  }"
+                  @click="handleSort(item.columnComment, 'asc')"
+                />
+                <i
+                  ref=""
+                  class="el-icon-caret-bottom -mt-1.2"
+                  style="font-size: 15px; cursor: pointer"
+                  :class="{
+                    'is-sort':
+                      currentSortField === item.columnComment &&
+                      currentSortDirection === 'desc',
+                  }"
+                  @click="handleSort(item.columnComment, 'desc')"
+                />
+              </div>
+
+              <span>{{ item.columnComment }}</span>
+
+              <!-- 列过滤 -->
+              <FilterColumnInQuery
+                v-if="item.listOperationResult && filterAlive"
+                :key="`filter_${item.id}_${filterKey}`"
+                :ref="`filter_${item.id}`"
+                :label="item.columnComment"
+                :prop="item.columnName"
+                :filter-list="queryData"
+                :query-id="$route.query.id"
+                :filter-list-key="item.columnName"
+                order-type="queryManage"
+                :param-list="queryParamList"
+                :active-filters="customFilters"
+                :html-type="item.htmlType"
+                :dict-type="item.dictType"
+                :is-clear="isClear"
+                @select-data="
+                  (data, field, htmlType) =>
+                    filterData(data, field, htmlType, item)
+                "
+              >
+                <template slot="reference">
+                  <div
+                    class="flex items-center justify-center text-gray-600"
+                    :class="{ 'has-filter': hasFilter(item.columnName) }"
+                  >
+                    <svg-icon
+                      icon-class="search"
+                      class="ml-2px"
+                      style="font-size: 14px; cursor: pointer"
+                    />
+                  </div>
+                </template>
+              </FilterColumnInQuery>
+            </div>
+          </template>
+
+          <template slot-scope="{ row }">
+            <div
+              :class="{ active: item.example }"
+              @click="
+                pathChange(item.example, row[item.columnComment], item, row)
+              "
+            >
+              <dict-tag
+                v-if="
+                  isDict(item.columnComment) &&
+                  ![undefined, null].includes(row[item.columnComment])
+                "
+                :type="isDict(item.columnComment)"
+                :value="row[item.columnComment]"
+              />
+              <span
+                v-else-if="
+                  row[item.columnComment] && isDateTime(item.columnComment)
+                "
+              >
+                {{ formatToDateTime(row[item.columnComment]) }}
+              </span>
+              <span v-else>{{ row[item.columnComment] }}</span>
+            </div>
+          </template>
+        </el-table-column>
+      </template>
+    </el-table>
+
+    <!-- 分页 -->
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="pageNo"
+      :limit.sync="pageSize"
+      :page-sizes="[10, 20, 50, 100, 200, 500, 1000, 5000]"
+      layout="total, sizes, prev, pager, next, jumper"
+      @pagination="handleSearch"
+    />
+
+    <!-- 弹窗 -->
+    <ReportPrint ref="reportPrint" />
+    <ParameterDetail ref="parameterDetailRef" @success="getQueryData" />
+  </el-card>
+</template>
+
+<script>
+import * as QueryManageApi from "@/api/mes/queryManage";
+import { getQueryManageButtonPage } from "@/api/mes/queryManage/button";
+import { saveAs } from "file-saver";
+import * as XLSX from "xlsx";
+import FilterColumnInQuery from "./components/FilterColumnInQuery.vue";
+import DictTag from "@/components/DictTag";
+import ParameterDetail from "./components/ParameterDetail.vue";
+import ReportPrint from "./components/ReportPrint.vue";
+import request from "@/utils/request";
+import { getAccessToken, getTenantId } from "@/utils/auth";
+import axios from "axios";
+// 此组件使用缓存,在querymanage/queryFormNew页面之间切换时,会先查看缓存。
+export default {
+  name: "QueryForm",
+  components: {
+    FilterColumnInQuery,
+    ParameterDetail,
+    ReportPrint,
+    DictTag,
+  },
+  data() {
+    return {
+      title: "", // ContentWrap 的 title
+      message: "", // ContentWrap 的 message
+      baseUrl: "",
+      queryList: [],
+      searchData: [],
+      queryData: [],
+      list: [],
+      originalList: [],
+      pageNo: 1,
+      pageSize: 20,
+      total: 0,
+      loading: false,
+      form: {},
+      visible: false,
+      customFilters: {},
+      currentSortField: null,
+      currentSortDirection: null,
+      dynamicButtons: [],
+      queryParamList: [],
+      exportLoading: false,
+      showMultipleList: [],
+      isClear: false,
+      multipleSelection: [],
+      headerWidthChange: false,
+      filterKey: 0,
+      filterAlive: true,
+      pageCache: {}, // { [id]: 快照 }
+      curPageId: null, // 当前正在展示的页面 id
+    };
+  },
+  watch: {
+    // 监听路由参数变化,确保标题正确更新
+    "$route.query.name": {
+      handler(newName) {
+        this.title =
+          newName || this.$route.redirectedFrom?.meta?.title || "查询";
+      },
+    },
+    // 监听路由参数变化,确保数据正确加载
+    "$route.query.id": {
+      handler() {
+        this.loadIfNeed();
+      },
+    },
+    // filterAlive(v) {
+    //   console.log(
+    //     `[%c父%c] filterAlive 变为 ${v}  ${Date.now()}`,
+    //     "color:blue",
+    //     ""
+    //   );
+    // },
+  },
+  activated() {
+    this.loadIfNeed();
+  },
+  beforeDestroy() {
+    this.saveColumnWidth();
+  },
+  beforeCreate() {
+    const max = 10;
+    this.$watch(
+      "pageCache",
+      function (val) {
+        const keys = Object.keys(val);
+        if (keys.length > max) delete val[keys[0]]; // LRU
+      },
+      { deep: true }
+    );
+  },
+  created() {
+    this.baseUrl = process.env.VUE_APP_REPORT_API;
+    this.loadIfNeed();
+  },
+  methods: {
+    // 新增:一次性拿齐头部与表格数据
+    async loadPageData(id, params = {}) {
+      const [col, btn, table] = await Promise.all([
+        QueryManageApi.getQueryManageColumnListByMasterId(id),
+        getQueryManageButtonPage({ masterId: id }),
+        QueryManageApi.loadTableData({
+          pageNo: params.pageNo || 1,
+          pageSize: params.pageSize || 20,
+          paramList: params.paramList || [],
+          id,
+        }),
+      ]);
+      const list = table?.data?.list || [];
+      return {
+        queryData: Array.isArray(col.data) ? col.data : [],
+        dynamicButtons: btn?.data?.list || [],
+        list,
+        total: table?.data?.total || 0,
+        originalList: JSON.parse(JSON.stringify(list)),
+        // 用户现场
+        queryParamList: params.paramList || [],
+        customFilters: params.customFilters || {},
+        pageNo: params.pageNo || 1,
+        pageSize: params.pageSize || 20,
+        currentSortField: params.currentSortField || "",
+        currentSortDirection: params.currentSortDirection || "",
+      };
+    },
+    // 统一入口:有缓存直接恢复,没有才调接口
+    loadIfNeed() {
+      const id = this.$route.query.id;
+      if (!id) return;
+
+      this.title =
+        this.$route.query.name ||
+        this.$route.redirectedFrom?.meta?.title ||
+        "查询";
+
+      // 命中缓存 → 恢复现场
+      if (this.pageCache[id]) {
+        Object.assign(this, this.pageCache[id]);
+        this.filterKey += 1;
+        return;
+      }
+
+      // 未命中 → 第一次进或缓存被清,走接口
+      this.loading = true;
+      this.loadPageData(id)
+        .then((data) => {
+          Object.assign(this, data);
+          this.$set(this.pageCache, id, data);
+        })
+        .finally(() => {
+          this.loading = false;
+        });
+    },
+    /* 生命周期 */
+    async init() {
+      this.title =
+        this.$route.query?.name ||
+        this.$route.redirectedFrom?.meta?.title ||
+        "查询";
+      // 需要提示文字时,再写 this.message = 'xxx'
+      if (this.$route.query.infraQueryId) {
+        await this.getFilterData();
+      } else {
+        this.buildQueryParamFromRoute();
+        await this.getQueryData();
+      }
+    },
+
+    /* 以下所有方法保持与原文件一致,不再赘述 */
+    async getQueryData() {
+      try {
+        this.loading = true;
+        const data = await QueryManageApi.getQueryManageColumnListByMasterId(
+          this.$route.query.id
+        );
+        // 确保queryData是数组
+        this.queryData = Array.isArray(data.data) ? data.data || [] : [];
+        this.searchData = this.queryData.filter(
+          (item) => item.listOperationResult && item.ifHide === false
+        );
+        this.showMultipleList = this.queryData.filter(
+          (item) => item.listOperationCondition === "1"
+        );
+        this.queryList = [];
+        for (let i = 0; i < this.searchData.length; i++) {
+          const { dataType, htmlType, dictType, columnName } =
+            this.searchData[i];
+          this.queryList.push({
+            dataType,
+            htmlType,
+            dictType,
+            key: columnName,
+            operate: dataType === "ym_string_query_type" ? "like" : "",
+          });
+        }
+        await this.getManageButtons(this.$route.query?.id);
+        await this.getList();
+      } finally {
+        this.loading = false;
+      }
+    },
+
+    async getList() {
+      // console.log("触发了getList方法");
+      try {
+        this.loading = true;
+        const data = await QueryManageApi.loadTableData({
+          pageNo: this.pageNo,
+          pageSize: this.pageSize,
+          paramList: this.queryParamList,
+          id: this.$route.query?.id,
+        });
+        this.list = data?.data?.list || [];
+        this.originalList = JSON.parse(JSON.stringify(data?.data?.list || []));
+        this.total = data?.data?.total || 0;
+      } catch (error) {
+        console.error("getList错误:", error);
+      } finally {
+        this.loading = false;
+        // ===== 新增:把当前现场写回缓存 =====
+        const id = this.$route.query.id;
+        if (id && this.pageCache[id]) {
+          this.pageCache[id] = {
+            ...this.pageCache[id],
+            list: this.list,
+            total: this.total,
+            originalList: this.originalList,
+            queryParamList: this.queryParamList,
+            customFilters: this.customFilters,
+            pageNo: this.pageNo,
+            pageSize: this.pageSize,
+            currentSortField: this.currentSortField,
+            currentSortDirection: this.currentSortDirection,
+          };
+        }
+        // =====================================
+      }
+    },
+
+    async getManageButtons(masterId) {
+      const data = await getQueryManageButtonPage({ masterId });
+      this.dynamicButtons = data?.data?.list || [];
+    },
+
+    // async handleDynamicButtonClick(btn = {}) {
+    //   const {
+    //     ifJimu,
+    //     interfaceUrl,
+    //     printSoft,
+    //     operateType,
+    //     paramList = [],
+    //   } = btn;
+
+    //   // 确认数据值和类型
+    //   if (ifJimu) {
+    //     this.handlePrintByJimu(btn);
+    //   } else if (this.isFullUrl(interfaceUrl) && printSoft) {
+    //     this.handlePrintByOtherSoft(btn);
+    //   } else if (operateType === 2) {
+    //     this.$refs.parameterDetailRef.open(
+    //       "create",
+    //       btn,
+    //       this.$route.query?.id,
+    //       [],
+    //       this.queryData
+    //     );
+    //   } else if (operateType === 3) {
+    //     if (this.multipleSelection.length === 0) {
+    //       return this.$message.warning("请先选择行数据!");
+    //     }
+    //     if (this.multipleSelection.length > 1) {
+    //       return this.$message.warning("只能选择单条数据修改!");
+    //     }
+    //     this.$refs.parameterDetailRef.open(
+    //       "update",
+    //       btn,
+    //       this.$route.query?.id,
+    //       this.multipleSelection,
+    //       this.queryData
+    //     );
+    //   } else if (operateType === 7) {
+    //     this.$router.push({ path: `${interfaceUrl}` });
+    //   } else {
+    //     this.handleDynamic(btn);
+    //   }
+    // },
+    async handleDynamicButtonClick(btn = {}) {
+      const {
+        ifJimu,
+        interfaceUrl,
+        printSoft,
+        operateType,
+        paramList = [],
+        ifArray,
+      } = btn;
+
+      // 统一校验函数
+      const checkSelection = () => {
+        if (this.multipleSelection.length === 0) {
+          this.$message.warning("请先选择数据!");
+          return false;
+        }
+        // 核心:根据 ifArray 决定是否允许多选
+        if (ifArray !== true && this.multipleSelection.length > 1) {
+          this.$message.warning("只能选择单条数据!");
+          return false;
+        }
+        return true;
+      };
+
+      if (ifJimu) {
+        if (!checkSelection()) return;
+        this.handlePrintByJimu(btn);
+      } else if (this.isFullUrl(interfaceUrl) && printSoft) {
+        if (!checkSelection()) return;
+        this.handlePrintByOtherSoft(btn);
+      } else if (operateType === 2) {
+        // 新增不需要选数据
+        this.$refs.parameterDetailRef.open(
+          "create",
+          btn,
+          this.$route.query?.id,
+          [],
+          this.queryData
+        );
+      } else if (operateType === 3) {
+        // 修改
+        if (!checkSelection()) return;
+        this.$refs.parameterDetailRef.open(
+          "update",
+          btn,
+          this.$route.query?.id,
+          this.multipleSelection,
+          this.queryData
+        );
+      } else if (operateType === 7) {
+        this.$router.push({ path: `${interfaceUrl}` });
+      } else {
+        // 自定义按钮
+        if (!checkSelection()) return;
+        this.handleDynamic(btn);
+      }
+    },
+
+    getButtonIcon(btn) {
+      const { buttonName, operateType } = btn;
+      const name = buttonName.toLowerCase();
+
+      // 根据按钮名称关键词分配图标
+      if (
+        name.includes("新增") ||
+        name.includes("添加") ||
+        name.includes("创建")
+      ) {
+        return "el-icon-circle-plus";
+      } else if (name.includes("保存") || name.includes("提交")) {
+        return "el-icon-check";
+      } else if (name.includes("审核") || name.includes("审批")) {
+        return "el-icon-document-checked";
+      }
+
+      // 根据操作类型分配图标
+      switch (operateType) {
+        case 2: // 新增
+          return "el-icon-circle-plus";
+        case 3: // 修改
+          return "el-icon-edit";
+        case 4: // 删除
+          return "el-icon-delete";
+        case 5: // 导出
+          return "el-icon-download";
+        case 6: // 打印
+          return "el-icon-printer";
+        case 7: // 跳转
+          return "el-icon-right";
+        default:
+          return "el-icon-star-off"; // 默认图标
+      }
+    },
+
+    getButtonType(btn) {
+      const { operateType } = btn;
+
+      // 根据操作类型分配
+      switch (operateType) {
+        case 2: // 新增
+          return "success";
+        case 3: // 修改
+          return "warning";
+        case 4: // 删除
+          return "danger";
+        case 5: // 导出
+          return "primary";
+        case 6: // 打印
+          return "info";
+        case 7: // 跳转
+          return "success";
+        default:
+          return "primary";
+      }
+    },
+
+    formatToDateTime(val) {
+      const d = val ? new Date(val) : new Date();
+      if (isNaN(d.getTime())) return "";
+      const pad = (n) => String(n).padStart(2, "0");
+      return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(
+        d.getDate()
+      )} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
+    },
+
+    handlePrintByJimu(params) {
+      // if (this.multipleSelection.length === 0) {
+      //   return this.$message.warning("请先选择数据!");
+      // }
+      // if (this.multipleSelection.length > 1) {
+      //   return this.$message.warning("只能选择单条数据打印!");
+      // }
+      // 确认数据值和类型
+
+      // 验证interfaceUrl
+      const interfaceUrl = String(params.interfaceUrl || "").trim();
+      if (!interfaceUrl || interfaceUrl === "0") {
+        return this.$message.error("接口地址未配置或格式错误!");
+      }
+
+      const token = getAccessToken();
+      const tenantId = getTenantId();
+      const { paramList = [], reportId } = params;
+      const fieldValues = this.paramsToConver(paramList);
+      const printParams = new URLSearchParams({
+        token,
+        tenantId,
+        ...fieldValues,
+      });
+      const src = `${
+        this.baseUrl
+      }${interfaceUrl}${reportId}?${printParams.toString()}`;
+      this.$refs.reportPrint.open(
+        src,
+        this.$route.query?.name ||
+          this.$route.redirectedFrom?.meta?.title ||
+          "报表打印"
+      );
+    },
+
+    async handlePrintByOtherSoft(params) {
+      // if (this.multipleSelection.length === 0) {
+      //   return this.$message.warning("请先选择数据!");
+      // }
+
+      // 确认数据值和类型
+
+      const { interfaceUrl, paramList, restMethod, templet } = params;
+      if (!templet) return this.$message.warning("未配置打印模板!");
+
+      // 确保 url 是合法字符串,而不是数字 0 或其他无效值
+      const url = String(interfaceUrl || "").trim();
+      if (!url || url === "0") {
+        this.$message.error("接口地址未配置或格式错误!");
+        return; // 确保return掉
+      }
+
+      const fieldValues = this.paramsToConver(paramList);
+      const printParams = { ...fieldValues, fileUrl: templet };
+      const config = {
+        method: restMethod,
+        url,
+        [restMethod === "get" || restMethod === "delete" ? "params" : "data"]:
+          printParams,
+      };
+      await axios(config);
+      this.$message.success("操作成功!");
+      this.getList();
+    },
+
+    async handleDynamic(params) {
+      // if (this.multipleSelection.length === 0) {
+      //   return this.$message.warning("请先选择数据!");
+      // }
+
+      // 确认数据值和类型
+
+      const {
+        interfaceUrl,
+        restMethod = "get",
+        paramList = [],
+        requestParameter = "0",
+      } = params;
+
+      // 确保 url 是合法字符串,而不是数字 0 或其他无效值
+      const url = String(interfaceUrl || "").trim();
+      if (!url || url === "0") {
+        this.$message.error("接口地址未配置或格式错误!");
+        return; // 确保return掉
+      }
+
+      const fieldValues = this.paramsToConver(paramList);
+      const config = { method: restMethod, url };
+      this.handleGetDelete(config, fieldValues, String(requestParameter));
+
+      // 直接使用request(config)而不是request[restMethod]?.(config),确保method被正确处理
+      await request(config);
+
+      this.$message.success("操作成功");
+      this.getList();
+    },
+
+    handleGetDelete(config, fieldValues, requestParameter) {
+      switch (requestParameter) {
+        case "1":
+          config.params = fieldValues;
+          break;
+        case "3":
+          const queryString = new URLSearchParams(fieldValues).toString();
+          config.url = queryString
+            ? `${config.url}?${queryString}`
+            : config.url;
+          break;
+        case "4":
+          config.headers = { ...config.headers, ...fieldValues };
+          break;
+        default:
+          config.data = fieldValues;
+      }
+    },
+
+    paramsToConver(paramList) {
+      const matchListWithParam = paramList
+        ?.filter((item) =>
+          this.queryData.some((query) => item.columnId === query?.id)
+        )
+        .map((item) => {
+          const paramItem = this.queryData.find(
+            (query) => item.columnId === query?.id
+          );
+          return { ...item, columnComment: paramItem?.columnComment };
+        });
+      const result = this.generateMappedData(
+        this.multipleSelection,
+        matchListWithParam
+      );
+      const allFields = [...new Set(result.flatMap(Object.keys))];
+      return allFields.reduce((acc, field) => {
+        acc[field] = result.map((item) => item[field]).join(",");
+        return acc;
+      }, {});
+    },
+
+    generateMappedData(selectList, keys) {
+      return selectList.map((item) =>
+        keys.reduce((res, key) => {
+          const { columnComment, paramName } = key;
+          if (item.hasOwnProperty(columnComment)) {
+            const field = paramName.split(".").pop();
+            res[field] = item[columnComment];
+          }
+          return res;
+        }, {})
+      );
+    },
+
+    isFullUrl(url) {
+      try {
+        const parsed = new URL(url);
+        return parsed.protocol === "http:" || parsed.protocol === "https:";
+      } catch {
+        return false;
+      }
+    },
+
+    isDict(key) {
+      if (!Array.isArray(this.queryData)) return false;
+      const item = this.queryData.find((item) => item.columnComment === key);
+      return item ? item.dictType : false;
+    },
+
+    isNumber(key) {
+      if (!Array.isArray(this.queryData)) return false;
+      const item = this.queryData.find((item) => item.columnComment === key);
+      return item && item.dataType === "ym_int_query_type";
+    },
+
+    // isDateTime(key) {
+    //   if (!Array.isArray(this.queryData)) return false;
+    //   const item = this.queryData.find((item) => item.columnComment === key);
+    //   return item && item.javaType === "LocalDateTime";
+    // },
+    isDateTime(key) {
+      if (!Array.isArray(this.queryData)) return false;
+      const item = this.queryData.find((item) => item.columnComment === key);
+      if (!item) return false;
+
+      // 扩展更多时间类型
+      const timeTypes = [
+        "LocalDateTime",
+        "LocalDate",
+        "LocalTime",
+        "Date",
+        "Timestamp",
+        "DateTime",
+        "java.util.Date",
+        "java.sql.Date",
+        "java.sql.Timestamp",
+      ];
+
+      return timeTypes.includes(item.javaType);
+    },
+
+    hasFilter(field) {
+      return !!(this.customFilters[field] && this.customFilters[field].length);
+    },
+
+    handleSort(field, direction) {
+      if (
+        this.currentSortField === field &&
+        this.currentSortDirection === direction
+      ) {
+        this.currentSortField = "";
+        this.currentSortDirection = "";
+        this.list = JSON.parse(JSON.stringify(this.originalList));
+        return;
+      }
+      this.currentSortField = field;
+      this.currentSortDirection = direction;
+      this.list = [...this.originalList].sort((a, b) => {
+        let valA = a[field];
+        let valB = b[field];
+        valA =
+          typeof valA === "string" ? parseFloat(valA.replace(/,/g, "")) : valA;
+        valB =
+          typeof valB === "string" ? parseFloat(valB.replace(/,/g, "")) : valB;
+        if (isNaN(valA)) {
+          return direction === "asc"
+            ? valA > valB
+              ? 1
+              : -1
+            : valA < valB
+            ? 1
+            : -1;
+        }
+        if (isNaN(valB)) {
+          return direction === "asc"
+            ? valA > valB
+              ? 1
+              : -1
+            : valA < valB
+            ? 1
+            : -1;
+        }
+        return direction === "asc" ? valA - valB : valB - valA;
+      });
+    },
+
+    filterData(data, field, type, row) {
+      const { dataType } = row;
+      this.$set(this.customFilters, field, data);
+      const isDateRange = data[0]?.isRange;
+      if (isDateRange) {
+        const valueList = data.flatMap((item) => item.column).join(",");
+        const idx = this.queryParamList.findIndex((item) => item.key === field);
+        if (idx > -1) {
+          this.queryParamList[idx].value = valueList;
+        } else {
+          this.queryParamList.push({
+            key: field,
+            operate: "like",
+            htmlType: type,
+            value: valueList,
+          });
+        }
+        this.getList();
+        return;
+      }
+      let valueStr;
+      if (data.length) {
+        valueStr =
+          dataType === "ym_int_query_type"
+            ? data.map((item) => item.column).join(",")
+            : data.map((item) => `'${item.column}'`).join(",");
+        const idx = this.queryParamList.findIndex((item) => item.key === field);
+        if (idx > -1) {
+          this.queryParamList[idx].value = valueStr;
+        } else {
+          this.queryParamList.push({
+            key: field,
+            operate: "like",
+            htmlType: type,
+            value: valueStr,
+          });
+        }
+      } else {
+        const idx = this.queryParamList.findIndex((item) => item.key === field);
+        if (idx > -1) this.queryParamList.splice(idx, 1);
+      }
+      this.getList();
+    },
+
+    AllConditionReset() {
+      /* 0. 先让表格进入“空壳”状态 */
+      this.filterAlive = false;
+      const tmp = this.queryData; // 暂存
+      this.queryData = []; // 强制清空
+
+      // 清空过滤和排序条件
+      this.queryParamList = [];
+      this.customFilters = {};
+      this.currentSortField = null;
+      this.currentSortDirection = null;
+      this.pageNo = 1;
+      this.pageSize = 20;
+      this.multipleSelection = [];
+      this.isClear = true;
+
+      this.$nextTick(() => {
+        /* 1. 再一次性把列塞回去,配合新的 filterKey */
+        this.filterKey++;
+        this.queryData = tmp;
+        /* 2. 重新激活 filter 组件 */
+        this.filterAlive = true; // ← 关键:重新激活
+
+        /* 3. 等待组件重新渲染后获取数据 */
+        this.$nextTick(() => {
+          this.getList();
+          // 重置 isClear 状态
+          setTimeout(() => (this.isClear = false), 1000);
+        });
+      });
+    },
+    handleSearch() {
+      this.list = [];
+      this.getList();
+    },
+
+    async Export() {
+      try {
+        await this.$confirm("确定导出当前页?", "提示", { type: "warning" });
+
+        // 当前页条数
+        const realSize = this.list.length;
+        if (!realSize) {
+          this.$message.warning("当前无数据可导出");
+          return;
+        }
+
+        // 用后端导出接口,只把当前页码 & 条数发过去
+        const data = await QueryManageApi.exportExcel({
+          id: this.$route.query?.id,
+          paramList: this.queryParamList,
+          pageNo: this.pageNo, // 当前页码
+          pageSize: realSize, // 当前页实际条数
+        });
+
+        // 同一套下载逻辑
+        this.$download.excel(data, `${this.title}_第${this.pageNo}页.xls`);
+      } catch (error) {
+        if (error !== "cancel") {
+          console.error("导出失败:", error);
+          this.$message.error("导出失败");
+        }
+      }
+    },
+
+    async ExportAll() {
+      try {
+        await this.$confirm("确定导出全部?", "提示", { type: "warning" });
+        this.exportLoading = true;
+        const data = await QueryManageApi.exportExcel({
+          id: this.$route.query?.id,
+          paramList: this.queryParamList,
+        });
+        this.$download.excel(data, this.title + ".xls");
+      } catch (error) {
+        // 忽略取消操作的错误
+        if (error !== "cancel") {
+          console.error("导出失败:", error);
+          this.$message.error("导出失败");
+        }
+      } finally {
+        this.exportLoading = false;
+      }
+    },
+
+    getHandleNumber(val) {
+      return Math.round(parseFloat(val) * 100000000) / 100000000;
+    },
+
+    getSummaries({ columns }) {
+      const sums = [];
+      columns.forEach((column, index) => {
+        if (index === 0) {
+          sums[index] = "总计";
+          return;
+        }
+        const values = this.list.map((item) => Number(item[column.property]));
+        if (this.isNumber(column.property)) {
+          sums[index] = values.reduce((acc, cur) => {
+            const value = Number(cur);
+            return !isNaN(value) ? this.getHandleNumber(acc + cur) : acc;
+          }, 0);
+        }
+      });
+      return sums;
+    },
+
+    handleSelectionChange(val) {
+      this.multipleSelection = val;
+    },
+
+    pathChange(path, val, item, row) {
+      if (path === "sameRouter") return;
+      if (!path) return;
+      const data = {};
+      this.dynamicButtons
+        .filter((v) => v.ifHide && v.paramList.length)
+        .forEach((v) =>
+          v.paramList.forEach((val) => {
+            data[val.paramName] = val.value || row[val.paramComment];
+          })
+        );
+      this.$router.push({ path: `${path}`, query: { id: val, ...data } });
+    },
+
+    handleBack() {
+      this.$router.push({ path: "/querymanage/queryManageIndex" });
+    },
+
+    handleResize() {
+      this.headerWidthChange = true;
+    },
+
+    async saveColumnWidth() {
+      if (!this.headerWidthChange) return;
+      this.headerWidthChange = false;
+
+      const widthList = [];
+      // 1. 所有“逻辑上应该显示”的列
+      const visibleMap = new Map(
+        this.queryData.filter((c) => !c.ifHide).map((c) => [c.columnComment, c]) // 用列名当 key
+      );
+
+      // 2. 真正渲染出来的 th
+      const thList = Array.from(
+        this.$refs.tableRef.$el.querySelectorAll(
+          ".el-table__header-wrapper tr th"
+        )
+      ).filter(
+        (th) =>
+          !th.classList.contains("el-table-column--selection") &&
+          !th.classList.contains("el-table-column--index")
+      );
+
+      thList.forEach((th) => {
+        const w = th.offsetWidth;
+        if (!w) return; // 虚拟滚动或隐藏时直接跳过
+        const comment = th.innerText.trim();
+        const col = visibleMap.get(comment);
+        if (col) widthList.push({ id: col.id, javaField: w });
+      });
+
+      if (widthList.length) {
+        await QueryManageApi.updateWidth(widthList);
+      }
+    },
+
+    buildQueryParamFromRoute() {
+      const queryParam = [];
+      for (const i in this.$route.query) {
+        if (i !== "id" && i !== "name") {
+          queryParam.push({
+            key: i,
+            operate: "like",
+            htmlType: "input",
+            value: "'" + this.$route.query[i] + "'",
+          });
+        }
+      }
+      this.queryParamList = queryParam;
+    },
+
+    async getFilterData() {
+      const data = await getQueryManageButtonPage({
+        masterId: this.$route.query?.infraQueryId,
+      });
+      const arr = [];
+      data.list
+        .filter((v) => v.ifHide && v.paramList.length)
+        .forEach((v) =>
+          v.paramList.forEach((val) => {
+            if (val.filter) arr.push(val.paramName);
+          })
+        );
+      const queryParam = [];
+      for (const i in this.$route.query) {
+        if (arr.indexOf(i) > -1) {
+          queryParam.push({
+            key: i,
+            operate: "like",
+            htmlType: "input",
+            value: "'" + this.$route.query[i] + "'",
+          });
+        }
+      }
+      this.queryParamList = queryParam;
+      await this.getQueryData();
+    },
+    handleRefresh() {
+      const id = this.$route.query.id;
+      if (!id) return;
+      this.$delete(this.pageCache, id); // 清缓存
+      this.loading = true;
+      this.loadPageData(id)
+        .then((data) => {
+          Object.assign(this, data);
+          this.$set(this.pageCache, id, data);
+        })
+        .finally(() => {
+          this.loading = false;
+        });
+    },
+    rowStyle({ row }) {
+      // 有值才上色,没有就保持默认
+      return row.colorControl ? { backgroundColor: row.colorControl } : {};
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+/* ---------- ContentWrap 原样式 ---------- */
+.el-content-wrap {
+  /* 自定义 */
+  overflow-y: auto;
+}
+.text-16px {
+  font-size: 16px;
+}
+.font-700 {
+  font-weight: 700;
+}
+.ml-5px {
+  margin-left: 5px;
+}
+.max-w-200px {
+  max-width: 200px;
+}
+.mb-3px {
+  margin-bottom: 3px;
+}
+
+/* ---------- 原来的表格样式 ---------- */
+:deep(.el-table__row) {
+  height: 10px !important;
+}
+.el-table--medium .el-table__cell,
+.el-table--small .el-table__cell {
+  padding: 1px 0 !important;
+}
+.active:hover {
+  cursor: pointer;
+}
+.has-filter,
+.is-sort,
+.active {
+  color: #1890ff !important;
+}
+</style>

+ 46 - 104
src/views/mes/queryManage/queryNew.vue

@@ -185,6 +185,7 @@
     <!-- 弹窗 -->
     <ReportPrint ref="reportPrint" />
     <ParameterDetail ref="parameterDetailRef" @success="getQueryData" />
+    <ParameterBatch ref="parameterBatchRef" @success="getQueryData" />
   </el-card>
 </template>
 
@@ -196,16 +197,18 @@ import * as XLSX from "xlsx";
 import FilterColumnInQuery from "./components/FilterColumnInQuery.vue";
 import DictTag from "@/components/DictTag";
 import ParameterDetail from "./components/ParameterDetail.vue";
+import ParameterBatch from "./components/ParameterBatch.vue";
 import ReportPrint from "./components/ReportPrint.vue";
 import request from "@/utils/request";
 import { getAccessToken, getTenantId } from "@/utils/auth";
 import axios from "axios";
-// 此组件使用了缓存,在/querymanage/queryFormNew页面之间切换时,先在缓存中搜索,如果命中了先使用缓存数据,没有命中则调用接口
+// 此组件没有使用缓存,在不同页面之间切换时,会调用接口重新获取数据
 export default {
   name: "QueryForm",
   components: {
     FilterColumnInQuery,
     ParameterDetail,
+    ParameterBatch,
     ReportPrint,
     DictTag,
   },
@@ -237,8 +240,7 @@ export default {
       headerWidthChange: false,
       filterKey: 0,
       filterAlive: true,
-      pageCache: {}, // { [id]: 快照 }
-      curPageId: null, // 当前正在展示的页面 id
+      parameterBatchRef: null,
     };
   },
   watch: {
@@ -252,7 +254,7 @@ export default {
     // 监听路由参数变化,确保数据正确加载
     "$route.query.id": {
       handler() {
-        this.loadIfNeed();
+        this.init();
       },
     },
     // filterAlive(v) {
@@ -263,84 +265,23 @@ export default {
     //   );
     // },
   },
+  mounted() {
+    this.init();
+    this.$nextTick(() => {
+      this.parameterBatchRef = this.$refs.parameterBatchRef;
+    });
+  },
   activated() {
-    this.loadIfNeed();
+    // 每次激活标签页时都调用init(),确保数据正确加载
+    this.init();
   },
   beforeDestroy() {
     this.saveColumnWidth();
   },
-  beforeCreate() {
-    const max = 10;
-    this.$watch(
-      "pageCache",
-      function (val) {
-        const keys = Object.keys(val);
-        if (keys.length > max) delete val[keys[0]]; // LRU
-      },
-      { deep: true }
-    );
-  },
   created() {
     this.baseUrl = process.env.VUE_APP_REPORT_API;
-    this.loadIfNeed();
   },
   methods: {
-    // 新增:一次性拿齐头部与表格数据
-    async loadPageData(id, params = {}) {
-      const [col, btn, table] = await Promise.all([
-        QueryManageApi.getQueryManageColumnListByMasterId(id),
-        getQueryManageButtonPage({ masterId: id }),
-        QueryManageApi.loadTableData({
-          pageNo: params.pageNo || 1,
-          pageSize: params.pageSize || 20,
-          paramList: params.paramList || [],
-          id,
-        }),
-      ]);
-      const list = table?.data?.list || [];
-      return {
-        queryData: Array.isArray(col.data) ? col.data : [],
-        dynamicButtons: btn?.data?.list || [],
-        list,
-        total: table?.data?.total || 0,
-        originalList: JSON.parse(JSON.stringify(list)),
-        // 用户现场
-        queryParamList: params.paramList || [],
-        customFilters: params.customFilters || {},
-        pageNo: params.pageNo || 1,
-        pageSize: params.pageSize || 20,
-        currentSortField: params.currentSortField || "",
-        currentSortDirection: params.currentSortDirection || "",
-      };
-    },
-    // 统一入口:有缓存直接恢复,没有才调接口
-    loadIfNeed() {
-      const id = this.$route.query.id;
-      if (!id) return;
-
-      this.title =
-        this.$route.query.name ||
-        this.$route.redirectedFrom?.meta?.title ||
-        "查询";
-
-      // 命中缓存 → 恢复现场
-      if (this.pageCache[id]) {
-        Object.assign(this, this.pageCache[id]);
-        this.filterKey += 1;
-        return;
-      }
-
-      // 未命中 → 第一次进或缓存被清,走接口
-      this.loading = true;
-      this.loadPageData(id)
-        .then((data) => {
-          Object.assign(this, data);
-          this.$set(this.pageCache, id, data);
-        })
-        .finally(() => {
-          this.loading = false;
-        });
-    },
     /* 生命周期 */
     async init() {
       this.title =
@@ -407,23 +348,6 @@ export default {
         console.error("getList错误:", error);
       } finally {
         this.loading = false;
-        // ===== 新增:把当前现场写回缓存 =====
-        const id = this.$route.query.id;
-        if (id && this.pageCache[id]) {
-          this.pageCache[id] = {
-            ...this.pageCache[id],
-            list: this.list,
-            total: this.total,
-            originalList: this.originalList,
-            queryParamList: this.queryParamList,
-            customFilters: this.customFilters,
-            pageNo: this.pageNo,
-            pageSize: this.pageSize,
-            currentSortField: this.currentSortField,
-            currentSortDirection: this.currentSortDirection,
-          };
-        }
-        // =====================================
       }
     },
 
@@ -516,8 +440,20 @@ export default {
       } else if (operateType === 3) {
         // 修改
         if (!checkSelection()) return;
-        this.$refs.parameterDetailRef.open(
-          "update",
+
+        // 1 条继续用原弹窗
+        if (this.multipleSelection.length === 1) {
+          this.$refs.parameterDetailRef.open(
+            "update",
+            btn,
+            this.$route.query?.id,
+            this.multipleSelection,
+            this.queryData
+          );
+          return;
+        }
+        // >=2 条用批量列表
+        this.$refs.parameterBatchRef.open(
           btn,
           this.$route.query?.id,
           this.multipleSelection,
@@ -1095,18 +1031,24 @@ export default {
       await this.getQueryData();
     },
     handleRefresh() {
-      const id = this.$route.query.id;
-      if (!id) return;
-      this.$delete(this.pageCache, id); // 清缓存
-      this.loading = true;
-      this.loadPageData(id)
-        .then((data) => {
-          Object.assign(this, data);
-          this.$set(this.pageCache, id, data);
-        })
-        .finally(() => {
-          this.loading = false;
-        });
+      // 初始化所有条件
+      location.reload(true);
+
+      // 初始化所有条件
+      // this.queryParamList = [];
+      // this.customFilters = {};
+      // this.currentSortField = null;
+      // this.currentSortDirection = null;
+      // this.pageNo = 1;
+      // this.pageSize = 20;
+      // this.multipleSelection = [];
+      // this.isClear = true;
+
+      // // 重新拉取按钮、列配置和表格数据
+      // this.init();
+
+      // // 重置isClear状态
+      // setTimeout(() => (this.isClear = false), 1000);
     },
     rowStyle({ row }) {
       // 有值才上色,没有就保持默认