From 8a8588588fcd50dc8c01193d0a9ee8a632ced54d Mon Sep 17 00:00:00 2001 From: Chuck1sn Date: Sat, 14 Jun 2025 12:34:11 +0800 Subject: [PATCH] tablefilter --- frontend/src/components/README.md | 235 -------------------- frontend/src/components/TableFilterForm.vue | 131 +++++++++++ frontend/src/views/BindRoleView.vue | 174 ++++++++------- frontend/src/views/UserView.vue | 122 +++++----- 4 files changed, 287 insertions(+), 375 deletions(-) delete mode 100644 frontend/src/components/README.md create mode 100644 frontend/src/components/TableFilterForm.vue diff --git a/frontend/src/components/README.md b/frontend/src/components/README.md deleted file mode 100644 index 44b9a2d..0000000 --- a/frontend/src/components/README.md +++ /dev/null @@ -1,235 +0,0 @@ -# 表单布局组件说明 - -本项目提供了四种表单布局组件和一个通用按钮组件,用于不同场景下的数据展示和交互: - -## 1. TableFormLayout.vue - -PC端表格布局组件,适用于不需要checkbox的普通表格展示。 - -### 使用方法 - -```vue - - - - - - -``` - -### 属性 - -- `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 - - - - -``` - -### 属性 - -- `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 - - - - - - - - - - -``` - -### 属性 - -- `items`: 要展示的数据数组 -- `idField`: (可选) 指定数据项的ID字段名,默认为 'id' -- `keyField`: (可选) 指定数据项的唯一键字段名,用于v-for的key - -### 插槽 - -- `title`: 卡片标题 -- `status`: 状态指示器 -- `content`: 卡片主要内容 -- `tags`: (可选) 标签/分类 -- `actions`: 操作按钮 - -## 4. MobileCardListWithCheckbox.vue - -移动端卡片布局组件,带有checkbox功能,适用于需要多选的卡片(如绑定关系管理页面)。 - -### 使用方法 - -```vue - - - - - -``` - -### 属性 - -- `items`: 要展示的数据数组 -- `idField`: (可选) 指定数据项的ID字段名,默认为 'id' -- `keyField`: (可选) 指定数据项的唯一键字段名,用于v-for的key -- `modelValue`: (可选) 选中项的ID数组,支持v-model双向绑定 - -### 事件 - -- `update:modelValue`: 当选中项变化时触发,参数为选中项的ID数组 - -## 5. TableButton.vue - -通用按钮组件,适用于表格和卡片中的操作按钮。 - -### 使用方法 - -```vue - -默认按钮 - - - - - 编辑 - - - -次要按钮 -成功按钮 -危险按钮 -警告按钮 -信息按钮 - - -超小按钮 -小按钮 -中按钮 -大按钮 - - -移动端按钮 - - -禁用按钮 -``` - -### 属性 - -- `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 - -
- - - -
- - - - ``` diff --git a/frontend/src/components/TableFilterForm.vue b/frontend/src/components/TableFilterForm.vue new file mode 100644 index 0000000..bd28ae8 --- /dev/null +++ b/frontend/src/components/TableFilterForm.vue @@ -0,0 +1,131 @@ + + + diff --git a/frontend/src/views/BindRoleView.vue b/frontend/src/views/BindRoleView.vue index 8dd8ecb..9d87ba7 100644 --- a/frontend/src/views/BindRoleView.vue +++ b/frontend/src/views/BindRoleView.vue @@ -4,60 +4,38 @@

角色分配

-
-
-
- -
-
- -
- -
+ + + +
@@ -117,26 +95,60 @@ import MobileCardListWithCheckbox from "@/components/MobileCardListWithCheckbox. import BindModal from "@/components/PopupModal.vue"; import UnModal from "@/components/PopupModal.vue"; import TableButton from "@/components/TableButton.vue"; +import TableFilterForm from "@/components/TableFilterForm.vue"; +import type { FilterItem } from "@/components/TableFilterForm.vue"; import TableFormLayout from "@/components/TableFormLayout.vue"; import TablePagination from "@/components/TablePagination.vue"; import { useRolesQuery } from "@/composables/role/useRolesQuery"; +import { useActionExcStore } from "@/composables/store/useActionExcStore"; import { RouteName } from "@/router/constants"; import { Modal, type ModalInterface, initFlowbite } from "flowbite"; -import { onMounted, ref, watch } from "vue"; +import { onMounted, reactive, ref } from "vue"; import { useRoute } from "vue-router"; import { useRoleBind } from "../composables/role/useRoleBind"; import useAlertStore from "../composables/store/useAlertStore"; -import { useActionExcStore } from "@/composables/store/useActionExcStore"; -import VueDatePicker from "@vuepic/vue-datepicker"; -import "@vuepic/vue-datepicker/dist/main.css"; -const roleName = ref(""); +const filterConfig: FilterItem[] = [ + { + type: "input", + name: "roleName", + placeholder: "角色名", + }, + { + type: "select", + name: "bindState", + options: [ + { value: "BIND", label: "已绑定" }, + { value: "UNBIND", label: "未绑定" }, + { value: "ALL", label: "全部" }, + ], + }, +]; + +const filterValues = reactive<{ + roleName: string; + bindState: "BIND" | "ALL" | "UNBIND"; +}>({ + roleName: "", + bindState: "ALL", +}); + +const updateFilterValues = ( + values: Record, +) => { + if (values.roleName !== undefined) { + filterValues.roleName = values.roleName as string; + } + if (values.bindState !== undefined) { + filterValues.bindState = values.bindState as "BIND" | "ALL" | "UNBIND"; + } +}; + const checkedRoleIds = ref([]); const roleBindModal = ref(); const roleUnbindModal = ref(); const allChecked = ref(false); const $route = useRoute(); -const bindState = ref<"BIND" | "ALL" | "UNBIND">("ALL"); const alertStore = useAlertStore(); const { total, roles, fetchRolesWith } = useRolesQuery(); @@ -162,9 +174,9 @@ const handleBindRoleSubmit = async () => { level: "success", }); await fetchRolesWith({ - name: roleName.value, + name: filterValues.roleName, userId: Number($route.params.userId), - bindState: bindState.value, + bindState: filterValues.bindState, }); }; @@ -178,17 +190,17 @@ const handleUnbindRoleSubmit = async () => { level: "success", }); await fetchRolesWith({ - name: roleName.value, + name: filterValues.roleName, userId: Number($route.params.userId), - bindState: bindState.value, + bindState: filterValues.bindState, }); }; onMounted(async () => { await fetchRolesWith({ - name: roleName.value, + name: filterValues.roleName, userId: Number($route.params.userId), - bindState: bindState.value, + bindState: filterValues.bindState, }); initFlowbite(); const $bindModalElement: HTMLElement | null = @@ -198,11 +210,9 @@ onMounted(async () => { } const $unbindModalElement: HTMLElement | null = document.querySelector("#role-unbind-modal"); - roleUnbindModal.value = new Modal( - $unbindModalElement, - {}, - { id: "role-unbind-modal" }, - ); + if ($unbindModalElement) { + roleUnbindModal.value = new Modal($unbindModalElement, {}); + } actionExcStore.setCallback((result) => { if (result) { handleSearch(); @@ -210,37 +220,29 @@ onMounted(async () => { }); }); +const clearCheckedRoleIds = () => { + checkedRoleIds.value = []; +}; + const handleSearch = async () => { await fetchRolesWith({ - name: roleName.value, + name: filterValues.roleName, userId: Number($route.params.userId), - bindState: bindState.value, + bindState: filterValues.bindState, }); }; const handlePageChange = async (page: number, pageSize: number) => { await fetchRolesWith( { - name: roleName.value, + name: filterValues.roleName, userId: Number($route.params.userId), - bindState: bindState.value, + bindState: filterValues.bindState, }, page, pageSize, ); }; - -watch(allChecked, () => { - if (allChecked.value) { - checkedRoleIds.value = roles.value?.map((r) => r.id!) ?? []; - } else { - checkedRoleIds.value = []; - } -}); - -const clearCheckedRoleIds = () => { - checkedRoleIds.value = []; -}; diff --git a/frontend/src/views/UserView.vue b/frontend/src/views/UserView.vue index 0723389..f1dd1a3 100644 --- a/frontend/src/views/UserView.vue +++ b/frontend/src/views/UserView.vue @@ -4,40 +4,21 @@

用户管理

-
- -
- -
-
- -
- -
-
- - - - - - -
+ + + +
@@ -178,25 +159,58 @@ import Breadcrumbs from "@/components/Breadcrumbs.vue"; import Button from "@/components/Button.vue"; import UserDeleteModal from "@/components/PopupModal.vue"; import SortIcon from "@/components/SortIcon.vue"; +import TableFilterForm, { + type FilterItem, +} from "@/components/TableFilterForm.vue"; import TableFormLayout from "@/components/TableFormLayout.vue"; import TablePagination from "@/components/TablePagination.vue"; import UserUpsertModal from "@/components/UserUpsertModal.vue"; import { useSort } from "@/composables/sort"; +import { useActionExcStore } from "@/composables/store/useActionExcStore"; import useUserDelete from "@/composables/user/useUserDelete"; import { useUserQuery } from "@/composables/user/useUserQuery"; import { RouteName } from "@/router/constants"; import type { UserUpsertSubmitModel } from "@/types/user"; import { dayjs, formatDate } from "@/utils/dateUtil"; import { Modal, type ModalInterface, initFlowbite } from "flowbite"; -import { nextTick, onMounted, ref } from "vue"; +import { nextTick, onMounted, reactive, ref } from "vue"; import { useRouter } from "vue-router"; import type { components } from "../api/types/schema"; import useAlertStore from "../composables/store/useAlertStore"; import { useUserUpsert } from "../composables/user/useUserUpsert"; -import { useActionExcStore } from "@/composables/store/useActionExcStore"; -const dateRange = ref(); -const username = ref(""); +const filterConfig: FilterItem[] = [ + { + type: "input", + name: "username", + placeholder: "用户名", + }, + { + type: "date-range", + name: "dateRange", + format: "yyyy/MM/dd HH:mm:ss - yyy/MM/dd HH:mm:ss", + }, +]; + +const filterValues = reactive<{ + username: string; + dateRange?: Date[]; +}>({ + username: "", + dateRange: undefined, +}); + +const updateFilterValues = ( + values: Record, +) => { + if (values.username !== undefined) { + filterValues.username = values.username as string; + } + if (values.dateRange !== undefined) { + filterValues.dateRange = values.dateRange as Date[]; + } +}; + const selectedUser = ref(); const userUpsertModal = ref(); const userDeleteModal = ref(); @@ -218,7 +232,7 @@ const columns = [ onMounted(async () => { await fetchUsersWith({ - username: username.value, + username: filterValues.username, }); initFlowbite(); const $upsertModalElement: HTMLElement | null = @@ -247,9 +261,9 @@ const handleUpsertUserSubmit = async (data: UserUpsertSubmitModel) => { }); await fetchUsersWith( { - username: username.value, - startDate: formatDate(dateRange?.value?.[0]), - endDate: formatDate(dateRange?.value?.[1]), + username: filterValues.username, + startDate: formatDate(filterValues.dateRange?.[0]), + endDate: formatDate(filterValues.dateRange?.[1]), }, 1, 10, @@ -303,9 +317,9 @@ const handleSortClick = async (field: string) => { handleSort(field); await fetchUsersWith( { - username: username.value, - startDate: formatDate(dateRange?.value?.[0]), - endDate: formatDate(dateRange?.value?.[1]), + username: filterValues.username, + startDate: formatDate(filterValues.dateRange?.[0]), + endDate: formatDate(filterValues.dateRange?.[1]), }, 1, 10, @@ -323,9 +337,9 @@ const handleDeleteUserSubmit = async () => { }); await fetchUsersWith( { - username: username.value, - startDate: formatDate(dateRange?.value?.[0]), - endDate: formatDate(dateRange?.value?.[1]), + username: filterValues.username, + startDate: formatDate(filterValues.dateRange?.[0]), + endDate: formatDate(filterValues.dateRange?.[1]), }, 1, 10, @@ -345,9 +359,9 @@ const handleDeleteUserClick = async ( const handleSearch = async () => { await fetchUsersWith( { - username: username.value, - startDate: formatDate(dateRange?.value?.[0]), - endDate: formatDate(dateRange?.value?.[1]), + username: filterValues.username, + startDate: formatDate(filterValues.dateRange?.[0]), + endDate: formatDate(filterValues.dateRange?.[1]), }, 1, 10, @@ -358,9 +372,9 @@ const handleSearch = async () => { const handlePageChange = async (page: number, pageSize: number) => { await fetchUsersWith( { - username: username.value, - startDate: formatDate(dateRange?.value?.[0]), - endDate: formatDate(dateRange?.value?.[1]), + username: filterValues.username, + startDate: formatDate(filterValues.dateRange?.[0]), + endDate: formatDate(filterValues.dateRange?.[1]), }, page, pageSize,