diff --git a/backend/src/main/java/com/zl/mjga/dto/PageRequestDto.java b/backend/src/main/java/com/zl/mjga/dto/PageRequestDto.java index ec78587..35a5d39 100644 --- a/backend/src/main/java/com/zl/mjga/dto/PageRequestDto.java +++ b/backend/src/main/java/com/zl/mjga/dto/PageRequestDto.java @@ -1,8 +1,10 @@ package com.zl.mjga.dto; +import static com.zl.mjga.utils.StringCaseUtils.convertCamelCaseToSnake; import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.name; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -17,11 +19,12 @@ public class PageRequestDto { public static final String REGEX = "^[a-zA-Z][a-zA-Z0-9_]*$"; - public static final String SPACE = " "; + public static final String COLON = ":"; private long page; private long size; + @Schema(description = "排序字段", example = "name:asc,age:desc", type = "string") private Map sortBy = new HashMap<>(); public PageRequestDto(int page, int size) { @@ -68,10 +71,12 @@ public class PageRequestDto { } public List> getSortFields() { - List> sortFields = sortBy.entrySet().stream() + List> sortFields = + sortBy.entrySet().stream() .map( - (entry) -> - field(name(entry.getKey())).sort(SortOrder.valueOf(entry.getValue().getKeyword()))) + (entry) -> + field(name(convertCamelCaseToSnake(entry.getKey()))) + .sort(SortOrder.valueOf(entry.getValue().getKeyword()))) .toList(); if (sortFields.isEmpty()) { return List.of(field(name("id")).sort(SortOrder.ASC)); @@ -108,7 +113,7 @@ public class PageRequestDto { return result; } for (String fieldSpaceDirection : sortBy.split(",")) { - String[] fieldDirectionArray = fieldSpaceDirection.split(SPACE); + String[] fieldDirectionArray = fieldSpaceDirection.split(COLON); if (fieldDirectionArray.length != 2) { throw new IllegalArgumentException( String.format( diff --git a/backend/src/main/java/com/zl/mjga/dto/urp/UserUpsertDto.java b/backend/src/main/java/com/zl/mjga/dto/urp/UserUpsertDto.java index 2a3dc46..51e6f0f 100644 --- a/backend/src/main/java/com/zl/mjga/dto/urp/UserUpsertDto.java +++ b/backend/src/main/java/com/zl/mjga/dto/urp/UserUpsertDto.java @@ -11,8 +11,7 @@ import lombok.NoArgsConstructor; @Data public class UserUpsertDto { private Long id; - @NotEmpty - private String username; + @NotEmpty private String username; private String password; @NotNull private Boolean enable; } diff --git a/backend/src/main/java/com/zl/mjga/repository/PermissionRepository.java b/backend/src/main/java/com/zl/mjga/repository/PermissionRepository.java index 5984779..d19e89b 100644 --- a/backend/src/main/java/com/zl/mjga/repository/PermissionRepository.java +++ b/backend/src/main/java/com/zl/mjga/repository/PermissionRepository.java @@ -3,7 +3,6 @@ package com.zl.mjga.repository; import static org.jooq.generated.mjga.tables.Permission.PERMISSION; import static org.jooq.generated.mjga.tables.Role.ROLE; import static org.jooq.impl.DSL.*; -import static org.jooq.impl.DSL.noField; import com.zl.mjga.dto.PageRequestDto; import com.zl.mjga.dto.urp.PermissionQueryDto; diff --git a/backend/src/main/java/com/zl/mjga/repository/PositionRepository.java b/backend/src/main/java/com/zl/mjga/repository/PositionRepository.java index 3adeb39..164a3d8 100644 --- a/backend/src/main/java/com/zl/mjga/repository/PositionRepository.java +++ b/backend/src/main/java/com/zl/mjga/repository/PositionRepository.java @@ -2,7 +2,6 @@ package com.zl.mjga.repository; import static org.jooq.generated.mjga.Tables.*; import static org.jooq.impl.DSL.noCondition; -import static org.jooq.impl.DSL.noField; import com.zl.mjga.dto.PageRequestDto; import com.zl.mjga.dto.position.PositionQueryDto; diff --git a/backend/src/main/java/com/zl/mjga/utils/StringCaseUtils.java b/backend/src/main/java/com/zl/mjga/utils/StringCaseUtils.java new file mode 100644 index 0000000..e30cef7 --- /dev/null +++ b/backend/src/main/java/com/zl/mjga/utils/StringCaseUtils.java @@ -0,0 +1,15 @@ +package com.zl.mjga.utils; + +public class StringCaseUtils { + public static String convertCamelCaseToSnake(String input) { + StringBuilder result = new StringBuilder(); + for (char c : input.toCharArray()) { + if (Character.isUpperCase(c)) { + result.append("_").append(Character.toLowerCase(c)); + } else { + result.append(c); + } + } + return result.toString(); + } +} diff --git a/frontend/src/api/schema/openapi.json b/frontend/src/api/schema/openapi.json index 936563f..9678d32 100644 --- a/frontend/src/api/schema/openapi.json +++ b/frontend/src/api/schema/openapi.json @@ -1219,14 +1219,9 @@ "format": "int64" }, "sortBy": { - "type": "object", - "additionalProperties": { - "type": "string", - "enum": [ - "ASC", - "DESC" - ] - } + "type": "string", + "description": "排序字段", + "example": "name:asc,age:desc" }, "offset": { "type": "integer", diff --git a/frontend/src/api/types/schema.d.ts b/frontend/src/api/types/schema.d.ts index e59476f..a254da5 100644 --- a/frontend/src/api/types/schema.d.ts +++ b/frontend/src/api/types/schema.d.ts @@ -544,9 +544,11 @@ export interface components { page?: number; /** Format: int64 */ size?: number; - sortBy?: { - [key: string]: "ASC" | "DESC"; - }; + /** + * @description 排序字段 + * @example name:asc,age:desc + */ + sortBy?: string; /** Format: int64 */ offset?: number; sortFields?: components["schemas"]["SortFieldObject"][]; diff --git a/frontend/src/components/SortIcon.vue b/frontend/src/components/SortIcon.vue new file mode 100644 index 0000000..f2b100f --- /dev/null +++ b/frontend/src/components/SortIcon.vue @@ -0,0 +1,18 @@ + + + diff --git a/frontend/src/composables/sort.ts b/frontend/src/composables/sort.ts new file mode 100644 index 0000000..e25b95e --- /dev/null +++ b/frontend/src/composables/sort.ts @@ -0,0 +1,42 @@ +import { computed, ref } from "vue"; + +export const useSort = () => { + const sortFields = ref< + { + field: string; + order: "asc" | "desc" | undefined; + }[] + >([]); + + const getSortField = (field: string) => { + return sortFields.value.find((item) => item.field === field); + }; + + const sortBy = computed(() => { + return sortFields.value + .map((item) => `${item.field}:${item.order}`) + .join(","); + }); + + const handleSort = async (field: string) => { + if (sortFields.value?.find((item) => item.field === field)) { + sortFields.value = sortFields.value?.map((item) => + item.field === field + ? { ...item, order: item.order === "asc" ? "desc" : undefined } + : item, + ); + } else { + sortFields.value.push({ field, order: "asc" }); + } + sortFields.value = sortFields.value?.filter( + (item) => item.order !== undefined, + ); + }; + + return { + sortFields, + sortBy, + handleSort, + getSortField, + }; +}; diff --git a/frontend/src/composables/user/useUserQuery.ts b/frontend/src/composables/user/useUserQuery.ts index 8286359..edf7d4b 100644 --- a/frontend/src/composables/user/useUserQuery.ts +++ b/frontend/src/composables/user/useUserQuery.ts @@ -24,6 +24,7 @@ export const useUserQuery = () => { }, page = 1, size = 10, + sortBy = "id:desc", ) => { const { data } = await client.GET("/iam/users", { params: { @@ -31,6 +32,7 @@ export const useUserQuery = () => { pageRequestDto: { page, size, + sortBy, }, userQueryDto: param, }, diff --git a/frontend/src/views/UserView.vue b/frontend/src/views/UserView.vue index b89493b..fdd9c75 100644 --- a/frontend/src/views/UserView.vue +++ b/frontend/src/views/UserView.vue @@ -25,7 +25,7 @@ @@ -42,7 +42,12 @@ 用户名 - 创建时间 + +
+ 创建时间 + +
+ 状态 分配 操作 @@ -147,18 +152,18 @@ import { useRouter } from "vue-router"; import type { components } from "../api/types/schema"; import useAlertStore from "../composables/store/useAlertStore"; import { useUserUpsert } from "../composables/user/useUserUpsert"; +import { useSort } from "@/composables/sort"; +import SortIcon from "@/components/SortIcon.vue"; const username = ref(""); const selectedUser = ref(); const userUpsertModal = ref(); const userDeleteModal = ref(); const router = useRouter(); - const { total, users, fetchUsersWith } = useUserQuery(); - const { deleteUser } = useUserDelete(); const userUpsert = useUserUpsert(); - +const { sortFields, sortBy, handleSort, getSortField } = useSort(); const alertStore = useAlertStore(); onMounted(async () => { @@ -193,9 +198,14 @@ const handleUpsertUserSubmit = async (data: UserUpsertSubmitModel) => { content: "操作成功", level: "success", }); - await fetchUsersWith({ - username: username.value, - }); + await fetchUsersWith( + { + username: username.value, + }, + 1, + 10, + sortBy.value, + ); }; const handleUpsertUserClick = async ( @@ -240,6 +250,18 @@ const handleBindPositionClick = async ( }); }; +const handleSortClick = async (field: string) => { + handleSort(field); + await fetchUsersWith( + { + username: username.value, + }, + 1, + 10, + sortBy.value, + ); +}; + const handleDeleteUserSubmit = async () => { if (!selectedUser?.value?.id) return; await deleteUser(selectedUser.value.id); @@ -248,9 +270,14 @@ const handleDeleteUserSubmit = async () => { content: "删除成功", level: "success", }); - await fetchUsersWith({ - username: username.value, - }); + await fetchUsersWith( + { + username: username.value, + }, + 1, + 10, + sortBy.value, + ); }; const handleDeleteUserClick = async ( @@ -263,9 +290,14 @@ const handleDeleteUserClick = async ( }; const handleSearch = async () => { - await fetchUsersWith({ - username: username.value, - }); + await fetchUsersWith( + { + username: username.value, + }, + 1, + 10, + sortBy.value, + ); }; const handlePageChange = async (page: number, pageSize: number) => { @@ -275,6 +307,7 @@ const handlePageChange = async (page: number, pageSize: number) => { }, page, pageSize, + sortBy.value, ); };