mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-13 21:27:19 +08:00
add date picker
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
package com.zl.mjga.dto.urp;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import lombok.*;
|
||||
|
||||
@AllArgsConstructor
|
||||
@@ -8,6 +8,6 @@ import lombok.*;
|
||||
@Data
|
||||
public class UserQueryDto {
|
||||
private String username;
|
||||
private LocalDateTime starDate;
|
||||
private LocalDateTime endDate;
|
||||
private OffsetDateTime startDate;
|
||||
private OffsetDateTime endDate;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.zl.mjga.repository;
|
||||
|
||||
import static org.jooq.generated.mjga.Tables.*;
|
||||
import static org.jooq.generated.mjga.tables.User.USER;
|
||||
import static org.jooq.impl.DSL.*;
|
||||
|
||||
@@ -65,8 +64,8 @@ public class UserRepository extends UserDao {
|
||||
? USER.USERNAME.like("%" + userQueryDto.getUsername() + "%")
|
||||
: noCondition())
|
||||
.and(
|
||||
userQueryDto.getStarDate() != null
|
||||
? USER.CREATE_TIME.ge(userQueryDto.getStarDate())
|
||||
userQueryDto.getStartDate() != null
|
||||
? USER.CREATE_TIME.ge(userQueryDto.getStartDate())
|
||||
: noCondition())
|
||||
.and(
|
||||
userQueryDto.getEndDate() != null
|
||||
@@ -79,13 +78,7 @@ public class UserRepository extends UserDao {
|
||||
}
|
||||
|
||||
public Result<Record> pageFetchBy(PageRequestDto pageRequestDto, UserQueryDto userQueryDto) {
|
||||
return ctx()
|
||||
.select(asterisk(), DSL.count().over().as("total_user"))
|
||||
.from(USER)
|
||||
.where(
|
||||
userQueryDto.getUsername() != null
|
||||
? USER.USERNAME.like("%" + userQueryDto.getUsername() + "%")
|
||||
: noCondition())
|
||||
return selectBy(userQueryDto)
|
||||
.orderBy(pageRequestDto.getSortFields())
|
||||
.limit(pageRequestDto.getSize())
|
||||
.offset(pageRequestDto.getOffset())
|
||||
|
||||
@@ -3,7 +3,7 @@ CREATE SCHEMA IF NOT EXISTS mjga;
|
||||
CREATE TABLE mjga.user (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
username VARCHAR NOT NULL UNIQUE,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
password VARCHAR NOT NULL,
|
||||
enable BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
VITE_APP_PORT=5173
|
||||
VITE_SOURCE_MAP=true
|
||||
# mock
|
||||
VITE_ENABLE_MOCK=true
|
||||
VITE_BASE_URL=http://localhost:5173
|
||||
#VITE_ENABLE_MOCK=true
|
||||
#VITE_BASE_URL=http://localhost:5173
|
||||
# local
|
||||
#VITE_ENABLE_MOCK=false
|
||||
#VITE_BASE_URL=http://localhost:8080
|
||||
VITE_ENABLE_MOCK=false
|
||||
VITE_BASE_URL=http://localhost:8080
|
||||
# dev
|
||||
#VITE_ENABLE_MOCK=false
|
||||
#VITE_BASE_URL=https://localhost/api
|
||||
|
||||
26
frontend/package-lock.json
generated
26
frontend/package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@tailwindcss/vite": "^4.0.14",
|
||||
"@vuepic/vue-datepicker": "^11.0.2",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"apexcharts": "^3.46.0",
|
||||
"dayjs": "^1.11.13",
|
||||
@@ -2691,6 +2692,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vuepic/vue-datepicker": {
|
||||
"version": "11.0.2",
|
||||
"resolved": "http://mirrors.tencent.com/npm/@vuepic/vue-datepicker/-/vue-datepicker-11.0.2.tgz",
|
||||
"integrity": "sha512-uHh78mVBXCEjam1uVfTzZ/HkyDwut/H6b2djSN9YTF+l/EA+XONfdCnOVSi1g+qVGSy65DcQAwyBNidAssnudQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"date-fns": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.1.0.tgz",
|
||||
@@ -3237,6 +3253,16 @@
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "http://mirrors.tencent.com/npm/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "http://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.13.tgz",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"dependencies": {
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@tailwindcss/vite": "^4.0.14",
|
||||
"@vuepic/vue-datepicker": "^11.0.2",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"apexcharts": "^3.46.0",
|
||||
"dayjs": "^1.11.13",
|
||||
|
||||
@@ -8,4 +8,6 @@ import Alert from "./components/Alert.vue";
|
||||
<Alert/>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
51
frontend/src/assets/datepicker.css
Normal file
51
frontend/src/assets/datepicker.css
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 日期选择器亮色主题 - 与 Flowbite 蓝色主题匹配 */ .dp__theme_light {
|
||||
/* 基础颜色 */
|
||||
--dp-background-color: #fff;
|
||||
--dp-text-color: #1f2937; /* 对应 Flowbite 的 gray-800 */
|
||||
|
||||
/* 主色调 */
|
||||
--dp-primary-color: #2563eb; /* 对应 Flowbite 的 primary-600 */
|
||||
--dp-primary-disabled-color: #93c5fd; /* 对应 Flowbite 的 primary-300 */
|
||||
--dp-primary-text-color: #fff;
|
||||
|
||||
/* 次要颜色 */
|
||||
--dp-secondary-color: #9ca3af; /* 对应 Flowbite 的 gray-400 */
|
||||
|
||||
/* 边框颜色 */
|
||||
--dp-border-color: var(--color-gray-300); /* 对应 Flowbite 的 gray-200 */
|
||||
--dp-menu-border-color: #e5e7eb;
|
||||
|
||||
/* 禁用状态 */
|
||||
--dp-disabled-color: #f3f4f6; /* 对应 Flowbite 的 gray-100 */
|
||||
--dp-disabled-color-text: #9ca3af; /* 对应 Flowbite 的 gray-400 */
|
||||
|
||||
/* 滚动条 */
|
||||
--dp-scroll-bar-background: #f3f4f6; /* 对应 Flowbite 的 gray-100 */
|
||||
--dp-scroll-bar-color: #9ca3af; /* 对应 Flowbite 的 gray-400 */
|
||||
|
||||
/* 成功状态 */
|
||||
--dp-success-color: #10b981; /* 对应 Tailwind 的 emerald-500 */
|
||||
--dp-success-color-disabled: #6ee7b7; /* 对应 Tailwind 的 emerald-300 */
|
||||
|
||||
/* 图标颜色 */
|
||||
--dp-icon-color: #6b7280; /* 对应 Flowbite 的 gray-500 */
|
||||
|
||||
/* 危险/错误状态 */
|
||||
--dp-danger-color: #ef4444; /* 对应 Tailwind 的 red-500 */
|
||||
--dp-marker-color: #ef4444;
|
||||
|
||||
/* 提示颜色 */
|
||||
--dp-tooltip-color: #f9fafb; /* 对应 Flowbite 的 gray-50 */
|
||||
|
||||
/* 高亮颜色 */
|
||||
--dp-highlight-color: rgb(37 99 235 / 10%); /* 对应 Flowbite 的 primary-600 透明度 */
|
||||
|
||||
/* 日期范围相关 */
|
||||
--dp-range-between-dates-background-color: var(--dp-hover-color, #eff6ff);
|
||||
--dp-range-between-dates-text-color: var(--dp-hover-text-color, #1f2937);
|
||||
--dp-range-between-border-color: var(--dp-hover-color, #eff6ff);
|
||||
|
||||
/* 圆角设置 - 匹配项目中的 rounded-lg */
|
||||
--dp-border-radius: 0.5rem; /* 8px,匹配 Tailwind 的 rounded-lg */
|
||||
--dp-cell-border-radius: 0.375rem; /* 6px,稍微小一点,更美观 */
|
||||
}
|
||||
@@ -21,6 +21,8 @@ export const useUserQuery = () => {
|
||||
const fetchUsersWith = async (
|
||||
param: {
|
||||
username?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
},
|
||||
page = 1,
|
||||
size = 10,
|
||||
|
||||
@@ -8,6 +8,9 @@ import useUserAuth from "./composables/auth/useUserAuth";
|
||||
import useAlertStore from "./composables/store/useAlertStore";
|
||||
import router from "./router";
|
||||
import makeErrorHandler from "./utils/errorHandler";
|
||||
import VueDatePicker from "@vuepic/vue-datepicker";
|
||||
import "@vuepic/vue-datepicker/dist/main.css";
|
||||
import "./assets/datepicker.css";
|
||||
|
||||
async function enableMocking() {
|
||||
if (import.meta.env.VITE_ENABLE_MOCK === "false") {
|
||||
@@ -29,5 +32,6 @@ enableMocking().then(() => {
|
||||
app.use(router);
|
||||
const errorHandler = makeErrorHandler(router, signOut, showAlert);
|
||||
app.config.errorHandler = errorHandler;
|
||||
app.component("VueDatePicker", VueDatePicker);
|
||||
app.mount("#app");
|
||||
});
|
||||
|
||||
@@ -8,4 +8,9 @@ dayjs.locale("zh-cn");
|
||||
dayjs.extend(timezone);
|
||||
dayjs.tz.setDefault("Asia/Shanghai");
|
||||
|
||||
export default dayjs;
|
||||
const formatDate = (date?: Date) => {
|
||||
if (!date) return undefined;
|
||||
return dayjs(date).format("YYYY-MM-DDTHH:mm:ss.SSSZ");
|
||||
};
|
||||
|
||||
export { dayjs, formatDate };
|
||||
|
||||
@@ -127,6 +127,8 @@ import { useRoute } from "vue-router";
|
||||
import { useRoleBind } from "../composables/role/useRoleBind";
|
||||
import useAlertStore from "../composables/store/useAlertStore";
|
||||
import { useActionExcStore } from "@/composables/store/useActionExcStore";
|
||||
import VueDatePicker from "@vuepic/vue-datepicker";
|
||||
import "@vuepic/vue-datepicker/dist/main.css";
|
||||
|
||||
const roleName = ref<string>("");
|
||||
const checkedRoleIds = ref<number[]>([]);
|
||||
|
||||
@@ -173,8 +173,7 @@ import { useJobControl } from "@/composables/job/useJobControl";
|
||||
import { useJobsPaginationQuery } from "@/composables/job/useJobQuery";
|
||||
import { useJobUpdate } from "@/composables/job/useJobUpdate";
|
||||
import useAlertStore from "@/composables/store/useAlertStore";
|
||||
import { useMobileStyles } from "@/composables/useMobileStyles";
|
||||
import dayjs from "@/utils/dateUtil";
|
||||
import { dayjs } from "@/utils/dateUtil";
|
||||
import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import type { components } from "../api/types/schema";
|
||||
|
||||
@@ -5,23 +5,27 @@
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">用户管理</h1>
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-4 gap-y-3 sm:gap-y-0">
|
||||
<form class="w-full sm:max-w-xs">
|
||||
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
||||
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 20 20">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
|
||||
</svg>
|
||||
<form class="w-full sm:w-auto flex flex-col xs:flex-row gap-2 xs:gap-3 items-stretch xs:items-center">
|
||||
<div class="flex-grow">
|
||||
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only">Search</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
||||
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 20 20">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input type="search" id="default-search" v-model="username"
|
||||
class="block w-full p-2.5 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500"
|
||||
placeholder="用户名" required />
|
||||
</div>
|
||||
<input type="search" id="default-search" v-model="username"
|
||||
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>
|
||||
<VueDatePicker v-model="dateRange" locale="zh-CN" range format="yyyy/MM/dd HH:mm:ss - yyy/MM/dd HH:mm:ss">
|
||||
</VueDatePicker>
|
||||
<button type="submit"
|
||||
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5"
|
||||
@click.prevent="handleSearch">搜索</button>
|
||||
</form>
|
||||
<!-- Create Modal toggle -->
|
||||
<Button :handleClick="() => handleUpsertUserClick(undefined)" :isLoading="false" :abortable="false"
|
||||
@@ -182,7 +186,7 @@ import useUserDelete from "@/composables/user/useUserDelete";
|
||||
import { useUserQuery } from "@/composables/user/useUserQuery";
|
||||
import { RouteName } from "@/router/constants";
|
||||
import type { UserUpsertSubmitModel } from "@/types/user";
|
||||
import dayjs from "@/utils/dateUtil";
|
||||
import { dayjs, formatDate } from "@/utils/dateUtil";
|
||||
import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
@@ -191,6 +195,7 @@ import useAlertStore from "../composables/store/useAlertStore";
|
||||
import { useUserUpsert } from "../composables/user/useUserUpsert";
|
||||
import { useActionExcStore } from "@/composables/store/useActionExcStore";
|
||||
|
||||
const dateRange = ref<Date[]>();
|
||||
const username = ref<string>("");
|
||||
const selectedUser = ref<components["schemas"]["UserRolePermissionDto"]>();
|
||||
const userUpsertModal = ref<ModalInterface>();
|
||||
@@ -243,6 +248,8 @@ const handleUpsertUserSubmit = async (data: UserUpsertSubmitModel) => {
|
||||
await fetchUsersWith(
|
||||
{
|
||||
username: username.value,
|
||||
startDate: formatDate(dateRange?.value?.[0]),
|
||||
endDate: formatDate(dateRange?.value?.[1]),
|
||||
},
|
||||
1,
|
||||
10,
|
||||
@@ -297,6 +304,8 @@ const handleSortClick = async (field: string) => {
|
||||
await fetchUsersWith(
|
||||
{
|
||||
username: username.value,
|
||||
startDate: formatDate(dateRange?.value?.[0]),
|
||||
endDate: formatDate(dateRange?.value?.[1]),
|
||||
},
|
||||
1,
|
||||
10,
|
||||
@@ -315,6 +324,8 @@ const handleDeleteUserSubmit = async () => {
|
||||
await fetchUsersWith(
|
||||
{
|
||||
username: username.value,
|
||||
startDate: formatDate(dateRange?.value?.[0]),
|
||||
endDate: formatDate(dateRange?.value?.[1]),
|
||||
},
|
||||
1,
|
||||
10,
|
||||
@@ -335,6 +346,8 @@ const handleSearch = async () => {
|
||||
await fetchUsersWith(
|
||||
{
|
||||
username: username.value,
|
||||
startDate: formatDate(dateRange?.value?.[0]),
|
||||
endDate: formatDate(dateRange?.value?.[1]),
|
||||
},
|
||||
1,
|
||||
10,
|
||||
@@ -346,6 +359,8 @@ const handlePageChange = async (page: number, pageSize: number) => {
|
||||
await fetchUsersWith(
|
||||
{
|
||||
username: username.value,
|
||||
startDate: formatDate(dateRange?.value?.[0]),
|
||||
endDate: formatDate(dateRange?.value?.[1]),
|
||||
},
|
||||
page,
|
||||
pageSize,
|
||||
|
||||
Reference in New Issue
Block a user