Compare commits

...

5 Commits

  1. 4
      .env
  2. 3
      .env.development
  3. 2
      LICENSE
  4. 6
      build/utils.ts
  5. 1
      build/vite/plugin/html.ts
  6. 3
      eslint.config.js
  7. 90
      package.json
  8. 5380
      pnpm-lock.yaml
  9. 1
      public/resource/tinymce/langs/en.js
  10. 1
      public/resource/tinymce/langs/zh_CN.js
  11. 2
      src/components/Container/src/LazyContainer.vue
  12. 8
      src/components/Container/src/collapse/CollapseContainer.vue
  13. 2
      src/components/ContextMenu/src/createContextMenu.ts
  14. 4
      src/components/CronTab/index.ts
  15. 360
      src/components/CronTab/src/CronTabInner.vue
  16. 55
      src/components/CronTab/src/CronTabInput.vue
  17. 18
      src/components/CronTab/src/CronTabModal.vue
  18. 10
      src/components/CronTab/src/cron.data.ts
  19. 101
      src/components/CronTab/src/tabs/DayUI.vue
  20. 69
      src/components/CronTab/src/tabs/HourUI.vue
  21. 69
      src/components/CronTab/src/tabs/MinuteUI.vue
  22. 69
      src/components/CronTab/src/tabs/MonthUI.vue
  23. 69
      src/components/CronTab/src/tabs/SecondUI.vue
  24. 135
      src/components/CronTab/src/tabs/WeekUI.vue
  25. 55
      src/components/CronTab/src/tabs/YearUI.vue
  26. 204
      src/components/CronTab/src/tabs/useTabMixin.ts
  27. 50
      src/components/CronTab/src/validator.ts
  28. 2
      src/components/Description/src/typing.ts
  29. 2
      src/components/EllipsisText/src/_utils.ts
  30. 38
      src/components/Form/src/components/ApiSelect.vue
  31. 2
      src/components/Form/src/components/FileUpload.vue
  32. 21
      src/components/Form/src/components/FormItem.vue
  33. 1
      src/components/Form/src/helper.ts
  34. 5
      src/components/Form/src/hooks/useFormEvents.ts
  35. 20
      src/components/Form/src/types/index.ts
  36. 2
      src/components/Markdown/src/Markdown.vue
  37. 2
      src/components/Markdown/src/MarkdownViewer.vue
  38. 6
      src/components/Modal/src/components/ModalWrapper.vue
  39. 5
      src/components/Preview/src/Functional.vue
  40. 19
      src/components/Table/src/BasicTable.vue
  41. 4
      src/components/Table/src/components/HeaderCell.vue
  42. 2
      src/components/Table/src/components/editable/EditableCell.vue
  43. 3
      src/components/Table/src/components/editable/helper.ts
  44. 2
      src/components/Table/src/hooks/useColumns.ts
  45. 2
      src/components/Table/src/hooks/useRender.ts
  46. 1
      src/components/Table/src/hooks/useTable.ts
  47. 1
      src/components/Table/src/props.ts
  48. 2
      src/components/Table/src/types/column.ts
  49. 9
      src/components/Table/src/types/table.ts
  50. 28
      src/components/Tree/src/BasicTree.vue
  51. 191
      src/enums/systemEnum.ts
  52. 2
      src/hooks/setting/index.ts
  53. 2
      src/hooks/web/useContentHeight.ts
  54. 18
      src/hooks/web/useMessage.tsx
  55. 2
      src/hooks/web/usePermission.ts
  56. 3
      src/layouts/default/header/MultipleHeader.vue
  57. 18
      src/layouts/default/menu/index.vue
  58. 4
      src/logics/error-handle/index.ts
  59. 4
      src/router/guard/permissionGuard.ts
  60. 2
      src/router/helper/menuHelper.ts
  61. 2
      src/store/modules/lock.ts
  62. 3
      src/store/modules/multipleTab.ts
  63. 2
      src/store/modules/permission.ts
  64. 4
      src/store/modules/user.ts
  65. 2
      src/utils/cache/storageCache.ts
  66. 6
      src/utils/cipher.ts
  67. 2
      src/utils/color.ts
  68. 22
      src/utils/dateUtil.ts
  69. 2
      src/utils/domUtils.ts
  70. 2
      src/utils/env.ts
  71. 2
      src/utils/file/download.ts
  72. 3
      src/utils/http/axios/Axios.ts
  73. 6
      src/utils/index.ts
  74. 2
      src/utils/is.ts
  75. 11
      src/utils/mitt.ts
  76. 12
      src/utils/props.ts
  77. 2
      src/utils/tree.ts
  78. 41
      src/views/dashboard/analysis/components/GrowCard.vue
  79. 61
      src/views/dashboard/analysis/components/SalesProductPie.vue
  80. 34
      src/views/dashboard/analysis/components/SiteAnalysis.vue
  81. 83
      src/views/dashboard/analysis/components/VisitAnalysis.vue
  82. 47
      src/views/dashboard/analysis/components/VisitAnalysisBar.vue
  83. 91
      src/views/dashboard/analysis/components/VisitRadar.vue
  84. 79
      src/views/dashboard/analysis/components/VisitSource.vue
  85. 16
      src/views/dashboard/analysis/components/props.ts
  86. 43
      src/views/dashboard/analysis/data.ts
  87. 26
      src/views/dashboard/analysis/index.vue
  88. 34
      src/views/dashboard/workbench/components/DynamicInfo.vue
  89. 87
      src/views/dashboard/workbench/components/ProjectCard.vue
  90. 53
      src/views/dashboard/workbench/components/QuickNav.vue
  91. 91
      src/views/dashboard/workbench/components/SaleRadar.vue
  92. 36
      src/views/dashboard/workbench/components/WorkbenchHeader.vue
  93. 57
      src/views/dashboard/workbench/components/data.ts
  94. 39
      src/views/dashboard/workbench/index.vue
  95. 2
      vite.config.ts

4
.env

@ -2,7 +2,7 @@
VITE_PORT = 3000 VITE_PORT = 3000
# 网站标题 # 网站标题
VITE_GLOB_APP_TITLE = 大白充电 VITE_GLOB_APP_TITLE = AdminWebTemplate
# 简称,用于配置文件名字 不要出现空格、数字开头等特殊字符 # 简称,用于配置文件名字 不要出现空格、数字开头等特殊字符
VITE_GLOB_APP_SHORT_NAME = FAST_IOT_Admin VITE_GLOB_APP_SHORT_NAME = Admin_Web_Template

3
.env.development

@ -7,7 +7,6 @@ VITE_PUBLIC_PATH = /
# 本地开发代理,可以解决跨域及多地址代理 # 本地开发代理,可以解决跨域及多地址代理
# 如果接口地址匹配到,则会转发到http://localhost:3000,防止本地出现跨域问题 # 如果接口地址匹配到,则会转发到http://localhost:3000,防止本地出现跨域问题
# 可以有多个,注意多个不能换行,否则代理将会失效 # 可以有多个,注意多个不能换行,否则代理将会失效
# VITE_PROXY = [["/api","https://cop-demo.szsciit.com/api"]]
VITE_PROXY = [["/api","http://192.168.1.100:10408"]] VITE_PROXY = [["/api","http://192.168.1.100:10408"]]
# 是否删除Console.log # 是否删除Console.log
@ -17,7 +16,7 @@ VITE_DROP_CONSOLE = false
VITE_GLOB_API_URL = VITE_GLOB_API_URL =
# 文件上传接口 可选 # 文件上传接口 可选
VITE_GLOB_UPLOAD_URL = /api/baymax-resource/oss/endpoint/put-file VITE_GLOB_UPLOAD_URL = /api/
# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换 # 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换
VITE_GLOB_API_URL_PREFIX = /api VITE_GLOB_API_URL_PREFIX = /api

2
LICENSE

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024-present, fast-iot Copyright (c) 2024-present, admin-web-template
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

6
build/utils.ts

@ -34,7 +34,7 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
try { try {
realName = JSON.parse(realName.replace(/'/g, '"')) realName = JSON.parse(realName.replace(/'/g, '"'))
} }
catch (error) { catch {
realName = '' realName = ''
} }
} }
@ -71,8 +71,8 @@ export async function getEnvConfig(
match = 'VITE_GLOB_', match = 'VITE_GLOB_',
confFiles = getConfFiles(), confFiles = getConfFiles(),
): Promise<{ ): Promise<{
[key: string]: string [key: string]: string
}> { }> {
let envConfig = {} let envConfig = {}
for (const confFile of confFiles) { for (const confFile of confFiles) {

1
build/vite/plugin/html.ts

@ -8,7 +8,6 @@ import { createHtmlPlugin } from 'vite-plugin-html'
export function configHtmlPlugin({ isBuild }: { isBuild: boolean }) { export function configHtmlPlugin({ isBuild }: { isBuild: boolean }) {
const htmlPlugin: PluginOption[] = createHtmlPlugin({ const htmlPlugin: PluginOption[] = createHtmlPlugin({
minify: isBuild, minify: isBuild,
viteNext: true,
}) })
return htmlPlugin return htmlPlugin
} }

3
eslint.config.js

@ -10,6 +10,9 @@ export default antfu(
'vue/component-name-in-template-casing': 'off', 'vue/component-name-in-template-casing': 'off',
'vue/require-toggle-inside-transition': 'off', 'vue/require-toggle-inside-transition': 'off',
'ts/no-use-before-define': 'off', 'ts/no-use-before-define': 'off',
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'regexp/no-unused-capturing-group': 'off',
}, },
}, },
unocss.configs.flat, unocss.configs.flat,

90
package.json

