Browse Source

chore: cleanup

main
刘凯 1 year ago
parent
commit
49659c91fd
  1. 1
      src/enums/pageEnum.ts
  2. 14
      src/layouts/default/feature/index.vue
  3. 16
      src/layouts/default/footer/index.vue
  4. 26
      src/layouts/default/header/components/FullScreen.vue
  5. 5
      src/layouts/default/header/components/index.ts
  6. 121
      src/layouts/default/header/components/notify/NoticeList.vue
  7. 192
      src/layouts/default/header/components/notify/data.ts
  8. 35
      src/layouts/default/header/components/notify/index.vue
  9. 14
      src/layouts/default/header/components/user-dropdown/index.vue
  10. 12
      src/layouts/default/header/index.vue
  11. 35
      src/layouts/default/tabs/components/FoldButton.vue
  12. 28
      src/layouts/default/tabs/components/TabRedo.vue
  13. 10
      src/layouts/default/tabs/index.vue
  14. 12
      src/router/routes/index.ts
  15. 31
      src/router/routes/modules/about.ts
  16. 8
      src/settings/siteSetting.ts
  17. 5
      src/store/modules/permission.ts
  18. 8
      src/utils/http/axios/index.ts
  19. 107
      src/views/base/about/index.vue
  20. 28
      src/views/base/profile/MsgNotify.vue
  21. 5
      src/views/base/profile/data.ts
  22. 2
      src/views/base/profile/index.vue
  23. 3
      src/views/bpm/definition/index.vue
  24. 43
      src/views/bpm/form/FormModal.vue
  25. 9
      src/views/bpm/form/editor/index.vue
  26. 46
      src/views/bpm/form/form.data.ts
  27. 86
      src/views/bpm/form/index.vue
  28. 58
      src/views/bpm/group/GroupModal.vue
  29. 134
      src/views/bpm/group/group.data.ts
  30. 78
      src/views/bpm/group/index.vue
  31. 78
      src/views/bpm/model/ModelImportModal.vue
  32. 58
      src/views/bpm/model/ModelModal.vue
  33. 3
      src/views/bpm/model/editor/index.vue
  34. 143
      src/views/bpm/model/index.vue
  35. 225
      src/views/bpm/model/model.data.ts
  36. 42
      src/views/bpm/oa/leave/create.vue
  37. 40
      src/views/bpm/oa/leave/detail.vue
  38. 104
      src/views/bpm/oa/leave/index.vue
  39. 158
      src/views/bpm/oa/leave/leave.data.ts
  40. 32
      src/views/bpm/processInstance/create/create.data.ts
  41. 115
      src/views/bpm/processInstance/create/index.vue
  42. 3
      src/views/bpm/processInstance/detail/index.vue
  43. 84
      src/views/bpm/processInstance/index.vue
  44. 118
      src/views/bpm/processInstance/processInstance.data.ts
  45. 79
      src/views/bpm/task/done/done.data.ts
  46. 53
      src/views/bpm/task/done/index.vue
  47. 44
      src/views/bpm/task/todo/index.vue
  48. 59
      src/views/bpm/task/todo/todo.data.ts
  49. 3
      src/views/bpm/taskAssignRule/index.vue
  50. 25
      src/views/infra/apiAccessLog/AccessLogModal.vue
  51. 222
      src/views/infra/apiAccessLog/apiAccessLog.data.ts
  52. 74
      src/views/infra/apiAccessLog/index.vue
  53. 25
      src/views/infra/apiErrorLog/ErrorLogModal.vue
  54. 248
      src/views/infra/apiErrorLog/apiErrorLog.data.ts
  55. 101
      src/views/infra/apiErrorLog/index.vue
  56. 10
      src/views/infra/build/index.vue
  57. 96
      src/views/infra/codegen/EditTable.vue
  58. 157
      src/views/infra/codegen/codegen.data.ts
  59. 70
      src/views/infra/codegen/components/BasicInfoForm.vue
  60. 62
      src/views/infra/codegen/components/CloumInfoForm.vue
  61. 55
      src/views/infra/codegen/components/FinishForm.vue
  62. 43
      src/views/infra/codegen/components/ImportTableModal.vue
  63. 144
      src/views/infra/codegen/components/PreviewModal.vue
  64. 328
      src/views/infra/codegen/components/data.ts
  65. 109
      src/views/infra/codegen/index.vue
  66. 58
      src/views/infra/config/ConfigModal.vue
  67. 139
      src/views/infra/config/config.data.ts
  68. 95
      src/views/infra/config/index.vue
  69. 56
      src/views/infra/dataSourceConfig/DataSourceConfigModal.vue
  70. 66
      src/views/infra/dataSourceConfig/dataSourceConfig.data.ts
  71. 86
      src/views/infra/dataSourceConfig/index.vue
  72. 53
      src/views/infra/dbDoc/index.vue
  73. 12
      src/views/infra/druid/index.vue
  74. 79
      src/views/infra/file/file.data.ts
  75. 92
      src/views/infra/file/index.vue
  76. 58
      src/views/infra/fileConfig/FileConfigModal.vue
  77. 179
      src/views/infra/fileConfig/ficleConfig.data.ts
  78. 107
      src/views/infra/fileConfig/index.vue
  79. 92
      src/views/infra/job/JobModal.vue
  80. 141
      src/views/infra/job/index.vue
  81. 172
      src/views/infra/job/job.data.ts
  82. 28
      src/views/infra/job/logger/JobLogModal.vue
  83. 70
      src/views/infra/job/logger/index.vue
  84. 150
      src/views/infra/job/logger/jobLog.data.ts
  85. 53
      src/views/infra/redis/components/CommandStats.vue
  86. 55
      src/views/infra/redis/components/Memory.vue
  87. 46
      src/views/infra/redis/index.vue
  88. 64
      src/views/infra/redis/redis.data.ts
  89. 29
      src/views/infra/server/index.vue
  90. 14
      src/views/infra/skywalking/index.vue
  91. 16
      src/views/infra/swagger/index.vue
  92. 3
      src/views/infra/testDemo/index.vue
  93. 112
      src/views/infra/webSocket/index.vue
  94. 3
      src/views/mall/market/banner/index.vue
  95. 3
      src/views/mall/product/brand/index.vue
  96. 3
      src/views/mall/product/category/index.vue
  97. 3
      src/views/mall/product/property/index.vue
  98. 3
      src/views/mall/product/spu/index.vue
  99. 3
      src/views/mall/promotion/coupon/index.vue
  100. 3
      src/views/mall/promotion/couponTemplate/index.vue
  101. Some files were not shown because too many files have changed in this diff Show More

1
src/enums/pageEnum.ts

@ -7,7 +7,6 @@ export enum PageEnum {
ERROR_PAGE = '/exception', ERROR_PAGE = '/exception',
// error log page path // error log page path
ERROR_LOG_PAGE = '/error-log/list', ERROR_LOG_PAGE = '/error-log/list',
MESSAGE_PAGE = '/profile/notify-message',
} }
export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight' export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight'

14
src/layouts/default/feature/index.vue

