zhengyiming
2025-11-11 7f4fd9a7d182b8da1a7130355c718914cc086293
feat: s
9个文件已修改
3个文件已添加
1121 ■■■■ 已修改文件
.eslintrc-auto-import.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
auto-imports.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constants/apiEnumText.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.ts 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/common/tree.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CustomerManage/CustomerManage.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/EmploymentManage/TaskManageList.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/FlexJobManage/FlexJobManage.vue 95 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Permission/RoleManage.vue 276 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Permission/components/AddOrEditRoleDialog.vue 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Permission/components/dialogAuthorizeV2.vue 356 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/UserManage/UserManageList.vue 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc-auto-import.json
@@ -41,6 +41,7 @@
    "EnumEnterpriseCostType": true,
    "EnumEnterpriseRealMethod": true,
    "EnumEnterpriseType": true,
    "EnumEnterpriseTypeText": true,
    "EnumEnterpriseWalletAccess": true,
    "EnumEnterpriseWalletAccessText": true,
    "EnumEnterpriseWalletAccessTextForSettle": true,
auto-imports.d.ts
@@ -44,6 +44,7 @@
  const EnumEnterpriseCostType: typeof import('./src/constants/apiEnum')['EnumEnterpriseCostType']
  const EnumEnterpriseRealMethod: typeof import('./src/constants/apiEnum')['EnumEnterpriseRealMethod']
  const EnumEnterpriseType: typeof import('./src/constants/apiEnum')['EnumEnterpriseType']
  const EnumEnterpriseTypeText: typeof import('./src/constants/apiEnumText')['EnumEnterpriseTypeText']
  const EnumEnterpriseWalletAccess: typeof import('./src/constants/apiEnum')['EnumEnterpriseWalletAccess']
  const EnumEnterpriseWalletAccessText: typeof import('./src/constants/enterpriseWallet')['EnumEnterpriseWalletAccessText']
  const EnumEnterpriseWalletAccessTextForSettle: typeof import('./src/constants/task')['EnumEnterpriseWalletAccessTextForSettle']
@@ -373,6 +374,7 @@
    readonly EnumEnterpriseCostType: UnwrapRef<typeof import('./src/constants/apiEnum')['EnumEnterpriseCostType']>
    readonly EnumEnterpriseRealMethod: UnwrapRef<typeof import('./src/constants/apiEnum')['EnumEnterpriseRealMethod']>
    readonly EnumEnterpriseType: UnwrapRef<typeof import('./src/constants/apiEnum')['EnumEnterpriseType']>
    readonly EnumEnterpriseTypeText: UnwrapRef<typeof import('./src/constants/apiEnumText')['EnumEnterpriseTypeText']>
    readonly EnumEnterpriseWalletAccess: UnwrapRef<typeof import('./src/constants/apiEnum')['EnumEnterpriseWalletAccess']>
    readonly EnumEnterpriseWalletAccessText: UnwrapRef<typeof import('./src/constants/enterpriseWallet')['EnumEnterpriseWalletAccessText']>
    readonly EnumEnterpriseWalletAccessTextForSettle: UnwrapRef<typeof import('./src/constants/task')['EnumEnterpriseWalletAccessTextForSettle']>