@ -1,5 +1,5 @@
{ {
"name": "charing-web", "name": "admin-web-template",
"type": "module", "type": "module",
"version": "0.1.0-beta.0", "version": "0.1.0-beta.0",
"packageManager": "pnpm@8.10.0", "packageManager": "pnpm@8.10.0",
@ -24,84 +24,84 @@
"release": "bumpp -x \"pnpm run changelog\" --all" "release": "bumpp -x \"pnpm run changelog\" --all"
}, },
"dependencies": { "dependencies": {
"@ant-design/colors": "^7.0.2", "@ant-design/colors": "^7.1.0",
"@ant-design/icons-vue": "^7.0.1", "@ant-design/icons-vue": "^7.0.1",
"@codemirror/commands": "^6.3.3", "@codemirror/commands": "^6.6.0",
"@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1", "@codemirror/lang-json": "^6.0.1",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@codemirror/view": "^6.25.1", "@codemirror/view": "^6.28.4",
"@vueuse/core": "^10.6.1", "@vueuse/core": "^10.11.0",
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"ant-design-vue": "~4.0.8", "ant-design-vue": "^4.2.3",
"axios": "^1.6.4", "axios": "^1.7.2",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"cron-parser": "^4.9.0", "cron-parser": "^4.9.0",
"cropperjs": "^1.6.1", "cropperjs": "^1.6.2",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.10", "dayjs": "^1.11.11",
"echarts": "^5.4.3", "echarts": "^5.5.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-to-regexp": "^6.2.1", "path-to-regexp": "^6.2.2",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1", "pinia-plugin-persistedstate": "^3.2.1",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"qs": "^6.11.2", "qs": "^6.12.3",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"sortablejs": "^1.15.1", "sortablejs": "^1.15.2",
"tinymce": "^5.10.9", "tinymce": "^5.10.9",
"vditor": "^3.9.8", "vditor": "^3.10.4",
"vite-plugin-html": "^3.2.1", "vite-plugin-html": "^3.2.2",
"vue": "~3.3.8", "vue": "^3.4.31",
"vue-i18n": "^9.9.0", "vue-i18n": "^9.13.1",
"vue-json-pretty": "^2.3.0", "vue-json-pretty": "^2.4.0",
"vue-router": "^4.2.5", "vue-router": "^4.4.0",
"vue-types": "^5.1.1", "vue-types": "^5.1.3",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^2.6.2", "@antfu/eslint-config": "^2.22.3",
"@iconify/json": "^2.2.164", "@iconify/json": "^2.2.228",
"@types/codemirror": "^5.60.15", "@types/codemirror": "^5.60.15",
"@types/crypto-js": "^4.2.1", "@types/crypto-js": "^4.2.2",
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.0", "@types/node": "^20.14.10",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/qs": "^6.9.11", "@types/qs": "^6.9.15",
"@types/sortablejs": "^1.15.5", "@types/sortablejs": "^1.15.8",
"@unocss/eslint-config": "^0.58.3", "@unocss/eslint-config": "^0.58.9",
"@vitejs/plugin-vue": "^4.6.2", "@vitejs/plugin-vue": "^4.6.2",
"@vitejs/plugin-vue-jsx": "^3.1.0", "@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/compiler-sfc": "^3.4.10", "@vue/compiler-sfc": "^3.4.31",
"bumpp": "^9.2.1", "bumpp": "^9.4.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "^16.3.1", "dotenv": "^16.4.5",
"eslint": "^8.56.0", "eslint": "^8.57.0",
"esno": "^4.0.0", "esno": "^4.7.0",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"inquirer": "^9.2.12", "inquirer": "^9.3.5",
"less": "^4.2.0", "less": "^4.2.0",
"lint-staged": "^15.2.0", "lint-staged": "^15.2.7",
"picocolors": "^1.0.0", "picocolors": "^1.0.1",
"postcss": "^8.4.33", "postcss": "^8.4.39",
"postcss-html": "^1.5.0", "postcss-html": "^1.7.0",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"rimraf": "^5.0.5", "rimraf": "^5.0.9",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"simple-git-hooks": "^2.9.0", "simple-git-hooks": "^2.11.1",
"terser": "^5.26.0", "terser": "^5.31.2",
"typescript": "^5.3.3", "typescript": "^5.5.3",
"unocss": "^0.58.3", "unocss": "^0.61.3",
"vite": "^4.5.1", "vite": "^5.3.3",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-pwa": "^0.17.4", "vite-plugin-pwa": "^0.17.5",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vite-vue-plugin-html": "^1.0.5", "vite-vue-plugin-html": "^1.0.5",
"vue-tsc": "^1.8.27" "vue-tsc": "^2.0.26"
}, },
"simple-git-hooks": { "simple-git-hooks": {
"pre-commit": "pnpm lint-staged && pnpm type:check", "pre-commit": "pnpm lint-staged && pnpm type:check",

5380
pnpm-lock.yaml

File diff suppressed because it is too large Load Diff

1
public/resource/tinymce/langs/en.js

@ -1,3 +1,4 @@
// eslint-disable-next-line no-undef
tinymce.addI18n('es', { tinymce.addI18n('es', {
'Redo': 'Rehacer', 'Redo': 'Rehacer',
'Undo': 'Deshacer', 'Undo': 'Deshacer',

1
public/resource/tinymce/langs/zh_CN.js

@ -1,3 +1,4 @@
// eslint-disable-next-line no-undef
tinymce.addI18n('zh_CN', { tinymce.addI18n('zh_CN', {
'Redo': '\u91CD\u505A', 'Redo': '\u91CD\u505A',
'Undo': '\u64A4\u9500', 'Undo': '\u64A4\u9500',

2
src/components/Container/src/LazyContainer.vue

@ -112,7 +112,7 @@ function initIntersectionObserver() {
root: toRef(props, 'viewport'), root: toRef(props, 'viewport'),
}) })
} }
catch (e) { catch {
init() init()
} }
} }

8
src/components/Container/src/collapse/CollapseContainer.vue

@ -72,12 +72,12 @@ export default defineComponent({
<CollapseTransition enable={props.canExpan}> <CollapseTransition enable={props.canExpan}>
{props.loading {props.loading
? ( ? (
<Skeleton active={props.loading} /> <Skeleton active={props.loading} />
) )
: ( : (
<div class={`${prefixCls}__body`} v-show={show.value}> <div class={`${prefixCls}__body`} v-show={show.value}>
{slots.default?.()} {slots.default?.()}
</div> </div>
)} )}
</CollapseTransition> </CollapseTransition>
</div> </div>

2
src/components/ContextMenu/src/createContextMenu.ts

@ -49,7 +49,7 @@ export const createContextMenu = function (options: CreateContextOptions) {
try { try {
dom && body.removeChild(dom) dom && body.removeChild(dom)
} }
catch (error) {} catch {}
}) })
body.removeEventListener('click', handleClick) body.removeEventListener('click', handleClick)
body.removeEventListener('scroll', handleClick) body.removeEventListener('scroll', handleClick)

4
src/components/CronTab/index.ts

@ -1,4 +0,0 @@
export { default as CronTab } from './src/CronTabInput.vue'
export { default as CronTabInner } from './src/CronTabInner.vue'
export { default as CronTabModal } from './src/CronTabModal.vue'
export { default as CronValidator } from './src/validator'

360
src/components/CronTab/src/CronTabInner.vue

@ -1,360 +0,0 @@
<script lang="ts" setup>
import { computed, provide, reactive, ref, watch } from 'vue'
import { Col, Divider, Input, Row, TabPane, Tabs, Textarea, Tooltip } from 'ant-design-vue'
import CronParser from 'cron-parser'
import SecondUI from './tabs/SecondUI.vue'
import MinuteUI from './tabs/MinuteUI.vue'
import HourUI from './tabs/HourUI.vue'
import DayUI from './tabs/DayUI.vue'
import MonthUI from './tabs/MonthUI.vue'
import WeekUI from './tabs/WeekUI.vue'
import YearUI from './tabs/YearUI.vue'
import { cronEmits, cronProps } from './cron.data'
import { useDesign } from '@/hooks/web/useDesign'
import { dateFormat } from '@/utils/dateUtil'
import { simpleDebounce } from '@/utils'
const props = defineProps({ ...cronProps })
const emit = defineEmits([...cronEmits])
const { prefixCls } = useDesign('cron-inner')
provide('prefixCls', prefixCls)
const activeKey = ref(props.hideSecond ? 'minute' : 'second')
const second = ref('*')
const minute = ref('*')
const hour = ref('*')
const day = ref('*')
const month = ref('*')
const week = ref('?')
const year = ref('*')
const inputValues = reactive({
second: '',
minute: '',
hour: '',
day: '',
month: '',
week: '',
year: '',
cron: '',
})
const preTimeList = ref('执行预览,会忽略年份参数。')
// cron
const cronValueInner = computed(() => {
const result: string[] = []
if (!props.hideSecond)
result.push(second.value ? second.value : '*')
result.push(minute.value ? minute.value : '*')
result.push(hour.value ? hour.value : '*')
result.push(day.value ? day.value : '*')
result.push(month.value ? month.value : '*')
result.push(week.value ? week.value : '?')
if (!props.hideYear && !props.hideSecond)
result.push(year.value ? year.value : '*')
return result.join(' ')
})
//
const cronValueNoYear = computed(() => {
const v = cronValueInner.value
if (props.hideYear || props.hideSecond)
return v
const vs = v.split(' ')
if (vs.length >= 6) {
// Quartz
vs[5] = convertWeekToQuartz(vs[5])
}
return vs.slice(0, vs.length - 1).join(' ')
})
const calTriggerList = simpleDebounce(calTriggerListInner, 500)
watch(
() => props.value,
(newVal) => {
if (newVal === cronValueInner.value)
return
formatValue()
},
)
watch(cronValueInner, (newValue) => {
calTriggerList()
emitValue(newValue)
assignInput()
})
assignInput()
formatValue()
calTriggerListInner()
function assignInput() {
inputValues.second = second.value
inputValues.minute = minute.value
inputValues.hour = hour.value
inputValues.day = day.value
inputValues.month = month.value
inputValues.week = week.value
inputValues.year = year.value
inputValues.cron = cronValueInner.value
}
function formatValue() {
if (!props.value)
return
const values = props.value.split(' ').filter(item => !!item)
if (!values || values.length <= 0)
return
let i = 0
if (!props.hideSecond)
second.value = values[i++]
if (values.length > i)
minute.value = values[i++]
if (values.length > i)
hour.value = values[i++]
if (values.length > i)
day.value = values[i++]
if (values.length > i)
month.value = values[i++]
if (values.length > i)
week.value = values[i++]
if (values.length > i)
year.value = values[i]
assignInput()
}
// Quartz
// 1 = 2 = 3 = 4 = 5 = 6 = 7 =
function convertWeekToQuartz(week: string) {
const convert = (v: string) => {
if (v === '0')
return '1'
if (v === '1')
return '0'
return (Number.parseInt(v) - 1).toString()
}
// 1-7 or 1/7
const patten1 = /^([0-7])([-/])([0-7])$/
// 1,4,7
const patten2 = /^([0-7])(,[0-7])+$/
if (/^[0-7]$/.test(week)) {
return convert(week)
}
else if (patten1.test(week)) {
return week.replace(patten1, (_$0, before, separator, after) => {
if (separator === '/')
return convert(before) + separator + after
else
return convert(before) + separator + convert(after)
})
}
else if (patten2.test(week)) {
return week
.split(',')
.map(v => convert(v))
.join(',')
}
return week
}
function calTriggerListInner() {
//
if (props.remote) {
props.remote(cronValueInner.value, +new Date(), (v) => {
preTimeList.value = v
})
return
}
const format = 'yyyy-MM-dd hh:mm:ss'
const options = {
currentDate: dateFormat(new Date(), format),
}
const iter = CronParser.parseExpression(cronValueNoYear.value, options)
const result: string[] = []
for (let i = 1; i <= 10; i++)
result.push(dateFormat(new Date(iter.next() as any), format))
preTimeList.value = result.length > 0 ? result.join('\n') : '无执行时间'
}
function onInputBlur() {
second.value = inputValues.second
minute.value = inputValues.minute
hour.value = inputValues.hour
day.value = inputValues.day
month.value = inputValues.month
week.value = inputValues.week
year.value = inputValues.year
}
function onInputCronBlur(event) {
emitValue(event.target.value)
}
function emitValue(value) {
emit('change', value)
emit('update:value', value)
}
</script>
<template>
<div :class="`${prefixCls}`">
<div class="content">
<Tabs v-model:activeKey="activeKey" size="small">
<TabPane v-if="!hideSecond" key="second" tab="秒">
<SecondUI v-model:value="second" :disabled="disabled" />
</TabPane>
<TabPane key="minute" tab="分">
<MinuteUI v-model:value="minute" :disabled="disabled" />
</TabPane>
<TabPane key="hour" tab="时">
<HourUI v-model:value="hour" :disabled="disabled" />
</TabPane>
<TabPane key="day" tab="日">
<DayUI v-model:value="day" :week="week" :disabled="disabled" />
</TabPane>
<TabPane key="month" tab="月">
<MonthUI v-model:value="month" :disabled="disabled" />
</TabPane>
<TabPane key="week" tab="周">
<WeekUI v-model:value="week" :day="day" :disabled="disabled" />
</TabPane>
<TabPane v-if="!hideYear && !hideSecond" key="year" tab="年">
<YearUI v-model:value="year" :disabled="disabled" />
</TabPane>
</Tabs>
<Divider />
<!-- 执行时间预览 -->
<Row :gutter="8">
<Col :span="18" style="margin-top: 22px">
<Row :gutter="8">
<Col :span="8" style="margin-bottom: 12px">
<Input v-model:value="inputValues.second" @blur="onInputBlur">
<template #addonBefore>
<span class="allow-click" @click="activeKey = 'second'"></span>
</template>
</Input>
</Col>
<Col :span="8" style="margin-bottom: 12px">
<Input v-model:value="inputValues.minute" @blur="onInputBlur">
<template #addonBefore>
<span class="allow-click" @click="activeKey = 'minute'"></span>
</template>
</Input>
</Col>
<Col :span="8" style="margin-bottom: 12px">
<Input v-model:value="inputValues.hour" @blur="onInputBlur">
<template #addonBefore>
<span class="allow-click" @click="activeKey = 'hour'"></span>
</template>
</Input>
</Col>
<Col :span="8" style="margin-bottom: 12px">
<Input v-model:value="inputValues.day" @blur="onInputBlur">
<template #addonBefore>
<span class="allow-click" @click="activeKey = 'day'"></span>
</template>
</Input>
</Col>
<Col :span="8" style="margin-bottom: 12px">
<Input v-model:value="inputValues.month" @blur="onInputBlur">
<template #addonBefore>
<span class="allow-click" @click="activeKey = 'month'"></span>
</template>
</Input>
</Col>
<Col :span="8" style="margin-bottom: 12px">
<Input v-model:value="inputValues.week" @blur="onInputBlur">
<template #addonBefore>
<span class="allow-click" @click="activeKey = 'week'"></span>
</template>
</Input>
</Col>
<Col :span="8">
<Input v-model:value="inputValues.year" @blur="onInputBlur">
<template #addonBefore>
<span class="allow-click" @click="activeKey = 'year'"></span>
</template>
</Input>
</Col>
<Col :span="16">
<Input v-model:value="inputValues.cron" @blur="onInputCronBlur">
<template #addonBefore>
<Tooltip title="Cron表达式">
</Tooltip>
</template>
</Input>
</Col>
</Row>
</Col>
<Col :span="6">
<div>近十次执行时间不含年</div>
<Textarea :value="preTimeList" :rows="5" />
</Col>
</Row>
</div>
</div>
</template>
<style lang="less">
@prefix-cls: ~'@{namespace}-cron-inner';
.@{prefix-cls} {
.content {
.ant-checkbox-wrapper + .ant-checkbox-wrapper {
margin-left: 0;
}
}
&-config-list {
margin: 0 10px 10px;
text-align: left;
.item {
margin-top: 5px;
font-size: 14px;
span {
padding: 0 2px;
}
}
.choice {
padding: 5px 8px;
}
.w60 {
width: 60px;
min-width: 60px;
}
.w80 {
width: 80px;
min-width: 80px;
}
.list {
margin: 0 20px;
}
.list-check-item {
width: 4em;
padding: 1px 3px;
}
.list-cn .list-check-item {
width: 5em;
}
.tip-info {
color: #999;
}
}
.allow-click {
cursor: pointer;
}
}
</style>

55
src/components/CronTab/src/CronTabInput.vue

@ -1,55 +0,0 @@
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { Input } from 'ant-design-vue'
import CronTabModal from './CronTabModal.vue'
import { cronEmits, cronProps } from './cron.data'
import { useModal } from '@/components/Modal'
import { propTypes } from '@/utils/propTypes'
const props = defineProps({
...cronProps,
placeholder: propTypes.string.def('请输入cron表达式'),
exeStartTime: propTypes.oneOfType([propTypes.number, propTypes.string, propTypes.object]).def(0),
})
const emit = defineEmits([...cronEmits])
const [registerModal, { openModal }] = useModal()
const editCronValue = ref(props.value)
watch(
() => props.value,
(newVal) => {
if (newVal !== editCronValue.value)
editCronValue.value = newVal
},
)
watch(editCronValue, (newVal) => {
emit('change', newVal)
emit('update:value', newVal)
})
function showConfigModal() {
if (!props.disabled)
openModal()
}
</script>
<template>
<div>
<Input v-model:value="editCronValue" :placeholder="placeholder" :disabled="disabled">
<template #addonAfter>
<a class="cursor-pointer" :disabled="disabled ? 'disabled' : null" @click="showConfigModal">
<span class="i-ant-design:setting-outlined relative right-0.5 top-0.25" />
<span>选择</span>
</a>
</template>
</Input>
<CronTabModal
v-model:value="editCronValue"
:exe-start-time="exeStartTime"
:hide-year="hideYear"
:remote="remote"
:hide-second="hideSecond"
@register="registerModal"
/>
</div>
</template>

18
src/components/CronTab/src/CronTabModal.vue

@ -1,18 +0,0 @@
<script lang="ts" setup>
import CronTab from './CronTabInner.vue'
import { BasicModal, useModalInner } from '@/components/Modal'
defineOptions({ name: 'CronTabModal', inheritAttrs: false })
const [registerModal, { closeModal }] = useModalInner()
function onOk() {
closeModal()
}
</script>
<template>
<BasicModal title="Cron表达式" width="800px" @register="registerModal" @ok="onOk">
<CronTab v-bind="$attrs" />
</BasicModal>
</template>

10
src/components/CronTab/src/cron.data.ts

@ -1,10 +0,0 @@
import { propTypes } from '@/utils/propTypes'
export const cronEmits = ['change', 'update:value']
export const cronProps = {
value: propTypes.string.def(''),
disabled: propTypes.bool.def(false),
hideSecond: propTypes.bool.def(false),
hideYear: propTypes.bool.def(false),
remote: propTypes.func,
}

101
src/components/CronTab/src/tabs/DayUI.vue

@ -1,101 +0,0 @@
<script lang="ts">
import { computed, defineComponent, watch } from 'vue'
import { Checkbox, Input, Radio } from 'ant-design-vue'
import { TypeEnum, useTabEmits, useTabProps, useTabSetup } from './useTabMixin'
export default defineComponent({
name: 'DayUI',
components: { AInput: Input, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group },
props: useTabProps({
defaultValue: '*',
props: {
week: { type: String, default: '?' },
},
}),
emits: useTabEmits(),
setup(props, context) {
const disabledChoice = computed(() => {
return (props.week && props.week !== '?') || props.disabled
})
const setup = useTabSetup(props, context, {
defaultValue: '*',
valueWork: 1,
minValue: 1,
maxValue: 31,
valueRange: { start: 1, end: 31 },
valueLoop: { start: 1, interval: 1 },
disabled: disabledChoice,
})
const typeWorkAttrs = computed(() => ({
disabled: setup.type.value !== TypeEnum.work || props.disabled || disabledChoice.value,
...setup.inputNumberAttrs.value,
}))
watch(
() => props.week,
() => {
setup.updateValue(disabledChoice.value ? '?' : setup.computeValue.value)
},
)
return { ...setup, typeWorkAttrs }
},
})
</script>
<template>
<div :class="`${prefixCls}-config-list`">
<RadioGroup v-model:value="type">
<div class="item">
<Radio :value="TypeEnum.unset" v-bind="beforeRadioAttrs">
不设置
</Radio>
<span class="tip-info">日和周只能设置其中之一</span>
</div>
<div class="item">
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">
每日
</Radio>
</div>
<div class="item">
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">
区间
</Radio>
<span> </span>
<AInput v-model:value="valueRange.start" type="number" v-bind="typeRangeAttrs" />
<span> </span>
<AInput v-model:value="valueRange.end" type="number" v-bind="typeRangeAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">
循环
</Radio>
<span> </span>
<AInput v-model:value="valueLoop.start" type="number" class="w-4" v-bind="typeLoopAttrs" />
<span> 日开始间隔 </span>
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.last" v-bind="beforeRadioAttrs">
最后一日
</Radio>
</div>
<div class="item">
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">
指定
</Radio>
<div class="list">
<CheckboxGroup v-model:value="valueList">
<template v-for="i in specifyRange" :key="i">
<Checkbox :value="i" v-bind="typeSpecifyAttrs">
{{ i }}
</Checkbox>
</template>
</CheckboxGroup>
</div>
</div>
</RadioGroup>
</div>
</template>

69
src/components/CronTab/src/tabs/HourUI.vue

@ -1,69 +0,0 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { Checkbox, Input, Radio } from 'ant-design-vue'
import { useTabEmits, useTabProps, useTabSetup } from './useTabMixin'
export default defineComponent({
name: 'HourUI',
components: { AInput: Input, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group },
props: useTabProps({
defaultValue: '*',
}),
emits: useTabEmits(),
setup(props, context) {
return useTabSetup(props, context, {
defaultValue: '*',
minValue: 0,
maxValue: 23,
valueRange: { start: 0, end: 23 },
valueLoop: { start: 0, interval: 1 },
})
},
})
</script>
<template>
<div :class="`${prefixCls}-config-list`">
<RadioGroup v-model:value="type">
<div class="item">
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">
每时
</Radio>
</div>
<div class="item">
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">
区间
</Radio>
<span> </span>
<AInput v-model:value="valueRange.start" type="number" v-bind="typeRangeAttrs" />
<span> </span>
<AInput v-model:value="valueRange.end" type="number" v-bind="typeRangeAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">
循环
</Radio>
<span> </span>
<AInput v-model:value="valueLoop.start" type="number" v-bind="typeLoopAttrs" />
<span> 时开始间隔 </span>
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">
指定
</Radio>
<div class="list">
<CheckboxGroup v-model:value="valueList">
<template v-for="i in specifyRange" :key="i">
<Checkbox :value="i" v-bind="typeSpecifyAttrs">
{{ i }}
</Checkbox>
</template>
</CheckboxGroup>
</div>
</div>
</RadioGroup>
</div>
</template>

69
src/components/CronTab/src/tabs/MinuteUI.vue

@ -1,69 +0,0 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { Checkbox, Input, Radio } from 'ant-design-vue'
import { useTabEmits, useTabProps, useTabSetup } from './useTabMixin'
export default defineComponent({
name: 'MinuteUI',
components: { AInput: Input, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group },
props: useTabProps({
defaultValue: '*',
}),
emits: useTabEmits(),
setup(props, context) {
return useTabSetup(props, context, {
defaultValue: '*',
minValue: 0,
maxValue: 59,
valueRange: { start: 0, end: 59 },
valueLoop: { start: 0, interval: 1 },
})
},
})
</script>
<template>
<div :class="`${prefixCls}-config-list`">
<RadioGroup v-model:value="type">
<div class="item">
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">
每分
</Radio>
</div>
<div class="item">
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">
区间
</Radio>
<span> </span>
<AInput v-model:value="valueRange.start" type="number" v-bind="typeRangeAttrs" />
<span> </span>
<AInput v-model:value="valueRange.end" type="number" v-bind="typeRangeAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">
循环
</Radio>
<span> </span>
<AInput v-model:value="valueLoop.start" type="number" v-bind="typeLoopAttrs" />
<span> 分开始间隔 </span>
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">
指定
</Radio>
<div class="list">
<CheckboxGroup v-model:value="valueList">
<template v-for="i in specifyRange" :key="i">
<Checkbox :value="i" v-bind="typeSpecifyAttrs">
{{ i }}
</Checkbox>
</template>
</CheckboxGroup>
</div>
</div>
</RadioGroup>
</div>
</template>

69
src/components/CronTab/src/tabs/MonthUI.vue

@ -1,69 +0,0 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { Checkbox, Input, Radio } from 'ant-design-vue'
import { useTabEmits, useTabProps, useTabSetup } from './useTabMixin'
export default defineComponent({
name: 'MonthUI',
components: { AInput: Input, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group },
props: useTabProps({
defaultValue: '*',
}),
emits: useTabEmits(),
setup(props, context) {
return useTabSetup(props, context, {
defaultValue: '*',
minValue: 1,
maxValue: 12,
valueRange: { start: 1, end: 12 },
valueLoop: { start: 1, interval: 1 },
})
},
})
</script>
<template>
<div :class="`${prefixCls}-config-list`">
<RadioGroup v-model:value="type">
<div class="item">
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">
每月
</Radio>
</div>
<div class="item">
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">
区间
</Radio>
<span> </span>
<AInput v-model:value="valueRange.start" type="number" v-bind="typeRangeAttrs" />
<span> </span>
<AInput v-model:value="valueRange.end" type="number" v-bind="typeRangeAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">
循环
</Radio>
<span> </span>
<AInput v-model:value="valueLoop.start" type="number" v-bind="typeLoopAttrs" />
<span> 月开始间隔 </span>
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">
指定
</Radio>
<div class="list">
<CheckboxGroup v-model:value="valueList">
<template v-for="i in specifyRange" :key="i">
<Checkbox :value="i" v-bind="typeSpecifyAttrs">
{{ i }}
</Checkbox>
</template>
</CheckboxGroup>
</div>
</div>
</RadioGroup>
</div>
</template>

69
src/components/CronTab/src/tabs/SecondUI.vue

@ -1,69 +0,0 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { Checkbox, Input, Radio } from 'ant-design-vue'
import { useTabEmits, useTabProps, useTabSetup } from './useTabMixin'
export default defineComponent({
name: 'SecondUI',
components: { AInput: Input, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group },
props: useTabProps({
defaultValue: '*',
}),
emits: useTabEmits(),
setup(props, context) {
return useTabSetup(props, context, {
defaultValue: '*',
minValue: 0,
maxValue: 59,
valueRange: { start: 0, end: 59 },
valueLoop: { start: 0, interval: 1 },
})
},
})
</script>
<template>
<div :class="`${prefixCls}-config-list`">
<RadioGroup v-model:value="type">
<div class="item">
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">
每秒
</Radio>
</div>
<div class="item">
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">
区间
</Radio>
<span> </span>
<AInput v-model:value="valueRange.start" type="number" v-bind="typeRangeAttrs" />
<span> </span>
<AInput v-model:value="valueRange.end" type="number" v-bind="typeRangeAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">
循环
</Radio>
<span> </span>
<AInput v-model:value="valueLoop.start" type="number" v-bind="typeLoopAttrs" />
<span> 秒开始间隔 </span>
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">
指定
</Radio>
<div class="list">
<CheckboxGroup v-model:value="valueList">
<template v-for="i in specifyRange" :key="i">
<Checkbox :value="i" v-bind="typeSpecifyAttrs">
{{ i }}
</Checkbox>
</template>
</CheckboxGroup>
</div>
</div>
</RadioGroup>
</div>
</template>

135
src/components/CronTab/src/tabs/WeekUI.vue

@ -1,135 +0,0 @@
<script lang="ts">
import { computed, defineComponent, watch } from 'vue'
import { Checkbox, Input, Radio, Select } from 'ant-design-vue'
import { TypeEnum, useTabEmits, useTabProps, useTabSetup } from './useTabMixin'
const WEEK_MAP_EN = {
1: 'SUN',
2: 'MON',
3: 'TUE',
4: 'WED',
5: 'THU',
6: 'FRI',
7: 'SAT',
}
const WEEK_MAP_CN = {
1: '周日',
2: '周一',
3: '周二',
4: '周三',
5: '周四',
6: '周五',
7: '周六',
}
export default defineComponent({
name: 'WeekUI',
components: { AInput: Input, ASelect: Select, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group },
props: useTabProps({
defaultValue: '?',
props: {
day: { type: String, default: '*' },
},
}),
emits: useTabEmits(),
setup(props, context) {
const disabledChoice = computed(() => {
return (props.day && props.day !== '?') || props.disabled
})
const setup = useTabSetup(props, context, {
defaultType: TypeEnum.unset,
defaultValue: '?',
minValue: 1,
maxValue: 7,
// 0,7 1
valueRange: { start: 1, end: 7 },
valueLoop: { start: 2, interval: 1 },
disabled: disabledChoice,
})
const weekOptions = computed(() => {
const options: { label: string, value: number }[] = []
for (const weekKey of Object.keys(WEEK_MAP_CN)) {
const weekName: string = WEEK_MAP_CN[weekKey]
options.push({
value: Number.parseInt(weekKey),
label: weekName,
})
}
return options
})
const typeRangeSelectAttrs = computed(() => ({
class: ['w80'],
disabled: setup.typeRangeAttrs.value.disabled,
}))
const typeLoopSelectAttrs = computed(() => ({
class: ['w80'],
disabled: setup.typeLoopAttrs.value.disabled,
}))
watch(
() => props.day,
() => {
setup.updateValue(disabledChoice.value ? '?' : setup.computeValue.value)
},
)
return {
...setup,
weekOptions,
typeLoopSelectAttrs,
typeRangeSelectAttrs,
WEEK_MAP_CN,
WEEK_MAP_EN,
}
},
})
</script>
<template>
<div :class="`${prefixCls}-config-list`">
<RadioGroup v-model:value="type">
<div class="item">
<Radio :value="TypeEnum.unset" v-bind="beforeRadioAttrs">
不设置
</Radio>
<span class="tip-info">日和周只能设置其中之一</span>
</div>
<div class="item">
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">
区间
</Radio>
<span> </span>
<ASelect v-model:value="valueRange.start" :options="weekOptions" v-bind="typeRangeSelectAttrs" />
<span> </span>
<ASelect v-model:value="valueRange.end" :options="weekOptions" v-bind="typeRangeSelectAttrs" />
</div>
<div class="item">
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">
循环
</Radio>
<span> </span>
<ASelect v-model:value="valueLoop.start" :options="weekOptions" v-bind="typeLoopSelectAttrs" />
<span> 开始间隔 </span>
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">
指定
</Radio>
<div class="list list-cn">
<CheckboxGroup v-model:value="valueList">
<template v-for="opt in weekOptions" :key="opt.value">
<Checkbox :value="opt.value" v-bind="typeSpecifyAttrs">
{{ opt.label }}
</Checkbox>
</template>
</CheckboxGroup>
</div>
</div>
</RadioGroup>
</div>
</template>

55
src/components/CronTab/src/tabs/YearUI.vue

@ -1,55 +0,0 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { Input, Radio } from 'ant-design-vue'
import { useTabEmits, useTabProps, useTabSetup } from './useTabMixin'
export default defineComponent({
name: 'YearUI',
components: { AInput: Input, Radio, RadioGroup: Radio.Group },
props: useTabProps({
defaultValue: '*',
}),
emits: useTabEmits(),
setup(props, context) {
const nowYear = new Date().getFullYear()
return useTabSetup(props, context, {
defaultValue: '*',
minValue: 0,
valueRange: { start: nowYear, end: nowYear + 100 },
valueLoop: { start: nowYear, interval: 1 },
})
},
})
</script>
<template>
<div :class="`${prefixCls}-config-list`">
<RadioGroup v-model:value="type">
<div class="item">
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">
每年
</Radio>
</div>
<div class="item">
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">
区间
</Radio>
<span> </span>
<AInput v-model:value="valueRange.start" type="number" class="w80" v-bind="typeRangeAttrs" />
<span> </span>
<AInput v-model:value="valueRange.end" type="number" class="w80" v-bind="typeRangeAttrs" />
<span> </span>
</div>
<div class="item">
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">
循环
</Radio>
<span> </span>
<AInput v-model:value="valueLoop.start" type="number" class="w80" v-bind="typeLoopAttrs" />
<span> 年开始间隔 </span>
<AInput v-model:value="valueLoop.interval" type="number" class="w80" v-bind="typeLoopAttrs" />
<span> </span>
</div>
</RadioGroup>
</div>
</template>

204
src/components/CronTab/src/tabs/useTabMixin.ts

@ -1,204 +0,0 @@
// 主要用于日和星期的互斥使用
import { computed, inject, reactive, ref, unref, watch } from 'vue'
import { propTypes } from '@/utils/propTypes'
export enum TypeEnum {
unset = 'UNSET',
every = 'EVERY',
range = 'RANGE',
loop = 'LOOP',
work = 'WORK',
last = 'LAST',
specify = 'SPECIFY',
}
// use 公共 props
export function useTabProps(options) {
const defaultValue = options?.defaultValue ?? '?'
return {
value: propTypes.string.def(defaultValue),
disabled: propTypes.bool.def(false),
...options?.props,
}
}
// use 公共 emits
export function useTabEmits() {
return ['change', 'update:value']
}
// use 公共 setup
export function useTabSetup(props, context, options) {
const { emit } = context
const prefixCls = inject('prefixCls')
const defaultValue = ref(options?.defaultValue ?? '?')
// 类型
const type = ref(options.defaultType ?? TypeEnum.every)
const valueList = ref<any[]>([])
// 对于不同的类型,所定义的值也有所不同
const valueRange = reactive(options.valueRange)
const valueLoop = reactive(options.valueLoop)
const valueWeek = reactive(options.valueWeek)
const valueWork = ref(options.valueWork)
const maxValue = ref(options.maxValue)
const minValue = ref(options.minValue)
// 根据不同的类型计算出的value
const computeValue = computed(() => {
const valueArray: any[] = []
switch (type.value) {
case TypeEnum.unset:
valueArray.push('?')
break
case TypeEnum.every:
valueArray.push('*')
break
case TypeEnum.range:
valueArray.push(`${valueRange.start}-${valueRange.end}`)
break
case TypeEnum.loop:
valueArray.push(`${valueLoop.start}/${valueLoop.interval}`)
break
case TypeEnum.work:
valueArray.push(`${valueWork.value}W`)
break
case TypeEnum.last:
valueArray.push('L')
break
case TypeEnum.specify:
if (valueList.value.length === 0)
valueList.value.push(minValue.value)
valueArray.push(valueList.value.join(','))
break
default:
valueArray.push(defaultValue.value)
break
}
return valueArray.length > 0 ? valueArray.join('') : defaultValue.value
})
// 指定值范围区间,介于最小值和最大值之间
const specifyRange = computed(() => {
const range: number[] = []
if (maxValue.value != null) {
for (let i = minValue.value; i <= maxValue.value; i++)
range.push(i)
}
return range
})
watch(
() => props.value,
(val) => {
if (val !== computeValue.value)
parseValue(val)
},
{ immediate: true },
)
watch(computeValue, v => updateValue(v))
function updateValue(value) {
emit('change', value)
emit('update:value', value)
}
/**
* parseValue
* @param value
*/
function parseValue(value) {
if (value === computeValue.value)
return
try {
if (!value || value === defaultValue.value) {
type.value = TypeEnum.every
}
else if (value.includes('?')) {
type.value = TypeEnum.unset
}
else if (value.includes('-')) {
type.value = TypeEnum.range
const values = value.split('-')
if (values.length >= 2) {
valueRange.start = Number.parseInt(values[0])
valueRange.end = Number.parseInt(values[1])
}
}
else if (value.includes('/')) {
type.value = TypeEnum.loop
const values = value.split('/')
if (values.length >= 2) {
valueLoop.start = value[0] === '*' ? 0 : Number.parseInt(values[0])
valueLoop.interval = Number.parseInt(values[1])
}
}
else if (value.includes('W')) {
type.value = TypeEnum.work
const values = value.split('W')
if (!values[0] && !Number.isNaN(values[0]))
valueWork.value = Number.parseInt(values[0])
}
else if (value.includes('L')) {
type.value = TypeEnum.last
}
else if (value.includes(',') || !Number.isNaN(value)) {
type.value = TypeEnum.specify
valueList.value = value.split(',').map(item => Number.parseInt(item))
}
else {
type.value = TypeEnum.every
}
}
catch (e) {
type.value = TypeEnum.every
}
}
const beforeRadioAttrs = computed(() => ({
class: ['choice'],
disabled: props.disabled || unref(options.disabled),
}))
const inputNumberAttrs = computed(() => ({
class: ['w60'],
max: maxValue.value,
min: minValue.value,
precision: 0,
}))
const typeRangeAttrs = computed(() => ({
disabled: type.value !== TypeEnum.range || props.disabled || unref(options.disabled),
...inputNumberAttrs.value,
}))
const typeLoopAttrs = computed(() => ({
disabled: type.value !== TypeEnum.loop || props.disabled || unref(options.disabled),
...inputNumberAttrs.value,
}))
const typeSpecifyAttrs = computed(() => ({
disabled: type.value !== TypeEnum.specify || props.disabled || unref(options.disabled),
class: ['list-check-item'],
}))
return {
type,
TypeEnum,
prefixCls,
defaultValue,
valueRange,
valueLoop,
valueWeek,
valueList,
valueWork,
maxValue,
minValue,
computeValue,
specifyRange,
updateValue,
parseValue,
beforeRadioAttrs,
inputNumberAttrs,
typeRangeAttrs,
typeLoopAttrs,
typeSpecifyAttrs,
}
}

50
src/components/CronTab/src/validator.ts

@ -1,50 +0,0 @@
/* eslint-disable prefer-promise-reject-errors */
import CronParser from 'cron-parser'
import type { ValidatorRule } from 'ant-design-vue/lib/form/interface'
const cronRule: ValidatorRule = {
// eslint-disable-next-line no-empty-pattern
validator({}, value) {
// 没填写就不校验
if (!value)
return Promise.resolve()
const values: string[] = value.split(' ').filter(item => !!item)
if (values.length > 7)
return Promise.reject('Cron表达式最多7项!')
// 检查第7项
let val: string = value
if (values.length === 7) {
const year = values[6]
if (year !== '*' && year !== '?') {
let yearValues: string[] = []
if (year.includes('-'))
yearValues = year.split('-')
else if (year.indexOf('/'))
yearValues = year.split('/')
else
yearValues = [year]
// 判断是否都是数字
const checkYear = yearValues.some(item => Number.isNaN(Number(item)))
if (checkYear)
return Promise.reject(`Cron表达式参数[年]错误:${year}`)
}
// 取其中的前六项
val = values.slice(0, 6).join(' ')
}
// 6位 没有年
// 5位没有秒、年
try {
const iter = CronParser.parseExpression(val)
iter.next()
return Promise.resolve()
}
catch (e: any) {
return Promise.reject(`Cron表达式错误:${e}`)
}
},
}
export default cronRule.validator

2
src/components/Description/src/typing.ts

@ -36,7 +36,7 @@ export interface DescriptionProps extends DescriptionsProps {
} }
export interface DescInstance { export interface DescInstance {
setDescProps(descProps: Partial<DescriptionProps>): void setDescProps: (descProps: Partial<DescriptionProps>) => void
} }
export type Register = (descInstance: DescInstance) => void export type Register = (descInstance: DescInstance) => void

2
src/components/EllipsisText/src/_utils.ts

@ -1,7 +1,7 @@
// cancelAnimationFrame // cancelAnimationFrame
export const cancelAnimationFrame = window.cancelAnimationFrame export const cancelAnimationFrame = window.cancelAnimationFrame
// 使用 requestAnimationFrame 模拟 setTimeout 和 setInterval // 使用 requestAnimationFrame 模拟 setTimeout 和 setInterval
export function rafTimeout(fn: Function, delay = 0, interval = false): object { export function rafTimeout(fn: (...args: any) => any, delay = 0, interval = false): object {
const requestAnimationFrame const requestAnimationFrame
= typeof window !== 'undefined' ? window.requestAnimationFrame : () => {} = typeof window !== 'undefined' ? window.requestAnimationFrame : () => {}
let start: any = null let start: any = null

38
src/components/Form/src/components/ApiSelect.vue

@ -3,19 +3,14 @@ import type { PropType } from 'vue'
import { computed, ref, unref, watch } from 'vue' import { computed, ref, unref, watch } from 'vue'
import { Select } from 'ant-design-vue' import { Select } from 'ant-design-vue'
import type { SelectValue } from 'ant-design-vue/es/select' import type { SelectValue } from 'ant-design-vue/es/select'
import { get, omit } from 'lodash-es' import { get, isEqual, omit } from 'lodash-es'
import { LoadingOutlined } from '@ant-design/icons-vue' import { LoadingOutlined } from '@ant-design/icons-vue'
import { isFunction } from '@/utils/is' import { isFunction } from '@/utils/is'
import { useRuleFormItem } from '@/hooks/component/useFormItem' import { useRuleFormItem } from '@/hooks/component/useFormItem'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
interface OptionsItem { interface OptionsItem { label?: string, value?: string, disabled?: boolean, [name: string]: any }
label?: string
value?: string
disabled?: boolean
[key: string]: any
}
defineOptions({ name: 'ApiSelect', inheritAttrs: false }) defineOptions({ name: 'ApiSelect', inheritAttrs: false })
@ -39,13 +34,15 @@ const props = defineProps({
default: [], default: [],
}, },
}) })
const emit = defineEmits(['options-change', 'change', 'update:value']) const emit = defineEmits(['options-change', 'change', 'update:value'])
const options = ref<OptionsItem[]>([])
const optionsRef = ref<OptionsItem[]>([])
const loading = ref(false) const loading = ref(false)
// //
const isFirstLoaded = ref(false) const isFirstLoaded = ref(false)
const emitData = ref<OptionsItem[]>([]) const emitData = ref<OptionsItem[]>([])
const { t } = useI18n() const { t } = useI18n()
// Embedded in the form, just use the hook binding to perform form verification // Embedded in the form, just use the hook binding to perform form verification
@ -54,7 +51,7 @@ const [state] = useRuleFormItem(props, 'value', 'change', emitData)
const getOptions = computed(() => { const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props const { labelField, valueField, numberToString } = props
const data = unref(options).reduce((prev, next: any) => { const data = unref(optionsRef).reduce((prev, next: any) => {
if (next) { if (next) {
const value = get(next, valueField) const value = get(next, valueField)
prev.push({ prev.push({
@ -77,8 +74,10 @@ watch(
watch( watch(
() => props.params, () => props.params,
() => { (value, oldValue) => {
!unref(isFirstLoaded) && fetch() if (isEqual(value, oldValue))
return
fetch()
}, },
{ deep: true, immediate: props.immediate }, { deep: true, immediate: props.immediate },
) )
@ -87,33 +86,33 @@ async function fetch() {
const api = props.api const api = props.api
if (!api || !isFunction(api) || loading.value) if (!api || !isFunction(api) || loading.value)
return return
options.value = [] optionsRef.value = []
try { try {
loading.value = true loading.value = true
const res = await api(props.params) const res = await api(props.params)
isFirstLoaded.value = true isFirstLoaded.value = true
if (Array.isArray(res)) { if (Array.isArray(res)) {
options.value = res optionsRef.value = res
emitChange() emitChange()
return return
} }
if (props.resultField) if (props.resultField)
options.value = get(res, props.resultField) || [] optionsRef.value = get(res, props.resultField) || []
emitChange() emitChange()
} }
catch (error) { catch (error) {
console.warn(error) console.warn(error)
// reset status
isFirstLoaded.value = false
} }
finally { finally {
loading.value = false loading.value = false
// reset status
isFirstLoaded.value = false
} }
} }
async function handleFetch(open: boolean) { async function handleFetch(visible: boolean) {
if (open) { if (visible) {
if (props.alwaysLoad) if (props.alwaysLoad)
await fetch() await fetch()
else if (!props.immediate && !unref(isFirstLoaded)) else if (!props.immediate && !unref(isFirstLoaded))
@ -126,7 +125,6 @@ function emitChange() {
} }
function handleChange(_, ...args) { function handleChange(_, ...args) {
emit('change', args[0] ? args[0].value : undefined)
emitData.value = args emitData.value = args
} }
</script> </script>

2
src/components/Form/src/components/FileUpload.vue

@ -264,7 +264,7 @@ function uidGenerator() {
} }
function getFileName(path) { function getFileName(path) {
if (path.lastIndexOf('\\') >= 0) { if (path.includes('\\')) {
const reg = /\\/g const reg = /\\/g
path = path.replace(reg, '/') path = path.replace(reg, '/')
} }

21
src/components/Form/src/components/FormItem.vue

@ -197,7 +197,10 @@ export default defineComponent({
*/ */
if (getRequired) { if (getRequired) {
if (!rules || rules.length === 0) { if (!rules || rules.length === 0) {
rules = [{ required: getRequired, validator }] const trigger = NO_AUTO_LINK_COMPONENTS.includes(component || 'Input')
? 'blur'
: 'change'
rules = [{ required: getRequired, validator, trigger }]
} }
else { else {
const requiredIndex: number = rules.findIndex(rule => Reflect.has(rule, 'required')) const requiredIndex: number = rules.findIndex(rule => Reflect.has(rule, 'required'))
@ -265,12 +268,12 @@ export default defineComponent({
const on = { const on = {
[eventKey]: (...args: Nullable<Recordable<any>>[]) => { [eventKey]: (...args: Nullable<Recordable<any>>[]) => {
const [e] = args const [e] = args
if (propsData[eventKey])
propsData[eventKey](...args)
const target = e ? e.target : null const target = e ? e.target : null
const value = target ? (isCheck ? target.checked : target.value) : e const value = target ? (isCheck ? target.checked : target.value) : e
props.setFormModel(field, value, props.schema) props.setFormModel(field, value, props.schema)
if (propsData[eventKey])
propsData[eventKey](...args)
}, },
} }
const Comp = componentMap.get(component) as ReturnType<typeof defineComponent> const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>
@ -309,11 +312,11 @@ export default defineComponent({
const { label, helpMessage, helpComponentProps, subLabel } = props.schema const { label, helpMessage, helpComponentProps, subLabel } = props.schema
const renderLabel = subLabel const renderLabel = subLabel
? ( ? (
<span> <span>
{label} {label}
{' '} {' '}
<span class="text-secondary">{subLabel}</span> <span class="text-secondary">{subLabel}</span>
</span> </span>
) )
: ( : (
label label

1
src/components/Form/src/helper.ts

@ -85,4 +85,5 @@ export const NO_AUTO_LINK_COMPONENTS: ComponentType[] = [
'AutoComplete', 'AutoComplete',
'RadioButtonGroup', 'RadioButtonGroup',
'ImageUpload', 'ImageUpload',
'ApiSelect',
] ]

5
src/components/Form/src/hooks/useFormEvents.ts

@ -165,7 +165,7 @@ export function useFormEvents({
validKeys.push(nestKey) validKeys.push(nestKey)
} }
} }
catch (e) { catch {
// key not exist // key not exist
if (isDef(defaultValueRef.value[nestKey])) if (isDef(defaultValueRef.value[nestKey]))
unref(formModel)[nestKey] = cloneDeep(unref(defaultValueRef.value[nestKey])) unref(formModel)[nestKey] = cloneDeep(unref(defaultValueRef.value[nestKey]))
@ -313,8 +313,9 @@ export function useFormEvents({
&& (!(item.field in currentFieldsValue) && (!(item.field in currentFieldsValue)
|| isNil(currentFieldsValue[item.field]) || isNil(currentFieldsValue[item.field])
|| isEmpty(currentFieldsValue[item.field])) || isEmpty(currentFieldsValue[item.field]))
) ) {
obj[item.field] = item.defaultValue obj[item.field] = item.defaultValue
}
}) })
setFieldsValue(obj) setFieldsValue(obj)
} }

20
src/components/Form/src/types/index.ts

@ -1,4 +1,4 @@
import type { Component, VNodeProps } from 'vue' import type { Component, Ref, VNodeProps } from 'vue'
type ColSpanType = number | string type ColSpanType = number | string
export interface ColEx { export interface ColEx {
@ -92,7 +92,7 @@ type MethodsNameToCamelCase<
: `${M}${T}` : `${M}${T}`
type MethodsNameTransform<T> = { type MethodsNameTransform<T> = {
[K in keyof T as K extends `on${string}` ? MethodsNameToCamelCase<K> : never]: T[K]; [K in keyof T as K extends `on${string}` ? MethodsNameToCamelCase<K> : never]: T[K]
} }
type ExtractPropTypes<T extends Component> = T extends new (...args: any) => any type ExtractPropTypes<T extends Component> = T extends new (...args: any) => any
@ -113,16 +113,14 @@ interface _CustomComponents {
ApiTransfer: ExtractPropTypes<(typeof import('../components/ApiTransfer.vue'))['default']> ApiTransfer: ExtractPropTypes<(typeof import('../components/ApiTransfer.vue'))['default']>
InputCountDown: ExtractPropTypes<(typeof import('@/components/CountDown/src/CountdownInput.vue'))['default']> InputCountDown: ExtractPropTypes<(typeof import('@/components/CountDown/src/CountdownInput.vue'))['default']>
FileUpload: ExtractPropTypes<(typeof import('../components/FileUpload.vue'))['default']> FileUpload: ExtractPropTypes<(typeof import('../components/FileUpload.vue'))['default']>
Editor: ExtractPropTypes<(typeof import('@/components/Tinymce/src/Editor.vue'))['default']>
CronTab: ExtractPropTypes<(typeof import('@/components/CronTab/src/CronTabInput.vue'))['default']>
ApiCheckboxGroup: ExtractPropTypes<(typeof import('../components/ApiCheckboxGroup.vue'))['default']> ApiCheckboxGroup: ExtractPropTypes<(typeof import('../components/ApiCheckboxGroup.vue'))['default']>
} }
type CustomComponents<T = _CustomComponents> = { type CustomComponents<T = _CustomComponents> = {
[K in keyof T]: T[K] & MethodsNameTransform<T[K]>; [K in keyof T]: T[K] & MethodsNameTransform<T[K]>
} }
export interface ComponentProps { interface _ComponentProps {
Input: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['default']> Input: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['default']>
InputGroup: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['InputGroup']> InputGroup: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['InputGroup']>
InputPassword: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['InputPassword']> InputPassword: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['InputPassword']>
@ -160,7 +158,13 @@ export interface ComponentProps {
Divider: ExtractPropTypes<(typeof import('ant-design-vue/es/divider'))['default']> Divider: ExtractPropTypes<(typeof import('ant-design-vue/es/divider'))['default']>
ApiTransfer: CustomComponents['ApiTransfer'] & ExtractPropTypes<(typeof import('ant-design-vue/es/transfer'))['default']> ApiTransfer: CustomComponents['ApiTransfer'] & ExtractPropTypes<(typeof import('ant-design-vue/es/transfer'))['default']>
FileUpload: CustomComponents['FileUpload'] & ExtractPropTypes<(typeof import('ant-design-vue/es/upload'))['default']> FileUpload: CustomComponents['FileUpload'] & ExtractPropTypes<(typeof import('ant-design-vue/es/upload'))['default']>
Editor: CustomComponents['Editor']
CronTab: CustomComponents['CronTab']
ApiCheckboxGroup: CustomComponents['ApiCheckboxGroup'] & ComponentProps['CheckboxGroup'] ApiCheckboxGroup: CustomComponents['ApiCheckboxGroup'] & ComponentProps['CheckboxGroup']
} }
type ExtendRefTypes<T> = T extends any ? Ref<T> : never
export type ComponentProps = {
[K in keyof _ComponentProps]: {
[Key in keyof _ComponentProps[K]]: _ComponentProps[K][Key] | ExtendRefTypes<_ComponentProps[K][Key]>
}
}

2
src/components/Markdown/src/Markdown.vue

@ -132,7 +132,7 @@ function destroy() {
try { try {
vditorInstance?.destroy?.() vditorInstance?.destroy?.()
} }
catch (error) {} catch {}
vditorRef.value = null vditorRef.value = null
initedRef.value = false initedRef.value = false
} }

2
src/components/Markdown/src/MarkdownViewer.vue

@ -51,7 +51,7 @@ function destroy() {
try { try {
vditorInstance?.destroy?.() vditorInstance?.destroy?.()
} }
catch (error) {} catch {}
vditorPreviewRef.value = null vditorPreviewRef.value = null
} }

6
src/components/Modal/src/components/ModalWrapper.vue

@ -81,9 +81,11 @@ onUnmounted(() => {
async function scrollTop() { async function scrollTop() {
nextTick(() => { nextTick(() => {
const wrapperRefDom = unref(wrapperRef) const wrapperRefDom = unref(wrapperRef)
if (!wrapperRefDom) if (!wrapperRefDom) {
return return
;(wrapperRefDom as any)?.scrollTo?.(0) }
(wrapperRefDom as any)?.scrollTo?.(0)
}) })
} }

5
src/components/Preview/src/Functional.vue

@ -103,10 +103,11 @@ export default defineComponent({
// //
function initMouseWheel() { function initMouseWheel() {
const wrapEl = unref(wrapElRef) const wrapEl = unref(wrapElRef)
if (!wrapEl) if (!wrapEl) {
return return
}
;(wrapEl as any).onmousewheel = scrollFunc (wrapEl as any).onmousewheel = scrollFunc
// onmousewheelDOMMouseScroll // onmousewheelDOMMouseScroll
document.body.addEventListener('DOMMouseScroll', scrollFunc) document.body.addEventListener('DOMMouseScroll', scrollFunc)
// //

19
src/components/Table/src/BasicTable.vue

@ -71,9 +71,8 @@ const getProps = computed(() => {
const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false) const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false)
watchEffect(() => { watchEffect(() => {
unref(isFixedHeightPage) if (unref(isFixedHeightPage) && props.canResize)
&& props.canResize warn('\'canResize\' of BasicTable may not work in PageWrapper with \'fixedHeight\' (especially in hot updates)')
&& warn('\'canResize\' of BasicTable may not work in PageWrapper with \'fixedHeight\' (especially in hot updates)')
}) })
const { getLoading, setLoading } = useLoading(getProps) const { getLoading, setLoading } = useLoading(getProps)
@ -130,7 +129,8 @@ function handleTableChange(pagination: any, filters: any, sorter: any, extra: an
emit('change', pagination, filters, sorter) emit('change', pagination, filters, sorter)
// useTableonChange // useTableonChange
const { onChange } = unref(getProps) const { onChange } = unref(getProps)
onChange && isFunction(onChange) && onChange(pagination, filters, sorter, extra) if (onChange && isFunction(onChange))
onChange(pagination, filters, sorter, extra)
} }
const { const {
@ -331,13 +331,14 @@ emit('register', tableAction, formActions)
<slot name="bodyCell" v-bind="((data || {}) as SlotBodyCellProps<T>)" /> <slot name="bodyCell" v-bind="((data || {}) as SlotBodyCellProps<T>)" />
</template> </template>
<template #expandIcon="{ expanded, record, onExpand }"> <template #expandIcon="renderExpandIconProps">
<!-- if children is an array, render ExpandIcon --> <!-- if children is an array, render ExpandIcon -->
<ExpandIcon <ExpandIcon
:class="{ invisible: !record.children }" v-if="renderExpandIconProps"
:expanded="expanded" :class="{ invisible: !renderExpandIconProps.record.children }"
:record="record" :expanded="renderExpandIconProps.expanded"
:on-expand="onExpand" :record="renderExpandIconProps.record"
:on-expand="renderExpandIconProps.onExpand"
:load-data="loadData" :load-data="loadData"
/> />
</template> </template>

4
src/components/Table/src/components/HeaderCell.vue

@ -37,10 +37,10 @@ export default defineComponent({
<div> <div>
{getIsEdit.value {getIsEdit.value
? ( ? (
<EditTableHeaderCell>{getTitle.value}</EditTableHeaderCell> <EditTableHeaderCell>{getTitle.value}</EditTableHeaderCell>
) )
: ( : (
<span class="default-header-cell">{getTitle.value}</span> <span class="default-header-cell">{getTitle.value}</span>
)} )}
{getHelpMessage.value && ( {getHelpMessage.value && (
<BasicHelp text={getHelpMessage.value} class={`${prefixCls}__help`} /> <BasicHelp text={getHelpMessage.value} class={`${prefixCls}__help`} />

2
src/components/Table/src/components/editable/EditableCell.vue

@ -274,7 +274,7 @@ export default defineComponent({
value, value,
}) })
} }
catch (e) { catch {
result = false result = false
} }
finally { finally {

3
src/components/Table/src/components/editable/helper.ts

@ -20,8 +20,9 @@ export function createPlaceholderMessage(component: ComponentType) {
|| component.includes('Switch') || component.includes('Switch')
|| component.includes('DatePicker') || component.includes('DatePicker')
|| component.includes('TimePicker') || component.includes('TimePicker')
) ) {
return t('common.chooseText') return t('common.chooseText')
}
return '' return ''
} }

2
src/components/Table/src/hooks/useColumns.ts

@ -318,7 +318,7 @@ export function formatCell(text: string, format: CellFormat, record: Recordable,
if (isMap(format)) if (isMap(format))
return format.get(text) return format.get(text)
} }
catch (error) { catch {
return text return text
} }
} }

2
src/components/Table/src/hooks/useRender.ts

@ -111,7 +111,7 @@ export const useRender = {
const data = JSON.parse(json) const data = JSON.parse(json)
return h(JsonPreview, { data }) return h(JsonPreview, { data })
} }
catch (e) { catch {
return json return json
} }
} }

1
src/components/Table/src/hooks/useTable.ts

@ -124,7 +124,6 @@ export function useTable<T>(tableProps?: Props<T>): [
getSize: () => { getSize: () => {
return toRaw(getTableInstance().getSize()) return toRaw(getTableInstance().getSize())
}, },
// eslint-disable-next-line ts/ban-types
updateTableData: <K extends keyof T | (string & {})>(index: number, key: K, value: K extends keyof T ? T[K] : any) => { updateTableData: <K extends keyof T | (string & {})>(index: number, key: K, value: K extends keyof T ? T[K] : any) => {
return getTableInstance().updateTableData(index, key, value) return getTableInstance().updateTableData(index, key, value)
}, },

1
src/components/Table/src/props.ts

@ -129,7 +129,6 @@ export function defineTableProps<T>() {
default: null, default: null,
}, },
rowKey: { rowKey: {
// eslint-disable-next-line ts/ban-types
type: [String, Function] as PropType<keyof T | ((record: T) => keyof T) | (string & {})>, type: [String, Function] as PropType<keyof T | ((record: T) => keyof T) | (string & {})>,
default: '', default: '',
}, },

2
src/components/Table/src/types/column.ts

@ -138,7 +138,7 @@ export interface ColumnProps<T> {
* Sort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to true * Sort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to true
* @type boolean | Function * @type boolean | Function
*/ */
sorter?: boolean | Function sorter?: boolean | ((...args: any) => any)
/** /**
* Order of sorted values: 'ascend' 'descend' false * Order of sorted values: 'ascend' 'descend' false

9
src/components/Table/src/types/table.ts

@ -115,7 +115,6 @@ export interface TableActionType<T = Recordable> {
getRowSelection: () => TableRowSelection<EditRecordRow<T>> getRowSelection: () => TableRowSelection<EditRecordRow<T>>
getCacheColumns: () => BasicColumn[] getCacheColumns: () => BasicColumn[]
emit?: EmitType emit?: EmitType
// eslint-disable-next-line ts/ban-types
updateTableData: <K extends keyof T | (string & {})>(index: number, key: K, value: K extends keyof T ? T[K] : any) => Promise<EditRecordRow<T>> updateTableData: <K extends keyof T | (string & {})>(index: number, key: K, value: K extends keyof T ? T[K] : any) => Promise<EditRecordRow<T>>
setShowPagination: (show: boolean) => Promise<void> setShowPagination: (show: boolean) => Promise<void>
getShowPagination: () => boolean getShowPagination: () => boolean
@ -215,8 +214,6 @@ export interface BasicTableProps<T = Recordable<any>> {
// 在分页改变的时候清空选项 // 在分页改变的时候清空选项
clearSelectOnPageChange?: boolean clearSelectOnPageChange?: boolean
//
// eslint-disable-next-line ts/ban-types
rowKey?: keyof T | ((record: T) => keyof T) | (string & {}) rowKey?: keyof T | ((record: T) => keyof T) | (string & {})
// 数据 // 数据
dataSource?: T[] dataSource?: T[]
@ -273,7 +270,7 @@ export interface BasicTableProps<T = Recordable<any>> {
* Customize row expand Icon. * Customize row expand Icon.
* @type Function | VNodeChild * @type Function | VNodeChild
*/ */
expandIcon?: Function | VNodeChild | JSX.Element expandIcon?: (...args: any) => any | VNodeChild | JSX.Element
/** /**
* Whether to expand row by clicking anywhere in the whole row * Whether to expand row by clicking anywhere in the whole row
@ -291,7 +288,7 @@ export interface BasicTableProps<T = Recordable<any>> {
* Table footer renderer * Table footer renderer
* @type Function | VNodeChild * @type Function | VNodeChild
*/ */
footer?: Function | VNodeChild | JSX.Element footer?: ((...args: any) => any) | VNodeChild | JSX.Element
/** /**
* Indent size in pixels of tree data * Indent size in pixels of tree data
@ -381,7 +378,7 @@ export interface BasicTableProps<T = Recordable<any>> {
* *
* @version 1.5.4 * @version 1.5.4
*/ */
transformCellText?: Function transformCellText?: (...args: any) => any
/** /**
* Callback executed before editable cell submit value, not for row-editor * Callback executed before editable cell submit value, not for row-editor

28
src/components/Tree/src/BasicTree.vue

@ -360,11 +360,11 @@ export default defineComponent({
const titleDom = isHighlight const titleDom = isHighlight
? ( ? (
<span class={unref(getBindValues)?.blockNode ? `${bem('content')}` : ''}> <span class={unref(getBindValues)?.blockNode ? `${bem('content')}` : ''}>
<span>{title.slice(0, searchIdx)}</span> <span>{title.slice(0, searchIdx)}</span>
<span style={highlightStyle}>{searchText}</span> <span style={highlightStyle}>{searchText}</span>
<span>{title.slice(searchIdx + (searchText as string).length)}</span> <span>{title.slice(searchIdx + (searchText as string).length)}</span>
</span> </span>
) )
: ( : (
title title
@ -377,17 +377,17 @@ export default defineComponent({
<span class={`${bem('title')} pl-2`} onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}> <span class={`${bem('title')} pl-2`} onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}>
{slots?.title {slots?.title
? ( ? (
<> <>
{iconDom} {iconDom}
{getSlot(slots, 'title', item)} {getSlot(slots, 'title', item)}
</> </>
) )
: ( : (
<> <>
{iconDom} {iconDom}
{titleDom} {titleDom}
<span class={bem('actions')}>{renderAction(item)}</span> <span class={bem('actions')}>{renderAction(item)}</span>
</> </>
)} )}
</span> </span>
) )

191
src/enums/systemEnum.ts

@ -1,191 +0,0 @@
export enum StationStatus {
Closed = '0',
Using = '1',
}
/**
*
*/
export const StationStatusOptions = [
{
value: StationStatus.Closed,
label: '关闭中',
},
{
value: StationStatus.Using,
label: '使用中',
},
]
export enum StationEnvironments {
OpenAir = '0',
Underground = '1',
Carport = '2',
}
/**
*
*/
export const StationEnvironmentOptions = [
{
value: StationEnvironments.OpenAir,
label: '露天',
},
{
value: StationEnvironments.Underground,
label: '地下',
},
{
value: StationEnvironments.Carport,
label: '车棚',
},
]
/**
*
*/
export const BuildingLotOptions = [
{
value: '1',
label: '居民区',
},
{
value: '2',
label: '公共机构',
},
{
value: '3',
label: '企事业单位',
},
{
value: '4',
label: '写字楼',
},
{
value: '5',
label: '工业园区',
},
{
value: '6',
label: '交通枢纽',
},
{
value: '7',
label: '大型文体设施',
},
{
value: '8',
label: '城市绿地',
},
{
value: '9',
label: '大型建筑配建停车场',
},
{
value: '10',
label: '路边停车位',
},
{
value: '11',
label: '城际高速服务区',
},
{
value: '255',
label: '其他',
},
]
/**
*
*/
export const OperateWayOptions = [
{
value: 'self',
label: '自营',
},
{
value: 'other',
label: '代运营',
},
]
export enum ParkingChargeWay {
Free = '0',
LimitedFree = '1',
Changing = '2',
}
export const ParkingChargeWayOptions = [
{
value: ParkingChargeWay.Free,
label: '免费停车',
},
{
value: ParkingChargeWay.LimitedFree,
label: '限时免费',
},
{
value: ParkingChargeWay.Changing,
label: '收费停车',
},
]
/**
*
*/
export const PayStatusOptions = [
{
label: '未支付',
value: 'notPay',
},
{
label: '支付中',
value: 'paying',
},
{
label: '支付成功',
value: 'suc',
},
{
label: '支付失败',
value: 'fail',
},
{
label: '待退款',
value: 'waitRefund',
},
{
label: '退款中',
value: 'refunding',
},
{
label: '退款成功',
value: 'refundSuc',
},
{
label: '退款失败',
value: 'refundFailed',
},
]
/**
*
*/
export const PayModesEnum = [
{
label: '微信分支付',
value: 'wechatFen',
},
{
label: '微信扫码',
value: '1',
},
{
label: '支付宝扫码',
value: '2',
},
{
label: '银座钱包',
value: '3',
},
]

2
src/hooks/setting/index.ts

@ -12,7 +12,7 @@ export function useGlobSetting(): Readonly<GlobConfig> {
VITE_GLOB_UPLOAD_URL, VITE_GLOB_UPLOAD_URL,
} = getAppEnvConfig() } = getAppEnvConfig()
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) { if (!/[a-z_]*/i.test(VITE_GLOB_APP_SHORT_NAME)) {
warn( warn(
'VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.', 'VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.',
) )

2
src/hooks/web/useContentHeight.ts

@ -53,7 +53,7 @@ export function useContentHeight(
function calcSubtractSpace(element: Element | null | undefined, direction: 'all' | 'top' | 'bottom' = 'all'): number { function calcSubtractSpace(element: Element | null | undefined, direction: 'all' | 'top' | 'bottom' = 'all'): number {
function numberPx(px: string) { function numberPx(px: string) {
return Number(px.replace(/[^\d]/g, '')) return Number(px.replace(/\D/g, ''))
} }
let subtractHeight = 0 let subtractHeight = 0
const ZERO_PX = '0px' const ZERO_PX = '0px'

18
src/hooks/web/useMessage.tsx

@ -8,15 +8,15 @@ import { useI18n } from './useI18n'
import { isString } from '@/utils/is' import { isString } from '@/utils/is'
export interface NotifyApi { export interface NotifyApi {
info(config: NotificationArgsProps): void info: (config: NotificationArgsProps) => void
success(config: NotificationArgsProps): void success: (config: NotificationArgsProps) => void
error(config: NotificationArgsProps): void error: (config: NotificationArgsProps) => void
warn(config: NotificationArgsProps): void warn: (config: NotificationArgsProps) => void
warning(config: NotificationArgsProps): void warning: (config: NotificationArgsProps) => void
open(args: NotificationArgsProps): void open: (args: NotificationArgsProps) => void
close(key: string): void close: (key: string) => void
config(options: ConfigProps): void config: (options: ConfigProps) => void
destroy(): void destroy: () => void
} }
export declare type NotificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' export declare type NotificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'

2
src/hooks/web/usePermission.ts

@ -27,7 +27,7 @@ export function usePermission() {
try { try {
router.addRoute(route as unknown as RouteRecordRaw) router.addRoute(route as unknown as RouteRecordRaw)
} }
catch (e) {} catch {}
}) })
permissionStore.setLastBuildMenuTime() permissionStore.setLastBuildMenuTime()
closeAll() closeAll()

3
src/layouts/default/header/MultipleHeader.vue

@ -64,8 +64,9 @@ const getPlaceholderDomStyle = computed((): CSSProperties => {
(unref(getShowFullHeaderRef) || !unref(getSplit)) (unref(getShowFullHeaderRef) || !unref(getSplit))
&& unref(getShowHeader) && unref(getShowHeader)
&& !unref(getFullContent) && !unref(getFullContent)
) ) {
height += HEADER_HEIGHT height += HEADER_HEIGHT
}
if (unref(getShowMultipleTab) && !unref(getFullContent)) if (unref(getShowMultipleTab) && !unref(getFullContent))
height += TABS_HEIGHT height += TABS_HEIGHT

18
src/layouts/default/menu/index.vue

@ -135,17 +135,17 @@ export default defineComponent({
return null return null
return !props.isHorizontal return !props.isHorizontal
? ( ? (
<SimpleMenu {...menuProps} isSplitMenu={unref(getSplit)} items={menus} /> <SimpleMenu {...menuProps} isSplitMenu={unref(getSplit)} items={menus} />
) )
: ( : (
<BasicMenu <BasicMenu
{...(menuProps as any)} {...(menuProps as any)}
isHorizontal={props.isHorizontal} isHorizontal={props.isHorizontal}
type={unref(getMenuType)} type={unref(getMenuType)}
showLogo={unref(getIsShowLogo)} showLogo={unref(getIsShowLogo)}
mode={unref(getComputedMenuMode as any)} mode={unref(getComputedMenuMode as any)}
items={menus} items={menus}
/> />
) )
} }

4
src/logics/error-handle/index.ts

@ -19,13 +19,13 @@ function processStackMsg(error: Error) {
return '' return ''
let stack = error.stack let stack = error.stack
.replace(/\n/gi, '') // Remove line breaks to save the size of the transmitted content .replace(/\n/g, '') // Remove line breaks to save the size of the transmitted content
.replace(/\bat\b/gi, '@') // At in chrome, @ in ff .replace(/\bat\b/gi, '@') // At in chrome, @ in ff
.split('@') // Split information with @ .split('@') // Split information with @
.slice(0, 9) // The maximum stack length (Error.stackTraceLimit = 10), so only take the first 10 .slice(0, 9) // The maximum stack length (Error.stackTraceLimit = 10), so only take the first 10
.map(v => v.replace(/^\s*|\s*$/g, '')) // Remove extra spaces .map(v => v.replace(/^\s*|\s*$/g, '')) // Remove extra spaces
.join('~') // Manually add separators for later display .join('~') // Manually add separators for later display
.replace(/\?[^:]+/gi, '') // Remove redundant parameters of js file links (?x=1 and the like) .replace(/\?[^:]+/g, '') // Remove redundant parameters of js file links (?x=1 and the like)
const msg = error.toString() const msg = error.toString()
if (!stack.includes(msg)) if (!stack.includes(msg))
stack = `${msg}@${stack}` stack = `${msg}@${stack}`

4
src/router/guard/permissionGuard.ts

@ -87,7 +87,7 @@ export function createPermissionGuard(router: Router) {
try { try {
await userStore.getUserInfoAction(userInfo?.user) await userStore.getUserInfoAction(userInfo?.user)
} }
catch (err) { catch {
next() next()
return return
} }
@ -104,7 +104,7 @@ export function createPermissionGuard(router: Router) {
try { try {
router.addRoute(route as unknown as RouteRecordRaw) router.addRoute(route as unknown as RouteRecordRaw)
} }
catch (e) {} catch {}
}) })
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw) router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw)

2
src/router/helper/menuHelper.ts

@ -81,7 +81,7 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi
/** /**
* config menu with given params * config menu with given params
*/ */
const menuParamRegex = /(?::)([\s\S]+?)((?=\/)|$)/g const menuParamRegex = /:([\s\S]+?)(?=\/)|$/g
export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) { export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) {
const { path, paramPath } = toRaw(menu) const { path, paramPath } = toRaw(menu)

2
src/store/modules/lock.ts

@ -50,7 +50,7 @@ export const useLockStore = defineStore('app-lock', {
// return res // return res
} }
catch (error) { catch {
return false return false
} }
} }

3
src/store/modules/multipleTab.ts

@ -123,8 +123,9 @@ export const useMultipleTabStore = defineStore('app-multiple-tab', {
|| path === PageEnum.BASE_LOGIN || path === PageEnum.BASE_LOGIN
|| !name || !name
|| [REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string) || [REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string)
) ) {
return return
}
let updateIndex = -1 let updateIndex = -1
// Existing pages, do not add tabs repeatedly // Existing pages, do not add tabs repeatedly

2
src/store/modules/permission.ts

@ -129,7 +129,7 @@ export const usePermissionStore = defineStore('app-permission', {
try { try {
patcher(routes) patcher(routes)
} }
catch (e) { catch {
// 已处理完毕跳出循环 // 已处理完毕跳出循环
} }
} }

4
src/store/modules/user.ts

@ -129,7 +129,7 @@ export const useUserStore = defineStore('app-user', {
try { try {
router.addRoute(route as unknown as RouteRecordRaw) router.addRoute(route as unknown as RouteRecordRaw)
} }
catch (e) {} catch {}
}) })
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw) router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw)
permissionStore.setDynamicAddedRoute(true) permissionStore.setDynamicAddedRoute(true)
@ -155,7 +155,7 @@ export const useUserStore = defineStore('app-user', {
.map(i => `${i[0].toUpperCase()}${i.slice(1)}`) .map(i => `${i[0].toUpperCase()}${i.slice(1)}`)
.join('') .join('')
const component = __menu.path.replaceAll(/\/:[\s|\S]*/g, '') const component = __menu.path.replaceAll(/\/:[\s\S]*/g, '')
let isPadIndexExtension = false let isPadIndexExtension = false
if (!component.endsWith('index') && !/\/:/.test(__menu.path)) if (!component.endsWith('index') && !/\/:/.test(__menu.path))

2
src/utils/cache/storageCache.ts vendored

@ -84,7 +84,7 @@ export function createStorage({
this.remove(key) this.remove(key)
} }
catch (e) { catch {
return def return def
} }
} }

6
src/utils/cipher.ts

@ -10,13 +10,13 @@ import SHA512 from 'crypto-js/sha512'
// Define an interface for encryption // Define an interface for encryption
// 定义一个加密器的接口 // 定义一个加密器的接口
export interface Encryption { export interface Encryption {
encrypt(plainText: string): string encrypt: (plainText: string) => string
decrypt(cipherText: string): string decrypt: (cipherText: string) => string
} }
// Define an interface for Hashing // Define an interface for Hashing
// 定义一个哈希算法的接口 // 定义一个哈希算法的接口
export interface Hashing { export interface Hashing {
hash(data: string): string hash: (data: string) => string
} }
export interface EncryptionParams { export interface EncryptionParams {

2
src/utils/color.ts

@ -6,7 +6,7 @@
* @return Boolean * @return Boolean
*/ */
export function isHexColor(color: string) { export function isHexColor(color: string) {
const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/ const reg = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i
return reg.test(color) return reg.test(color)
} }

22
src/utils/dateUtil.ts

@ -29,28 +29,6 @@ export function betweenDay(date1, date2) {
return Math.floor((date2.getTime() - date1.getTime()) / (24 * 3600 * 1000)) return Math.floor((date2.getTime() - date1.getTime()) / (24 * 3600 * 1000))
} }
export function formatDate(date, fmt) {
date = convertDate(date)
const o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'H+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
'S': date.getMilliseconds(), // 毫秒
}
if (/(y+)/.test(fmt)) {
// 年份
fmt = fmt.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length))
}
for (const k in o) {
if (new RegExp(`(${k})`).test(fmt))
fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : (`00${o[k]}`).substr((`${o[k]}`).length))
}
return fmt
}
export function addTime(date, time) { export function addTime(date, time) {
date = convertDate(date) date = convertDate(date)
return new Date(date.getTime() + time) return new Date(date.getTime() + time)

2
src/utils/domUtils.ts

@ -18,7 +18,7 @@ export function getBoundingClientRect(element: Element): DOMRect | number {
} }
function trim(string: string) { function trim(string: string) {
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '') return (string || '').replace(/^\s+|\s+$/g, '')
} }
/* istanbul ignore next */ /* istanbul ignore next */

2
src/utils/env.ts

@ -24,7 +24,7 @@ export function getAppEnvConfig() {
VITE_GLOB_UPLOAD_URL, VITE_GLOB_UPLOAD_URL,
} = ENV } = ENV
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) { if (!/^[a-z_]*$/i.test(VITE_GLOB_APP_SHORT_NAME)) {
warn( warn(
'VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.', 'VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.',
) )

2
src/utils/file/download.ts

@ -59,7 +59,7 @@ export function downloadByUrl({ url, target = '_blank', fileName }: { url: strin
const isChrome = window.navigator.userAgent.toLowerCase().includes('chrome') const isChrome = window.navigator.userAgent.toLowerCase().includes('chrome')
const isSafari = window.navigator.userAgent.toLowerCase().includes('safari') const isSafari = window.navigator.userAgent.toLowerCase().includes('safari')
if (/(iP)/g.test(window.navigator.userAgent)) { if (/(iP)/.test(window.navigator.userAgent)) {
console.error('Your browser does not support download!') console.error('Your browser does not support download!')
return false return false
} }

3
src/utils/http/axios/Axios.ts

@ -234,8 +234,9 @@ export class VAxios {
contentType !== ContentTypeEnum.FORM_URLENCODED contentType !== ContentTypeEnum.FORM_URLENCODED
|| !Reflect.has(config, 'data') || !Reflect.has(config, 'data')
|| config.method?.toUpperCase() === RequestEnum.GET || config.method?.toUpperCase() === RequestEnum.GET
) ) {
return config return config
}
return { return {
...config, ...config,

6
src/utils/index.ts

@ -130,7 +130,7 @@ interface EventShim {
} }
export type WithInstall<T> = T & { export type WithInstall<T> = T & {
install(app: App): void install: (app: App) => void
} & EventShim } & EventShim
export type CustomComponent = Component & { displayName?: string } export type CustomComponent = Component & { displayName?: string }
@ -176,10 +176,10 @@ export function simpleDebounce(fn, delay = 100) {
export function toCamelCase(str: string, upperCaseFirst: boolean) { export function toCamelCase(str: string, upperCaseFirst: boolean) {
str = (str || '') str = (str || '')
.replace(/[-|\/](.)/g, (group1) => { .replace(/[-|/](.)/g, (group1) => {
return group1.toUpperCase() return group1.toUpperCase()
}) })
.replaceAll(/[-|\/]/g, '') .replaceAll(/[-|/]/g, '')
if (upperCaseFirst && str) if (upperCaseFirst && str)
str = str.charAt(0).toUpperCase() + str.slice(1) str = str.charAt(0).toUpperCase() + str.slice(1)

2
src/utils/is.ts

@ -61,6 +61,6 @@ export const isServer = typeof window === 'undefined'
export const isClient = !isServer export const isClient = !isServer
export function isHttpUrl(path: string): boolean { export function isHttpUrl(path: string): boolean {
const reg = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- ./?%&=]*)?/ const reg = /^http(s)?:\/\/([\w\-]+\.)+[\w\-]+(\/[\w\- ./?%&=]*)?$/
return reg.test(path) return reg.test(path)
} }

11
src/utils/mitt.ts

@ -25,15 +25,12 @@ export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
export interface Emitter<Events extends Record<EventType, unknown>> { export interface Emitter<Events extends Record<EventType, unknown>> {
all: EventHandlerMap<Events> all: EventHandlerMap<Events>
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void on: (<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>) => void) & ((type: '*', handler: WildcardHandler<Events>) => void)
on(type: '*', handler: WildcardHandler<Events>): void
off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>): void off: (<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>) => void) & ((type: '*', handler: WildcardHandler<Events>) => void)
off(type: '*', handler: WildcardHandler<Events>): void
emit<Key extends keyof Events>(type: Key, event: Events[Key]): void emit: (<Key extends keyof Events>(type: Key, event: Events[Key]) => void) & (<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never) => void)
emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void clear: () => void
clear(): void
} }
/** /**

12
src/utils/props.ts

@ -138,13 +138,13 @@ export function buildProps<
: [O[K]] extends NativePropType : [O[K]] extends NativePropType
? O[K] ? O[K]
: O[K] extends BuildPropOption< : O[K] extends BuildPropOption<
infer T, infer T,
infer _D, infer _D,
infer R, infer R,
infer V, infer V,
infer C infer C
> >
? BuildPropReturn<T, O[K]['default'], R, V, C> ? BuildPropReturn<T, O[K]['default'], R, V, C>
: never : never
} }

2
src/utils/tree.ts

@ -268,7 +268,7 @@ export function handleTree2(data, id, parentId, children, rootId) {
// 返回每一项的子级数组 // 返回每一项的子级数组
return father[id] === child[parentId] return father[id] === child[parentId]
}) })
// eslint-disable-next-line no-unused-expressions
branchArr.length > 0 ? (father.children = branchArr) : '' branchArr.length > 0 ? (father.children = branchArr) : ''
// 返回第一层 // 返回第一层
return father[parentId] === rootId return father[parentId] === rootId

41
src/views/dashboard/analysis/components/GrowCard.vue

@ -1,41 +0,0 @@
<script lang="ts" setup>
import { Card, Tag } from 'ant-design-vue'
import { growCardList } from '../data'
import { CountTo } from '@/components/CountTo'
import { SvgIcon } from '@/components/Icon'
defineProps({
loading: {
type: Boolean,
},
})
</script>
<template>
<div class="md:flex">
<template v-for="(item, index) in growCardList" :key="item.title">
<Card
:loading="loading"
:title="item.title"
class="w-full md:w-1/4 !md:mt-0"
:class="{ '!md:mr-4': index + 1 < 4, '!mt-4': index > 0 }"
>
<template #extra>
<Tag :color="item.color">
{{ item.action }}
</Tag>
</template>
<div class="flex items-center justify-between px-4 py-4">
<CountTo prefix="$" :start-val="1" :end-val="item.value" class="text-2xl" />
<SvgIcon :name="item.icon" :size="40" />
</div>
<div class="flex justify-between p-2 px-4">
<span>{{ item.title }}</span>
<CountTo prefix="$" :start-val="1" :end-val="item.total" />
</div>
</Card>
</template>
</div>
</template>

61
src/views/dashboard/analysis/components/SalesProductPie.vue

@ -1,61 +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: Boolean,
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: {
trigger: 'item',
},
series: [
{
name: '访问来源',
type: 'pie',
radius: '80%',
center: ['50%', '50%'],
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
data: [
{ value: 500, name: '电子产品' },
{ value: 310, name: '服装' },
{ value: 274, name: '化妆品' },
{ value: 400, name: '家居' },
].sort((a, b) => {
return a.value - b.value
}),
roseType: 'radius',
animationType: 'scale',
animationEasing: 'exponentialInOut',
animationDelay() {
return Math.random() * 400
},
},
],
})
},
{ immediate: true },
)
</script>
<template>
<Card title="成交占比" :loading="loading">
<div ref="chartRef" :style="{ width, height }" />
</Card>
</template>

34
src/views/dashboard/analysis/components/SiteAnalysis.vue

@ -1,34 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { Card } from 'ant-design-vue'
import VisitAnalysis from './VisitAnalysis.vue'
import VisitAnalysisBar from './VisitAnalysisBar.vue'
const activeKey = ref('tab1')
const tabListTitle = [
{
key: 'tab1',
tab: '流量趋势',
},
{
key: 'tab2',
tab: '访问量',
},
]
function onTabChange(key: string) {
activeKey.value = key
}
</script>
<template>
<Card :tab-list="tabListTitle" v-bind="$attrs" :active-tab-key="activeKey" @tab-change="onTabChange">
<p v-if="activeKey === 'tab1'">
<VisitAnalysis />
</p>
<p v-if="activeKey === 'tab2'">
<VisitAnalysisBar />
</p>
</Card>
</template>

83
src/views/dashboard/analysis/components/VisitAnalysis.vue

@ -1,83 +0,0 @@
<script lang="ts" setup>
import type { Ref } from 'vue'
import { onMounted, ref } from 'vue'
import { basicProps } from './props'
import { useECharts } from '@/hooks/web/useECharts'
defineProps({
...basicProps,
})
const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
onMounted(() => {
setOptions({
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
width: 1,
color: '#019680',
},
},
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [...Array.from({ length: 18 })].map((_item, index) => `${index + 6}:00`),
splitLine: {
show: true,
lineStyle: {
width: 1,
type: 'solid',
color: 'rgba(226,226,226,0.5)',
},
},
axisTick: {
show: false,
},
},
yAxis: [
{
type: 'value',
max: 80000,
splitNumber: 4,
axisTick: {
show: false,
},
splitArea: {
show: true,
areaStyle: {
color: ['rgba(255,255,255,0.2)', 'rgba(226,226,226,0.2)'],
},
},
},
],
grid: { left: '1%', right: '1%', top: '2 %', bottom: 0, containLabel: true },
series: [
{
smooth: true,
data: [111, 222, 4000, 18000, 33333, 55555, 66666, 33333, 14000, 36000, 66666, 44444, 22222, 11111, 4000, 2000, 500, 333, 222, 111],
type: 'line',
areaStyle: {},
itemStyle: {
color: '#5ab1ef',
},
},
{
smooth: true,
data: [33, 66, 88, 333, 3333, 5000, 18000, 3000, 1200, 13000, 22000, 11000, 2221, 1201, 390, 198, 60, 30, 22, 11],
type: 'line',
areaStyle: {},
itemStyle: {
color: '#019680',
},
},
],
})
})
</script>
<template>
<div ref="chartRef" :style="{ height, width }" />
</template>

47
src/views/dashboard/analysis/components/VisitAnalysisBar.vue

@ -1,47 +0,0 @@
<script lang="ts" setup>
import type { Ref } from 'vue'
import { onMounted, ref } from 'vue'
import { basicProps } from './props'
import { useECharts } from '@/hooks/web/useECharts'
defineProps({
...basicProps,
})
const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
onMounted(() => {
setOptions({
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
width: 1,
color: '#019680',
},
},
},
grid: { left: '1%', right: '1%', top: '2 %', bottom: 0, containLabel: true },
xAxis: {
type: 'category',
data: [...Array.from({ length: 12 })].map((_item, index) => `${index + 1}`),
},
yAxis: {
type: 'value',
max: 8000,
splitNumber: 4,
},
series: [
{
data: [3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000, 3200, 4800],
type: 'bar',
barMaxWidth: 80,
},
],
})
})
</script>
<template>
<div ref="chartRef" :style="{ height, width }" />
</template>

91
src/views/dashboard/analysis/components/VisitRadar.vue

@ -1,91 +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: Boolean,
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({
legend: {
bottom: 0,
data: ['访问', '购买'],
},
tooltip: {},
radar: {
radius: '60%',
splitNumber: 8,
indicator: [
{
name: '电脑',
},
{
name: '充电器',
},
{
name: '耳机',
},
{
name: '手机',
},
{
name: 'Ipad',
},
{
name: '耳机',
},
],
},
series: [
{
type: 'radar',
symbolSize: 0,
areaStyle: {
shadowBlur: 0,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1,
},
data: [
{
value: [90, 50, 86, 40, 50, 20],
name: '访问',
itemStyle: {
color: '#b6a2de',
},
},
{
value: [70, 75, 70, 76, 20, 85],
name: '购买',
itemStyle: {
color: '#5ab1ef',
},
},
],
},
],
})
},
{ immediate: true },
)
</script>
<template>
<Card title="转化率" :loading="loading">
<div ref="chartRef" :style="{ width, height }" />
</Card>
</template>

79
src/views/dashboard/analysis/components/VisitSource.vue

@ -1,79 +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: Boolean,
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: {
trigger: 'item',
},
legend: {
bottom: '1%',
left: 'center',
},
series: [
{
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
name: '访问来源',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '12',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 580, name: '邮件营销' },
{ value: 484, name: '联盟广告' },
],
animationType: 'scale',
animationEasing: 'exponentialInOut',
animationDelay() {
return Math.random() * 100
},
},
],
})
},
{ immediate: true },
)
</script>
<template>
<Card title="访问来源" :loading="loading">
<div ref="chartRef" :style="{ width, height }" />
</Card>
</template>

16
src/views/dashboard/analysis/components/props.ts

@ -1,16 +0,0 @@
import type { PropType } from 'vue'
export interface BasicProps {
width: string
height: string
}
export const basicProps = {
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '280px',
},
}

43
src/views/dashboard/analysis/data.ts

@ -1,43 +0,0 @@
export interface GrowCardItem {
icon: string
title: string
value: number
total: number
color: string
action: string
}
export const growCardList: GrowCardItem[] = [
{
title: '访问数',
icon: 'visit-count',
value: 2000,
total: 120000,
color: 'green',
action: '月',
},
{
title: '成交额',
icon: 'total-sales',
value: 20000,
total: 500000,
color: 'blue',
action: '月',
},
{
title: '下载数',
icon: 'download-count',
value: 8000,
total: 120000,
color: 'orange',
action: '周',
},
{
title: '成交数',
icon: 'transaction',
value: 5000,
total: 50000,
color: 'purple',
action: '年',
},
]

26
src/views/dashboard/analysis/index.vue

@ -1,26 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import GrowCard from './components/GrowCard.vue'
import SiteAnalysis from './components/SiteAnalysis.vue'
import VisitSource from './components/VisitSource.vue'
import VisitRadar from './components/VisitRadar.vue'
import SalesProductPie from './components/SalesProductPie.vue'
const loading = ref(true)
setTimeout(() => {
loading.value = false
}, 500)
</script>
<template>
<div class="p-4">
<GrowCard :loading="loading" class="enter-y" />
<SiteAnalysis class="enter-y !my-4" :loading="loading" />
<div class="enter-y md:flex">
<VisitRadar class="w-full md:w-1/3" :loading="loading" />
<VisitSource class="w-full !my-4 md:w-1/3 !md:mx-4 !md:my-0" :loading="loading" />
<SalesProductPie class="w-full md:w-1/3" :loading="loading" />
</div>
</div>
</template>

34
src/views/dashboard/workbench/components/DynamicInfo.vue

@ -1,34 +0,0 @@
<script lang="ts" setup>
import { Card, List } from 'ant-design-vue'
import { dynamicInfoItems } from './data'
import { SvgIcon } from '@/components/Icon'
const ListItem = List.Item
const ListItemMeta = List.Item.Meta
</script>
<template>
<Card title="最新动态" v-bind="$attrs">
<template #extra>
<a-button type="link">
更多
</a-button>
</template>
<List item-layout="horizontal" :data-source="dynamicInfoItems">
<template #renderItem="{ item }">
<ListItem>
<ListItemMeta>
<template #description>
{{ item.date }}
</template>
<!-- eslint-disable-next-line -->
<template #title> {{ item.name }} <span v-html="item.desc"> </span> </template>
<template #avatar>
<SvgIcon :name="item.avatar" :size="30" />
</template>
</ListItemMeta>
</ListItem>
</template>
</List>
</Card>
</template>

87
src/views/dashboard/workbench/components/ProjectCard.vue

@ -1,87 +0,0 @@
<script lang="ts" setup>
import { Card, CardGrid } from 'ant-design-vue'
interface GroupItem {
title: string
icon: string
color: string
desc: string
date: string
group: string
}
const groupItems: GroupItem[] = [
{
title: 'Github',
icon: 'i-carbon:logo-github',
color: '',
desc: '不要等待机会,而要创造机会。',
group: '开源组',
date: '2021-04-01',
},
{
title: 'Vue',
icon: 'i-ion:logo-vue',
color: '#3fb27f',
desc: '现在的你决定将来的你。',
group: '算法组',
date: '2021-04-01',
},
{
title: 'Html5',
icon: 'i-ion:logo-html5',
color: '#e18525',
desc: '没有什么才能比努力更重要。',
group: '上班摸鱼',
date: '2021-04-01',
},
{
title: 'Angular',
icon: 'i-ion:logo-angular',
color: '#bf0c2c',
desc: '热情和欲望可以突破一切难关。',
group: 'UI',
date: '2021-04-01',
},
{
title: 'React',
icon: 'i-bx:bxl-react',
color: '#00d8ff',
desc: '健康的身体是实现目标的基石。',
group: '技术牛',
date: '2021-04-01',
},
{
title: 'Js',
icon: 'i-ion:logo-javascript',
color: '#EBD94E',
desc: '路是走出来的,而不是空想出来的。',
group: '架构组',
date: '2021-04-01',
},
]
</script>
<template>
<Card title="项目" v-bind="$attrs">
<template #extra>
<a-button type="link">
更多
</a-button>
</template>
<CardGrid v-for="item in groupItems" :key="item.title" class="!w-full !md:w-1/3">
<span class="flex">
<span :class="item.icon" :style="{ color: item.color }" class="text-[30px]" />
<span class="ml-4 text-lg">{{ item.title }}</span>
</span>
<div class="text-secondary mt-2 h-10 flex">
{{ item.desc }}
</div>
<div class="text-secondary flex justify-between">
<span>{{ item.group }}</span>
<span>{{ item.date }}</span>
</div>
</CardGrid>
</Card>
</template>

53
src/views/dashboard/workbench/components/QuickNav.vue

@ -1,53 +0,0 @@
<script lang="ts" setup>
import { Card, CardGrid } from 'ant-design-vue'
interface NavItem {
title: string
icon: string
color: string
}
const navItems: NavItem[] = [
{
title: '首页',
icon: 'i-ion:home-outline',
color: '#1fdaca',
},
{
title: '仪表盘',
icon: 'i-ion:grid-outline',
color: '#bf0c2c',
},
{
title: '组件',
icon: 'i-ion:layers-outline',
color: '#e18525',
},
{
title: '系统管理',
icon: 'i-ion:settings-outline',
color: '#3fb27f',
},
{
title: '权限管理',
icon: 'i-ion:key-outline',
color: '#4daf1bc9',
},
{
title: '图表',
icon: 'i-ion:bar-chart-outline',
color: '#00d8ff',
},
]
</script>
<template>
<Card title="快捷导航">
<CardGrid v-for="item in navItems" :key="item.title">
<span class="flex flex-col items-center">
<span :class="item.icon" :style="{ color: item.color }" class="text-[20px]" />
<span class="text-md mt-2 truncate">{{ item.title }}</span>
</span>
</CardGrid>
</Card>
</template>

91
src/views/dashboard/workbench/components/SaleRadar.vue

@ -1,91 +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: Boolean,
width: propTypes.string.def('100%'),
height: propTypes.string.def('400px'),
})
const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
watch(
() => props.loading,
() => {
if (props.loading)
return
setOptions({
legend: {
bottom: 0,
data: ['Visits', 'Sales'],
},
tooltip: {},
radar: {
radius: '60%',
splitNumber: 8,
indicator: [
{
name: '2017',
},
{
name: '2017',
},
{
name: '2018',
},
{
name: '2019',
},
{
name: '2020',
},
{
name: '2021',
},
],
},
series: [
{
type: 'radar',
symbolSize: 0,
areaStyle: {
shadowBlur: 0,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1,
},
data: [
{
value: [90, 50, 86, 40, 50, 20],
name: 'Visits',
itemStyle: {
color: '#b6a2de',
},
},
{
value: [70, 75, 70, 76, 20, 85],
name: 'Sales',
itemStyle: {
color: '#67e0e3',
},
},
],
},
],
})
},
{ immediate: true },
)
</script>
<template>
<Card title="销售统计" :loading="loading">
<div ref="chartRef" :style="{ width, height }" />
</Card>
</template>

36
src/views/dashboard/workbench/components/WorkbenchHeader.vue

@ -1,36 +0,0 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { Avatar } from 'ant-design-vue'
import { useUserStore } from '@/store/modules/user'
import headerImg from '@/assets/images/header.jpg'
const userStore = useUserStore()
const userinfo = computed(() => userStore.getUserInfo)
</script>
<template>
<div class="lg:flex">
<Avatar :src="userinfo.user.avatar || headerImg" :size="72" class="!mx-auto !block" />
<div class="mt-2 flex flex-col justify-center md:ml-6 md:mt-0">
<h1 class="text-md md:text-lg">
早安, {{ userinfo.user.real_name }}, 开始您一天的工作吧
</h1>
<span class="text-secondary"> 今日晴20 - 32 </span>
</div>
<div class="mt-4 flex flex-1 justify-end md:mt-0">
<div class="flex flex-col justify-center text-right">
<span class="text-secondary"> 待办 </span>
<span class="text-2xl">2/10</span>
</div>
<div class="mx-12 flex flex-col justify-center text-right md:mx-16">
<span class="text-secondary"> 项目 </span>
<span class="text-2xl">8</span>
</div>
<div class="mr-4 flex flex-col justify-center text-right md:mr-10">
<span class="text-secondary"> 团队 </span>
<span class="text-2xl">300</span>
</div>
</div>
</div>
</template>

57
src/views/dashboard/workbench/components/data.ts

@ -1,57 +0,0 @@
interface DynamicInfoItem {
avatar: string
name: string
date: string
desc: string
}
export const dynamicInfoItems: DynamicInfoItem[] = [
{
avatar: 'dynamic-avatar-1',
name: '威廉',
date: '刚刚',
desc: '在 <a>开源组</a> 创建了项目 <a>Vue</a>',
},
{
avatar: 'dynamic-avatar-2',
name: '艾文',
date: '1个小时前',
desc: '关注了 <a>威廉</a> ',
},
{
avatar: 'dynamic-avatar-3',
name: '克里斯',
date: '1天前',
desc: '发布了 <a>个人动态</a> ',
},
{
avatar: 'dynamic-avatar-4',
name: 'Kay',
date: '2天前',
desc: '发表文章 <a>如何编写一个Vite插件</a> ',
},
{
avatar: 'dynamic-avatar-5',
name: '皮特',
date: '3天前',
desc: '回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>',
},
{
avatar: 'dynamic-avatar-6',
name: '杰克',
date: '1周前',
desc: '关闭了问题 <a>如何运行项目</a> ',
},
{
avatar: 'dynamic-avatar-1',
name: '威廉',
date: '1周前',
desc: '发布了 <a>个人动态</a> ',
},
{
avatar: 'dynamic-avatar-1',
name: '威廉',
date: '2021-04-01 20:00',
desc: '推送了代码到 <a>Github</a>',
},
]

39
src/views/dashboard/workbench/index.vue

@ -1,39 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { Card } from 'ant-design-vue'
import WorkbenchHeader from './components/WorkbenchHeader.vue'
import ProjectCard from './components/ProjectCard.vue'
import QuickNav from './components/QuickNav.vue'
import DynamicInfo from './components/DynamicInfo.vue'
import SaleRadar from './components/SaleRadar.vue'
import { PageWrapper } from '@/components/Page'
const loading = ref(true)
setTimeout(() => {
loading.value = false
}, 500)
</script>
<template>
<PageWrapper>
<template #headerContent>
<WorkbenchHeader />
</template>
<div class="lg:flex">
<div class="enter-y w-full !mr-4 lg:w-7/10">
<ProjectCard :loading="loading" class="enter-y" />
<DynamicInfo :loading="loading" class="enter-y !my-4" />
</div>
<div class="enter-y w-full lg:w-3/10">
<QuickNav :loading="loading" class="enter-y" />
<Card class="enter-y !my-4" :loading="loading">
<img class="mx-auto h-30 xl:h-50" src="@/assets/svg/illustration.svg">
</Card>
<SaleRadar :loading="loading" class="enter-y" />
</div>
</div>
</PageWrapper>
</template>

2
vite.config.ts

@ -50,7 +50,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
}, },
// @/xxxx => src/xxxx // @/xxxx => src/xxxx
{ {
find: /\@\//, find: /@\//,
replacement: `${pathResolve('src')}/`, replacement: `${pathResolve('src')}/`,
}, },
], ],

Loading…
Cancel
Save