Explorar el Código

新增套餐管理界面,支持套餐菜单权限、批量添加套餐租户和移除套餐租户功能
优化角色管理添加员工标题显示角色名称
优化权限管理新增权限点API接口默认展开第一级
更新admin接口

zhontai hace 2 años
padre
commit
5a719e136b

+ 19 - 0
src/api/admin/Dict.ts

@@ -84,6 +84,25 @@ export class DictApi<SecurityDataType = unknown> extends HttpClient<SecurityData
       format: 'json',
       ...params,
     })
+  /**
+   * No description
+   *
+   * @tags dict
+   * @name GetListByNames
+   * @summary 根据字典类型名称列表查询字典列表
+   * @request POST:/api/admin/dict/get-list-by-names
+   * @secure
+   */
+  getListByNames = (data: string[], params: RequestParams = {}) =>
+    this.request<ResultOutputDictionaryStringListDictGetListDto, any>({
+      path: `/api/admin/dict/get-list-by-names`,
+      method: 'POST',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      format: 'json',
+      ...params,
+    })
   /**
    * No description
    *

+ 325 - 0
src/api/admin/Pkg.ts

@@ -0,0 +1,325 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+import { AxiosResponse } from 'axios'
+import {
+  PageInputPkgGetPageDto,
+  PkgAddInput,
+  PkgAddPkgTenantListInput,
+  PkgSetPkgPermissionsInput,
+  PkgUpdateInput,
+  ResultOutputInt64,
+  ResultOutputListInt64,
+  ResultOutputListPkgGetListOutput,
+  ResultOutputListPkgGetPkgTenantListOutput,
+  ResultOutputPageOutputPkgGetPageOutput,
+  ResultOutputPkgGetOutput,
+} from './data-contracts'
+import { ContentType, HttpClient, RequestParams } from './http-client'
+
+export class PkgApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name Get
+   * @summary 查询
+   * @request GET:/api/admin/pkg/get
+   * @secure
+   */
+  get = (
+    query?: {
+      /** @format int64 */
+      id?: number
+    },
+    params: RequestParams = {}
+  ) =>
+    this.request<ResultOutputPkgGetOutput, any>({
+      path: `/api/admin/pkg/get`,
+      method: 'GET',
+      query: query,
+      secure: true,
+      format: 'json',
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name GetList
+   * @summary 查询列表
+   * @request GET:/api/admin/pkg/get-list
+   * @secure
+   */
+  getList = (
+    query?: {
+      /** 名称 */
+      Name?: string
+    },
+    params: RequestParams = {}
+  ) =>
+    this.request<ResultOutputListPkgGetListOutput, any>({
+      path: `/api/admin/pkg/get-list`,
+      method: 'GET',
+      query: query,
+      secure: true,
+      format: 'json',
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name GetPage
+   * @summary 查询分页
+   * @request POST:/api/admin/pkg/get-page
+   * @secure
+   */
+  getPage = (data: PageInputPkgGetPageDto, params: RequestParams = {}) =>
+    this.request<ResultOutputPageOutputPkgGetPageOutput, any>({
+      path: `/api/admin/pkg/get-page`,
+      method: 'POST',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      format: 'json',
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name GetPkgTenantList
+   * @summary 查询套餐租户列表
+   * @request GET:/api/admin/pkg/get-pkg-tenant-list
+   * @secure
+   */
+  getPkgTenantList = (
+    query?: {
+      /** 租户名 */
+      TenantName?: string
+      /**
+       * 套餐Id
+       * @format int64
+       */
+      PkgId?: number
+    },
+    params: RequestParams = {}
+  ) =>
+    this.request<ResultOutputListPkgGetPkgTenantListOutput, any>({
+      path: `/api/admin/pkg/get-pkg-tenant-list`,
+      method: 'GET',
+      query: query,
+      secure: true,
+      format: 'json',
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name GetPkgPermissionList
+   * @summary 查询套餐权限列表
+   * @request GET:/api/admin/pkg/get-pkg-permission-list
+   * @secure
+   */
+  getPkgPermissionList = (
+    query?: {
+      /**
+       * 套餐编号
+       * @format int64
+       */
+      pkgId?: number
+    },
+    params: RequestParams = {}
+  ) =>
+    this.request<ResultOutputListInt64, any>({
+      path: `/api/admin/pkg/get-pkg-permission-list`,
+      method: 'GET',
+      query: query,
+      secure: true,
+      format: 'json',
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name SetPkgPermissions
+   * @summary 设置套餐权限
+   * @request POST:/api/admin/pkg/set-pkg-permissions
+   * @secure
+   */
+  setPkgPermissions = (data: PkgSetPkgPermissionsInput, params: RequestParams = {}) =>
+    this.request<AxiosResponse, any>({
+      path: `/api/admin/pkg/set-pkg-permissions`,
+      method: 'POST',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name AddPkgTenant
+   * @summary 添加套餐租户
+   * @request POST:/api/admin/pkg/add-pkg-tenant
+   * @secure
+   */
+  addPkgTenant = (data: PkgAddPkgTenantListInput, params: RequestParams = {}) =>
+    this.request<AxiosResponse, any>({
+      path: `/api/admin/pkg/add-pkg-tenant`,
+      method: 'POST',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name RemovePkgTenant
+   * @summary 移除套餐租户
+   * @request POST:/api/admin/pkg/remove-pkg-tenant
+   * @secure
+   */
+  removePkgTenant = (data: PkgAddPkgTenantListInput, params: RequestParams = {}) =>
+    this.request<AxiosResponse, any>({
+      path: `/api/admin/pkg/remove-pkg-tenant`,
+      method: 'POST',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name Add
+   * @summary 添加
+   * @request POST:/api/admin/pkg/add
+   * @secure
+   */
+  add = (data: PkgAddInput, params: RequestParams = {}) =>
+    this.request<ResultOutputInt64, any>({
+      path: `/api/admin/pkg/add`,
+      method: 'POST',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      format: 'json',
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name Update
+   * @summary 修改
+   * @request PUT:/api/admin/pkg/update
+   * @secure
+   */
+  update = (data: PkgUpdateInput, params: RequestParams = {}) =>
+    this.request<AxiosResponse, any>({
+      path: `/api/admin/pkg/update`,
+      method: 'PUT',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name Delete
+   * @summary 彻底删除
+   * @request DELETE:/api/admin/pkg/delete
+   * @secure
+   */
+  delete = (
+    query?: {
+      /** @format int64 */
+      id?: number
+    },
+    params: RequestParams = {}
+  ) =>
+    this.request<AxiosResponse, any>({
+      path: `/api/admin/pkg/delete`,
+      method: 'DELETE',
+      query: query,
+      secure: true,
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name BatchDelete
+   * @summary 批量彻底删除
+   * @request PUT:/api/admin/pkg/batch-delete
+   * @secure
+   */
+  batchDelete = (data: number[], params: RequestParams = {}) =>
+    this.request<AxiosResponse, any>({
+      path: `/api/admin/pkg/batch-delete`,
+      method: 'PUT',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name SoftDelete
+   * @summary 删除
+   * @request DELETE:/api/admin/pkg/soft-delete
+   * @secure
+   */
+  softDelete = (
+    query?: {
+      /** @format int64 */
+      id?: number
+    },
+    params: RequestParams = {}
+  ) =>
+    this.request<AxiosResponse, any>({
+      path: `/api/admin/pkg/soft-delete`,
+      method: 'DELETE',
+      query: query,
+      secure: true,
+      ...params,
+    })
+  /**
+   * No description
+   *
+   * @tags pkg
+   * @name BatchSoftDelete
+   * @summary 批量删除
+   * @request PUT:/api/admin/pkg/batch-soft-delete
+   * @secure
+   */
+  batchSoftDelete = (data: number[], params: RequestParams = {}) =>
+    this.request<AxiosResponse, any>({
+      path: `/api/admin/pkg/batch-soft-delete`,
+      method: 'PUT',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      ...params,
+    })
+}

+ 3 - 3
src/api/admin/Role.ts

@@ -14,7 +14,7 @@ import {
   PageInputRoleGetPageDto,
   ResultOutputInt64,
   ResultOutputListRoleGetListOutput,
-  ResultOutputListUserGetRoleUserListOutput,
+  ResultOutputListRoleGetRoleUserListOutput,
   ResultOutputPageOutputRoleGetPageOutput,
   ResultOutputRoleGetOutput,
   RoleAddInput,
@@ -113,7 +113,7 @@ export class RoleApi<SecurityDataType = unknown> extends HttpClient<SecurityData
     },
     params: RequestParams = {}
   ) =>
-    this.request<ResultOutputListUserGetRoleUserListOutput, any>({
+    this.request<ResultOutputListRoleGetRoleUserListOutput, any>({
       path: `/api/admin/role/get-role-user-list`,
       method: 'GET',
       query: query,
@@ -126,7 +126,7 @@ export class RoleApi<SecurityDataType = unknown> extends HttpClient<SecurityData
    *
    * @tags role
    * @name AddRoleUser
-   * @summary 新增角色用户
+   * @summary 添加角色用户
    * @request POST:/api/admin/role/add-role-user
    * @secure
    */

+ 246 - 16
src/api/admin/data-contracts.ts

@@ -375,6 +375,8 @@ export interface DictAddInput {
 export interface DictGetListDto {
   /** 字典类型编码 */
   dictTypeCode?: string | null
+  /** 字典类型名称 */
+  dictTypeName?: string | null
   /**
    * 主键Id
    * @format int64
@@ -1333,6 +1335,22 @@ export interface PageInputLogGetPageDto {
   filter?: LogGetPageDto
 }
 
+/** 分页信息输入 */
+export interface PageInputPkgGetPageDto {
+  /**
+   * 当前页标
+   * @format int32
+   */
+  currentPage?: number
+  /**
+   * 每页大小
+   * @format int32
+   */
+  pageSize?: number
+  dynamicFilter?: DynamicFilterInfo
+  filter?: PkgGetPageDto
+}
+
 /** 分页信息输入 */
 export interface PageInputRoleGetPageDto {
   /**
@@ -1480,6 +1498,17 @@ export interface PageOutputOprationLogListOutput {
   list?: OprationLogListOutput[] | null
 }
 
+/** 分页信息输出 */
+export interface PageOutputPkgGetPageOutput {
+  /**
+   * 数据总数
+   * @format int64
+   */
+  total?: number
+  /** 数据 */
+  list?: PkgGetPageOutput[] | null
+}
+
 /** 分页信息输出 */
 export interface PageOutputRoleGetPageOutput {
   /**
@@ -2132,6 +2161,160 @@ export interface PermissionUpdateMenuInput {
   id: number
 }
 
+/** 添加 */
+export interface PkgAddInput {
+  /**
+   * 父级Id
+   * @format int64
+   */
+  parentId?: number
+  /** 名称 */
+  name?: string | null
+  /** 编码 */
+  code?: string | null
+  /** 说明 */
+  description?: string | null
+  /**
+   * 排序
+   * @format int32
+   */
+  sort?: number
+  /** 启用 */
+  enabled?: boolean
+}
+
+/** 添加套餐租户列表 */
+export interface PkgAddPkgTenantListInput {
+  /**
+   * 套餐
+   * @format int64
+   */
+  pkgId: number
+  /** 租户列表 */
+  tenantIds?: number[] | null
+}
+
+export interface PkgGetListOutput {
+  /**
+   * 主键
+   * @format int64
+   */
+  id?: number
+  /**
+   * 父级Id
+   * @format int64
+   */
+  parentId?: number
+  /** 名称 */
+  name?: string | null
+  /** 编码 */
+  code?: string | null
+  /**
+   * 排序
+   * @format int32
+   */
+  sort?: number
+  /** 描述 */
+  description?: string | null
+}
+
+export interface PkgGetOutput {
+  /**
+   * 父级Id
+   * @format int64
+   */
+  parentId?: number
+  /** 名称 */
+  name?: string | null
+  /** 编码 */
+  code?: string | null
+  /** 说明 */
+  description?: string | null
+  /**
+   * 排序
+   * @format int32
+   */
+  sort?: number
+  /** 启用 */
+  enabled?: boolean
+  /**
+   * 套餐Id
+   * @format int64
+   */
+  id: number
+}
+
+export interface PkgGetPageDto {
+  /** 名称 */
+  name?: string | null
+}
+
+export interface PkgGetPageOutput {
+  /**
+   * 主键
+   * @format int64
+   */
+  id?: number
+  /** 名称 */
+  name?: string | null
+  /** 编码 */
+  code?: string | null
+  /** 说明 */
+  description?: string | null
+  /** 启用 */
+  enabled?: boolean
+  /**
+   * 创建时间
+   * @format date-time
+   */
+  createdTime?: string | null
+}
+
+export interface PkgGetPkgTenantListOutput {
+  /**
+   * 主键Id
+   * @format int64
+   */
+  id?: number
+  /** 租户名 */
+  name?: string | null
+  /** 租户编码 */
+  code?: string | null
+}
+
+export interface PkgSetPkgPermissionsInput {
+  /** @format int64 */
+  pkgId: number
+  permissionIds: number[]
+}
+
+/** 修改 */
+export interface PkgUpdateInput {
+  /**
+   * 父级Id
+   * @format int64
+   */
+  parentId?: number
+  /** 名称 */
+  name?: string | null
+  /** 编码 */
+  code?: string | null
+  /** 说明 */
+  description?: string | null
+  /**
+   * 排序
+   * @format int32
+   */
+  sort?: number
+  /** 启用 */
+  enabled?: boolean
+  /**
+   * 套餐Id
+   * @format int64
+   */
+  id: number
+}
+
 /** 结果输出 */
 export interface ResultOutputApiGetOutput {
   /** 是否成功标记 */
@@ -2425,6 +2608,30 @@ export interface ResultOutputListPermissionListOutput {
   data?: PermissionListOutput[] | null
 }
 
+/** 结果输出 */
+export interface ResultOutputListPkgGetListOutput {
+  /** 是否成功标记 */
+  success?: boolean
+  /** 编码 */
+  code?: string | null
+  /** 消息 */
+  msg?: string | null
+  /** 数据 */
+  data?: PkgGetListOutput[] | null
+}
+
+/** 结果输出 */
+export interface ResultOutputListPkgGetPkgTenantListOutput {
+  /** 是否成功标记 */
+  success?: boolean
+  /** 编码 */
+  code?: string | null
+  /** 消息 */
+  msg?: string | null
+  /** 数据 */
+  data?: PkgGetPkgTenantListOutput[] | null
+}
+
 /** 结果输出 */
 export interface ResultOutputListRoleGetListOutput {
   /** 是否成功标记 */
@@ -2438,7 +2645,7 @@ export interface ResultOutputListRoleGetListOutput {
 }
 
 /** 结果输出 */
-export interface ResultOutputListString {
+export interface ResultOutputListRoleGetRoleUserListOutput {
   /** 是否成功标记 */
   success?: boolean
   /** 编码 */
@@ -2446,11 +2653,11 @@ export interface ResultOutputListString {
   /** 消息 */
   msg?: string | null
   /** 数据 */
-  data?: string[] | null
+  data?: RoleGetRoleUserListOutput[] | null
 }
 
 /** 结果输出 */
-export interface ResultOutputListUserGetRoleUserListOutput {
+export interface ResultOutputListString {
   /** 是否成功标记 */
   success?: boolean
   /** 编码 */
@@ -2458,7 +2665,7 @@ export interface ResultOutputListUserGetRoleUserListOutput {
   /** 消息 */
   msg?: string | null
   /** 数据 */
-  data?: UserGetRoleUserListOutput[] | null
+  data?: string[] | null
 }
 
 /** 结果输出 */
@@ -2568,6 +2775,18 @@ export interface ResultOutputPageOutputOprationLogListOutput {
   data?: PageOutputOprationLogListOutput
 }
 
+/** 结果输出 */
+export interface ResultOutputPageOutputPkgGetPageOutput {
+  /** 是否成功标记 */
+  success?: boolean
+  /** 编码 */
+  code?: string | null
+  /** 消息 */
+  msg?: string | null
+  /** 分页信息输出 */
+  data?: PageOutputPkgGetPageOutput
+}
+
 /** 结果输出 */
 export interface ResultOutputPageOutputRoleGetPageOutput {
   /** 是否成功标记 */
@@ -2672,6 +2891,17 @@ export interface ResultOutputPermissionGetMenuOutput {
   data?: PermissionGetMenuOutput
 }
 
+/** 结果输出 */
+export interface ResultOutputPkgGetOutput {
+  /** 是否成功标记 */
+  success?: boolean
+  /** 编码 */
+  code?: string | null
+  /** 消息 */
+  msg?: string | null
+  data?: PkgGetOutput
+}
+
 /** 结果输出 */
 export interface ResultOutputRoleGetOutput {
   /** 是否成功标记 */
@@ -2960,6 +3190,18 @@ export interface RoleGetPageOutput {
   createdTime?: string | null
 }
 
+export interface RoleGetRoleUserListOutput {
+  /**
+   * 主键Id
+   * @format int64
+   */
+  id?: number
+  /** 姓名 */
+  name?: string | null
+  /** 手机号 */
+  mobile?: string | null
+}
+
 /** 设置数据范围 */
 export interface RoleSetDataScopeInput {
   /**
@@ -3753,18 +3995,6 @@ export interface UserGetRoleDto {
   name?: string | null
 }
 
-export interface UserGetRoleUserListOutput {
-  /**
-   * 主键Id
-   * @format int64
-   */
-  id?: number
-  /** 姓名 */
-  name?: string | null
-  /** 手机号 */
-  mobile?: string | null
-}
-
 export interface UserPermissionsOutput {
   httpMethods?: string | null
   path?: string | null

+ 1 - 0
src/types/mitt.d.ts

@@ -41,6 +41,7 @@ declare type MittType<T = any> = {
   refreshPermission?: T
   refreshRole?: T
   refreshTenant?: T
+  refreshPkg?: T
   refreshUser?: T
   refreshView?: T
   refreshFile?: T

+ 8 - 3
src/views/admin/permission/components/permission-dot-form.vue

@@ -34,7 +34,6 @@
                 :data="state.apiTreeData"
                 node-key="id"
                 :props="{ label: 'path' }"
-                default-expand-all
                 render-after-expand
                 fit-input-width
                 clearable
@@ -44,6 +43,7 @@
                 collapse-tags-tooltip
                 :filter-node-method="onApiFilterNode"
                 class="w100"
+                :default-expanded-keys="state.expandRowKeys"
                 @current-change="onApiCurrentChange"
               >
                 <template #default="{ data }">
@@ -99,9 +99,9 @@ import { reactive, toRefs, getCurrentInstance, ref, PropType } from 'vue'
 import { PermissionListOutput, PermissionUpdateDotInput, ApiListOutput } from '/@/api/admin/data-contracts'
 import { PermissionApi } from '/@/api/admin/Permission'
 import { ApiApi } from '/@/api/admin/Api'
-import { listToTree } from '/@/utils/tree'
+import { listToTree, treeToList } from '/@/utils/tree'
 import eventBus from '/@/utils/mitt'
-import { trimStart, replace } from 'lodash-es'
+import { trimStart, replace, cloneDeep } from 'lodash-es'
 
 defineProps({
   title: {
@@ -122,6 +122,7 @@ const state = reactive({
   sureLoading: false,
   form: { enabled: true } as PermissionUpdateDotInput,
   apiTreeData: [] as ApiListOutput[],
+  expandRowKeys: [] as number[],
 })
 
 const { form } = toRefs(state)
@@ -141,6 +142,10 @@ const open = async (row: any = {}) => {
 
   await getApis()
 
+  state.expandRowKeys = treeToList(cloneDeep(state.apiTreeData))
+    .filter((a: ApiListOutput) => a.parentId === 0)
+    .map((a: ApiListOutput) => a.id) as number[]
+
   if (row.id > 0) {
     const res = await new PermissionApi().getDot({ id: row.id }).catch(() => {
       proxy.$modal.closeLoading()

+ 125 - 0
src/views/admin/pkg/components/pkg-form.vue

@@ -0,0 +1,125 @@
+<template>
+  <div>
+    <el-dialog
+      v-model="state.showDialog"
+      destroy-on-close
+      :title="title"
+      draggable
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      width="600px"
+    >
+      <el-form :model="form" ref="formRef" size="default" label-width="80px">
+        <el-row :gutter="35">
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+            <el-form-item label="名称" prop="name" :rules="[{ required: true, message: '请输入名称', trigger: ['blur', 'change'] }]">
+              <el-input v-model="form.name" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+            <el-form-item label="编码" prop="code">
+              <el-input v-model="form.code" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+            <el-form-item label="排序">
+              <el-input-number v-model="form.sort" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+            <el-form-item label="启用">
+              <el-switch v-model="form.enabled" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+            <el-form-item label="说明">
+              <el-input v-model="form.description" clearable type="textarea" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="onCancel" size="default">取 消</el-button>
+          <el-button type="primary" @click="onSure" size="default" :loading="state.sureLoading">确 定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup name="admin/pkg/form">
+import { reactive, toRefs, ref } from 'vue'
+import { PkgUpdateInput } from '/@/api/admin/data-contracts'
+import { PkgApi } from '/@/api/admin/Pkg'
+import { cloneDeep } from 'lodash-es'
+import eventBus from '/@/utils/mitt'
+
+defineProps({
+  title: {
+    type: String,
+    default: '',
+  },
+})
+
+const formRef = ref()
+const state = reactive({
+  showDialog: false,
+  sureLoading: false,
+  form: {} as PkgUpdateInput,
+})
+
+const { form } = toRefs(state)
+
+// 打开对话框
+const open = async (row: PkgUpdateInput = { id: 0 }) => {
+  let formData = cloneDeep(row) as PkgUpdateInput
+  if (row.id > 0) {
+    const res = await new PkgApi().get({ id: row.id }, { loading: true })
+
+    if (res?.success) {
+      formData = res.data as PkgUpdateInput
+      formData.parentId = formData.parentId && formData.parentId > 0 ? formData.parentId : undefined
+    }
+  }
+
+  state.form = formData
+  state.showDialog = true
+}
+
+// 取消
+const onCancel = () => {
+  state.showDialog = false
+}
+
+// 确定
+const onSure = () => {
+  formRef.value.validate(async (valid: boolean) => {
+    if (!valid) return
+
+    state.sureLoading = true
+    let res = {} as any
+    state.form.parentId = state.form.parentId && state.form.parentId > 0 ? state.form.parentId : undefined
+    if (state.form.id != undefined && state.form.id > 0) {
+      res = await new PkgApi().update(state.form, { showSuccessMessage: true }).catch(() => {
+        state.sureLoading = false
+      })
+    } else {
+      res = await new PkgApi().add(state.form, { showSuccessMessage: true }).catch(() => {
+        state.sureLoading = false
+      })
+    }
+
+    state.sureLoading = false
+
+    if (res?.success) {
+      eventBus.emit('refreshPkg')
+      state.showDialog = false
+    }
+  })
+}
+
+defineExpose({
+  open,
+})
+</script>

+ 158 - 0
src/views/admin/pkg/components/set-pkg-menu.vue

@@ -0,0 +1,158 @@
+<template>
+  <el-dialog
+    v-model="state.showDialog"
+    destroy-on-close
+    :title="innerTitle"
+    append-to-body
+    draggable
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    width="780px"
+  >
+    <div>
+      <el-tree
+        ref="permissionTreeRef"
+        :data="state.permissionTreeData"
+        node-key="id"
+        show-checkbox
+        highlight-current
+        default-expand-all
+        check-on-click-node
+        :expand-on-click-node="false"
+        :props="{ class: customNodeClass }"
+        :default-checked-keys="state.checkedKeys"
+      />
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="onCancel" size="default">取 消</el-button>
+        <el-button type="primary" @click="onSure" size="default" :loading="state.sureLoading">确 定</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup name="admin/pkg/components/set-pkg-menu">
+import { ref, reactive, getCurrentInstance, computed } from 'vue'
+import { PkgGetListOutput, PkgSetPkgPermissionsInput } from '/@/api/admin/data-contracts'
+import { PkgApi } from '/@/api/admin/Pkg'
+import { PermissionApi } from '/@/api/admin/Permission'
+import { ElTree } from 'element-plus'
+import { listToTree } from '/@/utils/tree'
+import { cloneDeep } from 'lodash-es'
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: '',
+  },
+})
+
+const innerTitle = computed(() => {
+  return props.title ? props.title : state.pkgName ? `设置【${state.pkgName}】菜单权限` : '设置菜单权限'
+})
+
+const state = reactive({
+  showDialog: false,
+  loading: false,
+  sureLoading: false,
+  permissionTreeData: [],
+  pkgId: 0 as number | undefined,
+  pkgName: '' as string | undefined | null,
+  checkedKeys: [],
+})
+
+const { proxy } = getCurrentInstance() as any
+const permissionTreeRef = ref<InstanceType<typeof ElTree>>()
+
+const getPkgPermissionList = async () => {
+  const res = await new PkgApi().getPkgPermissionList({ pkgId: state.pkgId })
+  state.checkedKeys = res?.success ? (res.data as never[]) : []
+}
+
+// 打开对话框
+const open = async (pkg: PkgGetListOutput) => {
+  state.pkgId = pkg.id
+  state.pkgName = pkg.name
+  proxy.$modal.loading()
+  await onQuery()
+  await getPkgPermissionList()
+  proxy.$modal.closeLoading()
+  state.showDialog = true
+}
+
+// 关闭对话框
+const close = () => {
+  state.showDialog = false
+}
+
+const onQuery = async () => {
+  state.loading = true
+
+  const res = await new PermissionApi().getPermissionList().catch(() => {
+    state.loading = false
+  })
+  if (res && res.data && res.data.length > 0) {
+    state.permissionTreeData = listToTree(cloneDeep(res.data))
+  } else {
+    state.permissionTreeData = []
+  }
+
+  state.loading = false
+}
+
+const customNodeClass = (data: any) => {
+  return data.row ? 'is-penultimate' : ''
+}
+
+// 取消
+const onCancel = () => {
+  state.showDialog = false
+}
+
+// 确定
+const onSure = async () => {
+  state.sureLoading = true
+  const permissionIds = permissionTreeRef.value?.getCheckedKeys(true)
+  const input = { pkgId: state.pkgId, permissionIds: permissionIds } as PkgSetPkgPermissionsInput
+  const res = await new PkgApi().setPkgPermissions(input, { showSuccessMessage: true }).catch(() => {
+    state.sureLoading = false
+  })
+  state.sureLoading = false
+
+  if (res?.success) {
+    state.showDialog = false
+  }
+}
+
+defineExpose({
+  open,
+  close,
+})
+</script>
+
+<style scoped lang="scss">
+:deep(.el-dialog__body) {
+  padding: 5px 10px;
+}
+:deep(.is-penultimate) {
+  .el-tree-node__children {
+    padding-left: 65px;
+    white-space: pre-wrap;
+    line-height: 100%;
+
+    .el-tree-node {
+      display: inline-block;
+    }
+
+    .el-tree-node__content {
+      padding-left: 12px !important;
+      padding-right: 12px;
+
+      .el-tree-node__expand-icon.is-leaf {
+        display: none;
+      }
+    }
+  }
+}
+</style>

+ 280 - 0
src/views/admin/pkg/index.vue

@@ -0,0 +1,280 @@
+<template>
+  <my-layout>
+    <pane size="50" min-size="30" max-size="70">
+      <div class="my-flex-column w100 h100">
+        <el-card class="mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
+          <el-form :inline="true" @submit.stop.prevent>
+            <el-form-item label="套餐名称">
+              <el-input v-model="state.filter.pkgName" placeholder="套餐名称" @keyup.enter="onQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="ele-Search" @click="onQuery"> 查询 </el-button>
+              <el-button v-auth="'api:admin:pkg:add'" type="primary" icon="ele-Plus" @click="onAdd"> 新增 </el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+
+        <el-card class="my-fill mt8" shadow="never">
+          <el-table
+            ref="pkgTableRef"
+            v-loading="state.loading"
+            :data="state.pkgTreeData"
+            row-key="id"
+            default-expand-all
+            :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+            highlight-current-row
+            style="width: 100%"
+            @current-change="onCurrentChange"
+          >
+            <el-table-column prop="name" label="套餐名称" min-width="120" show-overflow-tooltip />
+            <el-table-column prop="sort" label="排序" width="80" align="center" show-overflow-tooltip />
+            <el-table-column label="操作" width="100" fixed="right" header-align="center" align="center">
+              <template #default="{ row }">
+                <my-dropdown-more
+                  v-auths="['api:admin:pkg:set-pkg-permissions', 'api:admin:pkg:update', 'api:admin:pkg:delete']"
+                  style="margin-left: 0px"
+                >
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item v-if="auth('api:admin:pkg:set-pkg-permissions')" @click="onSetPkgMenu(row)">菜单权限</el-dropdown-item>
+                      <el-dropdown-item v-if="auth('api:admin:pkg:update')" @click="onEdit(row)">编辑套餐</el-dropdown-item>
+                      <el-dropdown-item v-if="auth('api:admin:pkg:delete')" @click="onDelete(row)">删除套餐</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </my-dropdown-more>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+      </div>
+    </pane>
+    <pane>
+      <div class="my-flex-column w100 h100">
+        <el-card class="mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
+          <el-form :inline="true" @submit.stop.prevent>
+            <el-form-item label="租户名">
+              <el-input v-model="state.filter.name" placeholder="租户名" @keyup.enter="onGetPkgTenantList" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="ele-Search" @click="onGetPkgTenantList"> 查询 </el-button>
+              <el-button v-auth="'api:admin:pkg:add-pkg-tenant'" type="primary" icon="ele-Plus" @click="onAddTenant"> 添加租户 </el-button>
+              <el-button v-auth="'api:admin:pkg:remove-pkg-tenant'" type="danger" icon="ele-Delete" @click="onRemoveTenant"> 移除租户 </el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+
+        <el-card class="my-fill mt8" shadow="never">
+          <el-table
+            ref="tenantTableRef"
+            v-loading="state.tenantListLoading"
+            :data="state.tenantListData"
+            row-key="id"
+            style="width: 100%"
+            @row-click="onTenantRowClick"
+          >
+            <el-table-column type="selection" width="55" />
+            <el-table-column prop="name" label="租户名" min-width="120" show-overflow-tooltip />
+            <el-table-column prop="code" label="租户编码" min-width="120" show-overflow-tooltip />
+          </el-table>
+        </el-card>
+      </div>
+    </pane>
+
+    <pkg-form ref="pkgFormRef" :title="state.pkgFormTitle" :pkg-tree-data="state.pkgFormTreeData"></pkg-form>
+    <tenant-select
+      ref="tenantSelectRef"
+      :title="`添加【${state.pkgName}】租户`"
+      multiple
+      :sure-loading="state.sureLoading"
+      @sure="onSureTenant"
+    ></tenant-select>
+    <set-pkg-menu ref="setPkgMenuRef"></set-pkg-menu>
+  </my-layout>
+</template>
+
+<script lang="ts" setup name="admin/pkg">
+import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, nextTick, defineAsyncComponent } from 'vue'
+import { PkgGetListOutput, PkgGetPkgTenantListOutput, PkgGetPageOutput, PkgAddPkgTenantListInput } from '/@/api/admin/data-contracts'
+import { PkgApi } from '/@/api/admin/Pkg'
+import { listToTree, filterTree } from '/@/utils/tree'
+import { ElTable } from 'element-plus'
+import { cloneDeep } from 'lodash-es'
+import eventBus from '/@/utils/mitt'
+import { auth } from '/@/utils/authFunction'
+import { Pane } from 'splitpanes'
+
+// 引入组件
+const PkgForm = defineAsyncComponent(() => import('./components/pkg-form.vue'))
+const SetPkgMenu = defineAsyncComponent(() => import('./components/set-pkg-menu.vue'))
+const TenantSelect = defineAsyncComponent(() => import('/@/views/admin/tenant/components/tenant-select.vue'))
+const MyDropdownMore = defineAsyncComponent(() => import('/@/components/my-dropdown-more/index.vue'))
+const MyLayout = defineAsyncComponent(() => import('/@/components/my-layout/index.vue'))
+
+const { proxy } = getCurrentInstance() as any
+
+const pkgTableRef = ref()
+const pkgFormRef = ref()
+const tenantTableRef = ref<InstanceType<typeof ElTable>>()
+const tenantSelectRef = ref()
+const setPkgMenuRef = ref()
+
+const state = reactive({
+  loading: false,
+  tenantListLoading: false,
+  sureLoading: false,
+  pkgFormTitle: '',
+  filter: {
+    name: '',
+    pkgName: '',
+  },
+  pkgTreeData: [] as any,
+  pkgFormTreeData: [] as any,
+  tenantListData: [] as PkgGetPkgTenantListOutput[],
+  pkgId: undefined as number | undefined,
+  pkgName: '' as string | null | undefined,
+})
+
+onMounted(() => {
+  onQuery()
+  eventBus.off('refreshPkg')
+  eventBus.on('refreshPkg', async () => {
+    onQuery()
+  })
+})
+
+onBeforeMount(() => {
+  eventBus.off('refreshPkg')
+})
+
+const onQuery = async () => {
+  state.loading = true
+  const res = await new PkgApi().getList().catch(() => {
+    state.loading = false
+  })
+  if (res && res.data && res.data.length > 0) {
+    state.pkgTreeData = filterTree(listToTree(cloneDeep(res.data)), state.filter.pkgName)
+    state.pkgFormTreeData = listToTree(cloneDeep(res.data).filter((a) => a.parentId === 0))
+    if (state.pkgTreeData.length > 0) {
+      nextTick(() => {
+        pkgTableRef.value!.setCurrentRow(state.pkgTreeData[0])
+      })
+    }
+  } else {
+    state.pkgTreeData = []
+    state.pkgFormTreeData = []
+  }
+
+  state.loading = false
+}
+
+const onAdd = (row: PkgGetListOutput | undefined = undefined) => {
+  state.pkgFormTitle = '新增套餐'
+  pkgFormRef.value.open({ parentId: row?.id })
+}
+
+const onEdit = (row: PkgGetListOutput) => {
+  state.pkgFormTitle = '编辑套餐'
+  pkgFormRef.value.open(row)
+}
+
+const onDelete = (row: PkgGetListOutput) => {
+  proxy.$modal
+    .confirmDelete(`确定要删除套餐【${row.name}】?`)
+    .then(async () => {
+      await new PkgApi().delete({ id: row.id }, { loading: true })
+      onQuery()
+    })
+    .catch(() => {})
+}
+
+const onGetPkgTenantList = async () => {
+  state.tenantListLoading = true
+  const res = await new PkgApi().getPkgTenantList({ PkgId: state.pkgId, TenantName: state.filter.name }).catch(() => {
+    state.tenantListLoading = false
+  })
+  state.tenantListLoading = false
+  if (res?.success) {
+    if (res.data && res.data.length > 0) {
+      state.tenantListData = res.data
+    } else {
+      state.tenantListData = []
+    }
+  }
+}
+
+const onCurrentChange = (currentRow: PkgGetListOutput) => {
+  if (!currentRow) {
+    return
+  }
+
+  state.pkgId = currentRow.id
+  state.pkgName = currentRow.name
+  onGetPkgTenantList()
+}
+
+const onTenantRowClick = (row: PkgGetPkgTenantListOutput) => {
+  // TODO: improvement typing when refactor table
+  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+  // @ts-expect-error
+  tenantTableRef.value!.toggleRowSelection(row, undefined)
+}
+
+const onAddTenant = () => {
+  if (!((state.pkgId as number) > 0)) {
+    proxy.$modal.msgWarning('请选择套餐')
+    return
+  }
+  tenantSelectRef.value.open({ pkgId: state.pkgId })
+}
+
+const onRemoveTenant = () => {
+  if (!((state.pkgId as number) > 0)) {
+    proxy.$modal.msgWarning('请选择套餐')
+    return
+  }
+
+  const selectionRows = tenantTableRef.value!.getSelectionRows() as PkgGetPageOutput[]
+
+  if (!((selectionRows.length as number) > 0)) {
+    proxy.$modal.msgWarning('请选择租户')
+    return
+  }
+
+  proxy.$modal
+    .confirm(`确定要移除吗?`)
+    .then(async () => {
+      const tenantIds = selectionRows?.map((a) => a.id)
+      const input = { pkgId: state.pkgId, tenantIds } as PkgAddPkgTenantListInput
+      await new PkgApi().removePkgTenant(input, { loading: true })
+      onGetPkgTenantList()
+    })
+    .catch(() => {})
+}
+
+const onSureTenant = async (tenants: PkgGetPageOutput[]) => {
+  if (!(tenants?.length > 0)) {
+    tenantSelectRef.value.close()
+    return
+  }
+
+  state.sureLoading = true
+  const tenantIds = tenants?.map((a) => a.id)
+  const input = { pkgId: state.pkgId, tenantIds } as PkgAddPkgTenantListInput
+  await new PkgApi().addPkgTenant(input, { showSuccessMessage: true }).catch(() => {
+    state.sureLoading = false
+  })
+  state.sureLoading = false
+  tenantSelectRef.value.close()
+  onGetPkgTenantList()
+}
+
+const onSetPkgMenu = (pkg: PkgGetListOutput) => {
+  if (!((pkg?.id as number) > 0)) {
+    proxy.$modal.msgWarning('请选择套餐')
+    return
+  }
+  setPkgMenuRef.value.open(pkg)
+}
+</script>
+
+<style scoped lang="scss"></style>

+ 12 - 4
src/views/admin/role/index.vue

@@ -105,7 +105,13 @@
     </pane>
 
     <role-form ref="roleFormRef" :title="state.roleFormTitle" :role-tree-data="state.roleFormTreeData"></role-form>
-    <user-select ref="userSelectRef" title="添加员工" multiple :sure-loading="state.sureLoading" @sure="onSureUser"></user-select>
+    <user-select
+      ref="userSelectRef"
+      :title="`添加【${state.roleName}】员工`"
+      multiple
+      :sure-loading="state.sureLoading"
+      @sure="onSureUser"
+    ></user-select>
     <set-role-menu ref="setRoleMenuRef"></set-role-menu>
     <set-role-data-scope ref="setRoleDataScopeRef"></set-role-data-scope>
   </my-layout>
@@ -113,7 +119,7 @@
 
 <script lang="ts" setup name="admin/role">
 import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, nextTick, defineAsyncComponent } from 'vue'
-import { RoleGetListOutput, UserGetRoleUserListOutput, UserGetPageOutput, RoleAddRoleUserListInput, RoleType } from '/@/api/admin/data-contracts'
+import { RoleGetListOutput, RoleGetRoleUserListOutput, UserGetPageOutput, RoleAddRoleUserListInput, RoleType } from '/@/api/admin/data-contracts'
 import { RoleApi } from '/@/api/admin/Role'
 import { listToTree, filterTree } from '/@/utils/tree'
 import { ElTable } from 'element-plus'
@@ -150,8 +156,9 @@ const state = reactive({
   },
   roleTreeData: [] as any,
   roleFormTreeData: [] as any,
-  userListData: [] as UserGetRoleUserListOutput[],
+  userListData: [] as RoleGetRoleUserListOutput[],
   roleId: undefined as number | undefined,
+  roleName: '' as string | null | undefined,
 })
 
 onMounted(() => {
@@ -254,11 +261,12 @@ const onCurrentChange = (currentRow: RoleGetListOutput, oldCurrentRow: RoleGetLi
 
   if ((currentRow?.parentId as number) !== 0 && (oldCurrentRow?.parentId as number) !== 0 && (currentRow?.id as number) > 0) {
     state.roleId = currentRow.id
+    state.roleName = currentRow.name
     onGetRoleUserList()
   }
 }
 
-const onUserRowClick = (row: UserGetRoleUserListOutput) => {
+const onUserRowClick = (row: RoleGetRoleUserListOutput) => {
   // TODO: improvement typing when refactor table
   // eslint-disable-next-line @typescript-eslint/ban-ts-comment
   // @ts-expect-error

+ 172 - 0
src/views/admin/tenant/components/tenant-select.vue

@@ -0,0 +1,172 @@
+<template>
+  <el-dialog
+    v-model="state.showDialog"
+    destroy-on-close
+    :title="title"
+    append-to-body
+    draggable
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    width="780px"
+  >
+    <div style="padding: 0px 0px 8px 8px; background-color: var(--ba-bg-color)">
+      <el-card shadow="never" :body-style="{ paddingBottom: '0' }" style="margin-top: 8px">
+        <el-form :model="state.filterModel" :inline="true" @submit.stop.prevent>
+          <el-form-item label="租户名" prop="name">
+            <el-input v-model="state.filterModel.name" placeholder="租户名" @keyup.enter="onQuery" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="ele-Search" @click="onQuery"> 查询 </el-button>
+          </el-form-item>
+        </el-form>
+      </el-card>
+
+      <el-card shadow="never" style="margin-top: 8px">
+        <el-table
+          ref="tenantTableRef"
+          :data="state.tenantListData"
+          style="width: 100%"
+          v-loading="state.loading"
+          row-key="id"
+          default-expand-all
+          :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+          :highlight-current-row="!multiple"
+          @row-click="onRowClick"
+          @row-dblclick="onRowDbClick"
+        >
+          <el-table-column v-if="multiple" type="selection" width="55" />
+          <el-table-column prop="name" label="租户名" min-width="80" show-overflow-tooltip />
+          <el-table-column prop="code" label="租户编码" min-width="120" show-overflow-tooltip />
+          <!-- <el-table-column prop="email" label="邮箱" min-width="120" show-overflow-tooltip /> -->
+        </el-table>
+        <div class="my-flex my-flex-end" style="margin-top: 20px">
+          <el-pagination
+            v-model:currentPage="state.pageInput.currentPage"
+            v-model:page-size="state.pageInput.pageSize"
+            :total="state.total"
+            :page-sizes="[10, 20, 50, 100]"
+            small
+            background
+            @size-change="onSizeChange"
+            @current-change="onCurrentChange"
+            layout="total, sizes, prev, pager, next"
+          />
+        </div>
+      </el-card>
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="onCancel" size="default">取 消</el-button>
+        <el-button type="primary" @click="onSure" size="default" :loading="sureLoading">确 定</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup name="admin/tenant/components/tenant-select">
+import { ref, reactive } from 'vue'
+import { ElTable } from 'element-plus'
+import { TenantListOutput, PageInputTenantGetPageDto } from '/@/api/admin/data-contracts'
+import { TenantApi } from '/@/api/admin/Tenant'
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: '',
+  },
+  multiple: {
+    type: Boolean,
+    default: false,
+  },
+  sureLoading: {
+    type: Boolean,
+    default: false,
+  },
+})
+
+const emits = defineEmits(['sure'])
+
+const tenantTableRef = ref<InstanceType<typeof ElTable>>()
+
+const state = reactive({
+  showDialog: false,
+  loading: false,
+  filterModel: {
+    name: '',
+  },
+  total: 0,
+  pageInput: {
+    currentPage: 1,
+    pageSize: 20,
+  } as PageInputTenantGetPageDto,
+  tenantListData: [] as Array<TenantListOutput>,
+})
+
+// 打开对话框
+const open = () => {
+  state.showDialog = true
+
+  onQuery()
+}
+
+// 关闭对话框
+const close = () => {
+  state.showDialog = false
+}
+
+const onQuery = async () => {
+  state.loading = true
+  const res = await new TenantApi().getPage(state.pageInput).catch(() => {
+    state.loading = false
+  })
+
+  state.tenantListData = res?.data?.list ?? []
+  state.total = res?.data?.total ?? 0
+  state.loading = false
+}
+
+const onSizeChange = (val: number) => {
+  state.pageInput.pageSize = val
+  onQuery()
+}
+
+const onCurrentChange = (val: number) => {
+  state.pageInput.currentPage = val
+  onQuery()
+}
+
+const onRowClick = (row: TenantListOutput) => {
+  // TODO: improvement typing when refactor table
+  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+  // @ts-expect-error
+  tenantTableRef.value!.toggleRowSelection(row, props.multiple ? undefined : true)
+}
+
+const onRowDbClick = () => {
+  if (!props.multiple) {
+    onSure()
+  }
+}
+
+// 取消
+const onCancel = () => {
+  state.showDialog = false
+}
+
+// 确定
+const onSure = () => {
+  const selectionRows = tenantTableRef.value!.getSelectionRows() as TenantListOutput[]
+  emits('sure', props.multiple ? selectionRows : selectionRows.length > 0 ? selectionRows[0] : null)
+}
+
+defineExpose({
+  open,
+  close,
+})
+</script>
+
+<style scoped lang="scss">
+:deep(.el-dialog__body) {
+  padding: 5px 10px;
+}
+</style>