refactor: ♻️ 日期范围查询组件封装避免日期的转换,用户和日志模块优化

This commit is contained in:
ray
2024-11-25 23:28:36 +08:00
parent 4b757bcfc7
commit 9beb7d4015
7 changed files with 214 additions and 304 deletions

View File

@@ -2,19 +2,19 @@ import request from "@/utils/request";
const LOG_BASE_URL = "/api/v1/logs"; const LOG_BASE_URL = "/api/v1/logs";
class LogAPI { const LogAPI = {
/** /**
* 获取日志分页列表 * 获取日志分页列表
* *
* @param queryParams 查询参数 * @param queryParams 查询参数
*/ */
static getPage(queryParams: LogPageQuery) { getPage(queryParams: LogPageQuery) {
return request<PageResult<LogPageVO[]>>({ return request<PageResult<LogVO[]>>({
url: `${LOG_BASE_URL}/page`, url: `${LOG_BASE_URL}/page`,
method: "GET", method: "GET",
data: queryParams, data: queryParams,
}); });
} },
/** /**
* 获取访问趋势 * 获取访问趋势
@@ -22,13 +22,13 @@ class LogAPI {
* @param queryParams * @param queryParams
* @returns * @returns
*/ */
static getVisitTrend(queryParams: VisitTrendQuery) { getVisitTrend(queryParams: VisitTrendQuery) {
return request<VisitTrendVO>({ return request<VisitTrendVO>({
url: `${LOG_BASE_URL}/visit-trend`, url: `${LOG_BASE_URL}/visit-trend`,
method: "GET", method: "GET",
data: queryParams, data: queryParams,
}); });
} },
/** /**
* 获取访问趋势 * 获取访问趋势
@@ -36,13 +36,13 @@ class LogAPI {
* @param queryParams * @param queryParams
* @returns * @returns
*/ */
static getVisitStats() { getVisitStats() {
return request<VisitStatsVO[]>({ return request<VisitStatsVO[]>({
url: `${LOG_BASE_URL}/visit-stats`, url: `${LOG_BASE_URL}/visit-stats`,
method: "GET", method: "GET",
}); });
} },
} };
export default LogAPI; export default LogAPI;
@@ -51,15 +51,15 @@ export default LogAPI;
*/ */
export interface LogPageQuery extends PageQuery { export interface LogPageQuery extends PageQuery {
/** 搜索关键字 */ /** 搜索关键字 */
keywords: string; keywords?: string;
/** 操作时间 */ /** 操作时间 */
createTime: [string, string]; createTime?: [string, string] | string;
} }
/** /**
* 系统日志分页VO * 系统日志分页VO
*/ */
export interface LogPageVO { export interface LogVO {
/** 主键 */ /** 主键 */
id?: number; id?: number;
/** 日志模块 */ /** 日志模块 */

View File

@@ -173,7 +173,7 @@ export interface UserPageQuery extends PageQuery {
deptId?: number; deptId?: number;
/** 开始时间 */ /** 开始时间 */
createTime?: [string, string] | string | undefined; createTime?: [string, string] | string;
/** 排序字段 */ /** 排序字段 */
field?: string; field?: string;

View File

@@ -0,0 +1,69 @@
<template>
<wd-calendar
v-model="dateRange"
:label="label"
type="daterange"
:placeholder="placeholder"
@confirm="handleConfirm"
/>
</template>
<script lang="ts" setup>
import { dayjs } from "wot-design-uni";
const props = defineProps({
modelValue: {
type: [Array, String] as PropType<[string, string] | string | undefined>,
default: () => undefined,
},
placeholder: {
type: String,
default: "请选择时间范围",
},
label: {
type: String,
default: "",
},
});
const emit = defineEmits(["update:modelValue"]);
const dateRange = ref<number[] | number | null>(null);
watch(
() => props.modelValue,
(val) => {
if (Array.isArray(val) && val.length === 2 && val[0] && val[1]) {
dateRange.value = val.map((item) => new Date(item).getTime());
} else {
dateRange.value = null;
}
},
{
immediate: true,
}
);
// 确认选择时间
const handleConfirm = () => {
if (Array.isArray(dateRange.value) && dateRange.value.length === 2) {
const startDate = dayjs(dateRange.value[0]).format("YYYY-MM-DD");
const endDate = dayjs(dateRange.value[1]).format("YYYY-MM-DD");
let newVal: any = [startDate, endDate];
// #ifdef MP-WEIXIN
newVal = `${startDate},${endDate}`;
// #endif
console.log("newVal", newVal);
emit("update:modelValue", newVal);
}
};
</script>
<style scoped>
.time-filter {
padding: 16rpx;
}
</style>

View File

@@ -1,8 +1,8 @@
<template> <template>
<view class="log"> <view class="log">
<!-- 筛选 --> <!-- 筛选 -->
<wd-drop-menu close-on-click-modal class="mb-20rpx"> <wd-drop-menu close-on-click-modal class="mb-24rpx">
<wd-drop-menu-item ref="dropMenu" title="筛选" icon="filter" icon-size="18px"> <wd-drop-menu-item ref="filterDropMenu" title="筛选" icon="filter" icon-size="18px">
<view> <view>
<wd-input <wd-input
v-model="queryParams.keywords" v-model="queryParams.keywords"
@@ -10,242 +10,148 @@
type="text" type="text"
placeholder="请输入关键字" placeholder="请输入关键字"
/> />
<wd-calendar
v-model="timeStampArray" <cu-date-query v-model="queryParams.createTime" :label="'日期选择'" />
label="日期选择"
type="daterange" <view class="flex-between py-2">
allow-same-day <wd-button class="w-20%" type="info" @click="handleResetQuery">重置</wd-button>
@confirm="handleConfirm" <wd-button class="w-70%" @click="handleQuery">查询</wd-button>
/>
<view class="flex-between mb-20rpx">
<wd-button class="mt-20rpx mb-20rpx" size="medium" @click="handleSearch">
查询
</wd-button>
<wd-button size="medium" type="info" @click="handleReset">重置</wd-button>
</view> </view>
</view> </view>
</wd-drop-menu-item> </wd-drop-menu-item>
</wd-drop-menu> </wd-drop-menu>
<!-- 卡片列表 --> <!-- 卡片列表 -->
<view v-for="(item, index) in pageData" :key="index"> <wd-card v-for="(item, index) in pageData" class="card-list">
<wd-card :title="item.operator"> <template #title>
<wd-row> {{ item.operator }}
<wd-col v-for="(rowItem, rowIndex) in listPageTypeArray" :key="rowIndex" :span="12"> </template>
<wd-col :span="rowItem.titleSpan">
<view>{{ rowItem.title }}</view>
</wd-col>
<wd-col :span="rowItem.keySpan">
<view>{{ item[rowItem.key] }}</view>
</wd-col>
</wd-col>
</wd-row>
<template #footer>
<wd-button size="small" plain @click="getDetail(item)">查看详情</wd-button>
</template>
</wd-card>
</view>
<!-- 查看详情 --> <wd-cell-group>
<wd-action-sheet v-model="detailShow"> <wd-cell title="模块" :value="item.module" />
<view class="p-50rpx"> <wd-cell title="内容" :value="item.content" />
<wd-row v-for="(rowItem, rowIndex) in detailTypeArray" :key="rowIndex" class="mt-20rpx"> <wd-cell title="IP" :value="item.ip" />
<wd-col :span="rowItem.titleSpan"> <wd-cell title="地区" :value="item.region" />
<view>{{ rowItem.title }}</view> </wd-cell-group>
</wd-col>
<wd-col :span="rowItem.keySpan"> <template #footer>
<view>{{ logDetail[rowItem.key] }}</view> <view class="flex-between">
</wd-col> <view class="text-left">
</wd-row> <wd-text text="创建时间:" size="small" class="font-bold" />
</view> <wd-text :text="item.createTime" size="small" />
</wd-action-sheet> </view>
<view class="text-right">
<wd-button type="primary" size="small" plain @click="handleViewDetail(item)">
查看详情
</wd-button>
</view>
</view>
</template>
</wd-card>
<!-- 详情弹窗 -->
<wd-popup v-model="detailDialogVisible" position="bottom">
<wd-cell-group>
<wd-cell title="操作人" :value="logDetail.operator" />
<wd-cell title="操作时间" :value="logDetail.createTime" />
<wd-cell title="模块" :value="logDetail.module" />
<wd-cell title="内容" :value="logDetail.content" />
<wd-cell title="IP" :value="logDetail.ip" />
<wd-cell title="地区" :value="logDetail.region" />
<wd-cell title="浏览器" :value="logDetail.region" />
<wd-cell title="终端系统" :value="logDetail.os" />
<wd-cell title="耗时(毫秒)" :value="logDetail.executionTime" />
</wd-cell-group>
</wd-popup>
<!-- 加载更多 --> <!-- 加载更多 -->
<wd-loadmore custom-class="loadmore" :state="loadMoreState" /> <wd-loadmore v-if="total > 0" :state="loadMoreState" @reload="loadmore" />
<wd-status-tip v-else-if="total == 0" image="search" tip="当前搜索无结果" />
</view> </view>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import LogAPI, { LogPageVO, LogPageQuery } from "@/api/system/log";
import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types"; import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types";
import { DropMenuItemExpose } from "wot-design-uni/components/wd-drop-menu-item/types"; import { DropMenuItemExpose } from "wot-design-uni/components/wd-drop-menu-item/types";
import { dayjs } from "wot-design-uni";
import LogAPI, { LogVO, LogPageQuery } from "@/api/system/log";
import CuDateQuery from "@/components/cu-date-query/index.vue";
const filterDropMenu = ref<DropMenuItemExpose>();
const loadMoreState = ref<LoadMoreState>("loading"); const loadMoreState = ref<LoadMoreState>("loading");
const queryParams = reactive<LogPageQuery>({ const queryParams = reactive<LogPageQuery>({
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
keywords: "",
createTime: ["", ""],
}); });
const timeStampArray = ref<number[]>([]); const total = ref(0);
const pageData = ref<LogVO[]>([]);
const logDetail = ref<LogVO>({});
const detailDialogVisible = ref(false);
/**
* 日期格式化
*/
function handleConfirm({ value }: Ref<number[]>) {
queryParams.createTime[0] = dayjs(value[0]).format("YYYY-MM-DD");
queryParams.createTime[1] = dayjs(value[1]).format("YYYY-MM-DD");
}
/** /**
* 搜索栏 * 搜索栏
*/ */
const dropMenu = ref<DropMenuItemExpose>();
function handleSearch() {
pageData.value = [];
dropMenu.value?.close();
handleQuery();
}
/**
* 重置搜索条件
*/
function handleReset() {
queryParams.pageNum = 1;
queryParams.keywords = "";
queryParams.createTime = ["", ""];
timeStampArray.value = [];
pageData.value = [];
dropMenu.value?.close();
handleQuery();
}
// 日志卡片列表数据
const pageData = ref<LogPageVO[]>([]);
onLoad(() => {
handleQuery();
});
/**
* 查询
*/
function handleQuery() { function handleQuery() {
filterDropMenu.value?.close();
queryParams.pageNum = 1;
loadmore();
}
/**
* 重置搜索
*/
function handleResetQuery() {
queryParams.keywords = undefined;
queryParams.createTime = undefined;
handleQuery();
}
/**
* 加载更多
*/
function loadmore() {
loadMoreState.value = "loading"; loadMoreState.value = "loading";
LogAPI.getPage(queryParams) LogAPI.getPage(queryParams)
.then((data) => { .then((data) => {
pageData.value?.push(...data.list); pageData.value = data.list;
total.value = data.total;
queryParams.pageNum++;
}) })
.catch((e) => { .catch((e) => {
console.log("系统异常", e); pageData.value = [];
}) })
.finally(() => { .finally(() => {
loadMoreState.value = "finished"; loadMoreState.value = "finished";
}); });
} }
/**
* 查看详情
*/
function handleViewDetail(item: LogVO) {
detailDialogVisible.value = true;
logDetail.value = item;
}
/** /**
* 触底事件 * 触底事件
*/ */
onReachBottom(() => { onReachBottom(() => {
queryParams.pageNum++; if (queryParams.pageNum * queryParams.pageSize < total.value) {
handleQuery(); loadmore();
} else if (queryParams.pageNum * queryParams.pageSize >= total.value) {
loadMoreState.value = "finished";
}
}); });
/** onLoad(() => {
* 卡片列表展示字段 handleQuery();
*/ });
interface listPageType {
title: string;
key: keyof LogPageVO;
titleSpan: Number;
keySpan: Number;
}
const listPageTypeArray = ref<listPageType[]>([
{
title: "模块",
key: "module",
titleSpan: 8,
keySpan: 16,
},
{
title: "内容",
key: "content",
titleSpan: 8,
keySpan: 16,
},
{
title: "时间",
key: "createTime",
titleSpan: 8,
keySpan: 16,
},
{
title: "地区",
key: "region",
titleSpan: 8,
keySpan: 16,
},
]);
/**
* 卡片内容详情
*/
const logDetail = ref<LogPageVO>({});
const detailShow = ref<boolean>(false);
const detailTypeArray = ref<listPageType[]>([
{
title: "模块",
key: "module",
titleSpan: 6,
keySpan: 18,
},
{
title: "内容",
key: "content",
titleSpan: 6,
keySpan: 18,
},
{
title: "时间",
key: "createTime",
titleSpan: 6,
keySpan: 18,
},
{
title: "地区",
key: "region",
titleSpan: 6,
keySpan: 18,
},
{
title: "IP",
key: "ip",
titleSpan: 6,
keySpan: 18,
},
{
title: "浏览器",
key: "browser",
titleSpan: 6,
keySpan: 18,
},
{
title: "终端系统",
key: "os",
titleSpan: 6,
keySpan: 18,
},
{
title: "执行时间(ms)",
key: "executionTime",
titleSpan: 8,
keySpan: 16,
},
]);
function getDetail(item: LogPageVO) {
detailShow.value = true;
logDetail.value = item;
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.log {
background: #f8f8f8;
}
.wd-col { .wd-col {
margin-top: 10rpx; margin-top: 10rpx;
} }
@@ -255,4 +161,14 @@ function getDetail(item: LogPageVO) {
justify-content: flex-end; justify-content: flex-end;
padding: 0 50rpx; padding: 0 50rpx;
} }
.card-list {
:deep(.wd-cell__wrapper) {
padding: 4rpx 0;
}
:deep(.wd-cell) {
padding-right: 10rpx;
background: #f8f8f8;
}
}
</style> </style>

View File

@@ -23,11 +23,11 @@
placeholder="用户名/昵称/手机号" placeholder="用户名/昵称/手机号"
/> />
<wd-calendar v-model="createTimeRange" label="创建时间" type="daterange" /> <cu-date-query v-model="queryParams.createTime" label="创建时间" />
<view class="flex-between py-2"> <view class="flex-between py-2">
<wd-button class="w-20%" type="info" @click="hendleResetQuery">重置</wd-button> <wd-button class="w-20%" type="info" @click="hendleResetQuery">重置</wd-button>
<wd-button class="w-70%" @click="handleQuery">确定</wd-button> <wd-button class="w-70%" @click="handleQuery">查询</wd-button>
</view> </view>
</view> </view>
</wd-drop-menu-item> </wd-drop-menu-item>
@@ -35,7 +35,7 @@
</view> </view>
<!-- 用户卡片 --> <!-- 用户卡片 -->
<wd-card v-for="item in dataList"> <wd-card v-for="item in pageData">
<template #title> <template #title>
<view class="flex-between"> <view class="flex-between">
<view class="flex-center"> <view class="flex-center">
@@ -82,7 +82,7 @@
</template> </template>
</wd-card> </wd-card>
<wd-loadmore v-if="total > 0" :state="state" @reload="loadmore" /> <wd-loadmore v-if="total > 0" :state="loadMoreState" @reload="loadmore" />
<wd-status-tip v-else-if="total == 0" image="search" tip="当前搜索无结果" /> <wd-status-tip v-else-if="total == 0" image="search" tip="当前搜索无结果" />
<wd-message-box /> <wd-message-box />
@@ -128,19 +128,19 @@
<script lang="ts" setup> <script lang="ts" setup>
import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types"; import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types";
import { FormRules } from "wot-design-uni/components/wd-form/types"; import { FormRules } from "wot-design-uni/components/wd-form/types";
import { useMessage, dayjs } from "wot-design-uni"; import { useMessage } from "wot-design-uni";
import UserAPI, { type UserPageQuery, UserPageVO, UserForm } from "@/api/system/user"; import UserAPI, { type UserPageQuery, UserPageVO, UserForm } from "@/api/system/user";
import RoleAPI from "@/api/system/role"; import RoleAPI from "@/api/system/role";
import DeptAPI from "@/api/system/dept"; import DeptAPI from "@/api/system/dept";
import CuPicker from "@/components/CuPicker.vue"; import CuPicker from "@/components/cu-picker/index.vue";
import CuDateQuery from "@/components/cu-date-query/index.vue";
const message = useMessage(); const message = useMessage();
const loadMoreState = ref<LoadMoreState>("loading");
const loading = ref(false); const filterDropMenu = ref();
const state = ref<LoadMoreState>("loading"); const userFormRef = ref();
const dataList = ref<UserPageVO[]>([]);
const sortValue = ref(0); const sortValue = ref(0);
const sortOptions = ref<Record<string, any>[]>([ const sortOptions = ref<Record<string, any>[]>([
@@ -154,11 +154,9 @@ const queryParams: UserPageQuery = {
pageSize: 10, pageSize: 10,
}; };
const createTimeRange = ref<any[]>([null, null]);
const filterDropMenu = ref();
const total = ref(0); const total = ref(0);
const pageData = ref<UserPageVO[]>([]);
const dialog = reactive({ const dialog = reactive({
visible: false, visible: false,
}); });
@@ -176,10 +174,8 @@ const initialFormData: UserForm = {
const formData = reactive<UserForm>({ ...initialFormData }); const formData = reactive<UserForm>({ ...initialFormData });
const userFormRef = ref();
const roleOptions = ref<Record<string, any>[]>([]); const roleOptions = ref<Record<string, any>[]>([]);
const deptOptions = ref<OptionType[]>([]); const deptOptions = ref<OptionType[]>([]);
const rules: FormRules = { const rules: FormRules = {
username: [{ required: true, message: "请输入用户名" }], username: [{ required: true, message: "请输入用户名" }],
nickname: [{ required: true, message: "请输入昵称" }], nickname: [{ required: true, message: "请输入昵称" }],
@@ -243,21 +239,6 @@ const handleSortChange = ({ value }: { value: number }) => {
const handleQuery = () => { const handleQuery = () => {
filterDropMenu.value?.close(); filterDropMenu.value?.close();
queryParams.pageNum = 1; queryParams.pageNum = 1;
// 格式化时间范围
const startDate = createTimeRange.value[0]
? dayjs(createTimeRange.value[0]).format("YYYY-MM-DD")
: "";
const endDate = createTimeRange.value[1]
? dayjs(createTimeRange.value[1]).format("YYYY-MM-DD")
: "";
queryParams.createTime = [startDate, endDate];
// #ifdef MP-WEIXIN
queryParams.createTime = `${startDate},${endDate}`;
// #endif
loadmore(); loadmore();
}; };
@@ -265,27 +246,27 @@ const handleQuery = () => {
* 重置查询 * 重置查询
*/ */
const hendleResetQuery = () => { const hendleResetQuery = () => {
filterDropMenu.value?.close(); queryParams.keywords = undefined;
queryParams.pageNum = 1; queryParams.createTime = undefined;
queryParams.keywords = ""; handleQuery();
queryParams.createTime = "";
createTimeRange.value = ["", ""];
loadmore();
}; };
/** /**
* 加载更多 * 加载更多
*/ */
function loadmore() { function loadmore() {
state.value = "loading"; loadMoreState.value = "loading";
UserAPI.getPage(queryParams) UserAPI.getPage(queryParams)
.then((data) => { .then((data) => {
dataList.value = data.list; pageData.value = data.list;
total.value = data.total; total.value = data.total;
queryParams.pageNum++; queryParams.pageNum++;
}) })
.catch((e) => {
pageData.value = [];
})
.finally(() => { .finally(() => {
state.value = "finished"; loadMoreState.value = "finished";
}); });
} }
@@ -293,20 +274,13 @@ function loadmore() {
* 打开弹窗 * 打开弹窗
*/ */
async function handleOpenDialog(id?: number) { async function handleOpenDialog(id?: number) {
loading.value = true;
dialog.visible = true; dialog.visible = true;
roleOptions.value = await RoleAPI.getOptions(); roleOptions.value = await RoleAPI.getOptions();
deptOptions.value = await DeptAPI.getOptions(); deptOptions.value = await DeptAPI.getOptions();
if (id) { if (id) {
UserAPI.getFormData(id) UserAPI.getFormData(id).then((data) => {
.then((data) => { Object.assign(formData, { ...data });
Object.assign(formData, { ...data }); });
})
.finally(() => {
loading.value = false;
});
} else {
loading.value = false;
} }
} }
@@ -378,7 +352,7 @@ onReachBottom(() => {
if (queryParams.pageNum * queryParams.pageSize < total.value) { if (queryParams.pageNum * queryParams.pageSize < total.value) {
loadmore(); loadmore();
} else if (queryParams.pageNum * queryParams.pageSize >= total.value) { } else if (queryParams.pageNum * queryParams.pageSize >= total.value) {
state.value = "finished"; loadMoreState.value = "finished";
} }
}); });

View File

@@ -16,7 +16,6 @@ export default function request<T>(options: UniApp.RequestOptions): Promise<T> {
...options.header, ...options.header,
Authorization: getToken() ? `Bearer ${getToken()}` : "", Authorization: getToken() ? `Bearer ${getToken()}` : "",
}, },
data: handleData(options.data, options.method || "GET"),
success: (response) => { success: (response) => {
console.log("success response", response); console.log("success response", response);
const resData = response.data as ResponseData<T>; const resData = response.data as ResponseData<T>;
@@ -60,51 +59,3 @@ export default function request<T>(options: UniApp.RequestOptions): Promise<T> {
}); });
}); });
} }
/**
* 处理请求数据
* @param data 请求数据
* @returns 处理后的数据
*/
function handleData(data: any, method: string) {
// 非微信小程序且非GET请求则不处理数据
const appInfo = uni.getAppBaseInfo();
if (method !== "GET" && appInfo.hostName !== "WeChat") {
return data;
}
if (!data) return data;
// 如果是对象,遍历处理每个属性
if (typeof data === "object") {
const result: Record<string, any> = {};
for (const key in data) {
const value = data[key];
if (Array.isArray(value)) {
let res = handleArray(value);
if (res) {
result[key] = res;
}
} else {
result[key] = value;
}
}
return result;
}
return data;
}
/**
* 处理数组
* @param value 数组
* @returns 逗号分隔字符串
*/
function handleArray(value: any[]) {
let str = "";
for (const item of value) {
if (item != 0 && item) {
str += `${item},`;
}
}
return str;
}