tablefilter

This commit is contained in:
Chuck1sn
2025-06-14 12:34:11 +08:00
parent ac6c50ff28
commit 8a8588588f
4 changed files with 287 additions and 375 deletions

View File

@@ -1,235 +0,0 @@
# 表单布局组件说明
本项目提供了四种表单布局组件和一个通用按钮组件,用于不同场景下的数据展示和交互:
## 1. TableFormLayout.vue
PC端表格布局组件适用于不需要checkbox的普通表格展示。
### 使用方法
```vue
<TableFormLayout
:items="dataItems"
:columns="columns"
:keyField="'id'"
@sort="handleSortClick">
<!-- 自定义列内容 -->
<template #fieldName="{ item }">
{{ item.fieldValue }}
</template>
<!-- 排序图标 -->
<template #sort-icon="{ field }">
<SortIcon :sortField="getSortField(field)" />
</template>
</TableFormLayout>
```
### 属性
- `items`: 要展示的数据数组
- `columns`: 列配置数组,每个列对象包含 `title``field``sortable`(可选)、`class`(可选)
- `idField`: (可选) 指定数据项的ID字段名默认为 'id'
- `keyField`: (可选) 指定数据项的唯一键字段名用于v-for的key
- `hasCheckbox`: (可选) 是否显示复选框默认为false
### 事件
- `sort`: 当点击可排序列时触发,参数为字段名
- `update:checkedItems`: 当选中项变化时触发参数为选中项的ID数组
- `all-checked-change`: 当全选/取消全选时触发,参数为是否全选
## 2. TableFormLayoutWithCheckbox.vue
PC端表格布局组件带有checkbox功能适用于需要多选的表格如绑定关系管理页面
### 使用方法
```vue
<TableFormLayoutWithCheckbox
:items="dataItems"
:columns="columns"
:keyField="'id'"
v-model="checkedIds"
@all-checked-change="allChecked = $event">
<!-- 自定义列内容 -->
<template #fieldName="{ item }">
{{ item.fieldValue }}
</template>
</TableFormLayoutWithCheckbox>
```
### 属性
- `items`: 要展示的数据数组
- `columns`: 列配置数组
- `idField`: (可选) 指定数据项的ID字段名默认为 'id'
- `keyField`: (可选) 指定数据项的唯一键字段名用于v-for的key
- `modelValue`: (可选) 选中项的ID数组支持v-model双向绑定
### 事件
- `update:modelValue`: 当选中项变化时触发参数为选中项的ID数组
- `sort`: 当点击可排序列时触发,参数为字段名
- `all-checked-change`: 当全选/取消全选时触发,参数为是否全选
## 3. MobileCardList.vue
移动端卡片布局组件适用于不需要checkbox的普通卡片展示。
### 使用方法
```vue
<MobileCardList
:items="dataItems"
:keyField="'id'">
<!-- 标题区域 -->
<template #title="{ item }">
{{ item.title }}
</template>
<!-- 状态区域 -->
<template #status="{ item }">
<div class="flex items-center">
<div class="h-2.5 w-2.5 rounded-full me-2" :class="getStatusClass(item)"></div>
<span>{{ getStatusText(item) }}</span>
</div>
</template>
<!-- 内容区域 -->
<template #content="{ item }">
<div>
<p class="text-xs font-medium text-gray-600">标签</p>
<p class="text-sm text-gray-900">{{ item.value }}</p>
</div>
</template>
<!-- 操作按钮区域 -->
<template #actions="{ item }">
<div class="flex gap-x-2">
<TableButton variant="primary" size="xs" isMobile @click="handleEdit(item)">
<template #icon>
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
</svg>
</template>
编辑
</TableButton>
<TableButton variant="danger" size="xs" isMobile @click="handleDelete(item)">删除</TableButton>
</div>
</template>
</MobileCardList>
```
### 属性
- `items`: 要展示的数据数组
- `idField`: (可选) 指定数据项的ID字段名默认为 'id'
- `keyField`: (可选) 指定数据项的唯一键字段名用于v-for的key
### 插槽
- `title`: 卡片标题
- `status`: 状态指示器
- `content`: 卡片主要内容
- `tags`: (可选) 标签/分类
- `actions`: 操作按钮
## 4. MobileCardListWithCheckbox.vue
移动端卡片布局组件带有checkbox功能适用于需要多选的卡片如绑定关系管理页面
### 使用方法
```vue
<MobileCardListWithCheckbox
:items="dataItems"
:keyField="'id'"
v-model="checkedIds">
<!-- 与MobileCardList用法相同 -->
<template #title="{ item }">{{ item.title }}</template>
<template #content="{ item }">{{ item.content }}</template>
</MobileCardListWithCheckbox>
```
### 属性
- `items`: 要展示的数据数组
- `idField`: (可选) 指定数据项的ID字段名默认为 'id'
- `keyField`: (可选) 指定数据项的唯一键字段名用于v-for的key
- `modelValue`: (可选) 选中项的ID数组支持v-model双向绑定
### 事件
- `update:modelValue`: 当选中项变化时触发参数为选中项的ID数组
## 5. TableButton.vue
通用按钮组件,适用于表格和卡片中的操作按钮。
### 使用方法
```vue
<!-- 基本用法 -->
<TableButton @click="handleClick">默认按钮</TableButton>
<!-- 带图标 -->
<TableButton variant="primary" @click="handleEdit">
<template #icon>
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
</svg>
</template>
编辑
</TableButton>
<!-- 不同变体 -->
<TableButton variant="secondary">次要按钮</TableButton>
<TableButton variant="success">成功按钮</TableButton>
<TableButton variant="danger">危险按钮</TableButton>
<TableButton variant="warning">警告按钮</TableButton>
<TableButton variant="info">信息按钮</TableButton>
<!-- 不同尺寸 -->
<TableButton size="xs">超小按钮</TableButton>
<TableButton size="sm">小按钮</TableButton>
<TableButton size="md">中按钮</TableButton>
<TableButton size="lg">大按钮</TableButton>
<!-- 移动端尺寸 -->
<TableButton size="sm" isMobile>移动端按钮</TableButton>
<!-- 禁用状态 -->
<TableButton disabled>禁用按钮</TableButton>
```
### 属性
- `variant`: (可选) 按钮变体类型,可选值为 'primary'、'secondary'、'success'、'danger'、'warning'、'info',默认为 'primary'
- `size`: (可选) 按钮尺寸,可选值为 'xs'、'sm'、'md'、'lg',默认为 'md'
- `disabled`: (可选) 是否禁用,默认为 false
- `className`: (可选) 自定义CSS类名
- `isMobile`: (可选) 是否为移动端尺寸,默认为 false
### 事件
- `click`: 当按钮被点击时触发
## 使用建议
1. 对于普通的数据展示页面(如用户管理、岗位管理等),使用 `TableFormLayout``MobileCardList`
2. 对于需要多选功能的页面(如角色绑定、部门绑定等),使用 `TableFormLayoutWithCheckbox``MobileCardListWithCheckbox`
3. 对于表格和卡片中的操作按钮,使用 `TableButton` 组件
4. 根据屏幕尺寸自动切换布局:
```vue
<!-- 移动端卡片布局 -->
<div class="md:hidden">
<MobileCardList :items="items" :keyField="'id'">
<!-- 插槽内容 -->
</MobileCardList>
</div>
<!-- PC端表格布局 -->
<div class="hidden md:block">
<TableFormLayout :items="items" :columns="columns" :keyField="'id'">
<!-- 插槽内容 -->
</TableFormLayout>
</div>
```

