wujingjing
2025-03-10 0159386060edb946f29b5adcd9659dbfac06d6e1
src/components/chat/components/playBar/businessTable/index.vue
@@ -1,57 +1,101 @@
<template>
   <el-dialog :destroy-on-close="true" v-model="dialogIsShow" width="70%" :close-on-click-modal="false" @closed="closeDialog">
   <el-dialog
      class="limit-height"
      :destroy-on-close="true"
      v-model="dialogIsShow"
      width="70%"
      :close-on-click-modal="false"
      @closed="closeDialog"
   >
      <template #header>
         <div style="color: #fff">
            <SvgIcon name="ele-Document" :size="16" style="margin-right: 3px; display: inline; vertical-align: middle" />
            <span>业务表关联</span>
            <span>选择业务表数据</span>
         </div>
      </template>
      <AHMContainer type="card">
      <div class="flex-0 mb-3 flex items-center gap-2 justify-between font-bold">
         <div class="">请勾选下方的业务表,按条件筛选数据</div>
         <!-- bug -->
         <!-- <div class="flex items-center gap-2">
            <p>已选择 {{ checkedCount }} 张业务表,共 {{ totalRecordCount }} 条记录</p>
            <el-button link type="primary" @click="clearSelected">清空</el-button>
         </div> -->
      </div>
      <AHMContainer class="flex-auto" type="card" v-loading="submitLoading">
         <template #aside>
            <!-- 目录树 -->
            <LeftTreeByMgr
               showCheckbox
               v-loading="treeLoading"
               class="h100"
               ref="leftTreeRef"
               :defaultProps="{
                  id: 'id',
                  id: 'logicalId',
                  label: 'title',
                  children: 'children',
               }"
               @check="handleCheck"
               defaultExpandAll
               :treedata="listTreeData"
               title-name="场景列表"
               :show-title="false"
               :show-more-operate="false"
               :show-add="false"
               :current-node-key="currentListID"
               :node-icon="() => 'ele-Document'"
               :folder-icon="(_, data) => data.type === 'group'"
               @click="handleClickNode"
            >
            </LeftTreeByMgr>
         </template>
         <template #header>
            <el-form ref="queryFormRef" :inline="true" class="relative">
               <el-form-item :label="item.title" prop="title" v-for="item in filterColumns as any" :key="item.name">
                  <TableSearch v-model="item.value" :filter="item.filter" @input="debounceSearch(item)" />
               <el-form-item :label="item.title" prop="title" v-for="item in filterColumns as any" :key="item.name" class="items-center">
                  <TableSearch v-model="item.values" :filter="item.filter" @input="debounceSearch()" />
               </el-form-item>
            </el-form>
         </template>
         <template #main>
            <div class="w100 h100">
               <div class="h-full">
               <div class="h-full flex-column gap-2">
                  <div class="flex-0 flex-items-center flex-wrap">
                     <div class="ml-auto space-x-2 flex-items-center">
                        <DisplayMode
                           v-if="isChart"
                           :order="modeChangeOrder"
                           v-model="showMode"
                           :modeTypeMap="displayModeTypeMap"
                           @change="displayModeChange"
                        />
                        <ColFilter class="flex-0 ml-auto" :column-list="tableCheckData" />
                     </div>
                  </div>
                  <el-table
                     v-show="showTable"
                     v-loading="tableLoading"
                     ref="draggableTableRef"
                     class="h100"
                     class="flex-auto"
                     border
                     :row-class-name="isDragStatus ? 'cursor-move' : 'cursor-pointer'"
                     :data="tableData"
                     highlight-current-row
                     @sort-change="handleSortChange"
                  >
                     <el-table-column v-for="item in tableColumns" :prop="item.name" :label="item.title" show-overflow-tooltip>
                     <el-table-column
                        v-for="item in visibleTableColumns"
                        :prop="item.name"
                        sortable="custom"
                        :key="item.name"
                        :label="item.title"
                        show-overflow-tooltip
                     >
                     </el-table-column>
                  </el-table>
                  <template v-if="isRendered">
                     <ChartDisplay
                        class="flex-auto"
                        v-show="showMode === DisplayModeType.Chart"
                        :table-schema="currentNode"
                        :records="tableData"
                     />
                  </template>
               </div>
            </div>
         </template>
