fix table filter

This commit is contained in:
Chuck1sn
2025-06-14 13:07:00 +08:00
parent 8a8588588f
commit bb9cd2e529
9 changed files with 548 additions and 430 deletions

View File

@@ -4,60 +4,39 @@
<Breadcrumbs :names="['用户管理', '绑定部门']" :routes="[{ name: RouteName.USERVIEW }]" /> <Breadcrumbs :names="['用户管理', '绑定部门']" :routes="[{ name: RouteName.USERVIEW }]" />
<h1 class="text-xl sm:text-2xl mb-4 sm:mb-6 font-semibold text-gray-900">绑定部门</h1> <h1 class="text-xl sm:text-2xl mb-4 sm:mb-6 font-semibold text-gray-900">绑定部门</h1>
</div> </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"> <TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
<div class="flex-grow"> @update:values="updateFilterValues">
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label> <template #actions>
<div class="relative"> <div class="flex gap-x-2">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"> <TableButton variant="primary" @click="() => {
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" if (checkedDepartmentIds.length === 0) {
viewBox="0 0 20 20"> alertStore.showAlert({
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" content: '没有选择部门',
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" /> level: 'error',
</svg> });
</div> } else {
<input type="search" id="default-search" v-model="departmentName" departmentBindModal?.show();
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> 绑定
</TableButton>
<TableButton variant="danger" @click="() => {
if (checkedDepartmentIds.length === 0) {
alertStore.showAlert({
content: '没有选择部门',
level: 'error',
});
} else {
departmentUnbindModal?.show();
}
}">
解绑
</TableButton>
</div> </div>
<select id="bind-state" v-model="bindState" </template>
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"> </TableFilterForm>
<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 (checkedDepartmentIds.length === 0) {
alertStore.showAlert({
content: '没有选择部门',
level: 'error',
});
} else {
departmentBindModal?.show();
}
}">
绑定
</TableButton>
<TableButton variant="danger" @click="() => {
if (checkedDepartmentIds.length === 0) {
alertStore.showAlert({
content: '没有选择部门',
level: 'error',
});
} else {
departmentUnbindModal?.show();
}
}">
解绑
</TableButton>
</div>
</div>
<!-- 移动端卡片布局 --> <!-- 移动端卡片布局 -->
<div class="md:hidden space-y-4"> <div class="md:hidden space-y-4">
<MobileCardListWithCheckbox :items="departments || []" v-model="checkedDepartmentIds"> <MobileCardListWithCheckbox :items="departments || []" v-model="checkedDepartmentIds">
@@ -114,24 +93,63 @@ import MobileCardListWithCheckbox from "@/components/MobileCardListWithCheckbox.
import BindModal from "@/components/PopupModal.vue"; import BindModal from "@/components/PopupModal.vue";
import UnModal from "@/components/PopupModal.vue"; import UnModal from "@/components/PopupModal.vue";
import TableButton from "@/components/TableButton.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 TableFormLayout from "@/components/TableFormLayout.vue";
import TablePagination from "@/components/TablePagination.vue"; import TablePagination from "@/components/TablePagination.vue";
import { useDepartmentQuery } from "@/composables/department/useDepartmentQuery"; import { useDepartmentQuery } from "@/composables/department/useDepartmentQuery";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
import { RouteName } from "@/router/constants"; import { RouteName } from "@/router/constants";
import { Modal, type ModalInterface, initFlowbite } from "flowbite"; import { Modal, type ModalInterface, initFlowbite } from "flowbite";
import { onMounted, ref, watch } from "vue"; import { onMounted, reactive, ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useDepartmentBind } from "../composables/department/useDepartmentBind"; import { useDepartmentBind } from "../composables/department/useDepartmentBind";
import useAlertStore from "../composables/store/useAlertStore"; import useAlertStore from "../composables/store/useAlertStore";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
const departmentName = ref<string>(""); // 定义筛选配置
const filterConfig: FilterItem[] = [
{
type: "input",
name: "departmentName",
placeholder: "部门名",
},
{
type: "select",
name: "bindState",
options: [
{ value: "BIND", label: "已绑定" },
{ value: "UNBIND", label: "未绑定" },
{ value: "ALL", label: "全部" },
],
},
];
// 筛选值
const filterValues = reactive<{
departmentName: string;
bindState: "BIND" | "ALL" | "UNBIND";
}>({
departmentName: "",
bindState: "ALL",
});
// 更新筛选值
const updateFilterValues = (
values: Record<string, string | number | boolean | Date[] | undefined>,
) => {
if (values.departmentName !== undefined) {
filterValues.departmentName = values.departmentName as string;
}
if (values.bindState !== undefined) {
filterValues.bindState = values.bindState as "BIND" | "ALL" | "UNBIND";
}
};
const checkedDepartmentIds = ref<number[]>([]); const checkedDepartmentIds = ref<number[]>([]);
const departmentBindModal = ref<ModalInterface>(); const departmentBindModal = ref<ModalInterface>();
const departmentUnbindModal = ref<ModalInterface>(); const departmentUnbindModal = ref<ModalInterface>();
const allChecked = ref<boolean>(false); const allChecked = ref<boolean>(false);
const $route = useRoute(); const $route = useRoute();
const bindState = ref<"BIND" | "ALL" | "UNBIND">("ALL");
const alertStore = useAlertStore(); const alertStore = useAlertStore();
const actionExcStore = useActionExcStore(); const actionExcStore = useActionExcStore();
@@ -159,9 +177,9 @@ const handleBindDepartmentSubmit = async () => {
level: "success", level: "success",
}); });
await fetchDepartmentWith({ await fetchDepartmentWith({
name: departmentName.value, name: filterValues.departmentName,
userId: Number($route.params.userId), userId: Number($route.params.userId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
}; };
@@ -178,17 +196,17 @@ const handleUnbindDepartmentSubmit = async () => {
level: "success", level: "success",
}); });
await fetchDepartmentWith({ await fetchDepartmentWith({
name: departmentName.value, name: filterValues.departmentName,
userId: Number($route.params.userId), userId: Number($route.params.userId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
}; };
onMounted(async () => { onMounted(async () => {
await fetchDepartmentWith({ await fetchDepartmentWith({
name: departmentName.value, name: filterValues.departmentName,
userId: Number($route.params.userId), userId: Number($route.params.userId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
initFlowbite(); initFlowbite();
const $bindModalElement: HTMLElement | null = document.querySelector( const $bindModalElement: HTMLElement | null = document.querySelector(
@@ -210,18 +228,18 @@ onMounted(async () => {
const handleSearch = async () => { const handleSearch = async () => {
await fetchDepartmentWith({ await fetchDepartmentWith({
name: departmentName.value, name: filterValues.departmentName,
userId: Number($route.params.userId), userId: Number($route.params.userId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
}; };
const handlePageChange = async (page: number, pageSize: number) => { const handlePageChange = async (page: number, pageSize: number) => {
await fetchDepartmentWith( await fetchDepartmentWith(
{ {
name: departmentName.value, name: filterValues.departmentName,
userId: Number($route.params.userId), userId: Number($route.params.userId),
bindState: bindState.value, bindState: filterValues.bindState,
}, },
page, page,
pageSize, pageSize,

View File

@@ -4,60 +4,38 @@
<Breadcrumbs :names="['角色管理', '绑定权限']" :routes="[{ name: RouteName.ROLEVIEW }]" /> <Breadcrumbs :names="['角色管理', '绑定权限']" :routes="[{ name: RouteName.ROLEVIEW }]" />
<h1 class="text-xl sm:text-2xl mb-4 sm:mb-6 font-semibold text-gray-900">绑定权限</h1> <h1 class="text-xl sm:text-2xl mb-4 sm:mb-6 font-semibold text-gray-900">绑定权限</h1>
</div> </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"> <TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
<div class="flex-grow"> @update:values="updateFilterValues">
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label> <template #actions>
<div class="relative"> <div class="flex gap-x-2">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"> <TableButton variant="primary" @click="() => {
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" if (checkedPermissionIds.length === 0) {
viewBox="0 0 20 20"> alertStore.showAlert({
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" content: '没有选择权限',
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" /> level: 'error',
</svg> });
</div> } else {
<input type="search" id="default-search" v-model="permissionName" permissionBindModal?.show();
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> 绑定
</TableButton>
<TableButton variant="danger" @click="() => {
if (checkedPermissionIds.length === 0) {
alertStore.showAlert({
content: '没有选择权限',
level: 'error',
});
} else {
permissionUnbindModal?.show();
}
}">
解绑
</TableButton>
</div> </div>
<select id="bind-state" v-model="bindState" </template>
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"> </TableFilterForm>
<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 (checkedPermissionIds.length === 0) {
alertStore.showAlert({
content: '没有选择权限',
level: 'error',
});
} else {
permissionBindModal?.show();
}
}">
绑定
</TableButton>
<TableButton variant="danger" @click="() => {
if (checkedPermissionIds.length === 0) {
alertStore.showAlert({
content: '没有选择权限',
level: 'error',
});
} else {
permissionUnbindModal?.show();
}
}">
解绑
</TableButton>
</div>
</div>
<!-- 移动端卡片布局 --> <!-- 移动端卡片布局 -->
<div class="md:hidden space-y-4"> <div class="md:hidden space-y-4">
@@ -116,24 +94,63 @@ import MobileCardListWithCheckbox from "@/components/MobileCardListWithCheckbox.
import BindModal from "@/components/PopupModal.vue"; import BindModal from "@/components/PopupModal.vue";
import UnModal from "@/components/PopupModal.vue"; import UnModal from "@/components/PopupModal.vue";
import TableButton from "@/components/TableButton.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 TableFormLayout from "@/components/TableFormLayout.vue";
import TablePagination from "@/components/TablePagination.vue"; import TablePagination from "@/components/TablePagination.vue";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
import { RouteName } from "@/router/constants"; import { RouteName } from "@/router/constants";
import { Modal, type ModalInterface, initFlowbite } from "flowbite"; import { Modal, type ModalInterface, initFlowbite } from "flowbite";
import { onMounted, ref, watch } from "vue"; import { onMounted, reactive, ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { usePermissionBind } from "../composables/permission/usePermissionBind"; import { usePermissionBind } from "../composables/permission/usePermissionBind";
import usePermissionsQuery from "../composables/permission/usePermissionQuery"; import usePermissionsQuery from "../composables/permission/usePermissionQuery";
import useAlertStore from "../composables/store/useAlertStore"; import useAlertStore from "../composables/store/useAlertStore";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
const permissionName = ref<string>(""); // 定义筛选配置
const filterConfig: FilterItem[] = [
{
type: "input",
name: "permissionName",
placeholder: "权限名",
},
{
type: "select",
name: "bindState",
options: [
{ value: "BIND", label: "已绑定" },
{ value: "UNBIND", label: "未绑定" },
{ value: "ALL", label: "全部" },
],
},
];
// 筛选值
const filterValues = reactive<{
permissionName: string;
bindState: "BIND" | "ALL" | "UNBIND";
}>({
permissionName: "",
bindState: "ALL",
});
// 更新筛选值
const updateFilterValues = (
values: Record<string, string | number | boolean | Date[] | undefined>,
) => {
if (values.permissionName !== undefined) {
filterValues.permissionName = values.permissionName as string;
}
if (values.bindState !== undefined) {
filterValues.bindState = values.bindState as "BIND" | "ALL" | "UNBIND";
}
};
const checkedPermissionIds = ref<number[]>([]); const checkedPermissionIds = ref<number[]>([]);
const permissionBindModal = ref<ModalInterface>(); const permissionBindModal = ref<ModalInterface>();
const permissionUnbindModal = ref<ModalInterface>(); const permissionUnbindModal = ref<ModalInterface>();
const allChecked = ref<boolean>(false); const allChecked = ref<boolean>(false);
const $route = useRoute(); const $route = useRoute();
const bindState = ref<"BIND" | "ALL" | "UNBIND">("ALL");
const alertStore = useAlertStore(); const alertStore = useAlertStore();
const actionExcStore = useActionExcStore(); const actionExcStore = useActionExcStore();
@@ -160,9 +177,9 @@ const handleBindPermissionSubmit = async () => {
clearCheckedRoleIds(); clearCheckedRoleIds();
allChecked.value = false; allChecked.value = false;
await fetchPermissionsWith({ await fetchPermissionsWith({
name: permissionName.value, name: filterValues.permissionName,
roleId: Number($route.params.roleId), roleId: Number($route.params.roleId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
}; };
@@ -179,17 +196,17 @@ const handleUnbindPermissionSubmit = async () => {
clearCheckedRoleIds(); clearCheckedRoleIds();
allChecked.value = false; allChecked.value = false;
await fetchPermissionsWith({ await fetchPermissionsWith({
name: permissionName.value, name: filterValues.permissionName,
roleId: Number($route.params.roleId), roleId: Number($route.params.roleId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
}; };
onMounted(async () => { onMounted(async () => {
await fetchPermissionsWith({ await fetchPermissionsWith({
name: permissionName.value, name: filterValues.permissionName,
roleId: Number($route.params.roleId), roleId: Number($route.params.roleId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
initFlowbite(); initFlowbite();
const $bindModalElement: HTMLElement | null = document.querySelector( const $bindModalElement: HTMLElement | null = document.querySelector(
@@ -215,18 +232,18 @@ onMounted(async () => {
const handleSearch = async () => { const handleSearch = async () => {
await fetchPermissionsWith({ await fetchPermissionsWith({
name: permissionName.value, name: filterValues.permissionName,
roleId: Number($route.params.roleId), roleId: Number($route.params.roleId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
}; };
const handlePageChange = async (page: number, pageSize: number) => { const handlePageChange = async (page: number, pageSize: number) => {
await fetchPermissionsWith( await fetchPermissionsWith(
{ {
name: permissionName.value, name: filterValues.permissionName,
roleId: Number($route.params.roleId), roleId: Number($route.params.roleId),
bindState: bindState.value, bindState: filterValues.bindState,
}, },
page, page,
pageSize, pageSize,

View File

@@ -4,60 +4,38 @@
<Breadcrumbs :names="['用户管理', '绑定岗位']" :routes="[{ name: RouteName.USERVIEW }]" /> <Breadcrumbs :names="['用户管理', '绑定岗位']" :routes="[{ name: RouteName.USERVIEW }]" />
<h1 class="text-xl sm:text-2xl mb-4 sm:mb-6 font-semibold text-gray-900">绑定岗位</h1> <h1 class="text-xl sm:text-2xl mb-4 sm:mb-6 font-semibold text-gray-900">绑定岗位</h1>
</div> </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"> <TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
<div class="flex-grow"> @update:values="updateFilterValues">
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label> <template #actions>
<div class="relative"> <div class="flex gap-x-2">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"> <TableButton variant="primary" @click="() => {
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" if (checkedPositionIds.length === 0) {
viewBox="0 0 20 20"> alertStore.showAlert({
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" content: '没有选择岗位',
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" /> level: 'error',
</svg> });
</div> } else {
<input type="search" id="default-search" v-model="positionName" positionBindModal?.show();
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> 绑定
</TableButton>
<TableButton variant="danger" @click="() => {
if (checkedPositionIds.length === 0) {
alertStore.showAlert({
content: '没有选择岗位',
level: 'error',
});
} else {
positionUnbindModal?.show();
}
}">
解绑
</TableButton>
</div> </div>
<select id="bind-state" v-model="bindState" </template>
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"> </TableFilterForm>
<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 (checkedPositionIds.length === 0) {
alertStore.showAlert({
content: '没有选择岗位',
level: 'error',
});
} else {
positionBindModal?.show();
}
}">
绑定
</TableButton>
<TableButton variant="danger" @click="() => {
if (checkedPositionIds.length === 0) {
alertStore.showAlert({
content: '没有选择岗位',
level: 'error',
});
} else {
positionUnbindModal?.show();
}
}">
解绑
</TableButton>
</div>
</div>
<!-- 移动端卡片布局 --> <!-- 移动端卡片布局 -->
<div class="md:hidden space-y-4"> <div class="md:hidden space-y-4">
@@ -107,25 +85,64 @@ import MobileCardListWithCheckbox from "@/components/MobileCardListWithCheckbox.
import BindModal from "@/components/PopupModal.vue"; import BindModal from "@/components/PopupModal.vue";
import UnModal from "@/components/PopupModal.vue"; import UnModal from "@/components/PopupModal.vue";
import TableButton from "@/components/TableButton.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 TableFormLayout from "@/components/TableFormLayout.vue";
import TablePagination from "@/components/TablePagination.vue"; import TablePagination from "@/components/TablePagination.vue";
import { usePositionBind } from "@/composables/position/usePositionBind"; import { usePositionBind } from "@/composables/position/usePositionBind";
import { usePositionQuery } from "@/composables/position/usePositionQuery"; import { usePositionQuery } from "@/composables/position/usePositionQuery";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
import { useMobileStyles } from "@/composables/useMobileStyles"; import { useMobileStyles } from "@/composables/useMobileStyles";
import { RouteName } from "@/router/constants"; import { RouteName } from "@/router/constants";
import { Modal, type ModalInterface, initFlowbite } from "flowbite"; import { Modal, type ModalInterface, initFlowbite } from "flowbite";
import { onMounted, ref, watch } from "vue"; import { onMounted, reactive, ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import useAlertStore from "../composables/store/useAlertStore"; import useAlertStore from "../composables/store/useAlertStore";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
const positionName = ref<string>(""); // 定义筛选配置
const filterConfig: FilterItem[] = [
{
type: "input",
name: "positionName",
placeholder: "岗位名",
},
{
type: "select",
name: "bindState",
options: [
{ value: "BIND", label: "已绑定" },
{ value: "UNBIND", label: "未绑定" },
{ value: "ALL", label: "全部" },
],
},
];
// 筛选值
const filterValues = reactive<{
positionName: string;
bindState: "BIND" | "ALL" | "UNBIND";
}>({
positionName: "",
bindState: "ALL",
});
// 更新筛选值
const updateFilterValues = (
values: Record<string, string | number | boolean | Date[] | undefined>,
) => {
if (values.positionName !== undefined) {
filterValues.positionName = values.positionName as string;
}
if (values.bindState !== undefined) {
filterValues.bindState = values.bindState as "BIND" | "ALL" | "UNBIND";
}
};
const checkedPositionIds = ref<number[]>([]); const checkedPositionIds = ref<number[]>([]);
const positionBindModal = ref<ModalInterface>(); const positionBindModal = ref<ModalInterface>();
const positionUnbindModal = ref<ModalInterface>(); const positionUnbindModal = ref<ModalInterface>();
const allChecked = ref<boolean>(false); const allChecked = ref<boolean>(false);
const $route = useRoute(); const $route = useRoute();
const bindState = ref<"BIND" | "ALL" | "UNBIND">("ALL");
const alertStore = useAlertStore(); const alertStore = useAlertStore();
const actionExcStore = useActionExcStore(); const actionExcStore = useActionExcStore();
@@ -147,9 +164,9 @@ const handleBindPositionSubmit = async () => {
level: "success", level: "success",
}); });
await fetchPositionWith({ await fetchPositionWith({
name: positionName.value, name: filterValues.positionName,
userId: Number($route.params.userId), userId: Number($route.params.userId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
clearCheckedPositionIds(); clearCheckedPositionIds();
allChecked.value = false; allChecked.value = false;
@@ -163,9 +180,9 @@ const handleUnbindPositionSubmit = async () => {
level: "success", level: "success",
}); });
await fetchPositionWith({ await fetchPositionWith({
name: positionName.value, name: filterValues.positionName,
userId: Number($route.params.userId), userId: Number($route.params.userId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
clearCheckedPositionIds(); clearCheckedPositionIds();
allChecked.value = false; allChecked.value = false;
@@ -173,9 +190,9 @@ const handleUnbindPositionSubmit = async () => {
onMounted(async () => { onMounted(async () => {
await fetchPositionWith({ await fetchPositionWith({
name: positionName.value, name: filterValues.positionName,
userId: Number($route.params.userId), userId: Number($route.params.userId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
initFlowbite(); initFlowbite();
const $bindModalElement: HTMLElement | null = document.querySelector( const $bindModalElement: HTMLElement | null = document.querySelector(
@@ -201,18 +218,18 @@ onMounted(async () => {
const handleSearch = async () => { const handleSearch = async () => {
await fetchPositionWith({ await fetchPositionWith({
name: positionName.value, name: filterValues.positionName,
userId: Number($route.params.userId), userId: Number($route.params.userId),
bindState: bindState.value, bindState: filterValues.bindState,
}); });
}; };
const handlePageChange = async (page: number, pageSize: number) => { const handlePageChange = async (page: number, pageSize: number) => {
await fetchPositionWith( await fetchPositionWith(
{ {
name: positionName.value, name: filterValues.positionName,
userId: Number($route.params.userId), userId: Number($route.params.userId),
bindState: bindState.value, bindState: filterValues.bindState,
}, },
page, page,
pageSize, pageSize,

View File

@@ -4,35 +4,21 @@
<Breadcrumbs :names="['部门管理']" /> <Breadcrumbs :names="['部门管理']" />
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">部门管理</h1> <h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">部门管理</h1>
</div> </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:max-w-xs"> <TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label> @update:values="updateFilterValues">
<div class="relative"> <template #actions>
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"> <Button :handleClick="() => handleUpsertDepartmentClick()" :isLoading="false" :abortable="false"
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" submitContent="新增部门" class="w-full sm:w-auto">
viewBox="0 0 20 20"> <template #icon>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <svg class="w-4 h-4 me-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" /> 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> </svg>
</div> </template>
<input type="search" id="default-search" v-model="name" </Button>
class="block w-full p-3 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500" </template>
placeholder="部门名称" required /> </TableFilterForm>
<button type="submit"
class="text-white absolute end-1.5 bottom-1.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-1.5 sm:px-4 sm:py-2"
@click.prevent="handleSearch">搜索</button>
</div>
</form>
<Button :handleClick="() => handleUpsertDepartmentClick()" :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>
<!-- 移动端卡片布局 --> <!-- 移动端卡片布局 -->
<div class="md:hidden"> <div class="md:hidden">
@@ -131,19 +117,45 @@ import DepartmentUpsertModal from "@/components/DepartmentUpsertModal.vue";
import MobileCardList from "@/components/MobileCardList.vue"; import MobileCardList from "@/components/MobileCardList.vue";
import DepartmentDeleteModal from "@/components/PopupModal.vue"; import DepartmentDeleteModal from "@/components/PopupModal.vue";
import TableButton from "@/components/TableButton.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 TableFormLayout from "@/components/TableFormLayout.vue";
import TablePagination from "@/components/TablePagination.vue"; import TablePagination from "@/components/TablePagination.vue";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
import type { DepartmentUpsertModel } from "@/types/department"; import type { DepartmentUpsertModel } from "@/types/department";
import { Modal, type ModalInterface, initFlowbite } from "flowbite"; import { Modal, type ModalInterface, initFlowbite } from "flowbite";
import { nextTick, onMounted, ref } from "vue"; import { nextTick, onMounted, reactive, ref } from "vue";
import type { components } from "../api/types/schema"; import type { components } from "../api/types/schema";
import useDepartmentDelete from "../composables/department/useDepartmentDelete"; import useDepartmentDelete from "../composables/department/useDepartmentDelete";
import { useDepartmentQuery } from "../composables/department/useDepartmentQuery"; import { useDepartmentQuery } from "../composables/department/useDepartmentQuery";
import { useDepartmentUpsert } from "../composables/department/useDepartmentUpsert"; import { useDepartmentUpsert } from "../composables/department/useDepartmentUpsert";
import useAlertStore from "../composables/store/useAlertStore"; import useAlertStore from "../composables/store/useAlertStore";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
const name = ref<string>(""); // 定义筛选配置
const filterConfig = [
{
type: "input",
name: "departmentName",
placeholder: "部门名称",
},
] as FilterItem[];
// 筛选值
const filterValues = reactive<{
departmentName: string;
}>({
departmentName: "",
});
// 更新筛选值
const updateFilterValues = (
values: Record<string, string | number | boolean | Date[] | undefined>,
) => {
if (values.departmentName !== undefined) {
filterValues.departmentName = values.departmentName as string;
}
};
const selectedDepartment = ref<components["schemas"]["Department"]>(); const selectedDepartment = ref<components["schemas"]["Department"]>();
const departmentUpsertModal = ref<ModalInterface>(); const departmentUpsertModal = ref<ModalInterface>();
const departmentDeleteModal = ref<ModalInterface>(); const departmentDeleteModal = ref<ModalInterface>();
@@ -170,7 +182,7 @@ const columns = [
onMounted(async () => { onMounted(async () => {
await fetchDepartmentWith({ await fetchDepartmentWith({
name: name.value, name: filterValues.departmentName,
}); });
initFlowbite(); initFlowbite();
const $upsertModalElement: HTMLElement | null = document.querySelector( const $upsertModalElement: HTMLElement | null = document.querySelector(
@@ -202,7 +214,7 @@ const handleUpsertDepartmentSubmit = async (
level: "success", level: "success",
}); });
await fetchDepartmentWith({ await fetchDepartmentWith({
name: name.value, name: filterValues.departmentName,
}); });
}; };
@@ -210,14 +222,14 @@ const handleUpsertDepartmentClick = async (
department?: components["schemas"]["Department"], department?: components["schemas"]["Department"],
) => { ) => {
selectedDepartment.value = department; selectedDepartment.value = department;
await fetchAvailableDepartments(selectedDepartment.value?.id); await fetchAvailableDepartments(department?.id);
await nextTick(() => { await nextTick(() => {
departmentUpsertModal.value?.show(); departmentUpsertModal.value?.show();
}); });
}; };
const handleDeleteDepartmentSubmit = async () => { const handleDeleteDepartmentSubmit = async () => {
if (!selectedDepartment?.value?.id) return; if (!selectedDepartment.value?.id) return;
await deleteDepartment(selectedDepartment.value.id); await deleteDepartment(selectedDepartment.value.id);
departmentDeleteModal.value?.hide(); departmentDeleteModal.value?.hide();
alertStore.showAlert({ alertStore.showAlert({
@@ -225,7 +237,7 @@ const handleDeleteDepartmentSubmit = async () => {
level: "success", level: "success",
}); });
await fetchDepartmentWith({ await fetchDepartmentWith({
name: name.value, name: filterValues.departmentName,
}); });
}; };
@@ -240,17 +252,17 @@ const handleDeleteDepartmentClick = async (
const handleSearch = async () => { const handleSearch = async () => {
await fetchDepartmentWith({ await fetchDepartmentWith({
name: name.value, name: filterValues.departmentName,
}); });
}; };
const handlePageChange = async (page: number, size: number) => { const handlePageChange = async (page: number, pageSize: number) => {
await fetchDepartmentWith( await fetchDepartmentWith(
{ {
name: name.value, name: filterValues.departmentName,
}, },
page, page,
size, pageSize,
); );
}; };
</script> </script>

View File

@@ -4,26 +4,10 @@
<Breadcrumbs :names="['大模型管理']" /> <Breadcrumbs :names="['大模型管理']" />
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">大模型管理</h1> <h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">大模型管理</h1>
</div> </div>
<div class="mb-4">
<form class="w-full sm:max-w-xs"> <TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label> @update:values="updateFilterValues">
<div class="relative"> </TableFilterForm>
<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="name"
class="block w-full p-3 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 />
<button type="submit"
class="text-white absolute end-1.5 bottom-1.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-1.5 sm:px-4 sm:py-2"
@click.prevent="handleSearch">搜索</button>
</div>
</form>
</div>
<!-- 移动端卡片布局 --> <!-- 移动端卡片布局 -->
<div class="md:hidden"> <div class="md:hidden">
@@ -134,18 +118,44 @@
import Breadcrumbs from "@/components/Breadcrumbs.vue"; import Breadcrumbs from "@/components/Breadcrumbs.vue";
import LlmUpdateModal from "@/components/LlmUpdateModal.vue"; import LlmUpdateModal from "@/components/LlmUpdateModal.vue";
import MobileCardList from "@/components/MobileCardList.vue"; import MobileCardList from "@/components/MobileCardList.vue";
import TableFilterForm from "@/components/TableFilterForm.vue";
import type { FilterItem } from "@/components/TableFilterForm.vue";
import TableFormLayout from "@/components/TableFormLayout.vue"; import TableFormLayout from "@/components/TableFormLayout.vue";
import TablePagination from "@/components/TablePagination.vue"; import TablePagination from "@/components/TablePagination.vue";
import { useLlmQuery } from "@/composables/ai/useLlmQuery"; import { useLlmQuery } from "@/composables/ai/useLlmQuery";
import { useLlmUpdate } from "@/composables/ai/useLlmUpdate"; import { useLlmUpdate } from "@/composables/ai/useLlmUpdate";
import useAlertStore from "@/composables/store/useAlertStore"; import useAlertStore from "@/composables/store/useAlertStore";
import { Modal, type ModalInterface, initFlowbite } from "flowbite"; import { Modal, type ModalInterface, initFlowbite } from "flowbite";
import { nextTick, onMounted, ref } from "vue"; import { nextTick, onMounted, reactive, ref } from "vue";
import type { components } from "../api/types/schema"; import type { components } from "../api/types/schema";
// 定义筛选配置
const filterConfig: FilterItem[] = [
{
type: "input",
name: "modelName",
placeholder: "模型名称",
},
];
// 筛选值
const filterValues = reactive<{
modelName: string;
}>({
modelName: "",
});
// 更新筛选值
const updateFilterValues = (
values: Record<string, string | number | boolean | Date[] | undefined>,
) => {
if (values.modelName !== undefined) {
filterValues.modelName = values.modelName as string;
}
};
const llmUpdateModal = ref<ModalInterface>(); const llmUpdateModal = ref<ModalInterface>();
const selectedLlm = ref<components["schemas"]["LlmVm"]>(); const selectedLlm = ref<components["schemas"]["LlmVm"]>();
const name = ref<string>("");
const { llms, fetchLlmConfigs, total } = useLlmQuery(); const { llms, fetchLlmConfigs, total } = useLlmQuery();
const { updateLlmConfig } = useLlmUpdate(); const { updateLlmConfig } = useLlmUpdate();
@@ -171,15 +181,15 @@ const handleUpdateModalSubmit = async (llm: components["schemas"]["LlmVm"]) => {
level: "success", level: "success",
content: "操作成功", content: "操作成功",
}); });
await fetchLlmConfigs(); await fetchLlmConfigs(1, 10, filterValues.modelName);
}; };
const handleSearch = async () => { const handleSearch = async () => {
await fetchLlmConfigs(); await fetchLlmConfigs(1, 10, filterValues.modelName);
}; };
const handlePageChange = async (page: number, pageSize: number) => { const handlePageChange = async (page: number, pageSize: number) => {
await fetchLlmConfigs(page, pageSize); await fetchLlmConfigs(page, pageSize, filterValues.modelName);
}; };
const handleLlmUpdateClick = async (llm: components["schemas"]["LlmVm"]) => { const handleLlmUpdateClick = async (llm: components["schemas"]["LlmVm"]) => {
@@ -190,7 +200,7 @@ const handleLlmUpdateClick = async (llm: components["schemas"]["LlmVm"]) => {
}; };
onMounted(async () => { onMounted(async () => {
await fetchLlmConfigs(); await fetchLlmConfigs(1, 10, filterValues.modelName);
initFlowbite(); initFlowbite();
const $llmUpdateModalElement: HTMLElement | null = const $llmUpdateModalElement: HTMLElement | null =
document.querySelector("#llm-update-modal"); document.querySelector("#llm-update-modal");

View File

@@ -4,36 +4,21 @@
<Breadcrumbs :names="['权限管理']" /> <Breadcrumbs :names="['权限管理']" />
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">权限管理</h1> <h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">权限管理</h1>
</div> </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:max-w-xs"> <TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label> @update:values="updateFilterValues">
<div class="relative"> <template #actions>
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"> <Button :handleClick="() => handleUpsertPermissionClick(undefined)" :isLoading="false" :abortable="false"
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" submitContent="新增权限" class="w-full sm:w-auto">
viewBox="0 0 20 20"> <template #icon>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <svg class="w-4 h-4 me-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" /> 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> </svg>
</div> </template>
<input type="search" id="default-search" v-model="permissionName" </Button>
class="block w-full p-3 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500" </template>
placeholder="权限名" required /> </TableFilterForm>
<button type="submit"
class="text-white absolute end-1.5 bottom-1.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-1.5 sm:px-4 sm:py-2"
@click.prevent="handleSearch">搜索</button>
</div>
</form>
<!-- Create Modal toggle -->
<Button :handleClick="() => handleUpsertPermissionClick(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>
<!-- 移动端卡片布局 --> <!-- 移动端卡片布局 -->
<div class="md:hidden"> <div class="md:hidden">
@@ -125,26 +110,51 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Button from "@/components/Button.vue";
import PermissionUpsertModal from "@/components/PermissionUpsertModal.vue";
import PermissionDeleteModal from "@/components/PopupModal.vue";
import usePermissionDelete from "@/composables/permission/usePermissionDelete";
import type { components } from "@/api/types/schema"; import type { components } from "@/api/types/schema";
import Breadcrumbs from "@/components/Breadcrumbs.vue"; import Breadcrumbs from "@/components/Breadcrumbs.vue";
import Button from "@/components/Button.vue";
import MobileCardList from "@/components/MobileCardList.vue"; import MobileCardList from "@/components/MobileCardList.vue";
import PermissionUpsertModal from "@/components/PermissionUpsertModal.vue";
import PermissionDeleteModal from "@/components/PopupModal.vue";
import TableButton from "@/components/TableButton.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 TableFormLayout from "@/components/TableFormLayout.vue";
import TablePagination from "@/components/TablePagination.vue"; import TablePagination from "@/components/TablePagination.vue";
import usePermissionDelete from "@/composables/permission/usePermissionDelete";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
import { Modal, type ModalInterface, initFlowbite } from "flowbite"; import { Modal, type ModalInterface, initFlowbite } from "flowbite";
import { nextTick, onMounted, ref } from "vue"; import { nextTick, onMounted, reactive, ref } from "vue";
import usePermissionsQuery from "../composables/permission/usePermissionQuery"; import usePermissionsQuery from "../composables/permission/usePermissionQuery";
import usePermissionUpsert from "../composables/permission/usePermissionUpsert"; import usePermissionUpsert from "../composables/permission/usePermissionUpsert";
import useAlertStore from "../composables/store/useAlertStore"; import useAlertStore from "../composables/store/useAlertStore";
import type { PermissionUpsertModel } from "../types/permission"; import type { PermissionUpsertModel } from "../types/permission";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
const permissionName = ref<string>(""); // 定义筛选配置
const filterConfig = [
{
type: "input",
name: "permissionName",
placeholder: "权限名",
},
] as FilterItem[];
// 筛选值
const filterValues = reactive<{
permissionName: string;
}>({
permissionName: "",
});
// 更新筛选值
const updateFilterValues = (
values: Record<string, string | number | boolean | Date[] | undefined>,
) => {
if (values.permissionName !== undefined) {
filterValues.permissionName = values.permissionName as string;
}
};
const selectedPermission = ref<components["schemas"]["PermissionRespDto"]>(); const selectedPermission = ref<components["schemas"]["PermissionRespDto"]>();
const permissionUpsertModal = ref<ModalInterface>(); const permissionUpsertModal = ref<ModalInterface>();
const permissionDeleteModal = ref<ModalInterface>(); const permissionDeleteModal = ref<ModalInterface>();
@@ -164,7 +174,7 @@ const columns = [
onMounted(async () => { onMounted(async () => {
await fetchPermissionsWith({ await fetchPermissionsWith({
name: permissionName.value, name: filterValues.permissionName,
}); });
initFlowbite(); initFlowbite();
const $upsertModalElement: HTMLElement | null = document.querySelector( const $upsertModalElement: HTMLElement | null = document.querySelector(
@@ -189,7 +199,7 @@ onMounted(async () => {
const handleUpsertModalSubmit = async (data: PermissionUpsertModel) => { const handleUpsertModalSubmit = async (data: PermissionUpsertModel) => {
await permissionUpsert.upsertPermission(data); await permissionUpsert.upsertPermission(data);
await fetchPermissionsWith({ await fetchPermissionsWith({
name: permissionName.value, name: filterValues.permissionName,
}); });
permissionUpsertModal.value?.hide(); permissionUpsertModal.value?.hide();
alertStore.showAlert({ alertStore.showAlert({
@@ -208,7 +218,7 @@ const handleUpsertPermissionClick = async (
}; };
const handleDeleteModalSubmit = async () => { const handleDeleteModalSubmit = async () => {
if (!selectedPermission?.value?.id) return; if (!selectedPermission.value?.id) return;
await deletePermission(selectedPermission.value.id); await deletePermission(selectedPermission.value.id);
permissionDeleteModal.value?.hide(); permissionDeleteModal.value?.hide();
alertStore.showAlert({ alertStore.showAlert({
@@ -216,7 +226,7 @@ const handleDeleteModalSubmit = async () => {
level: "success", level: "success",
}); });
await fetchPermissionsWith({ await fetchPermissionsWith({
name: permissionName.value, name: filterValues.permissionName,
}); });
}; };
@@ -231,14 +241,14 @@ const handleDeletePermissionClick = async (
const handleSearch = async () => { const handleSearch = async () => {
await fetchPermissionsWith({ await fetchPermissionsWith({
name: permissionName.value, name: filterValues.permissionName,
}); });
}; };
const handlePageChange = async (page: number, pageSize: number) => { const handlePageChange = async (page: number, pageSize: number) => {
await fetchPermissionsWith( await fetchPermissionsWith(
{ {
name: permissionName.value, name: filterValues.permissionName,
}, },
page, page,
pageSize, pageSize,

View File

@@ -4,36 +4,21 @@
<Breadcrumbs :names="['岗位管理']" /> <Breadcrumbs :names="['岗位管理']" />
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">岗位管理</h1> <h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">岗位管理</h1>
</div> </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:max-w-xs"> <TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label> @update:values="updateFilterValues">
<div class="relative"> <template #actions>
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"> <Button :handleClick="() => handleUpsertPositionClick()" :isLoading="false" :abortable="false"
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" submitContent="新增岗位" class="w-full sm:w-auto">
viewBox="0 0 20 20"> <template #icon>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <svg class="w-4 h-4 me-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" /> 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> </svg>
</div> </template>
<input type="search" id="default-search" v-model="name" </Button>
class="block w-full p-3 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500" </template>
placeholder="岗位名称" required /> </TableFilterForm>
<button type="submit"
class="text-white absolute end-1.5 bottom-1.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-1.5 sm:px-4 sm:py-2"
@click.prevent="handleSearch">搜索</button>
</div>
</form>
<!-- Create Modal toggle -->
<Button :handleClick="() => handleUpsertPositionClick()" :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>
<!-- 移动端卡片布局 --> <!-- 移动端卡片布局 -->
<div class="md:hidden"> <div class="md:hidden">
@@ -121,18 +106,44 @@ import Button from "@/components/Button.vue";
import MobileCardList from "@/components/MobileCardList.vue"; import MobileCardList from "@/components/MobileCardList.vue";
import PositionDeleteModal from "@/components/PopupModal.vue"; import PositionDeleteModal from "@/components/PopupModal.vue";
import PositionUpsertModal from "@/components/PositionUpsertModal.vue"; import PositionUpsertModal from "@/components/PositionUpsertModal.vue";
import TableFilterForm from "@/components/TableFilterForm.vue";
import type { FilterItem } from "@/components/TableFilterForm.vue";
import TableFormLayout from "@/components/TableFormLayout.vue"; import TableFormLayout from "@/components/TableFormLayout.vue";
import TablePagination from "@/components/TablePagination.vue"; import TablePagination from "@/components/TablePagination.vue";
import usePositionDelete from "@/composables/position/usePositionDelete"; import usePositionDelete from "@/composables/position/usePositionDelete";
import { usePositionQuery } from "@/composables/position/usePositionQuery"; import { usePositionQuery } from "@/composables/position/usePositionQuery";
import { usePositionUpsert } from "@/composables/position/usePositionUpsert"; import { usePositionUpsert } from "@/composables/position/usePositionUpsert";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
import { Modal, type ModalInterface, initFlowbite } from "flowbite"; import { Modal, type ModalInterface, initFlowbite } from "flowbite";
import { nextTick, onMounted, ref } from "vue"; import { nextTick, onMounted, reactive, ref } from "vue";
import type { components } from "../api/types/schema"; import type { components } from "../api/types/schema";
import useAlertStore from "../composables/store/useAlertStore"; import useAlertStore from "../composables/store/useAlertStore";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
const name = ref<string>(""); // 定义筛选配置
const filterConfig = [
{
type: "input",
name: "positionName",
placeholder: "岗位名称",
},
] as FilterItem[];
// 筛选值
const filterValues = reactive<{
positionName: string;
}>({
positionName: "",
});
// 更新筛选值
const updateFilterValues = (
values: Record<string, string | number | boolean | Date[] | undefined>,
) => {
if (values.positionName !== undefined) {
filterValues.positionName = values.positionName as string;
}
};
const selectedPosition = ref<components["schemas"]["Position"]>(); const selectedPosition = ref<components["schemas"]["Position"]>();
const positionUpsertModal = ref<ModalInterface>(); const positionUpsertModal = ref<ModalInterface>();
const positionDeleteModal = ref<ModalInterface>(); const positionDeleteModal = ref<ModalInterface>();
@@ -154,7 +165,7 @@ const columns = [
onMounted(async () => { onMounted(async () => {
await fetchAllPositions(); await fetchAllPositions();
await fetchPositionWith({ await fetchPositionWith({
name: name.value, name: filterValues.positionName,
}); });
initFlowbite(); initFlowbite();
const $upsertModalElement: HTMLElement | null = document.querySelector( const $upsertModalElement: HTMLElement | null = document.querySelector(
@@ -187,7 +198,7 @@ const handleUpsertPositionSubmit = async (
}); });
fetchAllPositions(); fetchAllPositions();
await fetchPositionWith({ await fetchPositionWith({
name: name.value, name: filterValues.positionName,
}); });
}; };
@@ -201,16 +212,15 @@ const handleUpsertPositionClick = async (
}; };
const handleDeletePositionSubmit = async () => { const handleDeletePositionSubmit = async () => {
if (!selectedPosition?.value?.id) return; if (!selectedPosition.value?.id) return;
await deletePosition(selectedPosition.value.id); await deletePosition(selectedPosition.value.id);
positionDeleteModal.value?.hide(); positionDeleteModal.value?.hide();
alertStore.showAlert({ alertStore.showAlert({
content: "删除成功", content: "删除成功",
level: "success", level: "success",
}); });
fetchAllPositions();
await fetchPositionWith({ await fetchPositionWith({
name: name.value, name: filterValues.positionName,
}); });
}; };
@@ -225,17 +235,17 @@ const handleDeletePositionClick = async (
const handleSearch = async () => { const handleSearch = async () => {
await fetchPositionWith({ await fetchPositionWith({
name: name.value, name: filterValues.positionName,
}); });
}; };
const handlePageChange = async (page: number, size: number) => { const handlePageChange = async (page: number, pageSize: number) => {
await fetchPositionWith( await fetchPositionWith(
{ {
name: name.value, name: filterValues.positionName,
}, },
page, page,
size, pageSize,
); );
}; };
</script> </script>

View File

@@ -4,36 +4,21 @@
<Breadcrumbs :names="['角色管理']" /> <Breadcrumbs :names="['角色管理']" />
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">角色管理</h1> <h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">角色管理</h1>
</div> </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:max-w-xs"> <TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label> @update:values="updateFilterValues">
<div class="relative"> <template #actions>
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"> <Button :handleClick="() => handleUpsertRoleClick(undefined)" :isLoading="false" :abortable="false"
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" submitContent="新增角色" class="w-full sm:w-auto">
viewBox="0 0 20 20"> <template #icon>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <svg class="w-4 h-4 me-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" /> 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> </svg>
</div> </template>
<input type="search" id="default-search" v-model="roleName" </Button>
class="block w-full p-3 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500" </template>
placeholder="角色名" required /> </TableFilterForm>
<button type="submit"
class="text-white absolute end-1.5 bottom-1.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-1.5 sm:px-4 sm:py-2"
@click.prevent="handleSearch">搜索</button>
</div>
</form>
<!-- Create Modal toggle -->
<Button :handleClick="() => handleUpsertRoleClick(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>
<!-- 移动端卡片布局 --> <!-- 移动端卡片布局 -->
<div class="md:hidden"> <div class="md:hidden">
@@ -145,21 +130,47 @@ import Button from "@/components/Button.vue";
import MobileCardList from "@/components/MobileCardList.vue"; import MobileCardList from "@/components/MobileCardList.vue";
import RoleDeleteModal from "@/components/PopupModal.vue"; import RoleDeleteModal from "@/components/PopupModal.vue";
import RoleUpsertModal from "@/components/RoleUpsertModal.vue"; import RoleUpsertModal from "@/components/RoleUpsertModal.vue";
import TableFilterForm from "@/components/TableFilterForm.vue";
import type { FilterItem } from "@/components/TableFilterForm.vue";
import TableFormLayout from "@/components/TableFormLayout.vue"; import TableFormLayout from "@/components/TableFormLayout.vue";
import TablePagination from "@/components/TablePagination.vue"; import TablePagination from "@/components/TablePagination.vue";
import useRoleDelete from "@/composables/role/useRoleDelete"; import useRoleDelete from "@/composables/role/useRoleDelete";
import { useRolesQuery } from "@/composables/role/useRolesQuery"; import { useRolesQuery } from "@/composables/role/useRolesQuery";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
import { RouteName } from "@/router/constants"; import { RouteName } from "@/router/constants";
import type { RoleUpsertModel } from "@/types/role"; import type { RoleUpsertModel } from "@/types/role";
import { Modal, type ModalInterface, initFlowbite } from "flowbite"; 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 { useRouter } from "vue-router";
import type { components } from "../api/types/schema"; import type { components } from "../api/types/schema";
import { useRoleUpsert } from "../composables/role/useRoleUpsert"; import { useRoleUpsert } from "../composables/role/useRoleUpsert";
import useAlertStore from "../composables/store/useAlertStore"; import useAlertStore from "../composables/store/useAlertStore";
import { useActionExcStore } from "@/composables/store/useActionExcStore";
const roleName = ref<string>(""); // 定义筛选配置
const filterConfig = [
{
type: "input",
name: "roleName",
placeholder: "角色名",
},
] as FilterItem[];
// 筛选值
const filterValues = reactive<{
roleName: string;
}>({
roleName: "",
});
// 更新筛选值
const updateFilterValues = (
values: Record<string, string | number | boolean | Date[] | undefined>,
) => {
if (values.roleName !== undefined) {
filterValues.roleName = values.roleName as string;
}
};
const selectedRole = ref<components["schemas"]["RoleDto"]>(); const selectedRole = ref<components["schemas"]["RoleDto"]>();
const roleUpsertModal = ref<ModalInterface>(); const roleUpsertModal = ref<ModalInterface>();
const roleDeleteModal = ref<ModalInterface>(); const roleDeleteModal = ref<ModalInterface>();
@@ -181,7 +192,7 @@ const columns = [
onMounted(async () => { onMounted(async () => {
await fetchRolesWith({ await fetchRolesWith({
name: roleName.value, name: filterValues.roleName,
}); });
initFlowbite(); initFlowbite();
const $upsertModalElement: HTMLElement | null = const $upsertModalElement: HTMLElement | null =
@@ -203,14 +214,14 @@ onMounted(async () => {
const handleUpsertModalSubmit = async (data: RoleUpsertModel) => { const handleUpsertModalSubmit = async (data: RoleUpsertModel) => {
await upsertRole.upsertRole(data); await upsertRole.upsertRole(data);
await fetchRolesWith({
name: roleName.value,
});
roleUpsertModal.value?.hide(); roleUpsertModal.value?.hide();
alertStore.showAlert({ alertStore.showAlert({
content: "操作成功", content: "操作成功",
level: "success", level: "success",
}); });
await fetchRolesWith({
name: filterValues.roleName,
});
}; };
const handleUpsertRoleClick = async ( const handleUpsertRoleClick = async (
@@ -222,17 +233,28 @@ const handleUpsertRoleClick = async (
}); });
}; };
const handleDeletedModalSubmit = async () => { const handleBindPermissionClick = async (
if (!selectedRole?.value?.id) return; role: components["schemas"]["RoleDto"],
await deleteRole(selectedRole.value.id); ) => {
await fetchRolesWith({ router.push({
name: roleName.value, name: RouteName.BINDPERMISSIONVIEW,
params: {
roleId: role.id,
},
}); });
};
const handleDeletedModalSubmit = async () => {
if (!selectedRole.value?.id) return;
await deleteRole(selectedRole.value.id);
roleDeleteModal.value?.hide(); roleDeleteModal.value?.hide();
alertStore.showAlert({ alertStore.showAlert({
content: "删除成功", content: "删除成功",
level: "success", level: "success",
}); });
await fetchRolesWith({
name: filterValues.roleName,
});
}; };
const handleDeleteRoleClick = async ( const handleDeleteRoleClick = async (
@@ -244,24 +266,16 @@ const handleDeleteRoleClick = async (
}); });
}; };
const handleBindPermissionClick = async (
role: components["schemas"]["RoleDto"],
) => {
router.push({
name: RouteName.BINDPERMISSIONVIEW,
params: { roleId: role.id },
});
};
const handleSearch = async () => { const handleSearch = async () => {
await fetchRolesWith({ await fetchRolesWith({
name: roleName.value, name: filterValues.roleName,
}); });
}; };
const handlePageChange = async (page: number, pageSize: number) => { const handlePageChange = async (page: number, pageSize: number) => {
await fetchRolesWith( await fetchRolesWith(
{ {
name: roleName.value, name: filterValues.roleName,
}, },
page, page,
pageSize, pageSize,

View File

@@ -4,26 +4,10 @@
<Breadcrumbs :names="['任务管理']" /> <Breadcrumbs :names="['任务管理']" />
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">任务管理</h1> <h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">任务管理</h1>
</div> </div>
<div class="mb-4">
<form class="w-full sm:max-w-xs"> <TableFilterForm :filters="filterConfig" :initialValues="filterValues" @search="handleSearch"
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label> @update:values="updateFilterValues">
<div class="relative"> </TableFilterForm>
<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="jobName"
class="block w-full p-3 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 />
<button type="submit"
class="text-white absolute end-1.5 bottom-1.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-1.5 sm:px-4 sm:py-2"
@click.prevent="handleSearch">搜索</button>
</div>
</form>
</div>
<!-- 移动端卡片布局 --> <!-- 移动端卡片布局 -->
<div class="md:hidden"> <div class="md:hidden">
@@ -167,6 +151,8 @@ import Breadcrumbs from "@/components/Breadcrumbs.vue";
import MobileCardList from "@/components/MobileCardList.vue"; import MobileCardList from "@/components/MobileCardList.vue";
import PopupModal from "@/components/PopupModal.vue"; import PopupModal from "@/components/PopupModal.vue";
import SchedulerUpdateModal from "@/components/SchedulerUpdateModal.vue"; import SchedulerUpdateModal from "@/components/SchedulerUpdateModal.vue";
import TableFilterForm from "@/components/TableFilterForm.vue";
import type { FilterItem } from "@/components/TableFilterForm.vue";
import TableFormLayout from "@/components/TableFormLayout.vue"; import TableFormLayout from "@/components/TableFormLayout.vue";
import TablePagination from "@/components/TablePagination.vue"; import TablePagination from "@/components/TablePagination.vue";
import { useJobControl } from "@/composables/job/useJobControl"; import { useJobControl } from "@/composables/job/useJobControl";
@@ -175,10 +161,34 @@ import { useJobUpdate } from "@/composables/job/useJobUpdate";
import useAlertStore from "@/composables/store/useAlertStore"; import useAlertStore from "@/composables/store/useAlertStore";
import { dayjs } from "@/utils/dateUtil"; import { dayjs } from "@/utils/dateUtil";
import { Modal, type ModalInterface, initFlowbite } from "flowbite"; import { Modal, type ModalInterface, initFlowbite } from "flowbite";
import { nextTick, onMounted, ref } from "vue"; import { nextTick, onMounted, reactive, ref } from "vue";
import type { components } from "../api/types/schema"; import type { components } from "../api/types/schema";
const jobName = ref<string>(""); // 定义筛选配置
const filterConfig: FilterItem[] = [
{
type: "input",
name: "jobName",
placeholder: "任务名称",
},
];
// 筛选值
const filterValues = reactive<{
jobName: string;
}>({
jobName: "",
});
// 更新筛选值
const updateFilterValues = (
values: Record<string, string | number | boolean | Date[] | undefined>,
) => {
if (values.jobName !== undefined) {
filterValues.jobName = values.jobName as string;
}
};
const jobResumeModal = ref<ModalInterface>(); const jobResumeModal = ref<ModalInterface>();
const jobPauseModal = ref<ModalInterface>(); const jobPauseModal = ref<ModalInterface>();
const jobUpdateModal = ref<ModalInterface>(); const jobUpdateModal = ref<ModalInterface>();
@@ -249,7 +259,7 @@ const handleResumeModalSubmit = async () => {
content: "操作成功", content: "操作成功",
}); });
await fetchJobsWith({ await fetchJobsWith({
name: jobName.value, name: filterValues.jobName,
}); });
}; };
@@ -265,7 +275,7 @@ const handleUpdateModalSubmit = async (cronExpression: string) => {
content: "操作成功", content: "操作成功",
}); });
await fetchJobsWith({ await fetchJobsWith({
name: jobName.value, name: filterValues.jobName,
}); });
}; };
@@ -280,20 +290,20 @@ const handlePauseModalSubmit = async () => {
content: "操作成功", content: "操作成功",
}); });
await fetchJobsWith({ await fetchJobsWith({
name: jobName.value, name: filterValues.jobName,
}); });
}; };
const handleSearch = async () => { const handleSearch = async () => {
await fetchJobsWith({ await fetchJobsWith({
name: jobName.value, name: filterValues.jobName,
}); });
}; };
const handlePageChange = async (page: number, pageSize: number) => { const handlePageChange = async (page: number, pageSize: number) => {
await fetchJobsWith( await fetchJobsWith(
{ {
name: jobName.value, name: filterValues.jobName,
}, },
page, page,
pageSize, pageSize,
@@ -302,7 +312,7 @@ const handlePageChange = async (page: number, pageSize: number) => {
onMounted(async () => { onMounted(async () => {
await fetchJobsWith({ await fetchJobsWith({
name: jobName.value, name: filterValues.jobName,
}); });
initFlowbite(); initFlowbite();
const $jobResumeModalElement: HTMLElement | null = const $jobResumeModalElement: HTMLElement | null =