mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-14 05:33:42 +08:00
add field order
This commit is contained in:
@@ -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<String, Direction> sortBy = new HashMap<>();
|
||||
|
||||
public PageRequestDto(int page, int size) {
|
||||
@@ -68,10 +71,12 @@ public class PageRequestDto {
|
||||
}
|
||||
|
||||
public List<SortField<Object>> getSortFields() {
|
||||
List<SortField<Object>> sortFields = sortBy.entrySet().stream()
|
||||
List<SortField<Object>> 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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
15
backend/src/main/java/com/zl/mjga/utils/StringCaseUtils.java
Normal file
15
backend/src/main/java/com/zl/mjga/utils/StringCaseUtils.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
8
frontend/src/api/types/schema.d.ts
vendored
8
frontend/src/api/types/schema.d.ts
vendored
@@ -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"][];
|
||||
|
||||
18
frontend/src/components/SortIcon.vue
Normal file
18
frontend/src/components/SortIcon.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<span class="ml-1">
|
||||
<svg class="w-3.5 h-3.5" :class="{'text-blue-700': sortField?.order}" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path v-if="sortField?.order === 'asc'" d="M8 12l6-6 6 6"/>
|
||||
<path v-else-if="sortField?.order === 'desc'" d="M8 12l6 6 6-6"/>
|
||||
<path v-else d="M8 9l6-6 6 6M8 15l6 6 6-6"/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
sortField?: {
|
||||
field: string;
|
||||
order: "asc" | "desc" | undefined;
|
||||
};
|
||||
}>();
|
||||
</script>
|
||||
42
frontend/src/composables/sort.ts
Normal file
42
frontend/src/composables/sort.ts
Normal file
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</form>
|
||||
<!-- Create Modal toggle -->
|
||||
<button @click="handleUpsertUserClick(undefined)"
|
||||
class="flex items-center block 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-5 py-2.5 text-center absolute right-5 bottom-2"
|
||||
class="flex items-center 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-5 py-2.5 text-center absolute right-5 bottom-2"
|
||||
type="button">
|
||||
新增用户
|
||||
</button>
|
||||
@@ -42,7 +42,12 @@
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">用户名</th>
|
||||
<th scope="col" class="px-6 py-3">创建时间</th>
|
||||
<th scope="col" class="px-6 py-3 cursor-pointer" @click="handleSortClick('createTime')">
|
||||
<div class="flex items-center">
|
||||
<span>创建时间</span>
|
||||
<SortIcon :sortField="getSortField('createTime')" />
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">状态</th>
|
||||
<th scope="col" class="px-6 py-3">分配</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
@@ -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<string>("");
|
||||
const selectedUser = ref<components["schemas"]["UserRolePermissionDto"]>();
|
||||
const userUpsertModal = ref<ModalInterface>();
|
||||
const userDeleteModal = ref<ModalInterface>();
|
||||
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,
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user