Browse Source

feat(系统模块): 添加角色管理界面

shizhongming 2 years ago
parent
commit
acb224ea84
  1. 47
      src/api/sys/SystemApi.ts
  2. 2
      src/components/Form/index.ts
  3. 21
      src/components/Form/src/smart-boot/components/SmartApiSelectDict.vue
  4. 47
      src/components/Form/src/smart-boot/components/SmartApiSelectTable.vue
  5. 100
      src/components/Form/src/smart-boot/components/SmartUserSelectModal.vue
  6. 11
      src/components/Form/src/smart-boot/components/base/SmartTableSelect.less
  7. 127
      src/components/Form/src/smart-boot/components/base/SmartTableSelect.tsx
  8. 205
      src/components/Form/src/smart-boot/components/base/SmartTableSelectModal.tsx
  9. 103
      src/components/Form/src/smart-boot/components/user/SmartUserTableSelect.vue
  10. 236
      src/components/Form/src/smart-boot/hooks/useSmartTableSelect.ts
  11. 4
      src/components/SmartTable/src/SmartTable.less
  12. 2
      src/components/VxeTable/src/css/toolbar.scss
  13. 13
      src/components/registerGlobComp.ts
  14. 25
      src/directives/permission.ts
  15. 8
      src/enums/appEnum.ts
  16. 4
      src/hooks/web/usePermission.ts
  17. 70
      src/modules/system/views/role/RoleListView.api.ts
  18. 179
      src/modules/system/views/role/RoleListView.config.ts
  19. 144
      src/modules/system/views/role/RoleListView.vue
  20. 158
      src/modules/system/views/role/RoleSetFunction.vue
  21. 42
      src/modules/system/views/role/hook/useRoleSetUser.ts
  22. 24
      src/modules/system/views/role/lang/en_US.ts
  23. 24
      src/modules/system/views/role/lang/zh_CN.ts
  24. 12
      src/settings/projectSetting.ts
  25. 3
      types/config.d.ts

47
src/api/sys/SystemApi.ts

@ -0,0 +1,47 @@
import { defHttp, ApiServiceEnum } from '@/utils/http/axios';
enum Api {
listUser = 'sys/user/list',
listUserById = 'sys/user/listById',
listSystem = 'sys/system/list',
listSystemFilterByUser = 'sys/system/listAuthUser',
}
/**
*
* @param params
* @param useYn
*/
export const listUserApi = (params: Recordable = {}, useYn = true) => {
let parameter = params.parameter;
if (useYn) {
parameter = {
...parameter,
'useYn@=': true,
};
}
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.listUser,
data: {
...params,
parameter,
},
});
};
export const listUserByIdApi = (ids: any[]) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.listUserById,
data: ids,
});
};
export const listSystemApi = (params, filterByUser = false) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: filterByUser ? Api.listSystemFilterByUser : Api.listSystem,
data: params,
});
};

2
src/components/Form/index.ts

@ -13,5 +13,7 @@ export { default as ApiTree } from './src/components/ApiTree.vue';
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
export { default as ApiCascader } from './src/components/ApiCascader.vue';
export { default as ApiTransfer } from './src/components/ApiTransfer.vue';
export { default as SmartTableSelect } from './src/smart-boot/components/base/SmartTableSelect';
export { default as SmartUserSelectModal } from './src/smart-boot/components/SmartUserSelectModal.vue';
export { BasicForm };

21
src/components/Form/src/smart-boot/components/SmartApiSelectDict.vue

@ -0,0 +1,21 @@
<template>
<ApiSelect v-bind="$attrs" value-field="dictItemCode" label-field="dictItemName" :api="api" />
</template>
<script lang="ts" setup>
import ApiSelect from '../../components/ApiSelect.vue';
import { propTypes } from '@/utils/propTypes';
import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
const props = defineProps({
dictCode: propTypes.string.isRequired,
});
const api = () => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: 'sys/dict/listItemByCode',
data: props.dictCode,
});
};
</script>

47
src/components/Form/src/smart-boot/components/SmartApiSelectTable.vue

@ -0,0 +1,47 @@
<!--
查询表信息
-->
<template>
<ApiSelect
v-bind="$attrs"
:params="getParams"
value-field="value"
label-field="label"
:api="api"
/>
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes';
import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
import ApiSelect from '/@/components/Form/src/components/ApiSelect.vue';
import { computed } from 'vue';
const props = defineProps({
//
modelClassName: propTypes.string.isRequired,
valueFieldName: propTypes.string.isRequired,
labelFieldName: propTypes.string.isRequired,
params: propTypes.object.def({}),
});
const getParams = computed(() => {
const { modelClassName, valueFieldName, labelFieldName, params } = props;
return {
modelClassName,
valueFieldName,
labelFieldName,
queryParameter: params,
};
});
const api = (params) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: 'api/component/smart-form/listTableSelect',
data: params,
});
};
</script>
<style scoped></style>

100
src/components/Form/src/smart-boot/components/SmartUserSelectModal.vue

