feat: 操作日志添加按钮级权限管理

This commit is contained in:
2025-02-22 23:27:31 +08:00
parent f89c42ef61
commit 73856ad1cb
4 changed files with 262 additions and 77 deletions

View File

@@ -69,22 +69,57 @@ export const deleteUserLoginLogListAPI = (data: { ids: string[] }) => {
// ------------------------操作日志相关---------------------------------------- // ------------------------操作日志相关----------------------------------------
/**获取操作日志参数 */
type GetUserOperationParams = {
/**页码 */
page: number;
/**每页数量 */
pageSize: number;
/**用户账号 */
username?: string;
/**用户昵称 */
nickname?: string;
/**部门ID */
department_id?: string;
/**操作名称 */
name?: string;
/**操作状态 */
status?: number | string;
/**操作类型 */
type?: number | string;
/**开始时间 */
startTime?: string | number;
/**结束时间 */
endTime?: string | number;
};
/** /**
* 获取用户操作日志 * 获取用户操作日志
*/ */
export const getUserOperationsAPI = (params: { export const getUserOperationsAPI = (params: GetUserOperationParams) => {
page: number;
pageSize: number;
}) => {
return http.request<QueryListResult<OperationLogInfo>>( return http.request<QueryListResult<OperationLogInfo>>(
"GET", "GET",
"/api/log/operation", "/api/log/operation",
{ {
params params: filterEmptyObject(params)
} }
); );
}; };
/**
* 删除操作日志
* @returns
*/
export const deleteUserOperationsAPI = (id: string) => {
return http.request<null>("delete", `/api/log/delete/operation/${id}`);
};
/**批量删除操作日志 */
export const deleteUserOperationsListAPI = (data: { ids: string[] }) => {
return http.request<null>("delete", `/api/log/deleteList/operation`, {
data
});
};
// ------------------------服务相关---------------------------------------- // ------------------------服务相关----------------------------------------
/** /**

View File

@@ -108,7 +108,7 @@ export const useLogin = (tableRef: Ref) => {
prop: "online", prop: "online",
minWidth: 100, minWidth: 100,
cellRenderer: ({ row, props }) => ( cellRenderer: ({ row, props }) => (
<el-tag size={props.size} type={row.online ? "success" : "primary"}> <el-tag size={props.size} type={row.online ? "success" : ""}>
{row.online ? "在线" : "离线"} {row.online ? "在线" : "离线"}
</el-tag> </el-tag>
) )

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ref, reactive, computed } from "vue"; import { ref, reactive, computed } from "vue";
import { useOperation } from "./hook"; import { useOperation } from "./utils/hook";
import { getPickerShortcuts } from "../utils"; import { getPickerShortcuts } from "../utils";
import { PureTableBar } from "@/components/RePureTableBar"; import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
@@ -12,7 +12,7 @@ import View from "@iconify-icons/ep/view";
import Delete from "@iconify-icons/ep/delete"; import Delete from "@iconify-icons/ep/delete";
import Refresh from "@iconify-icons/ep/refresh"; import Refresh from "@iconify-icons/ep/refresh";
import { OperationLogInfo } from "types/monitor"; import { OperationLogInfo } from "types/monitor";
import { hasAuth } from "@/utils/auth";
defineOptions({ defineOptions({
name: "OperationLog" name: "OperationLog"
}); });
@@ -86,11 +86,12 @@ const {
dataList, dataList,
pagination, pagination,
selectedNum, selectedNum,
departments,
onSearch, onSearch,
clearAll,
resetForm, resetForm,
onbatchDel, onbatchDel,
getOperationName, getOperationName,
handleDelete,
handleSizeChange, handleSizeChange,
onSelectionCancel, onSelectionCancel,
handleCurrentChange, handleCurrentChange,
@@ -106,33 +107,89 @@ const {
:model="form" :model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
> >
<el-form-item label="所属模块" prop="module"> <el-form-item label="操作名称" prop="name">
<el-input <el-input
v-model="form.module" v-model="form.name"
placeholder="请输入所属模块" placeholder="请输入操作名称"
clearable clearable
class="!w-[170px]" class="!w-[200px]"
/>
</el-form-item>
<el-form-item label="操作类型" prop="type">
<el-select
v-model="form.type"
placeholder="请选择操作类型~"
clearable
class="!w-[200px]"
>
<el-option label="查询" :value="1" />
<el-option label="新增" :value="2" />
<el-option label="修改" :value="3" />
<el-option label="删除" :value="4" />
<el-option label="授权" :value="5" />
<el-option label="导出" :value="6" />
<el-option label="导入" :value="7" />
<el-option label="强退" :value="8" />
</el-select>
</el-form-item>
<el-form-item label="用户账号" prop="username">
<el-input
v-model="form.username"
placeholder="请输入用户账号~"
clearable
class="!w-[200px]"
/>
</el-form-item>
<el-form-item label="用户名称" prop="nickname">
<el-input
v-model="form.nickname"
placeholder="请输入用户名称~"
clearable
class="!w-[200px]"
/> />
</el-form-item> </el-form-item>
<el-form-item label="操作状态" prop="status"> <el-form-item label="操作状态" prop="status">
<el-select <el-select
v-model="form.status" v-model="form.status"
placeholder="请选择" placeholder="请选择操作状态~"
clearable clearable
class="!w-[150px]" class="!w-[200px]"
> >
<el-option label="成功" value="1" /> <el-option label="成功" :value="1" />
<el-option label="失败" value="0" /> <el-option label="失败" :value="0" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="所属部门:" prop="department_id">
<el-cascader
v-model="form.department_id"
class="!w-[200px]"
:options="departments"
:props="{
value: 'id',
label: 'name',
emitPath: false,
checkStrictly: true
}"
clearable
filterable
placeholder="请选择所属部门~"
>
<template #default="{ node, data }">
<span>{{ data.name }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</el-form-item>
<el-form-item label="操作时间" prop="operatingTime"> <el-form-item label="操作时间" prop="operatingTime">
<el-date-picker <el-date-picker
v-model="form.operatingTime" v-model="form.operationTime"
:shortcuts="getPickerShortcuts()" :shortcuts="getPickerShortcuts()"
type="datetimerange" type="datetimerange"
range-separator="" range-separator=""
start-placeholder="开始日期时间" start-placeholder="开始日期时间"
end-placeholder="结束日期时间" end-placeholder="结束日期时间"
value-format="x"
unlink-panels
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@@ -151,15 +208,6 @@ const {
</el-form> </el-form>
<PureTableBar title="操作日志" :columns="columns" @refresh="onSearch"> <PureTableBar title="操作日志" :columns="columns" @refresh="onSearch">
<template #buttons>
<!-- <el-popconfirm title="确定要删除所有日志数据吗?" @confirm="clearAll">
<template #reference>
<el-button type="danger" :icon="useRenderIcon(Delete)">
清空日志
</el-button>
</template>
</el-popconfirm> -->
</template>
<template v-slot="{ size, dynamicColumns }"> <template v-slot="{ size, dynamicColumns }">
<div <div
v-if="selectedNum > 0" v-if="selectedNum > 0"
@@ -177,9 +225,18 @@ const {
{{ t("buttons:Deselect") }} {{ t("buttons:Deselect") }}
</el-button> </el-button>
</div> </div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel"> <el-popconfirm
v-if="hasAuth('operation:btn:delete')"
title="是否确认删除?"
@confirm="onbatchDel"
>
<template #reference> <template #reference>
<el-button type="danger" text class="mr-1"> <el-button
type="danger"
text
class="mr-1"
:disabled="selectedNum < 0 || !hasAuth('operation:btn:delete')"
>
{{ t("buttons:DeleteInBatches") }} {{ t("buttons:DeleteInBatches") }}
</el-button> </el-button>
</template> </template>
@@ -211,11 +268,29 @@ const {
link link
type="primary" type="primary"
:size="size" :size="size"
:disabled="!hasAuth('operation:btn:info')"
:icon="useRenderIcon(View)" :icon="useRenderIcon(View)"
@click="onDetailHandle(row)" @click="onDetailHandle(row)"
> >
{{ t("buttons:Details") }} {{ t("buttons:Details") }}
</el-button> </el-button>
<el-popconfirm
:title="`是否删除这条操作记录?`"
@confirm="handleDelete(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
type="danger"
:disabled="!hasAuth('operation:btn:delete')"
:size="size"
:icon="useRenderIcon(Delete)"
>
{{ t("buttons:Delete") }}
</el-button>
</template>
</el-popconfirm>
</template> </template>
</pure-table> </pure-table>
</template> </template>

View File

@@ -1,19 +1,29 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import Detail from "./components/form.vue"; // import Detail from "../components/form.vue";
import { message } from "@/utils/message"; import { message } from "@/utils/message";
import { addDialog } from "@/components/ReDialog"; // import { addDialog } from "@/components/ReDialog";
import { getKeyList } from "@pureadmin/utils"; import { getKeyList, handleTree } from "@pureadmin/utils";
import { getUserOperationsAPI } from "@/api/monitor"; import {
getUserOperationsAPI,
deleteUserOperationsAPI,
deleteUserOperationsListAPI
} from "@/api/monitor";
import { usePublicHooks } from "@/views/system/hooks"; import { usePublicHooks } from "@/views/system/hooks";
import type { PaginationProps } from "@pureadmin/table"; import type { PaginationProps } from "@pureadmin/table";
import { type Ref, reactive, ref, onMounted, h } from "vue"; import { type Ref, reactive, ref, onMounted } from "vue";
import type { OperationLogInfo } from "types/monitor"; import type { OperationLogInfo } from "types/monitor";
import type { DepartmentInfo } from "types/system";
import { getDepartmentListAPI } from "@/api/system";
export function useOperation(tableRef: Ref) { export function useOperation(tableRef: Ref) {
const form = reactive({ const form = reactive({
module: "", name: "",
type: "",
username: "",
nickname: "",
status: "", status: "",
operatingTime: "" department_id: "",
operationTime: ""
}); });
const dataList = ref<OperationLogInfo[]>([]); const dataList = ref<OperationLogInfo[]>([]);
const loading = ref(true); const loading = ref(true);
@@ -97,18 +107,19 @@ export function useOperation(tableRef: Ref) {
}, },
{ {
label: "操作人员账号", label: "操作人员账号",
prop: "operator_name", prop: "operator_name"
minWidth: 100
}, },
{ {
label: "操作人员昵称", label: "操作人员昵称",
prop: "operator_nickname", prop: "operator_nickname"
minWidth: 100 },
{
label: "所属部门",
prop: "department_name"
}, },
{ {
label: "操作名称", label: "操作名称",
prop: "operation_name", prop: "operation_name"
minWidth: 100
}, },
{ {
label: "操作类型", label: "操作类型",
@@ -182,7 +193,7 @@ export function useOperation(tableRef: Ref) {
{ {
label: "操作", label: "操作",
fixed: "right", fixed: "right",
width: 100, width: 180,
slot: "operation" slot: "operation"
} }
]; ];
@@ -190,24 +201,42 @@ export function useOperation(tableRef: Ref) {
const handleSizeChange = async (val: number) => { const handleSizeChange = async (val: number) => {
const res = await getUserOperationsAPI({ const res = await getUserOperationsAPI({
page: pagination.currentPage, page: pagination.currentPage,
pageSize: val pageSize: val,
name: form.name,
type: form.type,
username: form.username,
nickname: form.nickname,
status: form.status,
department_id: form.department_id,
startTime: form.operationTime[0] ? form.operationTime[0] : null,
endTime: form.operationTime[1] ? form.operationTime[1] : null
}); });
if (res.success) { if (res.success) {
dataList.value = res.data.result; dataList.value = res.data.result;
pagination.total = res.data.total; pagination.total = res.data.total;
pagination.currentPage = res.data.page; pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
} }
}; };
const handleCurrentChange = async (val: number) => { const handleCurrentChange = async (val: number) => {
const res = await getUserOperationsAPI({ const res = await getUserOperationsAPI({
page: val, page: val,
pageSize: pagination.pageSize pageSize: pagination.pageSize,
name: form.name,
type: form.type,
username: form.username,
nickname: form.nickname,
status: form.status,
department_id: form.department_id,
startTime: form.operationTime[0] ? form.operationTime[0] : null,
endTime: form.operationTime[1] ? form.operationTime[1] : null
}); });
if (res.success) { if (res.success) {
dataList.value = res.data.result; dataList.value = res.data.result;
pagination.total = res.data.total; pagination.total = res.data.total;
pagination.currentPage = res.data.page; pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
} }
}; };
@@ -224,38 +253,58 @@ export function useOperation(tableRef: Ref) {
// 用于多选表格,清空用户的选择 // 用于多选表格,清空用户的选择
tableRef.value.getTableRef().clearSelection(); tableRef.value.getTableRef().clearSelection();
}; };
/**
*
* @param row
*/
const handleDelete = async (row: OperationLogInfo) => {
const res = await deleteUserOperationsAPI(row.id);
if (res.success) {
onSearch();
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/** 批量删除 */ /** 批量删除 */
const onbatchDel = () => { const onbatchDel = async () => {
// 返回当前选中的行 // 返回当前选中的行
const curSelected = tableRef.value.getTableRef().getSelectionRows(); const curSelected = tableRef.value.getTableRef().getSelectionRows();
// 接下来根据实际业务通过选中行的某项数据比如下面的id调用接口进行批量删除 const res = await deleteUserOperationsListAPI({
message(`已删除序号为 ${getKeyList(curSelected, "id")} 的数据`, { ids: getKeyList(curSelected, "id")
type: "success"
}); });
tableRef.value.getTableRef().clearSelection(); if (res.success) {
onSearch(); message(res.msg, {
}; type: "success"
});
/** 清空日志 */ tableRef.value.getTableRef().clearSelection();
const clearAll = async () => { onSearch();
// 根据实际业务,调用接口删除所有日志数据 } else {
message("已删除所有日志数据", { message(res.msg, {
type: "success" type: "error"
}); });
await onSearch(); }
}; };
const onSearch = async () => { const onSearch = async () => {
loading.value = true; loading.value = true;
const res = await getUserOperationsAPI({ const res = await getUserOperationsAPI({
page: pagination.currentPage, page: pagination.currentPage,
pageSize: pagination.pageSize pageSize: pagination.pageSize,
name: form.name,
type: form.type,
username: form.username,
nickname: form.nickname,
status: form.status,
department_id: form.department_id,
startTime: form.operationTime[0] ? form.operationTime[0] : null,
endTime: form.operationTime[1] ? form.operationTime[1] : null
}); });
if (res.success) { if (res.success) {
dataList.value = res.data.result; dataList.value = res.data.result;
pagination.total = res.data.total; pagination.total = res.data.total;
pagination.currentPage = res.data.page; pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
} }
setTimeout(() => { setTimeout(() => {
@@ -269,23 +318,49 @@ export function useOperation(tableRef: Ref) {
onSearch(); onSearch();
}; };
const onDetailHandle = () => { // const onDetailHandle = () => {
addDialog({ // addDialog({
title: `详情`, // title: `详情`,
props: {}, // props: {},
width: "40%", // width: "40%",
draggable: true, // draggable: true,
fullscreenIcon: true, // fullscreenIcon: true,
closeOnClickModal: false, // closeOnClickModal: false,
contentRenderer: () => h(Detail, {}), // contentRenderer: () => h(Detail, {}),
beforeSure: (done, {}) => { // beforeSure: (done, {}) => {
done(); // done();
} // }
}); // });
// };
/**部门列表 */
const departments = ref<DepartmentInfo[]>([]);
/**获取部门列表 */
const getDepartments = async () => {
const res = await getDepartmentListAPI({ page: 1, pageSize: 9999 });
if (res.success) {
departments.value = formatHigherOptions(
handleTree(res.data.result, "id", "parent_id")
);
} else {
departments.value = [];
}
};
const formatHigherOptions = (treeList: any) => {
// 根据返回数据的status字段值判断追加是否禁用disabled字段返回处理后的树结构用于上级部门级联选择器的展示实际开发中也是如此不可能前端需要的每个字段后端都会返回这时需要前端自行根据后端返回的某些字段做逻辑处理
if (!treeList || !treeList.length) return;
const newTreeList = [];
for (let i = 0; i < treeList.length; i++) {
treeList[i].disabled = treeList[i].status === 0 ? true : false;
formatHigherOptions(treeList[i].children);
newTreeList.push(treeList[i]);
}
return newTreeList;
}; };
onMounted(async () => { onMounted(async () => {
await onSearch(); await onSearch();
await getDepartments();
}); });
return { return {
@@ -295,12 +370,12 @@ export function useOperation(tableRef: Ref) {
dataList, dataList,
pagination, pagination,
selectedNum, selectedNum,
departments,
onSearch, onSearch,
clearAll,
resetForm, resetForm,
onbatchDel, onbatchDel,
getOperationName, getOperationName,
onDetailHandle, handleDelete,
handleSizeChange, handleSizeChange,
onSelectionCancel, onSelectionCancel,
handleCurrentChange, handleCurrentChange,