This commit is contained in:
dongshanshan 2023-10-31 16:53:48 +08:00
commit 18e2c33cef
19 changed files with 282 additions and 121 deletions

View File

@ -102,6 +102,7 @@
"codemirror", "codemirror",
"commitlint", "commitlint",
"cropperjs", "cropperjs",
"echart",
"echarts", "echarts",
"esnext", "esnext",
"esno", "esno",
@ -116,10 +117,12 @@
"sider", "sider",
"sortablejs", "sortablejs",
"stylelint", "stylelint",
"svgs",
"unocss", "unocss",
"unplugin", "unplugin",
"unref", "unref",
"videojs", "videojs",
"VITE",
"vitejs", "vitejs",
"vueuse", "vueuse",
"wangeditor", "wangeditor",

View File

@ -36,20 +36,20 @@
"@wangeditor/editor-for-vue": "^5.1.10", "@wangeditor/editor-for-vue": "^5.1.10",
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.5.1", "axios": "^1.6.0",
"benz-amr-recorder": "^1.1.5", "benz-amr-recorder": "^1.1.5",
"bpmn-js-token-simulation": "^0.10.0", "bpmn-js-token-simulation": "^0.10.0",
"camunda-bpmn-moddle": "^7.0.1", "camunda-bpmn-moddle": "^7.0.1",
"cropperjs": "^1.6.1", "cropperjs": "^1.6.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"diagram-js": "^12.5.0", "diagram-js": "^12.6.0",
"driver.js": "^1.3.0",
"echarts": "^5.4.3", "echarts": "^5.4.3",
"echarts-wordcloud": "^2.1.0", "echarts-wordcloud": "^2.1.0",
"element-plus": "2.4.1", "element-plus": "2.4.1",
"fast-xml-parser": "^4.3.2", "fast-xml-parser": "^4.3.2",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"intro.js": "^7.2.0",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"min-dash": "^4.1.1", "min-dash": "^4.1.1",
@ -64,7 +64,7 @@
"video.js": "^7.21.5", "video.js": "^7.21.5",
"vue": "^3.3.7", "vue": "^3.3.7",
"vue-dompurify-html": "^4.1.4", "vue-dompurify-html": "^4.1.4",
"vue-i18n": "^9.5.0", "vue-i18n": "^9.6.2",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"vue-types": "^5.1.1", "vue-types": "^5.1.1",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
@ -72,20 +72,19 @@
"xml-js": "^1.6.11" "xml-js": "^1.6.11"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^18.0.0", "@commitlint/cli": "^18.2.0",
"@commitlint/config-conventional": "^18.0.0", "@commitlint/config-conventional": "^18.1.0",
"@iconify/json": "^2.2.132", "@iconify/json": "^2.2.135",
"@intlify/unplugin-vue-i18n": "^1.4.0", "@intlify/unplugin-vue-i18n": "^1.4.0",
"@purge-icons/generated": "^0.9.0", "@purge-icons/generated": "^0.9.0",
"@types/intro.js": "^5.1.3",
"@types/lodash-es": "^4.17.10", "@types/lodash-es": "^4.17.10",
"@types/node": "^20.8.8", "@types/node": "^20.8.9",
"@types/nprogress": "^0.2.2", "@types/nprogress": "^0.2.2",
"@types/qrcode": "^1.5.4", "@types/qrcode": "^1.5.4",
"@types/qs": "^6.9.9", "@types/qs": "^6.9.9",
"@types/sortablejs": "^1.15.4", "@types/sortablejs": "^1.15.4",
"@typescript-eslint/eslint-plugin": "^6.9.0", "@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.0", "@typescript-eslint/parser": "^6.9.1",
"@unocss/transformer-variant-group": "^0.57.1", "@unocss/transformer-variant-group": "^0.57.1",
"@unocss/eslint-config": "^0.57.1", "@unocss/eslint-config": "^0.57.1",
"@vitejs/plugin-legacy": "^4.1.1", "@vitejs/plugin-legacy": "^4.1.1",
@ -99,24 +98,24 @@
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-define-config": "^1.24.1", "eslint-define-config": "^1.24.1",
"eslint-plugin-prettier": "^5.0.1", "eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-vue": "^9.18.0", "eslint-plugin-vue": "^9.18.1",
"lint-staged": "^15.0.2", "lint-staged": "^15.0.2",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"postcss-html": "^1.5.0", "postcss-html": "^1.5.0",
"postcss-scss": "^4.0.9", "postcss-scss": "^4.0.9",
"prettier": "^3.0.3", "prettier": "^3.0.3",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup": "^4.1.4", "rollup": "^4.1.5",
"sass": "^1.69.4", "sass": "^1.69.5",
"stylelint": "^15.11.0", "stylelint": "^15.11.0",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^13.0.0", "stylelint-config-recommended": "^13.0.0",
"stylelint-config-standard": "^34.0.0", "stylelint-config-standard": "^34.0.0",
"stylelint-order": "^6.0.3", "stylelint-order": "^6.0.3",
"terser": "^5.22.0", "terser": "^5.23.0",
"typescript": "5.2.2", "typescript": "5.2.2",
"unocss": "^0.57.1", "unocss": "^0.57.1",
"unplugin-auto-import": "^0.16.6", "unplugin-auto-import": "^0.16.7",
"unplugin-element-plus": "^0.8.0", "unplugin-element-plus": "^0.8.0",
"unplugin-vue-components": "^0.25.2", "unplugin-vue-components": "^0.25.2",
"vite": "4.5.0", "vite": "4.5.0",
@ -128,7 +127,7 @@
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-top-level-await": "^1.3.1", "vite-plugin-top-level-await": "^1.3.1",
"vue-eslint-parser": "^9.3.2", "vue-eslint-parser": "^9.3.2",
"vue-tsc": "^1.8.20" "vue-tsc": "^1.8.22"
}, },
"license": "MIT", "license": "MIT",
"repository": { "repository": {

View File

@ -3,7 +3,7 @@ defineComponent({
name: 'CardTitle' name: 'CardTitle'
}) })
const { title } = defineProps({ defineProps({
title: { title: {
type: String, type: String,
required: true required: true

View File

@ -1,20 +1,19 @@
<script lang="ts" setup> <script setup lang="ts">
import { provide, computed, watch, onMounted } from 'vue'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { ComponentSize, ElConfigProvider } from 'element-plus'
import { useLocaleStore } from '@/store/modules/locale' import { useLocaleStore } from '@/store/modules/locale'
import { useWindowSize } from '@vueuse/core'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { setCssVar } from '@/utils' import { setCssVar } from '@/utils'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { ElementPlusSize } from '@/types/elementPlus'
import { useWindowSize } from '@vueuse/core'
defineOptions({ name: 'ConfigGlobal' })
const { variables } = useDesign() const { variables } = useDesign()
const appStore = useAppStore() const appStore = useAppStore()
const props = defineProps({ const props = defineProps({
size: propTypes.oneOf<ElementPlusSize>(['default', 'small', 'large']).def('default') size: propTypes.oneOf<ComponentSize>(['default', 'small', 'large']).def('default')
}) })
provide('configGlobal', props) provide('configGlobal', props)
@ -53,9 +52,9 @@ const currentLocale = computed(() => localeStore.currentLocale)
<template> <template>
<ElConfigProvider <ElConfigProvider
:namespace="variables.elNamespace"
:locale="currentLocale.elLocale" :locale="currentLocale.elLocale"
:message="{ max: 1 }" :message="{ max: 1 }"
:namespace="variables.elNamespace"
:size="size" :size="size"
> >
<slot></slot> <slot></slot>

View File

@ -27,7 +27,7 @@ import * as DateUtil from '@/utils/formatTime'
defineOptions({ name: 'ShortcutDateRangePicker' }) defineOptions({ name: 'ShortcutDateRangePicker' })
const shortcutDays = ref(7) // , 7 const shortcutDays = ref(7) // , 7
const times = ref<[dayjs.ConfigType, dayjs.ConfigType]>(['', '']) // const times = ref<[string, string]>(['', '']) //
defineExpose({ times }) // defineExpose({ times }) //
/** 日期快捷选择 */ /** 日期快捷选择 */
const shortcuts = [ const shortcuts = [

View File

@ -32,7 +32,7 @@ onMounted(() => {
scrollContainer.value = getScrollContainer(refSticky.value!, true) scrollContainer.value = getScrollContainer(refSticky.value!, true)
useEventListener(scrollContainer, 'scroll', handleScroll) useEventListener(scrollContainer, 'scroll', handleScroll)
useEventListener('resize', handleReize) useEventListener('resize', handleResize)
handleScroll() handleScroll()
}) })
onActivated(() => { onActivated(() => {
@ -103,7 +103,7 @@ const handleScroll = () => {
reset() reset()
} }
} }
const handleReize = () => { const handleResize = () => {
if (isSticky.value && refSticky.value) { if (isSticky.value && refSticky.value) {
width.value = refSticky.value.getBoundingClientRect().width + 'px' width.value = refSticky.value.getBoundingClientRect().width + 'px'
} }

View File

@ -4,13 +4,13 @@ import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'Tooltip' }) defineOptions({ name: 'Tooltip' })
defineProps({ defineProps({
titel: propTypes.string.def(''), title: propTypes.string.def(''),
message: propTypes.string.def(''), message: propTypes.string.def(''),
icon: propTypes.string.def('ep:question-filled') icon: propTypes.string.def('ep:question-filled')
}) })
</script> </script>
<template> <template>
<span>{{ titel }}</span> <span>{{ title }}</span>
<ElTooltip :content="message" placement="top"> <ElTooltip :content="message" placement="top">
<Icon :icon="icon" class="relative top-1px ml-1px" /> <Icon :icon="icon" class="relative top-1px ml-1px" />
</ElTooltip> </ElTooltip>

49
src/hooks/web/useGuide.ts Normal file
View File

@ -0,0 +1,49 @@
import { Config, driver } from 'driver.js'
import 'driver.js/dist/driver.css'
import { useDesign } from '@/hooks/web/useDesign'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const { variables } = useDesign()
export const useGuide = (options?: Config) => {
const driverObj = driver(
options || {
showProgress: true,
nextBtnText: t('common.nextLabel'),
prevBtnText: t('common.prevLabel'),
doneBtnText: t('common.doneLabel'),
steps: [
{
element: `#${variables.namespace}-menu`,
popover: {
title: t('common.menu'),
description: t('common.menuDes'),
side: 'right'
}
},
{
element: `#${variables.namespace}-tool-header`,
popover: {
title: t('common.tool'),
description: t('common.toolDes'),
side: 'left'
}
},
{
element: `#${variables.namespace}-tags-view`,
popover: {
title: t('common.tagsView'),
description: t('common.tagsViewDes'),
side: 'bottom'
}
}
]
}
)
return {
...driverObj
}
}

View File

@ -1,47 +0,0 @@
import introJs from 'intro.js'
import { IntroJs, Step, Options } from 'intro.js'
import 'intro.js/introjs.css'
import { useDesign } from '@/hooks/web/useDesign'
export const useIntro = (setps?: Step[], options?: Options) => {
const { t } = useI18n()
const { variables } = useDesign()
const defaultSetps: Step[] = setps || [
{
element: `#${variables.namespace}-menu`,
title: t('common.menu'),
intro: t('common.menuDes'),
position: 'right'
},
{
element: `#${variables.namespace}-tool-header`,
title: t('common.tool'),
intro: t('common.toolDes'),
position: 'left'
},
{
element: `#${variables.namespace}-tags-view`,
title: t('common.tagsView'),
intro: t('common.tagsViewDes'),
position: 'bottom'
}
]
const defaultOptions: Options = options || {
prevLabel: t('common.prevLabel'),
nextLabel: t('common.nextLabel'),
skipLabel: t('common.skipLabel'),
doneLabel: t('common.doneLabel')
}
const introRef: IntroJs = introJs()
introRef.addSteps(defaultSetps).setOptions(defaultOptions)
return {
introRef
}
}

View File

@ -0,0 +1,21 @@
import { ref, onBeforeUnmount } from 'vue'
const useNetwork = () => {
const online = ref(true)
const updateNetwork = () => {
online.value = navigator.onLine
}
window.addEventListener('online', updateNetwork)
window.addEventListener('offline', updateNetwork)
onBeforeUnmount(() => {
window.removeEventListener('online', updateNetwork)
window.removeEventListener('offline', updateNetwork)
})
return { online }
}
export { useNetwork }

60
src/hooks/web/useNow.ts Normal file
View File

@ -0,0 +1,60 @@
import { dateUtil } from '@/utils/dateUtil'
import { reactive, toRefs } from 'vue'
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'
export const useNow = (immediate = true) => {
let timer: IntervalHandle
const state = reactive({
year: 0,
month: 0,
week: '',
day: 0,
hour: '',
minute: '',
second: 0,
meridiem: ''
})
const update = () => {
const now = dateUtil()
const h = now.format('HH')
const m = now.format('mm')
const s = now.get('s')
state.year = now.get('y')
state.month = now.get('M') + 1
state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]
state.day = now.get('date')
state.hour = h
state.minute = m
state.second = s
state.meridiem = now.format('A')
}
function start() {
update()
clearInterval(timer)
timer = setInterval(() => update(), 1000)
}
function stop() {
clearInterval(timer)
}
tryOnMounted(() => {
immediate && start()
})
tryOnUnmounted(() => {
stop()
})
return {
...toRefs(state),
start,
stop
}
}

View File

@ -0,0 +1,63 @@
import { useTagsViewStoreWithOut } from '@/store/modules/tagsView'
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router'
import { computed, nextTick, unref } from 'vue'
export const useTagsView = () => {
const tagsViewStore = useTagsViewStoreWithOut()
const { replace, currentRoute } = useRouter()
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
const closeAll = (callback?: Fn) => {
tagsViewStore.delAllViews()
callback?.()
}
const closeLeft = (callback?: Fn) => {
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
callback?.()
}
const closeRight = (callback?: Fn) => {
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
callback?.()
}
const closeOther = (callback?: Fn) => {
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
callback?.()
}
const closeCurrent = (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
if (view?.meta?.affix) return
tagsViewStore.delView(view || unref(currentRoute))
callback?.()
}
const refreshPage = async (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
tagsViewStore.delCachedView()
const { path, query } = view || unref(currentRoute)
await nextTick()
replace({
path: '/redirect' + path,
query: query
})
callback?.()
}
const setTitle = (title: string, path?: string) => {
tagsViewStore.setTitle(title, path)
}
return {
closeAll,
closeLeft,
closeRight,
closeOther,
closeCurrent,
refreshPage,
setTitle
}
}

View File

@ -1,54 +1,53 @@
const { t } = useI18n() import { useI18n } from '@/hooks/web/useI18n'
import { FormItemRule } from 'element-plus'
type Callback = (error?: string | Error | undefined) => void const { t } = useI18n()
interface LengthRange { interface LengthRange {
min: number min: number
max: number max: number
message: string message?: string
} }
export const useValidator = () => { export const useValidator = () => {
const required = (message?: string) => { const required = (message?: string): FormItemRule => {
return { return {
required: true, required: true,
message: message || t('common.required') message: message || t('common.required')
} }
} }
const lengthRange = (val: any, callback: Callback, options: LengthRange) => { const lengthRange = (options: LengthRange): FormItemRule => {
const { min, max, message } = options const { min, max, message } = options
if (val.length < min || val.length > max) {
callback(new Error(message)) return {
min,
max,
message: message || t('common.lengthRange', { min, max })
}
}
const notSpace = (message?: string): FormItemRule => {
return {
validator: (_, val, callback) => {
if (val?.indexOf(' ') !== -1) {
callback(new Error(message || t('common.notSpace')))
} else { } else {
callback() callback()
} }
} }
const notSpace = (val: any, callback: Callback, message: string) => {
// 用户名不能有空格
if (val.indexOf(' ') !== -1) {
callback(new Error(message))
} else {
callback()
} }
} }
const notSpecialCharacters = (val: any, callback: Callback, message: string) => { const notSpecialCharacters = (message?: string): FormItemRule => {
// 密码不能是特殊字符 return {
validator: (_, val, callback) => {
if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) { if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
callback(new Error(message)) callback(new Error(message || t('common.notSpecialCharacters')))
} else { } else {
callback() callback()
} }
} }
// 两个字符串是否想等
const isEqual = (val1: string, val2: string, callback: Callback, message: string) => {
if (val1 === val2) {
callback()
} else {
callback(new Error(message))
} }
} }
@ -56,7 +55,6 @@ export const useValidator = () => {
required, required,
lengthRange, lengthRange,
notSpace, notSpace,
notSpecialCharacters, notSpecialCharacters
isEqual
} }
} }

View File

@ -334,6 +334,6 @@ export function getLast1Year(): [dayjs.ConfigType, dayjs.ConfigType] {
export function getDateRange( export function getDateRange(
beginDate: dayjs.ConfigType, beginDate: dayjs.ConfigType,
endDate: dayjs.ConfigType endDate: dayjs.ConfigType
): [dayjs.ConfigType, dayjs.ConfigType] { ): [string, string] {
return [dayjs(beginDate).startOf('d'), dayjs(endDate).endOf('d')] return [dayjs(beginDate).startOf('d').toString(), dayjs(endDate).endOf('d').toString()]
} }

View File

@ -34,6 +34,13 @@ export const underlineToHump = (str: string): string => {
}) })
} }
/**
*
*/
export const humpToDash = (str: string): string => {
return str.replace(/([A-Z])/g, '-$1').toLowerCase()
}
export const setCssVar = (prop: string, val: any, dom = document.documentElement) => { export const setCssVar = (prop: string, val: any, dom = document.documentElement) => {
dom.style.setProperty(prop, val) dom.style.setProperty(prop, val)
} }
@ -67,7 +74,7 @@ export const trim = (str: string) => {
* @param {Date | number | string} time * @param {Date | number | string} time
* @param {String} fmt yyyy-MM-ddyyyy-MM-dd HH:mm:ss * @param {String} fmt yyyy-MM-ddyyyy-MM-dd HH:mm:ss
*/ */
export const formatTime = (time: Date | number | string, fmt: string) => { export function formatTime(time: Date | number | string, fmt: string) {
if (!time) return '' if (!time) return ''
else { else {
const date = new Date(time) const date = new Date(time)
@ -98,7 +105,7 @@ export const formatTime = (time: Date | number | string, fmt: string) => {
/** /**
* *
*/ */
export const toAnyString = () => { export function toAnyString() {
const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => { const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {
const r: number = (Math.random() * 16) | 0 const r: number = (Math.random() * 16) | 0
const v: number = c === 'x' ? r : (r & 0x3) | 0x8 const v: number = c === 'x' ? r : (r & 0x3) | 0x8
@ -107,6 +114,13 @@ export const toAnyString = () => {
return str return str
} }
/**
*
*/
export function firstUpperCase(str: string) {
return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
}
export const generateUUID = () => { export const generateUUID = () => {
if (typeof crypto === 'object') { if (typeof crypto === 'object') {
if (typeof crypto.randomUUID === 'function') { if (typeof crypto.randomUUID === 'function') {

View File

@ -38,7 +38,7 @@
<template #label> <template #label>
<Tooltip <Tooltip
message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头" message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
titel="路由地址" title="路由地址"
/> />
</template> </template>
<el-input v-model="formData.path" clearable placeholder="请输入路由地址" /> <el-input v-model="formData.path" clearable placeholder="请输入路由地址" />
@ -53,7 +53,7 @@
<template #label> <template #label>
<Tooltip <Tooltip
message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)" message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
titel="权限标识" title="权限标识"
/> />
</template> </template>
<el-input v-model="formData.permission" clearable placeholder="请输入权限标识" /> <el-input v-model="formData.permission" clearable placeholder="请输入权限标识" />
@ -74,7 +74,7 @@
</el-form-item> </el-form-item>
<el-form-item v-if="formData.type !== 3" label="显示状态" prop="visible"> <el-form-item v-if="formData.type !== 3" label="显示状态" prop="visible">
<template #label> <template #label>
<Tooltip message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" titel="显示状态" /> <Tooltip message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" title="显示状态" />
</template> </template>
<el-radio-group v-model="formData.visible"> <el-radio-group v-model="formData.visible">
<el-radio key="true" :label="true" border>显示</el-radio> <el-radio key="true" :label="true" border>显示</el-radio>
@ -85,7 +85,7 @@
<template #label> <template #label>
<Tooltip <Tooltip
message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单" message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单"
titel="总是显示" title="总是显示"
/> />
</template> </template>
<el-radio-group v-model="formData.alwaysShow"> <el-radio-group v-model="formData.alwaysShow">
@ -97,7 +97,7 @@
<template #label> <template #label>
<Tooltip <Tooltip
message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段" message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段"
titel="缓存状态" title="缓存状态"
/> />
</template> </template>
<el-radio-group v-model="formData.keepAlive"> <el-radio-group v-model="formData.keepAlive">

View File

@ -1,6 +1,6 @@
<template> <template>
<doc-alert title="功能权限" url="https://doc.iocoder.cn/resource-permission" /> <doc-alert title="功能权限" url="https://doc.iocoder.cn/resource-permission" />
<doc-alert title="菜单路由" url="https://doc.iocoder.cn/vue2/route/" /> <doc-alert title="菜单路由" url="https://doc.iocoder.cn/vue3/route/" />
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<ContentWrap> <ContentWrap>

View File

@ -27,7 +27,6 @@
"@intlify/unplugin-vue-i18n/types", "@intlify/unplugin-vue-i18n/types",
"vite/client", "vite/client",
"element-plus/global", "element-plus/global",
"@types/intro.js",
"@types/qrcode", "@types/qrcode",
"vite-plugin-svg-icons/client" "vite-plugin-svg-icons/client"
], ],

View File

@ -101,5 +101,8 @@ ${selector}:after {
] ]
], ],
presets: [presetUno({ dark: 'class', attributify: false })], presets: [presetUno({ dark: 'class', attributify: false })],
transformers: [transformerVariantGroup()] transformers: [transformerVariantGroup()],
shortcuts: {
'wh-full': 'w-full h-full'
}
}) })