src/constants/apiEnumText.ts
@@ -11,6 +11,11 @@
  [EnumUserType.Operation]: '运营',
};
export const EnumEnterpriseTypeText = {
  [EnumEnterpriseType.Supplier]: '承揽',
  [EnumEnterpriseType.PartyA]: '甲方',
};
export const EnumRoleWebApiDataPowerText = {
  [EnumRoleWebApiDataPower.Custom]: '自定义',
  [EnumRoleWebApiDataPower.Creator]: '个人数据',
src/router/index.ts
@@ -96,84 +96,6 @@
    },
  },
  {
    path: '/CustomerManage',
    redirect: 'noRedirect',
    component: Layout,
    hidden: false,
    alwaysShow: true,
    meta: {
      rank: 10010,
      title: '客户管理',
      rootMenu: true,
      icon: 'home',
    },
    children: [
      {
        path: '/CustomerManage',
        name: 'CustomerManage',
        hidden: false,
        alwaysShow: true,
        component: () => import('@/views/CustomerManage/CustomerManage.vue'),
        meta: {
          rank: 10011,
          title: '客户管理',
          // rootMenu: true,
          icon: 'home',
        },
      },
      {
        path: '/AddOrEditCustomer/:id?',
        name: 'AddOrEditCustomer',
        hidden: true,
        alwaysShow: false,
        component: () => import('@/views/CustomerManage/AddOrEditCustomer.vue'),
        meta: {
          rank: 10011,
          title: '新增客户',
        },
      },
      {
        path: '/CustomerDetail/:id',
        name: 'CustomerDetail',
        hidden: true,
        alwaysShow: false,
        component: () => import('@/views/CustomerManage/CustomerDetail.vue'),
        meta: {
          rank: 10011,
          title: '客户详情',
        },
      },
    ],
  },
  {
    path: '/FlexJobManage',
    redirect: 'noRedirect',
    component: Layout,
    hidden: false,
    alwaysShow: true,
    meta: {
      rank: 10010,
      title: '灵工管理',
      rootMenu: true,
      icon: 'home',
    },
    children: [
      {
        path: '/FlexJobManageList',
        name: 'FlexJobManageList',
        hidden: false,
        alwaysShow: true,
        component: () => import('@/views/FlexJobManage/FlexJobManage.vue'),
        meta: {
          rank: 10011,
          title: '灵工管理',
          // rootMenu: true,
          icon: 'home',
        },
      },
    ],
  },
  {
    path: '/EmploymentManage',
    redirect: 'noRedirect',
    component: Layout,
@@ -484,34 +406,6 @@
    ],
  },
  {
    path: '/UserManage',
    redirect: 'noRedirect',
    component: Layout,
    hidden: false,
    alwaysShow: true,
    meta: {
      rank: 10100,
      title: '用户管理',
      rootMenu: true,
      icon: 'home',
    },
    children: [
      {
        path: '/UserManageList',
        name: 'UserManageList',
        hidden: false,
        alwaysShow: true,
        component: () => import('@/views/UserManage/UserManageList.vue'),
        meta: {
          rank: 10101,
          title: '用户管理',
          // rootMenu: true,
          icon: 'home',
        },
      },
    ],
  },
  {
    path: '/Login',
    name: 'Login',
src/utils/common/tree.ts
@@ -1,3 +1,5 @@
import { TreeNodeData } from 'element-plus/es/components/tree/src/tree.type';
export function getTree(
  data: API.ModuleDto[],
  root?: string,
@@ -44,3 +46,27 @@
  return result;
}
type TreeCallback<T extends TreeNodeData, R> = (
  data: T,
  index: number,
  array: T[],
  parent?: T
) => R;
export function treeEach<T extends TreeNodeData>(
  treeData: T[],
  callback: TreeCallback<T, void>,
  getChildren: (data: T) => T[],
  parent?: T
) {
  for (let i = 0; i < treeData.length; i++) {
    const data = treeData[i];
    callback(data, i, treeData, parent);
    const children = getChildren(data);
    if (Array.isArray(children)) {
      treeEach(children, callback, getChildren, data);
    }
  }
}
src/views/CustomerManage/CustomerManage.vue
@@ -32,14 +32,16 @@
          </QueryFilterItem>
        </template>
        <template #btn>
          <el-button @click="goAddOrEdit()" icon="Plus" type="primary">新增客户</el-button>
          <el-button
            v-if="checkSubModuleItemShow('pageButton', 'addBtn')"
            @click="goAddOrEdit()"
            icon="Plus"
            type="primary"
            >新增客户</el-button
          >
        </template>
      </ProTableQueryFilterBar>
      <ProTableV2
        v-bind="proTableProps"
        :columns="CustomerManageColumns"
        :operationBtns="operationBtns"
      >
      <ProTableV2 v-bind="proTableProps" :columns="column" :operationBtns="operationBtns">
      </ProTableV2>
    </AppContainer>
  </LoadingLayout>
@@ -69,26 +71,22 @@
  name: 'CustomerManage',
});
const operationBtns = defineOperationBtns([
  {
    data: {
      enCode: 'detailBtn',
      name: '查看',
    },
const operationBtnMap: Record<string, OperationBtnType> = {
  detailBtn: {
    emits: {
      onClick: (role) => goDetail(role),
    },
  },
  {
    data: {
      enCode: 'editBtn',
      name: '编辑',
    },
  editBtn: {
    emits: {
      onClick: (role) => goAddOrEdit(role),
    },
  },
]);
};
const { checkSubModuleItemShow, column, operationBtns } = useAccess({
  operationBtnMap,
});
const BaseState = {
  loading: true,
src/views/EmploymentManage/TaskManageList.vue
@@ -87,6 +87,42 @@
  name: 'TaskManageList',
});
const operationBtnMap: Record<string, OperationBtnType> = {
  // editBtn: {
  //   emits: {
  //     onClick: (role) => goAddOrEdit(role),
  //   },
  //   extraProps: {
  //     hide: (row: API.GetTaskInfosQueryResultItem) => row.status === EnumTaskStatus.Complete,
  //   },
  // },
  detailBtn: {
    emits: {
      onClick: (row: API.GetTaskInfosQueryResultItem) => goDetail(row),
    },
  },
  // publishBtn: {
  //   emits: {
  //     onClick: (row: API.GetTaskInfosQueryResultItem) =>
  //       setTaskInfoReleaseStatus(row, EnumTaskReleaseStatus.InProcess),
  //   },
  //   extraProps: {
  //     hide: (row: API.GetTaskInfosQueryResultItem) =>
  //       row.releaseStatus === EnumTaskReleaseStatus.InProcess,
  //   },
  // },
  // unPublishBtn: {
  //   emits: {
  //     onClick: (row: API.GetTaskInfosQueryResultItem) =>
  //       setTaskInfoReleaseStatus(row, EnumTaskReleaseStatus.Stopped),
  //   },
  //   extraProps: {
  //     hide: (row: API.GetTaskInfosQueryResultItem) =>
  //       row.releaseStatus === EnumTaskReleaseStatus.Stopped,
  //   },
  // },
};
const operationBtns = defineOperationBtns([
  // {
  //   data: {
src/views/FlexJobManage/FlexJobManage.vue
@@ -79,17 +79,48 @@
          </QueryFilterItem>
        </template>
        <template #btn>
          <el-button @click="handleDownloadTemplate()" type="primary" link>模板下载</el-button>
          <el-button @click="handleBatchImportAdd()" type="primary">批量导入</el-button>
          <el-button @click="handleBatchUnSign()" type="primary">批量解约</el-button>
          <el-button @click="handleSendShotMessage()" type="primary">短信发送</el-button>
          <el-button @click="handleBatchSign()" type="primary">批量签约</el-button>
          <el-button @click="handleEnterpriseBatchSign()" type="primary">批量企业签约</el-button>
          <el-button
            v-if="checkSubModuleItemShow('pageButton', 'importBtn')"
            @click="handleDownloadTemplate()"
            type="primary"
            link
            >模板下载</el-button
          >
          <el-button
            v-if="checkSubModuleItemShow('pageButton', 'importBtn')"
            @click="handleBatchImportAdd()"
            type="primary"
            >批量导入</el-button
          >
          <el-button
            v-if="checkSubModuleItemShow('pageButton', 'batchUnSignBtn')"
            @click="handleBatchUnSign()"
            type="primary"
            >批量解约</el-button
          >
          <el-button
            v-if="checkSubModuleItemShow('pageButton', 'sendShotBtn')"
            @click="handleSendShotMessage()"
            type="primary"
            >短信发送</el-button
          >
          <el-button
            v-if="checkSubModuleItemShow('pageButton', 'batchSignBtn')"
            @click="handleBatchSign()"
            type="primary"
            >批量签约</el-button
          >
          <el-button
            v-if="checkSubModuleItemShow('pageButton', 'enterpriseBatchSignBtn')"
            @click="handleEnterpriseBatchSign()"
            type="primary"
            >批量企业签约</el-button
          >
        </template>
      </ProTableQueryFilterBar>
      <ProTableV2
        v-bind="proTableProps"
        :columns="FlexJobManageColumns"
        :columns="column"
        :operationBtns="operationBtns"
        show-column-check
        ref="proTable"
@@ -123,12 +154,10 @@
  FieldDatePicker,
  FieldRadio,
  FieldSelect,
  defineOperationBtns,
  useFormDialog,
  UploadUserFile,
  XLSXUtils,
} from '@bole-core/components';
import { FlexJobManageColumns } from './constants';
import { EnumTaskUserHireStatusText, EnumTaskUserSignContractStatusText } from '@/constants';
import { Message } from '@bole-core/core';
import { convertApi2FormUrlOnlyOne, downloadFileByUrl, format } from '@/utils';
@@ -145,31 +174,19 @@
  name: 'FlexJobManageList',
});
const operationBtns = defineOperationBtns([
  {
    data: {
      enCode: 'editBtn',
      name: '编辑',
    },
const operationBtnMap: Record<string, OperationBtnType> = {
  editBtn: {
    emits: {
      onClick: (role) => openDialog(role),
    },
  },
  {
    data: {
      enCode: 'detailBtn',
      name: '详情',
    },
  detailBtn: {
    emits: {
      onClick: (role: API.GetEnterpriseEmployeesQueryResultItem) =>
        handleStaffDetailEdit({ id: role.id, tabType: 'info' }),
    },
  },
  {
    data: {
      enCode: 'enterpriseSignBtn',
      name: '企业签约',
    },
  enterpriseSignBtn: {
    emits: {
      onClick: (role) => handleEnterpriseSign(role),
    },
@@ -181,11 +198,7 @@
        ),
    },
  },
  {
    data: {
      enCode: 'inviteSignBtn',
      name: '邀请签约',
    },
  inviteSignBtn: {
    emits: {
      onClick: (role) => handleInviteSign(role),
    },
@@ -197,11 +210,7 @@
        ),
    },
  },
  {
    data: {
      enCode: 'unSignBtn',
      name: '解约',
    },
  unSignBtn: {
    emits: {
      onClick: (role) => handleUnSign(role),
    },
@@ -213,17 +222,11 @@
        ),
    },
  },
  // {
  //   data: {
  //     enCode: 'delBtn',
  //     name: '删除',
  //   },
  //   props: { type: 'danger' },
  //   emits: {
  //     onClick: (role) => handleDelete(role),
  //   },
  // },
]);
};
const { checkSubModuleItemShow, column, operationBtns } = useAccess({
  operationBtnMap,
});
const router = useRouter();
src/views/Permission/RoleManage.vue
New file
@@ -0,0 +1,276 @@
<template>
  <LoadingLayout :loading="state.loading">
    <AppContainer>
      <ProTableQueryFilterBar @on-reset="reset">
        <template #query>
          <QueryFilterItem>
            <SearchInput
              v-model="extraParamState.queryCondition"
              style="width: 200px"
              placeholder="角色名称"
              @on-click-search="getList"
              @keyup.enter="getList()"
            >
            </SearchInput>
          </QueryFilterItem>
        </template>
        <template #btn>
          <el-button
            v-if="checkSubModuleItemShow('pageButton', 'addBtn')"
            @click="openDialog()"
            icon="Plus"
            type="primary"
            >新增</el-button
          >
        </template>
      </ProTableQueryFilterBar>
      <ProTableV2 v-bind="proTableProps" :columns="column" :operationBtns="operationBtns">
      </ProTableV2>
    </AppContainer>
    <AddOrEditRoleDialog v-bind="dialogProps" />
    <DialogAuthorizeV2 v-bind="dialogAuthorizeProps" authorizeType="Role" />
    <!-- <DialogMember v-model:visibleId="rowState.setMemberRoleId" /> -->
  </LoadingLayout>
