Commit f09a1a4b8eeb1fd1a5266311b2ae0dabf58d4223
1 parent
4a692ed2
refactor: operatelog
Showing
8 changed files
with
183 additions
and
224 deletions
yudao-ui-admin-vue3/src/api/system/operatelog/index.ts
| 1 | import request from '@/config/axios' | 1 | import request from '@/config/axios' |
| 2 | 2 | ||
| 3 | +export type OperateLogVO = { | ||
| 4 | + id: number | ||
| 5 | + userNickname: string | ||
| 6 | + traceId: string | ||
| 7 | + userId: number | ||
| 8 | + module: string | ||
| 9 | + name: string | ||
| 10 | + type: number | ||
| 11 | + content: string | ||
| 12 | + exts: object | ||
| 13 | + requestMethod: string | ||
| 14 | + requestUrl: string | ||
| 15 | + userIp: string | ||
| 16 | + userAgent: string | ||
| 17 | + javaMethod: string | ||
| 18 | + javaMethodArgs: string | ||
| 19 | + startTime: string | ||
| 20 | + duration: number | ||
| 21 | + resultCode: number | ||
| 22 | + resultMsg: string | ||
| 23 | + resultData: string | ||
| 24 | +} | ||
| 25 | +export interface OperateLogPageReqVO extends BasePage { | ||
| 26 | + module?: string | ||
| 27 | + userNickname?: string | ||
| 28 | + type?: number | ||
| 29 | + success?: boolean | ||
| 30 | + startTime?: string[] | ||
| 31 | +} | ||
| 32 | + | ||
| 3 | // 查询操作日志列表 | 33 | // 查询操作日志列表 |
| 4 | -export const getOperateLogPageApi = (params) => { | 34 | +export const getOperateLogPageApi = (params: OperateLogPageReqVO) => { |
| 5 | return request.get({ url: '/system/operate-log/page', params }) | 35 | return request.get({ url: '/system/operate-log/page', params }) |
| 6 | } | 36 | } |
| 7 | // 导出操作日志 | 37 | // 导出操作日志 |
| 8 | -export const exportOperateLogApi = (params) => { | 38 | +export const exportOperateLogApi = (params: OperateLogPageReqVO) => { |
| 9 | return request.download({ url: '/system/operate-log/export', params }) | 39 | return request.download({ url: '/system/operate-log/export', params }) |
| 10 | } | 40 | } |
yudao-ui-admin-vue3/src/api/system/operatelog/types.ts deleted
| 1 | -export type OperateLogVO = { | ||
| 2 | - id: number | ||
| 3 | - userNickname: string | ||
| 4 | - traceId: string | ||
| 5 | - userId: number | ||
| 6 | - module: string | ||
| 7 | - name: string | ||
| 8 | - type: number | ||
| 9 | - content: string | ||
| 10 | - exts: object | ||
| 11 | - requestMethod: string | ||
| 12 | - requestUrl: string | ||
| 13 | - userIp: string | ||
| 14 | - userAgent: string | ||
| 15 | - javaMethod: string | ||
| 16 | - javaMethodArgs: string | ||
| 17 | - startTime: string | ||
| 18 | - duration: number | ||
| 19 | - resultCode: number | ||
| 20 | - resultMsg: string | ||
| 21 | - resultData: string | ||
| 22 | -} |
yudao-ui-admin-vue3/src/hooks/web/useVxeCrudSchemas.ts
| @@ -20,6 +20,7 @@ export type VxeCrudSchema = { | @@ -20,6 +20,7 @@ export type VxeCrudSchema = { | ||
| 20 | primaryType?: VxeColumnPropTypes.Type | 20 | primaryType?: VxeColumnPropTypes.Type |
| 21 | // 是否开启操作栏插槽 | 21 | // 是否开启操作栏插槽 |
| 22 | action?: boolean | 22 | action?: boolean |
| 23 | + actionWidth?: string | ||
| 23 | columns: VxeCrudColumns[] | 24 | columns: VxeCrudColumns[] |
| 24 | } | 25 | } |
| 25 | type VxeCrudColumns = Omit<VxeTableColumn, 'children'> & { | 26 | type VxeCrudColumns = Omit<VxeTableColumn, 'children'> & { |
| @@ -204,7 +205,7 @@ const filterTableSchema = (crudSchema: VxeCrudSchema): VxeGridPropTypes.Columns | @@ -204,7 +205,7 @@ const filterTableSchema = (crudSchema: VxeCrudSchema): VxeGridPropTypes.Columns | ||
| 204 | const tableSchemaItem = { | 205 | const tableSchemaItem = { |
| 205 | title: t('table.action'), | 206 | title: t('table.action'), |
| 206 | field: 'actionbtns', | 207 | field: 'actionbtns', |
| 207 | - width: '240px', | 208 | + width: crudSchema.actionWidth ? crudSchema.actionWidth : '240px', |
| 208 | slots: { | 209 | slots: { |
| 209 | default: 'actionbtns_default' | 210 | default: 'actionbtns_default' |
| 210 | } | 211 | } |
yudao-ui-admin-vue3/src/views/system/dept/index.vue
| @@ -10,11 +10,6 @@ import { DeptVO } from '@/api/system/dept/types' | @@ -10,11 +10,6 @@ import { DeptVO } from '@/api/system/dept/types' | ||
| 10 | import { useMessage } from '@/hooks/web/useMessage' | 10 | import { useMessage } from '@/hooks/web/useMessage' |
| 11 | import { getListSimpleUsersApi } from '@/api/system/user' | 11 | import { getListSimpleUsersApi } from '@/api/system/user' |
| 12 | const message = useMessage() | 12 | const message = useMessage() |
| 13 | -interface Tree { | ||
| 14 | - id: number | ||
| 15 | - name: string | ||
| 16 | - children?: Tree[] | ||
| 17 | -} | ||
| 18 | 13 | ||
| 19 | const defaultProps = { | 14 | const defaultProps = { |
| 20 | children: 'children', | 15 | children: 'children', |
yudao-ui-admin-vue3/src/views/system/loginlog/loginLog.data.ts
| @@ -9,6 +9,7 @@ const crudSchemas = reactive<VxeCrudSchema>({ | @@ -9,6 +9,7 @@ const crudSchemas = reactive<VxeCrudSchema>({ | ||
| 9 | primaryKey: 'id', | 9 | primaryKey: 'id', |
| 10 | primaryType: 'seq', | 10 | primaryType: 'seq', |
| 11 | action: true, | 11 | action: true, |
| 12 | + actionWidth: '80px', | ||
| 12 | columns: [ | 13 | columns: [ |
| 13 | { | 14 | { |
| 14 | title: '日志类型', | 15 | title: '日志类型', |
yudao-ui-admin-vue3/src/views/system/operatelog/index.vue
| 1 | -<script setup lang="ts"> | ||
| 2 | -import dayjs from 'dayjs' | ||
| 3 | -import { useTable } from '@/hooks/web/useTable' | ||
| 4 | -import { allSchemas } from './operatelog.data' | ||
| 5 | -import { DICT_TYPE } from '@/utils/dict' | ||
| 6 | -import { useI18n } from '@/hooks/web/useI18n' | ||
| 7 | -import type { OperateLogVO } from '@/api/system/operatelog/types' | ||
| 8 | -import * as OperateLogApi from '@/api/system/operatelog' | ||
| 9 | -import { ref } from 'vue' | ||
| 10 | -const { t } = useI18n() // 国际化 | ||
| 11 | -// ========== 列表相关 ========== | ||
| 12 | -const { register, tableObject, methods } = useTable<OperateLogVO>({ | ||
| 13 | - getListApi: OperateLogApi.getOperateLogPageApi, | ||
| 14 | - exportListApi: OperateLogApi.exportOperateLogApi | ||
| 15 | -}) | ||
| 16 | -// ========== 详情相关 ========== | ||
| 17 | -const detailRef = ref() // 详情 Ref | ||
| 18 | -const dialogVisible = ref(false) // 是否显示弹出层 | ||
| 19 | -const dialogTitle = ref(t('action.detail')) // 弹出层标题 | ||
| 20 | -const { getList, setSearchParams, exportList } = methods | ||
| 21 | -// 详情 | ||
| 22 | -const handleDetail = (row: OperateLogVO) => { | ||
| 23 | - // 设置数据 | ||
| 24 | - detailRef.value = row | ||
| 25 | - dialogVisible.value = true | ||
| 26 | -} | ||
| 27 | -getList() | ||
| 28 | -</script> | ||
| 29 | <template> | 1 | <template> |
| 30 | <ContentWrap> | 2 | <ContentWrap> |
| 31 | - <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" /> | ||
| 32 | - </ContentWrap> | ||
| 33 | - <ContentWrap> | ||
| 34 | - <!-- 操作工具栏 --> | ||
| 35 | - <div class="mb-10px"> | ||
| 36 | - <el-button | ||
| 37 | - type="warning" | ||
| 38 | - v-hasPermi="['system:operate-log:export']" | ||
| 39 | - :loading="tableObject.exportLoading" | ||
| 40 | - @click="exportList('操作日志.xls')" | ||
| 41 | - > | ||
| 42 | - <Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }} | ||
| 43 | - </el-button> | ||
| 44 | - </div> | ||
| 45 | - <Table | ||
| 46 | - :columns="allSchemas.tableColumns" | ||
| 47 | - :selection="false" | ||
| 48 | - :data="tableObject.tableList" | ||
| 49 | - :loading="tableObject.loading" | ||
| 50 | - :pagination="{ | ||
| 51 | - total: tableObject.total | ||
| 52 | - }" | ||
| 53 | - v-model:pageSize="tableObject.pageSize" | ||
| 54 | - v-model:currentPage="tableObject.currentPage" | ||
| 55 | - @register="register" | ||
| 56 | - > | ||
| 57 | - <template #type="{ row }"> | ||
| 58 | - <DictTag :type="DICT_TYPE.SYSTEM_OPERATE_TYPE" :value="row.type" /> | 3 | + <!-- 列表 --> |
| 4 | + <vxe-grid ref="xGrid" v-bind="gridOptions" class="xtable-scrollbar"> | ||
| 5 | + <!-- 操作:新增 --> | ||
| 6 | + <template #toolbar_buttons> | ||
| 7 | + <XButton | ||
| 8 | + type="warning" | ||
| 9 | + preIcon="ep:download" | ||
| 10 | + :title="t('action.export')" | ||
| 11 | + v-hasPermi="['system:operate-log:export']" | ||
| 12 | + @click="handleExport()" | ||
| 13 | + /> | ||
| 59 | </template> | 14 | </template> |
| 60 | <template #duration="{ row }"> | 15 | <template #duration="{ row }"> |
| 61 | <span>{{ row.duration + 'ms' }}</span> | 16 | <span>{{ row.duration + 'ms' }}</span> |
| @@ -63,35 +18,68 @@ getList() | @@ -63,35 +18,68 @@ getList() | ||
| 63 | <template #resultCode="{ row }"> | 18 | <template #resultCode="{ row }"> |
| 64 | <span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span> | 19 | <span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span> |
| 65 | </template> | 20 | </template> |
| 66 | - <template #startTime="{ row }"> | ||
| 67 | - <span>{{ dayjs(row.startTime).format('YYYY-MM-DD HH:mm:ss') }}</span> | 21 | + <template #actionbtns_default="{ row }"> |
| 22 | + <!-- 操作:详情 --> | ||
| 23 | + <XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" /> | ||
| 68 | </template> | 24 | </template> |
| 69 | - <template #action="{ row }"> | ||
| 70 | - <el-button link type="primary" @click="handleDetail(row)"> | ||
| 71 | - <Icon icon="ep:view" class="mr-1px" /> {{ t('action.detail') }} | ||
| 72 | - </el-button> | ||
| 73 | - </template> | ||
| 74 | - </Table> | 25 | + </vxe-grid> |
| 75 | </ContentWrap> | 26 | </ContentWrap> |
| 76 | - <Dialog v-model="dialogVisible" :title="dialogTitle"> | ||
| 77 | - <!-- 对话框(详情) --> | ||
| 78 | - <Descriptions :schema="allSchemas.detailSchema" :data="detailRef"> | ||
| 79 | - <template #resultCode="{ row }"> | ||
| 80 | - <span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span> | ||
| 81 | - </template> | ||
| 82 | - <template #type="{ row }"> | ||
| 83 | - <DictTag :type="DICT_TYPE.SYSTEM_OPERATE_TYPE" :value="row.type" /> | ||
| 84 | - </template> | ||
| 85 | - <template #duration="{ row }"> | ||
| 86 | - <span>{{ row.duration + 'ms' }}</span> | ||
| 87 | - </template> | ||
| 88 | - <template #startTime="{ row }"> | ||
| 89 | - <span>{{ dayjs(row.startTime).format('YYYY-MM-DD HH:mm:ss') }}</span> | ||
| 90 | - </template> | ||
| 91 | - </Descriptions> | ||
| 92 | - <!-- 操作按钮 --> | 27 | + <!-- 弹窗 --> |
| 28 | + <XModal id="postModel" v-model="dialogVisible" :title="dialogTitle"> | ||
| 29 | + <template #default> | ||
| 30 | + <!-- 对话框(详情) --> | ||
| 31 | + <Descriptions :schema="allSchemas.detailSchema" :data="detailRef"> | ||
| 32 | + <template #resultCode="{ row }"> | ||
| 33 | + <span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span> | ||
| 34 | + </template> | ||
| 35 | + <template #duration="{ row }"> | ||
| 36 | + <span>{{ row.duration + 'ms' }}</span> | ||
| 37 | + </template> | ||
| 38 | + </Descriptions> | ||
| 39 | + </template> | ||
| 93 | <template #footer> | 40 | <template #footer> |
| 94 | - <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button> | 41 | + <!-- 按钮:关闭 --> |
| 42 | + <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> | ||
| 95 | </template> | 43 | </template> |
| 96 | - </Dialog> | 44 | + </XModal> |
| 97 | </template> | 45 | </template> |
| 46 | +<script setup lang="ts"> | ||
| 47 | +// 全局相关的 import | ||
| 48 | +import { ref } from 'vue' | ||
| 49 | +import { useI18n } from '@/hooks/web/useI18n' | ||
| 50 | +import { useVxeGrid } from '@/hooks/web/useVxeGrid' | ||
| 51 | +import { VxeGridInstance } from 'vxe-table' | ||
| 52 | +// 业务相关的 import | ||
| 53 | +import * as OperateLogApi from '@/api/system/operatelog' | ||
| 54 | +import { allSchemas } from './operatelog.data' | ||
| 55 | +import download from '@/utils/download' | ||
| 56 | + | ||
| 57 | +const { t } = useI18n() // 国际化 | ||
| 58 | +// 列表相关的变量 | ||
| 59 | +const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref | ||
| 60 | +const { gridOptions } = useVxeGrid<OperateLogApi.OperateLogVO>({ | ||
| 61 | + allSchemas: allSchemas, | ||
| 62 | + getListApi: OperateLogApi.getOperateLogPageApi | ||
| 63 | +}) | ||
| 64 | +// 弹窗相关的变量 | ||
| 65 | +const dialogVisible = ref(false) // 是否显示弹出层 | ||
| 66 | +const dialogTitle = ref('edit') // 弹出层标题 | ||
| 67 | +const actionLoading = ref(false) // 按钮 Loading | ||
| 68 | +const detailRef = ref() // 详情 Ref | ||
| 69 | +// 详情 | ||
| 70 | +const handleDetail = (row: OperateLogApi.OperateLogVO) => { | ||
| 71 | + // 设置数据 | ||
| 72 | + detailRef.value = row | ||
| 73 | + dialogVisible.value = true | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +// 导出操作 | ||
| 77 | +const handleExport = async () => { | ||
| 78 | + const queryParams = Object.assign( | ||
| 79 | + {}, | ||
| 80 | + JSON.parse(JSON.stringify(xGrid.value?.getRefMaps().refForm.value.data)) | ||
| 81 | + ) | ||
| 82 | + const res = await OperateLogApi.exportOperateLogApi(queryParams) | ||
| 83 | + download.excel(res, '岗位列表.xls') | ||
| 84 | +} | ||
| 85 | +</script> |
yudao-ui-admin-vue3/src/views/system/operatelog/operatelog.data.ts
| 1 | import { reactive } from 'vue' | 1 | import { reactive } from 'vue' |
| 2 | import { DICT_TYPE } from '@/utils/dict' | 2 | import { DICT_TYPE } from '@/utils/dict' |
| 3 | -import { useI18n } from '@/hooks/web/useI18n' | ||
| 4 | -import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas' | ||
| 5 | -const { t } = useI18n() // 国际化 | ||
| 6 | -const crudSchemas = reactive<CrudSchema[]>([ | ||
| 7 | - { | ||
| 8 | - label: t('common.index'), | ||
| 9 | - field: 'id', | ||
| 10 | - type: 'index', | ||
| 11 | - form: { | ||
| 12 | - show: false | ||
| 13 | - } | ||
| 14 | - }, | ||
| 15 | - { | ||
| 16 | - label: '操作模块', | ||
| 17 | - field: 'module', | ||
| 18 | - search: { | ||
| 19 | - show: true | ||
| 20 | - } | ||
| 21 | - }, | ||
| 22 | - { | ||
| 23 | - label: '操作名', | ||
| 24 | - field: 'name' | ||
| 25 | - }, | ||
| 26 | - { | ||
| 27 | - label: '操作类型', | ||
| 28 | - field: 'type', | ||
| 29 | - dictType: DICT_TYPE.SYSTEM_OPERATE_TYPE, | ||
| 30 | - search: { | ||
| 31 | - show: true | ||
| 32 | - } | ||
| 33 | - }, | ||
| 34 | - { | ||
| 35 | - label: '请求方法名', | ||
| 36 | - field: 'requestMethod' | ||
| 37 | - }, | ||
| 38 | - { | ||
| 39 | - label: '请求地址', | ||
| 40 | - field: 'requestUrl' | ||
| 41 | - }, | ||
| 42 | - { | ||
| 43 | - label: '操作人员', | ||
| 44 | - field: 'userNickname', | ||
| 45 | - search: { | ||
| 46 | - show: true | ||
| 47 | - } | ||
| 48 | - }, | ||
| 49 | - { | ||
| 50 | - label: '操作明细', | ||
| 51 | - field: 'content', | ||
| 52 | - table: { | ||
| 53 | - show: false | ||
| 54 | - } | ||
| 55 | - }, | ||
| 56 | - { | ||
| 57 | - label: '用户 IP', | ||
| 58 | - field: 'userIp', | ||
| 59 | - table: { | ||
| 60 | - show: false | ||
| 61 | - } | ||
| 62 | - }, | ||
| 63 | - { | ||
| 64 | - label: 'userAgent', | ||
| 65 | - field: 'userAgent' | ||
| 66 | - }, | ||
| 67 | - { | ||
| 68 | - label: '操作结果', | ||
| 69 | - field: 'resultCode', | ||
| 70 | - search: { | ||
| 71 | - show: true, | ||
| 72 | - component: 'Select', | ||
| 73 | - componentProps: { | ||
| 74 | - options: [ | ||
| 75 | - { label: '成功', value: true }, | ||
| 76 | - { label: '失败', value: false } | ||
| 77 | - ] | 3 | +import { VxeCrudSchema, useVxeCrudSchemas } from '@/hooks/web/useVxeCrudSchemas' |
| 4 | + | ||
| 5 | +const crudSchemas = reactive<VxeCrudSchema>({ | ||
| 6 | + primaryKey: 'id', | ||
| 7 | + primaryType: 'seq', | ||
| 8 | + action: true, | ||
| 9 | + actionWidth: '80px', | ||
| 10 | + columns: [ | ||
| 11 | + { | ||
| 12 | + title: '操作模块', | ||
| 13 | + field: 'module', | ||
| 14 | + isSearch: true | ||
| 15 | + }, | ||
| 16 | + { | ||
| 17 | + title: '操作名', | ||
| 18 | + field: 'name' | ||
| 19 | + }, | ||
| 20 | + { | ||
| 21 | + title: '操作类型', | ||
| 22 | + field: 'type', | ||
| 23 | + dictType: DICT_TYPE.SYSTEM_OPERATE_TYPE, | ||
| 24 | + isSearch: true | ||
| 25 | + }, | ||
| 26 | + { | ||
| 27 | + title: '请求方法名', | ||
| 28 | + field: 'requestMethod' | ||
| 29 | + }, | ||
| 30 | + { | ||
| 31 | + title: '请求地址', | ||
| 32 | + field: 'requestUrl' | ||
| 33 | + }, | ||
| 34 | + { | ||
| 35 | + title: '操作人员', | ||
| 36 | + field: 'userNickname', | ||
| 37 | + isSearch: true | ||
| 38 | + }, | ||
| 39 | + { | ||
| 40 | + title: '操作明细', | ||
| 41 | + field: 'content', | ||
| 42 | + isTable: false | ||
| 43 | + }, | ||
| 44 | + { | ||
| 45 | + title: '用户 IP', | ||
| 46 | + field: 'userIp', | ||
| 47 | + isTable: false | ||
| 48 | + }, | ||
| 49 | + { | ||
| 50 | + title: 'userAgent', | ||
| 51 | + field: 'userAgent' | ||
| 52 | + }, | ||
| 53 | + { | ||
| 54 | + title: '操作结果', | ||
| 55 | + field: 'resultCode', | ||
| 56 | + table: { | ||
| 57 | + slots: { | ||
| 58 | + default: 'resultCode' | ||
| 59 | + } | ||
| 78 | } | 60 | } |
| 79 | - } | ||
| 80 | - }, | ||
| 81 | - { | ||
| 82 | - label: '操作日期', | ||
| 83 | - field: 'startTime', | ||
| 84 | - form: { | ||
| 85 | - show: false | ||
| 86 | }, | 61 | }, |
| 87 | - search: { | ||
| 88 | - show: true, | ||
| 89 | - component: 'DatePicker', | ||
| 90 | - componentProps: { | ||
| 91 | - type: 'daterange', | ||
| 92 | - valueFormat: 'YYYY-MM-DD HH:mm:ss', | ||
| 93 | - defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)] | 62 | + { |
| 63 | + title: '操作日期', | ||
| 64 | + field: 'startTime', | ||
| 65 | + formatter: 'formatDate', | ||
| 66 | + isForm: false, | ||
| 67 | + search: { | ||
| 68 | + itemRender: { | ||
| 69 | + name: 'XDataTimePicker' | ||
| 70 | + } | ||
| 94 | } | 71 | } |
| 95 | - } | ||
| 96 | - }, | ||
| 97 | - { | ||
| 98 | - label: '执行时长', | ||
| 99 | - field: 'duration' | ||
| 100 | - }, | ||
| 101 | - { | ||
| 102 | - label: t('table.action'), | ||
| 103 | - field: 'action', | ||
| 104 | - width: '120px', | ||
| 105 | - form: { | ||
| 106 | - show: false | ||
| 107 | }, | 72 | }, |
| 108 | - detail: { | ||
| 109 | - show: false | 73 | + { |
| 74 | + title: '执行时长', | ||
| 75 | + field: 'duration', | ||
| 76 | + table: { | ||
| 77 | + slots: { | ||
| 78 | + default: 'duration' | ||
| 79 | + } | ||
| 80 | + } | ||
| 110 | } | 81 | } |
| 111 | - } | ||
| 112 | -]) | ||
| 113 | -export const { allSchemas } = useCrudSchemas(crudSchemas) | 82 | + ] |
| 83 | +}) | ||
| 84 | +export const { allSchemas } = useVxeCrudSchemas(crudSchemas) |
yudao-ui-admin-vue3/src/views/system/user/index.vue
| @@ -39,11 +39,6 @@ import { getAccessToken, getTenantId } from '@/utils/auth' | @@ -39,11 +39,6 @@ import { getAccessToken, getTenantId } from '@/utils/auth' | ||
| 39 | import { useMessage } from '@/hooks/web/useMessage' | 39 | import { useMessage } from '@/hooks/web/useMessage' |
| 40 | 40 | ||
| 41 | const message = useMessage() | 41 | const message = useMessage() |
| 42 | -interface Tree { | ||
| 43 | - id: number | ||
| 44 | - name: string | ||
| 45 | - children?: Tree[] | ||
| 46 | -} | ||
| 47 | 42 | ||
| 48 | const defaultProps = { | 43 | const defaultProps = { |
| 49 | children: 'children', | 44 | children: 'children', |