@ -0,0 +1,100 @@
<!--人员选择弹窗-->
<template>
<SmartTableSelectModal
v-bind="$attrs"
:table-props="tableProps"
:select-table-props="commonTableProps"
@register="registerModal"
label-field="fullName"
:list-api="listUserByIdApi"
@select-data="handleSelectData"
value-field="userId"
/>
</template>
<script lang="ts" setup>
import type { SmartTableProps } from '@/components/SmartTable';
import { reactive } from 'vue';
import { useI18n } from '@/hooks/web/useI18n';
import { listUserApi, listUserByIdApi } from '@/api/sys/SystemApi';
import { useModal } from '@/components/Modal';
import SmartTableSelectModal from './base/SmartTableSelectModal';
const { t } = useI18n();
const emit = defineEmits(['update:selectValues', 'selected']);
const [registerModal] = useModal();
const handleSelectData = (options: LabelValueOptions) => {
const userIdList = options.map((item) => item.value);
emit('update:selectValues', userIdList);
emit('selected', userIdList);
};
const commonTableProps: SmartTableProps = {
pagerConfig: true,
columns: [
{
type: 'checkbox',
width: 60,
align: 'center',
fixed: 'left',
},
{
title: '{system.views.user.table.username}',
field: 'username',
width: 120,
fixed: 'left',
},
{
title: '{system.views.user.table.fullName}',
field: 'fullName',
minWidth: 120,
fixed: 'left',
},
{
title: '{system.views.user.table.userType}',
field: 'userType',
width: 120,
},
],
};
const tableProps = reactive<SmartTableProps>({
useSearchForm: true,
checkboxConfig: {
rowTrigger: 'multiple',
},
searchFormConfig: {
compact: true,
colon: true,
searchWithSymbol: true,
actionColOptions: {
span: 12,
},
baseColProps: {
span: 12,
},
schemas: [
{
label: t('system.views.user.table.fullName'),
field: 'fullName',
component: 'Input',
searchSymbol: 'like',
},
],
},
proxyConfig: {
ajax: {
query: (params) => listUserApi(params.ajaxParameter),
},
},
...commonTableProps,
});
</script>
<style scoped></style>

11
src/components/Form/src/smart-boot/components/base/SmartTableSelect.less

@ -0,0 +1,11 @@
.smart-table-select {
@width: 80px;
.select {
width: calc(100% - @width - 8px);
}
.button {
width: @width;
}
}

127
src/components/Form/src/smart-boot/components/base/SmartTableSelect.tsx

@ -0,0 +1,127 @@
import type { SmartTableProps } from '@/components/SmartTable';
import { defineComponent, ref } from 'vue';
import { propTypes } from '@/utils/propTypes';
import { useModal } from '@/components/Modal';
import SmartTableSelectModal from './SmartTableSelectModal';
import './SmartTableSelect.less';
export default defineComponent({
name: 'SmartTableSelect',
props: {
// 是否支持多选
multiple: propTypes.bool.def(true),
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
// label字段
labelField: propTypes.string.isRequired,
// value字段
valueField: propTypes.string.isRequired,
tableProps: {
type: Object as PropType<SmartTableProps>,
required: true,
},
disabled: propTypes.bool.def(false),
size: String as PropType<string>,
},
emits: ['update:value', 'change'],
setup(props, { emit }) {
const [registerModal, { openModal }] = useModal();
const optionsRef = ref<Array<any>>([]);
const handleOptionChange = (options) => {
optionsRef.value = options;
};
const handleSelectData = (options: any[]) => {
emit(
'update:value',
options.map((item) => item.value),
);
emit(
'change',
options.map((item) => item.value),
);
};
const handleDeselect = (value) => {
const data = (props.value as any[]).filter((item) => item !== value);
emit('update:value', data);
emit('change', data);
};
return {
registerModal,
openModal,
handleSelectData,
optionsRef,
handleDeselect,
handleOptionChange,
};
},
render() {
const {
$attrs,
multiple,
tableProps,
$slots,
disabled,
$t,
openModal,
registerModal,
labelField,
valueField,
handleSelectData,
optionsRef,
value,
handleDeselect,
handleOptionChange,
size,
} = this;
const modalSlots: any = {
table: $slots.table,
};
return (
<div class="smart-table-select">
<a-row type="flex" gutter={8}>
<a-col class="select">
<a-select
{...$attrs}
size={size}
disabled={disabled}
style={{ width: '100%' }}
options={optionsRef}
open={false}
value={value}
onDeselect={handleDeselect}
mode={multiple ? 'multiple' : 'combobox'}
></a-select>
</a-col>
<a-col class="button">
<a-button
disabled={disabled}
size={size}
type="primary"
onClick={() => openModal(true, value || {})}
>
{$t('common.button.choose')}
</a-button>
</a-col>
</a-row>
<SmartTableSelectModal
{...$attrs}
onRegister={registerModal}
labelField={labelField}
onOptionChange={handleOptionChange}
onSelectData={handleSelectData}
valueField={valueField}
// @ts-ignore
selectValues={value}
multiple={multiple}
tableProps={tableProps}
>
{modalSlots}
</SmartTableSelectModal>
</div>
);
},
});

205
src/components/Form/src/smart-boot/components/base/SmartTableSelectModal.tsx