</template>
<script setup lang="ts">
import {
  ProTableQueryFilterBar,
  OperationBtnType,
  ProTableV2,
  SearchInput,
  LoadingLayout,
  AppContainer,
  QueryFilterItem,
  useTable,
  useFormDialog,
  FieldRadio,
} from '@bole-core/components';
import { useAccess } from '@/hooks';
import { Message } from '@bole-core/core';
import AddOrEditRoleDialog from './components/AddOrEditRoleDialog.vue';
import { EnumUserType } from '@/constants';
import DialogAuthorizeV2 from './components/dialogAuthorizeV2.vue';
import * as roleServices from '@/services/api/role';
defineOptions({
  name: 'RoleManage',
});
const operationBtnMap: Record<string, OperationBtnType> = {
  editBtn: { emits: { onClick: (role) => openDialog(role) } },
  delBtn: { emits: { onClick: (role) => handleDeleteRole(role) }, props: { type: 'danger' } },
  authorize: { emits: { onClick: (role) => openAuthorizeDialog(role) } },
  // member: { emits: { onClick: (role) => openMemberDialog(role) } },
  disabledBtn: {
    emits: { onClick: (role) => roleEnableOrForbid(role) },
    props: { type: 'danger' },
    extraProps: {
      hide: (row) => row.isDisabled,
    },
  },
  enableBtn: {
    emits: { onClick: (role) => roleEnableOrForbid(role) },
    extraProps: {
      hide: (row) => !row.isDisabled,
    },
  },
};
const { checkSubModuleItemShow, column, operationBtns } = useAccess({
  operationBtnMap,
});
const BaseState = {
  loading: true,
};
const state = reactive({ ...BaseState });
onMounted(async () => {
  await getList();
  state.loading = false;
});
const {
  getDataSource: getList,
  proTableProps,
  paginationState,
  extraParamState,
  reset,
} = useTable(
  async ({ pageIndex, pageSize }, extraParamState) => {
    try {
      let params: API.GetRolesQuery = {
        pageModel: {
          rows: pageSize,
          page: pageIndex,
          orderInput: extraParamState.orderInput,
        },
        userType: AppLocalConfig.userType,
        clientType: AppLocalConfig.clientType,
        enterpriseType: AppLocalConfig.enterpriseType,
        keywords: extraParamState.queryCondition,
      };
      let res = await roleServices.getRoles(params, {
        showLoading: !state.loading,
      });
      return res;
    } catch (error) {}
  },
  {
    defaultExtraParams: {
      queryCondition: '',
      orderInput: [{ property: 'id', order: EnumPagedListOrder.Desc }],
    },
    queryKey: ['roleServices/getRoles'],
    columnsRenderProps: {
      dataPower: { type: 'enum', valueEnum: EnumRoleWebApiDataPowerText },
    },
  }
);
async function openDialog(row?: API.GetRolesQueryResultItem) {
  try {
    if (row) {
      const detail = await roleServices.getRole({ id: row.id });
      handleEdit({
        id: row.id,
        name: row.name,
        remark: row.remark,
        userType: row.userType,
        clientType: row.clientType,
        dataRange: row.dataPower,
        detail: detail,
        minLevel: row.minLevel,
        enterpriseType: row.enterpriseType,
      });
    } else {
      handleAdd({
        userType: AppLocalConfig.userType,
        clientType: AppLocalConfig.clientType,
      });
    }
  } catch (error) {}
}
const { dialogProps, handleAdd, handleEdit, editForm, dialogState } = useFormDialog({
  onConfirm: handleAddOrEdit,
  defaultFormParams: {
    id: '',
    name: '',
    remark: '',
    userType: AppLocalConfig.userType,
    clientType: AppLocalConfig.clientType,
    dataRange: EnumRoleWebApiDataPower.All,
    detail: null as API.GetRoleQueryResult,
    minLevel: 1,
    enterpriseType: AppLocalConfig.enterpriseType,
  },
});
async function handleAddOrEdit() {
  try {
    const isEdit = editForm.id;
    let params: API.SaveRoleCommand = {
      name: editForm.name,
      remark: editForm.remark,
      dataPower: editForm.dataRange,
      userType: editForm.userType,
      clientType: editForm.clientType,
      minLevel: editForm.minLevel,
    };
    if (editForm.userType === EnumUserType.Enterprise) {
      params.enterpriseType = editForm.enterpriseType;
    }
    if (isEdit) {
      params = {
        ...editForm.detail,
        ...params,
      };
    }
    let res = await roleServices.saveRole(params);
    if (res) {
      Message.successMessage('操作成功');
      getList(isEdit ? paginationState.pageIndex : 1);
    }
  } catch (error) {}
}
async function handleDeleteRole(row: API.GetRolesQueryResultItem) {
  try {
    await Message.deleteMessage();
    let params: API.DeleteRoleCommand = {
      ids: [row.id],
    };
    let res = await roleServices.deleteRole(params);
    if (res) {
      Message.successMessage('操作成功');
      getList(paginationState.pageIndex);
    }
  } catch (error) {}
}
async function roleEnableOrForbid(row: API.GetRolesQueryResultItem) {
  try {
    await Message.tipMessage(`是否${row.isDisabled ? '启用' : '禁用'}角色`);
    let res = await roleServices.setRoleIsDisabled({
      ids: [row.id],
      isDisabled: !row.isDisabled,
    });
    if (res) {
      Message.successMessage('操作成功');
      getList(paginationState.pageIndex);
      return !!res;
    }
  } catch (error) {}
}
const rowState = reactive({
  authorizeId: '',
  setMemberRoleId: '',
});
const {
  dialogProps: dialogAuthorizeProps,
  handleAdd: handleAuthorizeAdd,
  editForm: authorizeForm,
} = useFormDialog({
  onConfirm: handleAuthorize,
  defaultFormParams: {
    detail: null as API.GetRoleQueryResult,
  },
});
async function openAuthorizeDialog(row: API.GetRolesQueryResultItem) {
  try {
    const detail = await roleServices.getRole({ id: row.id });
    handleAuthorizeAdd({
      detail: detail,
    });
  } catch (error) {}
}
async function handleAuthorize(selectedMenuIds: string[]) {
  console.log('selectedMenuIds: ', selectedMenuIds);
  try {
    let params: API.SaveRoleCommand = {
      ...authorizeForm.detail,
      menuIds: selectedMenuIds,
      // resources: resourceIds.map((x) => ({
      //   resourceId: x,
      //   dataPower: EnumRoleWebApiDataPower.All,
      // })),
    };
    let res = await roleServices.saveRole(params);
    if (res) {
      Message.successMessage('操作成功');
      getList(paginationState.pageIndex);
    }
  } catch (error) {}
}
// function openMemberDialog(row: API.IdentityRoleDto) {
//   rowState.setMemberRoleId = row.id;
// }
</script>
src/views/Permission/components/AddOrEditRoleDialog.vue
New file
@@ -0,0 +1,142 @@
<template>
  <ProDialog
    :title="innerForm.title"
    v-model="innerVisible"
    @close="onDialogClose"
    destroy-on-close
    draggable
  >
    <ProForm :rules="rules" :model="innerForm" ref="dialogForm" label-width="110px">
      <ProFormItemV2 label="角色名称" prop="name">
        <ProFormText placeholder="请输入角色名称" v-model.trim="innerForm.name"></ProFormText>
      </ProFormItemV2>
      <ProFormItemV2 label="等级" prop="minLevel">
        <ProFormInputNumber
          v-model="innerForm.minLevel"
          :min="1"
          :max="100"
          :controls="false"
        ></ProFormInputNumber>
      </ProFormItemV2>
      <!-- <ProFormItemV2
        label="企业类型"
        prop="enterpriseType"
        v-if="form.userType === EnumUserType.Enterprise"
      >
        <ProFormRadio
          v-model="form.enterpriseType"
          :value-enum="EnumEnterpriseTypeText"
          :buttonStyle="false"
        ></ProFormRadio>
      </ProFormItemV2> -->
      <ProFormItemV2 label="数据可见范围" prop="dataRange">
        <ProFormRadio
          v-model="form.dataRange"
          :value-enum="EnumRoleWebApiDataPowerTextForFilter"
          :buttonStyle="false"
        ></ProFormRadio>
      </ProFormItemV2>
      <ProFormItemV2 label="备注:" prop="remark">
        <ProFormTextArea
          v-model="innerForm.remark"
          placeholder="请输入备注"
          show-word-limit
          :maxlength="2000"
        ></ProFormTextArea>
      </ProFormItemV2>
    </ProForm>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="emit('onCancel')">取 消</el-button>
        <el-button type="primary" @click="handleConfirm">确 定</el-button>
      </span>
    </template>
  </ProDialog>
