Browse Source

1.增加下拉表格多选
2.处理表格分页多选保留值
3.处理分页参数

XueNing 6 months ago
parent
commit
56573a4156

+ 1 - 1
package.json

@@ -24,7 +24,7 @@
     "@wangeditor/editor-for-vue": "^5.1.12",
     "axios": "^1.7.2",
     "dayjs": "^1.11.11",
-    "element-plus": "^2.7.5",
+    "element-plus": "^2.7.6",
     "nprogress": "^0.2.0",
     "pinia": "^2.1.7",
     "splitpanes": "^3.1.5",

File diff suppressed because it is too large
+ 4021 - 176
pnpm-lock.yaml


+ 211 - 17
src/components/FsTableSelect/index.vue

@@ -1,22 +1,33 @@
 <script lang="ts">
 import { tableSelectProps, tableSelectEmits } from './props'
 import { ElPopover } from 'element-plus'
+import type { VxeTableInstance } from 'vxe-table'
 export default defineComponent({
   name: 'FsTableSelect',
   props: tableSelectProps,
   emits: tableSelectEmits,
   setup(props, { emit }) {
-    // 是否显示
-    const visible = ref(false)
     // popover 实例
     const popoverRef = ref<InstanceType<typeof ElPopover>>()
+    // 表格实例
+    const tableRef = ref<VxeTableInstance<any>>()
+    // 是否显示
+    const visible = ref(false)
+    // 选中展示的数据
+    const selectLabel = ref<string | Array<any>>('')
+    // 加载状态
+    const loading = ref(false)
+    // 表格数据
+    const tableData = ref<any[]>([])
+    // 页码
+    const pageIndex = ref<number>(1)
 
     // 是否未选中
     const isEmpty = computed<boolean>(() => {
       if (!props.multiple) {
-        return props.moduleValue == null || props.moduleValue === ''
+        return props.modelValue == null || props.modelValue === ''
       }
-      return !Array.isArray(props.moduleValue) || !props.moduleValue.length
+      return !Array.isArray(props.modelValue) || !props.modelValue.length
     })
 
     // 是否需要清空图标
@@ -32,12 +43,6 @@ export default defineComponent({
       emit('focus', e)
     }
 
-    const tableData = computed(() => {
-      return props.tableConfig?.datasource.then(res => {
-        return res
-      })
-    })
-
     /* 关闭弹窗 */
     const onBlur = (e: FocusEvent) => {
       emit('blur', e)
@@ -45,17 +50,122 @@ export default defineComponent({
 
     /* 清除事件 */
     const onClear = () => {
+      updateModelValue(props.multiple ? [] : null)
+      selectLabel.value = ''
+      // 取消表格全部选中
+      tableRef.value?.clearCheckboxRow()
       emit('clear')
     }
 
+    /* 单个清除事件 */
+    const onItemClear = (item: any) => {
+      const list = [...(selectLabel.value as Array<any>)]
+      const index = list.findIndex(x => x[props.labelKey] === item[props.labelKey])
+      list.splice(index, 1)
+      selectLabel.value = list
+
+      // 取消表格选中数据
+      tableRef.value?.toggleCheckboxRow(item)
+      updateModelValue(list.map(x => x[props.valueKey]))
+      emit('item-clear', { item, list })
+    }
+
+    /* 表格单选事件 */
+    const tableRadioChange = (data: any) => {
+      selectLabel.value = data.row[props.labelKey]
+      visible.value = false
+      // 发出选择事件
+      updateModelValue(data.row[props.valueKey])
+      emit('change', data.row)
+    }
+
+    /* 表格多选择事件 */
+    const tableCheckboxChange = (data: any) => {
+      let result = []
+      if (data.checked) {
+        // 使用 Set 去重
+        const uniqueArray = Array.from(
+          new Set([...selectLabel.value, ...data.records].map((x: any) => JSON.stringify(x)))
+        ).map((str: any) => JSON.parse(str))
+        selectLabel.value = uniqueArray
+        result = selectLabel.value.map(x => x[props.valueKey])
+      } else {
+        const selects = selectLabel.value as Array<any>
+        const index = selects.findIndex(x => x[props.valueKey] === data.row[props.valueKey])
+        selects?.splice(index, 1)
+        result = selects.map(x => x[props.valueKey])
+      }
+      // 发出选择事件
+      updateModelValue(result)
+      emit('change', selectLabel.value)
+    }
+
+    /* 分页改变事件 */
+    const paginationChange = (data: number) => {
+      pageIndex.value = data
+      request()
+    }
+
+    /* 表格请求完成 */
+    const tableDone = () => {
+      if (props.multiple) {
+        nextTick(() => {
+          const newTableData = tableRef.value?.getTableData().tableData
+          newTableData?.forEach(item => {
+            const temp =
+              Array.isArray(selectLabel.value) &&
+              selectLabel.value.find(x => x[props.valueKey] === item[props.valueKey])
+            temp && tableRef.value?.setCheckboxRow(item, true)
+          })
+        })
+      }
+    }
+
+    /* 请求数据 */
+    const request = () => {
+      if (typeof props.tableConfig?.datasource === 'function') {
+        loading.value = true
+        props.tableConfig
+          .datasource({
+            pageIndex: pageIndex.value,
+            pageSize: props.tableConfig.pageSize
+          })
+          .then((res: any) => {
+            tableData.value = res
+            tableDone()
+          })
+          .catch((e: any) => {
+            console.warn(e)
+          })
+          .finally(() => {
+            loading.value = false
+          })
+      } else {
+        console.warn('tableConfig.datasource 必须为 Promise')
+      }
+    }
+    request()
+
+    /* 更新选中值 */
+    const updateModelValue = (value: any) => {
+      emit('update:modelValue', value)
+    }
     return {
       popoverRef,
+      tableRef,
+      selectLabel,
       visible,
+      isEmpty,
+      loading,
       tableData,
       closeEnable,
       onFocus,
       onBlur,
-      onClear
+      onClear,
+      onItemClear,
+      tableRadioChange,
+      tableCheckboxChange,
+      paginationChange
     }
   }
 })
@@ -70,6 +180,7 @@ export default defineComponent({
     :popper-class="popperClass"
     :popper-options="popperOptions"
     trigger="click"
+    transition="el-zoom-in-top"
     teleported
   >
     <template #reference>
@@ -77,11 +188,11 @@ export default defineComponent({
         <el-input
           :size="size"
           :disabled="disabled"
-          :placeholder="placeholder"
+          :placeholder="multiple && !isEmpty ? '' : placeholder"
           :readonly="true"
           :validateEvent="false"
           :autocomplete="autocomplete"
-          :modelValue="moduleValue"
+          :modelValue="multiple ? '' : selectLabel"
           @focus="onFocus"
           @blur="onBlur"
         >
@@ -95,24 +206,107 @@ export default defineComponent({
                   <CircleClose />
                 </slot>
               </ElIcon>
-              <ElIcon>
-                <slot name="suffixIcon" :visible="visible">
+              <ElIcon class="select-down" :class="{ 'select-down-rotate': visible }">
+                <slot name="suffixIcon">
                   <ArrowDown />
                 </slot>
               </ElIcon>
             </div>
           </template>
         </el-input>
+        <div class="table-select-multiple" v-if="multiple">
+          <el-tag
+            type="info"
+            size="small"
+            disable-transitions
+            closable
+            style="margin-right: 5px"
+            v-for="item in selectLabel"
+            :key="item[valueKey]"
+            @close="onItemClear(item)"
+          >
+            {{ item[labelKey] }}
+          </el-tag>
+        </div>
       </div>
     </template>
-    <vxe-table :data="tableData">
-      <vxe-column v-for="(item, index) in tableConfig?.column" :key="index" v-bind="item"></vxe-column>
+    <vxe-table
+      :data="tableData"
+      :row-config="{ isCurrent: true, isHover: true }"
+      :radio-config="{ trigger: multiple ? '' : 'row' }"
+      :checkbox-config="{ trigger: multiple ? 'row' : '' }"
+      ref="tableRef"
+      @radio-change="tableRadioChange"
+      @checkbox-all="tableCheckboxChange"
+      @checkbox-change="tableCheckboxChange"
+      v-loading="loading"
+    >
+      <vxe-column type="checkbox" width="60" v-if="multiple"></vxe-column>
+      <vxe-column type="radio" width="40" v-else> </vxe-column>
+      <vxe-column v-for="(item, index) in tableConfig?.column" :key="index" v-bind="item">
+        <template #default="slotProps" v-if="item.slot">
+          <slot :name="item.slot" v-bind="slotProps || {}"></slot>
+        </template>
+      </vxe-column>
     </vxe-table>
+    <div class="table-select-pagination" v-if="tableConfig.total">
+      <el-pagination
+        background
+        layout="total, prev, pager, next, jumper"
+        size="small"
+        :pager-count="5"
+        :page-size="tableConfig.pageSize"
+        :total="tableConfig.total"
+        @current-change="paginationChange"
+      />
+    </div>
   </el-popover>
 </template>
 
 <style scoped lang="scss">
 .table-select-container {
   width: 100%;
+  position: relative;
+  .select-clear {
+    position: absolute;
+    right: 10px;
+    top: 49%;
+    transform: translateY(-50%);
+    cursor: pointer;
+    border-radius: 50%;
+    overflow: hidden;
+    z-index: 2;
+    color: #fff;
+    background-color: var(--el-color-info-light-3);
+    opacity: 0;
+    &:hover {
+      background-color: var(--el-color-info);
+    }
+  }
+  .select-down {
+    transition: transform 0.2s;
+  }
+  .select-down-rotate {
+    transform: rotate(180deg);
+  }
+  &:hover {
+    .select-clear {
+      opacity: 1;
+    }
+  }
+}
+.table-select-multiple {
+  position: absolute;
+  top: 0px;
+  width: calc(100% - 24px);
+  height: 100%;
+  z-index: 2;
+  padding: 4px 10px;
+  box-sizing: border-box;
+}
+.table-select-pagination {
+  display: flex;
+  justify-content: center;
+  margin-top: 12px;
 }
 </style>

+ 20 - 5
src/components/FsTableSelect/props.ts

@@ -5,7 +5,7 @@ import type { TableConfig } from './types'
 
 export const tableSelectProps = {
   // 绑定值
-  moduleValue: {
+  modelValue: {
     type: [String, Number, Array],
     default: ''
   },
@@ -19,13 +19,16 @@ export const tableSelectProps = {
     default: 'default'
   },
   // 是否支持清除
-  clearable: Boolean,
+  clearable: {
+    type: Boolean,
+    default: true
+  },
   // input 的 autocomplete 属性
   autocomplete: String,
   // value 的属性名
   valueKey: {
     type: String,
-    default: 'value'
+    default: 'id'
   },
   // label 的属性名
   labelKey: {
@@ -57,7 +60,13 @@ export const tableSelectProps = {
   popperOptions: Object as PropType<PopoverProps['popperOptions']>,
   // 表格配置
   tableConfig: {
-    type: Object as PropType<TableConfig>
+    type: Object as PropType<TableConfig>,
+    default: {
+      columns: [],
+      loading: false,
+      data: [],
+      total: 0
+    }
   }
 }
 
@@ -67,10 +76,16 @@ export type TableSelectProps = ExtractPropTypes<typeof tableSelectProps>
  * 事件
  */
 export const tableSelectEmits = {
+  // 更新绑定值
+  'update:modelValue': (_value: any) => true,
   // 清除按钮点击事件
   clear: () => true,
+  // 多选单个清除事件
+  'item-clear': (_value: object) => true,
   // 获取焦点事件
   focus: (_e: FocusEvent) => true,
   // 失去焦点事件
-  blur: (_e: FocusEvent) => true
+  blur: (_e: FocusEvent) => true,
+  // 输入框值改变事件
+  change: (_value: string | Array<any>) => true
 }

+ 10 - 2
src/components/FsTableSelect/types/index.ts

@@ -1,18 +1,26 @@
 /* 多选选中数据文本 */
 export interface TableConfig {
+  // 表格列
   column: Array<TableColumn>
-  datasource: Promise<any>
+  // 表格数据
+  datasource: (...args: any) => Promise<any>
+  // 总数
+  total?: Number
+  // 每页显示条数
+  pageSize?: Number
 }
 
 interface TableColumn {
   // 类型
   type?: string
   // 列名
-  prop?: string
+  field?: string
   // 表格宽度
   width?: number | string
   // 对齐方式
   align?: string
   // 标题
   title?: string
+  // 插槽
+  slot?: string
 }

+ 2 - 1
src/utils/request.ts

@@ -5,7 +5,8 @@ import { useUserStore } from '@/stores/user'
 import { logout } from '@/utils/micro'
 
 const request = axios.create({
-  baseURL: import.meta.env.VITE_BASE_API
+  baseURL: import.meta.env.VITE_BASE_API,
+  timeout: 1000
 })
 
 // 异常拦截处理器

+ 50 - 11
src/views/tableSelect/index.vue

@@ -2,15 +2,26 @@
 import FsTableSelect from '@/components/FsTableSelect/index.vue'
 import type { TableConfig } from '@/components/FsTableSelect/types'
 
-const data = ref('')
+const data = ref()
+const current = ref(null)
+
+const request = function ({ pageIndex }: any) {
+  return new Promise(resolve => {
+    setTimeout(() => {
+      const data = [
+        { name: '张三', sex: '男', id: '' + pageIndex + 1 },
+        { name: '李四', sex: '女', id: '' + pageIndex + 2 },
+        { name: '王五', sex: '男', id: '' + pageIndex + 3 },
+        { name: '赵六', sex: '女', id: '' + pageIndex + 4 },
+        { name: '张三', sex: '男', id: '' + pageIndex + 5 }
+      ]
+      resolve(data)
+    }, 1500)
+  })
+}
 
 const tableConfig: TableConfig = {
   column: [
-    {
-      type: 'selection',
-      width: 45,
-      align: 'center'
-    },
     {
       title: '#',
       type: 'seq',
@@ -19,23 +30,51 @@ const tableConfig: TableConfig = {
     },
     {
       title: '姓名',
-      prop: 'name',
+      field: 'name',
       align: 'center'
     },
     {
       title: '性别',
-      prop: 'name',
-      align: 'center'
+      align: 'center',
+      slot: 'sex'
     }
   ],
-  datasource: Promise.reject([{ name: '张三', sex: '性别' }])
+  datasource: request,
+  total: 100,
+  pageSize: 5
+}
+
+const tableSelectChange = (value: any) => {
+  current.value = value
+}
+
+const tableSelectClear = () => {
+  current.value = null
+}
+
+const tableSelectItemClear = ({ list }: any) => {
+  current.value = list
 }
 </script>
 
 <template>
   <div class="w-full bg-white p-4">
+    <p>当前选中行数据:{{ current }}</p>
+    <p>当前数据:{{ data }}</p>
     <div class="w-230px">
-      <fs-table-select v-model="data" :tableConfig="tableConfig" clearable />
+      <fs-table-select
+        v-model="data"
+        label-key="name"
+        multiple
+        :tableConfig="tableConfig"
+        @change="tableSelectChange"
+        @clear="tableSelectClear"
+        @item-clear="tableSelectItemClear"
+      >
+        <template #sex="{ row }">
+          <el-tag size="small">{{ row.sex }}</el-tag>
+        </template>
+      </fs-table-select>
     </div>
   </div>
 </template>

Some files were not shown because too many files changed in this diff