@@ -66,21 +110,109 @@
</template>
<script setup lang="ts" name="BusinessTable">
import { ElMessage } from 'element-plus';
import { computed, nextTick, ref, watch } from 'vue';
import { DisplayModeType, displayModeTypeMap } from '../../../chatComponents/summaryCom/components/recordSet/components/types';
import ChartDisplay from './ChartDisplay.vue';
import TableSearch from './search/index.vue';
import * as attachApi from '/@/api/attach';
import DisplayMode from '/@/components/chat/chatComponents/summaryCom/components/recordSet/components/DisplayMode.vue';
import AHMContainer from '/@/components/layout/AHMContainer.vue';
import ColFilter from '/@/components/table/colFilter/ColFilter.vue';
import LeftTreeByMgr from '/@/components/tree/leftTreeByMgr.vue';
import { useCompRef } from '/@/utils/types';
import { convertListToTree, debounce, travelTree } from '/@/utils/util';
const dialogIsShow = defineModel({
   type: Boolean,
});
const checkedNodes = ref([]);
const clearSelected = () => {
   leftTreeRef.value?.treeRef.setCheckedKeys([]);
   checkedNodes.value = [];
};
const checkedItemNodes = computed(() => {
   return checkedNodes.value.filter((item) => item.type === 'item');
});
const checkedCount = computed(() => {
   return checkedItemNodes.value.length;
});
const totalRecordCount = computed(() => {
   return checkedItemNodes.value.reduce((acc, item) => acc + (item.tableData?.length ?? 0), 0);
});
const resetBusinessTable = () => {
   checkedNodes.value = [];
};
const emit = defineEmits(['submit']);
const closeDialog = () => {
   dialogIsShow.value = false;
   resetBusinessTable();
};
const submitFormValue = () => {
const handleCheck = async (data, obj) => {
   checkedNodes.value = obj?.checkedNodes ?? [];
   await getUnGetData(checkedItemNodes.value);
   if (data.type === 'item') {
      handleClickNode(data);
   }
};
const isChart = computed(() => {
   return !!currentNode.value?.is_chart;
});
const showTable = computed(() => {
   return !isChart.value || (isChart.value && showMode.value === DisplayModeType.List);
});
//#region ====================== 模式切换 ======================
const showMode = ref(DisplayModeType.List);
const modeChangeOrder = [DisplayModeType.List, DisplayModeType.Chart];
const isRendered = ref(false);
const displayModeChange = (val: DisplayModeType) => {
   if (val === DisplayModeType.Chart) {
      nextTick(() => {
         isRendered.value = true;
      });
   }
};
//#endregion
const submitLoading = ref(false);
const getSubmitData = async (checkedList: any[]) => {
   submitLoading.value = true;
   await getUnGetData(checkedList).finally(() => {
      submitLoading.value = false;
   });
   const tables = checkedList.map((item) => {
      const indexMapItem = getIndexMapItem(item.columns);
      return {
         title: item.title,
         columns: item.columns
            .filter((item) => item.isShow)
            .map((item) => {
               return item.title;
            }),
         values: item.tableData.map((item) => {
            return Object.values(item).filter((item, index) => {
               return indexMapItem.get(index).isShow;
            });
         }),
      };
   });
   return tables;
};
const submitFormValue = async () => {
   const checkedList = leftTreeRef.value?.treeRef.getCheckedNodes().filter((item) => item.type === 'item');
   if (!checkedList?.length) {
      ElMessage.warning('请勾选业务表!');
      return;
   }
   const data = await getSubmitData(checkedList);
   emit('submit', data);
   dialogIsShow.value = false;
};
//#region ====================== 左侧树数据,tree init ======================
@@ -89,52 +221,126 @@
const listData = ref([]);
const currentListID = computed(() => currentNode.value?.id);
const currentNode = ref(null);
const getRowByLogicalId = (logicalId) => {
   let row;
   travelTree(listTreeData.value, (value, index, array, parent) => {
      if (value.logicalId === logicalId) {
         row = value;
         return true;
      }
   });
   return row;
};
const listTreeData = computed(() => {
   const treeData = convertListToTree(listData.value, {
      ID: 'id',
      ParentID: 'group',
   const listDataWithType = listData.value.map((item) => ({
      id: item.id,
      logicalId: `item-${item.id}`,
      logicalParentId: `group-${item.group}`,
      ...item,
      type: 'item',
   }));
   const groupList = Array.from(new Set(listDataWithType.filter((item) => item.group).map((item) => item.group))).map((item) => ({
      id: item,
      title: item,
      logicalId: `group-${item}`,
      type: 'group',
   }));
   const treeData = convertListToTree(listDataWithType.concat(groupList), {
      ID: 'logicalId',
      ParentID: 'logicalParentId',
      Children: 'children',
   });
   travelTree(treeData, (value, index, array, parent) => {
      if (value.columns) {
      if (value.type === 'item') {
         value.columns = value.columns.map((item) => {
            return {
               ...item,
               // 初始查询值
               value: '',
               values: [''],
               isShow: true,
            };
         });
      }
   });
   return treeData;
});
/**
 * 记录 orderMap
 */
const setOrderMap = (node) => {
   if (!node.orderMap) {
      node.orderMap = new Map();
   }
};
const resetRight = () => {
   showMode.value = DisplayModeType.List;
};
const handleClickNode = (data) => {
   if (data.type === 'group') {
      ElMessage.warning('请选择业务表');
      return;
   }
   nextTick(() => {
      leftTreeRef.value?.treeRef.setCurrentKey(data.id);
      leftTreeRef.value?.treeRef.setCurrentKey(data.logicalId);
   });
   currentNode.value = data;
   resetRight();
   setOrderMap(data);
   getTableData();
};
const getListTreeData = async () => {
   const res = await attachApi.getAttachTableList();
   treeLoading.value = true;
   const res = await attachApi.getAttachTableList().finally(() => {
      treeLoading.value = false;
   });
   listData.value = res.tables || [];
   const firstListTreeNode = listTreeData.value[0];
   let firstListTreeNode;
   travelTree(listTreeData.value, (value, index, array, parent) => {
      if (value.type === 'item') {
         firstListTreeNode = value;
         return true;
      }
   });
   if (firstListTreeNode) {
      handleClickNode(firstListTreeNode);
   } else {
      tableData.value = [];
      currentNode.value = null;
   }
};
//#endregion
//#region ====================== 指标管理表格数据,table init ======================
const tableLoading = ref(false);
const tableData = ref([]);
const isDragStatus = ref(false);
const tableData = computed(() => {
   return currentNode.value?.tableData || [];
});
const tableColumns = ref([]);
const tableColumns = computed(() => {
   return currentNode.value?.columns || [];
});
const tableCheckData = computed(() => {
   return tableColumns.value.map((item) => {
      return {
         prop: item.name,
         label: item.title,
         get isShow() {
            return item.isShow;
         },
         set isShow(value) {
            item.isShow = value;
         },
      };
   });
});
const visibleTableColumns = computed(() => {
   return tableColumns.value.filter((item) => item.isShow);
});
const getTableData = async () => {
   // allTableData.value = (res.values || []).map((item) => {
   //    item.create_time = item.create_time?.slice(0, 10);
@@ -144,52 +350,151 @@
   //    const len = getLenById(allTableData.value, value.id, value);
   //    value.title = `${value.title} (${len})`;
   // });
   tableColumns.value = currentNode.value.columns;
   if (!currentNode.value.tableData) {
      handleSearchInput();
   }
};
//#endregion
const indexMapItem = computed(() => {
   return new Map(
      tableColumns.value.map((item, index) => {
const getIndexMapItem = (columns) => {
   return new Map<number, any>(
      columns.map((item, index) => {
         return [index, item];
      })
   );
});
//#region ====================== 查询 ======================
const handleSearchInput = async (item) => {
   const params = {
      id: currentNode.value.id,
   } as any;
};
   if (filterColumns.value && filterColumns.value.length) {
      params.filter = filterColumns.value.map((item) => {
//#region ====================== 查询 ======================
const getFilterColumns = (node) => {
   return node?.columns?.filter((item) => item.filter) as any[];
};
const filterColumns = computed(() => {
   return getFilterColumns(currentNode.value);
});
const getUnEmptyFilter = (columns) => {
   const result = columns
      ?.map((item) => {
         return {
            col: item.name,
            filter: item.filter,
            value: item.value,
            values: item.values,
         };
         // values 不能为空
      })
      .filter((item) => {
         if (item.filter === 'like') {
            return item.values.every((item) => !!item);
         } else if (item.filter === 'time_range') {
            return !!item.values?.some((item) => !!item);
         } else {
            return true;
         }
      });
   return result;
};
const getSearchParams = (node) => {
   const params = {
      id: node.id,
      limit:100
   } as any;
   const filterColumns = getFilterColumns(node);
   const unEmptyFilterColumns = getUnEmptyFilter(filterColumns);
   if (unEmptyFilterColumns && unEmptyFilterColumns.length) {
      params.filter = JSON.stringify(unEmptyFilterColumns);
   }
   setOrderMap(node);
   const orderMap = node.orderMap;
   const orderList = Array.from(orderMap.entries())
      .map(([key, value]) => {
         return {
            col: key,
            order: value,
         };
      })
      .filter((item) => !!item.order);
   if (orderList?.length > 0) {
      params.order = JSON.stringify(orderList);
   }
   return params;
};
   const res = await attachApi.queryAttachTableRecords(params);
   tableData.value = (res.values || []).map((item, index) => {
const parseRecordData = (res, columns) => {
   const indexMapItem = getIndexMapItem(columns);
   return (res.values || []).map((item, index) => {
      const row = {} as any;
      item?.forEach((item, index) => {
         row[indexMapItem.value.get(index).name] = item;
      item?.forEach((item, index, array) => {
         if (indexMapItem.get(index)) {
            row[indexMapItem.get(index).name] = item;
         }
      });
      return row;
   });
};
const handleSearchItem = async (node, prop?, order?, column?) => {
   const params = getSearchParams(node);
   // 修正为当前要修改的 order
   if (prop) {
      // const foundItem = params.order.find((item) => item.col === prop);
      // if (foundItem) {
      //    foundItem.order = order;
      // }
      if (order) {
         // 排序只会有一个字段
         params.order = JSON.stringify([
            {
               col: prop,
               order: order,
            },
         ]);
      } else {
         params.order = undefined;
      }
   }
   tableLoading.value = true;
   const res = await attachApi.queryAttachTableRecords(params).finally(() => {
      tableLoading.value = false;
   });
   if (prop) {
      const orderMap = node.orderMap;
      orderMap.clear();
      orderMap.set(prop, order);
      column.order = getEleOrder(order);
   }
   node.tableData = parseRecordData(res, node.columns ?? []);
};
const handleSearchInput = async (prop?, order?, column?) => {
   handleSearchItem(currentNode.value, prop, order, column);
};
const getUnGetData = async (checkedList: any[]) => {
   const getDataPromiseList = [];
   for (const item of checkedList) {
      if (!item.tableData) {
         getDataPromiseList.push(handleSearchItem(item));
      }
   }
   // 等待所有数据获取完成
   await Promise.all(getDataPromiseList);
};
const debounceSearch = debounce(handleSearchInput, 400);
//#endregion
const filterColumns = computed(() => {
   return tableColumns.value.filter((item) => item.filter) as any[];
});
const orderMap = new Map();
const getEleOrder = (order) => {
   if (order === 'DESC') {
      return 'descending';
   } else if (order === 'ASC') {
      return 'ascending';
   } else {
      return '';
   }
};
const handleSortChange = ({ column, prop, order }) => {
   setOrderMap(currentNode.value);
   const orderMap = currentNode.value.orderMap;
   // 恢复原状,更新后再显示排序状态
   const curOrder = orderMap.get(prop) ?? null;
   column.order = curOrder;
@@ -202,6 +507,7 @@
   } else {
      sendOrder = '';
   }
   handleSearchInput(prop, sendOrder, column);
};
watch(
@@ -228,4 +534,24 @@
      font-size: 14px;
   }
}
:deep(.el-dialog__body) {
   height: calc(90vh - 111px) !important;
}
</style>
<style lang="scss">
.limit-height {
   .el-dialog__body {
      height: calc(90vh - 111px) !important;
      display: flex;
      flex-direction: column;
      overflow: hidden;
   }
}
.yw-layout-main {
   .el-card__body {
      padding: 10px;
   }
}
</style>