@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { FloatButton } from 'ant-design-vue' import { FloatButton } from 'ant-design-vue'
import { QuestionCircleOutlined } from '@ant-design/icons-vue'
import { computed, unref } from 'vue' import { computed, unref } from 'vue'
import { SettingButtonPositionEnum } from '@/enums/appEnum' import { SettingButtonPositionEnum } from '@/enums/appEnum'
@ -9,8 +8,6 @@ import { useRootSetting } from '@/hooks/setting/useRootSetting'
import { useUserStoreWithOut } from '@/store/modules/user' import { useUserStoreWithOut } from '@/store/modules/user'
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent' import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'
import SessionTimeoutLogin from '@/views/base/login/SessionTimeoutLogin.vue' import SessionTimeoutLogin from '@/views/base/login/SessionTimeoutLogin.vue'
import { openWindow } from '@/utils'
import { SITE_URL } from '@/settings/siteSetting'
defineOptions({ name: 'LayoutFeatures' }) defineOptions({ name: 'LayoutFeatures' })
const LayoutLockPage = createAsyncComponent(() => import('@/views/base/lock/index.vue')) const LayoutLockPage = createAsyncComponent(() => import('@/views/base/lock/index.vue'))
@ -41,17 +38,6 @@ const getIsFixedSettingDrawer = computed(() => {
<template> <template>
<LayoutLockPage /> <LayoutLockPage />
<FloatButton.BackTop v-if="getUseOpenBackTop" :target="getTarget" /> <FloatButton.BackTop v-if="getUseOpenBackTop" :target="getTarget" />
<FloatButton
shape="circle"
type="primary"
:badge="{ dot: true }"
:style="{ right: '64px' }"
@click="openWindow(SITE_URL)"
>
<template #icon>
<QuestionCircleOutlined />
</template>
</FloatButton>
<SettingDrawer <SettingDrawer
v-if="getIsFixedSettingDrawer" v-if="getIsFixedSettingDrawer"
class="absolute top-[45%] z-10 flex cursor-pointer items-center justify-items-center rounded-l-md rounded-r-none p-2.5" class="absolute top-[45%] z-10 flex cursor-pointer items-center justify-items-center rounded-l-md rounded-r-none p-2.5"

16
src/layouts/default/footer/index.vue

@ -1,15 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, unref } from 'vue' import { computed, ref, unref } from 'vue'
import { Layout } from 'ant-design-vue' import { Layout } from 'ant-design-vue'
import { GithubFilled } from '@ant-design/icons-vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useLayoutHeight } from '../content/useContentViewHeight' import { useLayoutHeight } from '../content/useContentViewHeight'
import { DOC_URL, GITHUB_URL, SITE_URL } from '@/settings/siteSetting'
import { openWindow } from '@/utils'
import { useI18n } from '@/hooks/web/useI18n'
import { useRootSetting } from '@/hooks/setting/useRootSetting' import { useRootSetting } from '@/hooks/setting/useRootSetting'
defineOptions({ name: 'LayoutFooter' }) defineOptions({ name: 'LayoutFooter' })
@ -18,7 +11,6 @@ const SITE_TITLE = ref(import.meta.env.VITE_GLOB_APP_TITLE)
const Footer = Layout.Footer const Footer = Layout.Footer
const { t } = useI18n()
const { getShowFooter } = useRootSetting() const { getShowFooter } = useRootSetting()
const { currentRoute } = useRouter() const { currentRoute } = useRouter()
@ -39,14 +31,6 @@ const getShowLayoutFooter = computed(() => {
<template> <template>
<Footer v-if="getShowLayoutFooter" ref="footerRef" class="text-center text-[var(--normal-text)]"> <Footer v-if="getShowLayoutFooter" ref="footerRef" class="text-center text-[var(--normal-text)]">
<div class="mb-2">
<a class="text-[var(--normal-text)] hover:text-[var(--hover-text)]" @click="openWindow(SITE_URL)">外包咨询</a>
<GithubFilled class="mx-7.5 hover:text-[var(--hover-text)]" @click="openWindow(GITHUB_URL)" />
<a class="text-[var(--normal-text)] hover:text-[var(--hover-text)]" @click="openWindow(DOC_URL)">{{
t('layout.footer.onlineDocument') }}</a>
</div>
<div>Copyright &copy;2023 {{ SITE_TITLE }}</div> <div>Copyright &copy;2023 {{ SITE_TITLE }}</div>
</Footer> </Footer>
</template> </template>

26
src/layouts/default/header/components/FullScreen.vue

@ -1,26 +0,0 @@
<script lang="ts" setup>
import { computed, unref } from 'vue'
import { Tooltip } from 'ant-design-vue'
import { useFullscreen } from '@vueuse/core'
import { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons-vue'
import { useI18n } from '@/hooks/web/useI18n'
defineOptions({ name: 'FullScreen' })
const { t } = useI18n()
const { toggle, isFullscreen } = useFullscreen()
//
isFullscreen.value = !!document.fullscreenElement
const getTitle = computed(() => {
return unref(isFullscreen) ? t('layout.header.tooltipExitFull') : t('layout.header.tooltipEntryFull')
})
</script>
<template>
<Tooltip :title="getTitle" placement="bottom" :mouse-enter-delay="0.5">
<span @click="toggle">
<FullscreenOutlined v-if="!isFullscreen" />
<FullscreenExitOutlined v-else />
</span>
</Tooltip>
</template>

5
src/layouts/default/header/components/index.ts

@ -1,4 +1,3 @@
import FullScreen from './FullScreen.vue'
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent' import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'
export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), { export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), {
@ -7,8 +6,4 @@ export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/i
export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue')) export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue'))
export const Notify = createAsyncComponent(() => import('./notify/index.vue'))
export const ErrorAction = createAsyncComponent(() => import('./ErrorAction.vue')) export const ErrorAction = createAsyncComponent(() => import('./ErrorAction.vue'))
export { FullScreen }

121
src/layouts/default/header/components/notify/NoticeList.vue

@ -1,121 +0,0 @@
<script lang="ts" setup>
import { computed, ref, unref, watch } from 'vue'
import { Avatar, List, Tag, Typography } from 'ant-design-vue'
import type { ListItem } from './data'
import { isNumber } from '@/utils/is'
const props = defineProps({
list: {
type: Array as PropType<ListItem[]>,
default: () => [],
},
pageSize: {
type: [Boolean, Number] as PropType<boolean | number>,
default: 5,
},
currentPage: {
type: Number,
default: 1,
},
titleRows: {
type: Number,
default: 1,
},
descRows: {
type: Number,
default: 2,
},
onTitleClick: {
type: Function as PropType<(Recordable) => void>,
},
})
const emit = defineEmits(['update:currentPage'])
const current = ref(props.currentPage || 1)
const getData = computed(() => {
const { pageSize, list } = props
if (pageSize === false)
return []
const size = isNumber(pageSize) ? pageSize : 5
return list.slice(size * (unref(current) - 1), size * unref(current))
})
watch(
() => props.currentPage,
(v) => {
current.value = v
},
)
const isTitleClickable = computed(() => !!props.onTitleClick)
const getPagination = computed(() => {
const { list, pageSize } = props
// compatible line 104
// if typeof pageSize is boolean, Number(true) && 5 = 5, Number(false) && 5 = 0
const size = isNumber(pageSize) ? pageSize : Number(pageSize) && 5
if (size > 0 && list && list.length > size) {
return {
total: list.length,
pageSize: size,
// size: 'small',
current: unref(current),
onChange(page) {
current.value = page
emit('update:currentPage', page)
},
}
}
else {
return false
}
})
function handleTitleClick(item: ListItem) {
props.onTitleClick && props.onTitleClick(item)
}
</script>
<template>
<List class="display-none" bordered :pagination="getPagination">
<template v-for="item in getData" :key="item.id">
<List.Item class="cursor-pointer overflow-hidden p-1.5 transition-all delay-300">
<List.Item.Meta>
<template #title>
<div class="mb-2 font-normal">
<Typography.Paragraph
style="width: 100%; margin-bottom: 0 !important"
:style="{ cursor: isTitleClickable ? 'pointer' : '' }"
:delete="!!item.titleDelete"
:ellipsis="$props.titleRows && $props.titleRows > 0 ? { rows: $props.titleRows, tooltip: !!item.title } : false"
:content="item.title"
@click="handleTitleClick(item)"
/>
<div v-if="item.extra" class="float-right mr-0 font-normal -mt-0.375">
<Tag class="mr-0" :color="item.color">
{{ item.extra }}
</Tag>
</div>
</div>
</template>
<template #avatar>
<Avatar v-if="item.avatar" class="mt-1" :src="item.avatar" />
<span v-else> {{ item.avatar }}</span>
</template>
<template #description>
<div>
<div v-if="item.description" class="text-xs/18">
<Typography.Paragraph
style="width: 100%; margin-bottom: 0 !important"
:ellipsis="$props.descRows && $props.descRows > 0 ? { rows: $props.descRows, tooltip: !!item.description } : false"
:content="item.description"
/>
</div>
<div class="mt-1 text-xs/18">
{{ item.datetime }}
</div>
</div>
</template>
</List.Item.Meta>
</List.Item>
</template>
</List>
</template>

192
src/layouts/default/header/components/notify/data.ts

@ -1,192 +0,0 @@
export interface ListItem {
id: string
avatar: string
// 通知的标题内容
title: string
// 是否在标题上显示删除线
titleDelete?: boolean
datetime: string
type: string
read?: boolean
description: string
clickClose?: boolean
extra?: string
color?: string
}
export interface TabItem {
key: string
name: string
list: ListItem[]
unreadlist?: ListItem[]
}
export const tabListData: TabItem[] = [
{
key: '1',
name: '通知',
list: [
{
id: '000000001',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '你收到了 14 份新周报',
description: '',
datetime: '2017-08-09',
type: '1',
},
{
id: '000000002',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
title: '你推荐的 曲妮妮 已通过第三轮面试',
description: '',
datetime: '2017-08-08',
type: '1',
},
{
id: '000000003',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
title: '这种模板可以区分多种通知类型',
description: '',
datetime: '2017-08-07',
// read: true,
type: '1',
},
{
id: '000000004',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000005',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '标题可以设置自动显示省略号,本例中标题行数已设为1行,如果内容超过1行将自动截断并支持tooltip显示完整标题。',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000006',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000007',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000008',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000009',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000010',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
],
},
{
key: '2',
name: '消息',
list: [
{
id: '000000006',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '曲丽丽 评论了你',
description: '描述信息描述信息描述信息',
datetime: '2017-08-07',
type: '2',
clickClose: true,
},
{
id: '000000007',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '朱偏右 回复了你',
description: '这种模板用于提醒谁与你发生了互动',
datetime: '2017-08-07',
type: '2',
clickClose: true,
},
{
id: '000000008',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '标题',
description:
'请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容',
datetime: '2017-08-07',
type: '2',
clickClose: true,
},
],
},
{
key: '3',
name: '待办',
list: [
{
id: '000000009',
avatar: '',
title: '任务名称',
description: '任务需要在 2017-01-12 20:00 前启动',
datetime: '',
extra: '未开始',
color: '',
type: '3',
},
{
id: '000000010',
avatar: '',
title: '第三方紧急代码变更',
description: '冠霖 需在 2017-01-07 前完成代码变更任务',
datetime: '',
extra: '马上到期',
color: 'red',
type: '3',
},
{
id: '000000011',
avatar: '',
title: '信息安全考试',
description: '指派竹尔于 2017-01-09 前完成更新并发布',
datetime: '',
extra: '已耗时 8 天',
color: 'gold',
type: '3',
},
{
id: '000000012',
avatar: '',
title: 'ABCD 版本发布',
description: '指派竹尔于 2017-01-09 前完成更新并发布',
datetime: '',
extra: '进行中',
color: 'blue',
type: '3',
},
],
},
]

35
src/layouts/default/header/components/notify/index.vue

@ -1,35 +0,0 @@
<script lang="ts" setup>
import { computed, onMounted } from 'vue'
import { Badge, Tooltip } from 'ant-design-vue'
import { BellOutlined } from '@ant-design/icons-vue'
import { storeToRefs } from 'pinia'
import { useGo } from '@/hooks/web/usePage'
import { PageEnum } from '@/enums/pageEnum'
import { useUserMessageStore } from '@/store/modules/userMessage'
const go = useGo()
const store = useUserMessageStore()
const { unreadCount } = storeToRefs(store)
const tips = computed<string>(() => {
if (unreadCount.value === 0)
return '查看站内信'
return `查看站内信: 未读 ${unreadCount.value}`
})
onMounted(async () => {
// store
store.updateUnreadCount()
})
</script>
<template>
<div>
<Tooltip :title="tips">
<Badge :count="unreadCount" :offset="[0, 15]" size="small" @click="go({ path: PageEnum.MESSAGE_PAGE })">
<BellOutlined />
</Badge>
</Tooltip>
</div>
</template>

14
src/layouts/default/header/components/user-dropdown/index.vue

@ -3,7 +3,6 @@ import { Avatar, Dropdown, Menu, MenuDivider } from 'ant-design-vue'
import { UserOutlined } from '@ant-design/icons-vue' import { UserOutlined } from '@ant-design/icons-vue'
import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface' import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'
import { computed } from 'vue' import { computed } from 'vue'
import { DOC_URL } from '@/settings/siteSetting'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting' import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
@ -11,7 +10,6 @@ import { useDesign } from '@/hooks/web/useDesign'
import { useModal } from '@/components/Modal' import { useModal } from '@/components/Modal'
import headerImg from '@/assets/images/header.jpg' import headerImg from '@/assets/images/header.jpg'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { openWindow } from '@/utils'
import { useGo } from '@/hooks/web/usePage' import { useGo } from '@/hooks/web/usePage'
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent' import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'
@ -49,11 +47,6 @@ function handleLoginOut() {
userStore.confirmLoginOut() userStore.confirmLoginOut()
} }
// open doc
function openDoc() {
openWindow(DOC_URL)
}
function openProfile() { function openProfile() {
go('/profile/index') go('/profile/index')
} }
@ -66,9 +59,6 @@ function handleMenuClick(e: MenuInfo) {
case 'logout': case 'logout':
handleLoginOut() handleLoginOut()
break break
case 'doc':
openDoc()
break
case 'lock': case 'lock':
handleLock() handleLock()
break break
@ -94,10 +84,6 @@ function handleMenuClick(e: MenuInfo) {
<template #overlay> <template #overlay>
<Menu @click="handleMenuClick"> <Menu @click="handleMenuClick">
<MenuItem key="profile" :text="t('layout.header.accountCenter')" icon="ion:person-outline" /> <MenuItem key="profile" :text="t('layout.header.accountCenter')" icon="ion:person-outline" />
<MenuItem
v-if="getShowDoc" key="doc" :text="t('layout.header.dropdownItemDoc')"
icon="ion:document-text-outline"
/>
<MenuDivider v-if="getShowDoc" /> <MenuDivider v-if="getShowDoc" />
<MenuItem <MenuItem
v-if="getUseLockPage" key="lock" :text="t('layout.header.tooltipLock')" v-if="getUseLockPage" key="lock" :text="t('layout.header.tooltipLock')"

12
src/layouts/default/header/index.vue

@ -4,10 +4,10 @@ import { computed, unref } from 'vue'
import { Layout } from 'ant-design-vue' import { Layout } from 'ant-design-vue'
import LayoutMenu from '../menu/index.vue' import LayoutMenu from '../menu/index.vue'
import LayoutTrigger from '../trigger/index.vue' import LayoutTrigger from '../trigger/index.vue'
import { ErrorAction, FullScreen, LayoutBreadcrumb, Notify, UserDropDown } from './components' import { ErrorAction, LayoutBreadcrumb, UserDropDown } from './components'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { AppLocalePicker, AppLogo, AppSearch, AppSizePicker } from '@/components/Application' import { AppLocalePicker, AppLogo, AppSearch } from '@/components/Application'
import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting' import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting'
import { useMenuSetting } from '@/hooks/setting/useMenuSetting' import { useMenuSetting } from '@/hooks/setting/useMenuSetting'
@ -34,7 +34,7 @@ const { prefixCls } = useDesign('layout-header')
const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting() const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting()
const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } = useRootSetting() const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } = useRootSetting()
const { getHeaderTheme, getShowFullScreen, getShowNotice, getShowContent, getShowBread, getShowHeaderLogo, getShowHeader, getShowSearch } const { getHeaderTheme, getShowContent, getShowBread, getShowHeaderLogo, getShowHeader, getShowSearch }
= useHeaderSetting() = useHeaderSetting()
const { getShowLocalePicker } = useLocale() const { getShowLocalePicker } = useLocale()
@ -111,12 +111,6 @@ const getMenuMode = computed(() => {
<ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" /> <ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" />
<Notify v-if="getShowNotice" :class="`${prefixCls}-action__item notify-item`" />
<FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
<AppSizePicker :show-text="false" :class="`${prefixCls}-action__item size-item`" />
<AppLocalePicker <AppLocalePicker
v-if="getShowLocalePicker" :reload="true" :show-text="false" v-if="getShowLocalePicker" :reload="true" :show-text="false"
:class="`${prefixCls}-action__item locale-item`" :class="`${prefixCls}-action__item locale-item`"

35
src/layouts/default/tabs/components/FoldButton.vue

@ -1,35 +0,0 @@
<script lang="ts" setup>
import { computed, unref } from 'vue'
import { Icon } from '@/components/Icon'
import { useDesign } from '@/hooks/web/useDesign'
import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting'
import { useMenuSetting } from '@/hooks/setting/useMenuSetting'
import { triggerWindowResize } from '@/utils/event'
defineOptions({ name: 'FoldButton' })
const { prefixCls } = useDesign('multiple-tabs-content')
const { getShowMenu, setMenuSetting } = useMenuSetting()
const { getShowHeader, setHeaderSetting } = useHeaderSetting()
const getIsUnFold = computed(() => !unref(getShowMenu) && !unref(getShowHeader))
const getIcon = computed(() => (unref(getIsUnFold) ? 'codicon:screen-normal' : 'codicon:screen-full'))
function handleFold() {
const isUnFold = unref(getIsUnFold)
setMenuSetting({
show: isUnFold,
hidden: !isUnFold,
})
setHeaderSetting({ show: isUnFold })
triggerWindowResize()
}
</script>
<template>
<span :class="`${prefixCls}__extra-fold`" @click="handleFold">
<Icon :icon="getIcon" />
</span>
</template>

28
src/layouts/default/tabs/components/TabRedo.vue

@ -1,28 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { RedoOutlined } from '@ant-design/icons-vue'
import { useDesign } from '@/hooks/web/useDesign'
import { useTabs } from '@/hooks/web/useTabs'
defineOptions({ name: 'TabRedo' })
const loading = ref(false)
const { prefixCls } = useDesign('multiple-tabs-content')
const { refreshPage } = useTabs()
async function handleRedo() {
loading.value = true
await refreshPage()
setTimeout(() => {
loading.value = false
// Animation execution time
}, 1200)
}
</script>
<template>
<span :class="`${prefixCls}__extra-redo`" @click="handleRedo">
<RedoOutlined :spin="loading" />
</span>
</template>

10
src/layouts/default/tabs/index.vue

@ -8,8 +8,6 @@ import { computed, ref, unref } from 'vue'
import { Tabs } from 'ant-design-vue' import { Tabs } from 'ant-design-vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import TabContent from './components/TabContent.vue' import TabContent from './components/TabContent.vue'
import FoldButton from './components/FoldButton.vue'
import TabRedo from './components/TabRedo.vue'
import { initAffixTabs, useTabsDrag } from './useMultipleTabs' import { initAffixTabs, useTabsDrag } from './useMultipleTabs'
import { multipleTabHeight } from '@/settings/designSetting' import { multipleTabHeight } from '@/settings/designSetting'
@ -36,7 +34,7 @@ const router = useRouter()
const { prefixCls } = useDesign('multiple-tabs') const { prefixCls } = useDesign('multiple-tabs')
const go = useGo() const go = useGo()
const { getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting() const { getShowQuick } = useMultipleTabSetting()
const getTabsState = computed(() => { const getTabsState = computed(() => {
return tabStore.getTabList.filter(item => !item.meta?.hideTab) return tabStore.getTabList.filter(item => !item.meta?.hideTab)
@ -112,10 +110,8 @@ function handleEdit(targetKey: string) {
</Tabs.TabPane> </Tabs.TabPane>
</template> </template>
<template v-if="getShowRedo || getShowQuick" #rightExtra> <template v-if="getShowQuick" #rightExtra>
<TabRedo v-if="getShowRedo" /> <TabContent is-extra :tab-item="$route" />
<TabContent v-if="getShowQuick" is-extra :tab-item="$route" />
<FoldButton v-if="getShowFold" />
</template> </template>
</Tabs> </Tabs>
</div> </div>

12
src/router/routes/index.ts

@ -68,18 +68,6 @@ export const ProfileRoute: AppRouteRecordRaw = {
title: t('routes.basic.profile'), title: t('routes.basic.profile'),
}, },
}, },
{
path: 'notify-message',
component: () => import('@/views/system/notify/my/index.vue'),
name: 'MyNotifyMessage',
meta: {
canTo: true,
hidden: true,
noTagsView: false,
icon: 'ant-design:bell-outlined',
title: t('routes.basic.notifyMessage'),
},
},
], ],
} }

31
src/router/routes/modules/about.ts

@ -1,31 +0,0 @@
import type { AppRouteModule } from '@/router/types'
import { LAYOUT } from '@/router/constant'
import { t } from '@/hooks/web/useI18n'
const about: AppRouteModule = {
path: '/about',
name: 'About',
component: LAYOUT,
redirect: '/about/index',
meta: {
hideChildrenInMenu: true,
icon: 'ant-design:pushpin-filled',
title: t('routes.dashboard.about'),
orderNo: 100000,
},
children: [
{
path: 'index',
name: 'AboutPage',
component: () => import('@/views/base/about/index.vue'),
meta: {
title: t('routes.dashboard.about'),
icon: 'ant-design:pushpin-filled',
hideMenu: true,
},
},
],
}
export default about

8
src/settings/siteSetting.ts

@ -1,8 +0,0 @@
// github repo url
export const GITHUB_URL = 'https://gitee.com/xingyuv/vue-vben-admin'
// vue-vben-admin-next-doc
export const DOC_URL = 'http://vben-doc.x-surge.com/'
// site url
export const SITE_URL = 'http://static.yudao.iocoder.cn/mp/xinyu370.jpeg'

5
src/store/modules/permission.ts

@ -7,7 +7,6 @@ import { useAppStoreWithOut } from './app'
import { store } from '@/store' import { store } from '@/store'
import type { AppRouteRecordRaw, Menu } from '@/router/types' import type { AppRouteRecordRaw, Menu } from '@/router/types'
import { asyncRoutes } from '@/router/routes' import { asyncRoutes } from '@/router/routes'
import about from '@/router/routes/modules/about'
import dashboard from '@/router/routes/modules/dashboard' import dashboard from '@/router/routes/modules/dashboard'
import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic' import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'
import { transformRouteToMenu } from '@/router/helper/menuHelper' import { transformRouteToMenu } from '@/router/helper/menuHelper'
@ -226,14 +225,14 @@ export const usePermissionStore = defineStore('app-permission', {
routeList = transformObjToRoute(routeList) routeList = transformObjToRoute(routeList)
// Background routing to menu structure // Background routing to menu structure
// 后台路由到菜单结构 // 后台路由到菜单结构
const backMenuList = transformRouteToMenu([dashboard, ...routeList, about]) const backMenuList = transformRouteToMenu([dashboard, ...routeList])
this.setBackMenuList(backMenuList) this.setBackMenuList(backMenuList)
// remove meta.ignoreRoute item // remove meta.ignoreRoute item
// 删除 meta.ignoreRoute 项 // 删除 meta.ignoreRoute 项
routeList = filter(routeList, routeRemoveIgnoreFilter) routeList = filter(routeList, routeRemoveIgnoreFilter)
routeList = routeList.filter(routeRemoveIgnoreFilter) routeList = routeList.filter(routeRemoveIgnoreFilter)
routeList = flatMultiLevelRoutes(routeList) routeList = flatMultiLevelRoutes(routeList)
routes = [PAGE_NOT_FOUND_ROUTE, dashboard, ...routeList, about] routes = [PAGE_NOT_FOUND_ROUTE, dashboard, ...routeList]
break break
} }

8
src/utils/http/axios/index.ts

@ -319,11 +319,3 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
) )
} }
export const defHttp = createAxios() export const defHttp = createAxios()
// other api url
// export const otherHttp = createAxios({
// requestOptions: {
// apiUrl: 'xxx',
// urlPrefix: 'xxx',
// },
// });

107
src/views/base/about/index.vue

@ -1,107 +0,0 @@
<script lang="ts" setup>
import { h } from 'vue'
import { Tag } from 'ant-design-vue'
import { PageWrapper } from '@/components/Page'
import type { DescItem } from '@/components/Description'
import { Description, useDescription } from '@/components/Description'
import { DOC_URL, GITHUB_URL, SITE_URL } from '@/settings/siteSetting'
const { pkg, lastBuildTime } = __APP_INFO__
const { dependencies, devDependencies, name, version } = pkg
const schema: DescItem[] = []
const devSchema: DescItem[] = []
const commonTagRender = (color: string) => curVal => h(Tag, { color }, () => curVal)
const commonLinkRender = (text: string) => href => h('a', { href, target: '_blank' }, text)
const infoSchema: DescItem[] = [
{
label: '版本',
field: 'version',
render: commonTagRender('blue'),
},
{
label: '最后编译时间',
field: 'lastBuildTime',
render: commonTagRender('blue'),
},
{
label: '文档地址',
field: 'doc',
render: commonLinkRender('文档地址'),
},
{
label: '预览地址',
field: 'preview',
render: commonLinkRender('预览地址'),
},
{
label: 'Github',
field: 'github',
render: commonLinkRender('Github'),
},
{
label: '外包服务',
field: 'outsourcing',
render: commonLinkRender('外包服务'),
},
]
const infoData = {
version,
lastBuildTime,
doc: DOC_URL,
preview: SITE_URL,
github: GITHUB_URL,
outsourcing: SITE_URL,
}
Object.keys(dependencies).forEach((key) => {
schema.push({ field: key, label: key })
})
Object.keys(devDependencies).forEach((key) => {
devSchema.push({ field: key, label: key })
})
const [register] = useDescription({
title: '生产环境依赖',
data: dependencies,
schema,
column: 3,
})
const [registerDev] = useDescription({
title: '开发环境依赖',
data: devDependencies,
schema: devSchema,
column: 3,
})
const [infoRegister] = useDescription({
title: '项目信息',
data: infoData,
schema: infoSchema,
column: 2,
})
</script>
<template>
<PageWrapper title="关于">
<template #headerContent>
<div class="flex items-center justify-between">
<span class="flex-1">
<a :href="GITHUB_URL" target="_blank">{{ name }}</a>
基于Vue3.0Vite Ant-Design-Vue TypeScript
的后台解决方案目标是为中大型项目开发,提供现成的开箱解决方案及丰富的示例,原则上不会限制任何代码用于商用<br>
同时我们也提供<a :href="SITE_URL" target="_blank">外包服务</a>
</span>
</div>
</template>
<Description class="enter-y" @register="infoRegister" />
<Description class="enter-y my-4" @register="register" />
<Description class="enter-y" @register="registerDev" />
</PageWrapper>
</template>

28
src/views/base/profile/MsgNotify.vue

@ -1,28 +0,0 @@
<script lang="ts" setup>
import { List, Switch } from 'ant-design-vue'
import { msgNotifyList } from './data'
import { CollapseContainer } from '@/components/Container/index'
const ListItem = List.Item
const ListItemMeta = List.Item.Meta
</script>
<template>
<CollapseContainer title="新消息通知" :can-expan="false">
<List>
<template v-for="item in msgNotifyList" :key="item.key">
<ListItem>
<ListItemMeta>
<template #title>
{{ item.title }}
<Switch class="float-right mr-7.5 mt-0" checked-children="" un-checked-children="" default-checked />
</template>
<template #description>
<div>{{ item.description }}</div>
</template>
</ListItemMeta>
</ListItem>
</template>
</List>
</CollapseContainer>
</template>

5
src/views/base/profile/data.ts

@ -30,11 +30,6 @@ export const settingList = [
name: '账号绑定', name: '账号绑定',
component: 'AccountBind', component: 'AccountBind',
}, },
{
key: '4',
name: '新消息通知',
component: 'MsgNotify',
},
] ]
// 基础设置 form // 基础设置 form

2
src/views/base/profile/index.vue

@ -5,7 +5,6 @@ import { settingList } from './data'
import BaseSetting from './BaseSetting.vue' import BaseSetting from './BaseSetting.vue'
import SecureSetting from './SecureSetting.vue' import SecureSetting from './SecureSetting.vue'
import AccountBind from './AccountBind.vue' import AccountBind from './AccountBind.vue'
import MsgNotify from './MsgNotify.vue'
import { ScrollContainer } from '@/components/Container/index' import { ScrollContainer } from '@/components/Container/index'
const wrapperRef = ref(null) const wrapperRef = ref(null)
@ -22,7 +21,6 @@ const tabBarStyle = { width: '220px' }
<BaseSetting v-if="item.component === 'BaseSetting'" /> <BaseSetting v-if="item.component === 'BaseSetting'" />
<SecureSetting v-if="item.component === 'SecureSetting'" /> <SecureSetting v-if="item.component === 'SecureSetting'" />
<AccountBind v-if="item.component === 'AccountBind'" /> <AccountBind v-if="item.component === 'AccountBind'" />
<MsgNotify v-if="item.component === 'MsgNotify'" />
</TabPane> </TabPane>
</template> </template>
</Tabs> </Tabs>

3
src/views/bpm/definition/index.vue

@ -1,3 +0,0 @@
<template>
<div>开发中 definition</div>
</template>

43
src/views/bpm/form/FormModal.vue

@ -1,43 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { VFormCreate } from '@/components/FormDesign'
import { BasicModal, useModalInner } from '@/components/Modal'
import { getForm } from '@/api/bpm/form'
defineOptions({ name: 'BpmFormModal' })
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const { createMessage } = useMessage()
const formConfig = ref({
schemas: [],
rule: [],
option: {},
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
setModalProps({ confirmLoading: false })
const res = await getForm(data.record.id)
formConfig.value.schemas = res.fields
formConfig.value.option = res.conf
})
async function handleSubmit() {
try {
closeModal()
emit('success')
createMessage.success(t('common.saveSuccessText'))
}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal v-bind="$attrs" title="表单详情" @register="registerModal" @ok="handleSubmit">
<VFormCreate :form-config="formConfig" />
</BasicModal>
</template>

9
src/views/bpm/form/editor/index.vue

@ -1,9 +0,0 @@
<script lang="ts" setup>
import { VFormDesign } from '@/components/FormDesign'
</script>
<template>
<div>
<VFormDesign />
</div>
</template>

46
src/views/bpm/form/form.data.ts

@ -1,46 +0,0 @@
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE } from '@/utils/dict'
export const columns: BasicColumn[] = [
{
title: '编号',
dataIndex: 'id',
width: 100,
},
{
title: '表单名',
dataIndex: 'name',
width: 180,
},
{
title: '状态',
dataIndex: 'status',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS)
},
},
{
title: '备注',
dataIndex: 'remark',
width: 180,
},
{
title: '创建时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '表单名',
field: 'name',
component: 'Input',
colProps: { span: 8 },
},
]