@ -0,0 +1,205 @@
import type { SmartTableProps } from '@/components/SmartTable';
import { computed, defineComponent, toRefs, unref, watch } from 'vue';
import { propTypes } from '@/utils/propTypes';
import { Col, Row } from 'ant-design-vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { SmartTable } from '@/components/SmartTable';
import { useSmartTableSelect } from '../../hooks/useSmartTableSelect';
export default defineComponent({
name: 'SmartTableSelectModal',
components: {
BasicModal,
},
props: {
tableProps: {
type: Object as PropType<SmartTableProps>,
required: true,
},
selectTableProps: {
type: Object as PropType<Partial<SmartTableProps>>,
},
// 是否多选
multiple: propTypes.bool.def(true),
// 是否显示选中
showSelect: propTypes.bool.def(false),
// label字段
labelField: propTypes.string.isRequired,
// value字段
valueField: propTypes.string.isRequired,
selectValues: propTypes.array.def([]),
listApi: {
type: Function as PropType<(data: any) => Promise<any>>,
required: true,
},
// 是否每次弹窗都加载数据
alwaysLoad: propTypes.bool.def(false),
},
emits: ['register', 'select-data', 'option-change'],
setup(props, { emit, slots }) {
const { tableProps, selectTableProps, valueField, selectValues, alwaysLoad, multiple } =
toRefs(props);
const hasTableSlot = computed<boolean>(() => {
return slots.table !== undefined;
});
const emitSelectData = () => {
const selectOptions = getSelectOptions();
closeModal();
emit('option-change', selectOptions);
emit('select-data', selectOptions, unref(selectRowsRef));
};
const getSelectOptions = (): LabelValueOptions => {
return unref(selectRowsRef).map((item) => {
return {
label: item[props.labelField],
value: item[props.valueField],
};
});
};
const {
registerTable,
handleCheckboxChange,
registerSelectTable,
selectRowsRef,
setSelectData,
addSelectData,
removeSelectData,
getSelectData,
getTableCheckboxConfig,
handleCheckboxAll,
getHasSearchForm,
query,
getTableRadioConfig,
handleRadioChange,
handleSetSelect,
} = useSmartTableSelect(
tableProps,
selectTableProps,
props.showSelect,
valueField,
selectValues,
hasTableSlot,
props.listApi,
alwaysLoad,
multiple,
);
const [registerModal, { closeModal }] = useModalInner(async (_) => {
if (unref(alwaysLoad)) {
await query();
await handleSetSelect();
}
});
watch(selectRowsRef, () => {
const selectOptions = getSelectOptions();
emit('option-change', selectOptions);
});
const handleOk = () => {
emitSelectData();
};
return {
registerModal,
registerTable,
setSelectData,
addSelectData,
removeSelectData,
getSelectData,
handleCheckboxChange,
registerSelectTable,
selectRowsRef,
handleOk,
getTableCheckboxConfig,
handleCheckboxAll,
getHasSearchForm,
getTableRadioConfig,
handleRadioChange,
};
},
render() {
const {
$attrs,
registerModal,
$slots,
setSelectData,
handleOk,
addSelectData,
removeSelectData,
selectRowsRef,
} = this;
return (
<BasicModal {...$attrs} onRegister={registerModal} onOk={handleOk}>
{$slots.table
? $slots.table({
setSelectData,
addSelectData,
removeSelectData,
selectData: selectRowsRef,
})
: renderTable(this)}
</BasicModal>
);
},
});
const renderTable = (instance) => {
const {
$attrs,
showSelect,
multiple,
registerTable,
handleCheckboxChange,
registerSelectTable,
selectRowsRef,
getTableCheckboxConfig,
handleCheckboxAll,
getHasSearchForm,
getTableRadioConfig,
handleRadioChange,
} = instance;
let tableAttrs = {
...$attrs,
};
if (multiple) {
tableAttrs = {
...tableAttrs,
checkboxConfig: unref(getTableCheckboxConfig),
onCheckboxChange: handleCheckboxChange,
onCheckboxAll: handleCheckboxAll,
};
} else {
tableAttrs = {
...tableAttrs,
radioConfig: unref(getTableRadioConfig),
onRadioChange: handleRadioChange,
};
}
return (
<Row>
<Col span={showSelect ? 12 : 24}>
<SmartTable
onRegister={registerTable}
checkboxConfig={getTableCheckboxConfig}
onCheckboxChange={handleCheckboxChange}
onCheckboxAll={handleCheckboxAll}
{...tableAttrs}
/>
</Col>
{showSelect ? (
<Col style={getHasSearchForm ? { marginTop: '58px' } : ''} span={12}>
<SmartTable data={selectRowsRef} onRegister={registerSelectTable} />
</Col>
) : (
''
)}
</Row>
);
};

103
src/components/Form/src/smart-boot/components/user/SmartUserTableSelect.vue

