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>

View File

@@ -4,60 +4,38 @@
<Breadcrumbs :names="['用户管理', '角色分配']" :routes="[{ name: RouteName.USERVIEW }]" />
<h1 class="text-xl sm:text-2xl mb-4 sm:mb-6 font-semibold text-gray-900">角色分配</h1>
</div>
<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">
<div class="flex-grow">
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</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="default-search" v-model="roleName"
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="角色名" required />
</div>
<TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
@update:values="updateFilterValues">
<template #actions>
<div class="flex gap-x-2">
<TableButton variant="primary" @click="() => {
if (checkedRoleIds.length === 0) {
alertStore.showAlert({
content: '没有选择角色',
level: 'error',
});
} else {
roleBindModal?.show();
}
}">
绑定
</TableButton>
<TableButton variant="danger" @click="() => {
if (checkedRoleIds.length === 0) {
alertStore.showAlert({
content: '没有选择角色',
level: 'error',
});
} else {
roleUnbindModal?.show();
}
}">
解绑
</TableButton>
</div>
<select id="countries" v-model="bindState"
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 value="BIND">已绑定</option>
<option value="UNBIND">未绑定</option>
<option value="ALL">全部</option>
</select>
<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="flex gap-x-2">
<TableButton variant="primary" @click="() => {
if (checkedRoleIds.length === 0) {
alertStore.showAlert({
content: '没有选择角色',
level: 'error',
});
} else {
roleBindModal?.show();
}
}">
绑定
</TableButton>
<TableButton variant="danger" @click="() => {
if (checkedRoleIds.length === 0) {
alertStore.showAlert({
content: '没有选择角色',
level: 'error',
});
} else {
roleUnbindModal?.show();
}
}">
解绑
</TableButton>
</div>
</div>
</template>
</TableFilterForm>
<!-- 移动端卡片布局 -->
<div class="md:hidden space-y-4">
@@ -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<string>("");
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<string, string | number | boolean | Date[] | undefined>,
) => {
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<number[]>([]);
const roleBindModal = ref<ModalInterface>();
const roleUnbindModal = ref<ModalInterface>();
const allChecked = ref<boolean>(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 = [];
};
</script>
<style scoped></style>

View File

@@ -4,40 +4,21 @@
<Breadcrumbs :names="['用户管理']" />
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">用户管理</h1>
</div>
<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">
<div class="flex-grow">
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</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="default-search" v-model="username"
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="用户名" required />
</div>
</div>
<VueDatePicker v-model="dateRange" locale="zh-CN" range format="yyyy/MM/dd HH:mm:ss - yyy/MM/dd HH:mm:ss">
</VueDatePicker>
<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>
<!-- Create Modal toggle -->
<Button :handleClick="() => handleUpsertUserClick(undefined)" :isLoading="false" :abortable="false"
submitContent="新增用户" class="w-full sm:w-auto">
<template #icon>
<svg class="w-4 h-4 me-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</template>
</Button>
</div>
<TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
@update:values="updateFilterValues">
<template #actions>
<Button :handleClick="() => handleUpsertUserClick(undefined)" :isLoading="false" :abortable="false"
submitContent="新增用户" class="w-full sm:w-auto">
<template #icon>
<svg class="w-4 h-4 me-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</template>
</Button>
</template>
</TableFilterForm>
<!-- 移动端卡片布局 -->
<div class="md:hidden space-y-4">
@@ -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<Date[]>();
const username = ref<string>("");
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<string, string | number | boolean | Date[] | undefined>,
) => {
if (values.username !== undefined) {
filterValues.username = values.username as string;
}
if (values.dateRange !== undefined) {
filterValues.dateRange = values.dateRange as Date[];
}
};
const selectedUser = ref<components["schemas"]["UserRolePermissionDto"]>();
const userUpsertModal = ref<ModalInterface>();
const userDeleteModal = ref<ModalInterface>();
@@ -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,