Merge branch 'dev'

This commit is contained in:
bunny 2025-05-02 15:47:43 +08:00
commit 88857fd01d
42 changed files with 357 additions and 101 deletions

View File

@ -32,21 +32,19 @@
- [运行项目](https://www.bilibili.com/video/BV1qodHYzErA/?spm_id_from=333.1387.homepage.video_card.click&vd_source=d42b5b664efb958be39eef8ee1196a7e)
- [前端部署](https://www.bilibili.com/video/BV1BddHYgEPq/?spm_id_from=333.1387.homepage.video_card.click&vd_source=d42b5b664efb958be39eef8ee1196a7e)
- [后端部署](https://www.bilibili.com/video/BV1BddHYgEFt/?spm_id_from=333.1387.homepage.video_card.click&vd_source=d42b5b664efb958be39eef8ee1196a7e)
- [Bunny v0.0.1 代码生成器](https://www.bilibili.com/video/BV1qddHYgErv/?spm_id_from=333.1387.homepage.video_card.click)
- [代码生成器](https://www.bilibili.com/video/BV1d4Lxz9E3j/?vd_source=d42b5b664efb958be39eef8ee1196a7e)
**Github地址**
- 权限后端https://github.com/BunnyMaster/bunny-admin-server
- 权限前端https://github.com/BunnyMaster/bunny-admin-web
- 代码生成器前端https://github.com/BunnyMaster/generator-code-web
- 代码生成器后端https://github.com/BunnyMaster/generator-code-server
- 代码生成器端https://github.com/BunnyMaster/generator-code-server
**`Gitee`地址**
**Gitee地址**
- 权限后端https://gitee.com/BunnyBoss/bunny-admin-server
- 权限前端https://gitee.com/BunnyBoss/bunny-admin-web
- 代码生成器前端https://gitee.com/BunnyBoss/generator-code-web
- 代码生成器后端https://gitee.com/BunnyBoss/generator-code-server
- 代码生成器端https://gitee.com/BunnyBoss/generator-code-server
## 🚀 项目简介
@ -83,6 +81,21 @@
![权限管理界面](./images/image-20250428223816172.png)
![角色管理界面](./images/image-20250428223843974.png)
## :tipping_hand_man:用法提示
> [!TIP]
>
> 多语言使用提示:
>
> 虽然直接让用户操作JSON文件有一定门槛多数用户不熟悉JSON格式但在多语言项目开发中JSON格式具有独特优势
>
> 1. 结构化特性 - 纯文本格式便于AI解析处理
> 2. 高效翻译流程:
> - 开发者只需完成中文版本
> - 上传JSON文件至AI翻译工具
> - 简单指令即可批量生成英文/繁体中文/韩语等版本
> 3. 显著节省开发时间 - 实现"一次编写,多语言适配"的高效工作流
## 🔐 权限控制体系
![image-20250428225337843](./images/image-20250428225337843-1745854181492-5.png)
@ -240,10 +253,10 @@ docker compose up -d
## 📈 后续规划
- [ ] 权限级别拖拽
- [ ] 权限树型结构动态添加、更新、删除
- [x] 权限级别拖拽
- [x] 权限树型结构动态添加、更新、删除
- [ ] 用户设置持久化存储到数据库
- [ ] 权限弹窗页面优化
- [x] 权限弹窗页面优化
- [x] 后端文档注释完善
- [x] 系统监控后端返回403停止请求
- [x] 优化用户配置权限逻辑,配置后热更新逻辑等

View File

@ -8,7 +8,7 @@ export const defaultConfig: AxiosRequestConfig = {
// 默认请求地址
baseURL: '/api',
// 设置超时时间
timeout: 6000,
timeout: 19000,
// @ts-expect-error
retry: 3, //设置全局重试请求次数(最多重试几次请求)
retryDelay: 3000, //设置全局请求间隔

View File

@ -43,9 +43,14 @@ export const getSystemApiInfoList = () => {
return http.request<BaseResult<any>>('get', 'permission/private/getSystemApiInfoList');
};
/** 权限---更新权限 */
/** 权限---批量修改权限父级 */
export const updatePermissionListByParentId = (data: any) => {
return http.request<BaseResult<object>>('put', 'permission/update/permissionListByParentId', { data });
return http.request<BaseResult<object>>('patch', 'permission/update/permissionListByParentId', { data });
};
/** 权限---批量更新权限 */
export const updatePermissionBatch = (data: any) => {
return http.request<BaseResult<object>>('patch', 'permission/update/permissionBatch', { data });
};
/** 角色和权限---根据角色id获取权限内容 */

View File

@ -74,7 +74,6 @@ export const userI18nStore = defineStore('i18nStore', {
/* 用文件更新多语言 */
async editI18nByFile(data: any) {
console.log(data);
const result = await uploadI18nFile(data);
return storeMessage(result);
},

View File

@ -8,6 +8,7 @@ import {
getSystemApiInfoList,
importPermission,
updatePermission,
updatePermissionBatch,
updatePermissionListByParentId,
} from '@/api/v1/system/power';
import { pageSizes } from '@/enums/baseConstant';
@ -57,7 +58,6 @@ export const usePermissionStore = defineStore('PermissionStore', {
const data = { ...this.pagination, ...this.form };
delete data.pageSizes;
delete data.total;
delete data.background;
// 获取权限列表
const result = await getPermissionPage(data);
@ -117,5 +117,11 @@ export const usePermissionStore = defineStore('PermissionStore', {
const result = await updatePermissionListByParentId(data);
return storeMessage(result);
},
/* 批量更新权限 */
async updatePermissionBatch(data: any) {
const result = await updatePermissionBatch(data);
return storeMessage(result);
},
},
});

View File

@ -23,7 +23,6 @@ function onResetPassword() {
addDialog({
title: `修改密码`,
width: '30%',
draggable: true,
closeOnClickModal: false,
fullscreenIcon: true,

View File

@ -97,8 +97,9 @@ onMounted(() => {
</ReAuth>
<PureTableBar :columns="columns" :title="$t('emailTemplate')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<!-- 新增 -->
<template #buttons>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>

View File

@ -24,7 +24,6 @@ export function onAdd() {
addDialog({
title: `${$t('addNew')}${$t('emailTemplate')}`,
width: '30%',
props: {
formInline: {
templateName: undefined,
@ -38,7 +37,18 @@ export function onAdd() {
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(EmailTemplateDialog, { ref: formRef }),
contentRenderer: () =>
h(EmailTemplateDialog, {
ref: formRef,
formInline: {
templateName: undefined,
emailUser: undefined,
subject: undefined,
isDefault: false,
body: undefined,
type: undefined,
},
}),
beforeSure: (done, { options }) => {
const form = options.props.formInline as FormItemProps;
formRef.value.formRef.validate(async (valid: any) => {
@ -59,7 +69,6 @@ export function onUpdate(row: any) {
addDialog({
title: `${$t('modify')}${$t('emailTemplate')}`,
width: '30%',
props: {
formInline: {
templateName: row.templateName,
@ -73,7 +82,18 @@ export function onUpdate(row: any) {
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(EmailTemplateDialog, { ref: formRef }),
contentRenderer: () =>
h(EmailTemplateDialog, {
ref: formRef,
formInline: {
templateName: row.templateName,
emailUser: row.emailUser,
subject: row.subject,
isDefault: row.isDefault,
body: row.body,
type: row.type,
},
}),
beforeSure: (done, { options }) => {
const form = options.props.formInline as FormItemProps;

View File

@ -120,7 +120,7 @@ onMounted(() => {
<PureTableBar :columns="columns" :title="$t('email_user_send_config')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>

View File

@ -24,7 +24,7 @@ export async function onSearch() {
export function onAdd() {
addDialog({
title: `${$t('addNew')}${$t('emailUsers')}`,
width: '30%',
props: {
formInline: {
email: undefined,
@ -58,7 +58,7 @@ export function onAdd() {
export function onUpdate(row: any) {
addDialog({
title: `${$t('modify')}${$t('emailUsers')}`,
width: '30%',
props: {
formInline: {
email: row.email,

View File

@ -70,7 +70,7 @@ onMounted(() => {
<PureTableBar :columns="columns" :title="$t('menuIcon')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>

View File

@ -21,7 +21,7 @@ export async function onSearch() {
export function onAdd() {
addDialog({
title: `${$t('addNew')} ${$t('menuIcon')}`,
width: '30%',
props: { formInline: { confirmText: '' } },
draggable: true,
fullscreenIcon: true,
@ -45,7 +45,7 @@ export function onAdd() {
export function onUpdate(row: any) {
addDialog({
title: `${$t('modify')} ${$t('menuIcon')}`,
width: '30%',
props: {
formInline: {
iconCode: row.iconCode,

View File

@ -333,7 +333,7 @@ onMounted(() => {
<!-- 提交内容 -->
<re-col v-if="hasAuth(auth.update)" :sm="24" :value="24" :xs="24">
<el-form-item>
<el-form-item label-width="0">
<el-button class="w-full" plain type="primary" @click="submitForm(ruleFormRef)">
{{ $t('modifyingConfiguration') }}
</el-button>

View File

@ -76,9 +76,11 @@ onMounted(() => {
<el-input v-model="i18nStore.form.typeName" :placeholder="`${$t('input')}${$t('i18n.typeName')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item>
<!-- 表格頂部搜索 -->
<el-button :icon="useRenderIcon('ri/search-line')" :loading="i18nStore.loading" type="primary" @click="onSearch">
{{ $t('search') }}
</el-button>
<!-- 表格頂部重置 -->
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(pageFormRef)">
{{ $t('buttons.reset') }}
</el-button>
@ -95,7 +97,9 @@ onMounted(() => {
</el-button>
<template #dropdown>
<el-dropdown-menu>
<!-- 到處為JSON -->
<el-dropdown-item @click="downloadI18nSetting('json')">{{ $t('download_json') }}</el-dropdown-item>
<!--導出爲Excel-->
<el-dropdown-item @click="downloadI18nSetting('excel')">{{ $t('download_excel') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
@ -113,7 +117,7 @@ onMounted(() => {
</el-dropdown>
<!-- 添加多语言 -->
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>
@ -148,12 +152,14 @@ onMounted(() => {
@page-size-change="onPageSizeChange"
@page-current-change="onCurrentPageChange"
>
<!-- 創建用戶名 -->
<template #createUser="{ row }">
<el-button v-show="row.createUser" link type="primary" @click="selectUserinfo(row.createUser)">
{{ row.createUsername }}
</el-button>
</template>
<!-- 更新用戶名 -->
<template #updateUser="{ row }">
<el-button v-show="row.updateUser" link type="primary" @click="selectUserinfo(row.updateUser)">
{{ row.updateUsername }}
@ -161,9 +167,12 @@ onMounted(() => {
</template>
<template #operation="{ row }">
<!-- 修改 -->
<el-button v-if="hasAuth(auth.update)" :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)">
{{ $t('modify') }}
</el-button>
<!-- 刪除確認 -->
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('confirmDelete')} ${row.translation}`" @confirm="onDelete(row)">
<template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">

View File

@ -29,7 +29,7 @@ export const updateI18nSetting = (fileType: string) => {
addDialog({
title: $t('update_multilingual'),
width: '30%',
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
@ -51,7 +51,7 @@ export const updateI18nSetting = (fileType: string) => {
export const onAdd = () => {
addDialog({
title: $t('addMultilingual'),
width: '30%',
props: { formInline: { keyName: '', translation: '', typeName: '' } },
draggable: true,
fullscreenIcon: true,
@ -107,7 +107,7 @@ export const onUpdate = (row: any) => {
addDialog({
title: $t('update_multilingual'),
width: '30%',
props: {
formInline: { keyName: row.keyName, translation: row.translation, typeName: row.typeName },
},

View File

@ -50,7 +50,7 @@ onMounted(() => {
<PureTableBar :columns="columns" :title="$t('i18n_type')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>
</template>

View File

@ -19,7 +19,7 @@ export async function onSearch() {
export function onAdd() {
addDialog({
title: `添加多语言类型`,
width: '30%',
props: {
formInline: { typeName: '', summary: '', isDefault: false },
},
@ -45,7 +45,7 @@ export function onAdd() {
export function onUpdate(row: any) {
addDialog({
title: `修改多语言类型`,
width: '30%',
props: {
formInline: {
typeName: row.typeName,

View File

@ -151,7 +151,7 @@ onMounted(() => {
<!-- 简介 -->
<el-form-item :label="$t('summary')" prop="summary">
<el-input v-model="formState.summary" :autosize="{ minRows: 3, maxRows: 6 }" maxlength="200" minlength="4" show-word-limit type="textarea" />
<el-input v-model="formState.summary" :autosize="{ minRows: 3, maxRows: 6 }" maxlength="100" minlength="4" show-word-limit type="textarea" />
</el-form-item>
<!-- 消息等级 -->

View File

@ -23,7 +23,7 @@ defineExpose({ formRef });
</script>
<template>
<el-card shadow="never" style="height: calc(100vh - 200px); overflow: auto">
<el-card shadow="never" style="height: calc(100vh - 300px); overflow: auto">
<div class="split-pane">
<SplitPane :splitSet="settingLR">
<template #paneL>
@ -120,7 +120,7 @@ defineExpose({ formRef });
<!-- 简介 -->
<el-form-item :label="$t('summary')" prop="summary">
<el-input v-model="updateMessage.summary" :autosize="{ minRows: 3, maxRows: 6 }" maxlength="200" minlength="10" show-word-limit type="textarea" />
<el-input v-model="updateMessage.summary" :autosize="{ minRows: 3, maxRows: 6 }" maxlength="50" minlength="10" show-word-limit type="textarea" />
</el-form-item>
<!-- 消息等级 -->

View File

@ -91,7 +91,7 @@ onMounted(() => {
<PureTableBar :columns="columns" title="系统消息类型" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>

View File

@ -22,7 +22,7 @@ export async function onSearch() {
export function onAdd() {
addDialog({
title: `${$t('addNew')}${$t('messageType')}`,
width: '30%',
props: {
formInline: {
status: true,
@ -53,7 +53,7 @@ export function onAdd() {
export function onUpdate(row: any) {
addDialog({
title: `${$t('modify')}${$t('messageType')}`,
width: '30%',
props: {
formInline: {
status: row.status,

View File

@ -21,7 +21,7 @@ export async function onSearch() {
export function onView(row: any) {
addDialog({
title: `${$t('view')}${$t('quartzExecuteLog')}`,
width: '30%',
props: {
formInline: {
jobName: row.jobName,

View File

@ -21,7 +21,7 @@ export async function onSearch() {
export function onView(row: any) {
addDialog({
title: `${$t('view')}${$t('userLoginLog')}`,
width: '30%',
props: {
formInline: {
userId: row.userId,

View File

@ -81,7 +81,7 @@ onMounted(() => {
<PureTableBar :columns="columns" :title="$t('schedulersGroup')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>

View File

@ -22,7 +22,7 @@ export async function onSearch() {
export function onAdd() {
addDialog({
title: `${$t('addNew')}${$t('schedulersGroup')}`,
width: '30%',
props: {
formInline: {
groupName: undefined,
@ -51,7 +51,7 @@ export function onAdd() {
export function onUpdate(row: any) {
addDialog({
title: `${$t('modify')}${$t('schedulersGroup')}`,
width: '30%',
props: {
formInline: {
groupName: row.groupName,

View File

@ -78,7 +78,7 @@ onMounted(() => {
<PureTableBar :columns="columns" title="Schedulers视图" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>
</template>
@ -119,7 +119,8 @@ onMounted(() => {
</template>
<template #operation="{ row }">
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)">
<!-- 修改 -->
<el-button v-if="hasAuth(auth.update)" :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)">
{{ $t('modify') }}
</el-button>

View File

@ -4,6 +4,7 @@ export const auth = {
// 添加操作
add: ['schedulers::add'],
// 暂停
update: ['schedulers::update'],
pause: ['schedulers::update'],
// 恢复
resume: ['schedulers::update'],

View File

@ -20,7 +20,7 @@ export async function onSearch() {
export function onAdd() {
addDialog({
title: `${$t('addNew')}${$t('schedulers')}`,
width: '30%',
props: {
formInline: {
jobName: undefined,
@ -52,7 +52,7 @@ export function onAdd() {
export function onUpdate(row: any) {
addDialog({
title: `${$t('modify')}${$t('schedulers')}`,
width: '30%',
props: {
formInline: {
jobName: row.jobName,

View File

@ -151,7 +151,7 @@ onMounted(() => {
<PureTableBar :columns="columns" :title="$t('userinfo')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>

View File

@ -52,7 +52,6 @@ export function onAdd() {
isAddUserinfo.value = true;
addDialog({
title: `${$t('addNew')}${$t('adminUser')}`,
width: '30%',
props: {
formInline: {
username: undefined,
@ -70,7 +69,22 @@ export function onAdd() {
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(AdminUserDialog, { ref: formRef }),
contentRenderer: () =>
h(AdminUserDialog, {
ref: formRef,
formInline: {
username: undefined,
nickname: undefined,
email: undefined,
phone: undefined,
password: undefined,
avatar: undefined,
sex: undefined,
summary: undefined,
status: false,
deptId: undefined,
},
}),
beforeSure: (done, { options }) => {
const form = options.props.formInline as FormItemProps;
formRef.value.formRef.validate(async (valid: any) => {
@ -90,7 +104,6 @@ export function onUpdate(row: any) {
isAddUserinfo.value = false;
addDialog({
title: `${$t('modify')}${$t('adminUser')}`,
width: '30%',
props: {
formInline: {
username: row.username,
@ -108,7 +121,22 @@ export function onUpdate(row: any) {
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(AdminUserDialog, { ref: formRef }),
contentRenderer: () =>
h(AdminUserDialog, {
ref: formRef,
formInline: {
username: row.username,
nickname: row.nickname,
email: row.email,
phone: row.phone,
password: row.password,
avatar: row.avatar,
sex: row.sex,
summary: row.summary,
status: row.status,
deptId: row.deptId,
},
}),
beforeSure: (done, { options }) => {
const form = options.props.formInline as FormItemProps;
formRef.value.formRef.validate(async (valid: any) => {
@ -149,12 +177,12 @@ export const onDeleteBatch = async () => {
addDialog({
title: $t('deleteBatchTip'),
width: '30%',
props: { formInline: { confirmText: '' } },
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(DeleteBatchDialog, { ref: formDeletedBatchRef }),
contentRenderer: () => h(DeleteBatchDialog, { ref: formDeletedBatchRef, formInline: { confirmText: '' } }),
beforeSure: (done, { options }) => {
formDeletedBatchRef.value.formDeletedBatchRef.validate(async (valid: any) => {
if (!valid) return;
@ -229,7 +257,7 @@ export const onUploadAvatar = (row: any) => {
export const onResetPassword = (row: any) => {
addDialog({
title: `${$t('buttons.reset')} ${row.username} ${$t('userPassword')}`,
width: '30%',
draggable: true,
closeOnClickModal: false,
fullscreenIcon: true,

View File

@ -79,7 +79,7 @@ onMounted(() => {
@refresh="onSearch"
>
<template #buttons>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd()">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd()">
{{ $t('addNew') }}
</el-button>

View File

@ -21,7 +21,6 @@ export async function onSearch() {
export function onAdd(parentId: number = 0) {
addDialog({
title: `${$t('addNew')}${$t('dept')}`,
width: '30%',
props: {
formInline: {
parentId,
@ -33,7 +32,16 @@ export function onAdd(parentId: number = 0) {
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(DeptDialog, { ref: formRef }),
contentRenderer: () =>
h(DeptDialog, {
ref: formRef,
formInline: {
parentId,
manager: undefined,
deptName: undefined,
summary: undefined,
},
}),
beforeSure: (done, { options }) => {
const form = options.props.formInline as FormItemProps;
formRef.value.formRef.validate(async (valid: any) => {
@ -52,7 +60,7 @@ export function onAdd(parentId: number = 0) {
export function onUpdate(row: any) {
addDialog({
title: `${$t('modify')}${$t('dept')}`,
width: '30%',
props: {
formInline: {
parentId: row.parentId,
@ -64,7 +72,16 @@ export function onUpdate(row: any) {
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(DeptDialog, { ref: formRef }),
contentRenderer: () =>
h(DeptDialog, {
ref: formRef,
formInline: {
parentId: row.parentId,
manager: row.manager ? row.manager.split(',') : row.manager,
deptName: row.deptName,
summary: row.summary,
},
}),
beforeSure: (done, { options }) => {
const form = options.props.formInline as FormItemProps;
formRef.value.formRef.validate(async (valid: any) => {

View File

@ -85,22 +85,23 @@ onMounted(() => {
<PureTableBar :columns="columns" :title="$t('system_file')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
{{ $t('addNew') }}
</el-button>
<!-- 批量下载 -->
<el-button
v-if="hasAuth(auth.download)"
:disabled="!(selectRows.length > 0)"
:icon="useRenderIcon(Download)"
plain
type="success"
type="primary"
@click="onDownloadBatch"
>
{{ $t('download_batch') }}
</el-button>
<!-- 新增 -->
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>
<!-- 批量删除按钮 -->
<el-button v-if="hasAuth(auth.delete)" :disabled="!(selectRows.length > 0)" :icon="useRenderIcon(Delete)" plain type="danger" @click="onDeleteBatch">
{{ $t('deleteBatches') }}

View File

@ -25,7 +25,7 @@ export async function onSearch() {
export function onAdd() {
addDialog({
title: `${$t('addNew')}${$t('files')}`,
width: '30%',
props: {
formInline: {
filepath: undefined,
@ -69,7 +69,7 @@ export function onAdd() {
export function onUpdate(row: any) {
addDialog({
title: `${$t('modify')}${$t('files')}`,
width: '30%',
props: {
formInline: {
filename: row.filename,

View File

@ -72,7 +72,7 @@ onMounted(() => {
>
<template #buttons>
<!-- 添加菜单 -->
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd()">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd()">
{{ $t('addNew') }}
</el-button>

View File

@ -171,7 +171,7 @@ export const clearAllRolesSelect = async () => {
addDialog({
title: $t('doubleCheck'),
width: '30%',
draggable: true,
closeOnClickModal: false,
fullscreenIcon: true,

View File

@ -76,7 +76,7 @@ defineExpose({ formRef });
:filter-node-method="filterMethod"
:props="{ label: 'summary' }"
accordion
class="my-2 py-1 h-[220px] overflow-y-auto"
class="my-2 py-1 h-[350px] overflow-y-auto"
highlight-current
node-key="path"
@node-click="onNodeClick"

View File

@ -0,0 +1,114 @@
<script lang="ts" setup>
import { onMounted, ref, toRaw } from 'vue';
import type { TreeInstance } from 'element-plus';
import { FormInstance } from 'element-plus';
import { usePermissionStore } from '@/store/system/power';
import { TreeNode } from 'element-plus/es/components/tree-v2/src/types';
import { handleTree } from '@pureadmin/utils';
interface Tree {
[key: string]: any;
}
interface Props {
form: {
list: any;
};
}
const props = withDefaults(defineProps<Props>(), {
form: () => ({
list: [],
}),
});
const formRef = ref<FormInstance>();
const treeRef = ref<TreeInstance>();
const powerStore = usePermissionStore();
const form = ref(props.form);
//
const powerList = ref([]);
//
const isLoading = ref(false);
/* 加载系统接口列表 */
const onSearch = async () => {
isLoading.value = true;
await powerStore.loadPermissionList();
powerList.value = handleTree(powerStore.allPowerList);
isLoading.value = false;
};
/* 搜索名称 */
const filterMethod = (value: string, data: Tree) => {
if (!value) return true;
return data.summary.includes(value);
};
/* 节点拖拽完成 */
const onNodeDragEnd = (source: TreeNode, target: TreeNode, event: Event) => {
const current = source.data;
const parent = target.parent.data;
// parentId
if (parent.id) {
current.parentId = parent.id;
} else {
current.parentId = '0';
}
const data = toRaw(current);
form.value.list.push(data);
//
const map = new Map();
const array = form.value.list;
for (const item of array) {
map.set(item['id'], item);
}
form.value.list = Array.from(map.values());
};
onMounted(() => {
onSearch();
});
defineExpose({ formRef });
</script>
<template>
<el-tree
ref="treeRef"
v-loading="isLoading"
:data="powerList"
:expand-on-click-node="true"
:filter-node-method="filterMethod"
:props="{ label: 'powerName' }"
class="my-2 py-1 h-[530px] overflow-y-auto"
draggable
highlight-current
node-key="path"
@nodeDragEnd="onNodeDragEnd"
>
<template #default="{ node, data }">
<el-tooltip :content="data.powerCode">
<span>{{ node.label }}</span>
</el-tooltip>
<div class="custom-tree-node">
<el-text type="primary">{{ data.requestUrl }}</el-text>
<el-text type="danger">{{ data.requestMethod }}</el-text>
</div>
</template>
<template #empty>
<ElEmpty />
</template>
</el-tree>
</template>
<style scoped>
.custom-tree-node {
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
padding: 0 8px;
font-size: 14px;
}
</style>

View File

@ -20,6 +20,8 @@ import EditPen from '~icons/ep/edit-pen';
import Refresh from '~icons/ep/refresh';
import AddFill from '~icons/ri/add-circle-line';
import Upload from '~icons/ri/upload-line';
import More from '~icons/ep/more-filled';
import PermissionSortDialog from '@/views/system/permission/components/permission-sort-dialog.vue';
defineOptions({ name: 'PermissionManger' });
@ -62,13 +64,12 @@ const downloadPermission = (type: string) => {
/* 导入权限 */
const uploadPermission = async (type: string) => {
addDialog({
title: `${$t('modify')}${$t('role')}`,
width: '30%',
title: `${$t('modify')}${$t('power')}`,
props: { form: { file: undefined } },
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(FileUploadDialog, { ref: formRef }),
contentRenderer: () => h(FileUploadDialog, { ref: formRef, form: { file: undefined } }),
beforeSure: (done, { options }) => {
const form = options.props.form;
formRef.value.formRef.validate(async (valid: any) => {
@ -84,6 +85,25 @@ const uploadPermission = async (type: string) => {
},
});
};
/* 更新排序 */
const onUpdateSort = () => {
addDialog({
title: `${$t('modify')}权限排序`,
props: { form: { list: [] } },
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(PermissionSortDialog, { form: { list: [] } }),
beforeSure: async (done, { options }) => {
const form = options.props.form;
await powerStore.updatePermissionBatch(form.list);
done();
await onSearch();
},
});
};
onMounted(() => {
onSearch();
});
@ -150,26 +170,28 @@ onMounted(() => {
</el-dropdown>
<!-- 添加权限按钮 -->
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd()">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd()">
{{ $t('addNew') }}
</el-button>
<!-- 批量更新父级id -->
<el-button
v-if="hasAuth(auth.update)"
:disabled="!(powerIds.length > 0)"
:icon="useRenderIcon(EditPen)"
plain
type="primary"
@click="onUpdateBatchParent"
>
{{ $t('update_batches_parent') }}
</el-button>
<el-dropdown v-if="hasAuth(auth.update)" class="ml-1" type="primary">
<el-button :icon="useRenderIcon(More)" plain type="primary">更多操作</el-button>
<template #dropdown>
<el-dropdown-menu>
<!-- 批量更新父级id -->
<el-dropdown-item v-if="hasAuth(auth.update)" :icon="useRenderIcon(EditPen)" @click="onUpdateSort">拖拽排序</el-dropdown-item>
<!-- 批量更新父级id -->
<el-dropdown-item v-if="hasAuth(auth.update)" :disabled="!(powerIds.length > 0)" :icon="useRenderIcon(EditPen)" @click="onUpdateBatchParent">
{{ $t('update_batches_parent') }}
</el-dropdown-item>
<!-- 批量删除按钮 -->
<el-button v-if="hasAuth(auth.delete)" :disabled="!(powerIds.length > 0)" :icon="useRenderIcon(Delete)" plain type="danger" @click="onDeleteBatch">
{{ $t('deleteBatches') }}
</el-button>
<!-- 批量删除按钮 -->
<el-dropdown-item v-if="hasAuth(auth.delete)" :disabled="!(powerIds.length > 0)" :icon="useRenderIcon(Delete)" @click="onDeleteBatch">
{{ $t('deleteBatches') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<template v-slot="{ size, dynamicColumns }">

View File

@ -1,5 +1,5 @@
import { addDialog } from '@/components/ReDialog/index';
import PowerDialog from '@/views/system/permission/components/power-dialog.vue';
import PermissionFromDialog from '@/views/system/permission/components/permission-from-dialog.vue';
import { usePermissionStore } from '@/store/system/power';
import { h, reactive, ref } from 'vue';
import { messageBox } from '@/utils/message';
@ -25,7 +25,7 @@ export async function onSearch() {
export function onAdd(parentId = 0) {
addDialog({
title: `${$t('addNew')}${$t('power')}`,
width: '30%',
props: {
formInline: {
parentId,
@ -38,7 +38,17 @@ export function onAdd(parentId = 0) {
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(PowerDialog, { ref: formRef }),
contentRenderer: () =>
h(PermissionFromDialog, {
ref: formRef,
formInline: {
parentId,
powerCode: undefined,
powerName: undefined,
requestUrl: undefined,
requestMethod: undefined,
},
}),
beforeSure: (done, { options }) => {
const form = options.props.formInline as FormItemProps;
formRef.value.formRef.validate(async (valid: any) => {
@ -57,7 +67,7 @@ export function onAdd(parentId = 0) {
export function onUpdate(row: any) {
addDialog({
title: `${$t('modify')}${$t('power')}`,
width: '30%',
props: {
formInline: {
parentId: row.parentId,
@ -70,7 +80,17 @@ export function onUpdate(row: any) {
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(PowerDialog, { ref: formRef }),
contentRenderer: () =>
h(PermissionFromDialog, {
ref: formRef,
formInline: {
parentId: row.parentId,
powerCode: row.powerCode,
powerName: row.powerName,
requestUrl: row.requestUrl,
requestMethod: row.requestMethod,
},
}),
beforeSure: (done, { options }) => {
const form = options.props.formInline as FormItemProps;
formRef.value.formRef.validate(async (valid: any) => {
@ -132,7 +152,7 @@ export const onUpdateBatchParent = async () => {
await powerStore.loadPermissionList();
addDialog({
title: $t('update_batches_parent'),
width: '30%',
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,

View File

@ -73,7 +73,7 @@ const downloadRoleExcel = () => {
const onUpdateByFile = () => {
addDialog({
title: `${$t('modify')}${$t('role')}`,
width: '30%',
props: { form: { file: undefined } },
draggable: true,
fullscreenIcon: true,
@ -139,7 +139,7 @@ onMounted(() => {
{{ $t('file_import') }}
</el-button>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="primary" @click="onAdd">
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" plain type="success" @click="onAdd">
{{ $t('addNew') }}
</el-button>

View File

@ -34,7 +34,7 @@ export async function onSearch() {
export function onAdd() {
addDialog({
title: `${$t('addNew')}${$t('role')}`,
width: '30%',
props: { formInline: { roleCode: undefined, description: undefined } },
draggable: true,
fullscreenIcon: true,
@ -58,7 +58,7 @@ export function onAdd() {
export function onUpdate(row: any) {
addDialog({
title: `${$t('modify')}${$t('role')}`,
width: '30%',
props: { formInline: { roleCode: row.roleCode, description: row.description } },
draggable: true,
fullscreenIcon: true,