@ -0,0 +1,103 @@
<template>
<SmartTableSelect v-bind="computedProps" />
</template>
<script lang="ts" setup>
import type { SmartTableProps } from '@/components/SmartTable';
import { computed, useAttrs } from 'vue';
import SmartTableSelect from '../base/SmartTableSelect';
import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
import { useI18n } from '@/hooks/web/useI18n';
import { listUserApi } from '@/api/sys/SystemApi';
const { t } = useI18n();
const listUserById = (ids) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: 'sys/user/listById',
data: ids,
});
};
const tableProps: SmartTableProps = {
pagerConfig: true,
useSearchForm: true,
proxyConfig: {
ajax: {
query: ({ ajaxParameter }) => listUserApi(ajaxParameter),
},
},
checkboxConfig: {
rowTrigger: 'multiple',
highlight: true,
},
rowConfig: {
isHover: true,
},
searchFormConfig: {
compact: true,
colon: true,
layout: 'inline',
searchWithSymbol: true,
actionColOptions: { span: undefined },
baseColProps: {
span: 12,
},
schemas: [
{
label: t('system.views.user.table.fullName'),
field: 'fullName',
component: 'Input',
searchSymbol: 'like',
},
],
},
columns: [
{
type: 'checkbox',
width: 60,
align: 'center',
fixed: 'left',
},
{
title: '{system.views.user.table.username}',
field: 'username',
width: 120,
fixed: 'left',
},
{
title: '{system.views.user.table.fullName}',
field: 'fullName',
minWidth: 120,
fixed: 'left',
},
{
title: '{system.views.user.table.userType}',
field: 'userType',
width: 120,
},
],
};
const defaultProps = {
title: '选择人员',
labelField: 'fullName',
valueField: 'userId',
multiple: true,
listApi: listUserById,
tableProps,
defaultFullscreen: true,
};
const computedProps = computed(() => {
return {
...defaultProps,
...useAttrs(),
};
});
</script>
<style scoped></style>

236
src/components/Form/src/smart-boot/hooks/useSmartTableSelect.ts

@ -0,0 +1,236 @@
import type { SmartTableProps } from '@/components/SmartTable';
import { useSmartTable } from '@/components/SmartTable';
import type { ComputedRef, Ref } from 'vue';
import { computed, ref, unref, watch } from 'vue';
import { remove } from 'lodash-es';
export const useSmartTableSelect = (
tablePropsRef: Ref<SmartTableProps>,
selectTablePropsRef: Ref<SmartTableProps | undefined>,
showSelect: boolean,
valueFieldRef: Ref<string>,
selectValuesRef: Ref<Array<any>>,
hasTableSlot: ComputedRef<boolean>,
listApi: ((data: any) => Promise<any>) | undefined,
alwaysLoad: Ref<boolean>,
multiple: Ref<boolean>,
) => {
const getTableProps = computed<SmartTableProps>(() => {
const tableProps = unref(tablePropsRef);
if (unref(alwaysLoad) && tableProps.proxyConfig) {
tableProps.proxyConfig.autoLoad = false;
}
return {
...tableProps,
rowConfig: {
keyField: unref(valueFieldRef),
},
};
});
/**
*
*/
const getHasSearchForm = computed(() => {
return unref(tablePropsRef).useSearchForm;
});
const getTableCheckboxConfig = computed(() => {
return {
highlight: true,
checkRowKeys: unref(selectValuesRef),
};
});
/**
*
*/
const getTableRadioConfig = computed(() => {
const result: Recordable = {
highlight: true,
strict: false,
reserve: true,
};
const selectValues = unref(selectValuesRef);
if (selectValues && selectValues.length > 0) {
result.checkRowKey = selectValues[0];
}
return result;
});
watch(selectValuesRef, async () => {
selectRowsRef.value = await getSelectRows();
if (!unref(hasTableSlot)) {
handleSetSelect();
}
});
const handleSetSelect = async () => {
if (unref(multiple)) {
await handleSetSelectRows();
} else {
await handleSetRadioRow();
}
};
const handleSetSelectRows = async () => {
await getTableInstance()?.setAllCheckboxRow(false);
await setCheckboxRow(unref(selectRowsRef), true);
};
const handleSetRadioRow = async () => {
const selectRows = unref(selectRowsRef);
if (selectRows && selectRows.length > 0) {
await getTableInstance()?.clearRadioRow();
await getTableInstance()?.setRadioRow(selectRows[0]);
}
};
/**
*
*/
const getSelectRows = async () => {
const selectValues = unref(selectValuesRef);
if (!selectValues || selectValues.length === 0) {
return [];
}
const valueField = unref(valueFieldRef);
let tableData: any[] = [];
try {
tableData = getData();
} catch (e) {
// do nothing
}
// 没有匹配上的数据
let noDataValue: any[] = [];
const matchDataList: any[] = [];
if (tableData) {
tableData.forEach((item) => {
const key = item[valueField];
if (selectValues.includes(key)) {
matchDataList.push(item);
}
});
const matchKeyList = matchDataList.map((item) => item[valueField]);
noDataValue = selectValues.filter((item) => !matchKeyList.includes(item));
}
if (noDataValue.length > 0) {
// 没有匹配的数据
// 1、从已经选中的数据中查找
const selectRows = unref(selectRowsRef);
if (selectRows.length > 0) {
selectRows.forEach((item) => {
if (noDataValue.includes(item[valueField])) {
matchDataList.push(item);
}
});
const matchKeyList2 = matchDataList.map((item) => item[valueField]);
noDataValue = noDataValue.filter((item) => !matchKeyList2.includes(item));
}
}
if (noDataValue.length > 0) {
// 通过API查询
const result = await listApi!(noDataValue);
matchDataList.push(...result);
}
return matchDataList;
};
const [registerTable, { setCheckboxRow, getData, getTableInstance, query }] = useSmartTable(
unref(getTableProps),
);
const [registerSelectTable, { setPagination }] = useSmartTable(unref(selectTablePropsRef) || {});
const selectRowsRef = ref<any[]>([]);
/**
*
* @param dataList
*/
const setSelectData = (dataList: any[]) => {
console.log('-----------------');
selectRowsRef.value = dataList;
};
/**
*
* @param dataList
*/
const addSelectData = (dataList: any[]) => {
const selectRows = unref(selectRowsRef);
selectRows.push(...dataList);
};
/**
*
* @param dataList
*/
const removeSelectData = (dataList: any[]) => {
const selectRows = unref(selectRowsRef);
const valueField = unref(valueFieldRef);
remove(selectRows, (item) => {
return dataList.some((current) => current[valueField] === item[valueField]);
});
};
/**
*
*/
const getSelectData = () => unref(selectRowsRef);
const handleCheckboxChange = ({ checked, row }) => {
const selectRows = unref(selectRowsRef);
if (checked) {
addSelectData([row]);
} else {
removeSelectData([row]);
}
if (showSelect) {
setPagination({
total: selectRows.length,
});
}
};
/**
*
*/
const handleRadioChange = ({ row, newValue }) => {
setSelectData([]);
if (newValue) {
addSelectData([row]);
}
};
const handleCheckboxAll = ({ checked }) => {
const currentDataList = getData();
if (!currentDataList || currentDataList.length === 0) {
return;
}
if (checked) {
const keyList = unref(selectRowsRef).map((item) => item[unref(valueFieldRef)]);
addSelectData(
currentDataList.filter((item) => !keyList.includes(item[unref(valueFieldRef)])),
);
} else {
removeSelectData(currentDataList);
}
};
return {
registerTable,
handleCheckboxChange,
registerSelectTable,
selectRowsRef,
setSelectData,
addSelectData,
getSelectData,
removeSelectData,
getTableCheckboxConfig,
handleCheckboxAll,
getData,
getHasSearchForm,
query,
getTableRadioConfig,
handleRadioChange,
handleSetSelect,
};
};