</template>
<script setup lang="ts">
import { FormRules, FormInstance } from 'element-plus';
import {
  ProDialog,
  ProForm,
  ProFormItemV2,
  ProFormText,
  ProFormRadio,
  ProFormTextArea,
  ProFormInputNumber,
} from '@bole-core/components';
import {
  EnumRoleWebApiDataPowerTextForFilter,
  EnumEnterpriseTypeText,
  EnumUserType,
} from '@/constants';
defineOptions({
  name: 'AddOrEditRoleDialog',
});
type Props = {
  modelValue: boolean;
  form: {
    id: string;
    title?: string;
    name: string;
    remark: string;
    dataRange: EnumRoleWebApiDataPower;
    minLevel: number;
    enterpriseType: EnumEnterpriseType;
    userType: EnumUserType;
  };
};
const props = withDefaults(defineProps<Props>(), {
  modelValue: false,
});
const emit = defineEmits<{
  (e: 'update:modelValue', value: boolean): void;
  (e: 'update:form', value: Props['form']): void;
  (e: 'onConfirm'): void;
  (e: 'onCancel'): void;
}>();
const dialogForm = ref<FormInstance>();
const innerVisible = computed({
  get() {
    return props.modelValue;
  },
  set(val) {
    emit('update:modelValue', val);
  },
});
const innerForm = computed({
  get() {
    return props.form;
  },
  set(val) {
    emit('update:form', val);
  },
});
const rules = reactive<FormRules>({
  name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
});
function onDialogClose() {
  if (!dialogForm.value) return;
  dialogForm.value.resetFields();
}
function handleConfirm() {
  if (!dialogForm.value) return;
  dialogForm.value.validate((valid) => {
    if (valid) {
      emit('onConfirm');
    } else {
      return;
    }
  });
}
</script>
src/views/Permission/components/dialogAuthorizeV2.vue
New file
@@ -0,0 +1,356 @@
<template>
  <ProDialog
    class="custom-dialog"
    width="55%"
    :title="dialogTitle"
    v-model="visible"
    :top="'10vh'"
    :close-on-click-modal="false"
    :close-on-press-escape="false"
  >
    <div class="authorize-wrapper">
      <div class="container-wrapper left-wrapper">
        <el-scrollbar>
          <el-tree
            class="companyTree"
            :data="menusTree"
            :props="{
              children: 'children',
              label: 'name',
            }"
            node-key="id"
            :expand-on-click-node="false"
            :highlight-current="true"
            default-expand-all
            show-checkbox
            ref="moduleTree"
            :default-checked-keys="defaultCheckedKeys"
            @check="handleModuleCheck"
          >
            <template #default="{ node }">
              <div class="custom-tree-node">
                <type-tip :isMenu="node.data.type === EnumMenuType.Menu" />
                <div class="node-text" @click="handleSelectModule(node.data)">
                  {{ node.label }}
                </div>
              </div>
            </template>
          </el-tree>
        </el-scrollbar>
      </div>
      <div class="container-wrapper">
        <div class="type-wrapper">
          {{ SubModuleTitle[SubModuleType.PageButton] }}
        </div>
        <el-scrollbar>
          <el-tree
            v-show="!!state.currentMenuId"
            :data="[
              {
                name: '全选',
                id: 'pageButtonAll',
                children: menuPageButtons,
              },
            ]"
            :props="{
              children: 'children',
              label: 'name',
            }"
            node-key="id"
            :expand-on-click-node="false"
            :highlight-current="true"
            default-expand-all
            show-checkbox
            ref="pageButtonTree"
            :default-checked-keys="defaultCheckedKeys"
            @check="handlePageButtonCheck"
          >
            <template #default="{ node }">
              <div class="custom-tree-node">
                <span>{{ node.label }}</span>
              </div>
            </template>
          </el-tree>
        </el-scrollbar>
      </div>
      <div class="container-wrapper">
        <div class="type-wrapper">
          {{ SubModuleTitle[SubModuleType.DataButton] }}
        </div>
        <el-scrollbar>
          <el-tree
            v-show="!!state.currentMenuId"
            :data="[
              {
                name: '全选',
                id: 'dataButtonAll',
                children: menuDataButtons,
              },
            ]"
            :props="{
              children: 'children',
              label: 'name',
            }"
            node-key="id"
            :expand-on-click-node="false"
            :highlight-current="true"
            default-expand-all
            show-checkbox
            ref="dataButtonTree"
            :default-checked-keys="defaultCheckedKeys"
            @check="handleDataButtonCheck"
          >
            <template #default="{ node }">
              <div class="custom-tree-node">
                <span>{{ node.label }}</span>
              </div>
            </template>
          </el-tree>
        </el-scrollbar>
      </div>
      <div class="container-wrapper">
        <div class="type-wrapper">
          {{ SubModuleTitle[SubModuleType.Column] }}
        </div>
        <el-scrollbar>
          <el-tree
            v-show="!!state.currentMenuId"
            :data="[
              {
                name: '全选',
                id: 'dataColumnAll',
                children: menuFields,
              },
            ]"
            :props="{
              children: 'children',
              label: 'name',
            }"
            node-key="id"
            :expand-on-click-node="false"
            :highlight-current="true"
            default-expand-all
            show-checkbox
            ref="dataColumnTree"
            :default-checked-keys="defaultCheckedKeys"
            @check="handleDataColumnCheck"
          >
            <template #default="{ node }">
              <div class="custom-tree-node">
                <span>{{ node.label }}</span>
              </div>
            </template>
          </el-tree>
        </el-scrollbar>
      </div>
    </div>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="visible = false">取消</el-button>
        <el-button type="primary" @click="handleConfirm" class="btn-submit"> 确定 </el-button>
      </span>
    </template>
  </ProDialog>
