feat: 给用户管理、部门管理、权限管理、系统配置、国际化配置、角色管理、登录日志、操作日志、缓存列表、缓存监控和性能监控添加按钮级权限管理;调整加载个人信息逻辑

This commit is contained in:
皓月归尘 2025-02-23 22:05:59 +08:00
parent 475a8e4058
commit c0c127d090
36 changed files with 1980 additions and 158 deletions

View File

@ -22,6 +22,8 @@ buttons:OpenText: Open
buttons:CloseText: Close
buttons:Search: Search
buttons:Reset: Reset
buttons:Details: Details
buttons:DataList: DataList
buttons:Add: Add
buttons:Update: Update
buttons:Delete: Delete
@ -40,13 +42,13 @@ buttons:More: More
buttons:Deselect: Deselect
buttons:DeleteInBatches: Delete In Batches
buttons:ExportInBatches: Export In Batches
buttons:ExitInBatches: Exit In Batches
buttons:ExportAll: Export All
buttons:UploadAvatar: Upload Avatar
buttons:ResetPassword: Reset Password
buttons:RoleAllocation: Role Allocation
buttons:PermissionDetails: Permission Details
buttons:ForceToExit: Force Exit
buttons:Details: Details
search:Total: Total
search:History: History
search:Collect: Collect
@ -174,3 +176,16 @@ logout:message: Whether to exit the system?
logout:success: Logout Success
logout:fail: Logout Fail
logout:cancel: Logout Cancel
user:buttons:addRole: Add User Role
user:buttons:deleteRole: Delete User Role
user:buttons:updateRole: Update User Role
user:buttons:roleInfo: User Role Info
user:buttons:roleDataList: User Role Data List
user:buttons:permisssionList: User Permisssion List
user:buttons:uploadAvatar: Upload Avatar
user:buttons:resetPassword: Reset Password
role:buttons:addPermission: Add Role Permission
role:buttons:deletePermission: Delete Role Permission
role:buttons:updatePermission: Update Role Permission
role:buttons:permissiomInfo: Role Permissiom Info
role:buttons:permissionDataList: Rolr Permission Data List

View File

@ -22,6 +22,8 @@ buttons:OpenText: 开
buttons:CloseText:
buttons:Search: 搜索
buttons:Reset: 重置
buttons:Details: 详情
buttons:DataList: 数据列表
buttons:Add: 添加
buttons:Update: 修改
buttons:Delete: 删除
@ -46,7 +48,7 @@ buttons:ResetPassword: 重置密码
buttons:RoleAllocation: 角色分配
buttons:PermissionDetails: 权限详情
buttons:ForceToExit: 强制退出
buttons:Details: 详情
buttons:ExitInBatches: 批量强退
search:Total:
search:History: 搜索历史
search:Collect: 收藏
@ -174,3 +176,16 @@ logout:message: 是否退出当前系统?
logout:success: 退出成功
logout:fail: 退出失败
logout:cancel: 退出取消
user:buttons:addRole: 添加用户角色
user:buttons:deleteRole: 删除用户角色
user:buttons:updateRole: 更新用户角色
user:buttons:roleInfo: 用户角色详情
user:buttons:roleDataList: 用户角色列表
user:buttons:permisssionList: 用户权限列表
user:buttons:uploadAvatar: 上传头像
user:buttons:resetPassword: 重置密码
role:buttons:addPermission: 添加角色权限
role:buttons:deletePermission: 删除角色权限
role:buttons:updatePermission: 更新角色权限
role:buttons:permissiomInfo: 角色权限详情
role:buttons:permissionDataList: 角色权限列表

View File