4
src/components/SmartTable/src/SmartTable.less

@ -2,10 +2,6 @@
/* 表格样式 */
.smart-table {
.vxe-tools--wrapper {
margin-right: 12px;
}
/* 文本颜色 */
.text-color--success {

2
src/components/VxeTable/src/css/toolbar.scss

@ -8,7 +8,7 @@
margin-left: 10px;
}
.vxe-toolbar .vxe-tools--wrapper,
.vxe-toolbar .vxe-tools--wrapper .vxe-button,
.vxe-toolbar .vxe-tools--operate .vxe-button {
margin-left: 1px;
border-radius: 0 !important;

13
src/components/registerGlobComp.ts

@ -1,6 +1,6 @@
import type { App } from 'vue';
import { Button } from './Button';
import { Input, Layout, Radio, Tag } from 'ant-design-vue';
import { Input, Layout, Radio, Tag, Select, Tooltip, Tree } from 'ant-design-vue';
import VXETable from 'vxe-table';
import { i18n } from '@/locales/setupI18n';
@ -21,5 +21,14 @@ export function registerGlobComp(app: App) {
return key;
},
});
app.use(Input).use(Button).use(Layout).use(Radio).use(Tag).use(VXETable);
app
.use(Input)
.use(Button)
.use(Layout)
.use(Radio)
.use(Tag)
.use(Select)
.use(Tooltip)
.use(Tree)
.use(VXETable);
}

25
src/directives/permission.ts

@ -6,6 +6,8 @@
import type { App, Directive, DirectiveBinding } from 'vue';
import { usePermission } from '@/hooks/web/usePermission';
import { unref } from 'vue';
import { NoPermissionModeEnum } from '@/enums/appEnum';
function isAuth(el: Element, binding: any) {
const { hasPermission } = usePermission();
@ -25,8 +27,31 @@ const authDirective: Directive = {
mounted,
};
const permissionDirective: Directive = {
beforeMount(el, binding) {
const permission = binding.value;
if (!permission) {
return;
}
const { hasPermission, getNoPermissionMode } = usePermission();
const has = hasPermission(permission);
if (!has) {
const noPermissionMode = unref(getNoPermissionMode);
if (el.type === 'button') {
if (noPermissionMode === NoPermissionModeEnum.disabled) {
el.disabled = true;
} else if (noPermissionMode === NoPermissionModeEnum.hide) {
el.style.display = 'none';
}
}
// TODO:其他情况未处理
}
},
};
export function setupPermissionDirective(app: App) {
app.directive('auth', authDirective);
app.directive('permission', permissionDirective);
}
export default authDirective;

8
src/enums/appEnum.ts

@ -50,3 +50,11 @@ export enum RouterTransitionEnum {
FADE_BOTTOM = 'fade-bottom',
FADE_SCALE = 'fade-scale',
}
/**
*
*/
export enum NoPermissionModeEnum {
hide = 'hide',
disabled = 'disabled',
}

4
src/hooks/web/usePermission.ts

@ -16,6 +16,7 @@ import { RoleEnum } from '@/enums/roleEnum';
import { intersection } from 'lodash-es';
import { isArray } from '@/utils/is';
import { useMultipleTabStore } from '@/store/modules/multipleTab';
import { computed } from 'vue';
// User permissions related operations
export function usePermission() {
@ -115,5 +116,6 @@ export function usePermission() {
resume();
}
return { changeRole, hasPermission, togglePermissionMode, refreshMenu };
const getNoPermissionMode = computed(() => appStore.getProjectConfig.noPermissionMode);
return { changeRole, hasPermission, togglePermissionMode, refreshMenu, getNoPermissionMode };
}

70
src/modules/system/views/role/RoleListView.api.ts

@ -0,0 +1,70 @@
import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
enum Api {
list = 'sys/role/list',
getById = 'sys/role/getById',
listUser = 'sys/user/list',
listUserByRoleId = 'sys/user/listUserByRoleId',
setRoleUser = 'sys/role/setRoleUser',
delete = 'sys/role/batchDeleteById',
batchSaveUpdate = 'sys/role/batchSaveUpdate',
}
export const listApi = (parameter) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.list,
data: parameter,
});
};
export const deleteApi = (parameter: any[]) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.delete,
data: parameter.map((item) => item.roleId),
});
};
export const getByIdApi = (model) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.getById,
data: model.roleId,
});
};
export const listUserApi = (parameter?) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.listUser,
data: parameter,
});
};
export const listUserByRoleIdApi = (roleIds: number[]) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.listUserByRoleId,
data: roleIds,
});
};
export const setRoleUserApi = (roleId: number, userIdList: number[]) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.setRoleUser,
data: {
roleId,
userIdList,
},
});
};
export const batchSaveUpdateApi = (dataList: any[]) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.batchSaveUpdate,
data: dataList,
});
};