86
src/views/bpm/form/index.vue

@ -1,86 +0,0 @@
<script lang="ts" setup>
import BpmFormModal from './FormModal.vue'
import { columns, searchFormSchema } from './form.data'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteForm, getFormPage } from '@/api/bpm/form'
defineOptions({ name: 'BpmForm' })
const go = useGo()
const { t } = useI18n()
const { createMessage } = useMessage()
const [registerModal, { openModal }] = useModal()
const [registerTable, { reload }] = useTable({
title: '流程表单列表',
api: getFormPage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
actionColumn: {
width: 180,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handleCreate() {
// openModal(true, { isUpdate: false })
}
function openForm(record: Recordable) {
if (typeof record.id === 'number')
go({ name: 'BpmFormEditor', query: { id: record.id } })
}
function openDetail(record: Recordable) {
openModal(true, { record })
}
async function handleDelete(record: Recordable) {
await deleteForm(record.id)
createMessage.success(t('common.delSuccessText'))
reload()
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['bpm:form:create']" type="primary" :pre-icon="IconEnum.ADD" @click="handleCreate">
{{ t('action.create') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{ icon: IconEnum.EDIT, label: t('action.edit'), auth: 'bpm:form:update', onClick: openForm.bind(null, record) },
{ icon: IconEnum.VIEW, label: t('action.detail'), auth: 'bpm:form:query', onClick: openDetail.bind(null, record) },
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.delete'),
auth: 'bpm:form:delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<BpmFormModal @register="registerModal" @success="reload()" />
</div>
</template>

58
src/views/bpm/group/GroupModal.vue

@ -1,58 +0,0 @@
<script lang="ts" setup>
import { ref, unref } from 'vue'
import { formSchema } from './group.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { createUserGroup, getUserGroup, updateUserGroup } from '@/api/bpm/userGroup'
defineOptions({ name: 'BpmGroupModal' })
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const { createMessage } = useMessage()
const isUpdate = ref(true)
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
labelWidth: 120,
baseColProps: { span: 24 },
schemas: formSchema,
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
resetFields()
setModalProps({ confirmLoading: false })
isUpdate.value = !!data?.isUpdate
if (unref(isUpdate)) {
const res = await getUserGroup(data.record.id)
setFieldsValue({ ...res })
}
})
async function handleSubmit() {
try {
const values = await validate() as any
setModalProps({ confirmLoading: true })
if (unref(isUpdate))
await updateUserGroup(values)
else
await createUserGroup(values)
closeModal()
emit('success')
createMessage.success(t('common.saveSuccessText'))
}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>

134
src/views/bpm/group/group.data.ts

@ -1,134 +0,0 @@
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { getListSimpleUsers } from '@/api/system/user'
let users: any[] = []
async function getUserList() {
const res = await getListSimpleUsers()
users = res
}
await getUserList()
export const columns: BasicColumn[] = [
{
title: '编号',
dataIndex: 'id',
width: 100,
},
{
title: '组名',
dataIndex: 'name',
width: 180,
},
{
title: '描述',
dataIndex: 'description',
width: 200,
},
{
title: '成员',
dataIndex: 'memberUserIds',
width: 180,
customRender: ({ record, text }) => {
const names: any[] = []
if (text) {
for (const userId of record.memberUserIds) {
let isUser = false
users.forEach((user) => {
if (userId === user.id) {
names.push(user.nickname)
isUser = true
}
})
if (!isUser)
names.push(`未知(${userId})`)
}
return useRender.renderTags(names)
}
},
},
{
title: '状态',
dataIndex: 'status',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS)
},
},
{
title: '创建时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '组名',
field: 'name',
component: 'Input',
colProps: { span: 8 },
},
{
label: '状态',
field: 'status',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS) as any,
},
colProps: { span: 8 },
},
{
label: '创建时间',
field: 'createTime',
component: 'RangePicker',
colProps: { span: 8 },
},
]
export const formSchema: FormSchema[] = [
{
label: '编号',
field: 'id',
show: false,
component: 'Input',
},
{
label: '组名',
field: 'name',
required: true,
component: 'Input',
},
{
label: '描述',
field: 'description',
required: true,
component: 'Input',
},
{
label: '成员',
field: 'memberUserIds',
required: true,
component: 'ApiTransfer',
componentProps: {
api: () => getListSimpleUsers(),
showSearch: true,
labelField: 'nickname',
valueField: 'id',
},
},
{
label: '状态',
field: 'status',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS),
},
},
]

78
src/views/bpm/group/index.vue

@ -1,78 +0,0 @@
<script lang="ts" setup>
import GroupModal from './GroupModal.vue'
import { columns, searchFormSchema } from './group.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteUserGroup, getUserGroupPage } from '@/api/bpm/userGroup'
defineOptions({ name: 'BpmGroup' })
const { t } = useI18n()
const { createMessage } = useMessage()
const [registerModal, { openModal }] = useModal()
const [registerTable, { reload }] = useTable({
title: '用户分组列表',
api: getUserGroupPage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handleCreate() {
openModal(true, { isUpdate: false })
}
function handleEdit(record: Recordable) {
openModal(true, { record, isUpdate: true })
}
async function handleDelete(record: Recordable) {
await deleteUserGroup(record.id)
createMessage.success(t('common.delSuccessText'))
reload()
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['bpm:user-group:create']" type="primary" :pre-icon="IconEnum.ADD" @click="handleCreate">
{{ t('action.create') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{ icon: IconEnum.EDIT, label: t('action.edit'), auth: 'bpm:user-group:update', onClick: handleEdit.bind(null, record) },
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.delete'),
auth: 'bpm:user-group:delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<GroupModal @register="registerModal" @success="reload()" />
</div>
</template>

78
src/views/bpm/model/ModelImportModal.vue

@ -1,78 +0,0 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import type { FormSchema } from '@/components/Form'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { importModel } from '@/api/bpm/model'
defineOptions({ name: 'ModelImportForm' })
const emit = defineEmits(['success', 'register'])
const formSchema: FormSchema[] = [
{
label: '流程标识',
field: 'key',
component: 'Input',
required: true,
},
{
label: '流程名称',
field: 'name',
component: 'Input',
required: true,
},
{
label: '流程描述',
field: 'description',
component: 'Input',
},
{
label: '流程文件',
field: 'bpmnFile',
component: 'FileUpload',
componentProps: {
maxCount: 1,
fileType: 'file',
},
},
]
const { t } = useI18n()
const { createMessage } = useMessage()
const [registerForm, { resetFields, validate }] = useForm({
labelWidth: 120,
baseColProps: { span: 24 },
schemas: formSchema,
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(() => {
resetFields()
setModalProps({ confirmLoading: false })
})
async function handleSubmit() {
try {
const values = await validate()
setModalProps({ confirmLoading: true })
console.info(values)
await importModel(values)
closeModal()
emit('success')
createMessage.success(t('common.saveSuccessText'))
}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal v-bind="$attrs" title="导入流程" @register="registerModal" @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>

58
src/views/bpm/model/ModelModal.vue

@ -1,58 +0,0 @@
<script lang="ts" setup>
import { ref, unref } from 'vue'
import { formSchema } from './model.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { createModel, getModel, updateModel } from '@/api/bpm/model'
defineOptions({ name: 'ModelForm' })
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const { createMessage } = useMessage()
const isUpdate = ref(true)
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
labelWidth: 120,
baseColProps: { span: 24 },
schemas: formSchema,
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
resetFields()
setModalProps({ confirmLoading: false })
isUpdate.value = !!data?.isUpdate
if (unref(isUpdate)) {
const res = await getModel(data.record.id)
setFieldsValue({ ...res })
}
})
async function handleSubmit() {
try {
const values = await validate() as any
setModalProps({ confirmLoading: true })
if (unref(isUpdate))
await updateModel(values)
else
await createModel(values)
closeModal()
emit('success')
createMessage.success(t('common.saveSuccessText'))
}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>

3
src/views/bpm/model/editor/index.vue

@ -1,3 +0,0 @@
<template>
<div>123</div>
</template>

143
src/views/bpm/model/index.vue

@ -1,143 +0,0 @@
<script lang="ts" setup>
import ModelModal from './ModelModal.vue'
import ModelImportModal from './ModelImportModal.vue'
import { columns, searchFormSchema } from './model.data'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteModel, deployModel, getModelPage } from '@/api/bpm/model'
// import { getAccessToken, getTenantId } from '@/utils/auth'
defineOptions({ name: 'BpmModel' })
const go = useGo()
const { t } = useI18n()
const { createMessage } = useMessage()
const [registerModal, { openModal }] = useModal()
const [registerImportModal, { openModal: openImportModal }] = useModal()
// const uploadParams = ref({
// 'Authorization': `Bearer ${getAccessToken()}`,
// 'tenant-id': getTenantId(),
// })
const [registerTable, { reload }] = useTable({
title: '流程模型图列表',
api: getModelPage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handleCreate() {
openModal(true, { isUpdate: false })
}
function handleEdit(record: Recordable) {
openModal(true, { record, isUpdate: true })
}
/** 设计流程 */
function handleDesign(record: Recordable) {
go({ name: 'BpmModelEditor', query: { modelId: record.id } })
}
/** 点击任务分配按钮 */
function handleAssignRule(record: Recordable) {
go({ name: 'BpmTaskAssignRuleList', query: { modelId: record.id } })
}
/** 发布流程 */
async function handleDeploy(record: Recordable) {
await deployModel(record.id)
createMessage.success(t('common.successText'))
reload()
}
/** 跳转到指定流程定义列表 */
function handleDefinitionList(record: Recordable) {
go({ name: 'BpmProcessDefinition', query: { modelId: record.key } })
}
async function handleDelete(record: Recordable) {
await deleteModel(record.id)
createMessage.success(t('common.delSuccessText'))
reload()
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['bpm:model:create']" type="primary" :pre-icon="IconEnum.ADD" @click="handleCreate">
{{ t('action.create') }}
</a-button>
<a-button v-auth="['bpm:model:import']" type="primary" :pre-icon="IconEnum.UPLOAD" @click="openImportModal">
{{ t('action.import') }}
</a-button>
<!-- <BasicUpload
:max-size="20"
:max-number="1"
:empty-hide-preview="true"
:upload-params="uploadParams"
:api="importModel"
class="my-5"
:accept="['.bpmn', '.xml']"
@change="reload"
/> -->
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[{ icon: IconEnum.EDIT, label: t('action.edit'), auth: 'bpm:model:update', onClick: handleEdit.bind(null, record) }]"
:drop-down-actions="[
{ icon: IconEnum.EDIT, label: '设计流程', auth: 'bpm:model:update', onClick: handleDesign.bind(null, record) },
{ icon: IconEnum.EDIT, label: '分配规则', auth: 'bpm:task-assign-rule:query', onClick: handleAssignRule.bind(null, record) },
{
icon: IconEnum.EDIT,
label: '发布流程',
auth: 'bpm:model:deploy',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: handleDeploy.bind(null, record),
},
},
{
icon: IconEnum.EDIT,
label: '流程定义',
auth: 'bpm:process-definition:query',
onClick: handleDefinitionList.bind(null, record),
},
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.delete'),
auth: 'bpm:model:delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<ModelModal @register="registerModal" @success="reload()" />
<ModelImportModal @register="registerImportModal" @success="reload()" />
</div>
</template>

225
src/views/bpm/model/model.data.ts

@ -1,225 +0,0 @@
import { Button, Switch } from 'ant-design-vue'
import { h } from 'vue'
import { getSimpleForms } from '@/api/bpm/form'
import { updateModelState } from '@/api/bpm/model'
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { useGo } from '@/hooks/web/usePage'
import { useMessage } from '@/hooks/web/useMessage'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
export const columns: BasicColumn[] = [
{
title: '编号',
dataIndex: 'id',
defaultHidden: true,
width: 100,
},
{
title: '流程标识',
dataIndex: 'key',
width: 180,
},
{
title: '流程名称',
dataIndex: 'name',
width: 180,
customRender: ({ record }) => {
return h(Button, { type: 'link', onClick: handleBpmnDetail.bind(null, record) }, () => record.formName)
},
},
{
title: '流程分类',
dataIndex: 'category',
width: 120,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.BPM_MODEL_CATEGORY)
},
},
{
title: '表单信息',
dataIndex: 'formType',
width: 180,
customRender: ({ record }) => {
if (record.formId)
return h(Button, { type: 'link', onClick: handleFormDetail.bind(null, record) }, () => record.formName)
else if (record.formCustomCreatePath)
return h(Button, { type: 'link', onClick: handleFormDetail.bind(null, record) }, () => record.formCustomCreatePath)
else
return useRender.renderTag('暂无表单')
},
},
{
title: '创建时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
{
title: '最新部署的流程定义',
children: [
{
title: '流程版本',
dataIndex: 'processDefinition.version',
width: 160,
customRender: ({ record }) => {
if (record.processDefinition)
return useRender.renderTag(`v${record.processDefinition.version}`)
else
return useRender.renderTag('未部署')
},
},
{
title: '激活状态',
dataIndex: 'processDefinition.suspensionState',
width: 100,
customRender: ({ record }) => {
if (record.processDefinition) {
if (!Reflect.has(record, 'suspensionState'))
record.pendingStatus = false
return h(Switch, {
checked: record.processDefinition.suspensionState === 1,
checkedChildren: '激活',
unCheckedChildren: '挂起',
onChange(checked: boolean) {
const newStatus = checked ? 0 : 1
const { createMessage } = useMessage()
updateModelState(record.id, newStatus)
.then(() => {
record.status = newStatus
createMessage.success('已成功修改流程状态')
})
.catch(() => {
createMessage.error('修改流程状态失败')
})
.finally(() => {
record.pendingStatus = false
})
},
})
}
},
},
{
title: '部署时间',
dataIndex: 'processDefinition.deploymentTim',
width: 180,
customRender: ({ record }) => {
if (record.processDefinition)
return useRender.renderDate(record.processDefinition.deploymentTime)
},
},
],
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '流程标识',
field: 'key',
component: 'Input',
colProps: { span: 8 },
},
{
label: '流程名称',
field: 'name',
component: 'Input',
colProps: { span: 8 },
},
{
label: '流程分类',
field: 'category',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY) as any,
},
colProps: { span: 8 },
},
]
export const formSchema: FormSchema[] = [
{
label: '编号',
field: 'id',
show: false,
component: 'Input',
},
{
label: '流程标识',
field: 'key',
required: true,
component: 'Input',
dynamicDisabled: ({ values }) => !!values.id,
},
{
label: '流程名称',
field: 'name',
required: true,
component: 'Input',
dynamicDisabled: ({ values }) => !!values.id,
},
{
label: '流程分类',
field: 'category',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY) as any,
},
},
{
label: '流程描述',
field: 'description',
component: 'InputTextArea',
},
{
label: '表单类型',
field: 'formType',
component: 'Select',
ifShow: ({ values }) => !!values.id,
componentProps: {
options: getDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE) as any,
},
},
{
label: '流程表单',
field: 'formId',
component: 'ApiSelect',
ifShow: ({ values }) => !!values.id && values.formType === 10,
componentProps: {
api: () => getSimpleForms(),
labelField: 'name',
valueField: 'id',
},
},
{
label: '表单提交路由',
field: 'formCustomCreatePath',
component: 'Input',
helpMessage: '自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create',
ifShow: ({ values }) => !!values.id && values.formType === 20,
},
{
label: '表单查看路由',
field: 'formCustomViewPath',
component: 'Input',
helpMessage: '自定义表单的查看路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/view',
ifShow: ({ values }) => !!values.id && values.formType === 20,
},
]
function handleBpmnDetail(record: Recordable) {
console.info('handleBpmnDetail', record)
}
function handleFormDetail(record: Recordable) {
if (record.formType === 10) {
console.info('handleFormDetail')
}
else {
const go = useGo()
go({ path: record.formCustomCreatePath })
}
}

42
src/views/bpm/oa/leave/create.vue

@ -1,42 +0,0 @@
<script lang="ts" setup>
import { onMounted } from 'vue'
import { formSchema } from './leave.data'
import { BasicForm, useForm } from '@/components/Form'
import { PageWrapper } from '@/components/Page'
import { createLeave } from '@/api/bpm/leave'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
defineOptions({ name: 'LeaveCreate' })
const { t } = useI18n()
const { createMessage } = useMessage()
const [registerForm, { resetFields, validate }] = useForm({
labelWidth: 140,
baseColProps: { span: 24 },
schemas: formSchema,
showResetButton: false,
submitButtonOptions: { text: t('common.saveText') },
actionColOptions: { span: 23 },
})
async function handleSubmit() {
try {
const values = await validate() as any
await createLeave(values)
}
finally {
createMessage.success(t('common.saveSuccessText'))
}
}
onMounted(() => {
resetFields()
})
</script>
<template>
<PageWrapper>
<BasicForm class="mt-10 h-120 w-200" @register="registerForm" @submit="handleSubmit" />
</PageWrapper>
</template>

40
src/views/bpm/oa/leave/detail.vue

@ -1,40 +0,0 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { descSchema } from './leave.data'
import { Description, useDescription } from '@/components/Description'
import { PageWrapper } from '@/components/Page'
import { getLeave } from '@/api/bpm/leave'
import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'InfraJobModal' })
const props = defineProps({
id: propTypes.number.def(undefined),
})
const { query } = useRoute()
const datas = ref()
const [registerDesc] = useDescription({
schema: descSchema,
data: datas,
})
async function getInfo() {
const queryId = query.id as unknown as number // URL id
const res = await getLeave(props.id || queryId)
datas.value = res
}
/** 初始化 */
onMounted(async () => {
await getInfo()
})
</script>
<template>
<PageWrapper>
<Description :column="1" @register="registerDesc" />
</PageWrapper>
</template>

104
src/views/bpm/oa/leave/index.vue

