optimize: ♻️ 添加权限验证以及修改表格bug

This commit is contained in:
bunny 2024-11-05 02:07:20 +08:00
parent 3d5c2eaa12
commit 397dbcfdff
28 changed files with 611 additions and 201 deletions

View File

@ -8,7 +8,7 @@ export const fetchGetDeptList = (data: any) => {
/** 部门管理---获取所有部门管理列表 */
export const fetchGetAllDeptList = () => {
return http.request<BaseResult<object>>('get', 'dept/getAllDeptList');
return http.request<BaseResult<object>>('get', 'dept/noManage/getAllDeptList');
};
/** 部门管理---添加部门管理 */

View File

@ -0,0 +1,5 @@
import vxeTableBar from './src/bar';
import { withInstall } from '@pureadmin/utils';
/** 配合 `vxe-table` 实现快速便捷的表格操作 */
export const VxeTableBar = withInstall(vxeTableBar);

View File

@ -0,0 +1,266 @@
import Sortable from 'sortablejs';
import { transformI18n } from '@/plugins/i18n';
import { useEpThemeStoreHook } from '@/store/modules/epTheme';
import { cloneDeep, delay, getKeyList } from '@pureadmin/utils';
import { computed, defineComponent, getCurrentInstance, nextTick, type PropType, ref, unref } from 'vue';
import Fullscreen from '@iconify-icons/ri/fullscreen-fill';
import ExitFullscreen from '@iconify-icons/ri/fullscreen-exit-fill';
import DragIcon from '@/assets/table-bar/drag.svg?component';
import ExpandIcon from '@/assets/table-bar/expand.svg?component';
import RefreshIcon from '@/assets/table-bar/refresh.svg?component';
import SettingIcon from '@/assets/table-bar/settings.svg?component';
import CollapseIcon from '@/assets/table-bar/collapse.svg?component';
const props = {
/** 头部最左边的标题 */
title: {
type: String,
default: '列表',
},
vxeTableRef: {
type: Object as PropType<any>,
},
/** 需要展示的列 */
columns: {
type: Array as PropType<any>,
default: () => [],
},
/** 是否为树列表 */
tree: {
type: Boolean,
default: false,
},
isExpandAll: {
type: Boolean,
default: true,
},
tableKey: {
type: [String, Number] as PropType<string | number>,
default: '0',
},
};
export default defineComponent({
name: 'VxeTableBar',
props,
emits: ['refresh', 'fullscreen'],
setup(props, { emit, slots, attrs }) {
const size = ref('small');
const loading = ref(false);
const checkAll = ref(true);
const isFullscreen = ref(false);
const isIndeterminate = ref(false);
const instance = getCurrentInstance()!;
const isExpandAll = ref(props.isExpandAll);
let checkColumnList = getKeyList(cloneDeep(props?.columns), 'title');
const checkedColumns = ref(getKeyList(cloneDeep(props?.columns), 'title'));
const dynamicColumns = ref(cloneDeep(props?.columns));
const getDropdownItemStyle = computed(() => {
return s => {
return {
background: s === size.value ? useEpThemeStoreHook().epThemeColor : '',
color: s === size.value ? '#fff' : 'var(--el-text-color-primary)',
};
};
});
const iconClass = computed(() => {
return ['text-black', 'dark:text-white', 'duration-100', 'hover:!text-primary', 'cursor-pointer', 'outline-none'];
});
const topClass = computed(() => {
return ['flex', 'justify-between', 'pt-[3px]', 'px-[11px]', 'border-b-[1px]', 'border-solid', 'border-[#dcdfe6]', 'dark:border-[#303030]'];
});
function onReFresh() {
loading.value = true;
emit('refresh');
delay(500).then(() => (loading.value = false));
}
function onExpand() {
isExpandAll.value = !isExpandAll.value;
isExpandAll.value ? props.vxeTableRef.setAllTreeExpand(true) : props.vxeTableRef.clearTreeExpand();
props.vxeTableRef.refreshColumn();
}
function onFullscreen() {
isFullscreen.value = !isFullscreen.value;
emit('fullscreen', isFullscreen.value);
}
function reloadColumn() {
const curCheckedColumns = cloneDeep(dynamicColumns.value).filter(item => checkedColumns.value.includes(item.title));
props.vxeTableRef.reloadColumn(curCheckedColumns);
}
function handleCheckAllChange(val: boolean) {
checkedColumns.value = val ? checkColumnList : [];
isIndeterminate.value = false;
reloadColumn();
}
function handleCheckedColumnsChange(value: string[]) {
checkedColumns.value = value;
const checkedCount = value.length;
checkAll.value = checkedCount === checkColumnList.length;
isIndeterminate.value = checkedCount > 0 && checkedCount < checkColumnList.length;
}
async function onReset() {
checkAll.value = true;
isIndeterminate.value = false;
dynamicColumns.value = cloneDeep(props?.columns);
checkColumnList = [];
checkColumnList = getKeyList(cloneDeep(props?.columns), 'title');
checkedColumns.value = getKeyList(cloneDeep(props?.columns), 'title');
props.vxeTableRef.refreshColumn();
}
function changeSize(curSize: string) {
size.value = curSize;
props.vxeTableRef.refreshColumn();
}
const dropdown = {
dropdown: () => (
<el-dropdown-menu class='translation'>
<el-dropdown-item style={getDropdownItemStyle.value('medium')} onClick={() => changeSize('medium')}>
</el-dropdown-item>
<el-dropdown-item style={getDropdownItemStyle.value('small')} onClick={() => changeSize('small')}>
</el-dropdown-item>
<el-dropdown-item style={getDropdownItemStyle.value('mini')} onClick={() => changeSize('mini')}>
</el-dropdown-item>
</el-dropdown-menu>
),
};
/** 列展示拖拽排序 */
const rowDrop = (event: { preventDefault: () => void }) => {
event.preventDefault();
nextTick(() => {
const wrapper: HTMLElement = (instance?.proxy?.$refs[`VxeGroupRef${unref(props.tableKey)}`] as any).$el.firstElementChild;
Sortable.create(wrapper, {
animation: 300,
handle: '.drag-btn',
onEnd: ({ newIndex, oldIndex, item }) => {
const targetThElem = item;
const wrapperElem = targetThElem.parentNode as HTMLElement;
const oldColumn = dynamicColumns.value[oldIndex];
const newColumn = dynamicColumns.value[newIndex];
if (oldColumn?.fixed || newColumn?.fixed) {
// 当前列存在fixed属性 则不可拖拽
const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
if (newIndex > oldIndex) {
wrapperElem.insertBefore(targetThElem, oldThElem);
} else {
wrapperElem.insertBefore(targetThElem, oldThElem ? oldThElem.nextElementSibling : oldThElem);
}
return;
}
const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
dynamicColumns.value.splice(newIndex, 0, currentRow);
reloadColumn();
},
});
});
};
const isFixedColumn = (title: string) => {
return dynamicColumns.value.filter(item => transformI18n(item.title) === transformI18n(title))[0].fixed;
};
const rendTippyProps = (content: string) => {
// https://vue-tippy.netlify.app/props
return {
content,
offset: [0, 18],
duration: [300, 0],
followCursor: true,
hideOnClick: 'toggle',
};
};
const reference = {
reference: () => <SettingIcon class={['w-[16px]', iconClass.value]} v-tippy={rendTippyProps('列设置')} />,
};
return () => (
<>
<div {...attrs} class={['w-[99/100]', 'px-2', 'pb-2', 'bg-bg_color', isFullscreen.value ? ['!w-full', '!h-full', 'z-[2002]', 'fixed', 'inset-0'] : 'mt-2']}>
<div class='flex justify-between w-full h-[60px] p-4'>
{slots?.title ? slots.title() : <p class='font-bold truncate'>{props.title}</p>}
<div class='flex items-center justify-around'>
{slots?.buttons ? <div class='flex mr-4'>{slots.buttons()}</div> : null}
{props.tree ? (
<>
<ExpandIcon
class={['w-[16px]', iconClass.value]}
style={{
transform: isExpandAll.value ? 'none' : 'rotate(-90deg)',
}}
v-tippy={rendTippyProps(isExpandAll.value ? '折叠' : '展开')}
onClick={() => onExpand()}
/>
<el-divider direction='vertical' />
</>
) : null}
<RefreshIcon class={['w-[16px]', iconClass.value, loading.value ? 'animate-spin' : '']} v-tippy={rendTippyProps('刷新')} onClick={() => onReFresh()} />
<el-divider direction='vertical' />
<el-dropdown v-slots={dropdown} trigger='click' v-tippy={rendTippyProps('密度')}>
<CollapseIcon class={['w-[16px]', iconClass.value]} />
</el-dropdown>
<el-divider direction='vertical' />
<el-popover v-slots={reference} placement='bottom-start' popper-style={{ padding: 0 }} width='200' trigger='click'>
<div class={[topClass.value]}>
<el-checkbox class='!-mr-1' label='列展示' v-model={checkAll.value} indeterminate={isIndeterminate.value} onChange={value => handleCheckAllChange(value)} />
<el-button type='primary' link onClick={() => onReset()}>
</el-button>
</div>
<div class='pt-[6px] pl-[11px]'>
<el-scrollbar max-height='36vh'>
<el-checkbox-group ref={`VxeGroupRef${unref(props.tableKey)}`} modelValue={checkedColumns.value} onChange={value => handleCheckedColumnsChange(value)}>
<el-space direction='vertical' alignment='flex-start' size={0}>
{checkColumnList.map((item, index) => {
return (
<div class='flex items-center'>
<DragIcon class={['drag-btn w-[16px] mr-2', isFixedColumn(item) ? '!cursor-no-drop' : '!cursor-grab']} onMouseenter={(event: { preventDefault: () => void }) => rowDrop(event)} />
<el-checkbox key={index} label={item} value={item} onChange={reloadColumn}>
<span title={transformI18n(item)} class='inline-block w-[120px] truncate hover:text-text_color_primary'>
{transformI18n(item)}
</span>
</el-checkbox>
</div>
);
})}
</el-space>
</el-checkbox-group>
</el-scrollbar>
</div>
</el-popover>
<el-divider direction='vertical' />
<iconifyIconOffline
class={['w-[16px]', iconClass.value]}
icon={isFullscreen.value ? ExitFullscreen : Fullscreen}
v-tippy={isFullscreen.value ? '退出全屏' : '全屏'}
onClick={() => onFullscreen()}
/>
</div>
</div>
{slots.default({
size: size.value,
dynamicColumns: dynamicColumns.value,
})}
</div>
</>
);
},
});

View File

@ -110,7 +110,7 @@ export default defineComponent({
}
function handleCheckColumnListChange(val: boolean, label: string) {
dynamicColumns.value.filter(item => $t(item.label) === $t(label))[0].hide = !val;
dynamicColumns.value.filter(item => item.label === label)[0].hide = !val;
}
function onReset() {
@ -169,7 +169,7 @@ export default defineComponent({
};
const isFixedColumn = (label: string) => {
return !!dynamicColumns.value.filter(item => item.label === label)[0].fixed;
return dynamicColumns.value.filter(item => item.label === label)[0].fixed;
};
const rendTippyProps = (content: string) => ({

View File

@ -1,6 +1,7 @@
// 多组件库的国际化和本地项目国际化兼容
import { createI18n } from 'vue-i18n';
import type { App } from 'vue';
import type { App, WritableComputedRef } from 'vue';
import { isObject } from '@pureadmin/utils';
// ? 从本地存储中获取数据
const languageData = localStorage.getItem('i18nStore');
@ -17,7 +18,7 @@ export const i18n = createI18n({
messages: languageData ? JSON.parse(languageData).i18n : {},
});
/*const siphonI18n = (function () {
const siphonI18n = (function () {
// 仅初始化一次国际化配置
let cache = Object.fromEntries(
Object.entries(import.meta.glob('../../locales/!*.y(a)?ml', { eager: true })).map(([key, value]: any) => {
@ -30,7 +31,7 @@ export const i18n = createI18n({
};
})();
/!** 获取对象中所有嵌套对象的key键并将它们用点号分割组成字符串 *!/
/** 获取对象中所有嵌套对象的key键并将它们用点号分割组成字符串 */
function getObjectKeys(obj) {
const stack = [];
const keys: Set<string> = new Set();
@ -54,7 +55,7 @@ function getObjectKeys(obj) {
return keys;
}
/!** 将展开的key缓存 *!/
/** 将展开的key缓存 */
const keysCache: Map<string, Set<string>> = new Map();
const flatI18n = (prefix = 'zh') => {
let cache = keysCache.get(prefix);
@ -65,11 +66,11 @@ const flatI18n = (prefix = 'zh') => {
return cache;
};
/!**
/**
* locales文件夹下文件进行国际化匹配
* @param message message
* @returns message
*!/
*/
export function transformI18n(message: any = '') {
if (!message) {
return '';
@ -91,7 +92,7 @@ export function transformI18n(message: any = '') {
} else {
return message;
}
}*/
}
export const $t: any = (i18n.global as any).t as any;

View File

@ -312,6 +312,10 @@ function hasAuth(value: string | Array<string>): boolean {
/** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */
const metaAuths = getAuths();
if (!metaAuths) return false;
// 管理员权限
if (metaAuths.includes('*::*::*') || metaAuths.includes('*::*') || metaAuths.includes('*')) {
return true;
}
return isString(value) ? metaAuths.includes(value) : isIncludeAllChildren(value, metaAuths);
}

View File

@ -38,6 +38,8 @@ import Airplane from '@/assets/svg/airplane.svg';
import { useDeptStore } from '@/store/system/dept';
import { FormInstance } from 'element-plus';
import { usePublicHooks } from '@/views/hooks';
import { auth } from '@/views/system/adminUser/utils/auth';
import { hasAuth } from '@/router/utils';
const adminUserStore = useAdminUserStore();
const deptStore = useDeptStore();
@ -88,58 +90,62 @@ onMounted(() => {
<div :class="['flex', 'justify-between', deviceDetection() && 'flex-wrap']">
<tree ref="treeRef" :class="['mr-2', deviceDetection() ? 'w-full' : 'min-w-[200px]']" :treeData="deptList" :treeLoading="deptStore.loading" @tree-select="onTreeSelect" />
<div :class="[deviceDetection() ? ['w-full', 'mt-2'] : 'w-[calc(100%-200px)]']">
<el-form ref="formRef" :inline="true" :model="adminUserStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<!-- 查询用户名 -->
<el-form-item :label="$t('adminUser_username')" prop="username">
<el-input v-model="adminUserStore.form.username" :placeholder="`${$t('input')}${$t('adminUser_username')}`" class="!w-[180px]" clearable />
</el-form-item>
<Auth :value="auth.search">
<el-form ref="formRef" :inline="true" :model="adminUserStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<!-- 查询用户名 -->
<el-form-item :label="$t('adminUser_username')" prop="username">
<el-input v-model="adminUserStore.form.username" :placeholder="`${$t('input')}${$t('adminUser_username')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 查询昵称 -->
<el-form-item :label="$t('adminUser_nickname')" prop="nickname">
<el-input v-model="adminUserStore.form.nickname" :placeholder="`${$t('input')}${$t('adminUser_nickname')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 查询昵称 -->
<el-form-item :label="$t('adminUser_nickname')" prop="nickname">
<el-input v-model="adminUserStore.form.nickname" :placeholder="`${$t('input')}${$t('adminUser_nickname')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 查询邮箱 -->
<el-form-item :label="$t('adminUser_email')" prop="email">
<el-input v-model="adminUserStore.form.email" :placeholder="`${$t('input')}${$t('adminUser_email')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 查询邮箱 -->
<el-form-item :label="$t('adminUser_email')" prop="email">
<el-input v-model="adminUserStore.form.email" :placeholder="`${$t('input')}${$t('adminUser_email')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 查询手机号 -->
<el-form-item :label="$t('adminUser_phone')" prop="phone">
<el-input v-model="adminUserStore.form.phone" :placeholder="`${$t('input')}${$t('adminUser_phone')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 查询手机号 -->
<el-form-item :label="$t('adminUser_phone')" prop="phone">
<el-input v-model="adminUserStore.form.phone" :placeholder="`${$t('input')}${$t('adminUser_phone')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 查询性别 -->
<el-form-item :label="$t('adminUser_sex')" prop="sex">
<el-select v-model="adminUserStore.form.sex" :placeholder="`${$t('input')}${$t('adminUser_sex')}`" class="!w-[180px]" clearable filterable>
<el-option v-for="(item, index) in sexConstant" :key="index" :label="item.label" :navigationBar="false" :value="item.value" />
</el-select>
</el-form-item>
<!-- 查询性别 -->
<el-form-item :label="$t('adminUser_sex')" prop="sex">
<el-select v-model="adminUserStore.form.sex" :placeholder="`${$t('input')}${$t('adminUser_sex')}`" class="!w-[180px]" clearable filterable>
<el-option v-for="(item, index) in sexConstant" :key="index" :label="item.label" :navigationBar="false" :value="item.value" />
</el-select>
</el-form-item>
<!-- 查询简介 -->
<el-form-item :label="$t('adminUser_summary')" prop="summary">
<el-input v-model="adminUserStore.form.summary" :placeholder="`${$t('input')}${$t('adminUser_summary')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 查询简介 -->
<el-form-item :label="$t('adminUser_summary')" prop="summary">
<el-input v-model="adminUserStore.form.summary" :placeholder="`${$t('input')}${$t('adminUser_summary')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 查询状态 -->
<el-form-item :label="$t('adminUser_status')" prop="status">
<el-select v-model="adminUserStore.form.status" :placeholder="`${$t('input')}${$t('adminUser_status')}`" class="!w-[180px]" clearable filterable>
<el-option v-for="(item, index) in userStatus" :key="index" :label="item.label" :navigationBar="false" :value="item.value" />
</el-select>
</el-form-item>
<!-- 查询状态 -->
<el-form-item :label="$t('adminUser_status')" prop="status">
<el-select v-model="adminUserStore.form.status" :placeholder="`${$t('input')}${$t('adminUser_status')}`" class="!w-[180px]" clearable filterable>
<el-option v-for="(item, index) in userStatus" :key="index" :label="item.label" :navigationBar="false" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="adminUserStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="adminUserStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
</Auth>
<PureTableBar :columns="columns" :title="$t('userinfo')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd"> {{ $t('addNew') }}</el-button>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd">
{{ $t('addNew') }}
</el-button>
<!-- 批量删除按钮 -->
<el-button :disabled="!(deleteIds.length > 0)" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
<el-button v-if="hasAuth(auth.deleted)" :disabled="!(deleteIds.length > 0)" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
{{ $t('deleteBatches') }}
</el-button>
</template>
@ -212,10 +218,10 @@ onMounted(() => {
<template #operation="{ row }">
<!-- 修改 -->
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify') }} </el-button>
<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 :title="`${$t('delete')} ${row.username}?`" @confirm="onDelete(row)">
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('delete')} ${row.username}?`" @confirm="onDelete(row)">
<template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
{{ $t('delete') }}
@ -228,16 +234,20 @@ onMounted(() => {
<el-button :icon="useRenderIcon(More)" :size="size" class="ml-3 mt-[2px]" link type="primary" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<!-- 上传头像 -->
<el-dropdown-item v-if="hasAuth(auth.uploadAvatarByAdmin)">
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Upload)" :size="size" link type="primary" @click="onUploadAvatar(row)"> {{ $t('upload_avatar') }} </el-button>
</el-dropdown-item>
<el-dropdown-item>
<!-- 重置密码 -->
<el-dropdown-item v-if="hasAuth(auth.updateUserPasswordByAdmin)">
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Password)" :size="size" link type="primary" @click="onResetPassword(row)"> {{ $t('reset_passwords') }} </el-button>
</el-dropdown-item>
<el-dropdown-item>
<!-- 分配角色 -->
<el-dropdown-item v-if="hasAuth(auth.updateUserPasswordByAdmin)">
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Role)" :size="size" link type="primary" @click="onAssignRolesToUser(row)"> {{ $t('assign_roles') }} </el-button>
</el-dropdown-item>
<el-dropdown-item>
<!-- 强制下线 -->
<el-dropdown-item v-if="hasAuth(auth.forcedOffline)">
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Airplane)" :size="size" link type="primary" @click="onForcedOffline(row)"> {{ $t('forced_offline') }} </el-button>
</el-dropdown-item>
</el-dropdown-menu>

View File

@ -0,0 +1,18 @@
export const auth = {
// 分页查询
search: ['user::getAdminUserList'],
// 添加操作
add: ['user::addAdminUser'],
// 更新操作
update: ['user::updateAdminUser'],
// 删除操作
deleted: ['user::deleteAdminUser'],
// 管理员上传为用户修改头像
uploadAvatarByAdmin: ['user::uploadAvatarByAdmin'],
// 强制用户下线
forcedOffline: ['user::forcedOffline'],
// 修改用户状态:是否禁用
updateUserStatusByAdmin: ['user::updateUserStatusByAdmin'],
// 管理员修改密码
updateUserPasswordByAdmin: ['user::updateUserPasswordByAdmin'],
};

View File

@ -9,7 +9,7 @@ export const isAddUserinfo = ref(false);
// 表格列
export const columns: TableColumnList = [
{ type: 'selection', align: 'left' },
{ type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 },
{ type: 'index', index: (index: number) => index + 1, label: $t('index'), width: 60 },
// 用户名
{ label: $t('adminUser_username'), prop: 'username', width: 260 },
// 状态

View File

@ -256,7 +256,7 @@ export const onUploadAvatar = (row: any) => {
*/
export const onResetPassword = (row: any) => {
addDialog({
title: `重置 ${row.username} 用户的密码`,
title: `${$t('buttons.reset')} ${row.username} ${$t('userPassword')}`,
width: '30%',
draggable: true,
closeOnClickModal: false,
@ -287,7 +287,7 @@ export const onResetPassword = (row: any) => {
*/
export const onAssignRolesToUser = (row: any) => {
addDialog({
title: ` ${row.username} 分配角色`,
title: `${$t('for')} ${row.username} ${$t('assign_roles')}`,
width: '45%',
draggable: true,
closeOnClickModal: false,

View File

@ -14,41 +14,32 @@ import { useDeptStore } from '@/store/system/dept';
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import { handleTree } from '@/utils/tree';
import { FormInstance } from 'element-plus';
import { auth } from '@/views/system/dept/utils/auth';
import { hasAuth } from '@/router/utils';
const tableRef = ref();
const formRef = ref();
const deptStore = useDeptStore();
const datalist = computed(() => handleTree(deptStore.datalist));
/**
* * 当前页改变时
*/
/** 当前页改变时 */
const onCurrentPageChange = async (value: number) => {
deptStore.pagination.currentPage = value;
await onSearch();
};
/**
* * 当分页发生变化
* @param value
*/
/** 当分页发生变化 */
const onPageSizeChange = async (value: number) => {
deptStore.pagination.pageSize = value;
await onSearch();
};
/**
* * 选择多行
* @param rows
*/
/** 选择多行 */
const onSelectionChange = (rows: Array<any>) => {
deleteIds.value = rows.map((row: any) => row.id);
};
/**
* 重置表单
* @param formEl
*/
/** 重置表单 */
const resetForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
@ -62,25 +53,29 @@ onMounted(() => {
<template>
<div class="main">
<el-form ref="formRef" :inline="true" :model="deptStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item :label="$t('dept_deptName')" prop="deptName">
<el-input v-model="deptStore.form.deptName" :placeholder="`${$t('input')}${$t('dept_deptName')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('dept_summary')" prop="summary">
<el-input v-model="deptStore.form.summary" :placeholder="`${$t('input')}${$t('dept_summary')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="deptStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
<Auth :value="auth.search">
<el-form ref="formRef" :inline="true" :model="deptStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item :label="$t('dept_deptName')" prop="deptName">
<el-input v-model="deptStore.form.deptName" :placeholder="`${$t('input')}${$t('dept_deptName')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('dept_summary')" prop="summary">
<el-input v-model="deptStore.form.summary" :placeholder="`${$t('input')}${$t('dept_summary')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="deptStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
</Auth>
<PureTableBar :columns="columns" :isExpandAll="true" :tableRef="tableRef?.getTableRef()" :title="$t('dept')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd()"> {{ $t('addNew') }}</el-button>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd()">
{{ $t('addNew') }}
</el-button>
<!-- 批量删除按钮 -->
<el-button :disabled="!(deleteIds.length > 0)" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
<el-button v-if="hasAuth(auth.deleted)" :disabled="!(deleteIds.length > 0)" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
{{ $t('deleteBatches') }}
</el-button>
</template>
@ -120,9 +115,9 @@ onMounted(() => {
</template>
<template #operation="{ row }">
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify') }} </el-button>
<el-button :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="onAdd(row.id)"> {{ $t('addNew') }} </el-button>
<el-popconfirm :title="`${$t('delete')} ${row.deptName}?`" @confirm="onDelete(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-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="onAdd(row.id)"> {{ $t('addNew') }} </el-button>
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('delete')} ${row.deptName}?`" @confirm="onDelete(row)">
<template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
{{ $t('delete') }}

View File

@ -0,0 +1,10 @@
export const auth = {
// 分页查询
search: ['dept::getDeptList'],
// 添加操作
add: ['dept::addDept'],
// 更新操作
update: ['dept::updateDept'],
// 删除操作
deleted: ['dept::deleteDept'],
};

View File

@ -4,7 +4,7 @@ import { $t } from '@/plugins/i18n';
// 表格列
export const columns: TableColumnList = [
{ type: 'selection', align: 'left' },
{ type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 },
{ type: 'index', index: (index: number) => index + 1, label: $t('index'), width: 60 },
// 部门名称
{ label: $t('dept_deptName'), prop: 'deptName', width: 160 },
// 管理者

View File

@ -14,6 +14,8 @@ import { $t } from '@/plugins/i18n';
import { useFilesStore } from '@/store/monitor/files';
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import { FormInstance } from 'element-plus';
import { auth } from '@/views/system/files/utils/auth';
import { hasAuth } from '@/router/utils';
const tableRef = ref();
const formRef = ref();
@ -50,36 +52,40 @@ onMounted(() => {
<template>
<div class="main">
<el-form ref="formRef" :inline="true" :model="filesStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item :label="$t('files_filename')" prop="filename">
<el-input v-model="filesStore.form.filename" :placeholder="`${$t('input')}${$t('files_filename')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('files_filepath')" prop="filepath">
<el-input v-model="filesStore.form.filepath" :placeholder="`${$t('input')}${$t('files_filepath')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('files_fileType')" prop="fileType">
<el-input v-model="filesStore.form.fileType" :placeholder="`${$t('input')}${$t('files_fileType')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('files_downloadCount')" prop="downloadCount">
<el-input v-model="filesStore.form.downloadCount" :placeholder="`${$t('input')}${$t('files_downloadCount')}`" class="!w-[180px]" clearable min="0" type="number" />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="filesStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
<Auth :value="auth.search">
<el-form ref="formRef" :inline="true" :model="filesStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item :label="$t('files_filename')" prop="filename">
<el-input v-model="filesStore.form.filename" :placeholder="`${$t('input')}${$t('files_filename')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('files_filepath')" prop="filepath">
<el-input v-model="filesStore.form.filepath" :placeholder="`${$t('input')}${$t('files_filepath')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('files_fileType')" prop="fileType">
<el-input v-model="filesStore.form.fileType" :placeholder="`${$t('input')}${$t('files_fileType')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('files_downloadCount')" prop="downloadCount">
<el-input v-model="filesStore.form.downloadCount" :placeholder="`${$t('input')}${$t('files_downloadCount')}`" class="!w-[180px]" clearable min="0" type="number" />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="filesStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
</Auth>
<PureTableBar :columns="columns" :title="$t('system_file')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd"> {{ $t('addNew') }}</el-button>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd">
{{ $t('addNew') }}
</el-button>
<!-- 批量下载 -->
<el-button :disabled="!(selectRows.length > 0)" :icon="useRenderIcon(Download)" type="success" @click="onDownloadBatch">
<el-button v-if="hasAuth(auth.downloadFilesByFileId)" :disabled="!(selectRows.length > 0)" :icon="useRenderIcon(Download)" type="success" @click="onDownloadBatch">
{{ $t('download_batch') }}
</el-button>
<!-- 批量删除按钮 -->
<el-button :disabled="!(selectRows.length > 0)" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
<el-button v-if="hasAuth(auth.deleted)" :disabled="!(selectRows.length > 0)" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
{{ $t('deleteBatches') }}
</el-button>
</template>
@ -118,9 +124,11 @@ onMounted(() => {
</template>
<template #operation="{ row }">
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify') }} </el-button>
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onDownload(row)"> {{ $t('download') }} </el-button>
<el-popconfirm :title="`${$t('delete')} ${row.filename}?`" @confirm="onDelete(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-button v-if="hasAuth(auth.downloadFilesByFileId)" :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onDownload(row)">
{{ $t('download') }}
</el-button>
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('delete')} ${row.filename}?`" @confirm="onDelete(row)">
<template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
{{ $t('delete') }}

View File

@ -0,0 +1,14 @@
export const auth = {
// 更新操作
update: ['files::updateFiles'],
// 添加操作
add: ['files::addFiles'],
// 分页查询
search: ['files::getFilesList'],
// 删除操作
deleted: ['files::deleteFiles'],
// 上传
upload: ['files::upload'],
// 下载文件
downloadFilesByFileId: ['files::downloadFilesByFileId'],
};

View File

@ -4,7 +4,7 @@ import { $t } from '@/plugins/i18n';
// 表格列
export const columns: TableColumnList = [
{ type: 'selection', align: 'left' },
{ type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 },
{ type: 'index', index: (index: number) => index + 1, label: $t('index'), width: 60 },
// 文件的名称
{ label: $t('files_filename'), prop: 'filename', width: 400 },
// 文件在服务器上的存储路径

View File

@ -5,7 +5,7 @@ import { h, ref } from 'vue';
import { message, messageBox } from '@/utils/message';
import type { FormItemProps } from '@/views/system/files/utils/types';
import { $t } from '@/plugins/i18n';
import { downloadFilesByFileId, downloadFilesByFilepath } from '@/api/v1/files';
import { downloadFilesByFileId } from '@/api/v1/files';
import { download } from '@/utils/sso';
import type { UploadFiles } from 'element-plus';
import DeleteBatchDialog from '@/components/Table/DeleteBatchDialog.vue';
@ -154,7 +154,7 @@ export const onDeleteBatch = async () => {
* @param row
*/
export const onDownload = async (row: any) => {
const blob = await downloadFilesByFilepath({ filepath: row.filepath });
const blob = await downloadFilesByFileId({ id: row.id });
download(blob, row.filename);
};

View File

@ -28,6 +28,8 @@ import { selectUserinfo } from '@/components/Table/Userinfo/columns';
import Upload from '@iconify-icons/ri/upload-line';
import { FormInstance } from 'element-plus';
import { usePublicHooks } from '@/views/hooks';
import { auth } from '@/views/system/menu/utils/auth';
import { hasAuth } from '@/router/utils';
const formRef = ref();
const routerStore = userMenuStore();
@ -59,25 +61,34 @@ onMounted(() => {
<template>
<div class="main">
<el-form ref="formRef" :inline="true" :model="routerStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item label="菜单名称" prop="title">
<el-input v-model="routerStore.form.title" class="!w-[180px]" clearable placeholder="输入菜单名称" />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="routerStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
<Auth :value="auth.search">
<el-form ref="formRef" :inline="true" :model="routerStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item label="菜单名称" prop="title">
<el-input v-model="routerStore.form.title" :placeholder="$t('input')" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="routerStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
</Auth>
<PureTableBar :columns="columns" :isExpandAll="false" :tableRef="tableRef?.getTableRef()" title="菜单管理" @fullscreen="tableRef?.setAdaptive()" @refresh="onSearch">
<template #buttons>
<!-- 添加菜单 -->
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd()"> {{ $t('addNew') }}</el-button>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd()">
{{ $t('addNew') }}
</el-button>
<!-- 批量分配角色 -->
<el-button :disabled="!(selectIds.length > 0)" :icon="useRenderIcon('iwwa:assign')" type="warning" @click="assignBatchRolesToRouter()"> {{ $t('assignBatchRolesToRouter') }} </el-button>
<!-- 批量为菜单添加角色 -->
<el-button v-if="hasAuth(auth.assignAddBatchRolesToRouter)" :disabled="!(selectIds.length > 0)" :icon="useRenderIcon('iwwa:assign')" type="warning" @click="assignBatchRolesToRouter()">
{{ $t('assignBatchRolesToRouter') }}
</el-button>
<!-- 清除选中所以角色 -->
<el-button :disabled="!(selectIds.length > 0)" :icon="useRenderIcon('ic:baseline-clear')" type="danger" @click="clearAllRolesSelect()"> {{ $t('clearAllRolesSelect') }} </el-button>
<el-button v-if="hasAuth(auth.clearAllRolesSelect)" :disabled="!(selectIds.length > 0)" :icon="useRenderIcon('ic:baseline-clear')" type="danger" @click="clearAllRolesSelect()">
{{ $t('clearAllRolesSelect') }}
</el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
@ -130,13 +141,15 @@ onMounted(() => {
<template #operation="{ row }">
<!-- 修改 -->
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify') }} </el-button>
<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-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="onAdd(row.id)"> {{ $t('addNew') }} </el-button>
<Auth :value="auth.add">
<el-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="onAdd(row.id)"> {{ $t('addNew') }} </el-button>
</Auth>
<!-- 删除操作 -->
<el-popconfirm :title="`${$t('delete')} ${$t(row.title)}?`" @confirm="onDelete(row)">
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('delete')} ${$t(row.title)}?`" @confirm="onDelete(row)">
<template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
{{ $t('delete') }}
@ -145,9 +158,11 @@ onMounted(() => {
</el-popconfirm>
<!-- 分配角色 -->
<el-button v-show="row.menuType !== 3" :icon="useRenderIcon(Upload)" :size="size" class="reset-margin" link type="primary" @click="assignRolesToRouter(row)">
{{ $t('assign_roles') }}
</el-button>
<Auth :value="auth.assignRolesToRouter">
<el-button v-show="row.menuType !== 3" :icon="useRenderIcon(Upload)" :size="size" class="reset-margin" link type="primary" @click="assignRolesToRouter(row)">
{{ $t('assign_roles') }}
</el-button>
</Auth>
</template>
</pure-table>
</template>

View File

@ -6,6 +6,7 @@ import IconSelect from '@/components/SelectIcon/Select.vue';
import Segmented from '@/components/Segmented';
import { menuTypeOptions } from '@/enums';
import { FormProps } from '@/views/system/menu/utils/types';
import { $t } from '@/plugins/i18n';
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({}),
@ -20,13 +21,13 @@ defineExpose({ menuFormRef: ruleFormRef });
<el-form ref="ruleFormRef" :model="newFormInline" :rules="formRules" label-width="82px">
<el-row :gutter="30">
<re-col>
<el-form-item label="菜单类型">
<el-form-item :label="$t('menuType')">
<Segmented v-model="newFormInline.menuType" :options="menuTypeOptions" />
</el-form-item>
</re-col>
<re-col>
<el-form-item label="上级菜单">
<el-form-item :label="$t('previousMenu')">
<el-cascader
v-model="newFormInline.parentId"
:options="newFormInline.higherMenuOptions"
@ -45,29 +46,29 @@ defineExpose({ menuFormRef: ruleFormRef });
</re-col>
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="菜单名称" prop="title">
<el-form-item :label="$t('menuName')" prop="title">
<el-input v-model="newFormInline.title" clearable placeholder="请输入菜单名称" />
</el-form-item>
</re-col>
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="路由名称" prop="name">
<el-form-item :label="$t('routerName')" prop="name">
<el-input v-model="newFormInline.name" clearable placeholder="请输入路由名称" />
</el-form-item>
</re-col>
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="路由路径" prop="path">
<el-form-item :label="$t('routerPath')" prop="path">
<el-input v-model="newFormInline.path" clearable placeholder="请输入路由路径" />
</el-form-item>
</re-col>
<re-col v-show="newFormInline.menuType === 0" :sm="24" :value="12" :xs="24">
<el-form-item label="组件路径">
<el-form-item :label="$t('componentPath')">
<el-input v-model="newFormInline.component" clearable placeholder="请输入组件路径" />
</el-form-item>
</re-col>
<re-col :sm="24" :value="12" :xs="24">
<el-form-item v-model="newFormInline.rank" label="菜单排序" prop="rank">
<el-form-item v-model="newFormInline.rank" :label="$t('sort')" prop="rank">
<el-input-number v-model="newFormInline.rank" :max="9999" :min="1" class="!w-full" controls-position="right" />
</el-form-item>
</re-col>
@ -86,7 +87,9 @@ defineExpose({ menuFormRef: ruleFormRef });
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="是否显示">
<el-switch v-model="newFormInline.visible" active-text="开启" inactive-text="隐藏" inline-prompt style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949" />
<el-switch v-model="newFormInline.visible" :inactive-text="$t('visible')" active-text="开启" inline-prompt style="
--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949" />
</el-form-item>
</re-col>
</el-row>

View File

@ -0,0 +1,20 @@
export const auth = {
// 更新操作
update: ['router::updateMenu'],
// 修改菜单排序
updateMenuByIdWithRank: ['router::updateMenuByIdWithRank'],
// 添加操作
add: ['router::addMenu'],
// 分页查询
search: ['router::getMenusList'],
// 删除操作
deleted: ['router::deletedMenuByIds'],
// 为菜单分配角色
assignRolesToRouter: ['routerRole::assignRolesToRouter'],
// 批量为菜单添加角色
assignAddBatchRolesToRouter: ['routerRole::assignAddBatchRolesToRouter'],
// 根据菜单id获取所有角色
getRoleListByRouterId: ['routerRole::getRoleListByRouterId'],
// 清除选中菜单所有角色
clearAllRolesSelect: ['routerRole::clearAllRolesSelect'],
};

View File

@ -13,11 +13,11 @@ import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
const getMenuType = (type: number, text: boolean = false): any => {
switch (type) {
case 0:
return text ? '菜单' : 'primary';
return text ? $t('menu') : 'primary';
case 1:
return text ? 'iframe' : 'warning';
case 2:
return text ? '外链' : 'danger';
return text ? $t('externalLink') : 'danger';
}
};
@ -36,7 +36,7 @@ export const formatHigherMenuOptions = (treeList: any) => {
export const columns: TableColumnList = [
{ type: 'selection', align: 'left' },
{
label: '菜单名称',
label: $t('menuName'),
prop: 'title',
align: 'left',
cellRenderer: ({ row }) => (
@ -51,7 +51,7 @@ export const columns: TableColumnList = [
),
},
{
label: '菜单类型',
label: $t('menuType'),
prop: 'menuType',
width: 100,
cellRenderer: ({ row, props }) => (
@ -60,14 +60,14 @@ export const columns: TableColumnList = [
</ElTag>
),
},
{ label: '路由路径', prop: 'path' },
{ label: $t('routerPath'), prop: 'path' },
{
label: '组件路径',
label: $t('componentPath'),
prop: 'component',
formatter: ({ path, component }) => (isAllEmpty(component) ? path : component),
},
{ label: '排序', prop: 'rank', width: 80, slot: 'rank' },
{ label: '隐藏', prop: 'visible', slot: 'visible', width: 100 },
{ label: $t('sort'), prop: 'rank', width: 80, slot: 'rank' },
{ label: $t('visible'), prop: 'visible', slot: 'visible', width: 100 },
{ label: $t('table.updateTime'), prop: 'updateTime', sortable: true },
{ label: $t('table.createTime'), prop: 'createTime', sortable: true },
{ label: $t('table.createUser'), prop: 'createUser', slot: 'createUser', width: 130 },
@ -77,8 +77,7 @@ export const columns: TableColumnList = [
/** 自定义表单规则校验 */
export const formRules = reactive<FormRules>({
title: [{ required: true, message: '菜单名称为必填项', trigger: 'blur' }],
name: [{ required: true, message: '路由名称为必填项', trigger: 'blur' }],
path: [{ required: true, message: '路由路径为必填项且为"/"开头', trigger: ['blur', 'change'], pattern: /^\/.*/ }],
auths: [{ required: true, message: '权限标识为必填项', trigger: 'blur' }],
title: [{ required: true, message: $t('menuNameTip'), trigger: 'blur' }],
name: [{ required: true, message: $t('routerNameTip'), trigger: 'blur' }],
path: [{ required: true, message: $t('routerPathTip'), trigger: ['blur', 'change'], pattern: /^\/.*/ }],
});

View File

@ -30,7 +30,7 @@ export const onSearch = async () => {
/** 添加菜单 */
export function onAdd(parentId: any = 0) {
addDialog({
title: `新增菜单`,
title: $t('addNew') + $t('menu'),
props: {
formInline: {
menuType: 0,
@ -75,7 +75,7 @@ export function onAdd(parentId: any = 0) {
*/
export const onUpdate = (row?: FormItemProps) => {
addDialog({
title: `更新菜单`,
title: $t('update') + $t('menu'),
props: {
formInline: {
menuType: row?.menuType,
@ -209,7 +209,7 @@ export const onChangeMenuRank = async (row: any) => {
*/
export const assignRolesToRouter = (row: any) => {
addDialog({
title: `${$t(row.title)}分配角色`,
title: `${$t('for')}${$t(row.title)}${$t('assign_roles')}`,
width: '45%',
draggable: true,
closeOnClickModal: false,

View File

@ -14,6 +14,8 @@ import { usePowerStore } from '@/store/system/power';
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import { handleTree } from '@pureadmin/utils';
import { FormInstance } from 'element-plus';
import { auth } from '@/views/system/power/utils/auth';
import { hasAuth } from '@/router/utils';
const tableRef = ref();
const formRef = ref();
@ -62,34 +64,38 @@ onMounted(() => {
<template>
<div class="main">
<el-form ref="formRef" :inline="true" :model="powerStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item :label="$t('power_powerCode')" prop="powerCode">
<el-input v-model="powerStore.form.powerCode" :placeholder="`${$t('input')}${$t('power_powerCode')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('power_powerName')" prop="powerName">
<el-input v-model="powerStore.form.powerName" :placeholder="`${$t('input')}${$t('power_powerName')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('power_requestUrl')" prop="requestUrl">
<el-input v-model="powerStore.form.requestUrl" :placeholder="`${$t('input')}${$t('power_requestUrl')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="powerStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
<Auth :value="auth.search">
<el-form ref="formRef" :inline="true" :model="powerStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item :label="$t('power_powerCode')" prop="powerCode">
<el-input v-model="powerStore.form.powerCode" :placeholder="`${$t('input')}${$t('power_powerCode')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('power_powerName')" prop="powerName">
<el-input v-model="powerStore.form.powerName" :placeholder="`${$t('input')}${$t('power_powerName')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('power_requestUrl')" prop="requestUrl">
<el-input v-model="powerStore.form.requestUrl" :placeholder="`${$t('input')}${$t('power_requestUrl')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="powerStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
</Auth>
<PureTableBar :columns="columns" :isExpandAll="true" :tableRef="tableRef?.getTableRef()" :title="$t('power')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<!-- 添加权限按钮 -->
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd()"> {{ $t('addNew') }}</el-button>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd()">
{{ $t('addNew') }}
</el-button>
<!-- 批量更新父级id -->
<el-button :disabled="!(powerIds.length > 0)" :icon="useRenderIcon(EditPen)" type="primary" @click="onUpdateBatchParent">
<el-button v-if="hasAuth(auth.updateBatchByPowerWithParentId)" :disabled="!(powerIds.length > 0)" :icon="useRenderIcon(EditPen)" type="primary" @click="onUpdateBatchParent">
{{ $t('update_batches_parent') }}
</el-button>
<!-- 批量删除按钮 -->
<el-button :disabled="!(powerIds.length > 0)" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
<el-button v-if="hasAuth(auth.deleted)" :disabled="!(powerIds.length > 0)" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
{{ $t('deleteBatches') }}
</el-button>
</template>
@ -127,12 +133,14 @@ onMounted(() => {
{{ row.updateUsername }}
</el-button>
</template>
<template #operation="{ row }">
<!-- 修改 -->
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify') }} </el-button>
<el-button :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="onAdd(row.id)"> {{ $t('addNew') }} </el-button>
<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-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="onAdd(row.id)"> {{ $t('addNew') }} </el-button>
<!-- 删除 -->
<el-popconfirm :title="`${$t('delete')}${row.powerName}?`" @confirm="onDelete(row)">
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('delete')}${row.powerName}?`" @confirm="onDelete(row)">
<template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
{{ $t('delete') }}

View File

@ -0,0 +1,14 @@
export const auth = {
// 分页查询
search: ['power::getPowerList'],
// 获取所有权限
getAllPowers: ['power::getAllPowers'],
// 添加操作
add: ['power::addPower'],
// 更新操作
update: ['power::updatePower'],
// 批量修改权限父级
updateBatchByPowerWithParentId: ['power::updateBatchByPowerWithParentId'],
// 删除操作
deleted: ['power::deletePower'],
};

View File

@ -5,7 +5,7 @@ import type { FormRules } from 'element-plus';
// 表格列
export const columns: TableColumnList = [
{ type: 'selection', align: 'left' },
{ type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 },
{ type: 'index', index: (index: number) => index + 1, label: $t('index'), width: 60 },
// 权限编码
{ label: $t('power_powerCode'), prop: 'powerCode' },
// 权限名称

View File

@ -15,6 +15,8 @@ import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import { deviceDetection } from '@pureadmin/utils';
import Menu from '@iconify-icons/ep/menu';
import AssignPowersToRole from '@/views/system/role/assign-powers-to-role.vue';
import { auth } from '@/views/system/role/utils/auth';
import { hasAuth } from '@/router/utils';
const roleStore = useRoleStore();
@ -60,18 +62,20 @@ onMounted(() => {
<template>
<div class="main">
<el-form ref="formRef" :inline="true" :model="roleStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item :label="$t('role_roleCode')" prop="roleCode">
<el-input v-model="roleStore.form.roleCode" :placeholder="`${$t('input')}${$t('role_roleCode')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('role_description')" prop="description">
<el-input v-model="roleStore.form.description" :placeholder="`${$t('input')}${$t('role_description')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="roleStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
<Auth :value="auth.search">
<el-form ref="formRef" :inline="true" :model="roleStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item :label="$t('role_roleCode')" prop="roleCode">
<el-input v-model="roleStore.form.roleCode" :placeholder="`${$t('input')}${$t('role_roleCode')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('role_description')" prop="description">
<el-input v-model="roleStore.form.description" :placeholder="`${$t('input')}${$t('role_description')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="roleStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
</Auth>
<div ref="contentRef" :class="['flex', deviceDetection() ? 'flex-wrap' : '']">
<PureTableBar
@ -83,10 +87,12 @@ onMounted(() => {
@refresh="onSearch"
>
<template #buttons>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd"> {{ $t('addNew') }}</el-button>
<el-button v-if="hasAuth(auth.add)" :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd">
{{ $t('addNew') }}
</el-button>
<!-- 批量删除按钮 -->
<el-button v-show="deleteIds.length > 0" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
<el-button v-if="hasAuth(auth.deleted)" :disabled="!(deleteIds.length > 0)" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
{{ $t('delete_batches') }}
</el-button>
</template>
@ -125,10 +131,10 @@ onMounted(() => {
</template>
<template #operation="{ row }">
<!-- 修改 -->
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify') }} </el-button>
<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 :title="`${$t('delete')}${row.roleCode}?`" @confirm="onDelete(row)">
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('delete')}${row.roleCode}?`" @confirm="onDelete(row)">
<template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
{{ $t('delete') }}
@ -136,7 +142,9 @@ onMounted(() => {
</template>
</el-popconfirm>
<el-button :icon="useRenderIcon(Menu)" :size="size" class="reset-margin" link type="primary" @click="onMenuPowerClick(row)"> {{ $t('power_setting') }} </el-button>
<el-button v-if="hasAuth(auth.assignPowersToRole)" :icon="useRenderIcon(Menu)" :size="size" class="reset-margin" link type="primary" @click="onMenuPowerClick(row)">
{{ $t('power_setting') }}
</el-button>
</template>
</pure-table>
</template>

View File

@ -0,0 +1,12 @@
export const auth = {
// 分页查询
search: ['role::getRoleList'],
// 添加操作
add: ['role::addRole'],
// 更新操作
update: ['role::updateRole'],
// 删除操作
deleted: ['role::deleteRole'],
// 为角色分配权限
assignPowersToRole: ['rolePower::assignPowersToRole'],
};

View File

@ -4,7 +4,7 @@ import { $t } from '@/plugins/i18n';
// 表格列
export const columns: TableColumnList = [
{ type: 'selection', align: 'left' },
{ type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 },
{ type: 'index', index: (index: number) => index + 1, label: $t('index'), width: 60 },
// 角色代码
{ label: $t('role_roleCode'), prop: 'roleCode' },
// 描述