179
src/modules/system/views/role/RoleListView.config.ts

@ -0,0 +1,179 @@
import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
import type { FormSchema } from '@/components/Form';
import { tableUseYnClass } from '@/components/SmartTable';
export const getTableColumns = (): SmartColumn[] => {
return [
{
type: 'checkbox',
width: 60,
align: 'center',
fixed: 'left',
},
{
title: '{system.views.role.table.roleName}',
field: 'roleName',
width: 120,
fixed: 'left',
},
{
title: '{system.views.role.table.roleCode}',
field: 'roleCode',
width: 150,
fixed: 'left',
},
{
title: '{system.views.role.table.roleType}',
field: 'roleType',
width: 120,
},
{
...tableUseYnClass(),
sortable: true,
},
{
title: '{common.table.remark}',
field: 'remark',
minWidth: 160,
},
{
title: '{common.table.seq}',
field: 'seq',
width: 100,
sortable: true,
},
{
title: '{common.table.createTime}',
field: 'createTime',
width: 165,
sortable: true,
},
{
title: '{common.table.createUser}',
field: 'createUserId',
width: 120,
formatter: ({ row }: any) => {
if (row.createUser) {
return row.createUser.fullName;
}
return '';
},
},
{
title: '{common.table.updateTime}',
field: 'updateTime',
width: 165,
sortable: true,
},
{
title: '{common.table.updateUser}',
field: 'updateUserId',
width: 120,
formatter: ({ row }: any) => {
if (row.updateUser) {
return row.updateUser.fullName;
}
return '';
},
},
{
title: '{common.table.operation}',
field: 'operation',
width: 120,
fixed: 'right',
slots: {
default: 'table-operation',
},
},
];
};
export const getSearchSchemas = (t: Function): SmartSearchFormSchema[] => {
return [
{
label: t('system.views.role.table.roleName'),
field: 'roleName',
component: 'Input',
searchSymbol: 'like',
componentProps: {
style: {
width: '130px',
},
},
},
{
label: t('system.views.role.table.roleCode'),
field: 'roleCode',
component: 'Input',
searchSymbol: 'like',
componentProps: {
style: {
width: '130px',
},
},
},
{
label: t('common.title.useYn'),
field: 'useYn',
component: 'Select',
defaultValue: 1,
searchSymbol: '=',
componentProps: {
style: {
width: '100px',
},
options: [
{
label: t('common.form.use'),
value: 1,
},
{
label: t('common.form.noUse'),
value: 0,
},
],
},
},
];
};
export const getAddEditFormSchemas = (t: Function): FormSchema[] => {
return [
{
label: '',
field: 'roleId',
component: 'Input',
show: false,
},
{
label: t('system.views.role.table.roleCode'),
field: 'roleCode',
component: 'Input',
required: true,
},
{
label: t('system.views.role.table.roleName'),
field: 'roleName',
component: 'Input',
required: true,
},
{
label: t('common.table.useYn'),
field: 'useYn',
component: 'Switch',
defaultValue: true,
},
{
label: t('system.views.role.table.roleType'),
field: 'roleType',
component: 'Input',
},
{
label: t('common.table.seq'),
field: 'seq',
component: 'InputNumber',
required: true,
defaultValue: 1,
},
];
};

144
src/modules/system/views/role/RoleListView.vue