</template>
<script setup lang="ts">
import { Message } from '@bole-core/core';
import { SubModuleType, SubModuleTitle, EnumMenuType } from '@/constants';
import { getTree, treeEach } from '@/utils';
import { TreeInstance } from 'element-plus';
import { ProDialog } from '@bole-core/components';
import { useMenus, useMenu } from '@/hooks';
import data from '@iconify-icons/ep/tickets';
const TypeTip = defineComponent({
  name: 'TypeTip',
  props: ['isMenu'],
  render() {
    const { isMenu } = this;
    const tipText = isMenu ? `菜单` : '页面';
    return h(
      'span',
      {
        class: 'tip-text',
        style: {
          color: '#EB6100',
        },
      },
      '[' + tipText + ']'
    );
  },
});
type Props = {
  authorizeType: 'Role' | 'User';
};
const props = withDefaults(defineProps<Props>(), {});
const visible = defineModel({ type: Boolean });
type Form = {
  title?: string;
  detail: API.GetRoleQueryResult;
};
const form = defineModel<Form>('form');
const emit = defineEmits<{
  (e: 'onConfirm', selectedMenuIds: string[]): void;
  (e: 'onCancel'): void;
}>();
type CheckedResourceItem = {
  resourceId: string;
  menuId: string;
  resourceType: SubModuleType;
};
const state = reactive({
  currentMenuId: '',
  selectedMenuIds: [] as string[],
});
const defaultCheckedKeys = computed(() => Array.from(state.selectedMenuIds));
watch(visible, (newVal) => {
  if (newVal) {
    state.currentMenuId = '';
    const menuIds = form.value.detail?.menuIds ?? [];
    // menuIds.forEach((id) => state.selectedMenuIds.add(id));
    state.selectedMenuIds = [...menuIds];
  }
});
const dialogTitle = computed(() =>
  props.authorizeType === 'Role' ? '角色功能授权' : '账号功能授权'
);
const moduleTree = useTemplateRef<TreeInstance>('moduleTree');
const pageButtonTree = useTemplateRef<TreeInstance>('pageButtonTree');
const dataButtonTree = useTemplateRef<TreeInstance>('dataButtonTree');
const dataColumnTree = useTemplateRef<TreeInstance>('dataColumnTree');
type TreeRef = ReturnType<typeof useTemplateRef<TreeInstance>>;
const { menusTree, getMenuById } = useMenus({
  params: computed(() => ({
    userType: form.value.detail?.userType,
    clientType: form.value.detail?.clientType,
    enterpriseType: form.value.detail?.enterpriseType,
    roleId: form.value.detail?.id,
  })),
  enabled: computed(() => !!form.value.detail?.id),
});
const { menuFields, menuPageButtons, menuDataButtons } = useMenu({
  params: computed(() => ({
    id: state.currentMenuId,
    roleId: form.value.detail?.id,
  })),
  enabled: computed(() => !!state.currentMenuId),
});
function handleSelectModule(menu: API.GetMenusQueryResultItem) {
  state.currentMenuId = menu.id;
}
function handleModuleCheck(data, params) {
  handleCheck(data, params, moduleTree);
}
function handlePageButtonCheck(data, params) {
  handleCheck(data, params, pageButtonTree);
}
function handleDataButtonCheck(data, params) {
  handleCheck(data, params, dataButtonTree);
}
function handleDataColumnCheck(data, params) {
  handleCheck(data, params, dataColumnTree);
}
function handleCheck(data, params, treeRef: TreeRef) {
  const dataMap = {};
  treeEach(
    [treeRef.value.store.root],
    (node) => (dataMap[node.key] = node),
    (node) => node.childNodes
  );
  const uncachedCheckedKeys = params.checkedKeys.filter(
    (item) => !['pageButtonAll', 'dataButtonAll', 'dataColumnAll'].includes(item)
  );
  const cachedKeys = state.selectedMenuIds.filter(
    (item) => !(item in dataMap) && !uncachedCheckedKeys.includes(item)
  );
  state.selectedMenuIds = cachedKeys.concat(uncachedCheckedKeys);
}
function handleConfirm() {
  emit('onConfirm', state.selectedMenuIds);
}
</script>
<style lang="scss" scoped>
:deep(.custom-dialog) {
  min-width: 900px;
}
.authorize-wrapper {
  display: flex;
  height: 500px;
  border-bottom: 2px solid #f5f5f5;
  background: #dddddd;
  .container-wrapper {
    width: calc(25%);
    border-right: 2px solid #f5f5f5;
    // margin-right: 7px;
    background: #ffffff;
    .type-wrapper {
      display: flex;
      justify-content: flex-start;
      align-items: center;
      padding: 10px 16px;
      border-bottom: 2px solid #f5f5f5;
      color: #333333;
    }
    &:last-of-type {
      margin-right: 0px;
    }
    &.left-wrapper {
      margin-right: 7px;
      padding-top: 10px;
      width: calc(35% - 7px);
      :deep(.el-scrollbar) {
        width: 100%;
        height: 98%;
      }
    }
    :deep(.el-scrollbar) {
      width: 100%;
      height: calc(98% - 43px);
      .el-scrollbar__wrap {
        overflow: auto;
        .custom-tree-node {
          display: flex;
        }
      }
    }
  }
}
</style>
src/views/UserManage/UserManageList.vue
@@ -14,14 +14,16 @@
          </QueryFilterItem>
        </template>
        <template #btn>
          <el-button @click="openDialog()" icon="Plus" type="primary">新增</el-button>
          <el-button
            v-if="checkSubModuleItemShow('pageButton', 'addBtn')"
            @click="openDialog()"
            icon="Plus"
            type="primary"
            >新增</el-button
          >
        </template>
      </ProTableQueryFilterBar>
      <ProTableV2
        v-bind="proTableProps"
        :columns="UserManageColumns"
        :operationBtns="operationBtns"
      >
      <ProTableV2 v-bind="proTableProps" :columns="column" :operationBtns="operationBtns">
        <template #status="{ row }">
          <FieldSwitch
            v-model="row.status"
