a
This commit is contained in:
114
src/components/GenderTag/index.vue
Normal file
114
src/components/GenderTag/index.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<el-tag
|
||||
v-if="showTag"
|
||||
:type="type"
|
||||
:effect="effect"
|
||||
>
|
||||
<span class="gender-tag">
|
||||
<el-icon>
|
||||
<Male v-if="isMale" />
|
||||
<Female v-else-if="isFemale" />
|
||||
</el-icon>
|
||||
<span class="gender-label">{{ label }}</span>
|
||||
</span>
|
||||
</el-tag>
|
||||
<span v-else class="gender-tag" :class="genderClass">
|
||||
<el-icon>
|
||||
<Male v-if="isMale" />
|
||||
<Female v-else-if="isFemale" />
|
||||
</el-icon>
|
||||
<span class="gender-label">{{ label }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Male, Female } from '@element-plus/icons-vue'
|
||||
|
||||
interface Props {
|
||||
sex?: string | number; // 性别值:1=男,0=女
|
||||
showTag?: boolean; // 是否显示标签样式(有边框和背景),默认为 false
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
sex: '',
|
||||
showTag: false
|
||||
})
|
||||
|
||||
// 判断是否为男性(1=男)
|
||||
const isMale = computed(() => {
|
||||
const sex = String(props.sex)
|
||||
const sexNum = Number(props.sex)
|
||||
return sexNum === 1 || sex === '1'
|
||||
})
|
||||
|
||||
// 判断是否为女性(0=女)
|
||||
const isFemale = computed(() => {
|
||||
const sex = String(props.sex)
|
||||
const sexNum = Number(props.sex)
|
||||
return sexNum === 0 || sex === '0'
|
||||
})
|
||||
|
||||
// 根据性别计算显示内容
|
||||
const label = computed(() => {
|
||||
if (isMale.value) {
|
||||
return '男'
|
||||
} else if (isFemale.value) {
|
||||
return '女'
|
||||
}
|
||||
return '-'
|
||||
})
|
||||
|
||||
const type = computed(() => {
|
||||
if (isMale.value) {
|
||||
return 'primary' // 蓝色
|
||||
} else if (isFemale.value) {
|
||||
return 'danger' // 红色/粉色
|
||||
}
|
||||
return 'info' // 灰色
|
||||
})
|
||||
|
||||
const effect = computed(() => {
|
||||
if (isMale.value || isFemale.value) {
|
||||
return 'light'
|
||||
}
|
||||
return 'plain'
|
||||
})
|
||||
|
||||
const genderClass = computed(() => {
|
||||
if (isMale.value) {
|
||||
return 'gender-male'
|
||||
} else if (isFemale.value) {
|
||||
return 'gender-female'
|
||||
}
|
||||
return 'gender-unknown'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gender-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.gender-tag .el-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.gender-male {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.gender-female {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.gender-unknown {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
149
src/components/StatusTag/README.md
Normal file
149
src/components/StatusTag/README.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# StatusTag 组件
|
||||
|
||||
状态标签组件,用于显示状态值对应的标签文本,支持自定义样式和颜色。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持标签样式和纯文本样式两种显示模式
|
||||
- ✅ 支持自定义类型映射(typeMap)和颜色映射(colorMap)
|
||||
- ✅ 内置默认样式('1' → warning/dark,'0' → primary/light)
|
||||
- ✅ 外部传入优先,支持覆盖默认样式
|
||||
|
||||
## Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 必填 |
|
||||
|------|------|------|--------|------|
|
||||
| value | 当前状态值 | `string \| number` | `''` | 是 |
|
||||
| options | 选项列表,格式:`[{label: '是', value: '1'}, {label: '否', value: '0'}]` | `Option[]` | `[]` | 是 |
|
||||
| showTag | 是否显示标签样式(有边框和背景),`false` 为纯文本样式 | `boolean` | `true` | 否 |
|
||||
| typeMap | 自定义类型映射,用于标签模式,如:`{'1': {type: 'warning', effect: 'dark'}}` | `Record<string \| number, { type: string; effect?: string }>` | `{}` | 否 |
|
||||
| colorMap | 自定义颜色映射,用于纯文本模式,如:`{'1': '#E6A23C'}` | `Record<string \| number, string>` | `{}` | 否 |
|
||||
|
||||
### Option 接口
|
||||
|
||||
```typescript
|
||||
interface Option {
|
||||
label: string // 显示文本
|
||||
value: string | number // 选项值
|
||||
}
|
||||
```
|
||||
|
||||
## 默认样式
|
||||
|
||||
组件内置了默认的样式映射,无需传入 `typeMap` 或 `colorMap` 即可使用:
|
||||
|
||||
- **值 '1' 或 1**:
|
||||
- 标签模式:`warning` 类型 + `dark` 效果(橙色深色)
|
||||
- 纯文本模式:`var(--el-color-warning)`(橙色)
|
||||
|
||||
- **值 '0' 或 0**:
|
||||
- 标签模式:`primary` 类型 + `light` 效果(蓝色浅色)
|
||||
- 纯文本模式:`var(--el-color-primary)`(蓝色)
|
||||
|
||||
- **其他值**:
|
||||
- 标签模式:`info` 类型 + `light` 效果(灰色)
|
||||
- 纯文本模式:默认文本颜色
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础用法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<StatusTag
|
||||
:value="scope.row.tied"
|
||||
:options="YES_OR_NO"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import StatusTag from '/@/components/StatusTag/index.vue'
|
||||
|
||||
const YES_OR_NO = [
|
||||
{ label: '是', value: '1' },
|
||||
{ label: '否', value: '0' }
|
||||
]
|
||||
</script>
|
||||
```
|
||||
|
||||
### 纯文本模式(无边框和背景)
|
||||
|
||||
```vue
|
||||
<StatusTag
|
||||
:value="scope.row.tied"
|
||||
:options="YES_OR_NO"
|
||||
:show-tag="false"
|
||||
/>
|
||||
```
|
||||
|
||||
### 自定义类型映射
|
||||
|
||||
```vue
|
||||
<StatusTag
|
||||
:value="scope.row.status"
|
||||
:options="statusOptions"
|
||||
:type-map="{
|
||||
'1': { type: 'success', effect: 'dark' },
|
||||
'0': { type: 'danger', effect: 'light' }
|
||||
}"
|
||||
/>
|
||||
```
|
||||
|
||||
### 自定义颜色映射(纯文本模式)
|
||||
|
||||
```vue
|
||||
<StatusTag
|
||||
:value="scope.row.status"
|
||||
:options="statusOptions"
|
||||
:show-tag="false"
|
||||
:color-map="{
|
||||
'1': '#67C23A',
|
||||
'0': '#F56C6C'
|
||||
}"
|
||||
/>
|
||||
```
|
||||
|
||||
### 完整示例
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column label="是否退休" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<StatusTag
|
||||
:value="scope.row.tied"
|
||||
:options="YES_OR_NO"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import global from '/@/components/tools/commondict.vue'
|
||||
|
||||
const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
|
||||
const YES_OR_NO = global.YES_OR_NO
|
||||
</script>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **必须传入 `options`**:组件不提供默认选项,必须通过 `options` prop 传入选项列表
|
||||
2. **值匹配**:组件会自动匹配字符串和数字类型的值(如 `'1'` 和 `1` 会被视为相同)
|
||||
3. **样式优先级**:外部传入的 `typeMap` 和 `colorMap` 会覆盖默认样式
|
||||
4. **未匹配值**:如果 `value` 在 `options` 中找不到对应项,会显示 `'-'`
|
||||
|
||||
## 样式说明
|
||||
|
||||
### 标签模式(showTag: true)
|
||||
|
||||
使用 Element Plus 的 `el-tag` 组件,支持所有 `el-tag` 的类型和效果:
|
||||
- `type`: `success` | `info` | `warning` | `danger` | `primary`
|
||||
- `effect`: `dark` | `light` | `plain`
|
||||
|
||||
### 纯文本模式(showTag: false)
|
||||
|
||||
使用纯文本显示,通过 CSS 颜色控制样式,支持任何颜色值(CSS 变量、十六进制、RGB 等)。
|
||||
|
||||
140
src/components/StatusTag/index.vue
Normal file
140
src/components/StatusTag/index.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<el-tag
|
||||
v-if="showTag"
|
||||
:type="tagType"
|
||||
:effect="tagEffect"
|
||||
>
|
||||
{{ label }}
|
||||
</el-tag>
|
||||
<span v-else class="status-tag" :class="statusClass" :style="statusStyle">
|
||||
{{ label }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Option {
|
||||
label: string
|
||||
value: string | number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value?: string | number // 当前值
|
||||
options?: Option[] // 选项列表,格式:[{label: '是', value: '1'}, {label: '否', value: '0'}]
|
||||
showTag?: boolean // 是否显示标签样式(有边框和背景),默认为 true
|
||||
typeMap?: Record<string | number, { type: string; effect?: string }> // 自定义类型映射,如 {'1': {type: 'warning', effect: 'dark'}}
|
||||
colorMap?: Record<string | number, string> // 纯文本模式下的颜色映射,如 {'1': '#E6A23C'}
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: '',
|
||||
options: () => [],
|
||||
showTag: true,
|
||||
typeMap: () => ({}),
|
||||
colorMap: () => ({})
|
||||
})
|
||||
|
||||
// 默认的类型映射(只使用字符串键)
|
||||
const defaultTypeMap: Record<string, { type: string; effect: string }> = {
|
||||
'1': { type: 'warning', effect: 'dark' },
|
||||
'0': { type: 'primary', effect: 'light' }
|
||||
}
|
||||
|
||||
// 默认的颜色映射(只使用字符串键)
|
||||
const defaultColorMap: Record<string, string> = {
|
||||
'1': 'var(--el-color-warning)',
|
||||
'0': 'var(--el-color-primary)'
|
||||
}
|
||||
|
||||
// 获取值的字符串形式(用于查找映射)
|
||||
const getValueKey = (value: string | number): string => {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
// 合并后的类型映射(外部传入优先,否则使用默认)
|
||||
const mergedTypeMap = computed(() => {
|
||||
// 将外部传入的 typeMap 也转换为字符串键
|
||||
const externalTypeMap: Record<string, { type: string; effect?: string }> = {}
|
||||
Object.keys(props.typeMap).forEach(key => {
|
||||
externalTypeMap[String(key)] = props.typeMap[key]
|
||||
})
|
||||
return { ...defaultTypeMap, ...externalTypeMap }
|
||||
})
|
||||
|
||||
// 合并后的颜色映射(外部传入优先,否则使用默认)
|
||||
const mergedColorMap = computed(() => {
|
||||
// 将外部传入的 colorMap 也转换为字符串键
|
||||
const externalColorMap: Record<string, string> = {}
|
||||
Object.keys(props.colorMap).forEach(key => {
|
||||
externalColorMap[String(key)] = props.colorMap[key]
|
||||
})
|
||||
return { ...defaultColorMap, ...externalColorMap }
|
||||
})
|
||||
|
||||
// 合并后的选项列表(必须通过外部传入 options)
|
||||
const mergedOptions = computed(() => {
|
||||
// 必须传入 options,否则返回空数组
|
||||
return props.options && props.options.length > 0 ? props.options : []
|
||||
})
|
||||
|
||||
// 根据值找到对应的选项
|
||||
const currentOption = computed(() => {
|
||||
return mergedOptions.value.find((opt: Option) => {
|
||||
const optValue = String(opt.value)
|
||||
const propValue = String(props.value)
|
||||
return optValue === propValue || Number(opt.value) === Number(props.value)
|
||||
})
|
||||
})
|
||||
|
||||
// 显示标签
|
||||
const label = computed(() => {
|
||||
return currentOption.value?.label || '-'
|
||||
})
|
||||
|
||||
// 标签类型(showTag 为 true 时使用)
|
||||
const tagType = computed(() => {
|
||||
const valueKey = getValueKey(props.value)
|
||||
if (mergedTypeMap.value[valueKey]) {
|
||||
return mergedTypeMap.value[valueKey].type
|
||||
}
|
||||
return 'info'
|
||||
})
|
||||
|
||||
// 标签效果(showTag 为 true 时使用)
|
||||
const tagEffect = computed(() => {
|
||||
const valueKey = getValueKey(props.value)
|
||||
if (mergedTypeMap.value[valueKey]?.effect) {
|
||||
return mergedTypeMap.value[valueKey].effect
|
||||
}
|
||||
return 'light'
|
||||
})
|
||||
|
||||
// 纯文本模式下的样式类
|
||||
const statusClass = computed(() => {
|
||||
if (props.colorMap[props.value]) {
|
||||
return ''
|
||||
}
|
||||
return 'status-default'
|
||||
})
|
||||
|
||||
// 纯文本模式下的内联样式
|
||||
const statusStyle = computed(() => {
|
||||
const valueKey = getValueKey(props.value)
|
||||
if (mergedColorMap.value[valueKey]) {
|
||||
return { color: mergedColorMap.value[valueKey] }
|
||||
}
|
||||
return {}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-default {
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
</style>
|
||||
|
||||
17
src/components/TableColumn/Provider.vue
Normal file
17
src/components/TableColumn/Provider.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { provide } from 'vue'
|
||||
|
||||
interface Props {
|
||||
isColumnVisible: (propOrLabel: string) => boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// 提供 isColumnVisible 函数给子组件
|
||||
provide('isColumnVisible', props.isColumnVisible)
|
||||
</script>
|
||||
|
||||
76
src/components/TableColumn/README.md
Normal file
76
src/components/TableColumn/README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# TableColumn 组件
|
||||
|
||||
一个自动处理列显示/隐藏的 `el-table-column` 包装组件。
|
||||
|
||||
## 功能
|
||||
|
||||
- 自动根据 `isColumnVisible` 函数控制列的显示/隐藏
|
||||
- 完全兼容 `el-table-column` 的所有属性和插槽
|
||||
- 无需在每个列上手动添加 `v-if="isColumnVisible('xxx')"`
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 在父组件中提供 `isColumnVisible` 函数
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-table ref="tableRef">
|
||||
<TableColumnProvider :is-column-visible="isColumnVisible">
|
||||
<TableColumn prop="name" label="姓名" width="120" />
|
||||
<TableColumn prop="age" label="年龄" width="80" />
|
||||
</TableColumnProvider>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { provide } from 'vue'
|
||||
import TableColumnProvider from '/@/components/TableColumn/Provider.vue'
|
||||
import TableColumn from '/@/components/TableColumn/index.vue'
|
||||
|
||||
const isColumnVisible = (propOrLabel: string) => {
|
||||
// 你的列显示逻辑
|
||||
return true
|
||||
}
|
||||
|
||||
provide('isColumnVisible', isColumnVisible)
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 直接使用(如果已经在父组件中 provide)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-table>
|
||||
<TableColumn prop="name" label="姓名" width="120" />
|
||||
<TableColumn prop="age" label="年龄" width="80" />
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TableColumn from '/@/components/TableColumn/index.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
继承 `el-table-column` 的所有属性,包括:
|
||||
- `prop`: 列的字段名
|
||||
- `label`: 列的标题
|
||||
- `width`: 列宽度
|
||||
- `min-width`: 最小宽度
|
||||
- `fixed`: 是否固定
|
||||
- 等等...
|
||||
|
||||
## Slots
|
||||
|
||||
继承 `el-table-column` 的所有插槽,包括:
|
||||
- `default`: 默认插槽
|
||||
- `header`: 表头插槽
|
||||
- 等等...
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 需要在父组件中使用 `provide` 提供 `isColumnVisible` 函数,或者使用 `TableColumnProvider` 组件
|
||||
2. `isColumnVisible` 函数接收 `prop` 或 `label` 作为参数
|
||||
3. 如果既没有 `prop` 也没有 `label`,列将始终显示
|
||||
|
||||
64
src/components/TableColumn/index.vue
Normal file
64
src/components/TableColumn/index.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<el-table-column
|
||||
v-if="shouldShow"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template v-for="(_, name) in $slots" :key="name" #[name]="slotProps">
|
||||
<slot :name="name" v-bind="slotProps" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue'
|
||||
|
||||
interface Props {
|
||||
prop?: string
|
||||
label?: string
|
||||
// 其他 el-table-column 的所有属性都通过 $attrs 传递
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
prop: '',
|
||||
label: ''
|
||||
})
|
||||
|
||||
// 从父组件注入 isColumnVisible 函数
|
||||
const isColumnVisible = inject<(propOrLabel: string) => boolean>('isColumnVisible', () => true)
|
||||
|
||||
// 计算是否应该显示该列
|
||||
const shouldShow = computed(() => {
|
||||
// 优先使用 prop,如果没有 prop 则使用 label
|
||||
let key = props.prop || props.label || ''
|
||||
if (!key) {
|
||||
// 如果没有 prop 和 label,默认显示(可能是序号列等特殊列)
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果 key 是 label,尝试通过 labelToPropMap 映射到 prop
|
||||
// 这样可以确保与 useTableColumns 的提取逻辑一致
|
||||
if (!props.prop && props.label) {
|
||||
const labelToPropMap: Record<string, string> = {
|
||||
'是否退休': 'tied',
|
||||
'姓名/工号': 'nameNo',
|
||||
'性别': 'sex',
|
||||
'部门': 'deptName',
|
||||
'学历学位': 'dgreeName',
|
||||
'职称等级': 'professionalTitle',
|
||||
'岗位级别': 'stationLevelName',
|
||||
'职业资格等级': 'levelName',
|
||||
'职业资格工种': 'workName',
|
||||
'用工性质': 'employmentNatureName',
|
||||
'手机': 'telPhone',
|
||||
'家庭住址': 'homeAddress',
|
||||
'授课类型': 'teacherCate',
|
||||
'操作': 'action',
|
||||
}
|
||||
// 如果 label 在映射表中,使用映射后的 prop;否则使用 label
|
||||
key = labelToPropMap[props.label] || props.label
|
||||
}
|
||||
|
||||
return isColumnVisible(key)
|
||||
})
|
||||
</script>
|
||||
|
||||
162
src/components/TableColumnControl/README.md
Normal file
162
src/components/TableColumnControl/README.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# TableColumnControl 表格列显隐控制组件
|
||||
|
||||
一个通用的表格列显示/隐藏控制组件,支持动态控制表格列的显示状态。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 动态控制表格列的显示/隐藏
|
||||
- ✅ 支持全选/全不选
|
||||
- ✅ 支持重置为默认值
|
||||
- ✅ 支持 localStorage 持久化
|
||||
- ✅ 支持固定列(不可隐藏)
|
||||
- ✅ 支持始终显示的列
|
||||
- ✅ 可自定义触发按钮样式
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基础用法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<!-- 表格列控制按钮 -->
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
@change="handleColumnChange"
|
||||
/>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table :data="tableData">
|
||||
<el-table-column
|
||||
v-for="col in visibleTableColumns"
|
||||
:key="col.prop"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
|
||||
const tableColumns = [
|
||||
{ prop: 'name', label: '姓名', width: 120 },
|
||||
{ prop: 'age', label: '年龄', width: 80 },
|
||||
{ prop: 'email', label: '邮箱', width: 200 },
|
||||
{ prop: 'address', label: '地址', width: 300 }
|
||||
]
|
||||
|
||||
const visibleColumns = ref<string[]>(['name', 'age', 'email', 'address'])
|
||||
|
||||
// 根据 visibleColumns 过滤出需要显示的列
|
||||
const visibleTableColumns = computed(() => {
|
||||
return tableColumns.filter(col =>
|
||||
visibleColumns.value.includes(col.prop || col.label)
|
||||
)
|
||||
})
|
||||
|
||||
const handleColumnChange = (columns: string[]) => {
|
||||
console.log('显示的列:', columns)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 使用 localStorage 持久化
|
||||
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
storage-key="my-table-columns"
|
||||
/>
|
||||
```
|
||||
|
||||
### 固定列(不可隐藏)
|
||||
|
||||
```vue
|
||||
const tableColumns = [
|
||||
{ prop: 'name', label: '姓名', fixed: 'left' }, // 固定左侧,不可隐藏
|
||||
{ prop: 'age', label: '年龄' },
|
||||
{ prop: 'action', label: '操作', fixed: 'right' } // 固定右侧,不可隐藏
|
||||
]
|
||||
```
|
||||
|
||||
### 始终显示的列
|
||||
|
||||
```vue
|
||||
const tableColumns = [
|
||||
{ prop: 'name', label: '姓名', alwaysShow: true }, // 始终显示,不可隐藏
|
||||
{ prop: 'age', label: '年龄' }
|
||||
]
|
||||
```
|
||||
|
||||
### 自定义触发按钮
|
||||
|
||||
```vue
|
||||
<!-- 使用图标按钮 -->
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
trigger-type="default"
|
||||
trigger-circle
|
||||
/>
|
||||
|
||||
<!-- 使用文字按钮 -->
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
trigger-text="列设置"
|
||||
trigger-type="primary"
|
||||
/>
|
||||
|
||||
<!-- 使用链接按钮 -->
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
trigger-link
|
||||
trigger-text="自定义列"
|
||||
/>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|------|------|------|--------|
|
||||
| columns | 表格列配置数组 | Column[] | 必填 |
|
||||
| modelValue | 当前显示的列的 prop 或 label 数组 | string[] | - |
|
||||
| storageKey | localStorage 存储的 key,用于持久化 | string | - |
|
||||
| triggerType | 触发按钮的类型 | 'default' \| 'primary' \| 'success' \| 'warning' \| 'danger' \| 'info' \| 'text' | 'default' |
|
||||
| triggerSize | 触发按钮的大小 | 'large' \| 'default' \| 'small' | 'default' |
|
||||
| triggerCircle | 触发按钮是否为圆形 | boolean | false |
|
||||
| triggerText | 触发按钮的文字 | string | '' |
|
||||
| triggerLink | 触发按钮是否为链接样式 | boolean | false |
|
||||
|
||||
## Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|--------|------|----------|
|
||||
| update:modelValue | 显示的列变化时触发 | (columns: string[]) |
|
||||
| change | 显示的列变化时触发 | (columns: string[]) |
|
||||
|
||||
## Column 接口
|
||||
|
||||
```typescript
|
||||
interface Column {
|
||||
prop?: string // 列的 prop,用于标识列
|
||||
label: string // 列的标签
|
||||
fixed?: boolean | 'left' | 'right' // 固定列,不可隐藏
|
||||
alwaysShow?: boolean // 始终显示的列,不可隐藏
|
||||
[key: string]: any // 其他属性
|
||||
}
|
||||
```
|
||||
|
||||
## 插槽
|
||||
|
||||
| 插槽名 | 说明 |
|
||||
|--------|------|
|
||||
| trigger | 自定义触发按钮内容 |
|
||||
|
||||
196
src/components/TableColumnControl/USAGE.md
Normal file
196
src/components/TableColumnControl/USAGE.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# TableColumnControl 使用指南
|
||||
|
||||
## 两种使用方式
|
||||
|
||||
### 方式一:自动提取(推荐)✨
|
||||
|
||||
自动从 `el-table` 中提取列配置,无需手动配置。
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<!-- 列设置按钮 -->
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
v-model="visibleTableColumns"
|
||||
storage-key="my-table-columns"
|
||||
trigger-text="列设置"
|
||||
:auto-extract-options="{
|
||||
alwaysShow: ['name', 'action'], // 始终显示的列(prop 或 label)
|
||||
defaultHidden: ['remark'], // 默认隐藏的列
|
||||
}"
|
||||
/>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table ref="tableRef" :data="tableData">
|
||||
<el-table-column prop="name" label="姓名" width="120" />
|
||||
<el-table-column prop="age" label="年龄" width="80" />
|
||||
<el-table-column prop="email" label="邮箱" width="200" />
|
||||
<el-table-column prop="remark" label="备注" width="300" />
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button @click="handleEdit(scope.row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import type { TableInstance } from 'element-plus'
|
||||
|
||||
const tableRef = ref<TableInstance>()
|
||||
const visibleTableColumns = ref<string[]>([])
|
||||
const tableData = ref([...])
|
||||
</script>
|
||||
```
|
||||
|
||||
**优点:**
|
||||
- ✅ 无需手动配置列信息
|
||||
- ✅ 自动同步表格列的变化
|
||||
- ✅ 代码更简洁
|
||||
|
||||
### 方式二:手动配置
|
||||
|
||||
手动传入列配置,适合需要自定义列信息的场景。
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<!-- 列设置按钮 -->
|
||||
<TableColumnControl
|
||||
:columns="tableColumnConfig"
|
||||
v-model="visibleTableColumns"
|
||||
storage-key="my-table-columns"
|
||||
trigger-text="列设置"
|
||||
/>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table :data="tableData">
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('name')"
|
||||
prop="name"
|
||||
label="姓名"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('age')"
|
||||
prop="age"
|
||||
label="年龄"
|
||||
width="80"
|
||||
/>
|
||||
<!-- ... 其他列 ... -->
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
|
||||
const tableColumnConfig = [
|
||||
{ prop: 'name', label: '姓名', width: 120 },
|
||||
{ prop: 'age', label: '年龄', width: 80 },
|
||||
{ prop: 'email', label: '邮箱', width: 200 },
|
||||
{ prop: 'action', label: '操作', width: 150, fixed: 'right', alwaysShow: true }
|
||||
]
|
||||
|
||||
const visibleTableColumns = ref<string[]>([])
|
||||
const tableData = ref([...])
|
||||
|
||||
// 判断列是否显示
|
||||
const isColumnVisible = (propOrLabel: string) => {
|
||||
const column = tableColumnConfig.find(col => (col.prop || col.label) === propOrLabel)
|
||||
if (column && (column.fixed !== undefined || column.alwaysShow)) {
|
||||
return true
|
||||
}
|
||||
return visibleTableColumns.value.includes(propOrLabel)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 自动提取的配置选项
|
||||
|
||||
```typescript
|
||||
interface AutoExtractOptions {
|
||||
// 默认隐藏的列(prop 或 label)
|
||||
defaultHidden?: string[]
|
||||
|
||||
// 始终显示的列(prop 或 label)
|
||||
alwaysShow?: string[]
|
||||
|
||||
// 列配置映射(用于自定义列的显示名称等)
|
||||
columnMap?: Record<string, Partial<ColumnConfig>>
|
||||
}
|
||||
```
|
||||
|
||||
### 示例
|
||||
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
v-model="visibleTableColumns"
|
||||
storage-key="my-table-columns"
|
||||
:auto-extract-options="{
|
||||
// 默认隐藏备注列
|
||||
defaultHidden: ['remark', 'description'],
|
||||
|
||||
// 始终显示姓名和操作列
|
||||
alwaysShow: ['name', 'action'],
|
||||
|
||||
// 自定义列的显示名称
|
||||
columnMap: {
|
||||
'email': { label: '电子邮箱' },
|
||||
'phone': { label: '联系电话' }
|
||||
}
|
||||
}"
|
||||
/>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **自动提取的限制**:
|
||||
- 需要在表格渲染完成后才能提取列配置
|
||||
- 如果表格列是动态生成的,可能需要调用 `refreshColumns()` 方法
|
||||
|
||||
2. **固定列**:
|
||||
- 使用 `fixed="left"` 或 `fixed="right"` 的列会自动标记为不可隐藏
|
||||
- 在 `alwaysShow` 中指定的列也会不可隐藏
|
||||
|
||||
3. **存储键(storageKey)**:
|
||||
- 建议为每个页面使用唯一的 `storageKey`,避免列配置冲突
|
||||
- 格式建议:`页面名称-table-columns`,如 `user-list-table-columns`
|
||||
|
||||
4. **性能考虑**:
|
||||
- 自动提取会在组件挂载和表格更新时执行
|
||||
- 对于大型表格,建议使用手动配置以获得更好的性能
|
||||
|
||||
## 迁移指南
|
||||
|
||||
从手动配置迁移到自动提取:
|
||||
|
||||
**之前(手动配置):**
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:columns="tableColumnConfig"
|
||||
v-model="visibleTableColumns"
|
||||
storage-key="my-table-columns"
|
||||
/>
|
||||
```
|
||||
|
||||
**之后(自动提取):**
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
v-model="visibleTableColumns"
|
||||
storage-key="my-table-columns"
|
||||
/>
|
||||
```
|
||||
|
||||
只需要:
|
||||
1. 将 `:columns` 改为 `:table-ref="tableRef"`
|
||||
2. 在 `el-table` 上添加 `ref="tableRef"`
|
||||
3. 移除 `tableColumnConfig` 和 `isColumnVisible` 函数(如果不再需要)
|
||||
|
||||
461
src/components/TableColumnControl/index.vue
Normal file
461
src/components/TableColumnControl/index.vue
Normal file
@@ -0,0 +1,461 @@
|
||||
<template>
|
||||
<div class="table-column-control">
|
||||
<el-button
|
||||
:type="triggerType"
|
||||
:icon="Setting"
|
||||
:size="triggerSize"
|
||||
:circle="triggerCircle"
|
||||
:link="triggerLink"
|
||||
@click="visible = true"
|
||||
>
|
||||
<slot name="trigger">
|
||||
{{ triggerText || '列设置' }}
|
||||
</slot>
|
||||
</el-button>
|
||||
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="列显示设置"
|
||||
:width="dialogWidth"
|
||||
append-to-body
|
||||
>
|
||||
<div class="column-control-content">
|
||||
<div class="column-control-header">
|
||||
<div class="header-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleToggleSelectAll"
|
||||
>
|
||||
{{ isAllSelected ? '取消全选' : '全选' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column-control-body">
|
||||
<el-checkbox-group v-model="checkedColumns" @change="handleColumnChange" class="column-checkbox-group">
|
||||
<div
|
||||
v-for="column in actualColumns"
|
||||
:key="column.prop || column.label"
|
||||
class="column-item"
|
||||
>
|
||||
<el-checkbox
|
||||
:label="column.prop || column.label"
|
||||
:disabled="column.fixed !== undefined || column.alwaysShow"
|
||||
>
|
||||
{{ column.label }}
|
||||
</el-checkbox>
|
||||
<el-tag v-if="column.fixed !== undefined" size="small" type="info">
|
||||
{{ column.fixed === 'left' ? '固定左侧' : column.fixed === 'right' ? '固定右侧' : '固定' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="column-control-footer">
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, nextTick, type Ref } from 'vue'
|
||||
import { Setting } from '@element-plus/icons-vue'
|
||||
import type { TableInstance } from 'element-plus'
|
||||
import { useTableColumns, type ColumnConfig } from '/@/composables/useTableColumns'
|
||||
|
||||
interface Column {
|
||||
prop?: string
|
||||
label: string
|
||||
fixed?: boolean | 'left' | 'right'
|
||||
alwaysShow?: boolean // 始终显示的列,不可隐藏
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface Props {
|
||||
columns?: Column[] // 手动配置的列(可选,如果提供了 tableRef 则自动提取)
|
||||
tableRef?: Ref<TableInstance | undefined> // el-table 的 ref,用于自动提取列配置
|
||||
modelValue?: string[] // 当前显示的列
|
||||
storageKey?: string // localStorage 存储的 key,用于持久化
|
||||
triggerType?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text'
|
||||
triggerSize?: 'large' | 'default' | 'small'
|
||||
triggerCircle?: boolean
|
||||
triggerText?: string
|
||||
triggerLink?: boolean
|
||||
dialogWidth?: string // 对话框宽度
|
||||
// 自动提取时的配置选项
|
||||
autoExtractOptions?: {
|
||||
defaultHidden?: string[]
|
||||
alwaysShow?: string[]
|
||||
columnMap?: Record<string, Partial<ColumnConfig>>
|
||||
}
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
triggerType: 'default',
|
||||
triggerSize: 'default',
|
||||
triggerCircle: false,
|
||||
triggerText: '',
|
||||
triggerLink: false,
|
||||
dialogWidth: '600px'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string[]]
|
||||
'change': [value: string[]]
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const checkedColumns = ref<string[]>([])
|
||||
|
||||
// 监听弹窗打开,触发列配置重新提取
|
||||
watch(visible, (newVal) => {
|
||||
console.log('[TableColumnControl] 弹窗状态变化:', newVal)
|
||||
if (newVal && props.tableRef) {
|
||||
console.log('[TableColumnControl] 弹窗打开,tableRef 存在:', props.tableRef)
|
||||
console.log('[TableColumnControl] tableRef.value:', props.tableRef.value)
|
||||
console.log('[TableColumnControl] tableRef.value 类型:', typeof props.tableRef.value)
|
||||
|
||||
// 尝试多种方式获取表格实例
|
||||
const getTableInstance = (): TableInstance | null => {
|
||||
// 方法1: 直接从 props.tableRef.value 获取
|
||||
if (props.tableRef?.value) {
|
||||
console.log('[TableColumnControl] 从 props.tableRef.value 获取表格实例')
|
||||
return props.tableRef.value
|
||||
}
|
||||
|
||||
// 方法2: 尝试从 props.tableRef 本身获取(可能是直接的 ref 对象)
|
||||
if ((props.tableRef as any).value) {
|
||||
console.log('[TableColumnControl] 从 props.tableRef 的 value 属性获取表格实例')
|
||||
return (props.tableRef as any).value
|
||||
}
|
||||
|
||||
// 方法3: 如果 props.tableRef 本身就是表格实例(不应该发生,但作为备用)
|
||||
if ((props.tableRef as any).$el) {
|
||||
console.log('[TableColumnControl] props.tableRef 本身就是表格实例')
|
||||
return props.tableRef as any
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// 如果 tableRef.value 已经有值,直接提取
|
||||
const tableInstance = getTableInstance()
|
||||
if (tableInstance) {
|
||||
const tableEl = (tableInstance as any).$el
|
||||
if (tableEl) {
|
||||
console.log('[TableColumnControl] 表格实例已就绪,立即提取列配置')
|
||||
nextTick(() => {
|
||||
refreshAutoColumns()
|
||||
console.log('[TableColumnControl] 刷新后列数:', actualColumns.value.length)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 等待 tableRef.value 有值后再提取
|
||||
let waitCount = 0
|
||||
const maxWaitCount = 50 // 最多等待 5 秒(50 * 100ms)
|
||||
let waitTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const waitForTableRef = () => {
|
||||
// 尝试获取表格实例
|
||||
const tableInstance = getTableInstance()
|
||||
if (tableInstance) {
|
||||
const tableEl = (tableInstance as any).$el
|
||||
if (tableEl) {
|
||||
console.log('[TableColumnControl] 表格实例已就绪,开始提取列配置')
|
||||
nextTick(() => {
|
||||
refreshAutoColumns()
|
||||
console.log('[TableColumnControl] 刷新后列数:', actualColumns.value.length)
|
||||
|
||||
// 如果还是没有数据,多次重试
|
||||
let retryCount = 0
|
||||
const maxRetries = 10
|
||||
const retryInterval = setInterval(() => {
|
||||
retryCount++
|
||||
console.log(`[TableColumnControl] 第 ${retryCount} 次重试刷新列配置`)
|
||||
refreshAutoColumns()
|
||||
console.log(`[TableColumnControl] 重试后列数:`, actualColumns.value.length)
|
||||
if (actualColumns.value.length > 0 || retryCount >= maxRetries) {
|
||||
console.log('[TableColumnControl] 停止重试,最终列数:', actualColumns.value.length)
|
||||
clearInterval(retryInterval)
|
||||
}
|
||||
}, 200)
|
||||
})
|
||||
return // 成功获取,退出
|
||||
}
|
||||
}
|
||||
|
||||
// 继续等待
|
||||
waitCount++
|
||||
if (waitCount < maxWaitCount) {
|
||||
console.log(`[TableColumnControl] tableRef.value 还未就绪,等待中... (${waitCount}/${maxWaitCount})`)
|
||||
waitTimer = setTimeout(waitForTableRef, 100)
|
||||
} else {
|
||||
console.warn('[TableColumnControl] 等待超时,tableRef.value 仍未就绪')
|
||||
console.warn('[TableColumnControl] props.tableRef:', props.tableRef)
|
||||
console.warn('[TableColumnControl] props.tableRef?.value:', props.tableRef?.value)
|
||||
// 即使超时,也尝试提取一次(可能表格已经渲染了,只是 ref 没有正确绑定)
|
||||
console.log('[TableColumnControl] 尝试强制提取列配置')
|
||||
refreshAutoColumns()
|
||||
console.log('[TableColumnControl] 强制提取后列数:', actualColumns.value.length)
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟一下,确保表格已渲染
|
||||
setTimeout(() => {
|
||||
waitForTableRef()
|
||||
}, 300)
|
||||
|
||||
// 清理函数:弹窗关闭时清除等待定时器
|
||||
return () => {
|
||||
if (waitTimer) {
|
||||
clearTimeout(waitTimer)
|
||||
waitTimer = null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('[TableColumnControl] 弹窗打开但 tableRef 不存在或为空')
|
||||
}
|
||||
})
|
||||
|
||||
// 如果提供了 tableRef,使用自动提取;否则使用手动配置的 columns
|
||||
const tableColumnsResult = props.tableRef
|
||||
? useTableColumns(props.tableRef, props.storageKey, props.autoExtractOptions)
|
||||
: {
|
||||
columns: computed(() => []),
|
||||
visibleColumns: computed(() => []),
|
||||
updateVisibleColumns: () => {},
|
||||
refreshColumns: () => {},
|
||||
isColumnVisible: () => true
|
||||
}
|
||||
|
||||
const {
|
||||
columns: autoColumns,
|
||||
visibleColumns: autoVisibleColumns,
|
||||
updateVisibleColumns: updateAutoVisibleColumns,
|
||||
refreshColumns: refreshAutoColumns,
|
||||
isColumnVisible: autoIsColumnVisible
|
||||
} = tableColumnsResult
|
||||
|
||||
// 监听 tableRef.value 的变化,当它被赋值时触发列配置提取
|
||||
if (props.tableRef) {
|
||||
// 尝试多种方式监听 tableRef.value 的变化
|
||||
watch(() => {
|
||||
// 尝试多种方式获取 tableRef.value
|
||||
if (props.tableRef?.value) {
|
||||
return props.tableRef.value
|
||||
}
|
||||
if ((props.tableRef as any).value) {
|
||||
return (props.tableRef as any).value
|
||||
}
|
||||
return null
|
||||
}, (newVal, oldVal) => {
|
||||
if (newVal && newVal !== oldVal) {
|
||||
console.log('[TableColumnControl] tableRef.value 已赋值,触发列配置提取')
|
||||
// 延迟一下,确保表格完全渲染
|
||||
setTimeout(() => {
|
||||
nextTick(() => {
|
||||
refreshAutoColumns()
|
||||
console.log('[TableColumnControl] 列配置提取完成,列数:', actualColumns.value.length)
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
}, { immediate: true }) // 立即检查一次,如果 tableRef.value 已经有值,立即触发
|
||||
}
|
||||
|
||||
// 调试:检查 tableRef 传递
|
||||
console.log('[TableColumnControl] props.tableRef:', props.tableRef)
|
||||
console.log('[TableColumnControl] props.tableRef?.value:', props.tableRef?.value)
|
||||
|
||||
// 暴露给父组件使用
|
||||
defineExpose({
|
||||
isColumnVisible: autoIsColumnVisible,
|
||||
visibleColumns: autoVisibleColumns,
|
||||
refreshColumns: refreshAutoColumns
|
||||
})
|
||||
|
||||
// 实际使用的列配置
|
||||
const actualColumns = computed(() => {
|
||||
const result = props.tableRef && autoColumns.value.length > 0
|
||||
? autoColumns.value
|
||||
: props.columns || []
|
||||
console.log('[TableColumnControl] actualColumns 计算:', {
|
||||
hasTableRef: !!props.tableRef,
|
||||
autoColumnsLength: autoColumns.value.length,
|
||||
propsColumnsLength: props.columns?.length || 0,
|
||||
resultLength: result.length
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
// 初始化选中的列
|
||||
const initCheckedColumns = () => {
|
||||
if (props.modelValue && props.modelValue.length > 0) {
|
||||
checkedColumns.value = [...props.modelValue]
|
||||
} else if (props.tableRef && autoVisibleColumns.value.length > 0) {
|
||||
// 使用自动提取的可见列
|
||||
checkedColumns.value = [...autoVisibleColumns.value]
|
||||
} else if (props.storageKey) {
|
||||
// 从 localStorage 读取
|
||||
const saved = localStorage.getItem(props.storageKey)
|
||||
if (saved) {
|
||||
try {
|
||||
checkedColumns.value = JSON.parse(saved)
|
||||
} catch (e) {
|
||||
// 如果解析失败,使用默认值(所有列)
|
||||
checkedColumns.value = getDefaultColumns()
|
||||
}
|
||||
} else {
|
||||
checkedColumns.value = getDefaultColumns()
|
||||
}
|
||||
} else {
|
||||
checkedColumns.value = getDefaultColumns()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取默认显示的列(所有可隐藏的列)
|
||||
const getDefaultColumns = (): string[] => {
|
||||
return actualColumns.value
|
||||
.filter(col => !col.alwaysShow && col.fixed === undefined)
|
||||
.map(col => col.prop || col.label)
|
||||
}
|
||||
|
||||
// 获取所有可选择的列
|
||||
const selectableColumns = computed(() => {
|
||||
return actualColumns.value
|
||||
.filter(col => !col.alwaysShow && col.fixed === undefined)
|
||||
.map(col => col.prop || col.label)
|
||||
})
|
||||
|
||||
// 判断是否全选
|
||||
const isAllSelected = computed(() => {
|
||||
const selectable = selectableColumns.value
|
||||
if (selectable.length === 0) return false
|
||||
return selectable.every(col => checkedColumns.value.includes(col))
|
||||
})
|
||||
|
||||
// 切换全选/全不选
|
||||
const handleToggleSelectAll = () => {
|
||||
if (isAllSelected.value) {
|
||||
// 当前全选,执行全不选
|
||||
checkedColumns.value = []
|
||||
} else {
|
||||
// 当前未全选,执行全选
|
||||
checkedColumns.value = [...selectableColumns.value]
|
||||
}
|
||||
}
|
||||
|
||||
// 重置为默认值
|
||||
const handleReset = () => {
|
||||
checkedColumns.value = getDefaultColumns()
|
||||
handleColumnChange(checkedColumns.value)
|
||||
}
|
||||
|
||||
// 确认
|
||||
const handleConfirm = () => {
|
||||
handleColumnChange(checkedColumns.value)
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
// 列变化处理
|
||||
const handleColumnChange = (value: string[]) => {
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
|
||||
// 如果使用自动提取,同步更新
|
||||
if (props.tableRef) {
|
||||
updateAutoVisibleColumns(value)
|
||||
} else {
|
||||
// 保存到 localStorage
|
||||
if (props.storageKey) {
|
||||
localStorage.setItem(props.storageKey, JSON.stringify(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听外部 modelValue 变化
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal && newVal.length > 0) {
|
||||
checkedColumns.value = [...newVal]
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
initCheckedColumns()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-column-control {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.column-control-content {
|
||||
.column-control-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
|
||||
.header-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.column-control-body {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.column-checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 16px;
|
||||
}
|
||||
|
||||
.column-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 120px;
|
||||
|
||||
:deep(.el-checkbox) {
|
||||
margin-right: 0;
|
||||
|
||||
.el-checkbox__label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-control-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user