@ -0,0 +1,144 @@
<template>
<div class="full-height page-container">
<a-layout class="full-height">
<a-layout-content class="full-height">
<SmartTable
:size="getTableSize"
@register="registerTable"
@current-change="handleCurrentChange"
>
<template #table-operation="{ row }">
<SmartVxeTableAction :actions="getTableActions(row)" />
</template>
</SmartTable>
</a-layout-content>
<a-layout-sider theme="light" class="layout-set-function" width="240px">
<RoleSetFunction :role-id="currentRow.roleId" />
</a-layout-sider>
</a-layout>
<SmartUserSelectModal
@register="registerSetUserModal"
@selected="handleSetUser"
defaultFullscreen
showSelect
:title="$t('system.views.role.button.setRoleUser')"
:select-values="selectUserList"
/>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import {
useSmartTable,
SmartTable,
SmartVxeTableAction,
ActionItem,
} from '@/components/SmartTable';
import { useI18n } from '@/hooks/web/useI18n';
import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
import { SmartUserSelectModal } from '@/components/Form';
import { getAddEditFormSchemas, getSearchSchemas, getTableColumns } from './RoleListView.config';
import { listApi, batchSaveUpdateApi, deleteApi, getByIdApi } from './RoleListView.api';
import { SystemPermissions } from '@/modules/system/constants/SystemConstants';
import RoleSetFunction from './RoleSetFunction.vue';
import { useRoleSetUser } from './hook/useRoleSetUser';
import { hasPermission } from '@/utils/auth';
const { t } = useI18n();
const { getTableSize } = useSizeSetting();
const permissions = SystemPermissions.role;
const currentRow = ref<Recordable>({});
const handleCurrentChange = ({ row }: any) => {
currentRow.value = row;
};
const { registerSetUserModal, handleSetUser, handleShowSetUser, selectUserList } =
useRoleSetUser(t);
const getTableActions = (row): ActionItem[] => {
return [
{
label: t('common.button.edit'),
preIcon: 'ant-design:edit-out-lined',
auth: permissions.update,
onClick: () => editByRowModal(row),
},
{
label: t('system.views.role.button.setRoleUser'),
preIcon: 'ant-design:user-add-outlined',
auth: permissions.setRoleUser,
onClick: () => {
handleShowSetUser(row);
},
},
];
};
const [registerTable, { editByRowModal }] = useSmartTable({
id: 'sys_role_list',
columns: getTableColumns(),
border: true,
stripe: true,
height: 'auto',
highlightHoverRow: true,
highlightCurrentRow: true,
pagerConfig: true,
columnConfig: {
resizable: true,
},
useSearchForm: true,
searchFormConfig: {
compact: true,
colon: true,
searchWithSymbol: true,
schemas: getSearchSchemas(t),
layout: 'inline',
actionColOptions: {
span: undefined,
},
},
proxyConfig: {
ajax: {
query: (params) => listApi(params.ajaxParameter),
delete: ({ body: { removeRecords } }) => deleteApi(removeRecords),
getById: (model) => getByIdApi(model),
save: ({ body: { insertRecords, updateRecords } }) =>
batchSaveUpdateApi([...insertRecords, ...updateRecords]),
},
},
printConfig: {},
authConfig: {
authHandler: hasPermission,
},
toolbarConfig: {
zoom: true,
refresh: true,
column: true,
buttons: [
{ code: 'ModalAdd', auth: permissions.add },
{ code: 'delete', auth: permissions.delete },
],
},
addEditConfig: {
formConfig: {
schemas: getAddEditFormSchemas(t),
colon: true,
wrapperCol: { span: 18 },
labelCol: { span: 5 },
baseColProps: {
span: 24,
},
},
},
});
</script>
<style lang="less" scoped>
.layout-set-function {
margin-left: 5px;
}
</style>

158
src/modules/system/views/role/RoleSetFunction.vue

@ -0,0 +1,158 @@
<template>
<a-layout class="full-height">
<a-layout-header style="height: 48px; background: white; line-height: 48px; text-align: center">
<h3>{{ $t('system.views.role.title.setFunction') }}</h3>
</a-layout-header>
<Divider style="margin: 0" />
<a-layout-content style=" overflow: auto;background: white">
<Spin :spinning="dataLoading">
<a-tree
ref="treeRef"
v-model:checkedKeys="checkedKeysModel"
:tree-data="functionTreeData"
checkable
/>
</Spin>
</a-layout-content>
<Divider style="margin: 0" />
<a-layout-footer style="height: 50px; padding: 10px 0; background: white; text-align: center">
<div style="padding: 0 5px">
<a-button
v-permission="permissions.setFunction"
:loading="saveLoading"
block
type="primary"
@click="handleSave"
>
{{ $t('common.button.save') }}
</a-button>
</div>
</a-layout-footer>
</a-layout>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, toRefs, watch, unref } from 'vue';
import type { PropType } from 'vue';
import { message, Spin, Divider } from 'ant-design-vue';
import TreeUtils from '@/utils/TreeUtils';
import { SystemPermissions } from '@/modules/system/constants/SystemConstants';
import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
/**
* 设置角色对应的功能
*/
export default defineComponent({
name: 'RoleSetFunction',
components: {
Spin,
Divider,
},
props: {
roleId: {
type: Number as PropType<number>,
default: null,
},
},
setup(props) {
const { roleId } = toRefs(props);
const treeRef = ref();
//
const functionTreeData = ref<Array<any>>([]);
const dataLoading = ref(false);
const saveLoading = ref(false);
const checkedKeysModel = ref([]);
/**
* 加载功能树函数
*/
const loadFunctionTreeData = async () => {
dataLoading.value = true;
try {
const result = await defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: 'sys/function/list',
data: {
sortName: 'seq',
},
});
functionTreeData.value =
TreeUtils.convertList2Tree(
result.map(({ functionId, functionName, parentId }: any) => {
return {
key: functionId,
title: functionName,
parentId: parentId,
};
}),
(item) => item.key,
(item) => item.parentId,
0,
) || [];
} finally {
dataLoading.value = false;
}
};
/**
* 加载角色对应的功能ID
*/
const loadRoleFunctions = async () => {
if (roleId.value !== null) {
dataLoading.value = true;
try {
checkedKeysModel.value = await defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: 'sys/role/listFunctionId',
params: {
id: unref(roleId),
},
});
} finally {
dataLoading.value = false;
}
}
};
/**
* 执行保存操作
*/
const handleSave = async () => {
const tree = unref(treeRef);
if (roleId.value === null) {
message.error('请先选定角色');
return false;
}
saveLoading.value = true;
try {
await defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: 'sys/role/saveRoleMenu',
data: {
roleId: roleId.value,
functionIdList: tree.checkedKeys,
halfFunctionIdList: tree.halfCheckedKeys,
},
});
message.success('保存成功');
} finally {
saveLoading.value = false;
}
};
watch(roleId, loadRoleFunctions);
onMounted(loadFunctionTreeData);
return {
functionTreeData,
dataLoading,
saveLoading,
checkedKeysModel,
handleSave,
permissions: SystemPermissions.role,
treeRef,
};
},
});
</script>
<style scoped></style>

