feat: 完善参数页面的开发

This commit is contained in:
valarchie 2023-07-20 14:53:37 +08:00
parent 5aa607744b
commit 51c1deab44
6 changed files with 328 additions and 229 deletions

3
.gitignore vendored
View File

@ -12,6 +12,9 @@ npm-debug.log*
.pnpm-debug.log .pnpm-debug.log
tests/**/coverage/ tests/**/coverage/
# 本机调试debug配置文件
.vscode/launch.json
# Editor directories and files # Editor directories and files
.idea .idea
*.suo *.suo

76
src/api/system/config.ts Normal file
View File

@ -0,0 +1,76 @@
import { http } from "@/utils/http";
export interface ConfigQuery extends BasePageQuery {
/**
* key
*/
configKey?: string;
/**
*
*/
configName?: string;
/**
*
*/
isAllowChange?: string;
}
/**
* ConfigDTO,
*/
export interface ConfigDTO {
configId?: string;
configKey?: string;
configName?: string;
configOptions?: string[];
configValue?: string;
createTime?: Date;
isAllowChange?: string;
isAllowChangeStr?: string;
remark?: string;
}
/**
* ConfigUpdateCommand
*/
export interface UpdateConfigRequest {
configValue: string;
}
/** 获取配置列表 */
export const getConfigListApi = (params?: ConfigQuery) => {
return http.request<ResponseData<PageDTO<ConfigDTO>>>(
"get",
"/system/configs",
{
params
}
);
};
/** 获取配置信息 */
export const getConfigInfoApi = (configId: string) => {
return http.request<ResponseData<ConfigDTO>>(
"get",
`/system/config/${configId}`
);
};
/** 刷新配置缓存 */
export const updateConfigApi = (
configId: number,
data: UpdateConfigRequest
) => {
return http.request<ResponseData<PageDTO<ConfigDTO>>>(
"put",
`/system/config/${configId}`,
{
data
}
);
};
/** 刷新配置缓存 */
export const refreshConfigCacheApi = () => {
return http.request<ResponseData<void>>("delete", "/system/configs/cache");
};

View File

@ -0,0 +1,98 @@
<script setup lang="ts">
import { ref } from "vue";
import { useUserStoreHook } from "@/store/modules/user";
import { formRules } from "./utils/rule";
import { ConfigDTO } from "@/api/system/config";
interface FormProps<T> {
formInline: T;
}
/** TODO 有其他方式 来换掉这个props 父子组件传值吗? */
const props = withDefaults(defineProps<FormProps<ConfigDTO>>(), {
formInline: () => ({})
});
const formData = ref(props.formInline);
// TODO
const formRuleRef = ref();
function getFormRuleRef() {
return formRuleRef.value;
}
defineExpose({ getFormRuleRef });
</script>
<template>
<el-form
ref="formRuleRef"
:model="formData"
:rules="formRules"
label-width="82px"
>
<el-form-item label="参数名称" prop="configName">
<el-input
v-model="formData.configName"
clearable
placeholder="请输入参数名称"
:disabled="true"
/>
</el-form-item>
<el-form-item label="参数键名" prop="configKey">
<el-input
v-model="formData.configKey"
clearable
placeholder="请输入参数键名"
:disabled="true"
/>
</el-form-item>
<el-form-item label="参数值" prop="configValue">
<el-select
v-model="formData.configValue"
v-if="formData.configOptions.length > 0"
placeholder="请选择类型"
clearable
class="!w-[180px]"
>
<el-option
v-for="item in formData.configOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<el-input
v-else
v-model="formData.configValue"
placeholder="请输入参数键值"
/>
</el-form-item>
<el-form-item label="允许修改" prop="isAllowChange">
<el-select
v-model="formData.isAllowChange"
placeholder="请选择"
clearable
class="!w-[180px]"
:disabled="true"
>
<el-option
v-for="dict in useUserStoreHook().dictionaryList['common.yesOrNo']"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
placeholder="请输入内容"
:disabled="true"
/>
</el-form-item>
</el-form>
</template>

View File

