mirror of https://github.com/halo-dev/halo-admin
feat: user management supports conditional filtering and sorting (#862)
#### What type of PR is this? /kind feature #### What this PR does / why we need it: 用户管理列表支持条件筛选和排序,适配:https://github.com/halo-dev/halo/pull/3320 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/3290 #### Screenshots: <img width="922" alt="image" src="https://user-images.githubusercontent.com/21301288/219578426-de396dfb-7ece-496e-b740-d7a36321eafb.png"> #### Special notes for your reviewer: 测试方式: 1. Halo 需要切换到 https://github.com/halo-dev/halo/pull/3320 分支。 2. Console 需要 `pnpm build:packages` 3. 测试用户管理的关键词、条件、排序筛选是否工作正常。 #### Does this PR introduce a user-facing change? ```release-note Console 端的用户列表支持条件筛选和排序。 ```pull/867/head
parent
87d7592b29
commit
194ff7f4ad
|
@ -1,6 +1,5 @@
|
||||||
.gitignore
|
.gitignore
|
||||||
.npmignore
|
.npmignore
|
||||||
.openapi-generator-ignore
|
|
||||||
api.ts
|
api.ts
|
||||||
api/api-console-halo-run-v1alpha1-attachment-api.ts
|
api/api-console-halo-run-v1alpha1-attachment-api.ts
|
||||||
api/api-console-halo-run-v1alpha1-comment-api.ts
|
api/api-console-halo-run-v1alpha1-comment-api.ts
|
||||||
|
|
|
@ -38,6 +38,8 @@ import { GrantRequest } from '../models'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { User } from '../models'
|
import { User } from '../models'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
import { UserList } from '../models'
|
||||||
|
// @ts-ignore
|
||||||
import { UserPermission } from '../models'
|
import { UserPermission } from '../models'
|
||||||
/**
|
/**
|
||||||
* ApiConsoleHaloRunV1alpha1UserApi - axios parameter creator
|
* ApiConsoleHaloRunV1alpha1UserApi - axios parameter creator
|
||||||
|
@ -223,6 +225,85 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function (confi
|
||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* List users
|
||||||
|
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp
|
||||||
|
* @param {string} [keyword]
|
||||||
|
* @param {string} [role]
|
||||||
|
* @param {number} [size] Size of one page. Zero indicates no limit.
|
||||||
|
* @param {number} [page] The page number. Zero indicates no page.
|
||||||
|
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
||||||
|
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
listUsers: async (
|
||||||
|
sort?: Array<string>,
|
||||||
|
keyword?: string,
|
||||||
|
role?: string,
|
||||||
|
size?: number,
|
||||||
|
page?: number,
|
||||||
|
labelSelector?: Array<string>,
|
||||||
|
fieldSelector?: Array<string>,
|
||||||
|
options: AxiosRequestConfig = {},
|
||||||
|
): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/apis/api.console.halo.run/v1alpha1/users`
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
|
||||||
|
let baseOptions
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options }
|
||||||
|
const localVarHeaderParameter = {} as any
|
||||||
|
const localVarQueryParameter = {} as any
|
||||||
|
|
||||||
|
// authentication BasicAuth required
|
||||||
|
// http basic authentication required
|
||||||
|
setBasicAuthToObject(localVarRequestOptions, configuration)
|
||||||
|
|
||||||
|
// authentication BearerAuth required
|
||||||
|
// http bearer authentication required
|
||||||
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
|
if (sort) {
|
||||||
|
localVarQueryParameter['sort'] = Array.from(sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyword !== undefined) {
|
||||||
|
localVarQueryParameter['keyword'] = keyword
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role !== undefined) {
|
||||||
|
localVarQueryParameter['role'] = role
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size !== undefined) {
|
||||||
|
localVarQueryParameter['size'] = size
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page !== undefined) {
|
||||||
|
localVarQueryParameter['page'] = page
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labelSelector) {
|
||||||
|
localVarQueryParameter['labelSelector'] = labelSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldSelector) {
|
||||||
|
localVarQueryParameter['fieldSelector'] = fieldSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter)
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}
|
||||||
|
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers }
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Update current user profile, but password.
|
* Update current user profile, but password.
|
||||||
* @param {User} user
|
* @param {User} user
|
||||||
|
@ -328,6 +409,40 @@ export const ApiConsoleHaloRunV1alpha1UserApiFp = function (configuration?: Conf
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.grantPermission(name, grantRequest, options)
|
const localVarAxiosArgs = await localVarAxiosParamCreator.grantPermission(name, grantRequest, options)
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* List users
|
||||||
|
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp
|
||||||
|
* @param {string} [keyword]
|
||||||
|
* @param {string} [role]
|
||||||
|
* @param {number} [size] Size of one page. Zero indicates no limit.
|
||||||
|
* @param {number} [page] The page number. Zero indicates no page.
|
||||||
|
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
||||||
|
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async listUsers(
|
||||||
|
sort?: Array<string>,
|
||||||
|
keyword?: string,
|
||||||
|
role?: string,
|
||||||
|
size?: number,
|
||||||
|
page?: number,
|
||||||
|
labelSelector?: Array<string>,
|
||||||
|
fieldSelector?: Array<string>,
|
||||||
|
options?: AxiosRequestConfig,
|
||||||
|
): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserList>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.listUsers(
|
||||||
|
sort,
|
||||||
|
keyword,
|
||||||
|
role,
|
||||||
|
size,
|
||||||
|
page,
|
||||||
|
labelSelector,
|
||||||
|
fieldSelector,
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Update current user profile, but password.
|
* Update current user profile, but password.
|
||||||
* @param {User} user
|
* @param {User} user
|
||||||
|
@ -403,6 +518,29 @@ export const ApiConsoleHaloRunV1alpha1UserApiFactory = function (
|
||||||
.grantPermission(requestParameters.name, requestParameters.grantRequest, options)
|
.grantPermission(requestParameters.name, requestParameters.grantRequest, options)
|
||||||
.then((request) => request(axios, basePath))
|
.then((request) => request(axios, basePath))
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* List users
|
||||||
|
* @param {ApiConsoleHaloRunV1alpha1UserApiListUsersRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
listUsers(
|
||||||
|
requestParameters: ApiConsoleHaloRunV1alpha1UserApiListUsersRequest = {},
|
||||||
|
options?: AxiosRequestConfig,
|
||||||
|
): AxiosPromise<UserList> {
|
||||||
|
return localVarFp
|
||||||
|
.listUsers(
|
||||||
|
requestParameters.sort,
|
||||||
|
requestParameters.keyword,
|
||||||
|
requestParameters.role,
|
||||||
|
requestParameters.size,
|
||||||
|
requestParameters.page,
|
||||||
|
requestParameters.labelSelector,
|
||||||
|
requestParameters.fieldSelector,
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
.then((request) => request(axios, basePath))
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Update current user profile, but password.
|
* Update current user profile, but password.
|
||||||
* @param {ApiConsoleHaloRunV1alpha1UserApiUpdateCurrentUserRequest} requestParameters Request parameters.
|
* @param {ApiConsoleHaloRunV1alpha1UserApiUpdateCurrentUserRequest} requestParameters Request parameters.
|
||||||
|
@ -474,6 +612,62 @@ export interface ApiConsoleHaloRunV1alpha1UserApiGrantPermissionRequest {
|
||||||
readonly grantRequest: GrantRequest
|
readonly grantRequest: GrantRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request parameters for listUsers operation in ApiConsoleHaloRunV1alpha1UserApi.
|
||||||
|
* @export
|
||||||
|
* @interface ApiConsoleHaloRunV1alpha1UserApiListUsersRequest
|
||||||
|
*/
|
||||||
|
export interface ApiConsoleHaloRunV1alpha1UserApiListUsersRequest {
|
||||||
|
/**
|
||||||
|
* Sort property and direction of the list result. Supported fields: creationTimestamp
|
||||||
|
* @type {Array<string>}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
|
||||||
|
*/
|
||||||
|
readonly sort?: Array<string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
|
||||||
|
*/
|
||||||
|
readonly keyword?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
|
||||||
|
*/
|
||||||
|
readonly role?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of one page. Zero indicates no limit.
|
||||||
|
* @type {number}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
|
||||||
|
*/
|
||||||
|
readonly size?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The page number. Zero indicates no page.
|
||||||
|
* @type {number}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
|
||||||
|
*/
|
||||||
|
readonly page?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label selector for filtering.
|
||||||
|
* @type {Array<string>}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
|
||||||
|
*/
|
||||||
|
readonly labelSelector?: Array<string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field selector for filtering.
|
||||||
|
* @type {Array<string>}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1UserApiListUsers
|
||||||
|
*/
|
||||||
|
readonly fieldSelector?: Array<string>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request parameters for updateCurrentUser operation in ApiConsoleHaloRunV1alpha1UserApi.
|
* Request parameters for updateCurrentUser operation in ApiConsoleHaloRunV1alpha1UserApi.
|
||||||
* @export
|
* @export
|
||||||
|
@ -555,6 +749,31 @@ export class ApiConsoleHaloRunV1alpha1UserApi extends BaseAPI {
|
||||||
.then((request) => request(this.axios, this.basePath))
|
.then((request) => request(this.axios, this.basePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List users
|
||||||
|
* @param {ApiConsoleHaloRunV1alpha1UserApiListUsersRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1UserApi
|
||||||
|
*/
|
||||||
|
public listUsers(
|
||||||
|
requestParameters: ApiConsoleHaloRunV1alpha1UserApiListUsersRequest = {},
|
||||||
|
options?: AxiosRequestConfig,
|
||||||
|
) {
|
||||||
|
return ApiConsoleHaloRunV1alpha1UserApiFp(this.configuration)
|
||||||
|
.listUsers(
|
||||||
|
requestParameters.sort,
|
||||||
|
requestParameters.keyword,
|
||||||
|
requestParameters.role,
|
||||||
|
requestParameters.size,
|
||||||
|
requestParameters.page,
|
||||||
|
requestParameters.labelSelector,
|
||||||
|
requestParameters.fieldSelector,
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
.then((request) => request(this.axios, this.basePath))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update current user profile, but password.
|
* Update current user profile, but password.
|
||||||
* @param {ApiConsoleHaloRunV1alpha1UserApiUpdateCurrentUserRequest} requestParameters Request parameters.
|
* @param {ApiConsoleHaloRunV1alpha1UserApiUpdateCurrentUserRequest} requestParameters Request parameters.
|
||||||
|
|
|
@ -17,20 +17,25 @@ import {
|
||||||
VStatusDot,
|
VStatusDot,
|
||||||
VLoading,
|
VLoading,
|
||||||
Toast,
|
Toast,
|
||||||
|
IconRefreshLine,
|
||||||
|
VEmpty,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import UserEditingModal from "./components/UserEditingModal.vue";
|
import UserEditingModal from "./components/UserEditingModal.vue";
|
||||||
import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
|
import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
|
||||||
import GrantPermissionModal from "./components/GrantPermissionModal.vue";
|
import GrantPermissionModal from "./components/GrantPermissionModal.vue";
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { User, UserList } from "@halo-dev/api-client";
|
import type { Role, User, UserList } from "@halo-dev/api-client";
|
||||||
import { rbacAnnotations } from "@/constants/annotations";
|
import { rbacAnnotations } from "@/constants/annotations";
|
||||||
import { formatDatetime } from "@/utils/date";
|
import { formatDatetime } from "@/utils/date";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import Fuse from "fuse.js";
|
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import { useRoleStore } from "@/stores/role";
|
import { useRoleStore } from "@/stores/role";
|
||||||
|
import { getNode } from "@formkit/core";
|
||||||
|
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||||
|
import { useFetchRole } from "../roles/composables/use-role";
|
||||||
|
import FilterCleanButton from "@/components/filter/FilterCleanButton.vue";
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
|
|
||||||
|
@ -53,16 +58,18 @@ const users = ref<UserList>({
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const selectedUserNames = ref<string[]>([]);
|
const selectedUserNames = ref<string[]>([]);
|
||||||
const selectedUser = ref<User>();
|
const selectedUser = ref<User>();
|
||||||
|
const keyword = ref("");
|
||||||
const refreshInterval = ref();
|
const refreshInterval = ref();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const roleStore = useRoleStore();
|
const roleStore = useRoleStore();
|
||||||
|
|
||||||
let fuse: Fuse<User> | undefined = undefined;
|
|
||||||
|
|
||||||
const ANONYMOUSUSER_NAME = "anonymousUser";
|
const ANONYMOUSUSER_NAME = "anonymousUser";
|
||||||
|
|
||||||
const handleFetchUsers = async (options?: { mute?: boolean }) => {
|
const handleFetchUsers = async (options?: {
|
||||||
|
mute?: boolean;
|
||||||
|
page?: number;
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
clearInterval(refreshInterval.value);
|
clearInterval(refreshInterval.value);
|
||||||
|
|
||||||
|
@ -70,18 +77,22 @@ const handleFetchUsers = async (options?: { mute?: boolean }) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await apiClient.extension.user.listv1alpha1User({
|
if (options?.page) {
|
||||||
|
users.value.page = options.page;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await apiClient.user.listUsers({
|
||||||
page: users.value.page,
|
page: users.value.page,
|
||||||
size: users.value.size,
|
size: users.value.size,
|
||||||
|
keyword: keyword.value,
|
||||||
fieldSelector: [`name!=${ANONYMOUSUSER_NAME}`],
|
fieldSelector: [`name!=${ANONYMOUSUSER_NAME}`],
|
||||||
|
sort: [selectedSortItem.value?.value].filter(
|
||||||
|
(item) => !!item
|
||||||
|
) as string[],
|
||||||
|
role: selectedRole.value?.metadata.name,
|
||||||
});
|
});
|
||||||
users.value = data;
|
|
||||||
|
|
||||||
fuse = new Fuse(data.items, {
|
users.value = data;
|
||||||
keys: ["spec.displayName", "metadata.name", "spec.email"],
|
|
||||||
useExtendedSearch: true,
|
|
||||||
threshold: 0.2,
|
|
||||||
});
|
|
||||||
|
|
||||||
const deletedUsers = users.value.items.filter(
|
const deletedUsers = users.value.items.filter(
|
||||||
(user) => !!user.metadata.deletionTimestamp
|
(user) => !!user.metadata.deletionTimestamp
|
||||||
|
@ -100,16 +111,6 @@ const handleFetchUsers = async (options?: { mute?: boolean }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const keyword = ref("");
|
|
||||||
|
|
||||||
const searchResults = computed(() => {
|
|
||||||
if (!fuse || !keyword.value) {
|
|
||||||
return users.value.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fuse?.search(keyword.value).map((item) => item.item);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handlePaginationChange = async ({
|
const handlePaginationChange = async ({
|
||||||
page,
|
page,
|
||||||
size,
|
size,
|
||||||
|
@ -239,6 +240,62 @@ onMounted(() => {
|
||||||
editingModal.value = true;
|
editingModal.value = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
function handleKeywordChange() {
|
||||||
|
const keywordNode = getNode("keywordInput");
|
||||||
|
if (keywordNode) {
|
||||||
|
keyword.value = keywordNode._value as string;
|
||||||
|
}
|
||||||
|
handleFetchUsers({ page: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClearKeyword() {
|
||||||
|
keyword.value = "";
|
||||||
|
handleFetchUsers({ page: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SortItem {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SortItems: SortItem[] = [
|
||||||
|
{
|
||||||
|
label: "较近创建",
|
||||||
|
value: "creationTimestamp,desc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "较早创建",
|
||||||
|
value: "creationTimestamp,asc",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const selectedSortItem = ref<SortItem>();
|
||||||
|
|
||||||
|
function handleSortItemChange(sortItem?: SortItem) {
|
||||||
|
selectedSortItem.value = sortItem;
|
||||||
|
handleFetchUsers({ page: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { roles } = useFetchRole();
|
||||||
|
const selectedRole = ref<Role>();
|
||||||
|
|
||||||
|
function handleRoleChange(role?: Role) {
|
||||||
|
selectedRole.value = role;
|
||||||
|
handleFetchUsers({ page: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClearFilters() {
|
||||||
|
selectedRole.value = undefined;
|
||||||
|
selectedSortItem.value = undefined;
|
||||||
|
keyword.value = "";
|
||||||
|
handleFetchUsers({ page: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasFilters = computed(() => {
|
||||||
|
return selectedRole.value || selectedSortItem.value || keyword.value;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<UserEditingModal
|
<UserEditingModal
|
||||||
|
@ -308,49 +365,53 @@ onMounted(() => {
|
||||||
@change="handleCheckAllChange"
|
@change="handleCheckAllChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-full flex-1 sm:w-auto">
|
<div class="flex w-full flex-1 items-center sm:w-auto">
|
||||||
<FormKit
|
<div
|
||||||
v-if="!selectedUserNames.length"
|
v-if="!selectedUserNames.length"
|
||||||
v-model="keyword"
|
class="flex items-center gap-2"
|
||||||
placeholder="输入关键词搜索"
|
>
|
||||||
type="text"
|
<FormKit
|
||||||
></FormKit>
|
id="keywordInput"
|
||||||
|
outer-class="!p-0"
|
||||||
|
:model-value="keyword"
|
||||||
|
name="keyword"
|
||||||
|
placeholder="输入关键词搜索"
|
||||||
|
type="text"
|
||||||
|
@keyup.enter="handleKeywordChange"
|
||||||
|
></FormKit>
|
||||||
|
|
||||||
|
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||||
|
关键词:{{ keyword }}
|
||||||
|
</FilterTag>
|
||||||
|
|
||||||
|
<FilterTag v-if="selectedRole" @close="handleRoleChange()">
|
||||||
|
角色:{{
|
||||||
|
selectedRole.metadata.annotations?.[
|
||||||
|
rbacAnnotations.DISPLAY_NAME
|
||||||
|
] || selectedRole.metadata.name
|
||||||
|
}}
|
||||||
|
</FilterTag>
|
||||||
|
|
||||||
|
<FilterTag
|
||||||
|
v-if="selectedSortItem"
|
||||||
|
@close="handleSortItemChange()"
|
||||||
|
>
|
||||||
|
排序:{{ selectedSortItem.label }}
|
||||||
|
</FilterTag>
|
||||||
|
|
||||||
|
<FilterCleanButton
|
||||||
|
v-if="hasFilters"
|
||||||
|
@click="handleClearFilters"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<VSpace v-else>
|
<VSpace v-else>
|
||||||
<VButton type="danger" @click="handleDeleteInBatch">
|
<VButton type="danger" @click="handleDeleteInBatch">
|
||||||
删除
|
删除
|
||||||
</VButton>
|
</VButton>
|
||||||
</VSpace>
|
</VSpace>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="false" class="mt-4 flex sm:mt-0">
|
<div class="mt-4 flex sm:mt-0">
|
||||||
<VSpace spacing="lg">
|
<VSpace spacing="lg">
|
||||||
<FloatingDropdown>
|
|
||||||
<div
|
|
||||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
|
||||||
>
|
|
||||||
<span class="mr-0.5">状态</span>
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<template #popper>
|
|
||||||
<div class="w-52 p-4">
|
|
||||||
<ul class="space-y-1">
|
|
||||||
<li
|
|
||||||
v-close-popper
|
|
||||||
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
|
||||||
>
|
|
||||||
<span class="truncate">正常</span>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-close-popper
|
|
||||||
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
|
||||||
>
|
|
||||||
<span class="truncate">已禁用</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FloatingDropdown>
|
|
||||||
<FloatingDropdown>
|
<FloatingDropdown>
|
||||||
<div
|
<div
|
||||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||||
|
@ -364,28 +425,19 @@ onMounted(() => {
|
||||||
<div class="w-52 p-4">
|
<div class="w-52 p-4">
|
||||||
<ul class="space-y-1">
|
<ul class="space-y-1">
|
||||||
<li
|
<li
|
||||||
|
v-for="(role, index) in roles"
|
||||||
|
:key="index"
|
||||||
v-close-popper
|
v-close-popper
|
||||||
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
||||||
|
@click="handleRoleChange(role)"
|
||||||
>
|
>
|
||||||
<span class="truncate">Super Administrator</span>
|
<span class="truncate">
|
||||||
</li>
|
{{
|
||||||
<li
|
role.metadata.annotations?.[
|
||||||
v-close-popper
|
rbacAnnotations.DISPLAY_NAME
|
||||||
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
] || role.metadata.name
|
||||||
>
|
}}
|
||||||
<span class="truncate">Administrator</span>
|
</span>
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-close-popper
|
|
||||||
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
|
||||||
>
|
|
||||||
<span class="truncate">Editor</span>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-close-popper
|
|
||||||
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
|
||||||
>
|
|
||||||
<span class="truncate">Guest</span>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -404,33 +456,67 @@ onMounted(() => {
|
||||||
<div class="w-72 p-4">
|
<div class="w-72 p-4">
|
||||||
<ul class="space-y-1">
|
<ul class="space-y-1">
|
||||||
<li
|
<li
|
||||||
|
v-for="(sortItem, index) in SortItems"
|
||||||
|
:key="index"
|
||||||
v-close-popper
|
v-close-popper
|
||||||
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
||||||
|
@click="handleSortItemChange(sortItem)"
|
||||||
>
|
>
|
||||||
<span class="truncate">较近登录</span>
|
<span class="truncate">{{ sortItem.label }}</span>
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-close-popper
|
|
||||||
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
|
||||||
>
|
|
||||||
<span class="truncate">较晚登录</span>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</FloatingDropdown>
|
</FloatingDropdown>
|
||||||
|
<div class="flex flex-row gap-2">
|
||||||
|
<div
|
||||||
|
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||||
|
@click="handleFetchUsers()"
|
||||||
|
>
|
||||||
|
<IconRefreshLine
|
||||||
|
v-tooltip="`刷新`"
|
||||||
|
:class="{ 'animate-spin text-gray-900': loading }"
|
||||||
|
class="h-4 w-4 text-gray-600 group-hover:text-gray-900"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</VSpace>
|
</VSpace>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<VLoading v-if="loading" />
|
<VLoading v-if="loading" />
|
||||||
|
|
||||||
|
<Transition v-else-if="!users.total" appear name="fade">
|
||||||
|
<VEmpty
|
||||||
|
message="当前没有符合筛选条件的用户,你可以尝试刷新或者创建新用户"
|
||||||
|
title="当前没有符合筛选条件的用户"
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<VSpace>
|
||||||
|
<VButton @click="handleFetchUsers()">刷新</VButton>
|
||||||
|
<VButton
|
||||||
|
v-permission="['system:users:manage']"
|
||||||
|
type="secondary"
|
||||||
|
@click="editingModal = true"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<IconAddCircle class="h-full w-full" />
|
||||||
|
</template>
|
||||||
|
新建用户
|
||||||
|
</VButton>
|
||||||
|
</VSpace>
|
||||||
|
</template>
|
||||||
|
</VEmpty>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
<Transition v-else appear name="fade">
|
<Transition v-else appear name="fade">
|
||||||
<ul
|
<ul
|
||||||
class="box-border h-full w-full divide-y divide-gray-100"
|
class="box-border h-full w-full divide-y divide-gray-100"
|
||||||
role="list"
|
role="list"
|
||||||
>
|
>
|
||||||
<li v-for="(user, index) in searchResults" :key="index">
|
<li v-for="(user, index) in users.items" :key="index">
|
||||||
<VEntity :is-selected="checkSelection(user)">
|
<VEntity :is-selected="checkSelection(user)">
|
||||||
<template
|
<template
|
||||||
v-if="currentUserHasPermission(['system:users:manage'])"
|
v-if="currentUserHasPermission(['system:users:manage'])"
|
||||||
|
|
Loading…
Reference in New Issue