42
src/modules/system/views/role/hook/useRoleSetUser.ts

@ -0,0 +1,42 @@
import { useModal } from '@/components/Modal';
import { message } from 'ant-design-vue';
import { ref, unref } from 'vue';
import { listUserByRoleIdApi, setRoleUserApi } from '../RoleListView.api';
export const useRoleSetUser = (t: Function) => {
const [registerSetUserModal, { openModal, setModalProps, closeModal }] = useModal();
const currentRole = ref<Recordable | null>(null);
const selectUserList = ref<number[]>([]);
const handleShowSetUser = async (role: Recordable) => {
currentRole.value = role;
openModal(true, { a: 1 });
try {
setModalProps({ loading: true });
const result = await listUserByRoleIdApi([role.roleId]);
selectUserList.value = result.map((item) => item.userId);
} finally {
setModalProps({ loading: false });
}
};
const handleSetUser = async (userId: number[]) => {
selectUserList.value = userId;
try {
setModalProps({ confirmLoading: true });
await setRoleUserApi(unref(currentRole)!.roleId, userId);
message.success(t('common.message.OperationSucceeded'));
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
};
return {
selectUserList,
handleShowSetUser,
registerSetUserModal,
handleSetUser,
};
};

24
src/modules/system/views/role/lang/en_US.ts

@ -0,0 +1,24 @@
export default {
system: {
views: {
role: {
title: {
setFunction: 'Set function',
},
table: {
roleName: 'Role name',
roleCode: 'Role code',
roleType: 'Role type',
},
validate: {
roleName: 'Please enter role name',
roleCode: 'Please enter role code',
roleType: 'Please enter role type',
},
button: {
setRoleUser: 'Set user',
},
},
},
},
};

24
src/modules/system/views/role/lang/zh_CN.ts

@ -0,0 +1,24 @@
export default {
system: {
views: {
role: {
title: {
setFunction: '设置功能',
},
table: {
roleName: '角色名称',
roleCode: '角色编码',
roleType: '角色类型',
},
validate: {
roleName: '请输入角色名称',
roleCode: '请输入角色编码',
roleType: '请输入角色类型',
},
button: {
setRoleUser: '关联用户',
},
},
},
},
};

12
src/settings/projectSetting.ts

@ -1,18 +1,19 @@
import type { ProjectConfig } from '#/config';
import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '@/enums/menuEnum';
import { MenuModeEnum, MenuTypeEnum, MixSidebarTriggerEnum, TriggerEnum } from '@/enums/menuEnum';
import { CacheTypeEnum } from '@/enums/cacheEnum';
import {
ContentEnum,
NoPermissionModeEnum,
PermissionModeEnum,
ThemeEnum,
RouterTransitionEnum,
SettingButtonPositionEnum,
SessionTimeoutProcessingEnum,
SettingButtonPositionEnum,
ThemeEnum,
} from '@/enums/appEnum';
import {
SIDE_BAR_BG_COLOR_LIST,
HEADER_PRESET_BG_COLOR_LIST,
APP_PRESET_COLOR_LIST,
HEADER_PRESET_BG_COLOR_LIST,
SIDE_BAR_BG_COLOR_LIST,
} from './designSetting';
// ! You need to clear the browser cache after the change
@ -189,6 +190,7 @@ const setting: ProjectConfig = {
table: 'small',
form: 'small',
},
noPermissionMode: NoPermissionModeEnum.disabled,
};
export default setting;

3
types/config.d.ts

@ -6,6 +6,7 @@ import {
RouterTransitionEnum,
SettingButtonPositionEnum,
SessionTimeoutProcessingEnum,
NoPermissionModeEnum,
} from '@/enums/appEnum';
import { CacheTypeEnum } from '@/enums/cacheEnum';
@ -138,6 +139,8 @@ export interface ProjectConfig {
removeAllHttpPending: boolean;
// 尺寸配置
sizeConfig: SizeConfig;
// 无权限模式
noPermissionMode: NoPermissionModeEnum;
}
export interface GlobConfig {

Loading…
Cancel
Save