Parcourir la source

更新自定义指标组件拖拽排序功能,现在配置可以保存在本地,但表格字段顺序仍有问题

fxs il y a 8 mois
Parent
commit
b97efda310

+ 2 - 0
components.d.ts

@@ -7,6 +7,8 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    CustomDialog: typeof import('./src/components/dialog/customDialog.vue')['default']
+    CustomIndicatorDialog: typeof import('./src/components/dialog/customIndicatorDialog.vue')['default']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']

+ 497 - 0
src/components/dialog/customIndicatorDialog.vue

@@ -0,0 +1,497 @@
+<script setup lang="ts">
+import type { BaseFieldItem, TableFields } from '@/types/Tables/table'
+
+import { ref, reactive } from 'vue'
+import { useTable } from '@/hooks/useTable'
+
+const { isFixedField } = useTable()
+
+interface DialogProps {
+  indicatorFields: Array<BaseFieldItem<TableFields>>
+  fixedIndicator: Array<TableFields>
+  unFixedIndicator: Array<TableFields>
+  tableFieldsInfo: Array<TableFields>
+  fixedFields: Array<string>
+}
+
+const props = withDefaults(defineProps<DialogProps>(), {})
+
+const emits = defineEmits(['updateFields'])
+
+// 被排序过后的表格字段信息
+const sortedTableFieldsInfo = reactive<Array<TableFields>>([])
+
+// 点击应用后新生成的字段信息
+const newTableFieldsInfo = reactive<Array<TableFields>>([])
+
+// 自定义指标的展示状态
+const customIndicatorVisible = ref<boolean>(false)
+
+// 自定义指标搜索值
+const indicatorSearch = ref<string>('')
+
+// 自定义指标左侧导航选中值
+const indicatorNavActive = ref<string>(props.indicatorFields[0]?.name)
+
+// 拖拽的容器
+const dragContentRef = ref<HTMLElement>()
+
+// 当前正在被拖拽的元素
+const dragingRef = ref<HTMLElement>()
+
+const indicatorOpen = () => {}
+
+const indicatorClose = () => {}
+
+/**
+ * @description: 初始化一下被排序过的表格字段信息,这里要注意深拷贝
+ * @return {*}
+ */
+const initSortedTableFieldsInfo = () => {
+  sortedTableFieldsInfo.splice(
+    0,
+    sortedTableFieldsInfo.length,
+    ...JSON.parse(JSON.stringify(props.tableFieldsInfo)),
+  )
+}
+
+/**
+ * @description: 跳转到指定的指标区域
+ * @param {*} name 标签分段的字段名
+ * @return {*}
+ */
+const goToIndicator = (name: string) => {
+  let el = document.querySelector(`div[data-anchor="${name}"]`) as HTMLElement
+  if (!el) return
+  indicatorNavActive.value = name
+  el.scrollIntoView({ behavior: 'smooth' })
+}
+
+/**
+ * @description: 展示自定义指标弹窗
+ * @return {*}
+ */
+const showCustomIndicator = () => {
+  sortedTableFieldsInfo.splice(
+    0,
+    sortedTableFieldsInfo.length,
+    ...JSON.parse(JSON.stringify(props.tableFieldsInfo)),
+  )
+
+  customIndicatorVisible.value = true
+}
+
+/**
+ * @description: 判断当前是否可以插入
+ * @param {*} e 触发事件的事件对象
+ * @return {*}
+ */
+const canInsert = (e: DragEvent): boolean => {
+  return !(
+    !e.target ||
+    !dragingRef.value ||
+    !dragContentRef.value ||
+    dragingRef.value === e.target ||
+    e.target === dragContentRef.value
+  )
+}
+
+/**
+ * @description: 拖动开始
+ * @param {*} e 拖动事件对象
+ * @return {*}
+ */
+const dragStart = (e: DragEvent) => {
+  if (!dragContentRef.value || !e.target || !e.dataTransfer) return
+  e.dataTransfer.effectAllowed = 'move' // 设置拖拽类型
+  dragingRef.value = e.target as HTMLElement // 获取当前拖拽的元素
+}
+
+/**
+ * @description: 拖拽过程
+ * @param {*} e 拖拽事件对象
+ * @return {*}
+ */
+const dragEnter = (e: DragEvent) => {
+  e.preventDefault()
+  if (!canInsert(e)) return
+  let listArray = Array.from(dragContentRef.value!.childNodes) // 整个可拖动的列表
+  let curIndex = listArray.indexOf(dragingRef.value!) // 当前拖动元素在整个列表中的文职
+  let targeIndex = listArray.indexOf(e.target as HTMLElement) // 目前拖动到的元素在整个列表中的位置
+
+  if (curIndex < targeIndex) {
+    dragContentRef.value!.insertBefore(
+      dragingRef.value!,
+      (e.target as HTMLElement).nextSibling,
+    )
+  } else {
+    dragContentRef.value!.insertBefore(
+      dragingRef.value!,
+      e.target as HTMLElement,
+    )
+  }
+}
+
+/**
+ * @description: 拖拽结束
+ * @param {*} e 拖拽对象
+ * @return {*}
+ */
+const dragOver = (e: DragEvent) => {
+  e.preventDefault()
+  if (!e.target) return
+  // 把被排序过的字段信息先清空一下,防止之前的污染
+  sortedTableFieldsInfo.splice(0, sortedTableFieldsInfo.length)
+  let sortedNodes = Array.from(dragContentRef.value!.children)
+  let sortedFields: Array<string> = []
+  // 把所有能够排序的节点的name提取出来,这些就是已经排好序的字段名
+  sortedNodes.forEach((node: Element) => {
+    if (node.nodeType === 1) {
+      // 判断是否为元素节点
+      sortedFields.push(node.getAttribute('name') as string)
+    }
+  })
+  // 临时复制一份原本的字段信息,防止污染
+  let tempFields: Array<TableFields> = JSON.parse(
+    JSON.stringify(props.tableFieldsInfo),
+  )
+
+  // 根据排好序的字段名,去原本的字段信息中去找对应的字段名,把他加入到新字段信息中
+  sortedFields.forEach((item: string) => {
+    let fFiled = tempFields.find(field => field.name === item)
+    if (fFiled) sortedTableFieldsInfo.push(fFiled)
+  })
+
+  // 还剩下固定字段的信息,把他加入数组头部
+  sortedTableFieldsInfo.unshift(...props.fixedIndicator)
+}
+
+/**
+ * @description: 获取当前被激活的指标
+ * @return {*}
+ */
+const getActivedIndicator = (): string[] => {
+  let result: string[] = []
+  props.indicatorFields.forEach(item => {
+    item.children.forEach(field => {
+      if (field.state) result.push(field.name)
+    })
+  })
+  return result
+}
+
+/**
+ * @description: 应用配置的自定义指标
+ * @return {*}
+ */
+const applyCustomIndicator = () => {
+  customIndicatorVisible.value = false
+
+  // 拿到当前激活的所有自定义指标
+  let actived = getActivedIndicator()
+  // 把目前排好序的字段信息复制给新的字段信息
+  newTableFieldsInfo.splice(
+    0,
+    newTableFieldsInfo.length,
+    ...JSON.parse(JSON.stringify(sortedTableFieldsInfo)),
+  )
+
+  // 根据当前已经激活的字段name,去吧字段信息中的state改为true
+  newTableFieldsInfo.map(item => {
+    // console.log(item.name)
+    if (actived.includes(item.name)) {
+      item.state = true
+    } else {
+      item.state = false
+    }
+  })
+
+  emits('updateFields', newTableFieldsInfo)
+}
+
+initSortedTableFieldsInfo()
+
+defineExpose({ showCustomIndicator })
+</script>
+
+<template>
+  <div class="customIndicatorContainer">
+    <el-dialog
+      v-model="customIndicatorVisible"
+      title="自定义指标"
+      width="1096"
+      :append-to-body="true"
+      :align-center="true"
+      @open="indicatorOpen"
+      @close="indicatorClose"
+      class="el-dialog"
+    >
+      <div class="indicatorBox">
+        <div class="topWrapper">
+          <el-input
+            v-model="indicatorSearch"
+            style="width: 240px"
+            placeholder="Please Input"
+          />
+        </div>
+        <div class="indicatorWrapper">
+          <div class="indicatorSider">
+            <div
+              @click="goToIndicator(item.name)"
+              v-for="item in indicatorFields"
+              class="indicatorCategory"
+              :class="{
+                indicatorCategoryActive: indicatorNavActive === item.name,
+              }"
+            >
+              {{ item.label }}
+            </div>
+          </div>
+          <div class="indicatorContent">
+            <div
+              v-for="field in indicatorFields"
+              :data-anchor="field.name"
+              class="indicatorBlock"
+            >
+              <div class="indicatorGroup">
+                <span>{{ field.label }}</span>
+              </div>
+              <el-row class="indicatorItem">
+                <el-col :span="8" v-for="item in field.children"
+                  ><el-checkbox
+                    style="margin-bottom: 16px"
+                    v-model="item.state"
+                    :label="item.label"
+                    :disabled="isFixedField(props.fixedFields, item)"
+                /></el-col>
+              </el-row>
+            </div>
+          </div>
+        </div>
+        <div class="dragWrapper">
+          <div class="dragHeader">
+            <div class="dragTitle">已选指标</div>
+            <div class="dragDes">拖拽可自定义指标顺序</div>
+            <div class="fixedIndicator">
+              <div class="dragBlock notAllow" v-for="item in fixedIndicator">
+                {{ item.label }}
+              </div>
+            </div>
+            <div class="dragSepreate">以上指标将横向固定</div>
+          </div>
+          <div
+            class="dragContent"
+            ref="dragContentRef"
+            @dragstart="dragStart"
+            @dragenter="dragEnter"
+            @dragover="dragOver"
+          >
+            <div
+              draggable="true"
+              v-for="item in unFixedIndicator"
+              class="dragBlock"
+              :name="item.name"
+            >
+              {{ item.label }}
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="customIndicatorVisible = false">取消</el-button>
+          <el-button type="primary" @click="applyCustomIndicator">
+            应用
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.indicatorBox {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+
+.topWrapper {
+  margin-bottom: 16px;
+}
+
+.indicatorWrapper {
+  display: flex;
+  width: 832px;
+  height: 516px;
+  border: 1px solid #eaebec;
+  border-radius: 4px;
+}
+
+.indicatorSider {
+  flex-shrink: 0;
+  width: 160px;
+  overflow: auto;
+  border-right: 1px solid #eaebec;
+}
+
+.indicatorCategory {
+  padding-left: 16px;
+  font-size: 14px;
+  line-height: 40px;
+  color: #333;
+  cursor: pointer;
+}
+
+.indicatorCategoryActive {
+  color: #197afb;
+  background-color: #d6eaff;
+}
+
+.indicatorContent {
+  width: 672px;
+  overflow: auto;
+  scroll-behavior: smooth;
+}
+
+.indicatorGroup {
+  color: rgb(51, 51, 51);
+  color-scheme: light;
+  display: block;
+  font-weight: 700;
+  font-size: 14px;
+  word-break: break-all;
+  -webkit-font-smoothing: subpixel-antialiased;
+  margin-bottom: 16px;
+}
+
+.indicatorBlock {
+  width: 100%;
+  padding: 16px 0 0 24px;
+  border-bottom: 1px solid #eaebec;
+  // display: flex;
+  // flex-wrap: wrap;
+}
+
+.dragWrapper {
+  position: absolute;
+  top: 0;
+  right: 0;
+  flex-shrink: 0;
+  width: 216px;
+  height: 565px;
+  padding: 10px 0;
+  overflow: auto;
+  background-color: #f8f8f9;
+  border-right: 1px solid #eaebec;
+}
+
+.dragHeader,
+.dragContent {
+  padding: 0 16px;
+}
+
+.dragTitle {
+  font-size: 14px;
+  font-weight: 700;
+  line-height: 100%;
+  color: #333;
+}
+
+.dragDes {
+  margin: 8px 0;
+  font-size: 12px;
+  line-height: 100%;
+  color: #999;
+}
+
+.dragBlock {
+  position: relative;
+  width: 184px;
+  height: 40px;
+  padding: 0 30px 0 36px;
+  overflow: hidden;
+  line-height: 40px;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  background-color: #fff;
+  border-bottom: 1px solid #e8eaec;
+  cursor: all-scroll;
+  transition: 0.1s all;
+}
+
+.dragBlock:not(.notAllow):hover {
+  background-color: #e7e7e7;
+}
+
+// .draging {
+//   position: relative;
+// }
+
+// .draging::after {
+//   content: '';
+//   position: absolute;
+//   top: 0;
+//   right: 0;
+//   bottom: 0;
+//   left: 0;
+// }
+
+// 禁止拖拽
+.notAllow {
+  cursor: not-allowed;
+}
+
+.dragSepreate {
+  position: relative;
+  margin: 16px 0 0;
+  font-size: 12px;
+  color: #999;
+  text-align: center;
+}
+
+.dragWrapper .dragSepreate::before {
+  position: absolute;
+  top: 9px;
+  left: 0;
+  width: 32px;
+  height: 1px;
+  content: '';
+  background-color: #e8eaec;
+}
+.dragWrapper .dragSepreate::after {
+  position: absolute;
+  top: 9px;
+  right: 0;
+  width: 32px;
+  height: 1px;
+  content: '';
+  background-color: #e8eaec;
+}
+
+.dragWrapper .dragBlock::before {
+  position: absolute;
+  top: 16px;
+  left: 12px;
+  width: 16px;
+  height: 2px;
+  content: '';
+  background-color: #999;
+}
+.dragWrapper .dragBlock::after {
+  position: absolute;
+  bottom: 16px;
+  left: 12px;
+  width: 16px;
+  height: 2px;
+  content: '';
+  background-color: #999;
+}
+
+.dragContent {
+  margin-top: 16px;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+</style>

+ 104 - 299
src/components/table/Table.vue

@@ -8,14 +8,24 @@ import type { FormInstance } from 'element-plus'
 import type { TableData, BaseFieldInfo } from '@/types/Tables/table'
 
 import { TableFilterType } from '@/types/Tables/table'
-
-import { computed, onMounted, reactive, ref, watch } from 'vue'
+import { resetReactive } from '@/utils/common'
+import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
 import { Search } from '@element-plus/icons-vue'
 import { Plus, Operation } from '@element-plus/icons-vue'
+import { useTable } from '@/hooks/useTable'
+
+import customIndicatorDialog from '../dialog/customIndicatorDialog.vue'
+
+type CustomIndicatorDialog = typeof customIndicatorDialog
 
 const props = withDefaults(defineProps<TableProps>(), {})
-const tableFieldLWidth = 300 // 长单元格的宽度
-const tableFieldSWidth = 200 // 短单元格的宽度
+
+const emits = defineEmits(['updateCustomIndicator'])
+
+const { isFixedField } = useTable()
+
+const tableFieldLWidth = 200 // 长单元格的宽度
+const tableFieldSWidth = 150 // 短单元格的宽度
 
 // 表格数据
 const tableData = reactive<Array<TableData>>(props.tableData)
@@ -26,33 +36,11 @@ const tableContent = ref<HTMLElement>()
 // table的横向滚动条
 const elScrollBarH = ref<HTMLElement>()
 
-//表格字段信息
-const tableFieldsInfo = reactive<Array<BaseFieldItem<TableFields>>>(
-  JSON.parse(JSON.stringify(props.tableFields)),
-)
+// 表格字段信息
+const tableFieldsInfo = reactive<Array<TableFields>>([])
 
 // 自定义指标的字段信息,为了不影响原本的表格字段信息
-const indicatorFields = reactive<Array<BaseFieldItem<TableFields>>>(
-  JSON.parse(JSON.stringify(props.tableFields)),
-)
-
-// 自定义指标中需要固定的指标
-const fixedIndicator = computed<TableFields[]>(() => {
-  let result: TableFields[] = []
-  indicatorFields.forEach(item => {
-    result.push(...item.children.filter(item => item.fixed))
-  })
-  return result
-})
-
-// 没有被固定的指标
-const unFixedIndicator = computed<TableFields[]>(() => {
-  let result: TableFields[] = []
-  indicatorFields.forEach(item => {
-    result.push(...item.children.filter(item => !item.fixed && item.state))
-  })
-  return result
-})
+const indicatorFields = reactive<Array<BaseFieldItem<TableFields>>>([])
 
 // 表格整体容器
 const tableContainer = ref<HTMLElement>()
@@ -80,9 +68,6 @@ const filterFields = ref<Array<any>>([])
 // 批量操作选中的值
 const batchOper = ref<string>()
 
-// 自定义指标的展示状态
-const customIndicatorVisible = ref<boolean>(false)
-
 // 批量操作的选项数组
 const batchOperList = reactive<
   Array<{
@@ -101,10 +86,58 @@ const batchOperList = reactive<
 ])
 
 /**
+ * @description: 初始化自定义指标的字段信息
+ * @return {*}
+ */
+const initIndicatorFields = () => {
+  resetReactive(indicatorFields)
+  indicatorFields.splice(
+    0,
+    indicatorFields.length,
+    ...JSON.parse(JSON.stringify(props.tableFields)),
+  )
+}
+
+/**
+ * @description: 初始化表格字段,把所有字段都放到一个数组中
+ * @return {*}
+ */
+const initTableFields = () => {
+  resetReactive(tableFieldsInfo)
+  props.tableFields.forEach(item => {
+    item.children.forEach(item => {
+      tableFieldsInfo.push(item)
+    })
+  })
+}
+
+// 自定义指标中需要固定的指标
+const fixedIndicator = computed<TableFields[]>(() => {
+  let result: TableFields[] = []
+  indicatorFields.forEach(item => {
+    result.push(...item.children.filter(item => item.fixed))
+  })
+  return result
+})
+
+// 没有被固定的指标
+const unFixedIndicator = computed<TableFields[]>(() => {
+  let result: TableFields[] = []
+  indicatorFields.forEach(item => {
+    result.push(...item.children.filter(item => !item.fixed && item.state))
+  })
+  // return JSON.parse(JSON.stringify(result))
+  return result
+})
+
+/**
  * @description: 初始化查询表单
  * @return {*}
  */
 const initfilterForm = () => {
+  // filterFormData
+  resetReactive(filterFormData)
+  resetReactive(filterFieldsStateList)
   for (let [k, v] of Object.entries(props.filtersInfo)) {
     // 表单给初始值
     filterFormData[k] = v.value ? v.value : ''
@@ -123,6 +156,7 @@ const initfilterForm = () => {
  * @return {*}
  */
 const initFilterFields = () => {
+  resetReactive(filterFields.value)
   filterFieldsStateList.forEach(v => {
     filterFields.value.push(v.value)
   })
@@ -134,7 +168,6 @@ const initFilterFields = () => {
  */
 const resetFilterForm = () => {
   filterFormRef.value?.resetFields()
-  console.log(filterFormData)
 }
 
 /**
@@ -209,30 +242,35 @@ const setElScrollBar = () => {
   }
 }
 
-/**
- * @description: 判断当前字段是否是需要固定的字段
- * @param {*} info 字段信息
- * @return {boolean}
- */
-const isFixedField = (info: BaseFieldInfo): boolean => {
-  return props.fixedFields.includes(info.name)
-}
+const customIndicatorDialogRef = ref<CustomIndicatorDialog>()
 
 /**
  * @description: 展示自定义指标弹窗
  * @return {*}
  */
 const showCustomIndicator = () => {
-  customIndicatorVisible.value = true
+  if (!customIndicatorDialogRef.value) return
+  customIndicatorDialogRef.value.showCustomIndicator()
 }
 
-initfilterForm()
-initFilterFields()
+/**
+ * @description: 应用自定义指标
+ * @param {*} newTableFieldsInfo 新的表格字段信息
+ * @return {*}
+ */
+const applyCustomIndicator = (newTableFieldsInfo: Array<TableFields>) => {
+  tableFieldsInfo.splice(0, tableFieldsInfo.length, ...newTableFieldsInfo)
+  emits('updateCustomIndicator', indicatorFields)
+}
 
 watch(
   () => props.tableData,
   newData => {
     tableData.splice(0, tableData.length, ...newData)
+    initfilterForm()
+    initFilterFields()
+    initTableFields()
+    initIndicatorFields()
   },
   {
     deep: true,
@@ -241,24 +279,6 @@ watch(
 onMounted(() => {
   initScroll()
 })
-
-// 自定义指标搜索值
-const indicatorSearch = ref<string>('')
-
-/**
- * @description: 跳转到指定的指标区域
- * @param {*} name 标签分段的字段名
- * @return {*}
- */
-const goToIndicator = (name: string) => {
-  let el = document.querySelector(`div[data-anchor="${name}"]`) as HTMLElement
-  console.log(el)
-  el.scrollIntoView({ behavior: 'smooth' })
-}
-
-const indicatorOpen = () => {}
-
-const indicatorClose = () => {}
 </script>
 
 <template>
@@ -396,101 +416,32 @@ const indicatorClose = () => {}
           border
           :scrollbar-always-on="true"
         >
-          <template v-for="k in tableFieldsInfo">
-            <template v-for="item in k.children" :key="item.name">
-              <el-table-column
-                :width="
-                  isFixedField(item) ? tableFieldLWidth : tableFieldSWidth
-                "
-                :fixed="isFixedField(item)"
-                :prop="item.name"
-                :label="item.label"
-                v-if="item.state"
-              >
-              </el-table-column>
-            </template>
+          <template v-for="item in tableFieldsInfo" :key="item.name">
+            <el-table-column
+              :width="
+                isFixedField(props.fixedFields, item)
+                  ? tableFieldLWidth
+                  : tableFieldSWidth
+              "
+              :fixed="isFixedField(props.fixedFields, item)"
+              :prop="item.name"
+              :label="item.label"
+              v-if="item.state"
+            >
+            </el-table-column>
           </template>
         </el-table>
       </div>
     </div>
-    <div class="customIndicatorContainer">
-      <el-dialog
-        v-model="customIndicatorVisible"
-        title="自定义指标"
-        width="1096"
-        :append-to-body="true"
-        :align-center="true"
-        @open="indicatorOpen"
-        @close="indicatorClose"
-        class="el-dialog"
-      >
-        <div class="indicatorBox">
-          <div class="topWrapper">
-            <el-input
-              v-model="indicatorSearch"
-              style="width: 240px"
-              placeholder="Please Input"
-            />
-          </div>
-          <div class="indicatorWrapper">
-            <div class="indicatorSider">
-              <div
-                @click="goToIndicator(item.name)"
-                v-for="item in indicatorFields"
-                class="indicatorCategory"
-              >
-                {{ item.label }}
-              </div>
-            </div>
-            <div class="indicatorContent">
-              <div
-                v-for="field in indicatorFields"
-                :data-anchor="field.name"
-                class="indicatorBlock"
-              >
-                <div class="indicatorGroup">
-                  <span>{{ field.label }}</span>
-                </div>
-                <el-row class="indicatorItem">
-                  <el-col :span="8" v-for="item in field.children"
-                    ><el-checkbox
-                      style="margin-bottom: 16px"
-                      v-model="item.state"
-                      :label="item.label"
-                  /></el-col>
-                </el-row>
-              </div>
-            </div>
-          </div>
-          <div class="dragWrapper">
-            <div class="dragHeader">
-              <div class="dragTitle">已选指标</div>
-              <div class="dragDes">拖拽可自定义指标顺序</div>
-              <div class="fixedIndicator">
-                <div class="dragBlock" v-for="item in fixedIndicator">
-                  {{ item.label }}
-                </div>
-              </div>
-              <div class="dragSepreate">以上指标将横向固定</div>
-            </div>
-            <div class="dragContent">
-              <div class="dragBlock" v-for="item in unFixedIndicator">
-                {{ item.label }}
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <template #footer>
-          <div class="dialog-footer">
-            <el-button @click="customIndicatorVisible = false">取消</el-button>
-            <el-button type="primary" @click="customIndicatorVisible = false">
-              应用
-            </el-button>
-          </div>
-        </template>
-      </el-dialog>
-    </div>
+    <customIndicatorDialog
+      :indicator-fields="indicatorFields"
+      :un-fixed-indicator="unFixedIndicator"
+      :fixed-indicator="fixedIndicator"
+      :table-fields-info="tableFieldsInfo"
+      :fixed-fields="props.fixedFields"
+      ref="customIndicatorDialogRef"
+      @update-fields="applyCustomIndicator"
+    ></customIndicatorDialog>
   </div>
 </template>
 
@@ -577,150 +528,4 @@ const indicatorClose = () => {}
   // position: fixed !important;
   bottom: 5px;
 }
-
-.indicatorBox {
-  position: relative;
-  width: 100%;
-  height: 100%;
-}
-
-.topWrapper {
-  margin-bottom: 16px;
-}
-
-.indicatorWrapper {
-  display: flex;
-  width: 832px;
-  height: 516px;
-  border: 1px solid #eaebec;
-  border-radius: 4px;
-}
-
-.indicatorSider {
-  flex-shrink: 0;
-  width: 160px;
-  overflow: auto;
-  border-right: 1px solid #eaebec;
-}
-
-.indicatorCategory {
-  padding-left: 16px;
-  font-size: 14px;
-  line-height: 40px;
-  color: #333;
-  cursor: pointer;
-}
-
-.indicatorCategoryActive {
-  color: #197afb;
-  background-color: #d6eaff;
-}
-
-.indicatorContent {
-  width: 672px;
-  overflow: auto;
-  scroll-behavior: smooth;
-}
-
-.indicatorGroup {
-  color: rgb(51, 51, 51);
-  color-scheme: light;
-  display: block;
-  font-weight: 700;
-  font-size: 14px;
-  word-break: break-all;
-  -webkit-font-smoothing: subpixel-antialiased;
-  margin-bottom: 16px;
-}
-
-.indicatorBlock {
-  width: 100%;
-  padding: 16px 0 0 24px;
-  border-bottom: 1px solid #eaebec;
-  // display: flex;
-  // flex-wrap: wrap;
-}
-
-.dragWrapper {
-  position: absolute;
-  top: 0;
-  right: 0;
-  flex-shrink: 0;
-  width: 216px;
-  height: 565px;
-  padding: 10px 0;
-  overflow: auto;
-  background-color: #f8f8f9;
-  border-right: 1px solid #eaebec;
-}
-
-.dragHeader,
-.dragContent {
-  padding: 0 16px;
-}
-
-.dragBlock {
-  position: relative;
-  width: 184px;
-  height: 40px;
-  padding: 0 30px 0 36px;
-  overflow: hidden;
-  line-height: 40px;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  background-color: #fff;
-  border-bottom: 1px solid #e8eaec;
-}
-
-.dragSepreate {
-  position: relative;
-  margin: 16px 0 0;
-  font-size: 12px;
-  color: #999;
-  text-align: center;
-}
-
-.dragWrapper .dragSepreate::before {
-  position: absolute;
-  top: 9px;
-  left: 0;
-  width: 32px;
-  height: 1px;
-  content: '';
-  background-color: #e8eaec;
-}
-.dragWrapper .dragSepreate::after {
-  position: absolute;
-  top: 9px;
-  right: 0;
-  width: 32px;
-  height: 1px;
-  content: '';
-  background-color: #e8eaec;
-}
-
-.dragWrapper .dragBlock::before {
-  position: absolute;
-  top: 16px;
-  left: 12px;
-  width: 16px;
-  height: 2px;
-  content: '';
-  background-color: #999;
-}
-.dragWrapper .dragBlock::after {
-  position: absolute;
-  bottom: 16px;
-  left: 12px;
-  width: 16px;
-  height: 2px;
-  content: '';
-  background-color: #999;
-}
-
-.dragContent {
-  margin-top: 16px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
 </style>

+ 15 - 1
src/hooks/useTable.ts

@@ -1,4 +1,4 @@
-import type { TableData } from '@/types/Tables/table'
+import type { TableData, BaseFieldInfo } from '@/types/Tables/table'
 import axiosInstance from '@/utils/axios/axiosInstance'
 export function useTable() {
   const getData = async (
@@ -15,7 +15,21 @@ export function useTable() {
       return []
     }
   }
+
+  /**
+   * @description: 判断当前字段是否是需要固定的字段
+   * @param {*} info 字段信息
+   * @return {boolean}
+   */
+  const isFixedField = (
+    fixedFields: Array<string>,
+    info: BaseFieldInfo,
+  ): boolean => {
+    return fixedFields.includes(info.name)
+  }
+
   return {
     getData,
+    isFixedField,
   }
 }

+ 8 - 8
src/main.ts

@@ -23,13 +23,13 @@ app.use(router)
  * @param {*} info 错误来源类型信息
  * @return {*}
  */
-app.config.errorHandler = (err, instance, info) => {
-  console.log('-----------------')
-  console.log('未被捕获的异常')
-  console.log(err)
-  console.log(instance)
-  console.log(info)
-  console.log('-----------------')
-}
+// app.config.errorHandler = (err, instance, info) => {
+//   console.log('-----------------')
+//   console.log('未被捕获的异常')
+//   console.log(err)
+//   console.log(instance)
+//   console.log(info)
+//   console.log('-----------------')
+// }
 
 app.mount('#app')

+ 39 - 11
src/utils/common/index.ts

@@ -2,13 +2,14 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-26 15:46:42
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-10-15 10:47:42
- * @FilePath: \Game-Backstage-Management-System\src\utils\common\index.ts
+ * @LastEditTime: 2024-10-19 16:35:03
+ * @FilePath: \Quantity-Creation-Management-System\src\utils\common\index.ts
  * @Description:
  *
  */
 
 import { ElMessage } from 'element-plus'
+import type { Reactive } from 'vue'
 
 /**
  * @description:  包装防抖函数
@@ -18,7 +19,7 @@ import { ElMessage } from 'element-plus'
  */
 export function debounceFunc<T extends (...args: any[]) => any>(
   func: T,
-  delay: number
+  delay: number,
 ): (...args: Parameters<T>) => void {
   let timer: ReturnType<typeof setTimeout> | null
   return (...args: Parameters<T>) => {
@@ -37,7 +38,7 @@ export function debounceFunc<T extends (...args: any[]) => any>(
  */
 export function throttleFunc<T extends (...args: any[]) => any>(
   func: T,
-  delay: number
+  delay: number,
 ): (...args: Parameters<T>) => void {
   let lastCall = 0
 
@@ -52,7 +53,10 @@ export function throttleFunc<T extends (...args: any[]) => any>(
 }
 
 // 小数转百分比
-export function decimalToPercentage(decimal: number, decimalPlaces: number = 0): string {
+export function decimalToPercentage(
+  decimal: number,
+  decimalPlaces: number = 0,
+): string {
   // Convert the decimal to a percentage by multiplying by 100
   const percentage = decimal * 100
 
@@ -121,7 +125,10 @@ export const createDateRange = (day: number): Array<Date> => {
  * @param {string} endTime 结束时间
  * @return {boolean} 返回是否有效
  */
-export const invaildDateRange = (startTime: string, endTime: string): boolean => {
+export const invaildDateRange = (
+  startTime: string,
+  endTime: string,
+): boolean => {
   const start = new Date(startTime).getTime()
   const end = new Date(endTime).getTime()
   // 判断日期是否有效,`isNaN` 判断生成的时间是否为 "Invalid Date"
@@ -149,14 +156,14 @@ export const copyText = (text: string) => {
       .then(() => {
         ElMessage({
           type: 'success',
-          message: '复制成功'
+          message: '复制成功',
         })
         reslove(true)
       })
-      .catch((err) => {
+      .catch(err => {
         ElMessage({
           type: 'error',
-          message: '复制失败'
+          message: '复制失败',
         })
         reject(err)
         throw new Error(err)
@@ -170,7 +177,7 @@ export const copyText = (text: string) => {
  * @return {*}
  */
 export const clearReactiveData = (data: Record<string, any>) => {
-  Object.keys(data).forEach((key) => {
+  Object.keys(data).forEach(key => {
     delete data[key]
   })
 }
@@ -207,7 +214,28 @@ export const compareWatchData = (data: any, store: any): boolean => {
  * @param {boolean} matchCase 是否区分大小写,默认为true,即不区分大小写
  * @return {boolean} 返回匹配结果
  */
-export const fuzzySearch = (pattern: string, text: string, matchCase: boolean = true): boolean => {
+export const fuzzySearch = (
+  pattern: string,
+  text: string,
+  matchCase: boolean = true,
+): boolean => {
   const regex = new RegExp(pattern, matchCase ? 'i' : '') // 'i' 标志表示忽略大小写
   return regex.test(text)
 }
+
+/**
+ * @description: 清空reactive对象的所有属性并保持响应式
+ * @param {Reactive} obj 传入的对象
+ * @return {*}
+ */
+export const resetReactive = (
+  obj: Reactive<{ [key: string]: any } | Array<any>>,
+) => {
+  if (Array.isArray(obj)) {
+    obj.splice(0, obj.length)
+  } else {
+    Object.keys(obj).forEach(key => {
+      delete obj[key]
+    })
+  }
+}

+ 44 - 14
src/views/Promotion/adManage/ttad.vue

@@ -29,7 +29,7 @@ import Menu from '@/components/navigation/Menu.vue'
 import Table from '@/components/table/Table.vue'
 
 interface TableInfo {
-  [key: string]: any
+  [key: string]: TableInfoItem<TableData, BaseFieldItem<TableFields>>
   account: TableInfoItem<AdmgeTTAccData, BaseFieldItem<AdmgeTTAccFileds>> // 账户信息
   project: TableInfoItem<AdmgeTTProjData, BaseFieldItem<AdmgeTTProjFileds>> // 项目信息
   advertise: TableInfoItem<AdmgeTTAdData, BaseFieldItem<AdmgeTTAdFileds>> // 广告信息
@@ -72,7 +72,7 @@ const AllFields: AllFieldsInfo = {
     { label: '账户流动', name: 'accountFlow', state: true, fixed: false },
     { label: '账户名', name: 'accountName', state: true, fixed: true },
     { label: '账户状态', name: 'accountStatus', state: true, fixed: false },
-    { label: '操作', name: 'action', state: false, fixed: true },
+    { label: '操作', name: 'action', state: true, fixed: true },
     {
       label: '广告账户数量',
       name: 'adAccountCount',
@@ -178,7 +178,7 @@ const accFilterInfo: FilterInfo = {
     label: '创量项目',
     name: 'ttProject',
     type: TableFilterType.Select,
-    value: '',
+    value: 'A',
     options: [
       { label: '项目A', value: 'A' },
       { label: '项目B', value: 'B' },
@@ -188,7 +188,7 @@ const accFilterInfo: FilterInfo = {
     label: '优化师',
     name: 'optimizer',
     type: TableFilterType.Select,
-    value: '',
+    value: 'jack',
     options: [
       { label: '优化师1', value: 'jack' },
       { label: '优化师2', value: 'rose' },
@@ -204,7 +204,7 @@ const accFilterInfo: FilterInfo = {
     label: '产品',
     name: 'product',
     type: TableFilterType.Select,
-    value: '',
+    value: '1',
     options: [
       { label: '产品1', value: '1' },
       { label: '产品2', value: '2' },
@@ -212,18 +212,21 @@ const accFilterInfo: FilterInfo = {
   },
 }
 
+// 所有的查询表单信息
 const allFilters: AllFilterInfo = {
   account: accFilterInfo,
   project: {},
   advertise: {},
 }
 
+// 所有需要固定的字段
 const allFixedFileds: AllFixedFiledsInfo = {
   account: ['accountName', 'action'],
   project: [],
   advertise: [],
 }
 
+// 所有的表格请求
 const tableReqInfo: { [key: string]: any } = {
   account: AllApi.mockAdTTAcc,
   project: AllApi.mockAdTTProj,
@@ -237,11 +240,7 @@ const tableReqInfo: { [key: string]: any } = {
 const tableInfo = reactive<TableInfo>({
   account: {
     data: [],
-    fields: [
-      { label: '基础', name: 'base1', children: AllFields['account'] },
-      { label: '基础2', name: 'base2', children: AllFields['account'] },
-      { label: '基础3', name: 'base3', children: AllFields['account'] },
-    ],
+    fields: [{ label: '基础', name: 'base1', children: AllFields['account'] }],
     filters: allFilters['account'],
     fixedFields: allFixedFileds['account'],
   },
@@ -287,14 +286,20 @@ const dateChange = (val: Array<Date>) => {
   //   emits('changeDate', val)
 }
 
+/**
+ * @description: 切换菜单
+ * @param {*} val 新菜单名
+ * @return {*}
+ */
 const menuActiveChange = (val: string) => {
   activeMenu.value = val
   updateTableData()
-  console.log('执行')
-  console.log(activeMenu.value)
-  console.log(tableInfo)
 }
 
+/**
+ * @description: 更新表格数据
+ * @return {*}
+ */
 const updateTableData = () => {
   getData(tableReqInfo[activeMenu.value]).then((res: Array<TableData>) => {
     tableInfo[activeMenu.value].data.splice(
@@ -305,8 +310,31 @@ const updateTableData = () => {
   })
 }
 
+const saveTableInfo = () => {
+  localStorage.setItem(`ttadTableInfo`, JSON.stringify(tableInfo))
+}
+
+const getTableInfo = (): TableInfo => {
+  let info = localStorage.getItem(`ttadTableInfo`)
+  if (!info) return {} as any
+  return JSON.parse(info) as TableInfo
+}
+
+const updateCustomIndicator = (
+  newIndicator: Array<BaseFieldItem<TableFields>>,
+) => {
+  let activeTableFields = tableInfo[activeMenu.value].fields
+  activeTableFields.splice(0, activeTableFields.length, ...newIndicator)
+  saveTableInfo()
+}
+
 updateTableData()
-onMounted(() => {})
+onMounted(() => {
+  let info: TableInfo = getTableInfo()
+  if (Object.keys(info).length !== 0) {
+    Object.assign(tableInfo, info)
+  }
+})
 </script>
 
 <template>
@@ -342,6 +370,7 @@ onMounted(() => {})
         :table-data="tableInfo[activeMenu].data"
         :fixed-fields="tableInfo[activeMenu].fixedFields"
         :filters-info="tableInfo[activeMenu].filters"
+        @update-custom-indicator="updateCustomIndicator"
       ></Table>
     </div>
   </div>
@@ -357,6 +386,7 @@ onMounted(() => {})
 .ttAdHeader {
   width: 100%;
   border-bottom: 1px solid #e0e0e0;
+  background-color: white;
   display: flex;
   justify-content: space-between;
   align-items: center;