add date picker

This commit is contained in:
Chuck1sn
2025-06-13 14:36:47 +08:00
parent a00c3e129f
commit ac6c50ff28
14 changed files with 138 additions and 38 deletions

View File

@@ -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;
}

View File

@@ -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())

View File

@@ -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
);

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -8,4 +8,6 @@ import Alert from "./components/Alert.vue";
<Alert/>
</template>
<style scoped></style>
<style scoped>
</style>

View 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稍微小一点更美观 */
}

View File

@@ -21,6 +21,8 @@ export const useUserQuery = () => {
const fetchUsersWith = async (
param: {
username?: string;
startDate?: string;
endDate?: string;
},
page = 1,
size = 10,

View File

@@ -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");
});

View File

@@ -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 };

View File

@@ -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[]>([]);

View File

@@ -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";

View File

@@ -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,