@ -1,104 +0,0 @@
<script lang="ts" setup>
import { columns, searchFormSchema } from './leave.data'
import { useI18n } from '@/hooks/web/useI18n'
import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { getLeavePage } from '@/api/bpm/leave'
import { useGo } from '@/hooks/web/usePage'
import { useMessage } from '@/hooks/web/useMessage'
import { cancelProcessInstance } from '@/api/bpm/processInstance'
defineOptions({ name: 'BpmLeave' })
const { t } = useI18n()
const go = useGo()
const { createMessage } = useMessage()
const [registerTable, { reload }] = useTable({
title: '请假列表',
api: getLeavePage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
actionColumn: {
width: 160,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
/** 添加操作 */
function handleCreate() {
go({ name: 'OALeaveCreate' })
}
/** 详情操作 */
function handleDetail(row) {
go({
name: 'OALeaveDetail',
query: {
id: row.id,
},
})
}
/** 取消请假操作 */
async function cancelLeave(row) {
// //
// const { value } = await ElMessageBox.prompt('', '', {
// confirmButtonText: t('common.ok'),
// cancelButtonText: t('common.cancel'),
// inputPattern: /^[\s\S]*.*\S[\s\S]*$/, //
// inputErrorMessage: '',
// })
const value = ''
//
await cancelProcessInstance(row.id, value)
createMessage.success(t('common.delSuccessText'))
reload()
}
/** 审批进度 */
function handleProcessDetail(row) {
go({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstanceId,
},
})
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button type="primary" :pre-icon="IconEnum.ADD" @click="handleCreate">
发起请假
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{ icon: IconEnum.SEARCH, label: t('action.detail'), auth: 'bpm:oa-leave:query', onClick: handleDetail.bind(null, record) },
{ icon: IconEnum.LOG, label: '进度', auth: 'bpm:oa-leave:query', onClick: handleProcessDetail.bind(null, record) },
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.cancel'),
auth: 'bpm:oa-leave:create',
ifShow: () => {
return record.result === 1
},
onClick: cancelLeave.bind(null, record),
},
]"
/>
</template>
</template>
</BasicTable>
</div>
</template>

158
src/views/bpm/oa/leave/leave.data.ts

@ -1,158 +0,0 @@
import type { DescItem } from '@/components/Description'
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
export const columns: BasicColumn[] = [
{
title: '申请编号',
dataIndex: 'id',
width: 100,
},
{
title: '状态',
dataIndex: 'result',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)
},
},
{
title: '开始时间',
dataIndex: 'startTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
{
title: '结束时间',
dataIndex: 'endTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
{
title: '请假类型',
dataIndex: 'type',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.BPM_OA_LEAVE_TYPE)
},
},
{
title: '原因',
dataIndex: 'reason',
width: 180,
},
{
title: '申请时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '请假类型',
field: 'type',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE) as any,
},
colProps: { span: 8 },
},
{
label: '申请时间',
field: 'createTime',
component: 'RangePicker',
colProps: { span: 8 },
},
{
label: '结果',
field: 'result',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT),
},
colProps: { span: 8 },
},
{
label: '原因',
field: 'reason',
component: 'Input',
colProps: { span: 8 },
},
]
export const formSchema: FormSchema[] = [
{
label: '请假类型',
field: 'type',
required: true,
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE) as any,
},
},
{
label: '开始时间',
field: 'startTime',
required: true,
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
},
},
{
label: '结束时间',
field: 'endTime',
required: true,
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
},
},
{
label: '原因',
field: 'reason',
required: true,
component: 'Input',
},
]
export const descSchema: DescItem[] = [
{
label: '请假类型',
field: 'merchantOrderId',
render: (curVal) => {
return useRender.renderTag(curVal)
},
},
{
label: '开始时间',
field: 'startTime',
render: (curVal) => {
return useRender.renderDate(curVal, 'YYYY-MM-DD')
},
},
{
label: '结束时间',
field: 'endTime',
render: (curVal) => {
return useRender.renderDate(curVal, 'YYYY-MM-DD')
},
},
{
label: '原因',
field: 'reason',
},
]

32
src/views/bpm/processInstance/create/create.data.ts

@ -1,32 +0,0 @@
import type { BasicColumn } from '@/components/Table'
import { DICT_TYPE } from '@/utils/dict'
import { useRender } from '@/components/Table'
export const columns: BasicColumn[] = [
{
title: '流程名称',
dataIndex: 'name',
width: 120,
},
{
title: '流程分类',
dataIndex: 'category',
width: 120,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.BPM_MODEL_CATEGORY)
},
},
{
title: '流程版本',
dataIndex: 'name',
width: 120,
customRender: ({ text }) => {
return useRender.renderTag(text)
},
},
{
title: '流程描述',
dataIndex: 'description',
width: 200,
},
]

115
src/views/bpm/processInstance/create/index.vue

@ -1,115 +0,0 @@
<script lang="ts" setup>
import { Card, Steps } from 'ant-design-vue'
import { ref } from 'vue'
import { columns } from './create.data'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { PageWrapper } from '@/components/Page'
import { IconEnum } from '@/enums/appEnum'
import Icon from '@/components/Icon'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { getProcessDefinitionBpmnXML, getProcessDefinitionList } from '@/api/bpm/definition'
import { createProcessInstance } from '@/api/bpm/processInstance'
defineOptions({ name: 'BpmProcessInstanceCreate' })
const go = useGo()
const { t } = useI18n()
const { createMessage } = useMessage()
const current = ref(0)
const bpmnXML = ref(null) // BPMN
const selectProcessInstance = ref() //
const [registerTable] = useTable({
api: getProcessDefinitionList,
columns,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
/** 处理选择流程的按钮操作 */
async function handleSelect(row) {
//
selectProcessInstance.value = row
//
if (row.formType === 10) {
//
// setConfAndFields2(detailForm, row.formConf, row.formFields)
//
bpmnXML.value = await getProcessDefinitionBpmnXML(row.id)
//
}
else if (row.formCustomCreatePath) {
await go({
path: row.formCustomCreatePath,
})
// Tab
}
}
/** 提交按钮 */
async function submitForm(formData) {
// if (!fApi.value || !selectProcessInstance.value)
// return
// //
// fApi.value.btn.loading(true)
try {
await createProcessInstance({
processDefinitionId: selectProcessInstance.value.id,
variables: formData,
})
//
createMessage.success('发起流程成功')
go()
}
finally {
// fApi.value.btn.loading(false)
}
}
</script>
<template>
<PageWrapper>
<div class="mx-auto my-0 mt-2.5 w-200">
<Steps :current="current">
<Steps.Step title="选择流程" />
<Steps.Step title="流程提交" />
</Steps>
</div>
<div class="m-5">
<BasicTable v-if="!selectProcessInstance" @register="registerTable">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{ icon: IconEnum.SEND, label: '选择', onClick: handleSelect.bind(null, record) },
]"
/>
</template>
</template>
</BasicTable>
<div v-if="selectProcessInstance">
<Card :title="`申请信息——【${selectProcessInstance.name}】`">
<template #extra>
<a-button type="primary" @click="selectProcessInstance = undefined">
<Icon icon="ep:delete" /> 选择其它流程
</a-button>
</template>
<p>表单</p>
<a-button type="primary" @click="submitForm">
提交
</a-button>
<p>流程图</p>
</Card>
</div>
</div>
</PageWrapper>
</template>

3
src/views/bpm/processInstance/detail/index.vue

@ -1,3 +0,0 @@
<template>
<div>123</div>
</template>

84
src/views/bpm/processInstance/index.vue

@ -1,84 +0,0 @@
<script lang="ts" setup>
import { columns, searchFormSchema } from './processInstance.data'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { IconEnum } from '@/enums/appEnum'
import { cancelProcessInstance, getMyProcessInstancePage } from '@/api/bpm/processInstance'
defineOptions({ name: 'InfraApiErrorLog' })
const go = useGo()
const { t } = useI18n()
const { createMessage } = useMessage()
const [registerTable, { reload }] = useTable({
title: '我的流程列表',
api: getMyProcessInstancePage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
/** 发起流程操作 */
function handleCreate() {
go({ name: 'BpmProcessInstanceCreate' })
}
/** 查看详情 */
function handleDetail(record: Recordable) {
go({ name: 'BpmProcessInstanceDetail', query: { id: record.id } })
}
/** 取消按钮操作 */
async function handleCancel(record: Recordable) {
await cancelProcessInstance(record.id, 'TODO')
createMessage.success('取消成功')
reload()
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['bpm:process-instance:query']" :pre-icon="IconEnum.ADD" @click="handleCreate">
发起流程
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: IconEnum.VIEW,
label: t('action.detail'),
auth: 'bpm:process-instance:query',
onClick: handleDetail.bind(null, record),
},
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.cancel'),
ifShow: record.result === 1,
auth: 'bpm:process-instance:cancel',
popConfirm: {
title: t('action.cancel'),
placement: 'left',
confirm: handleCancel.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
</div>
</template>

118
src/views/bpm/processInstance/processInstance.data.ts

@ -1,118 +0,0 @@
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
export const columns: BasicColumn[] = [
{
title: '流程编号',
dataIndex: 'id',
width: 260,
},
{
title: '流程名称',
dataIndex: 'name',
width: 100,
},
{
title: '流程分类',
dataIndex: 'category',
width: 120,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.BPM_MODEL_CATEGORY)
},
},
{
title: '当前审批任务',
dataIndex: 'tasks',
width: 120,
customRender: ({ record }) => {
if (record.tasks && record.tasks.length > 0) {
const texts: any[] = []
record.tasks.forEach((val) => {
texts.push(val.name)
})
return useRender.renderTags(texts)
}
},
},
{
title: '状态',
dataIndex: 'status',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)
},
},
{
title: '结果',
dataIndex: 'result',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)
},
},
{
title: '提交时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
{
title: '结束时间',
dataIndex: 'endTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '流程名称',
field: 'name',
component: 'Input',
colProps: { span: 8 },
},
{
label: '所属流程',
field: 'processDefinitionId',
component: 'Input',
colProps: { span: 8 },
},
{
label: '流程分类',
field: 'category',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY) as any,
},
colProps: { span: 8 },
},
{
label: '状态',
field: 'status',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS),
},
colProps: { span: 8 },
},
{
label: '结果',
field: 'result',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT),
},
colProps: { span: 8 },
},
{
label: '提交时间',
field: 'createTime',
component: 'RangePicker',
colProps: { span: 8 },
},
]

79
src/views/bpm/task/done/done.data.ts