@ -31,6 +31,18 @@ export const deleteLocaleAPI = (id: string) => {
return http.request<null>("post", `/api/i18n/deleteLocale/${id}`);
};
/**
*
*
*/
export const deleteLocaleListAPI = (ids: string[]) => {
return http.request<null>("post", `/api/i18n/deleteLocaleList`, {
data: {
ids
}
});
};
/**
*
*/
@ -138,6 +150,18 @@ export const getI18nInfoAPI = (id: string) => {
export const deleteI18nAPI = (id: string) => {
return http.request<null>("post", `/api/i18n/deleteI18n/${id}`);
};
/**
*
*
*/
export const deleteI18nListAPI = (ids: string[]) => {
return http.request<null>("post", `/api/i18n/deleteI18nList`, {
data: {
ids
}
});
};
/**
*
* @param data

View File

@ -14,10 +14,27 @@ import { filterEmptyObject } from "./utils";
*
*/
export const getUserLoginLogAPI = (params: {
/**获取用户登录日志参数 */
type GetUserLoginLogParams = {
/**页码 */
page: number;
/**每页数量 */
pageSize: number;
}) => {
/**用户账号 */
username?: string;
/**用户昵称 */
nickname?: string;
/**部门ID */
department_id?: string;
/**登录状态 */
status?: number | string;
/**开始时间 */
startTime?: string | number;
/**结束时间 */
endTime?: string | number;
};
export const getUserLoginLogAPI = (params: GetUserLoginLogParams) => {
return http.request<QueryListResult<UserLoginLogInfo>>(
"get",
"/api/log/login",
@ -36,24 +53,73 @@ export const deleteUserOnlineAPI = (id: string) => {
return http.request<null>("delete", `/api/log/logout/${id}`);
};
/**用户批量强退*/
export const deleteUserOnlineListAPI = (data: { ids: string[] }) => {
return http.request<null>("delete", `/api/log/logoutList`, { data });
};
/**删除登录日志 */
export const deleteUserLoginLogAPI = (id: string) => {
return http.request<null>("delete", `/api/log/delete/login/${id}`);
};
/**批量删除登录日志 */
export const deleteUserLoginLogListAPI = (data: { ids: string[] }) => {
return http.request<null>("delete", `/api/log/deleteList/login`, { data });
};
// ------------------------操作日志相关----------------------------------------
/**获取操作日志参数 */
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: {
page: number;
pageSize: number;
}) => {
export const getUserOperationsAPI = (params: GetUserOperationParams) => {
return http.request<QueryListResult<OperationLogInfo>>(
"GET",
"/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

@ -318,6 +318,15 @@ export const deleteRoleAPI = (id: string) => {
return http.request<null>("post", `/api/role/delete/${id}`);
};
/**批量删除角色 */
export const deleteRoleListAPI = (ids: string[]) => {
return http.request<null>("post", `/api/role/deleteList`, {
data: {
ids
}
});
};
// --------------------------用户相关--------------------------------------
/**添加用户参数 */
@ -429,7 +438,7 @@ export const deleteUserAPI = (id: string) => {
* @param data ID列表
* @returns
*/
export const deleteUserListAPI = (data: { userIds: string[] }) => {
export const deleteUserListAPI = (data: { ids: string[] }) => {
return http.request<null>("post", `/api/user/deleteUserList`, { data });
};

View File

@ -27,7 +27,8 @@ import {
type UserInfo,
userInfoKey,
removeToken,
getTokenInfo
getTokenInfo,
getUserInfo
} from "@/utils/auth";
/** src/router/modules .ts remaining.ts
@ -129,6 +130,8 @@ router.beforeEach((to: ToRouteType, _from, next) => {
whiteList.includes(to.fullPath) ? next(_from.fullPath) : next();
}
const data = getTokenInfo();
/**获取个人信息 */
getUserInfo();
if (!data.isExpire) {
// 无权限跳转403页面
if (

View File

@ -150,7 +150,7 @@ export const useUserStore = defineStore({
return new Promise<ResponseResult<LoginResult>>((resolve, reject) => {
getLogin(data)
.then(data => {
if (data.code === 200) {
if (data.success) {
setToken(data.data);
}
resolve(data);
@ -189,7 +189,7 @@ export const useUserStore = defineStore({
return new Promise<ResponseResult<LoginResult>>((resolve, reject) => {
refreshTokenApi(data)
.then(data => {
if (data.code === 200) {
if (data.success) {
setToken(data.data);
}
resolve(data);

View File

@ -1,5 +1,6 @@
import { storageLocal } from "@pureadmin/utils";
import { useUserStoreHook } from "@/store/modules/user";
import { getUserInfoAPI } from "@/api/login";
export interface UserInfo {
/** 用户名 */
@ -109,3 +110,21 @@ export function getTokenInfo(): {
};
}
}
/**判断是否有权限 */
export const hasAuth = (auth: string) => {
return useUserStoreHook().permissions.includes(auth);
};
/**获取用户信息 */
export const getUserInfo = async () => {
const res = await getUserInfoAPI();
if (res.success) {
const user = storageLocal().getItem<object>(userInfoKey);
storageLocal().setItem(userInfoKey, {
...user,
...res.data
});
setUserInfo(res.data);
}
};

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref } from "vue";
import { message } from "@/utils/message";
import Avatar from "@/assets/user.png";
import type { FormInstance } from "element-plus";
import ReCropperPreview from "@/components/ReCropperPreview";
import { deviceDetection } from "@pureadmin/utils";
@ -47,7 +48,7 @@ const handleSubmitImage = async () => {
const res = await postUploadAvatarAPI(userInfo.id, {
file: cropperBlob.value
});
if (res.code === 200) {
if (res.success) {
message("更新头像成功", { type: "success" });
userInfo.avatar = `/file/${res.data.id}`;
const user = storageLocal().getItem<object>(userInfoKey);
@ -71,7 +72,7 @@ const onSubmit = async (formEl: FormInstance) => {
gender: userInfo.gender
};
const res = await putUpdateBaseUserInfoAPI(updateForm);
if (res.code === 200) {
if (res.success) {
message(res.msg, { type: "success" });
await getUserInfo();
} else {
@ -94,7 +95,10 @@ const onSubmit = async (formEl: FormInstance) => {
<h3 class="my-8">个人信息</h3>
<el-form ref="userInfoFormRef" label-position="top" :model="userInfo">
<el-form-item label="头像">
<el-avatar :size="80" :src="`/api/${userInfo.avatar}`" />
<el-avatar
:size="80"
:src="userInfo.avatar ? `/api/${userInfo.avatar}` : Avatar"
/>
<el-upload
ref="uploadRef"
accept="image/*"

View File

@ -2,7 +2,6 @@ import { message } from "@/utils/message";
import { addDialog } from "@/components/ReDialog";
import { reactive, ref, onMounted, watch } from "vue";
import { ElForm, ElFormItem, ElInput, ElProgress } from "element-plus";
import type { UserInfo } from "types/user";
import { getUserInfoAPI } from "@/api/login";
import {
putUpdateEmailAPI,
@ -12,6 +11,7 @@ import {
import { isAllEmpty, isEmail, isPhone, storageLocal } from "@pureadmin/utils";
import { zxcvbn } from "@zxcvbn-ts/core";
import { setUserInfo, userInfoKey } from "@/utils/auth";
import type { UserInfo } from "types/system";
export const useUserInfo = () => {
/** 密码正则密码格式应为8-18位数字、字母、符号的任意两种组合 */
@ -52,10 +52,9 @@ export const useUserInfo = () => {
nickname: "",
phone: "",
status: 0,
department_id: "",
create_time: "",
update_time: "",
roles: [],
permissions: []
update_time: ""
});
/**获取个人信息 */
const getUserInfo = async () => {
@ -200,7 +199,7 @@ export const useUserInfo = () => {
if (valid) {
// 表单规则校验通过
const res = await putUpdatePasswordAPI(passwordForm);
if (res.code === 200) {
if (res.success) {
done();
message(res.msg, {
type: "success"
@ -262,7 +261,7 @@ export const useUserInfo = () => {
if (valid) {
// 表单规则校验通过
const res = await putUpdatePhoneAPI(phoneForm);
if (res.code === 200) {
if (res.success) {
done();
message(res.msg, {
type: "success"
@ -323,7 +322,7 @@ export const useUserInfo = () => {
if (valid) {
// 表单规则校验通过
const res = await putUpdateEmailAPI(emailForm);
if (res.code === 200) {
if (res.success) {
done();
message(res.msg, {
type: "success"

View File

@ -91,7 +91,7 @@ const onUpdate = async (formEl: FormInstance | undefined) => {
code: ruleForm.code,
department_id: ruleForm.department_id
});
if (res.code === 200) {
if (res.success) {
message(transformI18n($t("login:RegisterSuccess")), {
type: "success"
});
@ -124,10 +124,10 @@ const start = async (
if (isValid) {
const res = await postGetCodeAPI({
username: ruleForm.username,
title: "注册",
title: "Register",
mail: ruleForm.email
});
if (res.code === 200) {
if (res.success) {
clearInterval(timer.value);
isDisabled.value = true;
text.value = `${time}`;

View File

@ -81,10 +81,10 @@ const start = async (
if (isValid) {
const res = await postGetCodeAPI({
username: ruleForm.username,
title: "重置",
title: "Reset",
mail: ruleForm.email
});
if (res.code === 200) {
if (res.success) {
clearInterval(timer.value);
isDisabled.value = true;
text.value = `${time}`;

View File

@ -34,8 +34,6 @@ import Check from "@iconify-icons/ep/check";
import User from "@iconify-icons/ri/user-3-fill";
import Info from "@iconify-icons/ri/information-line";
import { GetCaptchaAPI } from "@/api/login";
import { getUserInfoAPI } from "@/api/login";
import { setUserInfo } from "@/utils/auth";
const Period = getConfig("Period");
defineOptions({
@ -88,10 +86,6 @@ const onLogin = async (formEl: FormInstance | undefined) => {
//
return initRouter().then(async () => {
disabled.value = true;
const res = await getUserInfoAPI();
if (res.success) {
setUserInfo(res.data);
}
router
.push(getTopMenu(true).path)
.then(() => {

View File

@ -121,6 +121,7 @@ import * as echarts from "echarts";
import Monitor from "@iconify-icons/ep/monitor";
import PieChart from "@iconify-icons/ep/pie-chart";
import Odometer from "@iconify-icons/ep/odometer";
import { onBeforeRouteLeave } from "vue-router";
const cache = reactive({
commandStats: [] as { name: string; value: number }[],
@ -240,13 +241,18 @@ const initUsedMemoryChart = () => {
chart.setOption(option);
};
/**定时ID */
const timer = ref();
onMounted(async () => {
await getCacheInfo();
setInterval(() => {
timer.value = setInterval(() => {
getCacheInfo();
}, 60000);
});
/**页面离开时清除定时器 */
onBeforeRouteLeave(() => {
clearInterval(timer.value);
});
</script>
<style scoped lang="scss">

View File

@ -13,6 +13,7 @@
link
type="primary"
size="large"
:disabled="!hasAuth('cache:btn:list')"
:icon="useRenderIcon(Refresh)"
@click="refreshCacheNames()"
/>
@ -61,6 +62,7 @@
<el-button
link
type="primary"
:disabled="!hasAuth('cache:btn:delete')"
:icon="useRenderIcon(Delete)"
/>
</template>
@ -83,6 +85,7 @@
type="primary"
link
size="large"
:disabled="!hasAuth('cache:btn:list')"
:icon="useRenderIcon(Refresh)"
@click="refreshCacheKeys()"
/>
@ -122,6 +125,7 @@
<el-button
link
type="primary"
:disabled="!hasAuth('cache:btn:delete')"
:icon="useRenderIcon(Delete)"
/>
</template>
@ -153,6 +157,7 @@
style="float: right; padding: 3px 0"
link
type="primary"
:disabled="!hasAuth('cache:btn:delete')"
:icon="useRenderIcon(Delete)"
>清理全部</el-button
>
@ -207,6 +212,7 @@ import Document from "@iconify-icons/ep/document";
import Delete from "@iconify-icons/ep/delete";
import Refresh from "@iconify-icons/ep/refresh";
import { message } from "@/utils/message";
import { hasAuth } from "@/utils/auth";
defineOptions({
name: "CacheList"
});

View File

@ -5,9 +5,10 @@ import { getPickerShortcuts } from "../utils";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { useI18n } from "vue-i18n";
import Delete from "@iconify-icons/ep/delete";
import Plane from "@iconify-icons/ri/plane-line";
import Refresh from "@iconify-icons/ep/refresh";
import { hasAuth } from "@/utils/auth";
defineOptions({
name: "MonitorLogin"
});
@ -23,13 +24,16 @@ const {
dataList,
pagination,
selectedNum,
departments,
onSearch,
deleteUserHandle,
resetForm,
onbatchDel,
onbatchForce,
resetForm,
deleteUserHandle,
onSelectionCancel,
handleCurrentChange,
handleSizeChange,
handleDelete,
handleSelectionChange
} = useLogin(tableRef);
</script>
@ -42,12 +46,20 @@ const {
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
>
<el-form-item label="用户" prop="username">
<el-form-item label="用户账号" prop="username">
<el-input
v-model="form.username"
placeholder="请输入用户"
placeholder="请输入用户账号~"
clearable
class="!w-[150px]"
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 label="登录状态" prop="status">
@ -55,12 +67,33 @@ const {
v-model="form.status"
placeholder="请选择"
clearable
class="!w-[150px]"
class="!w-[200px]"
>
<el-option label="成功" value="1" />
<el-option label="失败" value="0" />
<el-option label="成功" :value="1" />
<el-option label="失败" :value="0" />
</el-select>
</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="loginTime">
<el-date-picker
v-model="form.loginTime"
@ -69,6 +102,8 @@ const {
range-separator="至"
start-placeholder="开始日期时间"
end-placeholder="结束日期时间"
value-format="x"
unlink-panels
/>
</el-form-item>
<el-form-item>
@ -86,16 +121,7 @@ const {
</el-form-item>
</el-form>
<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> -->
<PureTableBar title="登录日志管理" :columns="columns" @refresh="onSearch">
<template v-slot="{ size, dynamicColumns }">
<div
v-if="selectedNum > 0"
@ -113,9 +139,34 @@ const {
{{ t("buttons:Deselect") }}
</el-button>
</div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
<el-popconfirm
v-if="hasAuth('login:btn:logout')"
title="是否确认强制退出?"
@confirm="onbatchForce"
>
<template #reference>
<el-button type="danger" text class="mr-1">
<el-button
type="warning"
text
class="mr-1"
:disabled="selectedNum < 0 || !hasAuth('login:btn:logout')"
>
{{ t("buttons:ExitInBatches") }}
</el-button>
</template>
</el-popconfirm>
<el-popconfirm
v-if="hasAuth('login:btn:delete')"
title="是否确认删除?"
@confirm="onbatchDel"
>
<template #reference>
<el-button
type="danger"
text
class="mr-1"
:disabled="selectedNum < 0 || !hasAuth('login:btn:delete')"
>
{{ t("buttons:DeleteInBatches") }}
</el-button>
</template>
@ -142,6 +193,23 @@ const {
@page-current-change="handleCurrentChange"
>
<template #operation="{ row }">
<el-popconfirm
:title="`是否删除这条登录记录?`"
@confirm="handleDelete(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
type="danger"
:disabled="!hasAuth('login:btn:delete')"
:size="size"
:icon="useRenderIcon(Delete)"
>
{{ t("buttons:Delete") }}
</el-button>
</template>
</el-popconfirm>
<el-popconfirm
:title="`是否强制下线${row.username}`"
@confirm="deleteUserHandle(row.session_id)"
@ -151,7 +219,7 @@ const {
class="reset-margin"
link
type="primary"
:disabled="!row.online"
:disabled="!row.online || !hasAuth('login:btn:logout')"
:size="size"
:icon="useRenderIcon(Plane)"
>

View File

@ -1,17 +1,27 @@
import dayjs from "dayjs";
import { message } from "@/utils/message";
import { getKeyList } from "@pureadmin/utils";
import { deleteUserOnlineAPI, getUserLoginLogAPI } from "@/api/monitor";
import { getKeyList, handleTree } from "@pureadmin/utils";
import {
deleteUserOnlineAPI,
getUserLoginLogAPI,
deleteUserLoginLogAPI,
deleteUserOnlineListAPI,
deleteUserLoginLogListAPI
} from "@/api/monitor";
import { usePublicHooks } from "@/views/system/hooks";
import type { PaginationProps } from "@pureadmin/table";
import { type Ref, reactive, ref, onMounted } from "vue";
import type { UserLoginLogInfo } from "types/monitor";
import type { DepartmentInfo } from "types/system";
import { getDepartmentListAPI } from "@/api/system";
export const useLogin = (tableRef: Ref) => {
const form = reactive({
username: "",
nickname: "",
status: "",
loginTime: ""
department_id: "",
loginTime: []
});
/**
*
@ -93,6 +103,16 @@ export const useLogin = (tableRef: Ref) => {
</el-tag>
)
},
{
label: "在线状态",
prop: "online",
minWidth: 100,
cellRenderer: ({ row, props }) => (
<el-tag size={props.size} type={row.online ? "success" : ""}>
{row.online ? "在线" : "离线"}
</el-tag>
)
},
{
label: "登录时间",
prop: "login_time",
@ -103,7 +123,8 @@ export const useLogin = (tableRef: Ref) => {
{
label: "操作",
fixed: "right",
slot: "operation"
slot: "operation",
width: 200
}
];
@ -114,12 +135,19 @@ export const useLogin = (tableRef: Ref) => {
const handleCurrentChange = async (val: number) => {
const res = await getUserLoginLogAPI({
page: val,
pageSize: pagination.pageSize
pageSize: pagination.pageSize,
department_id: form.department_id,
status: form.status,
username: form.username,
nickname: form.nickname,
startTime: form.loginTime[0] ? form.loginTime[0] : null,
endTime: form.loginTime[1] ? form.loginTime[1] : null
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
}
};
@ -127,12 +155,19 @@ export const useLogin = (tableRef: Ref) => {
const handleSizeChange = async (val: number) => {
const res = await getUserLoginLogAPI({
page: pagination.currentPage,
pageSize: val
pageSize: val,
department_id: form.department_id,
status: form.status,
username: form.username,
nickname: form.nickname,
startTime: form.loginTime[0] ? form.loginTime[0] : null,
endTime: form.loginTime[1] ? form.loginTime[1] : null
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
}
};
@ -149,38 +184,77 @@ export const useLogin = (tableRef: Ref) => {
// 用于多选表格,清空用户的选择
tableRef.value.getTableRef().clearSelection();
};
/**处理删除 */
/**
*
* @param row
*/
const handleDelete = async (row: UserLoginLogInfo) => {
const res = await deleteUserLoginLogAPI(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();
// 接下来根据实际业务通过选中行的某项数据比如下面的id调用接口进行批量删除
message(`已删除序号为 ${getKeyList(curSelected, "id")} 的数据`, {
type: "success"
const res = await deleteUserLoginLogListAPI({
ids: getKeyList(curSelected, "id")
});
tableRef.value.getTableRef().clearSelection();
onSearch();
if (res.success) {
message(res.msg, {
type: "success"
});
tableRef.value.getTableRef().clearSelection();
onSearch();
} else {
message(res.msg, {
type: "error"
});
}
};
/** 清空日志 */
const clearAll = async () => {
// 根据实际业务,调用接口删除所有日志数据
message("已删除所有日志数据", {
type: "success"
/**批量强退 */
const onbatchForce = async () => {
// 返回当前选中的行
const curSelected = tableRef.value.getTableRef().getSelectionRows();
const res = await deleteUserOnlineListAPI({
ids: getKeyList(curSelected, "id")
});
await onSearch();
if (res.success) {
message(res.msg, {
type: "success"
});
tableRef.value.getTableRef().clearSelection();
onSearch();
} else {
message(res.msg, {
type: "error"
});
}
};
const onSearch = async () => {
loading.value = true;
const res = await getUserLoginLogAPI({
page: pagination.currentPage,
pageSize: pagination.pageSize
pageSize: pagination.pageSize,
department_id: form.department_id,
status: form.status,
username: form.username,
nickname: form.nickname,
startTime: form.loginTime[0] ? form.loginTime[0] : null,
endTime: form.loginTime[1] ? form.loginTime[1] : null
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
}
setTimeout(() => {
loading.value = false;
@ -201,9 +275,34 @@ export const useLogin = (tableRef: Ref) => {
onSearch();
}
};
/**部门列表 */
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(() => {
onSearch();
onMounted(async () => {
await onSearch();
await getDepartments();
});
return {
@ -213,14 +312,16 @@ export const useLogin = (tableRef: Ref) => {
dataList,
pagination,
selectedNum,
departments,
onSearch,
clearAll,
resetForm,
onbatchDel,
onbatchForce,
resetForm,
deleteUserHandle,
onSelectionCancel,
handleCurrentChange,
handleSizeChange,
handleDelete,
handleSelectionChange
};
};

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import dayjs from "dayjs";
import { ref, reactive, computed } from "vue";
import { useOperation } from "./hook";
import { useOperation } from "./utils/hook";
import { getPickerShortcuts } from "../utils";
import { PureTableBar } from "@/components/RePureTableBar";
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 Refresh from "@iconify-icons/ep/refresh";
import { OperationLogInfo } from "types/monitor";
import { hasAuth } from "@/utils/auth";
defineOptions({
name: "OperationLog"
});
@ -86,11 +86,12 @@ const {
dataList,
pagination,
selectedNum,
departments,
onSearch,
clearAll,
resetForm,
onbatchDel,
getOperationName,
handleDelete,
handleSizeChange,
onSelectionCancel,
handleCurrentChange,
@ -106,33 +107,89 @@ const {
:model="form"
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
v-model="form.module"
placeholder="请输入所属模块"
v-model="form.name"
placeholder="请输入操作名称"
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 label="操作状态" prop="status">
<el-select
v-model="form.status"
placeholder="请选择"
placeholder="请选择操作状态~"
clearable
class="!w-[150px]"
class="!w-[200px]"
>
<el-option label="成功" value="1" />
<el-option label="失败" value="0" />
<el-option label="成功" :value="1" />
<el-option label="失败" :value="0" />
</el-select>
</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-date-picker
v-model="form.operatingTime"
v-model="form.operationTime"
:shortcuts="getPickerShortcuts()"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期时间"
end-placeholder="结束日期时间"
value-format="x"
unlink-panels
/>
</el-form-item>
<el-form-item>
@ -151,15 +208,6 @@ const {
</el-form>
<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 }">
<div
v-if="selectedNum > 0"
@ -177,9 +225,18 @@ const {
{{ t("buttons:Deselect") }}
</el-button>
</div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
<el-popconfirm
v-if="hasAuth('operation:btn:delete')"
title="是否确认删除?"
@confirm="onbatchDel"
>
<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") }}
</el-button>
</template>
@ -211,11 +268,29 @@ const {
link
type="primary"
:size="size"
:disabled="!hasAuth('operation:btn:info')"
:icon="useRenderIcon(View)"
@click="onDetailHandle(row)"
>
{{ t("buttons:Details") }}
</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>
</pure-table>
</template>

View File

@ -0,0 +1,384 @@
import dayjs from "dayjs";
// import Detail from "../components/form.vue";
import { message } from "@/utils/message";
// import { addDialog } from "@/components/ReDialog";
import { getKeyList, handleTree } from "@pureadmin/utils";
import {
getUserOperationsAPI,
deleteUserOperationsAPI,
deleteUserOperationsListAPI
} from "@/api/monitor";
import { usePublicHooks } from "@/views/system/hooks";
import type { PaginationProps } from "@pureadmin/table";
import { type Ref, reactive, ref, onMounted } from "vue";
import type { OperationLogInfo } from "types/monitor";
import type { DepartmentInfo } from "types/system";
import { getDepartmentListAPI } from "@/api/system";
export function useOperation(tableRef: Ref) {
const form = reactive({
name: "",
type: "",
username: "",
nickname: "",
status: "",
department_id: "",
operationTime: ""
});
const dataList = ref<OperationLogInfo[]>([]);
const loading = ref(true);
const selectedNum = ref(0);
const { tagStyle } = usePublicHooks();
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true,
pageSizes: [10, 20, 30, 40, 50]
});
const getOperationType = (type: number) => {
switch (type) {
case 1:
return "primary";
case 2:
return "success";
case 3:
return "warning";
case 4:
return "danger";
case 5:
return "success";
case 6:
return "primary";
case 7:
return "success";
case 8:
return "danger";
default:
return "info";
}
};
const getOperationName = (type: number) => {
switch (type) {
case 1:
return "查询";
case 2:
return "新增";
case 3:
return "修改";
case 4:
return "删除";
case 5:
return "授权";
case 6:
return "导出";
case 7:
return "导入";
case 8:
return "强退";
default:
return "其他";
}
};
const getRequestType = (method: string) => {
switch (method) {
case "GET":
return "primary";
case "POST":
return "success";
case "PUT":
return "warning";
case "DELETE":
return "danger";
case "PATCH":
return "info";
default:
return "info";
}
};
const columns: TableColumnList = [
{
label: "勾选列", // 如果需要表格多选此处label必须设置
type: "selection",
fixed: "left",
reserveSelection: true // 数据刷新后保留选项
},
{
label: "操作人员账号",
prop: "operator_name"
},
{
label: "操作人员昵称",
prop: "operator_nickname"
},
{
label: "所属部门",
prop: "department_name"
},
{
label: "操作名称",
prop: "operation_name"
},
{
label: "操作类型",
prop: "operation_type",
cellRenderer: ({ row, props }) => (
<el-tag size={props.size} type={getOperationType(row.operation_type)}>
{getOperationName(row.operation_type)}
</el-tag>
)
},
{
label: "操作IP",
prop: "host"
},
{
label: "操作地点",
prop: "location"
},
{
label: "请求方法",
prop: "request_method",
cellRenderer: ({ row, props }) => (
<el-tag size={props.size} type={getRequestType(row.request_method)}>
{row.request_method}
</el-tag>
)
},
{
label: "请求路径",
prop: "request_path"
},
{
label: "请求耗时",
prop: "cost_time",
cellRenderer: ({ row, props }) => (
<el-tag
size={props.size}
type={row.cost_time < 1000 ? "success" : "warning"}
effect="plain"
>
{row.cost_time.toFixed(2)} ms
</el-tag>
)
},
{
label: "操作系统",
prop: "os",
hide: true
},
{
label: "浏览器类型",
prop: "browser",
hide: true
},
{
label: "操作状态",
prop: "status",
cellRenderer: ({ row, props }) => (
<el-tag size={props.size} style={tagStyle.value(row.status)}>
{row.status === 1 ? "成功" : "失败"}
</el-tag>
)
},
{
label: "操作时间",
prop: "operation_time",
minWidth: 180,
formatter: ({ operation_time }) =>
dayjs(operation_time).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",
fixed: "right",
width: 180,
slot: "operation"
}
];
const handleSizeChange = async (val: number) => {
const res = await getUserOperationsAPI({
page: pagination.currentPage,
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) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
}
};
const handleCurrentChange = async (val: number) => {
const res = await getUserOperationsAPI({
page: val,
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) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
}
};
/** 当CheckBox选择项发生变化时会触发该事件 */
const handleSelectionChange = val => {
selectedNum.value = val.length;
// 重置表格高度
tableRef.value.setAdaptive();
};
/** 取消选择 */
const onSelectionCancel = () => {
selectedNum.value = 0;
// 用于多选表格,清空用户的选择
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 = async () => {
// 返回当前选中的行
const curSelected = tableRef.value.getTableRef().getSelectionRows();
const res = await deleteUserOperationsListAPI({
ids: getKeyList(curSelected, "id")
});
if (res.success) {
message(res.msg, {
type: "success"
});
tableRef.value.getTableRef().clearSelection();
onSearch();
} else {
message(res.msg, {
type: "error"
});
}
};
const onSearch = async () => {
loading.value = true;
const res = await getUserOperationsAPI({
page: pagination.currentPage,
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) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
}
setTimeout(() => {
loading.value = false;
}, 500);
};
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
// const onDetailHandle = () => {
// addDialog({
// title: `详情`,
// props: {},
// width: "40%",
// draggable: true,
// fullscreenIcon: true,
// closeOnClickModal: false,
// contentRenderer: () => h(Detail, {}),
// beforeSure: (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 () => {
await onSearch();
await getDepartments();
});
return {
form,
loading,
columns,
dataList,
pagination,
selectedNum,
departments,
onSearch,
resetForm,
onbatchDel,
getOperationName,
handleDelete,
handleSizeChange,
onSelectionCancel,
handleCurrentChange,
handleSelectionChange
};
}

View File

@ -45,6 +45,7 @@
<PureTableBar title="配置管理" :columns="columns" @refresh="onSearch">
<template #buttons>
<el-button
v-if="hasAuth('config:btn:add')"
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog('新增')"
@ -69,7 +70,11 @@
{{ t("buttons:Deselect") }}
</el-button>
</div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
<el-popconfirm
v-if="hasAuth('config:btn:delete')"
title="是否确认删除?"
@confirm="onbatchDel"
>
<template #reference>
<el-button type="danger" text class="mr-1">
{{ t("buttons:DeleteInBatches") }}
@ -106,6 +111,7 @@
link
type="primary"
:size="size"
:disabled="!hasAuth('config:btn:update')"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
@ -119,6 +125,7 @@
<el-button
class="reset-margin"
link
:disabled="!hasAuth('config:btn:delete')"
type="danger"
:size="size"
:icon="useRenderIcon(Delete)"
@ -148,6 +155,7 @@ import EditPen from "@iconify-icons/ep/edit-pen";
import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
const { t } = useI18n();
import { hasAuth } from "@/utils/auth";
/**
* 表格Ref
*/

View File

@ -202,7 +202,7 @@ export const useConfig = (tableRef: Ref) => {
const res = await deleteConfigListAPI({
ids: getKeyList(curSelected, "id")
});
if (res.code === 200) {
if (res.success) {
message(`已删除项目名称为 ${getKeyList(curSelected, "name")} 的数据`, {
type: "success"
});

View File

@ -9,6 +9,7 @@ import EditPen from "@iconify-icons/ep/edit-pen";
import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
import { onBeforeRouteUpdate } from "vue-router";
import { hasAuth } from "@/utils/auth";
defineOptions({
name: "SystemDepartment"
@ -82,6 +83,7 @@ onBeforeRouteUpdate((to, from, next) => {
>
<template #buttons>
<el-button
v-if="hasAuth('department:btn:add')"
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog()"
@ -117,6 +119,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('department:btn:update')"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
@ -127,6 +130,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('department:btn:add')"
:icon="useRenderIcon(AddFill)"
@click="openDialog('新增', { parent_id: row.id } as any)"
>
@ -142,6 +146,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="danger"
:size="size"
:disabled="!hasAuth('department:btn:delete')"
:icon="useRenderIcon(Delete)"
>
{{ t("buttons:Delete") }}

View File

@ -219,7 +219,7 @@ export const useDepartment = () => {
*/
const handleDelete = async (row: DepartmentInfo) => {
const res = await deleteDepartmentAPI(row.id);
if (res.code === 200) {
if (res.success) {
message(`您删除了部门:${row.name}及其附属部门`, { type: "success" });
onSearch();
} else {

View File

@ -54,6 +54,7 @@
<PureTableBar title="国际化管理" :columns="columns" @refresh="onSearch">
<template #buttons>
<el-button
v-if="hasAuth('i18n:btn:add')"
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog('新增')"
@ -78,9 +79,18 @@
{{ t("buttons:Deselect") }}
</el-button>
</div>
<el-popconfirm title="是否确认删除?">
<el-popconfirm
v-if="hasAuth('i18n:btn:delete')"
title="是否确认删除?"
@confirm="onbatchDel"
>
<template #reference>
<el-button type="danger" text class="mr-1">
<el-button
type="danger"
text
class="mr-1"
:disabled="selectedNum < 0 || !hasAuth('i18n:btn:delete')"
>
{{ t("buttons:DeleteInBatches") }}
</el-button>
</template>
@ -115,6 +125,7 @@
link
type="primary"
:size="size"
:disabled="!hasAuth('i18n:btn:update')"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
@ -130,6 +141,7 @@
link
type="danger"
:size="size"
:disabled="!hasAuth('i18n:btn:delete')"
:icon="useRenderIcon(Delete)"
>
{{ t("buttons:Delete") }}
@ -148,7 +160,7 @@ defineOptions({
name: "I18nIndex"
});
import { ref } from "vue";
import { useI18n } from "./hook";
import { useI18n } from "./utils/hook";
import { useI18n as i18n } from "vue-i18n";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
@ -156,6 +168,7 @@ import Delete from "@iconify-icons/ep/delete";
import EditPen from "@iconify-icons/ep/edit-pen";
import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
import { hasAuth } from "@/utils/auth";
const { t } = i18n();
/**
* 表格Ref
@ -177,7 +190,8 @@ const {
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
onSelectionCancel
onSelectionCancel,
onbatchDel
} = useI18n(tableRef);
</script>

View File

@ -0,0 +1,308 @@
import dayjs from "dayjs";
import editForm from "../components/form.vue";
import { message } from "@/utils/message";
import { type Ref, ref, reactive, onMounted, h } from "vue";
import type { LanguageInfo, TranslationInfo } from "types/system";
import type { PaginationProps } from "@pureadmin/table";
import { addDialog } from "@/components/ReDialog";
import {
deleteI18nAPI,
deleteI18nListAPI,
getI18nListAPI,
getLocaleListAPI,
postAddI18nAPI,
putUpdateI18nAPI
} from "@/api/i18n";
import { getKeyList } from "@pureadmin/utils";
export const useI18n = (tableRef: Ref) => {
/**
*
*/
const form = reactive({
key: "",
locale_id: "",
translation: ""
});
/**
* Ref
*/
const formRef = ref(null);
/**
*
*/
const dataList = ref<TranslationInfo[]>([]);
/**
*
*/
const loading = ref(true);
/**
*
*/
const selectedNum = ref<number>(0);
/**
*
*/
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true,
pageSizes: [10, 20, 30, 40, 50]
});
/**
*
*/
const columns: TableColumnList = [
{
label: "勾选列", // 如果需要表格多选此处label必须设置
type: "selection",
fixed: "left",
reserveSelection: true // 数据刷新后保留选项
},
{
label: "国际化key",
prop: "key"
},
{
label: "国际化值",
prop: "translation"
},
{
label: "语言编码",
prop: "locale_code"
},
{
label: "语言名称",
prop: "locale_name"
},
{
label: "创建时间",
prop: "create_time",
formatter: ({ create_time }) =>
dayjs(create_time).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",
fixed: "right",
width: 220,
slot: "operation"
}
];
/**
*
*/
const onSearch = async () => {
loading.value = true;
const res = await getI18nListAPI({
page: pagination.currentPage,
pageSize: pagination.pageSize,
key: form.key,
locale_id: form.locale_id,
translation: form.translation
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
loading.value = false;
};
/**
*
* @param formEl ref
* @returns
*/
const resetForm = (formEl: any) => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
/**
*
* @param row
*/
const handleDelete = async (row: TranslationInfo) => {
const res = await deleteI18nAPI(row.id);
if (res.success) {
onSearch();
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/**
*
*/
const handleSizeChange = async (val: number) => {
const res = await getI18nListAPI({
page: pagination.currentPage,
pageSize: val,
key: form.key,
locale_id: form.locale_id,
translation: form.translation
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/**
*
* @param val
*/
const handleCurrentChange = async (val: number) => {
const res = await getI18nListAPI({
page: val,
pageSize: pagination.pageSize,
key: form.key,
locale_id: form.locale_id,
translation: form.translation
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/** 当CheckBox选择项发生变化时会触发该事件 */
const handleSelectionChange = async (val: any) => {
selectedNum.value = val.length;
// 重置表格高度
tableRef.value.setAdaptive();
};
/** 取消选择 */
const onSelectionCancel = async () => {
selectedNum.value = 0;
// 用于多选表格,清空用户的选择
tableRef.value.getTableRef().clearSelection();
};
/**
*
*/
const onbatchDel = async () => {
// 返回当前选中的行
const curSelected = tableRef.value.getTableRef().getSelectionRows();
const res = await deleteI18nListAPI(getKeyList(curSelected, "id"));
if (res.success) {
message(res.msg, {
type: "success"
});
tableRef.value.getTableRef().clearSelection();
onSearch();
} else {
message(res.msg, { type: "error", duration: 5000 });
}
};
const openDialog = async (title = "新增", row?: TranslationInfo) => {
addDialog({
title: `${title}国际化项`,
props: {
formInline: {
title: title,
key: row?.key ?? "",
locale_id: row?.locale_id ?? "",
translation: row?.translation ?? ""
}
},
width: "45%",
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () =>
h(editForm, {
formInline: {
title: title,
key: row?.key ?? "",
locale_id: row?.locale_id ?? "",
translation: row?.translation ?? ""
},
ref: formRef
}),
beforeSure: async (done, {}) => {
const FormData = formRef.value.newFormInline;
let form = {
key: FormData.key ?? "",
locale_id: FormData.locale_id ?? "",
translation: FormData.translation ?? ""
};
if (title === "新增") {
const res = await postAddI18nAPI(form);
if (res.success) {
done();
await onSearch();
}
message(res.msg, { type: res.success ? "success" : "error" });
} else {
const res = await putUpdateI18nAPI(form, row.id);
if (res.success) {
done();
await onSearch();
}
message(res.msg, { type: res.success ? "success" : "error" });
}
}
});
};
/**语言类型 */
const localeList = ref<LanguageInfo[]>([]);
/**
*
*/
const getLocaleList = async () => {
const res = await getLocaleListAPI({
page: 1,
pageSize: 9999
});
if (res.success) {
localeList.value = res.data.result;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/**
*
*/
onMounted(async () => {
await onSearch();
await getLocaleList();
});
return {
form,
formRef,
dataList,
loading,
pagination,
columns,
selectedNum,
localeList,
onSearch,
openDialog,
resetForm,
handleDelete,
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
onSelectionCancel,
onbatchDel,
getLocaleList
};
};

View File

@ -0,0 +1,50 @@
<template>
<el-form ref="ruleFormRef" :model="newFormInline" label-width="82px">
<el-row :gutter="30">
<re-col :value="24" :xm="24" :sm="24">
<el-form-item label="语言编码" prop="code">
<el-input
v-model="newFormInline.code"
placeholder="请输入语言编码~"
clearable
class="w-full"
/>
</el-form-item>
</re-col>
<re-col :value="24" :xm="24" :sm="24">
<el-form-item label="语言名称" prop="name">
<el-input
v-model="newFormInline.name"
placeholder="请输入语言名称~"
clearable
class="w-full"
/>
</el-form-item>
</re-col>
</el-row>
</el-form>
</template>
<script setup lang="ts">
import { ref } from "vue";
import ReCol from "@/components/ReCol";
const ruleFormRef = ref();
interface PropsInfo {
title: string;
code: string;
name: string;
}
type ProsData = {
formInline: PropsInfo;
};
const props = withDefaults(defineProps<ProsData>(), {
formInline: () => ({
title: "新增",
code: "",
name: ""
})
});
const newFormInline = ref<PropsInfo>(props.formInline);
defineExpose({ newFormInline });
</script>

View File

@ -0,0 +1,219 @@
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
>
<el-form-item label="语言编码" prop="code">
<el-input
v-model="form.code"
placeholder="请输入语言编码~"
clearable
class="!w-[180px]"
/>
</el-form-item>
<el-form-item label="语言名称" prop="name">
<el-input
v-model="form.name"
placeholder="请输入语言名称~"
clearable
class="!w-[180px]"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
@click="onSearch"
>
{{ t("buttons:Search") }}
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
{{ t("buttons:Reset") }}
</el-button>
</el-form-item>
</el-form>
<PureTableBar title="语言类型管理" :columns="columns" @refresh="onSearch">
<template #buttons>
<el-button
v-if="hasAuth('locale:btn:add')"
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog('新增')"
>
{{ t("buttons:Add") }}
</el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<div
v-if="selectedNum > 0"
v-motion-fade
class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center"
>
<div class="flex-auto">
<span
style="font-size: var(--el-font-size-base)"
class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
>
已选 {{ selectedNum }}
</span>
<el-button type="primary" text @click="onSelectionCancel">
{{ t("buttons:Deselect") }}
</el-button>
</div>
<el-popconfirm
v-if="hasAuth('locale:btn:delete')"
title="是否确认删除?"
@confirm="onbatchDel"
>
<template #reference>
<el-button
type="danger"
:disabled="!selectedNum || !hasAuth('locale:btn:delete')"
text
class="mr-1"
>
{{ t("buttons:DeleteInBatches") }}</el-button
>
</template>
</el-popconfirm>
</div>
<pure-table
ref="tableRef"
row-key="id"
adaptive
border
stripe
:adaptiveConfig="{ offsetBottom: 45 }"
align-whole="center"
table-layout="auto"
:loading="loading"
:size="size"
:data="dataList"
:columns="dynamicColumns"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
>
<template #operation="{ row }">
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(EditPen)"
:disabled="!hasAuth('locale:btn:update')"
@click="openDialog('修改', row)"
>
{{ t("buttons:Update") }}
</el-button>
<el-popconfirm
:title="`是否确认导出语言名称为 ${row.name} 的这条数据为yaml文件`"
@confirm="export_to_yaml(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:disabled="!hasAuth('i18n:btn:infoList')"
:icon="useRenderIcon(Download)"
>
{{ t("buttons:Export") }}
</el-button>
</template>
</el-popconfirm>
<el-popconfirm
:title="`是否确认删除语言名称为 ${row.name} 的这条数据`"
@confirm="handleDelete(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
type="danger"
:size="size"
:disabled="!hasAuth('locale:btn:delete')"
:icon="useRenderIcon(Delete)"
>
{{ t("buttons:Delete") }}
</el-button>
</template>
</el-popconfirm>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "LocaleIndex"
});
import { ref } from "vue";
import { useLocale } from "./utils/hook";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { useI18n } from "vue-i18n";
import Delete from "@iconify-icons/ep/delete";
import EditPen from "@iconify-icons/ep/edit-pen";
import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
import Download from "@iconify-icons/ri/file-download-line";
const { t } = useI18n();
import { hasAuth } from "@/utils/auth";
/**
* 表格Ref
*/
const tableRef = ref();
const {
form,
formRef,
dataList,
loading,
pagination,
columns,
selectedNum,
onSearch,
openDialog,
resetForm,
export_to_yaml,
handleDelete,
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
onSelectionCancel,
onbatchDel
} = useLocale(tableRef);
</script>
<style scoped lang="scss">
:deep(.el-dropdown-menu__item i) {
margin: 0;
}
:deep(.el-button:focus-visible) {
outline: none;
}
.main-content {
margin: 24px 24px 0 !important;
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
</style>

View File

@ -0,0 +1,303 @@
import dayjs from "dayjs";
import editForm from "../components/form.vue";
import { message } from "@/utils/message";
import { type Ref, ref, reactive, onMounted, h } from "vue";
import type { LanguageInfo, TranslationInfo } from "types/system";
import type { PaginationProps } from "@pureadmin/table";
import { addDialog } from "@/components/ReDialog";
import {
getLocaleListAPI,
deleteLocaleAPI,
deleteLocaleListAPI,
postAddLocaleAPI,
putUpdateLocaleAPI,
getI18nHandleListAPI
} from "@/api/i18n";
import jsyaml from "js-yaml";
import { getKeyList } from "@pureadmin/utils";
export const useLocale = (tableRef: Ref) => {
/**
*
*/
const form = reactive({
name: "",
code: ""
});
/**
* Ref
*/
const formRef = ref(null);
/**
*
*/
const dataList = ref<LanguageInfo[]>([]);
/**
*
*/
const loading = ref(true);
/**
*
*/
const selectedNum = ref<number>(0);
/**
*
*/
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true,
pageSizes: [10, 20, 30, 40, 50]
});
/**
*
*/
const columns: TableColumnList = [
{
label: "勾选列", // 如果需要表格多选此处label必须设置
type: "selection",
fixed: "left",
reserveSelection: true // 数据刷新后保留选项
},
{
label: "语言编码",
prop: "code"
},
{
label: "语言名称",
prop: "name"
},
{
label: "创建时间",
prop: "create_time",
formatter: ({ create_time }) =>
dayjs(create_time).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",
fixed: "right",
width: 250,
slot: "operation"
}
];
/**
*
*/
const onSearch = async () => {
loading.value = true;
const res = await getLocaleListAPI({
page: pagination.currentPage,
pageSize: pagination.pageSize,
name: form.name,
code: form.code
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
loading.value = false;
};
/**
*
* @param formEl ref
* @returns
*/
const resetForm = (formEl: any) => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
/**
*
* @param row
*/
const handleDelete = async (row: TranslationInfo) => {
const res = await deleteLocaleAPI(row.id);
if (res.success) {
onSearch();
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/**
*
*/
const handleSizeChange = async (val: number) => {
const res = await getLocaleListAPI({
page: pagination.currentPage,
pageSize: val,
name: form.name,
code: form.code
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/**
*
* @param val
*/
const handleCurrentChange = async (val: number) => {
const res = await getLocaleListAPI({
page: val,
pageSize: pagination.pageSize,
name: form.name,
code: form.code
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/** 当CheckBox选择项发生变化时会触发该事件 */
const handleSelectionChange = async (val: any) => {
selectedNum.value = val.length;
// 重置表格高度
tableRef.value.setAdaptive();
};
/** 取消选择 */
const onSelectionCancel = async () => {
selectedNum.value = 0;
// 用于多选表格,清空用户的选择
tableRef.value.getTableRef().clearSelection();
};
/**
*
*/
const onbatchDel = async () => {
// 返回当前选中的行
const curSelected = tableRef.value.getTableRef().getSelectionRows();
const res = await deleteLocaleListAPI(getKeyList(curSelected, "id"));
if (res.success) {
message(res.msg, {
type: "success"
});
tableRef.value.getTableRef().clearSelection();
onSearch();
} else {
message(res.msg, { type: "error", duration: 5000 });
}
};
const openDialog = async (title = "新增", row?: LanguageInfo) => {
addDialog({
title: `${title}国际化项`,
props: {
formInline: {
title: title,
name: row?.name ?? "",
code: row?.code ?? ""
}
},
width: "45%",
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () =>
h(editForm, {
formInline: {
title: title,
name: row?.name ?? "",
code: row?.code ?? ""
},
ref: formRef
}),
beforeSure: async (done, {}) => {
const FormData = formRef.value.newFormInline;
let form = {
name: FormData.name ?? "",
code: FormData.code ?? ""
};
if (title === "新增") {
const res = await postAddLocaleAPI(form);
if (res.success) {
done();
await onSearch();
}
message(res.msg, { type: res.success ? "success" : "error" });
} else {
const res = await putUpdateLocaleAPI(form, row.id);
if (res.success) {
done();
await onSearch();
}
message(res.msg, { type: res.success ? "success" : "error" });
}
}
});
};
/**
* YAML
*/
const export_to_yaml = async (row: LanguageInfo) => {
const res = await getI18nHandleListAPI(row.id); // 调用 API 获取数据
if (res.success) {
// 将 JSON 转换为 YAML
const yamlString = jsyaml.dump(res.data.data);
// 创建 Blob 对象
const blob = new Blob([yamlString], { type: "text/yaml" });
// 生成下载链接
const url = URL.createObjectURL(blob);
// 创建 <a> 元素并触发下载
const link = document.createElement("a");
link.href = url;
link.download = `${row.code}.yaml`; // 设置下载文件名
document.body.appendChild(link); // 将 <a> 元素添加到 DOM 中
link.click(); // 模拟点击下载
// 清理 URL 对象
URL.revokeObjectURL(url);
document.body.removeChild(link); // 移除 <a> 元素
}
};
/**
*
*/
onMounted(async () => {
await onSearch();
});
return {
form,
formRef,
dataList,
loading,
pagination,
columns,
selectedNum,
onSearch,
openDialog,
resetForm,
export_to_yaml,
handleDelete,
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
onSelectionCancel,
onbatchDel
};
};

View File

@ -224,11 +224,14 @@ defineExpose({ getRef });
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="菜单名称" prop="title">
<el-form-item
:label="newFormInline.menu_type === 3 ? '权限名称' : '菜单名称'"
prop="title"
>
<el-input
v-model="newFormInline.title"
clearable
placeholder="请输入菜单名称"
:placeholder="`请输入${newFormInline.menu_type === 3 ? '权限名称' : '菜单名称'}~`"
/>
</el-form-item>
</re-col>
@ -287,7 +290,9 @@ defineExpose({ getRef });
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="菜单排序">
<el-form-item
:label="newFormInline.menu_type === 3 ? '权限排序' : '菜单排序'"
>
<el-input-number
v-model="newFormInline.rank"
class="!w-full"

View File

@ -10,6 +10,7 @@ import EditPen from "@iconify-icons/ep/edit-pen";
import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
import { onBeforeRouteUpdate } from "vue-router";
import { hasAuth } from "@/utils/auth";
const { t } = useI18n();
defineOptions({
name: "SystemPermission"
@ -78,6 +79,7 @@ onBeforeRouteUpdate((to, from, next) => {
>
<template #buttons>
<el-button
v-if="hasAuth('permission:btn:add')"
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog()"
@ -111,6 +113,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('permission:btn:update')"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
@ -122,6 +125,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('permission:btn:add')"
:icon="useRenderIcon(AddFill)"
@click="openDialog('新增', { parent_id: row.id } as any)"
>
@ -135,7 +139,8 @@ onBeforeRouteUpdate((to, from, next) => {
<el-button
class="reset-margin"
link
type="primary"
type="danger"
:disabled="!hasAuth('permission:btn:delete')"
:size="size"
:icon="useRenderIcon(Delete)"
>

View File

@ -267,7 +267,7 @@ export const usePermission = () => {
}
}
const res = await postAddPermissionAPI(addForm);
if (res.code === 200) {
if (res.success) {
// 实际开发先调用新增接口,再进行下面操作
chores();
} else {
@ -305,7 +305,7 @@ export const usePermission = () => {
}
}
const res = await putUpdatePermissionAPI(curData.id, updateForm);
if (res.code === 200) {
if (res.success) {
chores();
} else {
message(`更新失败!`, { type: "error" });
@ -318,7 +318,7 @@ export const usePermission = () => {
};
const handleDelete = async (row: PermissionInfo) => {
const res = await deletePermissionAPI(row.id);
if (res.code === 200) {
if (res.success) {
message(`您删除了权限名称为${transformI18n(row.title)}的这条数据`, {
type: "success"
});

View File

@ -4,6 +4,7 @@ import type { FormRules } from "element-plus";
import { getDepartmentListAPI } from "@/api/system";
import type { DepartmentInfo } from "types/system";
import { usePublicHooks } from "../../hooks";
import { handleTree } from "@pureadmin/utils";
defineOptions({
name: "SystemRoleForm"
@ -52,8 +53,10 @@ function getRef() {
/**获取部门列表 */
const getDepartments = async () => {
const res = await getDepartmentListAPI({ page: 1, pageSize: 9999 });
if (res.code === 200) {
departments.value = formatHigherOptions(res.data.result);
if (res.success) {
departments.value = formatHigherOptions(
handleTree(res.data.result, "id", "parent_id")
);
} else {
departments.value = [];
}

View File

@ -19,6 +19,7 @@ import Close from "@iconify-icons/ep/close";
import Check from "@iconify-icons/ep/check";
import { onBeforeRouteUpdate } from "vue-router";
const { t } = useI18n();
import { hasAuth } from "@/utils/auth";
defineOptions({
name: "SystemRole"
});
@ -51,8 +52,8 @@ const {
curRow,
loading,
columns,
rowStyle,
dataList,
selectedNum,
treeData,
treeProps,
isLinkage,
@ -61,8 +62,11 @@ const {
isSelectAll,
treeSearchValue,
departments,
rowStyle,
onSearch,
resetForm,
onSelectionCancel,
onbatchDel,
openDialog,
handleMenu,
handleSave,
@ -71,8 +75,9 @@ const {
transformI18n,
onQueryChanged,
handleSizeChange,
handleCurrentChange
} = useRole(treeRef);
handleCurrentChange,
handleSelectionChange
} = useRole(treeRef, tableRef);
onMounted(() => {
useResizeObserver(contentRef, async () => {
await nextTick();
@ -180,6 +185,7 @@ onBeforeRouteUpdate((to, from, next) => {
>
<template #buttons>
<el-button
v-if="hasAuth('role:btn:add')"
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog()"
@ -188,8 +194,42 @@ onBeforeRouteUpdate((to, from, next) => {
</el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<div
v-if="selectedNum > 0"
v-motion-fade
class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center"
>
<div class="flex-auto">
<span
style="font-size: var(--el-font-size-base)"
class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
>
已选 {{ selectedNum }}
</span>
<el-button type="primary" text @click="onSelectionCancel">
{{ t("buttons:Deselect") }}
</el-button>
</div>
<el-popconfirm
v-if="hasAuth('role:btn:delete')"
title="是否确认删除?"
@confirm="onbatchDel"
>
<template #reference>
<el-button
type="danger"
text
class="mr-1"
:disabled="selectedNum < 0 || hasAuth('role:btn:delete')"
>
{{ t("buttons:DeleteInBatches") }}
</el-button>
</template>
</el-popconfirm>
</div>
<pure-table
ref="tableRef"
row-key="id"
align-whole="center"
showOverflowTooltip
table-layout="auto"
@ -208,6 +248,7 @@ onBeforeRouteUpdate((to, from, next) => {
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
>
@ -217,6 +258,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('role:btn:update')"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
@ -232,6 +274,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="danger"
:size="size"
:disabled="!hasAuth('role:btn:delete')"
:icon="useRenderIcon(Delete)"
>
{{ t("buttons:Delete") }}
@ -243,6 +286,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('role:btn:permissionInfo')"
:icon="useRenderIcon(Menu)"
@click="handleMenu(row)"
>

View File

@ -4,6 +4,7 @@ import { message } from "@/utils/message";
import {
deleteRoleAPI,
deleteRoleListAPI,
getPermissionListAPI,
getRoleListAPI,
postAddRoleAPI,
@ -26,7 +27,7 @@ import type {
RolePermissionInfo
} from "types/system";
export const useRole = (treeRef: Ref) => {
export const useRole = (treeRef: Ref, tableRef: Ref) => {
/**查询表单 */
const form = reactive({
name: "",
@ -47,6 +48,10 @@ export const useRole = (treeRef: Ref) => {
*
*/
const loading = ref(true);
/**
*
*/
const selectedNum = ref<number>(0);
/**
*
*/
@ -87,6 +92,12 @@ export const useRole = (treeRef: Ref) => {
/**是否全选 */
const isSelectAll = ref<boolean>(false);
const columns: TableColumnList = [
{
label: "勾选列", // 如果需要表格多选此处label必须设置
type: "selection",
fixed: "left",
reserveSelection: true // 数据刷新后保留选项
},
{
label: "角色名称",
prop: "name",
@ -150,7 +161,10 @@ export const useRole = (treeRef: Ref) => {
dataList.value = data.data.result;
pagination.total = data.data.total;
pagination.currentPage = data.data.page;
pagination.pageSize = data.data.pageSize;
message(data.msg, {
type: data.success ? "success" : "error"
});
setTimeout(() => {
loading.value = false;
}, 500);
@ -166,30 +180,69 @@ export const useRole = (treeRef: Ref) => {
page: pagination.currentPage,
pageSize: val
});
if (res.code === 200) {
if (res.success) {
const data = res.data;
dataList.value = data.result;
pagination.total = data.total;
pagination.currentPage = data.page;
pagination.pageSize = res.data.pageSize;
}
};
/**处理每页数量变化 */
/**
*
* @param val
*/
const handleCurrentChange = async (val: number) => {
loading.value = true;
const res = await getRoleListAPI({
page: val,
pageSize: pagination.pageSize
pageSize: pagination.pageSize,
...toRaw(form)
});
if (res.code === 200) {
const data = res.data;
dataList.value = data.result;
pagination.total = data.total;
pagination.currentPage = data.page;
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
loading.value = false;
};
/** 当CheckBox选择项发生变化时会触发该事件 */
const handleSelectionChange = async (val: any) => {
selectedNum.value = val.length;
// 重置表格高度
tableRef.value.setAdaptive();
};
/** 取消选择 */
const onSelectionCancel = async () => {
selectedNum.value = 0;
// 用于多选表格,清空用户的选择
tableRef.value.getTableRef().clearSelection();
};
/**
*
*/
const onbatchDel = async () => {
// 返回当前选中的行
const curSelected = tableRef.value.getTableRef().getSelectionRows();
const res = await deleteRoleListAPI(getKeyList(curSelected, "id"));
if (res.success) {
message(res.msg, {
type: "success"
});
tableRef.value.getTableRef().clearSelection();
onSearch();
} else {
message(res.msg, { type: "error", duration: 5000 });
}
};
const handleDelete = async (row: RoleInfo) => {
const res = await deleteRoleAPI(row.id);
if (res.code === 200) {
if (res.success) {
message(`您删除了角色名称为${row.name}的这条数据`, { type: "success" });
onSearch();
} else {
@ -237,12 +290,11 @@ export const useRole = (treeRef: Ref) => {
}
FormRef.validate(async (valid: any) => {
if (valid) {
console.log("curData", curData);
// 表单规则校验通过
if (title === "新增") {
// 实际开发先调用新增接口,再进行下面操作
const res = await postAddRoleAPI(curData);
if (res.code === 200) {
if (res.success) {
chores();
} else {
message(`添加失败!`, {
@ -253,7 +305,7 @@ export const useRole = (treeRef: Ref) => {
} else {
// 实际开发先调用修改接口,再进行下面操作
const res = await putUpdateRoleAPI(curData, row.id);
if (res.code === 200) {
if (res.success) {
chores();
} else {
message(`修改失败!`, {
@ -310,7 +362,7 @@ export const useRole = (treeRef: Ref) => {
const res = await putUpdateRolePermissionsAPI(id, {
permission_ids: permissions
});
if (res.code === 200) {
if (res.success) {
message(`角色名称为${name}的权限修改成功~`, {
type: "success"
});
@ -334,14 +386,16 @@ export const useRole = (treeRef: Ref) => {
/**获取部门列表 */
const getDepartments = async () => {
const res = await getDepartmentListAPI({ page: 1, pageSize: 9999 });
if (res.code === 200) {
departments.value = formatHigherOptions(res.data.result);
if (res.success) {
departments.value = formatHigherOptions(
handleTree(res.data.result, "id", "parent_id")
);
} else {
departments.value = [];
}
};
const formatHigherOptions = treeList => {
// 根据返回数据的status字段值判断追加是否禁用disabled字段返回处理后的树结构用于上级部门级联选择器的展示
const formatHigherOptions = (treeList: any) => {
// 根据返回数据的status字段值判断追加是否禁用disabled字段返回处理后的树结构用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理)
if (!treeList || !treeList.length) return;
const newTreeList = [];
for (let i = 0; i < treeList.length; i++) {
@ -379,6 +433,7 @@ export const useRole = (treeRef: Ref) => {
loading,
columns,
dataList,
selectedNum,
treeData,
treeProps,
isLinkage,
@ -390,6 +445,8 @@ export const useRole = (treeRef: Ref) => {
rowStyle,
onSearch,
resetForm,
onSelectionCancel,
onbatchDel,
openDialog,
handleMenu,
handleSave,
@ -398,6 +455,7 @@ export const useRole = (treeRef: Ref) => {
transformI18n,
onQueryChanged,
handleSizeChange,
handleCurrentChange
handleCurrentChange,
handleSelectionChange
};
};

View File

@ -16,6 +16,7 @@ import EditPen from "@iconify-icons/ep/edit-pen";
import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
import { onBeforeRouteUpdate } from "vue-router";
import { hasAuth } from "@/utils/auth";
defineOptions({
name: "SystemUser"
});
@ -117,6 +118,7 @@ onBeforeRouteUpdate((to, from, next) => {
<PureTableBar title="用户管理" :columns="columns" @refresh="onSearch">
<template #buttons>
<el-button
v-if="hasAuth('user:btn:addUser')"
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog()"
@ -141,7 +143,11 @@ onBeforeRouteUpdate((to, from, next) => {
{{ t("buttons:Deselect") }}
</el-button>
</div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
<el-popconfirm
v-if="hasAuth('user:btn:deleteUser')"
title="是否确认删除?"
@confirm="onbatchDel"
>
<template #reference>
<el-button type="danger" text class="mr-1">
{{ t("buttons:DeleteInBatches") }}
@ -178,13 +184,14 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('user:btn:updateUser')"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
{{ t("buttons:Update") }}
</el-button>
<el-popconfirm
:title="`是否确认删除用户名称为${row.name}的这条数据?`"
:title="`是否确认删除用户名称为${row.username}的这条数据?`"
@confirm="handleDelete(row)"
>
<template #reference>
@ -193,6 +200,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="danger"
:size="size"
:disabled="!hasAuth('user:btn:deleteUser')"
:icon="useRenderIcon(Delete)"
>
{{ t("buttons:Delete") }}
@ -216,6 +224,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('user:btn:uploadAvatar')"
:icon="useRenderIcon(Upload)"
@click="handleUpload(row)"
>
@ -228,6 +237,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('user:btn:reset_password')"
:icon="useRenderIcon(Password)"
@click="handleReset(row)"
>
@ -240,6 +250,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('user:btn:updateRole')"
:icon="useRenderIcon(Role)"
@click="handleRole(row)"
>
@ -252,6 +263,7 @@ onBeforeRouteUpdate((to, from, next) => {
link
type="primary"
:size="size"
:disabled="!hasAuth('user:btn:permissionList')"
:icon="useRenderIcon(Menu)"
@click="openPerDialog(row)"
>

View File

@ -105,8 +105,8 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => {
<el-image
fit="cover"
preview-teleported={true}
src={row.avatar || Avatar}
preview-src-list={Array.of(row.avatar || Avatar)}
src={`/api/${row.avatar}` || Avatar}
preview-src-list={Array.of(`/api/${row.avatar}` || Avatar)}
class="w-[24px] h-[24px] rounded-full align-middle"
/>
)
@ -191,7 +191,7 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => {
*/
const handleDelete = async (row: UserInfo) => {
const res = await deleteUserAPI(row.id);
if (res.code === 200) {
if (res.success) {
message(`您删除了用户账号为${row.username}的这条数据`, {
type: "success"
});
@ -261,10 +261,10 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => {
// 返回当前选中的行
const curSelected = tableRef.value.getTableRef().getSelectionRows();
const res = await deleteUserListAPI({
userIds: getKeyList(curSelected, "id")
ids: getKeyList(curSelected, "id")
});
if (res.code === 200) {
message(`已删除用户编号为 ${getKeyList(curSelected, "id")} 的数据`, {
if (res.success) {
message(res.msg, {
type: "success"
});
tableRef.value.getTableRef().clearSelection();
@ -404,7 +404,7 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => {
}
}
const res = await postAddUserAPI(addForm);
if (res.code === 200) {
if (res.success) {
// 实际开发先调用新增接口,再进行下面操作
chores();
} else {
@ -430,7 +430,7 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => {
}
}
const res = await putUpdateUserAPI(curData.id, updateForm);
if (res.code === 200) {
if (res.success) {
chores();
} else {
message(`更新失败!`, { type: "error" });
@ -453,14 +453,14 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => {
contentRenderer: () =>
h(croppingUpload, {
ref: cropRef,
imgSrc: row.avatar || Avatar,
imgSrc: `/api/${row.avatar}` || Avatar,
onCropper: info => (avatarInfo.value = info)
}),
beforeSure: async done => {
const res = await postUploadAvatarAPI(row.id, {
file: avatarInfo.value.blob
});
if (res.code === 200) {
if (res.success) {
// 根据实际业务使用avatarInfo.value和row里的某些字段去调用上传头像接口即可
message(`更新成功!`, { type: "success" });
onSearch(); // 刷新表格数据
@ -541,7 +541,7 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => {
const res = await putUpdateUserPasswordAPI(row.id, {
password: pwdForm.newPwd
});
if (res.code === 200) {
if (res.success) {
console.log(pwdForm.newPwd);
done();
message(`已成功重置 ${row.username} 的密码`, {
@ -593,7 +593,7 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => {
user_id: row.id,
role_ids: curData.ids as string[]
});
if (res.code === 200) {
if (res.success) {
message(`${row.username}--${row.nickname}的角色信息更新成功!`, {
type: "success",
duration: 5000