@ -1,24 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { useLoginLogHook } from "./utils/hook"; import { useHook } from "./utils/hook";
import { PureTableBar } from "@/components/RePureTableBar"; import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Delete from "@iconify-icons/ep/delete"; import EditPen from "@iconify-icons/ep/edit-pen";
import Search from "@iconify-icons/ep/search"; import Search from "@iconify-icons/ep/search";
import Refresh from "@iconify-icons/ep/refresh"; import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
// TODO
import { CommonUtils } from "@/utils/common";
/** 组件name最好和菜单表中的router_name一致 */ /** !!!重要!!! 组件name最好和菜单表中的router_name一致, copy的时候记得更改这个名字*/
defineOptions({ defineOptions({
name: "SystemOperationLog" name: "SystemConfig"
}); });
const loginLogStatusList = const yesOrNoList = useUserStoreHook().dictionaryList["common.yesOrNo"];
useUserStoreHook().dictionaryList["sysLoginLog.status"];
const tableRef = ref(); const tableRef = ref();
const searchFormRef = ref(); const searchFormRef = ref();
@ -28,16 +25,12 @@ const {
columns, columns,
dataList, dataList,
pagination, pagination,
timeRange,
defaultSort,
multipleSelection,
onSearch, onSearch,
resetForm, resetForm,
exportAllExcel, openDialog,
getLoginLogList, handleRefresh,
handleDelete, getList
handleBulkDelete } = useHook();
} = useLoginLogHook();
</script> </script>
<template> <template>
@ -49,53 +42,38 @@ const {
:model="searchFormParams" :model="searchFormParams"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
> >
<el-form-item label="登录IP" prop="ipAddress"> <el-form-item label="参数名称:" prop="configName">
<el-input <el-input
v-model="searchFormParams.ipAddress" v-model="searchFormParams.configName"
placeholder="请输入IP地址" placeholder="请输入参数名称"
clearable clearable
class="!w-[200px]" class="!w-[200px]"
/> />
</el-form-item> </el-form-item>
<el-form-item label="用户名:" prop="username"> <el-form-item label="参数键名:" prop="configKey">
<el-input <el-input
v-model="searchFormParams.username" v-model="searchFormParams.configKey"
placeholder="请选择用户名称" placeholder="请输入参数键名"
clearable clearable
class="!w-[200px]" class="!w-[200px]"
/> />
</el-form-item> </el-form-item>
<el-form-item label="状态:" prop="status"> <el-form-item label="允许修改:" prop="isAllowChange">
<el-select <el-select
v-model="searchFormParams.status" v-model="searchFormParams.isAllowChange"
placeholder="请选择状态" placeholder="请选择"
clearable clearable
class="!w-[180px]" class="!w-[180px]"
> >
<el-option <el-option
v-for="dict in loginLogStatusList" v-for="dict in yesOrNoList"
:key="dict.value" :key="dict.value"
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item>
<label class="el-form-item__label is-required font-bold"
>登录时间</label
>
<!-- TODO 如何消除这个v-model的warning -->
<el-date-picker
class="!w-[240px]"
v-model="timeRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item> <el-form-item>
<el-button <el-button
type="primary" type="primary"
@ -115,24 +93,19 @@ const {
</el-form> </el-form>
<!-- table bar 包裹 table --> <!-- table bar 包裹 table -->
<PureTableBar title="登录日志列表" :columns="columns" @refresh="onSearch"> <PureTableBar title="通知列表" :columns="columns" @refresh="onSearch">
<!-- 表格操作栏 --> <!-- 表格操作栏 -->
<template #buttons> <template #buttons>
<el-button <el-button
type="danger" type="warning"
:icon="useRenderIcon(Delete)" :icon="useRenderIcon(AddFill)"
@click="handleBulkDelete(tableRef)" @click="handleRefresh()"
> >
批量删除 刷新缓存
</el-button> </el-button>
<el-button
type="primary"
@click="CommonUtils.exportExcel(columns, dataList, '登录日志列表')"
>单页导出</el-button
>
<el-button type="primary" @click="exportAllExcel">全部导出</el-button>
</template> </template>
<template v-slot="{ size, dynamicColumns }"> <template v-slot="{ size, dynamicColumns }">
<!-- TODO sort-change 有其他好的处理方式吗 -->
<pure-table <pure-table
border border
ref="tableRef" ref="tableRef"
@ -144,37 +117,26 @@ const {
adaptive adaptive
:data="dataList" :data="dataList"
:columns="dynamicColumns" :columns="dynamicColumns"
:default-sort="defaultSort"
:pagination="pagination" :pagination="pagination"
:paginationSmall="size === 'small' ? true : false" :paginationSmall="size === 'small' ? true : false"
:header-cell-style="{ :header-cell-style="{
background: 'var(--el-table-row-hover-bg-color)', background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)' color: 'var(--el-text-color-primary)'
}" }"
@page-size-change="getLoginLogList" @page-size-change="getList"
@page-current-change="getLoginLogList" @page-current-change="getList"
@sort-change="getLoginLogList"
@selection-change="
rows => (multipleSelection = rows.map(item => item.logId))
"
> >
<template #operation="{ row }"> <template #operation="{ row }">
<el-popconfirm <el-button
:title="`是否确认删除编号为${row.logId}的这条日志`" class="reset-margin"
@confirm="handleDelete(row)" link
type="primary"
:size="size"
:icon="useRenderIcon(EditPen)"
@click="openDialog(row)"
> >
<template #reference> 修改
<el-button </el-button>
class="reset-margin"
link
type="danger"
:size="size"
:icon="useRenderIcon(Delete)"
>
删除
</el-button>
</template>
</el-popconfirm>
</template> </template>
</pure-table> </pure-table>
</template> </template>

View File

@ -1,26 +1,21 @@
import dayjs from "dayjs"; import editForm from "../form.vue";
import { message } from "@/utils/message"; import { message } from "@/utils/message";
import { ElMessageBox, Sort } from "element-plus"; import { addDialog } from "@/components/ReDialog";
import { type PaginationProps } from "@pureadmin/table";
import { import {
getLoginLogListApi, getConfigListApi,
deleteLoginLogApi, getConfigInfoApi,
exportLoginLogExcelApi, updateConfigApi,
LoginLogQuery refreshConfigCacheApi,
} from "@/api/system/log"; ConfigQuery,
import { reactive, ref, onMounted, toRaw } from "vue"; ConfigDTO,
import { useUserStoreHook } from "@/store/modules/user"; UpdateConfigRequest
} from "@/api/system/config";
import { reactive, ref, onMounted, h, toRaw } from "vue";
import { CommonUtils } from "@/utils/common"; import { CommonUtils } from "@/utils/common";
import { PaginationProps } from "@pureadmin/table";
const loginLogStatusMap =
useUserStoreHook().dictionaryMap["sysLoginLog.status"];
export function useLoginLogHook() {
const defaultSort: Sort = {
prop: "loginTime",
order: "descending"
};
export function useHook() {
const pagination: PaginationProps = { const pagination: PaginationProps = {
total: 0, total: 0,
pageSize: 10, pageSize: 10,
@ -28,190 +23,150 @@ export function useLoginLogHook() {
background: true background: true
}; };
const timeRange = ref([]); const searchFormParams = reactive<ConfigQuery>({
configKey: undefined,
const searchFormParams = reactive<LoginLogQuery>({ configName: undefined,
ipAddress: undefined, isAllowChange: undefined
username: undefined,
status: undefined,
beginTime: undefined,
endTime: undefined,
timeRangeColumn: defaultSort.prop
}); });
const formRef = ref();
const dataList = ref([]); const dataList = ref([]);
const pageLoading = ref(true); const pageLoading = ref(true);
const multipleSelection = ref([]); const multipleSelection = ref([]);
const columns: TableColumnList = [ const columns: TableColumnList = [
{ {
type: "selection", label: "参数编号",
align: "left" prop: "configId",
minWidth: 60
}, },
{ {
label: "日志编号", label: "参数名称",
prop: "logId", prop: "configName",
minWidth: 120
},
{
label: "参数键",
prop: "configKey",
minWidth: 120,
showOverflowTooltip: true
},
{
label: "参数值",
prop: "configValue",
minWidth: 150
},
{
label: "参数选项",
prop: "configOptions",
minWidth: 120
},
{
label: "允许更改",
prop: "isAllowChangeStr",
minWidth: 100 minWidth: 100
}, },
{ {
label: "用户名", label: "备注",
prop: "username", prop: "remark",
minWidth: 120, minWidth: 120,
sortable: "custom" showOverflowTooltip: true
},
{
label: "IP地址",
prop: "ipAddress",
minWidth: 120
},
{
label: "登录地点",
prop: "loginLocation",
minWidth: 120
},
{
label: "操作系统",
prop: "operationSystem",
minWidth: 120
},
{
label: "浏览器",
prop: "browser",
minWidth: 120
},
{
label: "状态",
prop: "status",
minWidth: 120,
cellRenderer: ({ row, props }) => (
<el-tag
size={props.size}
type={loginLogStatusMap[row.status].cssTag}
effect="plain"
>
{loginLogStatusMap[row.status].label}
</el-tag>
)
},
{
label: "状态名",
prop: "statusStr",
minWidth: 120,
hide: true
},
{
label: "登录时间",
minWidth: 160,
prop: "loginTime",
sortable: "custom",
formatter: ({ loginTime }) =>
dayjs(loginTime).format("YYYY-MM-DD HH:mm:ss")
}, },
{ {
label: "操作", label: "操作",
fixed: "right", fixed: "right",
width: 140, width: 120,
slot: "operation" slot: "operation"
} }
]; ];
async function onSearch() { function onSearch() {
// 点击搜索的时候 需要重置分页 // 点击搜索的时候 需要重置分页
pagination.currentPage = 1; pagination.currentPage = 1;
getLoginLogList();
getList();
} }
function resetForm(formEl, tableRef) { function resetForm(formEl, tableRef) {
if (!formEl) return; if (!formEl) return;
// 清空查询参数 // 清空查询参数
formEl.resetFields(); formEl.resetFields();
// 清空排序
searchFormParams.orderColumn = undefined;
searchFormParams.orderDirection = undefined;
// 清空时间查询 TODO 这块有点繁琐 有可以优化的地方吗?
// Form组件的resetFields方法无法清除datepicker里面的数据。
timeRange.value = [];
searchFormParams.beginTime = undefined;
searchFormParams.endTime = undefined;
tableRef.getTableRef().clearSort(); tableRef.getTableRef().clearSort();
// 重置分页并查询 // 重置分页并查询
onSearch(); onSearch();
} }
async function getLoginLogList(sort: Sort = defaultSort) { async function getList() {
pageLoading.value = true;
if (sort != null) {
CommonUtils.fillSortParams(searchFormParams, sort);
}
CommonUtils.fillPaginationParams(searchFormParams, pagination); CommonUtils.fillPaginationParams(searchFormParams, pagination);
CommonUtils.fillTimeRangeParams(searchFormParams, timeRange.value);
const { data } = await getLoginLogListApi(toRaw(searchFormParams)).finally( pageLoading.value = true;
const { data } = await getConfigListApi(toRaw(searchFormParams)).finally(
() => { () => {
pageLoading.value = false; pageLoading.value = false;
} }
); );
dataList.value = data.rows; dataList.value = data.rows;
pagination.total = data.total; pagination.total = data.total;
} }
async function exportAllExcel(sort: Sort = defaultSort) { async function handleRefresh() {
if (sort != null) { await refreshConfigCacheApi().then(() => {
CommonUtils.fillSortParams(searchFormParams, sort); message("刷新缓存成功", {
}
CommonUtils.fillPaginationParams(searchFormParams, pagination);
CommonUtils.fillTimeRangeParams(searchFormParams, timeRange.value);
exportLoginLogExcelApi(toRaw(searchFormParams), "登录日志.xls");
}
async function handleDelete(row) {
await deleteLoginLogApi([row.logId]).then(() => {
message(`您删除了操作编号为${row.logId}的这条数据`, {
type: "success" type: "success"
}); });
// 刷新列表 // 刷新列表
getLoginLogList(); getList();
}); });
} }
async function handleBulkDelete(tableRef) { async function handleUpdate(curData, done) {
if (multipleSelection.value.length === 0) { const request: UpdateConfigRequest = {
message("请选择需要删除的数据", { type: "warning" }); configValue: curData.configValue
return; };
} console.log("curData");
console.log(curData);
ElMessageBox.confirm( await updateConfigApi(curData.configId, request).then(() => {
`确认要<strong>删除</strong>编号为<strong style='color:var(--el-color-primary)'>[ ${multipleSelection.value} ]</strong>的日志吗?`, message(`您成功修改了配置:${curData.configName}`, {
"系统提示", type: "success"
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
dangerouslyUseHTMLString: true,
draggable: true
}
)
.then(async () => {
await deleteLoginLogApi(multipleSelection.value).then(() => {
message(`您删除了日志编号为[ ${multipleSelection.value} ]的数据`, {
type: "success"
});
// 刷新列表
getLoginLogList();
});
})
.catch(() => {
message("取消删除", {
type: "info"
});
// 清空checkbox选择的数据
tableRef.getTableRef().clearSelection();
}); });
// 关闭弹框
done();
// 刷新列表
getList();
});
}
async function openDialog(row?: ConfigDTO) {
const { data } = await getConfigInfoApi(row.configId);
addDialog({
title: `修改配置`,
props: {
formInline: data
},
width: "40%",
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(editForm, { ref: formRef }),
beforeSure: (done, { options }) => {
const formRuleRef = formRef.value.getFormRuleRef();
const curData = options.props.formInline;
formRuleRef.validate(valid => {
if (valid) {
handleUpdate(curData, done);
}
});
}
});
} }
onMounted(() => { onMounted(() => {
getLoginLogList(); getList();
}); });
return { return {
@ -220,15 +175,11 @@ export function useLoginLogHook() {
columns, columns,
dataList, dataList,
pagination, pagination,
defaultSort,
timeRange,
multipleSelection, multipleSelection,
getList,
onSearch, onSearch,
exportAllExcel,
// exportExcel,
getLoginLogList,
resetForm, resetForm,
handleDelete, handleRefresh,
handleBulkDelete openDialog
}; };
} }

View File

@ -0,0 +1,9 @@
import { reactive } from "vue";
import type { FormRules } from "element-plus";
/** 自定义表单规则校验 */
export const formRules = reactive(<FormRules>{
configValue: [
{ required: true, message: "角色名称为必填项", trigger: "blur" }
]
});