@ -1,79 +0,0 @@
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { getDate } from '@/utils/dateUtil'
import { DICT_TYPE } from '@/utils/dict'
export const columns: BasicColumn[] = [
{
title: '任务编号',
dataIndex: 'id',
width: 100,
},
{
title: '任务名称',
dataIndex: 'name',
width: 180,
},
{
title: '所属流程',
dataIndex: 'processInstance.name',
width: 180,
},
{
title: '流程发起人',
dataIndex: 'processInstance.startUserNickname',
width: 180,
},
{
title: '结果',
dataIndex: 'result',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)
},
},
{
title: '审批意见',
dataIndex: 'reason',
width: 180,
},
{
title: '创建时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
{
title: '审批时间',
dataIndex: 'endTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
{
title: '耗时',
dataIndex: 'durationInMillis',
width: 180,
customRender: ({ text }) => {
return getDate(text)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '流程名',
field: 'name',
component: 'Input',
colProps: { span: 8 },
},
{
label: '创建时间',
field: 'createTime',
component: 'RangePicker',
colProps: { span: 8 },
},
]

53
src/views/bpm/task/done/index.vue

@ -1,53 +0,0 @@
<script lang="ts" setup>
import { columns, searchFormSchema } from './done.data'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { IconEnum } from '@/enums/appEnum'
import { getDoneTaskPage } from '@/api/bpm/task'
defineOptions({ name: 'BpmDoneTask' })
const go = useGo()
const { t } = useI18n()
const [registerTable] = useTable({
title: '审批列表',
api: getDoneTaskPage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function openDetail(record: Recordable) {
console.info(record)
}
function handleAudit(record: Recordable) {
go({ name: 'BpmProcessInstanceDetail', query: { id: record.id } })
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{ icon: IconEnum.VIEW, label: t('action.detail'), onClick: openDetail.bind(null, record) },
{ icon: IconEnum.VIEW, label: '流程', onClick: handleAudit.bind(null, record) },
]"
/>
</template>
</template>
</BasicTable>
</div>
</template>

44
src/views/bpm/task/todo/index.vue

@ -1,44 +0,0 @@
<script lang="ts" setup>
import { columns, searchFormSchema } from './todo.data'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { IconEnum } from '@/enums/appEnum'
import { getTodoTaskPage } from '@/api/bpm/task'
defineOptions({ name: 'BpmTodoTask' })
const go = useGo()
const { t } = useI18n()
const [registerTable] = useTable({
title: '审批列表',
api: getTodoTaskPage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handleAudit(record: Recordable) {
go({ name: 'BpmProcessInstanceDetail', query: { id: record.id } })
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction :actions="[{ icon: IconEnum.VIEW, label: '审批进度', onClick: handleAudit.bind(null, record) }]" />
</template>
</template>
</BasicTable>
</div>
</template>

59
src/views/bpm/task/todo/todo.data.ts

@ -1,59 +0,0 @@
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
export const columns: BasicColumn[] = [
{
title: '任务编号',
dataIndex: 'id',
width: 100,
},
{
title: '任务名称',
dataIndex: 'name',
width: 180,
},
{
title: '所属流程',
dataIndex: 'processInstance.name',
width: 180,
},
{
title: '流程发起人',
dataIndex: 'processInstance.startUserNickname',
width: 180,
},
{
title: '创建时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
{
title: '状态',
dataIndex: 'suspensionState',
width: 180,
customRender: ({ text }) => {
if (text === 1)
return useRender.renderTag('激活', 'success')
else if (text === 2)
return useRender.renderTag('挂起', 'warning')
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '流程名',
field: 'name',
component: 'Input',
colProps: { span: 8 },
},
{
label: '创建时间',
field: 'createTime',
component: 'RangePicker',
colProps: { span: 8 },
},
]

3
src/views/bpm/taskAssignRule/index.vue

@ -1,3 +0,0 @@
<template>
<div>开发中 taskAssignRule</div>
</template>

25
src/views/infra/apiAccessLog/AccessLogModal.vue

@ -1,25 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { infoSchema } from './apiAccessLog.data'
import { BasicModal, useModalInner } from '@/components/Modal'
import { Description, useDescription } from '@/components/Description/index'
defineOptions({ name: 'AcessLogModal' })
const logData = ref()
const [registerModalInner, { closeModal }] = useModalInner((record: Recordable) => {
logData.value = record
})
const [registerDescription] = useDescription({
column: 1,
schema: infoSchema,
data: logData,
})
</script>
<template>
<BasicModal v-bind="$attrs" title="访问日志详情" width="800px" @register="registerModalInner" @ok="closeModal">
<Description @register="registerDescription" />
</BasicModal>
</template>

222
src/views/infra/apiAccessLog/apiAccessLog.data.ts

@ -1,222 +0,0 @@
import { h } from 'vue'
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import type { DescItem } from '@/components/Description/index'
export const columns: BasicColumn[] = [
{
title: '日志编号',
dataIndex: 'id',
width: 100,
},
{
title: '用户编号',
dataIndex: 'userId',
width: 100,
},
{
title: '用户类型',
dataIndex: 'userType',
width: 120,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.USER_TYPE)
},
},
{
title: '应用名',
dataIndex: 'applicationName',
width: 120,
},
{
title: '请求方法名',
dataIndex: 'requestMethod',
width: 120,
},
{
title: '请求地址',
dataIndex: 'requestUrl',
width: 250,
},
{
title: '请求时间',
dataIndex: 'beginTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
{
title: '执行时长',
dataIndex: 'duration',
width: 180,
customRender: ({ text }) => {
return useRender.renderText(text.toString(), 'ms')
},
},
{
title: '操作结果',
dataIndex: 'status',
width: 180,
ellipsis: true,
customRender: ({ record }) => {
const success = record.resultCode === 0
return useRender.renderTag(success ? '成功' : '失败', success ? '#87d068' : '#f50')
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '用户编号',
field: 'userId',
component: 'Input',
colProps: { span: 8 },
},
{
label: '用户类型',
field: 'userType',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.USER_TYPE) as any,
},
colProps: { span: 8 },
},
{
label: '应用名',
field: 'applicationName',
component: 'Input',
colProps: { span: 8 },
},
{
label: '请求地址',
field: 'requestUrl',
component: 'Input',
colProps: { span: 8 },
},
{
label: '请求时间',
field: 'beginTime',
component: 'RangePicker',
colProps: { span: 8 },
},
{
label: '执行时长',
field: 'duration',
component: 'Input',
colProps: { span: 8 },
},
{
label: '结果码',
field: 'resultCode',
component: 'Input',
colProps: { span: 8 },
},
]
const httpMethods = [
{ value: 'GET', color: '#108ee9' },
{ value: 'POST', color: '#2db7f5' },
{ value: 'PUT', color: 'warning' },
{ value: 'DELETE', color: '#f50' },
]
export const infoSchema: DescItem[] = [
{
label: '日志id',
field: 'id',
},
{
label: '链路id',
field: 'traceId',
show: data => data && data.traceId && data.traceId !== '',
},
{
label: '应用名称',
field: 'applicationName',
labelMinWidth: 100,
},
{
field: 'userId',
label: '用户id',
render(value, data) {
const tag = useRender.renderDict(data.userType, DICT_TYPE.USER_TYPE)
const uidTag = useRender.renderTag(`uid: ${value}`)
return h('span', {}, [tag, uidTag])
},
},
{
field: 'resultCode',
label: '请求结果',
render(value) {
return useRender.renderTag(value === 0 ? '成功' : '失败', value === 0 ? '#87d068' : '#f50')
},
},
{
field: 'resultMsg',
label: '响应信息',
show(data) {
return data && data.resultMsg && data.resultMsg !== ''
},
render(value) {
return h('span', { style: { color: 'red', fontWeight: 'bold' } }, value)
},
},
{
field: 'userIp',
label: '请求ip',
},
{
field: 'userAgent',
label: 'userAgent',
},
{
field: 'beginTime',
label: '请求时间',
render(value) {
return useRender.renderDate(value)
},
},
{
field: 'requestUrl',
label: '请求路径',
render(_, data) {
if (!data)
return ''
const { requestMethod, requestUrl } = data
const current = httpMethods.find(item => item.value === requestMethod.toUpperCase())
const methodTag = current ? useRender.renderTag(requestMethod, current.color) : requestMethod
return h('span', {}, [methodTag, requestUrl])
},
},
{
field: 'requestParams',
label: '请求参数',
render(value) {
return useRender.renderJsonPreview(value)
},
},
{
field: 'beginTime',
label: '请求开始时间',
render(value) {
return useRender.renderDate(value)
},
},
{
field: 'endTime',
label: '请求结束时间',
render(value) {
return useRender.renderDate(value)
},
},
{
field: 'duration',
label: '请求耗时',
render(value) {
// 为0的话需要转为string 否则不会显示
return useRender.renderText(String(value), 'ms')
},
},
]

74
src/views/infra/apiAccessLog/index.vue

@ -1,74 +0,0 @@
<script lang="ts" setup>
import { columns, searchFormSchema } from './apiAccessLog.data'
import AccessLogModal from './AccessLogModal.vue'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { IconEnum } from '@/enums/appEnum'
import type { ApiAccessLogExportReqVO } from '@/api/infra/apiAccessLog'
import { exportApiAccessLog, getApiAccessLogPage } from '@/api/infra/apiAccessLog'
import { useModal } from '@/components/Modal'
defineOptions({ name: 'InfraApiErrorLog' })
const { t } = useI18n()
const { createConfirm, createMessage } = useMessage()
const [registerTable, { getForm }] = useTable({
title: '访问日志列表',
api: getApiAccessLogPage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
showIndexColumn: false,
actionColumn: {
width: 120,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
const [registerModal, { openModal }] = useModal()
function handleShowInfo(record: Recordable) {
openModal(true, record)
}
async function handleExport() {
createConfirm({
title: t('common.exportTitle'),
iconType: 'warning',
content: t('common.exportMessage'),
async onOk() {
await exportApiAccessLog(getForm().getFieldsValue() as ApiAccessLogExportReqVO)
createMessage.success(t('common.exportSuccessText'))
},
})
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['infra:api-access-log:export']" :pre-icon="IconEnum.EXPORT" @click="handleExport">
{{ t('action.export') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: IconEnum.VIEW,
label: t('action.detail'),
onClick: handleShowInfo.bind(null, record),
},
]"
/>
</template>
</template>
</BasicTable>
<AccessLogModal @register="registerModal" />
</div>
</template>

25
src/views/infra/apiErrorLog/ErrorLogModal.vue

@ -1,25 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { infoSchema } from './apiErrorLog.data'
import { BasicModal, useModalInner } from '@/components/Modal'
import { Description, useDescription } from '@/components/Description/index'
defineOptions({ name: 'ErrorLogModal' })
const logData = ref()
const [registerModalInner, { closeModal }] = useModalInner((record: Recordable) => {
logData.value = record
})
const [registerDescription] = useDescription({
column: 1,
schema: infoSchema,
data: logData,
})
</script>
<template>
<BasicModal v-bind="$attrs" title="错误日志详情" width="800px" @register="registerModalInner" @ok="closeModal">
<Description @register="registerDescription" />
</BasicModal>
</template>

248
src/views/infra/apiErrorLog/apiErrorLog.data.ts

@ -1,248 +0,0 @@
import { Textarea } from 'ant-design-vue'
import { h } from 'vue'
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import type { DescItem } from '@/components/Description/index'
export const columns: BasicColumn[] = [
{
title: '日志编号',
dataIndex: 'id',
width: 100,
},
{
title: '用户编号',
dataIndex: 'userId',
width: 100,
},
{
title: '用户类型',
dataIndex: 'userType',
width: 120,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.USER_TYPE)
},
},
{
title: '应用名',
dataIndex: 'applicationName',
width: 120,
},
{
title: '请求方法名',
dataIndex: 'requestMethod',
width: 120,
},
{
title: '请求地址',
dataIndex: 'requestUrl',
width: 250,
},
{
title: '异常发生时间',
dataIndex: 'exceptionTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
{
title: '异常名',
dataIndex: 'exceptionName',
width: 250,
},
{
title: '处理状态',
dataIndex: 'processStatus',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '用户编号',
field: 'userId',
component: 'Input',
colProps: { span: 8 },
},
{
label: '用户名称',
field: 'username',
component: 'Input',
colProps: { span: 8 },
},
{
label: '用户类型',
field: 'userType',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.USER_TYPE) as any,
},
colProps: { span: 8 },
},
{
label: '应用名',
field: 'applicationName',
component: 'Input',
colProps: { span: 8 },
},
{
label: '请求地址',
field: 'requestUrl',
component: 'Input',
colProps: { span: 8 },
},
{
label: '异常时间',
field: 'exceptionTime',
component: 'RangePicker',
colProps: { span: 8 },
},
{
label: '处理状态',
field: 'processStatus',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS),
},
colProps: { span: 8 },
},
]
function renderText(value: string, color: string, bold = true) {
return h('span', { style: { color, fontWeight: bold ? 'bold' : 'normal' } }, value)
}
const httpMethods = [
{ value: 'GET', color: '#108ee9' },
{ value: 'POST', color: '#2db7f5' },
{ value: 'PUT', color: 'warning' },
{ value: 'DELETE', color: '#f50' },
]
export const infoSchema: DescItem[] = [
{
field: 'id',
label: '异常id',
},
{
field: 'traceId',
label: '链路ID',
show(data) {
return data && data.traceId && data.traceId !== ''
},
},
{
field: 'applicationName',
label: '应用名称',
labelMinWidth: 100,
},
{
field: 'processStatus',
label: '处理状态',
render(_, data) {
const { processStatus, processUserId } = data
const tag = useRender.renderDict(processStatus, DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)
if (!processUserId)
return tag
const uidTag = useRender.renderTag(`uid: ${processUserId}`)
return h('span', {}, [tag, uidTag])
},
},
{
field: 'processTime',
label: '处理时间',
show(data) {
return data && data.processTime && data.processTime !== ''
},
render(value) {
return useRender.renderDate(value)
},
},
{
field: 'userId',
label: '用户id',
render(value, data) {
const tag = useRender.renderDict(data.userType, DICT_TYPE.USER_TYPE)
const uidTag = useRender.renderTag(`uid: ${value}`)
return h('span', {}, [tag, uidTag])
},
},
{
field: 'userIp',
label: 'ip地址',
},
{
field: 'requestUrl',
label: '请求地址',
render(_, data) {
if (data) {
const { requestMethod } = data
const current = httpMethods.find(item => item.value === requestMethod)
const tag = current ? useRender.renderTag(requestMethod, current.color) : requestMethod
return h('span', {}, [tag, data.requestUrl])
}
},
},
{
field: 'requestParams',
label: '请求参数',
render(value) {
return useRender.renderJsonPreview(value)
},
},
{
field: 'userAgent',
label: 'userAgent',
},
{
field: 'exceptionTime',
label: '异常时间',
render(value) {
return useRender.renderDate(value)
},
},
{
field: 'exceptionClassName',
label: '异常类名/方法',
render(_, data) {
if (data)
return renderText(`${data.exceptionClassName} / ${data.exceptionMethodName}`, 'red')
},
},
{
field: 'exceptionMessage',
label: '异常信息',
render(value) {
return renderText(value, 'red')
},
},
{
field: 'exceptionFileName',
label: '异常文件名',
render(_, data) {
if (data)
return useRender.renderText(data.exceptionFileName, `Line: ${data.exceptionLineNumber}`)
},
},
{
field: 'exceptionName',
label: '异常名称',
},
{
field: 'exceptionRootCauseMessage',
label: '异常信息',
},
{
field: 'exceptionStackTrace',
label: '异常堆栈',
render(value) {
return h(Textarea, { value, readonly: true, style: { minHeight: '300px' } })
},
},
]

101
src/views/infra/apiErrorLog/index.vue

@ -1,101 +0,0 @@
<script lang="ts" setup>
import { columns, searchFormSchema } from './apiErrorLog.data'
import ErrorLogModal from './ErrorLogModal.vue'
import { useI18n } from '@/hooks/web/useI18n'
import { IconEnum } from '@/enums/appEnum'
import { InfraApiErrorLogProcessStatusEnum } from '@/enums/systemEnum'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import type { ApiErrorLogExportReqVO } from '@/api/infra/apiErrorLog'
import { exportApiErrorLog, getApiErrorLogPage, updateApiErrorLogProcess } from '@/api/infra/apiErrorLog'
import { useModal } from '@/components/Modal'
defineOptions({ name: 'InfraApiErrorLog' })
const { t } = useI18n()
const { createConfirm, createMessage } = useMessage()
const [registerTable, { getForm, reload }] = useTable({
title: '异常日志列表',
api: getApiErrorLogPage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
showIndexColumn: false,
actionColumn: {
width: 220,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
const [registerModal, { openModal }] = useModal()
function handleShowInfo(record: Recordable) {
openModal(true, record)
}
function handleProcessClick(record, processStatus: number, type: string) {
createConfirm({
iconType: 'warning',
content: `确认标记为${type}?`,
async onOk() {
await updateApiErrorLogProcess(record.id, processStatus)
createMessage.success(t('common.successText'))
reload()
},
})
}
async function handleExport() {
createConfirm({
title: t('common.exportTitle'),
iconType: 'warning',
content: t('common.exportMessage'),
async onOk() {
await exportApiErrorLog(getForm().getFieldsValue() as ApiErrorLogExportReqVO)
createMessage.success(t('common.exportSuccessText'))
},
})
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['infra:api-error-log:export']" :pre-icon="IconEnum.EXPORT" @click="handleExport">
{{ t('action.export') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: IconEnum.VIEW,
label: t('action.detail'),
onClick: handleShowInfo.bind(null, record),
},
{
icon: IconEnum.EDIT,
label: '已处理',
auth: 'infra:api-error-log:update-status',
ifShow: () => record.processStatus === InfraApiErrorLogProcessStatusEnum.INIT,
onClick: handleProcessClick.bind(null, record, InfraApiErrorLogProcessStatusEnum.DONE, '已处理'),
},
{
icon: IconEnum.EDIT,
label: '已忽略',
auth: 'infra:api-error-log:update-status',
ifShow: () => record.processStatus === InfraApiErrorLogProcessStatusEnum.INIT,
onClick: handleProcessClick.bind(null, record, InfraApiErrorLogProcessStatusEnum.IGNORE, '已忽略'),
},
]"
/>
</template>
</template>
</BasicTable>
<ErrorLogModal @register="registerModal" />
</div>
</template>

10
src/views/infra/build/index.vue

@ -1,10 +0,0 @@
<script lang="ts" setup>
import { PageWrapper } from '@/components/Page'
import { VFormDesign } from '@/components/FormDesign'
</script>
<template>
<PageWrapper dense fixed-height content-full-height>
<VFormDesign />
</PageWrapper>
</template>

96
src/views/infra/codegen/EditTable.vue

@ -1,96 +0,0 @@
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue'
import { Steps } from 'ant-design-vue'
import { useRoute } from 'vue-router'
import BasicInfoForm from './components/BasicInfoForm.vue'
import CloumInfoForm from './components/CloumInfoForm.vue'
import FinishForm from './components/FinishForm.vue'
import { PageWrapper } from '@/components/Page'
import { getCodegenTable, updateCodegenTable } from '@/api/infra/codegen'
const Step = Steps.Step
const { query } = useRoute()
//
const basicInfo = ref<any>()
//
const columnsInfo = ref<any[]>([])
const basicInfoValue = ref()
const columnsInfoValue = ref()
const current = ref(0)
const state = reactive({
initSetp2: false,
initSetp3: false,
})
function handleStep1Next(step1Values: any) {
current.value++
basicInfoValue.value = step1Values
state.initSetp2 = true
}
function handleStepPrev() {
current.value--
}
async function handleStep2Next(step2Values: any) {
current.value++
columnsInfoValue.value = step2Values
await handleSubmit()
state.initSetp3 = true
}
async function handleSubmit() {
basicInfoValue.value.id = query.id as unknown as number
const genTable = {
table: basicInfoValue.value,
columns: columnsInfoValue.value,
}
await updateCodegenTable(genTable)
}
function handleRedo() {
current.value = 0
state.initSetp2 = false
state.initSetp3 = false
}
async function getList() {
const tableId = query.id as unknown as number
const res = await getCodegenTable(tableId)
basicInfo.value = res.table
columnsInfo.value = res.columns
}
onMounted(async () => {
await getList()
})
</script>
<template>
<PageWrapper>
<div class="mx-auto my-0 mt-2.5 w-200">
<Steps :current="current">
<Step title="生成信息" />
<Step title="字段信息" />
<Step title="完成" />
</Steps>
</div>
<div class="m-5">
<BasicInfoForm v-show="current === 0" :basic-info="basicInfo" @next="handleStep1Next" />
<CloumInfoForm
v-show="current === 1"
v-if="state.initSetp2"
:columns-info="columnsInfo"
@prev="handleStepPrev"
@next="handleStep2Next"
/>
<FinishForm v-show="current === 2" v-if="state.initSetp3" @redo="handleRedo" />
</div>
</PageWrapper>
</template>

157
src/views/infra/codegen/codegen.data.ts

@ -1,157 +0,0 @@
import { getDataSourceConfigList } from '@/api/infra/dataSourceConfig'
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
const dataSourceConfigs = await getDataSourceConfigList()
export const columns: BasicColumn[] = [
{
title: '数据源',
dataIndex: 'dataSourceConfigId',
width: 100,
customRender: ({ text }) => {
for (const config of dataSourceConfigs) {
if (text === config.id)
return config.name
}
return `未知【${text}`
},
},
{
title: '表名称',
dataIndex: 'tableName',
width: 200,
},
{
title: '表描述',
dataIndex: 'tableComment',
width: 120,
},
{
title: '实体',
dataIndex: 'className',
width: 200,
},
{
title: '创建时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
{
title: '更新时间',
dataIndex: 'updateTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '表名称',
field: 'tableName',
component: 'Input',
colProps: { span: 8 },
},
{
label: '表描述',
field: 'tableComment',
component: 'Input',
colProps: { span: 8 },
},
{
label: '创建时间',
field: 'createTime',
component: 'RangePicker',
colProps: { span: 8 },
},
]
export const formSchema: FormSchema[] = [
{
label: '编号',
field: 'id',
show: false,
component: 'Input',
},
{
label: '岗位名称',
field: 'name',
required: true,
component: 'Input',
},
{
label: '岗位编码',
field: 'code',
required: true,
component: 'Input',
},
{
label: '岗位顺序',
field: 'sort',
required: true,
defaultValue: 0,
component: 'InputNumber',
},
{
label: '状态',
field: 'status',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS) as any,
},
},
{
label: '备注',
field: 'remark',
component: 'InputTextArea',
},
]
export const importTableColumns: BasicColumn[] = [
{
title: '表名称',
dataIndex: 'name',
width: 200,
},
{
title: '表描述',
dataIndex: 'comment',
width: 120,
},
]
export const importTableSearchFormSchema: FormSchema[] = [
{
label: '数据源',
field: 'dataSourceConfigId',
component: 'Select',
required: true,
defaultValue: dataSourceConfigs[0].id,
componentProps: {
options: dataSourceConfigs,
fieldNames: {
label: 'name',
value: 'id',
},
},
colProps: { span: 8 },
},
{
label: '表名称',
field: 'name',
component: 'Input',
colProps: { span: 8 },
},
{
label: '表描述',
field: 'comment',
component: 'Input',
colProps: { span: 8 },
},
]

70
src/views/infra/codegen/components/BasicInfoForm.vue

@ -1,70 +0,0 @@
<script lang="ts" setup>
import { Divider } from 'ant-design-vue'
import { watch } from 'vue'
import { basicInfoSchemas } from './data'
import { BasicForm, useForm } from '@/components/Form'
import type { CodegenTableVO } from '@/api/infra/codegen/types'
const props = defineProps({
basicInfo: {
type: Object as PropType<Nullable<CodegenTableVO>>,
default: () => null,
},
})
const emit = defineEmits(['next'])
const [register, { setFieldsValue, resetFields, validate }] = useForm({
labelWidth: 120,
schemas: basicInfoSchemas,
actionColOptions: {
span: 14,
},
showResetButton: false,
submitButtonOptions: {
text: '保存',
},
submitFunc: customSubmitFunc,
})
async function customSubmitFunc() {
try {
const values = await validate()
emit('next', values)
}
catch (error) {}
}
watch(
() => props.basicInfo,
(basicInfo) => {
if (!basicInfo)
return
resetFields()
setFieldsValue({ ...basicInfo })
},
{
deep: true,
immediate: true,
},
)
</script>
<template>
<div>
<div class="mx-auto my-0 w-80%">
<BasicForm @register="register" />
</div>
<Divider />
<h3 class="mb-3 text-base">
说明
</h3>
<h4 class="mb-1 text-sm">
基本信息
</h4>
<p> 配置生成的基本信息 </p>
<h4>生成信息</h4>
<p> 配置生成生成的详细信息 </p>
</div>
</template>

62
src/views/infra/codegen/components/CloumInfoForm.vue

@ -1,62 +0,0 @@
<script lang="ts" setup>
import { Divider } from 'ant-design-vue'
import { columns } from './data'
import type { EditRecordRow } from '@/components/Table'
import { BasicTable, useTable } from '@/components/Table'
import type { CodegenColumnVO } from '@/api/infra/codegen/types'
defineProps({
columnsInfo: {
type: Array as PropType<CodegenColumnVO[]>,
default: () => null,
},
})
const emit = defineEmits(['next', 'prev'])
const [registerTable, { getDataSource }] = useTable({
columns,
maxHeight: 700,
pagination: false,
useSearchForm: false,
showTableSetting: false,
showIndexColumn: false,
})
async function customResetFunc() {
emit('prev')
}
async function customSubmitFunc() {
const tableValue = getDataSource()
emit('next', tableValue)
}
function handleEdit(record: EditRecordRow) {
record.onEdit?.(true)
}
</script>
<template>
<div class="step2">
<div class="mx-auto my-0 w-full">
<BasicTable :data-source="columnsInfo" @register="registerTable" @row-click="handleEdit" />
</div>
<Divider />
<div class="flex justify-center">
<a-button @click="customResetFunc">
上一步
</a-button>
<a-button type="primary" @click="customSubmitFunc">
提交
</a-button>
</div>
<h3 class="mb-3 text-base">
说明
</h3>
<h4 class="mb-1 text-sm">
配置字段
</h4>
<p> 配置表的字段类型增删改查字典等 </p>
</div>
</template>

55
src/views/infra/codegen/components/FinishForm.vue

@ -1,55 +0,0 @@
<script lang="ts" setup>
import { Result } from 'ant-design-vue'
import { useRoute } from 'vue-router'
import PreviewModal from './PreviewModal.vue'
import { useModal } from '@/components/Modal'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import { useTabs } from '@/hooks/web/useTabs'
import { useMessage } from '@/hooks/web/useMessage'
import { downloadCodegen, getCodegenTable } from '@/api/infra/codegen'
const go = useGo()
const { closeCurrent } = useTabs()
const { query } = useRoute()
const { t } = useI18n()
const { createMessage } = useMessage()
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal()
function handlePreview() {
const tableId = query.id as unknown as number
const record = { id: tableId }
openPreviewModal(true, { record })
}
async function handleGenTable() {
const tableId = query.id as unknown as number
const res = await getCodegenTable(tableId)
await downloadCodegen(res.table)
createMessage.success(t('common.successText'))
}
function handleGoList() {
closeCurrent()
go('/infra/codegen')
}
</script>
<template>
<div class="m-5 bg-white px-8 py-12 dark:bg-dark">
<Result status="success" title="代码生成成功" sub-title="可点击下方按钮预览下载或返回列表页">
<template #extra>
<a-button key="console" type="primary" @click="handleGoList">
返回列表
</a-button>
<a-button key="preview" @click="handlePreview">
预览
</a-button>
<a-button key="download" @click="handleGenTable">
生成
</a-button>
</template>
</Result>
<PreviewModal @register="registerPreviewModal" />
</div>
</template>

43
src/views/infra/codegen/components/ImportTableModal.vue

@ -1,43 +0,0 @@
<script lang="ts" setup>
import { importTableColumns, importTableSearchFormSchema } from '../codegen.data'
import { BasicModal, useModalInner } from '@/components/Modal'
import { BasicTable, useTable } from '@/components/Table'
import { createCodegenList, getSchemaTableList } from '@/api/infra/codegen'
defineOptions({ name: 'InfraImportTableModal' })
const emit = defineEmits(['success', 'register'])
const [registerTable, { getSelectRowKeys, getForm }] = useTable({
api: getSchemaTableList,
columns: importTableColumns,
formConfig: {
labelWidth: 80,
schemas: importTableSearchFormSchema,
},
rowSelection: { type: 'checkbox' },
rowKey: 'name',
useSearchForm: true,
pagination: false,
showTableSetting: false,
showIndexColumn: false,
immediate: false,
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async () => {
setModalProps({ confirmLoading: false })
})
async function handleSubmit() {
const datas = await getSelectRowKeys()
const form = await getForm()
await createCodegenList({ dataSourceConfigId: form.getFieldsValue().dataSourceConfigId, tableNames: datas })
closeModal()
emit('success')
}
</script>
<template>
<BasicModal v-bind="$attrs" :width="800" title="导入" @register="registerModal" @ok="handleSubmit">
<BasicTable @register="registerTable" />
</BasicModal>
</template>

144
src/views/infra/codegen/components/PreviewModal.vue

@ -1,144 +0,0 @@
<script lang="ts" setup>
import { ref, unref } from 'vue'
import { Card, Tabs } from 'ant-design-vue'
import { useClipboard } from '@vueuse/core'
import { BasicTree } from '@/components/Tree'
import { BasicModal, useModalInner } from '@/components/Modal'
import { CodeEditor, MODE } from '@/components/CodeEditor'
import { previewCodegen } from '@/api/infra/codegen'
import type { CodegenPreviewVO } from '@/api/infra/codegen/types'
import { handleTree2 } from '@/utils/tree'
import { useMessage } from '@/hooks/web/useMessage'
defineOptions({ name: 'InfraPreviewModal' })
const { createMessage } = useMessage()
const fileTree = ref([])
const activeKey = ref('')
const modeValue = ref(MODE.JS)
const previewCodes = ref<CodegenPreviewVO[]>()
const [registerModal, { setModalProps }] = useModalInner(async (data) => {
setModalProps({ confirmLoading: false })
const res = await previewCodegen(data.record.id)
const file = handleFiles(res)
previewCodes.value = res
activeKey.value = res[0].filePath
fileTree.value = handleTree2(file, 'id', 'parentId', 'children', '/')
})
function handleSelect(keys) {
activeKey.value = keys[0]
}
/** 生成 files 目录 */
interface filesType {
id: string
label: string
parentId: string
}
function handleFiles(datas) {
const exists = {} // keyfile idvaluetrue
const files: filesType[] = []
//
for (const data of datas) {
let paths = data.filePath.split('/')
let fullPath = '' // id
// java
if (paths[paths.length - 1].includes('.java')) {
const newPaths: string[] = []
for (let i = 0; i < paths.length; i++) {
let path = paths[i]
if (path !== 'java') {
newPaths.push(path)
continue
}
newPaths.push(path)
// package
let tmp = ''
while (i < paths.length) {
path = paths[i + 1]
if (
path === 'controller'
|| path === 'convert'
|| path === 'dal'
|| path === 'enums'
|| path === 'service'
|| path === 'vo' //
|| path === 'mysql'
|| path === 'dataobject'
)
break
tmp = tmp ? `${tmp}.${path}` : path
i++
}
if (tmp)
newPaths.push(tmp)
}
paths = newPaths
}
// path
for (let i = 0; i < paths.length; i++) {
// files
const oldFullPath = fullPath
// replaceAll tabs replaceAll
fullPath = fullPath.length === 0 ? paths[i] : `${fullPath.replaceAll('.', '/')}/${paths[i]}`
if (exists[fullPath])
continue
// files
exists[fullPath] = true
files.push({
id: fullPath,
label: paths[i],
parentId: oldFullPath || '/', // "/"
})
}
}
return files
}
/** 复制 */
async function copy(text: string) {
const { copy, copied, isSupported } = useClipboard({ source: text })
if (!isSupported) {
createMessage.error('复制失败')
}
else {
await copy()
if (unref(copied))
createMessage.success('复制成功')
}
}
</script>
<template>
<BasicModal v-bind="$attrs" :default-fullscreen="true" title="预览代码" @register="registerModal">
<div class="flex">
<Card class="min-w-130 w-1/4">
<BasicTree
title="文件夹列表" toolbar :default-expand-all="true" tree-wrapper-class-name="h-[800px] overflow-auto"
:click-row-to-expand="false" :tree-data="fileTree" :field-names="{ key: 'id', title: 'label' }"
@select="handleSelect"
/>
</Card>
<Card class="w-3/4">
<Tabs v-model:activeKey="activeKey">
<Tabs.TabPane
v-for="item in previewCodes" :key="item.filePath"
:tab="item.filePath.substring(item.filePath.lastIndexOf('/') + 1)"
>
<a-button type="link" style="float: right; padding: 4px 60px;" @click="copy(item.code)">
复制
</a-button>
<CodeEditor class="max-h-200" :value="item.code" :mode="modeValue" :readonly="true" />
</Tabs.TabPane>
</Tabs>
</Card>
</div>
</BasicModal>
</template>

328
src/views/infra/codegen/components/data.ts

@ -1,328 +0,0 @@
import { listSimpleDictType } from '@/api/system/dict/type'
import { listSimpleMenus } from '@/api/system/menu'
import type { FormSchema } from '@/components/Form'
import type { BasicColumn } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
async function getDictTypeOptions() {
const dictTypeOptions: any[] = []
const res = await listSimpleDictType()
dictTypeOptions.push(...res)
return dictTypeOptions
}
export const basicInfoSchemas: FormSchema[] = [
{
label: '基本信息',
field: 'basicInfo',
component: 'Divider',
colProps: { span: 24 },
},
{
label: '表名称',
field: 'tableName',
required: true,
component: 'Input',
colProps: { span: 12 },
},
{
label: '表描述',
field: 'tableComment',
required: true,
component: 'Input',
colProps: { span: 12 },
},
{
label: '实体类名称',
field: 'className',
required: true,
helpMessage: '默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。',
component: 'Input',
colProps: { span: 12 },
},
{
label: '作者',
field: 'author',
required: true,
component: 'Input',
colProps: { span: 12 },
},
{
label: '生成信息',
field: 'genInfo',
component: 'Divider',
colProps: { span: 24 },
},
{
label: '生成模板',
field: 'templateType',
required: true,
component: 'Select',
defaultValue: '30',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE) as any,
},
colProps: { span: 12 },
},
{
label: '前端类型',
field: 'frontType',
required: true,
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE) as any,
},
colProps: { span: 12 },
},
{
label: '生成场景',
field: 'scene',
required: true,
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE),
},
colProps: { span: 12 },
},
{
label: '模块名',
field: 'moduleName',
required: true,
helpMessage: '模块名,即一级目录,例如 system、infra、tool 等等',
component: 'Input',
colProps: { span: 12 },
},
{
label: '业务名',
field: 'businessName',
required: true,
component: 'Input',
helpMessage: '业务名,即二级目录,例如 user、permission、dict 等等',
colProps: { span: 12 },
},
{
label: '类名称',
field: 'className',
required: true,
component: 'Input',
helpMessage: '类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等',
colProps: { span: 12 },
},
{
label: '类描述',
field: 'classComment',
required: true,
component: 'Input',
helpMessage: '用作类描述,例如 用户',
colProps: { span: 12 },
},
{
label: '上级菜单',
field: 'parentMenuId',
required: true,
component: 'ApiTreeSelect',
componentProps: {
api: () => listSimpleMenus(),
handleTree: 'id',
},
colProps: { span: 12 },
},
{
label: '自定义路径',
field: 'genPath',
component: 'Input',
helpMessage: '填写磁盘绝对路径,若不填写,则生成到当前Web项目下',
defaultValue: '/',
ifShow: ({ values }) => values.genType === '1',
colProps: { span: 12 },
},
{
label: '备注',
field: 'remark',
component: 'InputTextArea',
colProps: { span: 24 },
},
]
export const columns: BasicColumn[] = [
{
title: '字段列名',
dataIndex: 'columnName',
width: 60,
},
{
title: '基础属性',
children: [
{
title: '物理类型',
dataIndex: 'dataType',
editComponent: 'Select',
width: 50,
},
{
title: '字段描述',
dataIndex: 'columnComment',
editRow: true,
editComponent: 'Input',
width: 50,
},
{
title: 'Java类型',
dataIndex: 'javaType',
editRow: true,
editComponent: 'Select',
editComponentProps: {
options: [
{
label: 'Long',
value: 'Long',
},
{
label: 'String',
value: 'String',
},
{
label: 'Integer',
value: 'Integer',
},
{
label: 'Double',
value: 'Double',
},
{
label: 'BigDecimal',
value: 'BigDecimal',
},
{
label: 'LocalDateTime',
value: 'LocalDateTime',
},
{
label: 'Boolean',
value: 'Boolean',
},
],
},
width: 50,
},
{
title: 'java属性',
dataIndex: 'javaField',
editRow: true,
editComponent: 'Input',
width: 50,
},
],
},
{
title: '增删改查',
children: [
{
title: '插入',
dataIndex: 'createOperation',
editRow: true,
editComponent: 'Checkbox',
editValueMap: (value) => {
return value ? '是' : '否'
},
width: 40,
},
{
title: '编辑',
dataIndex: 'updateOperation',
editRow: true,
editComponent: 'Checkbox',
editValueMap: (value) => {
return value ? '是' : '否'
},
width: 40,
},
{
title: '列表',
dataIndex: 'listOperationResult',
editRow: true,
editComponent: 'Checkbox',
editValueMap: (value) => {
return value ? '是' : '否'
},
width: 40,
},
{
title: '查询',
dataIndex: 'listOperation',
editRow: true,
editComponent: 'Checkbox',
editValueMap: (value) => {
return value ? '是' : '否'
},
width: 40,
},
{
title: '查询方式',
dataIndex: 'listOperationCondition',
editRow: true,
editComponent: 'Select',
editComponentProps: {
options: [
{ label: '=', value: '=' },
{ label: '!=', value: '!=' },
{ label: '>', value: '>' },
{ label: '>=', value: '>=' },
{ label: '<', value: '<' },
{ label: '<=', value: '<=' },
{ label: 'LIKE', value: 'LIKE' },
{ label: 'BETWEEN', value: 'BETWEEN' },
],
},
width: 80,
},
{
title: '允许空',
dataIndex: 'nullable',
editRow: true,
editComponent: 'Checkbox',
editValueMap: (value) => {
return value ? '是' : '否'
},
width: 40,
},
{
title: '显示类型',
dataIndex: 'htmlType',
editRow: true,
editComponent: 'Select',
editComponentProps: {
options: [
{ label: '文本框', value: 'input' },
{ label: '文本域', value: 'textarea' },
{ label: '下拉框', value: 'select' },
{ label: '单选框', value: 'radio' },
{ label: '复选框', value: 'checkbox' },
{ label: '日期控件', value: 'datetime' },
{ label: '图片上传', value: 'imageUpload' },
{ label: '文件上传', value: 'fileUpload' },
{ label: '富文本控件', value: 'editor' },
],
},
width: 60,
},
{
title: '字典类型',
dataIndex: 'dictType',
editRow: true,
editComponent: 'Select',
editComponentProps: {
options: (await getDictTypeOptions()).map(item => ({ value: item.type, label: item.name })),
},
width: 100,
},
{
title: '示例',
dataIndex: 'example',
editRow: true,
editComponent: 'Input',
width: 60,
},
],
},
]

109
src/views/infra/codegen/index.vue

@ -1,109 +0,0 @@
<script lang="ts" setup>
import PreviewModal from './components/PreviewModal.vue'
import ImportTableModal from './components/ImportTableModal.vue'
import { columns, searchFormSchema } from './codegen.data'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteCodegenTable, downloadCodegen, getCodegenTablePage, syncCodegenFromDB } from '@/api/infra/codegen'
defineOptions({ name: 'InfraCodegen' })
const go = useGo()
const { t } = useI18n()
const { createMessage } = useMessage()
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal()
const [registerImportTableModal, { openModal: openImportTableModal }] = useModal()
const [registerTable, { reload }] = useTable({
title: '代码生成列表',
api: getCodegenTablePage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
showIndexColumn: false,
actionColumn: {
width: 360,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handlePreview(record: Recordable) {
openPreviewModal(true, {
record,
})
}
function handleEditTable(record: Recordable) {
go(`/codegen/editTable?id=${record.id}`)
}
async function handleGenTable(record: Recordable) {
await downloadCodegen(record)
createMessage.success(t('common.successText'))
}
async function handleSynchDb(record: Recordable) {
await syncCodegenFromDB(record.id)
createMessage.success(t('common.successText'))
reload()
}
async function handleDelete(record: Recordable) {
await deleteCodegenTable(record.id)
createMessage.success(t('common.delSuccessText'))
reload()
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['infra:codegen:create']" type="primary" :pre-icon="IconEnum.IMPORT" @click="openImportTableModal(true)">
{{ t('action.import') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{ icon: IconEnum.PREVIEW, label: '预览', auth: 'infra:codegen:preview', onClick: handlePreview.bind(null, record) },
{ icon: IconEnum.EDIT, label: t('action.edit'), auth: 'infra:codegen:update', onClick: handleEditTable.bind(null, record) },
{ icon: IconEnum.DOWNLOAD, label: '生成', auth: 'infra:codegen:download', onClick: handleGenTable.bind(null, record) },
{
icon: IconEnum.RESET,
label: t('action.sync'),
auth: 'infra:codegen:update',
popConfirm: {
title: `确认要强制同步${record.tableName}表结构吗?`,
placement: 'left',
confirm: handleSynchDb.bind(null, record),
},
},
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.delete'),
auth: 'infra:codegen:delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<PreviewModal @register="registerPreviewModal" />
<ImportTableModal @register="registerImportTableModal" @success="reload()" />
</div>
</template>

58
src/views/infra/config/ConfigModal.vue

@ -1,58 +0,0 @@
<script lang="ts" setup>
import { ref, unref } from 'vue'
import { formSchema } from './config.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { createConfig, getConfig, updateConfig } from '@/api/infra/config'
defineOptions({ name: 'InfraConfigModal' })
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const { createMessage } = useMessage()
const isUpdate = ref(true)
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
labelWidth: 120,
baseColProps: { span: 24 },
schemas: formSchema,
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
resetFields()
setModalProps({ confirmLoading: false })
isUpdate.value = !!data?.isUpdate
if (unref(isUpdate)) {
const res = await getConfig(data.record.id)
setFieldsValue({ ...res })
}
})
async function handleSubmit() {
try {
const values = await validate() as any
setModalProps({ confirmLoading: true })
if (unref(isUpdate))
await updateConfig(values)
else
await createConfig(values)
closeModal()
emit('success')
createMessage.success(t('common.saveSuccessText'))
}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>

139
src/views/infra/config/config.data.ts

@ -1,139 +0,0 @@
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
export const columns: BasicColumn[] = [
{
title: '参数主键',
dataIndex: 'id',
width: 100,
},
{
title: '参数分类',
dataIndex: 'category',
width: 180,
},
{
title: '参数名称',
dataIndex: 'name',
width: 100,
},
{
title: '参数键名',
dataIndex: 'key',
width: 120,
},
{
title: '参数键值',
dataIndex: 'value',
width: 120,
},
{
title: '系统内置',
dataIndex: 'type',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.INFRA_CONFIG_TYPE)
},
},
{
title: '是否可见',
dataIndex: 'visible',
width: 180,
customRender: ({ text }) => {
return useRender.renderTag(text ? '是' : '否')
},
},
{
title: '备注',
dataIndex: 'remark',
width: 180,
},
{
title: '创建时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '参数名称',
field: 'name',
component: 'Input',
colProps: { span: 8 },
},
{
label: '参数键名',
field: 'key',
component: 'Input',
colProps: { span: 8 },
},
{
label: '系统内置',
field: 'type',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE) as any,
},
colProps: { span: 8 },
},
{
label: '创建时间',
field: 'createTime',
component: 'RangePicker',
colProps: { span: 8 },
},
]
export const formSchema: FormSchema[] = [
{
label: '编号',
field: 'id',
show: false,
component: 'Input',
},
{
label: '参数分类',
field: 'category',
required: true,
component: 'Input',
},
{
label: '参数名称',
field: 'name',
required: true,
component: 'Input',
},
{
label: '参数键名',
field: 'key',
required: true,
component: 'Input',
},
{
label: '参数键值',
field: 'value',
required: true,
component: 'Input',
},
{
label: '是否可见',
field: 'visible',
component: 'RadioGroup',
componentProps: {
options: [
{ label: '是', value: true },
{ label: '否', value: false },
],
},
},
{
label: '备注',
field: 'remark',
component: 'InputTextArea',
},
]

95
src/views/infra/config/index.vue

@ -1,95 +0,0 @@
<script lang="ts" setup>
import ConfigModal from './ConfigModal.vue'
import { columns, searchFormSchema } from './config.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import type { ConfigExportReqVO } from '@/api/infra/config'
import { deleteConfig, exportConfig, getConfigPage } from '@/api/infra/config'
defineOptions({ name: 'InfraConfig' })
const { t } = useI18n()
const { createConfirm, createMessage } = useMessage()
const [registerModal, { openModal }] = useModal()
const [registerTable, { getForm, reload }] = useTable({
title: '配置中心列表',
api: getConfigPage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
showIndexColumn: false,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handleCreate() {
openModal(true, { isUpdate: false })
}
function handleEdit(record: Recordable) {
openModal(true, { record, isUpdate: true })
}
async function handleExport() {
createConfirm({
title: t('common.exportTitle'),
iconType: 'warning',
content: t('common.exportMessage'),
async onOk() {
await exportConfig(getForm().getFieldsValue() as ConfigExportReqVO)
createMessage.success(t('common.exportSuccessText'))
},
})
}
async function handleDelete(record: Recordable) {
await deleteConfig(record.id)
createMessage.success(t('common.delSuccessText'))
reload()
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['infra:config:create']" type="primary" :pre-icon="IconEnum.ADD" @click="handleCreate">
{{ t('action.create') }}
</a-button>
<a-button v-auth="['infra:config:export']" :pre-icon="IconEnum.EXPORT" @click="handleExport">
{{ t('action.export') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{ icon: IconEnum.EDIT, label: t('action.edit'), auth: 'infra:config:update', onClick: handleEdit.bind(null, record) },
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.delete'),
auth: 'infra:config:delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<ConfigModal @register="registerModal" @success="reload()" />
</div>
</template>

56
src/views/infra/dataSourceConfig/DataSourceConfigModal.vue

@ -1,56 +0,0 @@
<script lang="ts" setup>
import { ref, unref } from 'vue'
import { formSchema } from './dataSourceConfig.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { createDataSourceConfig, getDataSourceConfig, updateDataSourceConfig } from '@/api/infra/dataSourceConfig'
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const { createMessage } = useMessage()
const isUpdate = ref(true)
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
labelWidth: 120,
baseColProps: { span: 24 },
schemas: formSchema,
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
resetFields()
setModalProps({ confirmLoading: false })
isUpdate.value = !!data?.isUpdate
if (unref(isUpdate)) {
const res = await getDataSourceConfig(data.record.id)
setFieldsValue({ ...res })
}
})
async function handleSubmit() {
try {
const values = await validate() as any
setModalProps({ confirmLoading: true })
if (unref(isUpdate))
await updateDataSourceConfig(values)
else
await createDataSourceConfig(values)
closeModal()
emit('success')
createMessage.success(t('common.saveSuccessText'))
}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>

66
src/views/infra/dataSourceConfig/dataSourceConfig.data.ts

@ -1,66 +0,0 @@
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
export const columns: BasicColumn[] = [
{
title: '主键编号',
dataIndex: 'id',
width: 100,
},
{
title: '数据源名称',
dataIndex: 'name',
width: 180,
},
{
title: '数据源连接',
dataIndex: 'url',
width: 100,
},
{
title: '用户名',
dataIndex: 'username',
width: 120,
},
{
title: '创建时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
]
export const formSchema: FormSchema[] = [
{
label: '编号',
field: 'id',
show: false,
component: 'Input',
},
{
label: '数据源名称',
field: 'name',
required: true,
component: 'Input',
},
{
label: '数据源连接',
field: 'url',
required: true,
component: 'Input',
},
{
label: '用户名',
field: 'username',
required: true,
component: 'Input',
},
{
label: '密码',
field: 'password',
required: true,
component: 'Input',
},
]

86
src/views/infra/dataSourceConfig/index.vue

@ -1,86 +0,0 @@
<script lang="ts" setup>
import DataSourceConfigModal from './DataSourceConfigModal.vue'
import { columns } from './dataSourceConfig.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteDataSourceConfig, getDataSourceConfigList } from '@/api/infra/dataSourceConfig'
defineOptions({ name: 'InfraDataSourceConfig' })
const { t } = useI18n()
const { createMessage } = useMessage()
const [registerModal, { openModal }] = useModal()
const [registerTable, { reload }] = useTable({
title: '数据源列表',
api: getDataSourceConfigList,
columns,
pagination: false,
useSearchForm: false,
showTableSetting: true,
showIndexColumn: false,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handleCreate() {
openModal(true, { isUpdate: false })
}
function handleEdit(record: Recordable) {
openModal(true, { record, isUpdate: true })
}
async function handleDelete(record: Recordable) {
await deleteDataSourceConfig(record.id)
createMessage.success(t('common.delSuccessText'))
reload()
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['infra:data-source-config:create']" type="primary" :pre-icon="IconEnum.ADD" @click="handleCreate">
{{ t('action.create') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: IconEnum.EDIT,
label: t('action.edit'),
ifShow: record.id !== 0,
auth: 'infra:data-source-config:update',
onClick: handleEdit.bind(null, record),
},
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.delete'),
ifShow: record.id !== 0,
auth: 'infra:data-source-config:delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<DataSourceConfigModal @register="registerModal" @success="reload()" />
</div>
</template>

53
src/views/infra/dbDoc/index.vue

@ -1,53 +0,0 @@
<script setup lang="ts" name="InfraDbDoc">
import { onMounted, ref } from 'vue'
import { PageWrapper } from '@/components/Page'
import { useI18n } from '@/hooks/web/useI18n'
import { IFrame } from '@/components/IFrame'
import * as DbDocApi from '@/api/infra/dbDoc'
import { downloadByData } from '@/utils/file/download'
const { t } = useI18n()
const src = ref('')
/** 页面加载 */
async function init() {
const res = await DbDocApi.exportHtml()
const blob = new Blob([res], { type: 'text/html' })
const blobUrl = window.URL.createObjectURL(blob)
src.value = blobUrl
}
/** 处理导出 */
async function handleExport(type: string) {
if (type === 'HTML') {
const res = await DbDocApi.exportHtml()
downloadByData(res, '数据库文档.html')
}
if (type === 'Word') {
const res = await DbDocApi.exportWord()
downloadByData(res, '数据库文档.doc')
}
if (type === 'Markdown') {
const res = await DbDocApi.exportMarkdown()
downloadByData(res, '数据库文档.md')
}
}
onMounted(async () => {
await init()
})
</script>
<template>
<PageWrapper>
<div class="mb-3">
<a-button type="primary" size="small" class="mr-1" @click="handleExport('HTML')">
{{ `${t('action.export')}Html` }}
</a-button>
<a-button type="primary" size="small" class="mr-1" @click="handleExport('Word')">
{{ `${t('action.export')}Word` }}
</a-button>
<a-button type="primary" size="small" @click="handleExport('Markdown')">
{{ `${t('action.export')}Markdown` }}
</a-button>
</div>
<IFrame :src="src" />
</PageWrapper>
</template>

12
src/views/infra/druid/index.vue

@ -1,12 +0,0 @@
<script setup lang="ts" name="InfraDruid">
import { ref } from 'vue'
import { IFrame } from '@/components/IFrame'
const src = ref(`${import.meta.env.VITE_GLOB_BASE_URL}/druid/index.html`)
</script>
<template>
<div>
<IFrame :src="src" />
</div>
</template>

79
src/views/infra/file/file.data.ts

@ -1,79 +0,0 @@
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
export const columns: BasicColumn[] = [
{
title: '编号',
dataIndex: 'id',
width: 100,
},
{
title: '文件名',
dataIndex: 'name',
width: 200,
},
{
title: '文件 URL',
dataIndex: 'url',
width: 180,
customRender: ({ text }) => {
return useRender.renderImg(text)
},
},
{
title: '文件路径',
dataIndex: 'path',
width: 180,
},
{
title: '文件大小',
dataIndex: 'size',
width: 120,
customRender: ({ text }) => {
const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const srcSize = Number.parseFloat(text)
const index = Math.floor(Math.log(srcSize) / Math.log(1024))
const size = srcSize / 1024 ** index
return `${size.toFixed(2)} ${unitArr[index]}`
},
},
{
title: '文件类型',
dataIndex: 'type',
width: 100,
customRender: ({ text }) => {
return useRender.renderTag(text)
},
},
{
title: '文件内容',
dataIndex: 'content',
width: 180,
customRender: ({ text }) => {
return useRender.renderImg(text)
},
},
{
title: '上传时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '文件路径',
field: 'path',
component: 'Input',
colProps: { span: 8 },
},
{
label: '创建时间',
field: 'createTime',
component: 'RangePicker',
colProps: { span: 8 },
},
]

92
src/views/infra/file/index.vue

@ -1,92 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { columns, searchFormSchema } from './file.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { IconEnum } from '@/enums/appEnum'
import { BasicUpload } from '@/components/Upload'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteFile, getFilePage } from '@/api/infra/file'
import { getAccessToken, getTenantId } from '@/utils/auth'
import { copyText } from '@/utils/copyTextToClipboard'
import { uploadApi } from '@/api/base/upload'
defineOptions({ name: 'InfraFile' })
const { t } = useI18n()
const { createMessage } = useMessage()
const uploadParams = ref({
'Authorization': `Bearer ${getAccessToken()}`,
'tenant-id': getTenantId(),
})
const [registerTable, { reload }] = useTable({
title: '文件列表',
api: getFilePage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
showIndexColumn: false,
actionColumn: {
width: 160,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handleChange() {
reload()
}
function handleCopy(record: Recordable) {
copyText(record.url)
}
async function handleDelete(record: Recordable) {
await deleteFile(record.id)
createMessage.success(t('common.delSuccessText'))
reload()
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<BasicUpload
:max-size="20"
:max-number="10"
:empty-hide-preview="true"
:upload-params="uploadParams"
:api="uploadApi"
class="my-5"
:accept="['image/*']"
@change="handleChange"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{ icon: IconEnum.VIEW, label: '复制链接', onClick: handleCopy.bind(null, record) },
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.delete'),
auth: 'infra:file:delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
</div>
</template>

58
src/views/infra/fileConfig/FileConfigModal.vue

@ -1,58 +0,0 @@
<script lang="ts" setup>
import { ref, unref } from 'vue'
import { formSchema } from './ficleConfig.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { createFileConfig, getFileConfig, updateFileConfig } from '@/api/infra/fileConfig'
defineOptions({ name: 'InfraFileConfigModal' })
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const { createMessage } = useMessage()
const isUpdate = ref(true)
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
labelWidth: 120,
baseColProps: { span: 24 },
schemas: formSchema,
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
resetFields()
setModalProps({ confirmLoading: false })
isUpdate.value = !!data?.isUpdate
if (unref(isUpdate)) {
const res = await getFileConfig(data.record.id)
setFieldsValue({ ...res })
}
})
async function handleSubmit() {
try {
const values = await validate()
setModalProps({ confirmLoading: true })
if (unref(isUpdate))
await updateFileConfig(values)
else
await createFileConfig(values)
closeModal()
emit('success')
createMessage.success(t('common.saveSuccessText'))
}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>

179
src/views/infra/fileConfig/ficleConfig.data.ts

@ -1,179 +0,0 @@
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
export const columns: BasicColumn[] = [
{
title: '编号',
dataIndex: 'id',
width: 100,
},
{
title: '配置名',
dataIndex: 'name',
width: 180,
},
{
title: '存储器',
dataIndex: 'storage',
width: 100,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.INFRA_FILE_STORAGE)
},
},
{
title: '主配置',
dataIndex: 'master',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.INFRA_BOOLEAN_STRING)
},
},
{
title: '备注',
dataIndex: 'remark',
width: 180,
},
{
title: '创建时间',
dataIndex: 'createTime',
width: 180,
customRender: ({ text }) => {
return useRender.renderDate(text)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '配置名',
field: 'name',
component: 'Input',
colProps: { span: 8 },
},
{
label: '存储器',
field: 'storage',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE) as any,
},
colProps: { span: 8 },
},
{
label: '创建时间',
field: 'createTime',
component: 'RangePicker',
colProps: { span: 8 },
},
]
export const formSchema: FormSchema[] = [
{
label: '编号',
field: 'id',
show: false,
component: 'Input',
},
{
label: '配置名',
field: 'name',
required: true,
component: 'Input',
},
{
label: '存储器',
field: 'storage',
component: 'Select',
required: true,
dynamicDisabled: ({ values }) => !!values.id,
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE) as any,
},
},
{
label: '基础路径',
field: 'config.basePath',
required: true,
ifShow: ({ values }) => values.storage >= 10 && values.storage <= 12,
component: 'Input',
},
{
label: '主机地址',
field: 'config.host',
required: true,
ifShow: ({ values }) => values.storage >= 11 && values.storage <= 12,
component: 'Input',
},
{
label: '主机端口',
field: 'config.port',
required: true,
ifShow: ({ values }) => values.storage >= 11 && values.storage <= 12,
component: 'Input',
},
{
label: '用户名',
field: 'config.username',
required: true,
ifShow: ({ values }) => values.storage >= 11 && values.storage <= 12,
component: 'Input',
},
{
label: '密码',
field: 'config.password',
required: true,
ifShow: ({ values }) => values.storage >= 11 && values.storage <= 12,
component: 'Input',
},
{
label: '连接模式',
field: 'config.basePath',
required: true,
ifShow: ({ values }) => values.storage === 11,
component: 'Select',
componentProps: {
options: [
{ lable: '主动模式', key: 'Active', value: 'Active' },
{ lable: '被动模式', key: 'Passive', value: 'Passive' },
],
},
},
{
label: '节点地址',
field: 'config.endpoint',
required: true,
ifShow: ({ values }) => values.storage === 20,
component: 'Input',
},
{
label: '存储 bucket',
field: 'config.bucket',
required: true,
ifShow: ({ values }) => values.storage === 20,
component: 'Input',
},
{
label: 'accessKey',
field: 'config.accessKey',
ifShow: ({ values }) => values.storage === 20,
component: 'Input',
},
{
label: 'accessSecret',
field: 'config.accessSecret',
ifShow: ({ values }) => values.storage === 20,
component: 'Input',
},
{
label: '自定义域名',
field: 'config.domain',
required: true,
component: 'Input',
},
{
label: '备注',
field: 'remark',
component: 'InputTextArea',
},
]

107
src/views/infra/fileConfig/index.vue

@ -1,107 +0,0 @@
<script lang="ts" setup>
import FileConfigModal from './FileConfigModal.vue'
import { columns, searchFormSchema } from './ficleConfig.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteFileConfig, getFileConfigPage, testFileConfig, updateFileConfigMaster } from '@/api/infra/fileConfig'
defineOptions({ name: 'InfraFileConfig' })
const { t } = useI18n()
const { createConfirm, createMessage, createSuccessModal } = useMessage()
const [registerModal, { openModal }] = useModal()
const [registerTable, { reload }] = useTable({
title: '文件配置列表',
api: getFileConfigPage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
showIndexColumn: false,
actionColumn: {
width: 280,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handleCreate() {
openModal(true, { isUpdate: false })
}
function handleEdit(record: Recordable) {
openModal(true, { record, isUpdate: true })
}
async function handleTest(record: Recordable) {
const res = await testFileConfig(record.id)
createSuccessModal({ content: res })
}
function handleMaster(record: Recordable) {
createConfirm({
title: '主配置',
iconType: 'warning',
content: `是否确认修改配置编号为"${record.id}"的数据项为主配置?`,
async onOk() {
await updateFileConfigMaster(record.id)
createMessage.success('配置成功')
reload()
},
})
}
async function handleDelete(record: Recordable) {
await deleteFileConfig(record.id)
createMessage.success(t('common.delSuccessText'))
reload()
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['infra:file-config:create']" type="primary" :pre-icon="IconEnum.ADD" @click="handleCreate">
{{ t('action.create') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{ icon: IconEnum.EDIT, label: t('action.edit'), auth: 'infra:file-config:update', onClick: handleEdit.bind(null, record) },
{ icon: IconEnum.TEST, label: t('action.test'), auth: 'infra:file-config:update', onClick: handleTest.bind(null, record) },
{
icon: IconEnum.AUTH,
label: '主配置',
auth: 'infra:file-config:update',
ifShow: () => {
return !record.master
},
onClick: handleMaster.bind(null, record),
},
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.delete'),
auth: 'infra:file-config:delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<FileConfigModal @register="registerModal" @success="reload()" />
</div>
</template>

92
src/views/infra/job/JobModal.vue

@ -1,92 +0,0 @@
<script lang="ts" setup>
import { ref, unref } from 'vue'
import { Steps } from 'ant-design-vue'
import { descSchema, formSchema } from './job.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { Description, useDescription } from '@/components/Description'
import { createJob, getJob, getJobNextTimes, updateJob } from '@/api/infra/job'
import { formatToDateTime } from '@/utils/dateUtil'
defineOptions({ name: 'InfraJobModal' })
const emit = defineEmits(['success', 'register'])
const Step = Steps.Step
const { t } = useI18n()
const { createMessage } = useMessage()
const isUpdate = ref(true)
const isEdit = ref(true)
const datas = ref()
const nextTimes = ref()
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
labelWidth: 120,
baseColProps: { span: 24 },
schemas: formSchema,
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerDesc] = useDescription({
schema: descSchema,
data: datas,
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
isEdit.value = !!data?.isEdit
setModalProps({ confirmLoading: false })
if (data?.isEdit) {
resetFields()
isUpdate.value = !!data?.isUpdate
if (unref(isUpdate)) {
const res = await getJob(data.record.id)
setFieldsValue({ ...res })
}
}
else {
datas.value = await getJob(data.record.id)
nextTimes.value = await getJobNextTimes(data.record.id)
}
})
async function handleSubmit() {
try {
const values = await validate() as any
setModalProps({ confirmLoading: true })
if (unref(isUpdate))
await updateJob(values)
else
await createJob(values)
closeModal()
emit('success')
createMessage.success(t('common.saveSuccessText'))
}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal
v-bind="$attrs"
:title="isEdit ? (isUpdate ? t('action.edit') : t('action.create')) : t('action.detail')"
@register="registerModal"
@ok="handleSubmit"
>
<BasicForm v-if="isEdit" @register="registerForm" />
<Description v-if="!isEdit" :column="2" @register="registerDesc" />
<Steps v-if="!isEdit" progress-dot :current="nextTimes && nextTimes.length" direction="vertical">
<template v-for="(nextTime, index) in nextTimes" :key="index">
<Step :title="formatToDateTime(nextTime)" :description="'第' + `${index + 1}` + '次'" />
</template>
</Steps>
</BasicModal>
</template>

141
src/views/infra/job/index.vue

@ -1,141 +0,0 @@
<script lang="ts" setup>
import JobModal from './JobModal.vue'
import { columns, searchFormSchema } from './job.data'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import type { JobExportReqVO } from '@/api/infra/job'
import { deleteJob, exportJob, getJobPage, runJob, updateJobStatus } from '@/api/infra/job'
import { InfraJobStatusEnum } from '@/enums/systemEnum'
defineOptions({ name: 'InfraJob' })
const go = useGo()
const { t } = useI18n()
const { createConfirm, createMessage } = useMessage()
const [registerModal, { openModal }] = useModal()
const [registerTable, { getForm, reload }] = useTable({
title: '定时任务列表',
api: getJobPage,
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
showIndexColumn: false,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handleCreate() {
openModal(true, { isEdit: true, isUpdate: false })
}
function handleEdit(record: Recordable) {
openModal(true, { record, isEdit: true, isUpdate: true })
}
function handleChangeStatus(record: Recordable, open: boolean) {
const status = open ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP
const statusStr = open ? '开启' : '关闭'
createConfirm({
title: '调整状态',
iconType: 'warning',
content: `是否确认${statusStr}定时任务编号为"${record.id}"的数据项?`,
async onOk() {
await updateJobStatus(record.id, status)
createMessage.success(t('common.successText'))
reload()
},
})
}
function handleRun(record: Recordable) {
createConfirm({
title: '执行',
iconType: 'warning',
content: `确认要立即执行一次"${record.name}"任务吗?`,
async onOk() {
await runJob(record.id)
createMessage.success(t('common.successText'))
},
})
}
function handleView(record: Recordable) {
openModal(true, { record, isEdit: false })
}
function handleJobLog(record: Recordable) {
if (record.id > 0)
go(`/job/job-log?id=${record.id}`)
else
go('/job/job-log')
}
async function handleExport() {
createConfirm({
title: t('common.exportTitle'),
iconType: 'warning',
content: t('common.exportMessage'),
async onOk() {
await exportJob(getForm().getFieldsValue() as JobExportReqVO)
createMessage.success(t('common.exportSuccessText'))
},
})
}
async function handleDelete(record: Recordable) {
await deleteJob(record.id)
createMessage.success(t('common.delSuccessText'))
reload()
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['infra:job:create']" type="primary" :pre-icon="IconEnum.ADD" @click="handleCreate">
{{ t('action.create') }}
</a-button>
<a-button v-auth="['infra:job:export']" :pre-icon="IconEnum.EXPORT" @click="handleExport">
{{ t('action.export') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[{ icon: IconEnum.EDIT, label: t('action.edit'), onClick: handleEdit.bind(null, record) }]"
:drop-down-actions="[
{ icon: IconEnum.AUTH, label: '开启', auth: 'infra:job:update', onClick: handleChangeStatus.bind(null, record, true) },
{ icon: IconEnum.EDIT, label: '暂停', auth: 'infra:job:update', onClick: handleChangeStatus.bind(null, record, false) },
{ icon: IconEnum.TEST, label: '执行一次', auth: 'infra:job:trigger', onClick: handleRun.bind(null, record) },
{ icon: IconEnum.PREVIEW, label: '任务详细', auth: 'infra:job:query', onClick: handleView.bind(null, record) },
{ icon: IconEnum.LOG, label: '调度日志', auth: 'infra:job:query', onClick: handleJobLog.bind(null, record) },
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.delete'),
auth: 'infra:job:delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<JobModal @register="registerModal" @success="reload()" />
</div>
</template>

172
src/views/infra/job/job.data.ts

@ -1,172 +0,0 @@
import type { DescItem } from '@/components/Description'
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { useComponentRegister } from '@/components/Form'
import { CronTab } from '@/components/CronTab'
useComponentRegister('CronTab', CronTab)
export const columns: BasicColumn[] = [
{
title: '任务编号',
dataIndex: 'id',
width: 100,
},
{
title: '任务名称',
dataIndex: 'name',
width: 180,
},
{
title: '处理器的名字',
dataIndex: 'handlerName',
width: 180,
},
{
title: '处理器的参数',
dataIndex: 'handlerParam',
width: 180,
},
{
title: 'CRON 表达式',
dataIndex: 'cronExpression',
width: 200,
},
{
title: '任务状态',
dataIndex: 'status',
width: 180,
customRender: ({ text }) => {
return useRender.renderDict(text, DICT_TYPE.INFRA_JOB_STATUS)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '任务名称',
field: 'name',
component: 'Input',
colProps: { span: 8 },
},
{
label: '任务状态',
field: 'status',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_JOB_STATUS) as any,
},
colProps: { span: 8 },
},
{
label: '处理器的名字',
field: 'handlerName',
component: 'Input',
colProps: { span: 8 },
},
]
export const formSchema: FormSchema[] = [
{
label: '任务编号',
field: 'id',
show: false,
component: 'Input',
},
{
label: '任务名称',
field: 'name',
required: true,
component: 'Input',
},
{
label: '处理器的名字',
field: 'handlerName',
required: true,
dynamicDisabled: ({ values }) => !!values.id,
component: 'Input',
},
{
label: '处理器的参数',
field: 'handlerParam',
component: 'Input',
},
{
label: 'CRON 表达式',
field: 'cronExpression',
required: true,
component: 'CronTab',
},
{
label: '重试次数',
field: 'retryCount',
required: true,
helpMessage: '设置为 0 时,不进行重试',
defaultValue: 0,
component: 'InputNumber',
},
{
label: '重试间隔',
field: 'retryInterval',
required: true,
helpMessage: '单位:毫秒。设置为 0 时,无需间隔',
defaultValue: 0,
component: 'InputNumber',
suffix: '毫秒',
},
{
label: '监控超时时间',
field: 'monitorTimeout',
component: 'Input',
suffix: '毫秒',
},
]
export const descSchema: DescItem[] = [
{
label: '任务编号',
field: 'id',
},
{
label: '任务名称',
field: 'name',
},
{
label: '任务状态',
field: 'status',
render: (curVal) => {
return useRender.renderDict(curVal, DICT_TYPE.INFRA_JOB_STATUS)
},
},
{
label: '处理器的名字',
field: 'handlerName',
},
{
label: '处理器的参数',
field: 'handlerParam',
},
{
label: 'Cron 表达式',
field: 'cronExpression',
},
{
label: '重试次数',
field: 'retryCount',
},
{
label: '重试间隔',
field: 'cronExpression',
render: (curVal) => {
return useRender.renderText(curVal, ' 毫秒')
},
},
{
label: '监控超时时间',
field: 'monitorTimeout',
render: (curVal) => {
return curVal > 0 ? `${curVal} 毫秒` : '未开启'
},
},
]

28
src/views/infra/job/logger/JobLogModal.vue

@ -1,28 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { descSchema } from './jobLog.data'
import { Description, useDescription } from '@/components/Description'
import { BasicModal, useModalInner } from '@/components/Modal'
import { getJobLog } from '@/api/infra/jobLog'
defineOptions({ name: 'InfraJobLogModal' })
const datas = ref()
const [registerDesc] = useDescription({
schema: descSchema,
data: datas,
})
const [registerModal, { setModalProps }] = useModalInner(async (data) => {
setModalProps({ confirmLoading: false })
const res = await getJobLog(data.record.id)
datas.value = res
})
</script>
<template>
<BasicModal v-bind="$attrs" title="查看详情" @register="registerModal">
<Description :column="2" @register="registerDesc" />
</BasicModal>
</template>

70
src/views/infra/job/logger/index.vue

@ -1,70 +0,0 @@
<script lang="ts" setup>
import { useRoute } from 'vue-router'
import JobLogModal from './JobLogModal.vue'
import { columns, searchFormSchema } from './jobLog.data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import type { JobLogExportReqVO } from '@/api/infra/jobLog'
import { exportJobLog, getJobLogPage } from '@/api/infra/jobLog'
defineOptions({ name: 'InfraJobLog' })
const { t } = useI18n()
const { query } = useRoute()
const { createConfirm, createMessage } = useMessage()
const [registerModal, { openModal }] = useModal()
const [registerTable, { getForm, reload }] = useTable({
title: '定时任务日志列表',
api: getJobLogPage,
searchInfo: { id: query.id as unknown as number },
columns,
formConfig: { labelWidth: 120, schemas: searchFormSchema },
useSearchForm: true,
showTableSetting: true,
showIndexColumn: false,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
},
})
function handleDetail(record: Recordable) {
openModal(true, { record })
}
async function handleExport() {
createConfirm({
title: t('common.exportTitle'),
iconType: 'warning',
content: t('common.exportMessage'),
async onOk() {
await exportJobLog(getForm().getFieldsValue() as JobLogExportReqVO)
createMessage.success(t('common.exportSuccessText'))
},
})
}
</script>
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button v-auth="['infra:job:export']" :pre-icon="IconEnum.EXPORT" @click="handleExport">
{{ t('action.export') }}
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction :actions="[{ icon: IconEnum.EDIT, label: t('action.detail'), onClick: handleDetail.bind(null, record) }]" />
</template>
</template>
</BasicTable>
<JobLogModal @register="registerModal" @success="reload()" />
</div>
</template>

150
src/views/infra/job/logger/jobLog.data.ts

@ -1,150 +0,0 @@
import { h } from 'vue'
import type { DescItem } from '@/components/Description'
import type { BasicColumn, FormSchema } from '@/components/Table'
import { useRender } from '@/components/Table'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { JsonPreview } from '@/components/CodeEditor'
export const columns: BasicColumn[] = [
{
title: '日志编号',
dataIndex: 'id',
width: 100,
},
{
title: '任务编号',
dataIndex: 'jobId',
width: 100,
},
{
title: '处理器的名字',
dataIndex: 'handlerName',
width: 180,
},
{
title: '处理器的参数',
dataIndex: 'handlerParam',
width: 180,
},
{
title: '第几次执行',
dataIndex: 'executeIndex',
width: 100,
},
{
title: '执行时间',
dataIndex: 'beginTime',
width: 180,
customRender: ({ record }) => {
const startTime = useRender.renderDate(record.beginTime)
const endTime = useRender.renderDate(record.endTime)
return useRender.renderTags([startTime, endTime])
},
},
{
title: '执行时长',
dataIndex: 'duration',
width: 180,
customRender: ({ text }) => {
return useRender.renderText(text, ' 毫秒')
},
},
{
title: '任务状态',
dataIndex: 'status',
width: 180,
customRender: ({ record }) => {
return useRender.renderDict(record.status, DICT_TYPE.INFRA_JOB_LOG_STATUS)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '处理器的名字',
field: 'handlerName',
component: 'Input',
colProps: { span: 8 },
},
{
label: '开始执行时间',
field: 'beginTime',
component: 'DatePicker',
colProps: { span: 8 },
},
{
label: '结束执行时间',
field: 'endTime',
component: 'DatePicker',
colProps: { span: 8 },
},
{
label: '任务状态',
field: 'status',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_JOB_STATUS) as any,
},
colProps: { span: 8 },
},
{
label: '处理器的名字',
field: 'handlerName',
component: 'Input',
colProps: { span: 8 },
},
]
export const descSchema: DescItem[] = [
{
label: '日志编号',
field: 'id',
},
{
label: '任务编号',
field: 'jobId',
},
{
label: '处理器的名字',
field: 'handlerName',
},
{
label: '处理器的参数',
field: 'handlerParam',
},
{
label: '第几次执行',
field: 'executeIndex',
},
{
label: '执行时间',
field: 'beginTime',
render: (_, data) => {
const startTime = `开始: ${useRender.renderDate(data.beginTime)}`
const endTime = `结束: ${useRender.renderDate(data.endTime)}`
return h('span', {}, [startTime, h('br'), endTime])
},
},
{
label: '执行时长',
field: 'duration',
render: (curVal) => {
return useRender.renderText(curVal, ' 毫秒')
},
},
{
label: '任务状态',
field: 'status',
render: (curVal) => {
return useRender.renderDict(curVal, DICT_TYPE.INFRA_JOB_LOG_STATUS)
},
},
{
label: '执行结果',
field: 'result',
render: (curVal) => {
const data = JSON.parse(curVal)
return h(JsonPreview, { data })
},
},
]

53
src/views/infra/redis/components/CommandStats.vue

@ -1,53 +0,0 @@
<script lang="ts" setup>
import type { Ref } from 'vue'
import { ref, watch } from 'vue'
import { Card } from 'ant-design-vue'
import { useECharts } from '@/hooks/web/useECharts'
import { propTypes } from '@/utils/propTypes'
const props = defineProps({
loading: propTypes.bool.def(true),
commandStats: propTypes.array.def([]),
width: propTypes.string.def('100%'),
height: propTypes.string.def('300px'),
})
const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
const optionsData = ref<any[]>(props.commandStats)
watch(
() => props.loading,
() => {
if (props.loading)
return
setOptions({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)',
},
series: [
{
name: '命令',
type: 'pie',
roseType: 'radius',
radius: [15, 95],
center: ['50%', '38%'],
data: optionsData.value,
animationEasing: 'cubicInOut',
animationDuration: 1000,
},
],
})
},
{ immediate: true },
)
</script>
<template>
<Card title="命令统计" :loading="loading">
<div ref="chartRef" :style="{ width, height }" />
</Card>
</template>

55
src/views/infra/redis/components/Memory.vue

@ -1,55 +0,0 @@
<script lang="ts" setup>
import type { Ref } from 'vue'
import { ref, watch } from 'vue'
import { Card } from 'ant-design-vue'
import { useECharts } from '@/hooks/web/useECharts'
import { propTypes } from '@/utils/propTypes'
const props = defineProps({
loading: propTypes.bool.def(true),
memoryHuman: propTypes.string.def('0'),
width: propTypes.string.def('100%'),
height: propTypes.string.def('300px'),
})
const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
watch(
() => props.loading,
() => {
if (props.loading)
return
setOptions({
tooltip: {
formatter: `{b} <br/>{a} : ${props.memoryHuman}`,
},
series: [
{
name: '峰值',
type: 'gauge',
min: 0,
max: 100,
detail: {
formatter: props.memoryHuman,
},
data: [
{
value: Number.parseFloat(props.memoryHuman),
name: '内存消耗',
},
],
},
],
})
},
{ immediate: true },
)
</script>
<template>
<Card title="内存信息" :loading="loading">
<div ref="chartRef" :style="{ width, height }" />
</Card>
</template>

46
src/views/infra/redis/index.vue

@ -1,46 +0,0 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { baseInfoSchema } from './redis.data'
import { Description } from '@/components/Description'
import { getCache } from '@/api/infra/redis'
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'
defineOptions({ name: 'InfraRedis' })
const CommandStats = createAsyncComponent(() => import('./components/CommandStats.vue'))
const Memory = createAsyncComponent(() => import('./components/Memory.vue'))
const loading = ref(true)
const cacheInfo = ref<any>()
const commandStats = ref<any[]>([])
const memoryHuman = ref<any>()
async function getList() {
const res = await getCache()
cacheInfo.value = res.info
memoryHuman.value = res.info.used_memory_human
await res.commandStats.forEach((val) => {
commandStats.value.push({ name: val.command, value: val.calls })
})
loading.value = false
}
onMounted(async () => {
await getList()
})
</script>
<template>
<div class="p-4">
<Description
title="基础信息"
:collapse-options="{ canExpand: true, helpMessage: 'Redis 基本信息' }"
:column="6"
:data="cacheInfo"
:schema="baseInfoSchema"
/>
<div class="enter-y mt-4 md:flex">
<CommandStats class="w-full md:w-1/2" :loading="loading" :command-stats="commandStats" />
<Memory class="w-full !my-4 md:w-1/2 !md:mx-4 !md:my-0" :loading="loading" :memory-human="memoryHuman" />
</div>
</div>
</template>

64
src/views/infra/redis/redis.data.ts

@ -1,64 +0,0 @@
import type { DescItem } from '@/components/Description'
export const baseInfoSchema: DescItem[] = [
{
label: 'Redis版本',
field: 'redis_version',
},
{
label: '运行模式',
field: 'redis_mode',
render: (val) => {
return val === 'standalone' ? '单机' : '集群'
},
},
{
label: '端口',
field: 'tcp_port',
},
{
label: '客户端数',
field: 'connected_clients',
},
{
label: '运行时间(天)',
field: 'uptime_in_days',
},
{
label: '使用内存',
field: 'used_memory_human',
},
{
label: '使用CPU',
field: 'tcp_port',
render: (val) => {
return Number.parseFloat(val).toFixed(2)
},
},
{
label: '内存配置',
field: 'maxmemory_human',
},
{
label: 'AOF是否开启',
field: 'maxmemory_human',
render: (val) => {
return val === '0' ? '否' : '是'
},
},
{
label: 'RDB是否成功',
field: 'rdb_last_bgsave_status',
},
{
label: 'Key数量',
field: 'expired_keys',
},
{
label: '网络入口/出口',
field: 'instantaneous_input_kbps',
render: (_val, data) => {
return `${data.instantaneous_input_kbps}kps / ${data.instantaneous_output_kbps}kps`
},
},
]

29
src/views/infra/server/index.vue

@ -1,29 +0,0 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { IFrame } from '@/components/IFrame'
import { getConfigKey } from '@/api/infra/config'
defineOptions({ name: 'InfraAdminServer' })
const src = ref(`${import.meta.env.VITE_GLOB_BASE_URL}/admin/applications`)
const loading = ref(true)
async function getInfo() {
const res = await getConfigKey('url.spring-boot-admin')
if (res && res.length !== 0)
src.value = res
loading.value = false
}
onMounted(() => {
getInfo()
})
</script>
<template>
<div>
<IFrame v-if="!loading" :src="src" />
</div>
</template>

14
src/views/infra/skywalking/index.vue

@ -1,14 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { IFrame } from '@/components/IFrame'
defineOptions({ name: 'InfraSkywalking' })
const src = ref('http://skywalking.shop.iocoder.cn')
</script>
<template>
<div>
<IFrame :src="src" />
</div>
</template>

16
src/views/infra/swagger/index.vue

@ -1,16 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { IFrame } from '@/components/IFrame'
defineOptions({ name: 'InfraSwagger' })
// knife4j
// const src = ref(import.meta.env.VITE_GLOB_BASE_URL + '/doc.html')
const src = ref(`${import.meta.env.VITE_GLOB_BASE_URL}/swagger-ui`)
</script>
<template>
<div>
<IFrame :src="src" />
</div>
</template>

3
src/views/infra/testDemo/index.vue

@ -1,3 +0,0 @@
<template>
<div>开发中 testDemo</div>
</template>

112
src/views/infra/webSocket/index.vue

@ -1,112 +0,0 @@
<script lang="ts" setup>
import { computed, reactive, ref, watchEffect } from 'vue'
import { Input, Tag } from 'ant-design-vue'
import { useWebSocket } from '@vueuse/core'
import { PageWrapper } from '@/components/Page'
import { formatToDateTime } from '@/utils/dateUtil'
import { useUserStore } from '@/store/modules/user'
defineOptions({ name: 'InfraWebSocket' })
const InputTextArea = Input.TextArea
const userStore = useUserStore()
const state = reactive({
sendValue: '',
recordList: [] as { id: number, time: number, res: string }[],
})
const server = ref(
`${(`${import.meta.env.VITE_GLOB_BASE_URL}/websocket/message`).replace('http', 'ws')}?userId=${userStore.getUserInfo.user.id}`,
)
const { status, data, send, close, open } = useWebSocket(server.value, {
autoReconnect: false,
heartbeat: true,
})
watchEffect(() => {
if (data.value) {
try {
const res = JSON.parse(data.value)
state.recordList.push(res)
}
catch (error) {
state.recordList.push({
res: data.value,
id: Math.ceil(Math.random() * 1000),
time: new Date().getTime(),
})
}
}
})
const getIsOpen = computed(() => status.value === 'OPEN')
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red'))
const getList = computed(() => {
return [...state.recordList].reverse()
})
function handlerSend() {
send(state.sendValue)
state.sendValue = ''
}
function toggle() {
if (getIsOpen.value)
close()
else
open()
}
</script>
<template>
<PageWrapper title="WebSocket 示例">
<div class="flex">
<div class="w-1/3 p-4">
<div class="flex items-center">
<span class="mr-4 text-lg font-medium"> 连接状态: </span>
<Tag :color="getTagColor">
{{ status }}
</Tag>
</div>
<hr class="my-4">
<div class="flex">
<Input v-model:value="server" disabled>
<template #addonBefore>
服务地址
</template>
</Input>
<a-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggle">
{{ getIsOpen ? '关闭连接' : '开启连接' }}
</a-button>
</div>
<p class="mt-4 text-lg font-medium">
设置
</p>
<hr class="my-4">
<InputTextArea v-model:value="state.sendValue" placeholder="需要发送到服务器的内容" :disabled="!getIsOpen" allow-clear />
<a-button type="primary" block class="mt-4" :disabled="!getIsOpen" @click="handlerSend">
发送
</a-button>
</div>
<div class="ml-4 w-2/3 p-4">
<span class="mr-4 text-lg font-medium"> 消息记录: </span>
<hr class="my-4">
<div class="max-h-80 overflow-auto">
<ul>
<li v-for="item in getList" :key="item.time" class="mt-2">
<div class="flex items-center">
<span class="mr-2 font-medium text-primary">收到消息:</span>
<span>{{ formatToDateTime(item.time) }}</span>
</div>
<div>
{{ item.res }}
</div>
</li>
</ul>
</div>
</div>
</div>
</PageWrapper>
</template>

3
src/views/mall/market/banner/index.vue

@ -1,3 +0,0 @@
<template>
<div>开发中</div>
</template>

3
src/views/mall/product/brand/index.vue

@ -1,3 +0,0 @@
<template>
<div>开发中</div>
</template>

3
src/views/mall/product/category/index.vue

@ -1,3 +0,0 @@
<template>
<div>开发中</div>
</template>

3
src/views/mall/product/property/index.vue

@ -1,3 +0,0 @@
<template>
<div>开发中</div>
</template>

3
src/views/mall/product/spu/index.vue

@ -1,3 +0,0 @@
<template>
<div>开发中</div>
</template>

3
src/views/mall/promotion/coupon/index.vue

@ -1,3 +0,0 @@
<template>
<div>开发中</div>
</template>

3
src/views/mall/promotion/couponTemplate/index.vue

@ -1,3 +0,0 @@
<template>
<div>开发中</div>
</template>

Some files were not shown because too many files have changed in this diff Show More