View File

@@ -0,0 +1,131 @@
<template>
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-4 gap-y-3 sm:gap-y-0">
<form class="w-full sm:w-auto flex flex-col xs:flex-row gap-2 xs:gap-3 items-stretch xs:items-center">
<template v-for="(filter, index) in filters" :key="index">
<!-- 输入框类型 -->
<div v-if="filter.type === 'input'" class="flex-grow">
<label :for="`filter-input-${index}`" class="mb-2 text-sm font-medium text-gray-900 sr-only">{{ filter.label
}}</label>
<div class="relative">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
</svg>
</div>
<input type="search" :id="`filter-input-${index}`" v-model="filterValues[filter.name]"
class="block w-full p-2.5 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500"
:placeholder="filter.placeholder || ''" />
</div>
</div>
<!-- 日期范围选择器 -->
<div v-else-if="filter.type === 'date-range'" class="flex-grow">
<VueDatePicker v-model="filterValues[filter.name]" locale="zh-CN" range
:format="filter.format || 'yyyy/MM/dd HH:mm:ss - yyy/MM/dd HH:mm:ss'" :placeholder="filter.placeholder"
:enable-time-picker="filter.enableTimePicker !== false" :auto-apply="filter.autoApply !== false" />
</div>
<!-- 选择器 -->
<div v-else-if="filter.type === 'select'" class="flex-grow">
<select :id="`filter-select-${index}`" v-model="filterValues[filter.name]"
class="w-full xs:w-auto bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5">
<option v-for="option in filter.options" :key="String(option.value)" :value="option.value">
{{ option.label }}
</option>
</select>
</div>
</template>
<button type="submit"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5"
@click.prevent="handleSearch">
搜索
</button>
</form>
<!-- 额外操作按钮插槽 -->
<div class="w-full sm:w-auto">
<slot name="actions"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, watch } from "vue";
export interface FilterOption {
value: string | number | boolean;
label: string;
}
export interface FilterItem {
type: "input" | "select" | "date-range";
name: string;
label?: string;
placeholder?: string;
options?: FilterOption[];
format?: string;
enableTimePicker?: boolean;
autoApply?: boolean;
}
type FilterValues = Record<
string,
string | number | boolean | Date[] | undefined
>;
const props = defineProps<{
filters: FilterItem[];
initialValues?: FilterValues;
}>();
const emit = defineEmits<{
search: [values: FilterValues];
"update:values": [values: FilterValues];
}>();
// 初始化筛选值
const filterValues = reactive<FilterValues>({});
// 初始化默认值
onMounted(() => {
// 初始化所有筛选项的默认值
for (const filter of props.filters) {
if (props.initialValues && props.initialValues[filter.name] !== undefined) {
filterValues[filter.name] = props.initialValues[filter.name];
} else {
// 设置默认值
switch (filter.type) {
case "input":
filterValues[filter.name] = "";
break;
case "select":
filterValues[filter.name] =
filter.options && filter.options.length > 0
? filter.options[0].value
: "";
break;
case "date-range":
filterValues[filter.name] = undefined;
break;
}
}
}
});
// 监听筛选值变化
watch(
filterValues,
(newValues) => {
emit("update:values", { ...newValues });
},
{ deep: true },
);
// 处理搜索
const handleSearch = () => {
emit("search", { ...filterValues });
};
</script>