Просмотр исходного кода

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

shizhongming 2 лет назад
Родитель
Сommit
acb224ea84
25 измененных файлов с 1598 добавлено и 13 удалено
  1. 47 0
      src/api/sys/SystemApi.ts
  2. 2 0
      src/components/Form/index.ts
  3. 21 0
      src/components/Form/src/smart-boot/components/SmartApiSelectDict.vue
  4. 47 0
      src/components/Form/src/smart-boot/components/SmartApiSelectTable.vue
  5. 100 0
      src/components/Form/src/smart-boot/components/SmartUserSelectModal.vue
  6. 11 0
      src/components/Form/src/smart-boot/components/base/SmartTableSelect.less
  7. 127 0
      src/components/Form/src/smart-boot/components/base/SmartTableSelect.tsx
  8. 205 0
      src/components/Form/src/smart-boot/components/base/SmartTableSelectModal.tsx
  9. 103 0
      src/components/Form/src/smart-boot/components/user/SmartUserTableSelect.vue
  10. 236 0
      src/components/Form/src/smart-boot/hooks/useSmartTableSelect.ts
  11. 0 4
      src/components/SmartTable/src/SmartTable.less
  12. 1 1
      src/components/VxeTable/src/css/toolbar.scss
  13. 11 2
      src/components/registerGlobComp.ts
  14. 25 0
      src/directives/permission.ts
  15. 8 0
      src/enums/appEnum.ts
  16. 3 1
      src/hooks/web/usePermission.ts
  17. 70 0
      src/modules/system/views/role/RoleListView.api.ts
  18. 179 0
      src/modules/system/views/role/RoleListView.config.ts
  19. 144 0
      src/modules/system/views/role/RoleListView.vue
  20. 158 0
      src/modules/system/views/role/RoleSetFunction.vue
  21. 42 0
      src/modules/system/views/role/hook/useRoleSetUser.ts
  22. 24 0
      src/modules/system/views/role/lang/en_US.ts
  23. 24 0
      src/modules/system/views/role/lang/zh_CN.ts
  24. 7 5
      src/settings/projectSetting.ts
  25. 3 0
      types/config.d.ts

+ 47 - 0
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 - 0
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 ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
 export { default as ApiCascader } from './src/components/ApiCascader.vue';
 export { default as ApiCascader } from './src/components/ApiCascader.vue';
 export { default as ApiTransfer } from './src/components/ApiTransfer.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 };
 export { BasicForm };

+ 21 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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,
+  };
+};

+ 0 - 4
src/components/SmartTable/src/SmartTable.less

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

+ 1 - 1
src/components/VxeTable/src/css/toolbar.scss

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

+ 11 - 2
src/components/registerGlobComp.ts

@@ -1,6 +1,6 @@
 import type { App } from 'vue';
 import type { App } from 'vue';
 import { Button } from './Button';
 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 VXETable from 'vxe-table';
 
 
 import { i18n } from '@/locales/setupI18n';
 import { i18n } from '@/locales/setupI18n';
@@ -21,5 +21,14 @@ export function registerGlobComp(app: App) {
       return key;
       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 - 0
src/directives/permission.ts

@@ -6,6 +6,8 @@
 import type { App, Directive, DirectiveBinding } from 'vue';
 import type { App, Directive, DirectiveBinding } from 'vue';
 
 
 import { usePermission } from '@/hooks/web/usePermission';
 import { usePermission } from '@/hooks/web/usePermission';
+import { unref } from 'vue';
+import { NoPermissionModeEnum } from '@/enums/appEnum';
 
 
 function isAuth(el: Element, binding: any) {
 function isAuth(el: Element, binding: any) {
   const { hasPermission } = usePermission();
   const { hasPermission } = usePermission();
@@ -25,8 +27,31 @@ const authDirective: Directive = {
   mounted,
   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) {
 export function setupPermissionDirective(app: App) {
   app.directive('auth', authDirective);
   app.directive('auth', authDirective);
+  app.directive('permission', permissionDirective);
 }
 }
 
 
 export default authDirective;
 export default authDirective;

+ 8 - 0
src/enums/appEnum.ts

@@ -50,3 +50,11 @@ export enum RouterTransitionEnum {
   FADE_BOTTOM = 'fade-bottom',
   FADE_BOTTOM = 'fade-bottom',
   FADE_SCALE = 'fade-scale',
   FADE_SCALE = 'fade-scale',
 }
 }
+
+/**
+ * 无权限显示状态枚举
+ */
+export enum NoPermissionModeEnum {
+  hide = 'hide',
+  disabled = 'disabled',
+}

+ 3 - 1
src/hooks/web/usePermission.ts

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

+ 70 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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: '关联用户',
+        },
+      },
+    },
+  },
+};

+ 7 - 5
src/settings/projectSetting.ts

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

+ 3 - 0
types/config.d.ts

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