@@ -52,11 +54,9 @@
  QueryFilterItem,
  useTable,
  useFormDialog,
  defineOperationBtns,
  FieldSwitch,
} from '@bole-core/components';
import * as userServices from '@/services/api/user';
import { UserManageColumns } from './constants';
import { EnumUserStatus, EnumUserStatusText } from '@/constants';
import { ModelValueType } from 'element-plus';
import { Message } from '@bole-core/core';
@@ -68,21 +68,13 @@
  name: 'UserManageList',
});
const operationBtns = defineOperationBtns([
  {
    data: {
      enCode: 'editBtn',
      name: '编辑',
    },
const operationBtnMap: Record<string, OperationBtnType> = {
  editBtn: {
    emits: {
      onClick: (role) => openDialog(role),
    },
  },
  {
    data: {
      enCode: 'resetPasswordBtn',
      name: '重置密码',
    },
  resetPasswordBtn: {
    props: {
      type: 'danger',
    },
@@ -90,11 +82,7 @@
      onClick: (role) => openResetPasswordDialog(role),
    },
  },
  {
    data: {
      enCode: 'resetOperatorPasswordBtn',
      name: '重置操作密码',
    },
  resetOperatorPasswordBtn: {
    props: {
      type: 'danger',
    },
@@ -102,7 +90,11 @@
      onClick: (role) => openResetOperatorPasswordDialog(role),
    },
  },
]);
};
const { checkSubModuleItemShow, column, operationBtns } = useAccess({
  operationBtnMap,
});
const router = useRouter();
const BaseState = {