feat(Breadcrumbe): Add Breadcrumb component

style: change function to arrow function
This commit is contained in:
kailong321200875 2022-01-15 14:24:50 +08:00
parent 2fe9543b84
commit 4612e5544b
55 changed files with 586 additions and 270 deletions

View File

@ -50,5 +50,17 @@ export default [
} }
} }
} }
},
// 退出接口
{
url: '/user/loginOut',
method: 'get',
timeout,
response: () => {
return {
code: result_code,
data: null
}
}
} }
] as MockMethod[] ] as MockMethod[]

View File

@ -33,6 +33,7 @@ specifiers:
lodash-es: ^4.17.21 lodash-es: ^4.17.21
mockjs: ^1.1.0 mockjs: ^1.1.0
nprogress: ^0.2.0 nprogress: ^0.2.0
path-to-regexp: ^6.2.0
pinia: ^2.0.9 pinia: ^2.0.9
postcss: ^8.4.5 postcss: ^8.4.5
postcss-html: ^1.3.0 postcss-html: ^1.3.0
@ -41,7 +42,6 @@ specifiers:
pretty-quick: ^3.1.3 pretty-quick: ^3.1.3
qs: ^6.10.3 qs: ^6.10.3
rimraf: ^3.0.2 rimraf: ^3.0.2
screenfull: ^6.0.0
stylelint: ^14.2.0 stylelint: ^14.2.0
stylelint-config-html: ^1.0.0 stylelint-config-html: ^1.0.0
stylelint-config-prettier: ^9.0.3 stylelint-config-prettier: ^9.0.3
@ -74,9 +74,9 @@ dependencies:
lodash-es: registry.nlark.com/lodash-es/4.17.21 lodash-es: registry.nlark.com/lodash-es/4.17.21
mockjs: registry.npmmirror.com/mockjs/1.1.0 mockjs: registry.npmmirror.com/mockjs/1.1.0
nprogress: registry.npmmirror.com/nprogress/0.2.0 nprogress: registry.npmmirror.com/nprogress/0.2.0
path-to-regexp: registry.npmmirror.com/path-to-regexp/6.2.0
pinia: registry.npmmirror.com/pinia/2.0.9_typescript@4.5.4+vue@3.2.26 pinia: registry.npmmirror.com/pinia/2.0.9_typescript@4.5.4+vue@3.2.26
qs: registry.npmmirror.com/qs/6.10.3 qs: registry.npmmirror.com/qs/6.10.3
screenfull: registry.npmmirror.com/screenfull/6.0.0
vue: registry.npmmirror.com/vue/3.2.26 vue: registry.npmmirror.com/vue/3.2.26
vue-i18n: registry.npmmirror.com/vue-i18n/9.1.9_vue@3.2.26 vue-i18n: registry.npmmirror.com/vue-i18n/9.1.9_vue@3.2.26
vue-router: registry.npmmirror.com/vue-router/4.0.12_vue@3.2.26 vue-router: registry.npmmirror.com/vue-router/4.0.12_vue@3.2.26
@ -1732,7 +1732,7 @@ packages:
{ {
integrity: sha1-0t5eA0JOcH3BDHQGjd7a5wh0Gyc=, integrity: sha1-0t5eA0JOcH3BDHQGjd7a5wh0Gyc=,
registry: https://registry.npm.taobao.org/, registry: https://registry.npm.taobao.org/,
tarball: https://registry.nlark.com/eslint-utils/download/eslint-utils-2.1.0.tgz tarball: https://registry.nlark.com/eslint-utils/download/eslint-utils-2.1.0.tgz?cache=0&sync_timestamp=1631600361784&other_urls=https%3A%2F%2Fregistry.nlark.com%2Feslint-utils%2Fdownload%2Feslint-utils-2.1.0.tgz
} }
name: eslint-utils name: eslint-utils
version: 2.1.0 version: 2.1.0
@ -4246,17 +4246,6 @@ packages:
version: 1.0.7 version: 1.0.7
dev: true dev: true
registry.nlark.com/path-to-regexp/6.2.0:
resolution:
{
integrity: sha1-97OAMzYQTDRoia3s5hRmkjBkXzg=,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.nlark.com/path-to-regexp/download/path-to-regexp-6.2.0.tgz
}
name: path-to-regexp
version: 6.2.0
dev: true
registry.nlark.com/path-type/4.0.0: registry.nlark.com/path-type/4.0.0:
resolution: resolution:
{ {
@ -4959,7 +4948,7 @@ packages:
{ {
integrity: sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=, integrity: sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=,
registry: https://registry.npm.taobao.org/, registry: https://registry.npm.taobao.org/,
tarball: https://registry.nlark.com/semver/download/semver-5.7.1.tgz?cache=0&sync_timestamp=1631500167672&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsemver%2Fdownload%2Fsemver-5.7.1.tgz tarball: https://registry.nlark.com/semver/download/semver-5.7.1.tgz
} }
name: semver name: semver
version: 5.7.1 version: 5.7.1
@ -4971,7 +4960,7 @@ packages:
{ {
integrity: sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=, integrity: sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=,
registry: https://registry.npm.taobao.org/, registry: https://registry.npm.taobao.org/,
tarball: https://registry.nlark.com/semver/download/semver-6.3.0.tgz?cache=0&sync_timestamp=1631500167672&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsemver%2Fdownload%2Fsemver-6.3.0.tgz tarball: https://registry.nlark.com/semver/download/semver-6.3.0.tgz
} }
name: semver name: semver
version: 6.3.0 version: 6.3.0
@ -7066,8 +7055,8 @@ packages:
vue-i18n: vue-i18n:
optional: true optional: true
dependencies: dependencies:
'@intlify/message-compiler': registry.npmmirror.com/@intlify/message-compiler/9.2.0-beta.27 '@intlify/message-compiler': registry.npmmirror.com/@intlify/message-compiler/9.2.0-beta.28
'@intlify/shared': registry.npmmirror.com/@intlify/shared/9.2.0-beta.27 '@intlify/shared': registry.npmmirror.com/@intlify/shared/9.2.0-beta.28
jsonc-eslint-parser: registry.npmmirror.com/jsonc-eslint-parser/1.4.1 jsonc-eslint-parser: registry.npmmirror.com/jsonc-eslint-parser/1.4.1
source-map: registry.nlark.com/source-map/0.6.1 source-map: registry.nlark.com/source-map/0.6.1
vue-i18n: registry.npmmirror.com/vue-i18n/9.1.9_vue@3.2.26 vue-i18n: registry.npmmirror.com/vue-i18n/9.1.9_vue@3.2.26
@ -7123,18 +7112,18 @@ packages:
source-map: registry.nlark.com/source-map/0.6.1 source-map: registry.nlark.com/source-map/0.6.1
dev: false dev: false
registry.npmmirror.com/@intlify/message-compiler/9.2.0-beta.27: registry.npmmirror.com/@intlify/message-compiler/9.2.0-beta.28:
resolution: resolution:
{ {
integrity: sha512-T3mBTm0559VX6l+lh8p5gDJ9/IS1XbVXeeMNJ2zTzxrf4lXg8OuotNjaxG3ZsuauQ5OqqlArkMYryXGyZnHolA==, integrity: sha512-NBH9fZyitN2cijGt8bmU1W7ZPdhKbgW01L1RxJKFJW0cRaCmknJq63Aif1Q6xcxKt9ZhPbvIKHgPGzg1nWMfeA==,
registry: https://registry.npm.taobao.org/, registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/@intlify/message-compiler/download/@intlify/message-compiler-9.2.0-beta.27.tgz tarball: https://registry.npmmirror.com/@intlify/message-compiler/download/@intlify/message-compiler-9.2.0-beta.28.tgz
} }
name: '@intlify/message-compiler' name: '@intlify/message-compiler'
version: 9.2.0-beta.27 version: 9.2.0-beta.28
engines: { node: '>= 12' } engines: { node: '>= 12' }
dependencies: dependencies:
'@intlify/shared': registry.npmmirror.com/@intlify/shared/9.2.0-beta.27 '@intlify/shared': registry.npmmirror.com/@intlify/shared/9.2.0-beta.28
source-map: registry.nlark.com/source-map/0.6.1 source-map: registry.nlark.com/source-map/0.6.1
dev: true dev: true
@ -7178,15 +7167,15 @@ packages:
engines: { node: '>= 10' } engines: { node: '>= 10' }
dev: false dev: false
registry.npmmirror.com/@intlify/shared/9.2.0-beta.27: registry.npmmirror.com/@intlify/shared/9.2.0-beta.28:
resolution: resolution:
{ {
integrity: sha512-+Av77mIHy0qFkAq96mMAQGYcColMGN7e5+rUsyn3XxBw6oC3AGqYn/cQ6U/T3qOrzcHgcA+etAaLN3IxFqkJDw==, integrity: sha512-JBMcoj1D4kSAma7Vb0+d8z6lPLIn7hIdZJPxbU8bgeMMniwKLoIS/jGlEfrZihsB5+otckPeQp203z8skwVS0w==,
registry: https://registry.npm.taobao.org/, registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/@intlify/shared/download/@intlify/shared-9.2.0-beta.27.tgz tarball: https://registry.npmmirror.com/@intlify/shared/download/@intlify/shared-9.2.0-beta.28.tgz
} }
name: '@intlify/shared' name: '@intlify/shared'
version: 9.2.0-beta.27 version: 9.2.0-beta.28
engines: { node: '>= 12' } engines: { node: '>= 12' }
dev: true dev: true
@ -7212,7 +7201,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@intlify/bundle-utils': registry.npmmirror.com/@intlify/bundle-utils/2.2.0_vue-i18n@9.1.9 '@intlify/bundle-utils': registry.npmmirror.com/@intlify/bundle-utils/2.2.0_vue-i18n@9.1.9
'@intlify/shared': registry.npmmirror.com/@intlify/shared/9.2.0-beta.27 '@intlify/shared': registry.npmmirror.com/@intlify/shared/9.2.0-beta.28
'@rollup/pluginutils': registry.npmmirror.com/@rollup/pluginutils/4.1.2 '@rollup/pluginutils': registry.npmmirror.com/@rollup/pluginutils/4.1.2
debug: registry.npmmirror.com/debug/4.3.3 debug: registry.npmmirror.com/debug/4.3.3
fast-glob: registry.nlark.com/fast-glob/3.2.7 fast-glob: registry.nlark.com/fast-glob/3.2.7
@ -9689,7 +9678,7 @@ packages:
{ {
integrity: sha1-MOvR73wv3/AcOk8VEESvJfqwUj4=, integrity: sha1-MOvR73wv3/AcOk8VEESvJfqwUj4=,
registry: https://registry.npm.taobao.org/, registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-1.3.0.tgz tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-1.3.0.tgz?cache=0&sync_timestamp=1636378650851&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-visitor-keys%2Fdownload%2Feslint-visitor-keys-1.3.0.tgz
} }
name: eslint-visitor-keys name: eslint-visitor-keys
version: 1.3.0 version: 1.3.0
@ -9701,7 +9690,7 @@ packages:
{ {
integrity: sha1-9lMoJZMFknOSyTjtROsKXJsr0wM=, integrity: sha1-9lMoJZMFknOSyTjtROsKXJsr0wM=,
registry: https://registry.npm.taobao.org/, registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-2.1.0.tgz tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-2.1.0.tgz?cache=0&sync_timestamp=1636378650851&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-visitor-keys%2Fdownload%2Feslint-visitor-keys-2.1.0.tgz
} }
name: eslint-visitor-keys name: eslint-visitor-keys
version: 2.1.0 version: 2.1.0
@ -11134,6 +11123,16 @@ packages:
engines: { node: '>=8' } engines: { node: '>=8' }
dev: true dev: true
registry.npmmirror.com/path-to-regexp/6.2.0:
resolution:
{
integrity: sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/path-to-regexp/download/path-to-regexp-6.2.0.tgz
}
name: path-to-regexp
version: 6.2.0
registry.npmmirror.com/picocolors/1.0.0: registry.npmmirror.com/picocolors/1.0.0:
resolution: resolution:
{ {
@ -11609,18 +11608,6 @@ packages:
tslib: registry.npmmirror.com/tslib/2.3.1 tslib: registry.npmmirror.com/tslib/2.3.1
dev: true dev: true
registry.npmmirror.com/screenfull/6.0.0:
resolution:
{
integrity: sha512-LGY0nhNQkC4FX4DT4pZdJ5cZH5EOz9Gfh9KcVMl779pS677k4IV1Wv7sY/CwC9VKFT21fYgCh7zkTVVefi5XKA==,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/screenfull/download/screenfull-6.0.0.tgz
}
name: screenfull
version: 6.0.0
engines: { node: ^14.13.1 || >=16.0.0 }
dev: false
registry.npmmirror.com/shebang-regex/3.0.0: registry.npmmirror.com/shebang-regex/3.0.0:
resolution: resolution:
{ {
@ -12307,7 +12294,7 @@ packages:
esbuild: registry.npmmirror.com/esbuild/0.11.3 esbuild: registry.npmmirror.com/esbuild/0.11.3
fast-glob: registry.nlark.com/fast-glob/3.2.7 fast-glob: registry.nlark.com/fast-glob/3.2.7
mockjs: registry.npmmirror.com/mockjs/1.1.0 mockjs: registry.npmmirror.com/mockjs/1.1.0
path-to-regexp: registry.nlark.com/path-to-regexp/6.2.0 path-to-regexp: registry.npmmirror.com/path-to-regexp/6.2.0
vite: registry.npmmirror.com/vite/2.7.10_less@4.1.2 vite: registry.npmmirror.com/vite/2.7.10_less@4.1.2
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup

View File

@ -8,7 +8,7 @@ const appStore = useAppStore()
const size = computed(() => appStore.size) const size = computed(() => appStore.size)
function initDark() { const initDark = () => {
const isDarkTheme = isDark() const isDarkTheme = isDark()
appStore.setIsDark(isDarkTheme) appStore.setIsDark(isDarkTheme)
} }

View File

@ -9,3 +9,7 @@ export const loginApi = (data: UserLoginType) => {
UserLoginType UserLoginType
>) >)
} }
export const loginOutApi = () => {
return request({ url: '/user/loginOut', method: 'get' })
}

View File

@ -0,0 +1,89 @@
<script lang="tsx">
import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'
import { ref, watch, computed, unref, defineComponent, TransitionGroup } from 'vue'
import { useRouter } from 'vue-router'
// import { compile } from 'path-to-regexp'
import { usePermissionStore } from '@/store/modules/permission'
import { filterBreadcrumb } from './helper'
import { filter, treeToList } from '@/utils/tree'
import type { RouteLocationNormalizedLoaded, RouteMeta } from 'vue-router'
import { useI18n } from '@/hooks/web/useI18n'
import { Icon } from '@/components/Icon'
export default defineComponent({
name: 'Breadcrumb',
setup() {
const { currentRoute } = useRouter()
const { t } = useI18n()
const levelList = ref<AppRouteRecordRaw[]>([])
const permissionStore = usePermissionStore()
const menuRouters = computed(() => {
const routers = permissionStore.getRouters
return filterBreadcrumb(routers)
})
const getBreadcrumb = () => {
const currentPath = currentRoute.value.path
levelList.value = filter<AppRouteRecordRaw>(unref(menuRouters), (node: AppRouteRecordRaw) => {
return node.path === currentPath
})
}
const renderBreadcrumb = () => {
const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))
return breadcrumbList.map((v) => {
const disabled = v.redirect === 'noredirect'
const meta = v.meta as RouteMeta
return (
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
{meta?.icon ? (
<>
<Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title)}
</>
) : (
t(v?.meta?.title)
)}
</ElBreadcrumbItem>
)
})
}
watch(
() => currentRoute.value,
(route: RouteLocationNormalizedLoaded) => {
if (route.path.startsWith('/redirect/')) {
return
}
getBreadcrumb()
},
{
immediate: true
}
)
return () => (
<ElBreadcrumb separator="/" class="flex items-center h-full ml-[10px]">
<TransitionGroup appear enter-active-class="animate__animated animate__fadeInRight">
{renderBreadcrumb()}
</TransitionGroup>
</ElBreadcrumb>
)
}
})
</script>
<style lang="less" scoped>
:deep(.el-breadcrumb__item) {
display: flex;
.el-breadcrumb__inner {
display: flex;
align-items: center;
}
}
</style>

View File

@ -0,0 +1,31 @@
import { pathResolve } from '@/utils/routerHelper'
import type { RouteMeta } from 'vue-router'
export const filterBreadcrumb = (
routes: AppRouteRecordRaw[],
parentPath = ''
): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = []
for (const route of routes) {
const meta = route?.meta as RouteMeta
if (meta.hidden && !meta.showMainRoute) {
continue
}
const data: AppRouteRecordRaw =
!meta.alwaysShow && route.children?.length === 1
? { ...route.children[0], path: pathResolve(route.path, route.children[0].path) }
: { ...route }
data.path = pathResolve(parentPath, data.path)
if (data.children) {
data.children = filterBreadcrumb(data.children, data.path)
}
if (data) {
res.push(data)
}
}
return res
}

View File

@ -1,13 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, unref } from 'vue' import { computed, unref } from 'vue'
import { Icon } from '@/components/Icon'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore() const appStore = useAppStore()
const collapse = computed(() => appStore.getCollapse) const collapse = computed(() => appStore.getCollapse)
function toggleCollapse() { const toggleCollapse = () => {
const collapsed = unref(collapse) const collapsed = unref(collapse)
appStore.setCollapse(!collapsed) appStore.setCollapse(!collapsed)
} }

View File

@ -1,8 +1,33 @@
<script setup lang="ts"> <script setup lang="ts">
import { provide, computed } from 'vue' import { provide, computed, watch } from 'vue'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { ElConfigProvider } from 'element-plus' import { 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 { setCssVar } from '@/utils'
const appStore = useAppStore()
const { width } = useWindowSize()
watch(
() => width.value,
(width: number) => {
if (width < 768) {
!appStore.getMobile ? appStore.setMobile(true) : undefined
setCssVar('--left-menu-min-width', '0')
appStore.setCollapse(true)
appStore.getLayout !== 'classic' ? appStore.setLayout('classic') : undefined
} else {
appStore.getMobile ? appStore.setMobile(false) : undefined
setCssVar('--left-menu-min-width', '64px')
}
},
{
immediate: true
}
)
const localeStore = useLocaleStore() const localeStore = useLocaleStore()

View File

@ -62,7 +62,7 @@ export default defineComponent({
}) })
// //
function setValues(data: FormSetValuesType[]) { const setValues = (data: FormSetValuesType[]) => {
if (!data.length) return if (!data.length) return
const formData: Recordable = {} const formData: Recordable = {}
for (const v of data) { for (const v of data) {
@ -89,7 +89,7 @@ export default defineComponent({
) )
// 使 // 使
function renderWrap() { const renderWrap = () => {
const content = isCol ? ( const content = isCol ? (
<ElRow gutter={20}>{renderFormItemWrap()}</ElRow> <ElRow gutter={20}>{renderFormItemWrap()}</ElRow>
) : ( ) : (
@ -99,7 +99,7 @@ export default defineComponent({
} }
// el-col // el-col
function renderFormItemWrap() { const renderFormItemWrap = () => {
// hidden // hidden
return schema return schema
.filter((v) => !v.hidden) .filter((v) => !v.hidden)
@ -119,7 +119,7 @@ export default defineComponent({
} }
// formItem // formItem
function renderFormItem(item: FormSchema) { const renderFormItem = (item: FormSchema) => {
// options // options
const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer'] const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
const slotsMap: Recordable = { const slotsMap: Recordable = {
@ -162,7 +162,7 @@ export default defineComponent({
} }
// options // options
function renderOptions(item: FormSchema) { const renderOptions = (item: FormSchema) => {
switch (item.component) { switch (item.component) {
case 'Select': case 'Select':
const { renderSelectOptions } = useRenderSelect(slots) const { renderSelectOptions } = useRenderSelect(slots)
@ -181,7 +181,7 @@ export default defineComponent({
} }
// Form // Form
function getFormBindValue() { const getFormBindValue = () => {
// //
const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model'] const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']
const props = { ...unref(getProps) } const props = { ...unref(getProps) }

View File

@ -1,8 +1,8 @@
import { ElCheckbox, ElCheckboxButton } from 'element-plus' import { ElCheckbox, ElCheckboxButton } from 'element-plus'
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
export function useRenderChcekbox() { export const useRenderChcekbox = () => {
function renderChcekboxOptions(item: FormSchema) { const renderChcekboxOptions = (item: FormSchema) => {
// 如果有别名,就取别名 // 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField const labelAlias = item?.componentProps?.optionsAlias?.labelField
const valueAlias = item?.componentProps?.optionsAlias?.valueField const valueAlias = item?.componentProps?.optionsAlias?.valueField

View File

@ -1,8 +1,8 @@
import { ElRadio, ElRadioButton } from 'element-plus' import { ElRadio, ElRadioButton } from 'element-plus'
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
export function useRenderRadio() { export const useRenderRadio = () => {
function renderRadioOptions(item: FormSchema) { const renderRadioOptions = (item: FormSchema) => {
// 如果有别名,就取别名 // 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField const labelAlias = item?.componentProps?.optionsAlias?.labelField
const valueAlias = item?.componentProps?.optionsAlias?.valueField const valueAlias = item?.componentProps?.optionsAlias?.valueField

View File

@ -2,9 +2,9 @@ import { ElOption, ElOptionGroup } from 'element-plus'
import { getSlot } from '@/utils/tsxHelper' import { getSlot } from '@/utils/tsxHelper'
import { Slots } from 'vue' import { Slots } from 'vue'
export function useRenderSelect(slots: Slots) { export const useRenderSelect = (slots: Slots) => {
// 渲染 select options // 渲染 select options
function renderSelectOptions(item: FormSchema) { const renderSelectOptions = (item: FormSchema) => {
// 如果有别名,就取别名 // 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField const labelAlias = item?.componentProps?.optionsAlias?.labelField
return item?.componentProps?.options?.map((option) => { return item?.componentProps?.options?.map((option) => {
@ -25,7 +25,7 @@ export function useRenderSelect(slots: Slots) {
} }
// 渲染 select option item // 渲染 select option item
function renderSelectOptionItem(item: FormSchema, option: ComponentOptions) { const renderSelectOptionItem = (item: FormSchema, option: ComponentOptions) => {
// 如果有别名,就取别名 // 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField const labelAlias = item?.componentProps?.optionsAlias?.labelField
const valueAlias = item?.componentProps?.optionsAlias?.valueField const valueAlias = item?.componentProps?.optionsAlias?.valueField

View File

@ -17,7 +17,7 @@ interface PlaceholderMoel {
* @returns * @returns
* @description placeholder * @description placeholder
*/ */
export function setTextPlaceholder(schema: FormSchema): PlaceholderMoel { export const setTextPlaceholder = (schema: FormSchema): PlaceholderMoel => {
const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword'] const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword']
const selectMap = ['Select', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect'] const selectMap = ['Select', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect']
if (textMap.includes(schema?.component as string)) { if (textMap.includes(schema?.component as string)) {
@ -53,7 +53,7 @@ export function setTextPlaceholder(schema: FormSchema): PlaceholderMoel {
* @returns * @returns
* @description * @description
*/ */
export function setGridProp(col: ColProps = {}): ColProps { export const setGridProp = (col: ColProps = {}): ColProps => {
const colProps: ColProps = { const colProps: ColProps = {
// 如果有span代表用户优先级更高所以不需要默认栅格 // 如果有span代表用户优先级更高所以不需要默认栅格
...(col.span ...(col.span
@ -75,7 +75,7 @@ export function setGridProp(col: ColProps = {}): ColProps {
* @param item * @param item
* @returns clearable * @returns clearable
*/ */
export function setComponentProps(item: FormSchema): Recordable { export const setComponentProps = (item: FormSchema): Recordable => {
const notNeedClearable = ['ColorPicker'] const notNeedClearable = ['ColorPicker']
const componentProps: Recordable = notNeedClearable.includes(item.component as string) const componentProps: Recordable = notNeedClearable.includes(item.component as string)
? { ...item.componentProps } ? { ...item.componentProps }
@ -94,11 +94,11 @@ export function setComponentProps(item: FormSchema): Recordable {
* @param slotsProps * @param slotsProps
* @param field * @param field
*/ */
export function setItemComponentSlots( export const setItemComponentSlots = (
slots: Slots, slots: Slots,
slotsProps: Recordable = {}, slotsProps: Recordable = {},
field: string field: string
): Recordable { ): Recordable => {
const slotObj: Recordable = {} const slotObj: Recordable = {}
for (const key in slotsProps) { for (const key in slotsProps) {
if (slotsProps[key]) { if (slotsProps[key]) {
@ -118,7 +118,7 @@ export function setItemComponentSlots(
* @returns FormMoel * @returns FormMoel
* @description formModel * @description formModel
*/ */
export function initModel(schema: FormSchema[], formModel: Recordable) { export const initModel = (schema: FormSchema[], formModel: Recordable) => {
const model: Recordable = { ...formModel } const model: Recordable = { ...formModel }
schema.map((v) => { schema.map((v) => {
// 如果是hidden就删除对应的值 // 如果是hidden就删除对应的值
@ -138,7 +138,7 @@ export function initModel(schema: FormSchema[], formModel: Recordable) {
* @param field * @param field
* @returns FormIiem插槽 * @returns FormIiem插槽
*/ */
export function setFormItemSlots(slots: Slots, field: string): Recordable { export const setFormItemSlots = (slots: Slots, field: string): Recordable => {
const slotObj: Recordable = {} const slotObj: Recordable = {}
if (slots[`${field}-error`]) { if (slots[`${field}-error`]) {
slotObj['error'] = (data: Recordable) => { slotObj['error'] = (data: Recordable) => {

View File

@ -1,14 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, unref, ref, watch, nextTick } from 'vue' import { computed, unref, ref, watch, nextTick } from 'vue'
import { ElIcon } from 'element-plus' import { ElIcon } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import Iconify from '@purge-icons/generated' import Iconify from '@purge-icons/generated'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('icon')
const props = defineProps({ const props = defineProps({
// icon name // icon name
icon: propTypes.string, icon: propTypes.string,
@ -34,7 +29,7 @@ const getIconifyStyle = computed(() => {
} }
}) })
async function updateIcon(icon: string) { const updateIcon = async (icon: string) => {
if (unref(isLocal)) return if (unref(isLocal)) return
const el = unref(elRef) const el = unref(elRef)
@ -66,7 +61,7 @@ watch(
</script> </script>
<template> <template>
<ElIcon :class="prefixCls" :size="size" :color="color"> <ElIcon class="v-icon" :size="size" :color="color">
<svg v-if="isLocal" aria-hidden="true"> <svg v-if="isLocal" aria-hidden="true">
<use :xlink:href="symbolId" /> <use :xlink:href="symbolId" />
</svg> </svg>

View File

@ -2,7 +2,6 @@
import { ref, unref, computed, watch } from 'vue' import { ref, unref, computed, watch } from 'vue'
import { ElInput } from 'element-plus' import { ElInput } from 'element-plus'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { useConfigGlobal } from '@/hooks/web/useConfigGlobal' import { useConfigGlobal } from '@/hooks/web/useConfigGlobal'
import { zxcvbn } from '@zxcvbn-ts/core' import { zxcvbn } from '@zxcvbn-ts/core'
import type { ZxcvbnResult } from '@zxcvbn-ts/core' import type { ZxcvbnResult } from '@zxcvbn-ts/core'
@ -25,15 +24,10 @@ const { configGlobal } = useConfigGlobal()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
// class
const { getPrefixCls } = useDesign()
const prefixCls = ref(getPrefixCls('input-password'))
// inputtype // inputtype
const textType = ref<'password' | 'text'>('password') const textType = ref<'password' | 'text'>('password')
function changeTextType() { const changeTextType = () => {
textType.value = unref(textType) === 'text' ? 'password' : 'text' textType.value = unref(textType) === 'text' ? 'password' : 'text'
} }
@ -61,7 +55,7 @@ const getIconName = computed(() =>
</script> </script>
<template> <template>
<div :class="[prefixCls, `${prefixCls}--${configGlobal?.size}`]"> <div :class="['v-input-password', `v-input-password--${configGlobal?.size}`]">
<ElInput v-bind="$attrs" v-model="valueRef" :type="textType"> <ElInput v-bind="$attrs" v-model="valueRef" :type="textType">
<template #suffix> <template #suffix>
<Icon class="el-input__icon cursor-pointer" :icon="getIconName" @click="changeTextType" /> <Icon class="el-input__icon cursor-pointer" :icon="getIconName" @click="changeTextType" />
@ -69,10 +63,9 @@ const getIconName = computed(() =>
</ElInput> </ElInput>
<div <div
v-if="strength" v-if="strength"
:class="`${prefixCls}__bar`" class="v-input-password__bar relative h-6px mt-10px mb-6px mr-auto ml-auto"
class="relative h-6px mt-10px mb-6px mr-auto ml-auto"
> >
<div :class="`${prefixCls}__bar--fill`" :data-score="getPasswordStrength"></div> <div class="v-input-password__bar--fill" :data-score="getPasswordStrength"></div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -10,7 +10,7 @@ const langMap = computed(() => localeStore.getLocaleMap)
const currentLang = computed(() => localeStore.getLocale) const currentLang = computed(() => localeStore.getLocale)
function setLang(lang: LocaleType) { const setLang = (lang: LocaleType) => {
if (lang === unref(currentLang).lang) return if (lang === unref(currentLang).lang) return
// //
window.location.reload() window.location.reload()
@ -24,13 +24,7 @@ function setLang(lang: LocaleType) {
<template> <template>
<ElDropdown trigger="click" @command="setLang"> <ElDropdown trigger="click" @command="setLang">
<Icon <Icon :size="18" icon="ion:language-sharp" class="cursor-pointer" :class="$attrs.class" />
:size="18"
icon="ion:language-sharp"
color="var(--el-text-color-primary)"
class="cursor-pointer"
:class="$attrs.class"
/>
<template #dropdown> <template #dropdown>
<ElDropdownMenu> <ElDropdownMenu>
<ElDropdownItem v-for="item in langMap" :key="item.lang" :command="item.lang"> <ElDropdownItem v-for="item in langMap" :key="item.lang" :command="item.lang">

View File

@ -1,7 +1,6 @@
<script lang="tsx"> <script lang="tsx">
import { computed, defineComponent } from 'vue' import { computed, defineComponent } from 'vue'
import { ElMenu, ElScrollbar } from 'element-plus' import { ElMenu, ElScrollbar } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { usePermissionStore } from '@/store/modules/permission' import { usePermissionStore } from '@/store/modules/permission'
import type { LayoutType } from '@/config/app' import type { LayoutType } from '@/config/app'
@ -18,10 +17,6 @@ export default defineComponent({
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore()
const { getPrefixCls } = useDesign()
const preFixCls = getPrefixCls('menu')
const menuMode = computed((): 'vertical' | 'horizontal' => { const menuMode = computed((): 'vertical' | 'horizontal' => {
// //
const vertical: LayoutType[] = ['classic'] const vertical: LayoutType[] = ['classic']
@ -46,7 +41,7 @@ export default defineComponent({
return path return path
}) })
function menuSelect(index: string) { const menuSelect = (index: string) => {
if (isUrl(index)) { if (isUrl(index)) {
window.open(index) window.open(index)
} else { } else {
@ -57,8 +52,8 @@ export default defineComponent({
return () => ( return () => (
<div <div
class={[ class={[
preFixCls, 'v-menu',
'h-[100%] overflow-hidden', 'h-[100%] overflow-hidden z-100',
appStore.getCollapse appStore.getCollapse
? 'w-[var(--left-menu-min-width)]' ? 'w-[var(--left-menu-min-width)]'
: 'w-[var(--left-menu-max-width)]', : 'w-[var(--left-menu-max-width)]',
@ -147,7 +142,7 @@ export default defineComponent({
// //
:deep(.horizontal-collapse-transition) { :deep(.horizontal-collapse-transition) {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out !important; // transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out !important;
.@{prefix-cls}__title { .@{prefix-cls}__title {
display: none; display: none;
} }

View File

@ -6,11 +6,11 @@ import { useRenderMenuTitle } from './useRenderMenuTitle'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { pathResolve } from '@/utils/routerHelper' import { pathResolve } from '@/utils/routerHelper'
export function useRenderMenuItem( export const useRenderMenuItem = (
allRouters: AppRouteRecordRaw[] = [], allRouters: AppRouteRecordRaw[] = [],
menuMode: 'vertical' | 'horizontal' menuMode: 'vertical' | 'horizontal'
) { ) => {
function renderMenuItem(routers?: AppRouteRecordRaw[]) { const renderMenuItem = (routers?: AppRouteRecordRaw[]) => {
return (routers || allRouters).map((v) => { return (routers || allRouters).map((v) => {
const meta = (v.meta ?? {}) as RouteMeta const meta = (v.meta ?? {}) as RouteMeta
if (!meta.hidden) { if (!meta.hidden) {

View File

@ -2,18 +2,18 @@ import type { RouteMeta } from 'vue-router'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
export function useRenderMenuTitle() { export const useRenderMenuTitle = () => {
function renderMenuTitle(meta: RouteMeta) { const renderMenuTitle = (meta: RouteMeta) => {
const { t } = useI18n() const { t } = useI18n()
const { title = 'Please set title', icon } = meta const { title = 'Please set title', icon } = meta
return icon ? ( return icon ? (
<> <>
<Icon icon={meta.icon}></Icon> <Icon icon={meta.icon}></Icon>
<span>{t(title as string)}</span> <span class="v-menu__title">{t(title as string)}</span>
</> </>
) : ( ) : (
<span>{t(title as string)}</span> <span class="v-menu__title">{t(title as string)}</span>
) )
} }

View File

@ -1,11 +1,6 @@
import type { RouteMeta } from 'vue-router' import type { RouteMeta } from 'vue-router'
import { ref, unref } from 'vue' import { ref, unref } from 'vue'
import { findPath } from '@/utils/tree'
interface TreeConfig {
id: string
children: string
pid: string
}
type OnlyOneChildType = AppRouteRecordRaw & { noShowingChildren?: boolean } type OnlyOneChildType = AppRouteRecordRaw & { noShowingChildren?: boolean }
@ -14,50 +9,15 @@ interface HasOneShowingChild {
onlyOneChild?: OnlyOneChildType onlyOneChild?: OnlyOneChildType
} }
const DEFAULT_CONFIG: TreeConfig = { export const getAllParentPath = <T = Recordable>(treeData: T[], path: string) => {
id: 'id',
children: 'children',
pid: 'pid'
}
const getConfig = (config: Partial<TreeConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
export function getAllParentPath<T = Recordable>(treeData: T[], path: string) {
const menuList = findPath(treeData, (n) => n.path === path) as AppRouteRecordRaw[] const menuList = findPath(treeData, (n) => n.path === path) as AppRouteRecordRaw[]
return (menuList || []).map((item) => item.path) return (menuList || []).map((item) => item.path)
} }
export function findPath<T = any>( export const hasOneShowingChild = (
tree: any,
func: Fn,
config: Partial<TreeConfig> = {}
): T | T[] | null {
config = getConfig(config)
const path: T[] = []
const list = [...tree]
const visitedSet = new Set()
const { children } = config
while (list.length) {
const node = list[0]
if (visitedSet.has(node)) {
path.pop()
list.shift()
} else {
visitedSet.add(node)
node[children!] && list.unshift(...node[children!])
path.push(node)
if (func(node)) {
return path
}
}
}
return null
}
export function hasOneShowingChild(
children: AppRouteRecordRaw[] = [], children: AppRouteRecordRaw[] = [],
parent: AppRouteRecordRaw parent: AppRouteRecordRaw
): HasOneShowingChild { ): HasOneShowingChild => {
const onlyOneChild = ref<OnlyOneChildType>() const onlyOneChild = ref<OnlyOneChildType>()
const showingChildren = children.filter((v) => { const showingChildren = children.filter((v) => {

View File

@ -4,7 +4,7 @@ import { useFullscreen } from '@vueuse/core'
const { toggle, isFullscreen } = useFullscreen() const { toggle, isFullscreen } = useFullscreen()
function toggleFullscreen() { const toggleFullscreen = () => {
toggle() toggle()
} }
</script> </script>

View File

@ -9,7 +9,7 @@ const appStore = useAppStore()
const sizeMap = computed(() => appStore.sizeMap) const sizeMap = computed(() => appStore.sizeMap)
function setSize(size: ElememtPlusSzie) { const setSize = (size: ElememtPlusSzie) => {
appStore.setSize(size) appStore.setSize(size)
} }
</script> </script>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"></script> <script setup lang="ts"></script>
<template> <template>
<div>tagsView</div> <div class="h-[var(--tags-view-height)]">tagsView</div>
</template> </template>

View File

@ -2,7 +2,6 @@
import { ref } from 'vue' import { ref } from 'vue'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { ElSwitch } from 'element-plus' import { ElSwitch } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
import { useIcon } from '@/hooks/web/useIcon' import { useIcon } from '@/hooks/web/useIcon'
const Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' }) const Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' })
@ -14,21 +13,17 @@ const appStore = useAppStore()
// //
const isDark = ref(appStore.getIsDark) const isDark = ref(appStore.getIsDark)
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('theme-switch')
// switch // switch
const blackColor = 'var(--el-color-black)' const blackColor = 'var(--el-color-black)'
function themeChange(val: boolean) { const themeChange = (val: boolean) => {
appStore.setIsDark(val) appStore.setIsDark(val)
} }
</script> </script>
<template> <template>
<ElSwitch <ElSwitch
:class="prefixCls" class="v-theme-switch"
v-model="isDark" v-model="isDark"
inline-prompt inline-prompt
:border-color="blackColor" :border-color="blackColor"

View File

@ -4,6 +4,7 @@ import { useI18n } from '@/hooks/web/useI18n'
import { useCache } from '@/hooks/web/useCache' import { useCache } from '@/hooks/web/useCache'
import { resetRouter } from '@/router' import { resetRouter } from '@/router'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { loginOutApi } from '@/api/login'
const { t } = useI18n() const { t } = useI18n()
@ -11,16 +12,19 @@ const { wsCache } = useCache()
const { replace } = useRouter() const { replace } = useRouter()
function loginOut() { const loginOut = () => {
ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), { ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
confirmButtonText: t('common.ok'), confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'), cancelButtonText: t('common.cancel'),
type: 'warning' type: 'warning'
}) })
.then(() => { .then(async () => {
wsCache.clear() const res = await loginOutApi().catch(() => {})
resetRouter() // if (res) {
replace('/login') wsCache.clear()
resetRouter() //
replace('/login')
}
}) })
.catch(() => {}) .catch(() => {})
} }

View File

@ -1,6 +1,6 @@
import type { App } from 'vue' import type { App } from 'vue'
import { Icon } from './Icon' import { Icon } from './Icon'
export function setupGlobCom(app: App<Element>): void { export const setupGlobCom = (app: App<Element>): void => {
app.component('Icon', Icon) app.component('Icon', Icon)
} }

View File

@ -24,6 +24,7 @@ export interface AppState {
isDark: boolean isDark: boolean
size: ElememtPlusSzie size: ElememtPlusSzie
sizeMap: ElememtPlusSzie[] sizeMap: ElememtPlusSzie[]
mobile: boolean
} }
export const appModules: AppState = { export const appModules: AppState = {
@ -45,5 +46,6 @@ export const appModules: AppState = {
showMenuTab: false, // 是否固定一级菜单 showMenuTab: false, // 是否固定一级菜单
isDark: wsCache.get('isDark') || false, // 是否是暗黑模式 isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
size: wsCache.get('default') || 'default', // 组件尺寸 size: wsCache.get('default') || 'default', // 组件尺寸
sizeMap: ['default', 'large', 'small'] sizeMap: ['default', 'large', 'small'],
mobile: false // 是否是移动端
} }

View File

@ -6,8 +6,8 @@ import { config } from '@/config/axios/config'
const { default_headers } = config const { default_headers } = config
export function useAxios() { export const useAxios = () => {
function request(option: AxiosConfig): AxiosPromise { const request = (option: AxiosConfig): AxiosPromise => {
const { url, method, params, data, headersType, responseType } = option const { url, method, params, data, headersType, responseType } = option
return service({ return service({
url: url, url: url,

View File

@ -6,7 +6,7 @@ import WebStorageCache from 'web-storage-cache'
type CacheType = 'sessionStorage' | 'localStorage' type CacheType = 'sessionStorage' | 'localStorage'
export function useCache(type: CacheType = 'sessionStorage') { export const useCache = (type: CacheType = 'sessionStorage') => {
const wsCache: WebStorageCache = new WebStorageCache({ const wsCache: WebStorageCache = new WebStorageCache({
storage: type storage: type
}) })

View File

@ -1,6 +1,6 @@
import { inject } from 'vue' import { inject } from 'vue'
export function useConfigGlobal() { export const useConfigGlobal = () => {
const configGlobal = inject('configGlobal', {}) as ConfigGlobalTypes const configGlobal = inject('configGlobal', {}) as ConfigGlobalTypes
return { return {

View File

@ -1,13 +1,13 @@
import variables from '@/styles/variables.module.less' import variables from '@/styles/variables.module.less'
export function useDesign() { export const useDesign = () => {
const lessVariables = variables const lessVariables = variables
/** /**
* @param scope * @param scope
* @returns - * @returns -
*/ */
function getPrefixCls(scope: string) { const getPrefixCls = (scope: string) => {
return `${lessVariables.namespace}-${scope}` return `${lessVariables.namespace}-${scope}`
} }

View File

@ -2,7 +2,7 @@ import type { Form, FormExpose } from '@/components/Form'
import type { ElForm } from 'element-plus' import type { ElForm } from 'element-plus'
import { ref, unref, nextTick } from 'vue' import { ref, unref, nextTick } from 'vue'
export function useForm() { export const useForm = () => {
// From实例 // From实例
const formRef = ref<typeof Form & FormExpose>() const formRef = ref<typeof Form & FormExpose>()
@ -13,12 +13,12 @@ export function useForm() {
* @param ref Form实例 * @param ref Form实例
* @param elRef ElForm实例 * @param elRef ElForm实例
*/ */
function register(ref: typeof Form & FormExpose, elRef: ComponentRef<typeof ElForm>) { const register = (ref: typeof Form & FormExpose, elRef: ComponentRef<typeof ElForm>) => {
formRef.value = ref formRef.value = ref
elFormRef.value = elRef elFormRef.value = elRef
} }
async function getForm() { const getForm = async () => {
const form = unref(formRef) const form = unref(formRef)
if (!form) { if (!form) {
console.error('The form is not registered. Please use the register method to register') console.error('The form is not registered. Please use the register method to register')

View File

@ -11,7 +11,7 @@ type I18nGlobalTranslation = {
type I18nTranslationRestParameters = [string, any] type I18nTranslationRestParameters = [string, any]
function getKey(namespace: string | undefined, key: string) { const getKey = (namespace: string | undefined, key: string) => {
if (!namespace) { if (!namespace) {
return key return key
} }
@ -21,9 +21,11 @@ function getKey(namespace: string | undefined, key: string) {
return `${namespace}.${key}` return `${namespace}.${key}`
} }
export function useI18n(namespace?: string): { export const useI18n = (
namespace?: string
): {
t: I18nGlobalTranslation t: I18nGlobalTranslation
} { } => {
const normalFn = { const normalFn = {
t: (key: string) => { t: (key: string) => {
return getKey(namespace, key) return getKey(namespace, key)

View File

@ -2,6 +2,6 @@ import { h } from 'vue'
import type { VNode } from 'vue' import type { VNode } from 'vue'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
export function useIcon(props: IconTypes): VNode { export const useIcon = (props: IconTypes): VNode => {
return h(Icon, props) return h(Icon, props)
} }

View File

@ -2,7 +2,7 @@ import { i18n } from '@/plugins/vueI18n'
import { useLocaleStoreWithOut } from '@/store/modules/locale' import { useLocaleStoreWithOut } from '@/store/modules/locale'
import { setHtmlPageLang } from '@/plugins/vueI18n/helper' import { setHtmlPageLang } from '@/plugins/vueI18n/helper'
function setI18nLanguage(locale: LocaleType) { const setI18nLanguage = (locale: LocaleType) => {
const localeStore = useLocaleStoreWithOut() const localeStore = useLocaleStoreWithOut()
if (i18n.mode === 'legacy') { if (i18n.mode === 'legacy') {
@ -16,10 +16,10 @@ function setI18nLanguage(locale: LocaleType) {
setHtmlPageLang(locale) setHtmlPageLang(locale)
} }
export function useLocale() { export const useLocale = () => {
// Switching the language will change the locale of useI18n // Switching the language will change the locale of useI18n
// And submit to configuration modification // And submit to configuration modification
async function changeLocale(locale: LocaleType) { const changeLocale = async (locale: LocaleType) => {
const globalI18n = i18n.global const globalI18n = i18n.global
const langModule = await import(`../../locales/${locale}.ts`) const langModule = await import(`../../locales/${locale}.ts`)

View File

@ -6,11 +6,10 @@ import { useCssVar } from '@vueuse/core'
const primaryColor = useCssVar('--el-color-primary', document.documentElement) const primaryColor = useCssVar('--el-color-primary', document.documentElement)
export function useNProgress() { export const useNProgress = () => {
NProgress.configure({ showSpinner: false } as NProgressOptions) NProgress.configure({ showSpinner: false } as NProgressOptions)
initColor()
async function initColor() { const initColor = async () => {
await nextTick() await nextTick()
const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef
if (bar) { if (bar) {
@ -18,11 +17,13 @@ export function useNProgress() {
} }
} }
function start() { initColor()
const start = () => {
NProgress.start() NProgress.start()
} }
function done() { const done = () => {
NProgress.done() NProgress.done()
} }

View File

@ -5,7 +5,7 @@ import { useI18n } from '@/hooks/web/useI18n'
const appStore = useAppStoreWithOut() const appStore = useAppStoreWithOut()
export function useTitle(newTitle?: string) { export const useTitle = (newTitle?: string) => {
const { t } = useI18n() const { t } = useI18n()
const title = ref( const title = ref(
newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle

View File

@ -3,13 +3,13 @@ import { computed, defineComponent, KeepAlive } from 'vue'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { Menu } from '@/components/Menu' import { Menu } from '@/components/Menu'
import { useDesign } from '@/hooks/web/useDesign'
import { Collapse } from '@/components/Collapse' import { Collapse } from '@/components/Collapse'
import { LocaleDropdown } from '@/components/LocaleDropdown' import { LocaleDropdown } from '@/components/LocaleDropdown'
import { SizeDropdown } from '@/components/SizeDropdown' import { SizeDropdown } from '@/components/SizeDropdown'
import { UserInfo } from '@/components/UserInfo' import { UserInfo } from '@/components/UserInfo'
import { Screenfull } from '@/components/Screenfull' import { Screenfull } from '@/components/Screenfull'
// import { TagsView } from '@/components/TagsView' import { Breadcrumb } from '@/components/Breadcrumb'
import { TagsView } from '@/components/TagsView'
const tagsViewStore = useTagsViewStore() const tagsViewStore = useTagsViewStore()
@ -19,40 +19,50 @@ const getCaches = computed((): string[] => {
const appStore = useAppStore() const appStore = useAppStore()
const mobile = computed(() => appStore.getMobile)
const collapse = computed(() => appStore.getCollapse)
const classSuffix = computed(() => appStore.getLayout) const classSuffix = computed(() => appStore.getLayout)
const { getPrefixCls } = useDesign() const handleClickOutside = () => {
appStore.setCollapse(true)
const perFixCls = getPrefixCls('app') }
export default defineComponent({ export default defineComponent({
name: 'Layout', name: 'Layout',
setup() { setup() {
return () => ( return () => (
<section <section class={['v-app', `v-app__${classSuffix.value}`, 'w-[100%] h-[100%] relative']}>
class={[perFixCls, `${perFixCls}__${classSuffix.value}`, 'w-[100%] h-[100%] relative']} {mobile.value && !collapse.value ? (
> <div
class="absolute top-0 left-0 w-full h-full opacity-30 z-99 bg-[var(--el-color-black)]"
onClick={handleClickOutside}
></div>
) : undefined}
<Menu class="absolute top-0 left-0"></Menu> <Menu class="absolute top-0 left-0"></Menu>
<div <div
class={[ class={[
`${perFixCls}-right`, 'v-app-right',
'absolute top-0 h-[100%]', 'absolute top-0 h-[100%]',
appStore.getCollapse collapse.value
? 'w-[calc(100%-var(--left-menu-min-width))]' ? 'w-[calc(100%-var(--left-menu-min-width))]'
: 'w-[calc(100%-var(--left-menu-max-width))]', : 'w-[calc(100%-var(--left-menu-max-width))]',
appStore.getCollapse collapse.value
? 'left-[var(--left-menu-min-width)]' ? 'left-[var(--left-menu-min-width)]'
: 'left-[var(--left-menu-max-width)]' : 'left-[var(--left-menu-max-width)]',
'<md:(!left-0 !w-[100%])'
]} ]}
> >
<div <div
class={[ class={[
`${perFixCls}-right__tool`, 'v-app-right__tool',
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between' 'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between'
]} ]}
> >
<div class="h-full flex items-center"> <div class="h-full flex items-center">
<Collapse class="header__tigger"></Collapse> <Collapse class="header__tigger"></Collapse>
<Breadcrumb class="<md:hidden"></Breadcrumb>
</div> </div>
<div class="h-full flex items-center"> <div class="h-full flex items-center">
<Screenfull class="header__tigger"></Screenfull> <Screenfull class="header__tigger"></Screenfull>
@ -61,6 +71,9 @@ export default defineComponent({
<UserInfo class="header__tigger"></UserInfo> <UserInfo class="header__tigger"></UserInfo>
</div> </div>
</div> </div>
<div class="v-app-right__tags relative">
<TagsView></TagsView>
</div>
<router-view> <router-view>
{{ {{
default: ({ Component, route }) => ( default: ({ Component, route }) => (
@ -97,7 +110,8 @@ export default defineComponent({
&-right { &-right {
transition: left var(--transition-time-02); transition: left var(--transition-time-02);
&__tool { &__tool,
&__tags {
&::after { &::after {
position: absolute; position: absolute;
bottom: 0; bottom: 0;

View File

@ -31,7 +31,7 @@ import App from './App.vue'
import './permission' import './permission'
async function setupAll() { const setupAll = async () => {
const app = createApp(App) const app = createApp(App)
await setupI18n(app) await setupI18n(app)

View File

@ -7,7 +7,7 @@ const plugins = [ElLoading]
const components = [ElScrollbar] const components = [ElScrollbar]
export function setupElementPlus(app: App) { export const setupElementPlus = (app: App) => {
plugins.forEach((plugin) => { plugins.forEach((plugin) => {
app.use(plugin) app.use(plugin)
}) })

View File

@ -1,3 +1,3 @@
export function setHtmlPageLang(locale: LocaleType) { export const setHtmlPageLang = (locale: LocaleType) => {
document.querySelector('html')?.setAttribute('lang', locale) document.querySelector('html')?.setAttribute('lang', locale)
} }

View File

@ -6,7 +6,7 @@ import { setHtmlPageLang } from './helper'
export let i18n: ReturnType<typeof createI18n> export let i18n: ReturnType<typeof createI18n>
async function createI18nOptions(): Promise<I18nOptions> { const createI18nOptions = async (): Promise<I18nOptions> => {
const localeStore = useLocaleStoreWithOut() const localeStore = useLocaleStoreWithOut()
const locale = localeStore.getLocale const locale = localeStore.getLocale
const localeMap = localeStore.getLocaleMap const localeMap = localeStore.getLocaleMap
@ -35,7 +35,7 @@ async function createI18nOptions(): Promise<I18nOptions> {
} }
} }
export async function setupI18n(app: App) { export const setupI18n = async (app: App) => {
const options = await createI18nOptions() const options = await createI18nOptions()
i18n = createI18n(options) as I18n i18n = createI18n(options) as I18n
app.use(i18n) app.use(i18n)

View File

@ -121,7 +121,7 @@ const router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }) scrollBehavior: () => ({ left: 0, top: 0 })
}) })
export function resetRouter(): void { export const resetRouter = (): void => {
const resetWhiteNameList = ['RedirectRoot', 'Redirect', 'Login', 'Root', 'Dashboard', 'Page404'] const resetWhiteNameList = ['RedirectRoot', 'Redirect', 'Login', 'Root', 'Dashboard', 'Page404']
router.getRoutes().forEach((route) => { router.getRoutes().forEach((route) => {
const { name } = route const { name } = route
@ -131,7 +131,7 @@ export function resetRouter(): void {
}) })
} }
export function setupRouter(app: App<Element>) { export const setupRouter = (app: App<Element>) => {
app.use(router) app.use(router)
} }

View File

@ -3,7 +3,7 @@ import { createPinia } from 'pinia'
const store = createPinia() const store = createPinia()
export function setupStore(app: App<Element>) { export const setupStore = (app: App<Element>) => {
app.use(store) app.use(store)
} }

View File

@ -66,6 +66,9 @@ export const useAppStore = defineStore({
}, },
getSizeMap(): ElememtPlusSzie[] { getSizeMap(): ElememtPlusSzie[] {
return this.sizeMap return this.sizeMap
},
getMobile(): boolean {
return this.mobile
} }
}, },
actions: { actions: {
@ -128,10 +131,13 @@ export const useAppStore = defineStore({
setSize(size: ElememtPlusSzie) { setSize(size: ElememtPlusSzie) {
this.size = size this.size = size
wsCache.set('size', this.size) wsCache.set('size', this.size)
},
setMobile(mobile: boolean) {
this.mobile = mobile
} }
} }
}) })
export function useAppStoreWithOut() { export const useAppStoreWithOut = () => {
return useAppStore(store) return useAppStore(store)
} }

View File

@ -27,6 +27,6 @@ export const useLocaleStore = defineStore({
} }
}) })
export function useLocaleStoreWithOut() { export const useLocaleStoreWithOut = () => {
return useLocaleStore(store) return useLocaleStore(store)
} }

View File

@ -91,6 +91,6 @@ export const usePermissionStore = defineStore({
} }
}) })
export function usePermissionStoreWithOut() { export const usePermissionStoreWithOut = () => {
return usePermissionStore(store) return usePermissionStore(store)
} }

View File

@ -170,6 +170,6 @@ export const useTagsViewStore = defineStore({
} }
}) })
export function useTagsViewStoreWithOut() { export const useTagsViewStoreWithOut = () => {
return useTagsViewStore(store) return useTagsViewStore(store)
} }

View File

@ -5,7 +5,7 @@
* @param String color * @param String color
* @return Boolean * @return Boolean
*/ */
export function isHexColor(color: string) { export const isHexColor = (color: string) => {
const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/ const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/
return reg.test(color) return reg.test(color)
} }
@ -19,7 +19,7 @@ export function isHexColor(color: string) {
* @param g * @param g
* @param b * @param b
*/ */
export function rgbToHex(r: number, g: number, b: number) { export const rgbToHex = (r: number, g: number, b: number) => {
// tslint:disable-next-line:no-bitwise // tslint:disable-next-line:no-bitwise
const hex = ((r << 16) | (g << 8) | b).toString(16) const hex = ((r << 16) | (g << 8) | b).toString(16)
return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex
@ -30,7 +30,7 @@ export function rgbToHex(r: number, g: number, b: number) {
* @param {string} hex The color to transform * @param {string} hex The color to transform
* @returns The RGB representation of the passed color * @returns The RGB representation of the passed color
*/ */
export function hexToRGB(hex: string) { export const hexToRGB = (hex: string) => {
let sHex = hex.toLowerCase() let sHex = hex.toLowerCase()
if (isHexColor(hex)) { if (isHexColor(hex)) {
if (sHex.length === 4) { if (sHex.length === 4) {
@ -49,7 +49,7 @@ export function hexToRGB(hex: string) {
return sHex return sHex
} }
export function colorIsDark(color: string) { export const colorIsDark = (color: string) => {
if (!isHexColor(color)) return if (!isHexColor(color)) return
const [r, g, b] = hexToRGB(color) const [r, g, b] = hexToRGB(color)
.replace(/(?:\(|\)|rgb|RGB)*/g, '') .replace(/(?:\(|\)|rgb|RGB)*/g, '')
@ -64,7 +64,7 @@ export function colorIsDark(color: string) {
* @param {number} amount The amount to change the color by * @param {number} amount The amount to change the color by
* @returns {string} The HEX representation of the processed color * @returns {string} The HEX representation of the processed color
*/ */
export function darken(color: string, amount: number) { export const darken = (color: string, amount: number) => {
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color
amount = Math.trunc((255 * amount) / 100) amount = Math.trunc((255 * amount) / 100)
return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight( return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight(
@ -79,7 +79,7 @@ export function darken(color: string, amount: number) {
* @param {number} amount The amount to change the color by * @param {number} amount The amount to change the color by
* @returns {string} The processed color represented as HEX * @returns {string} The processed color represented as HEX
*/ */
export function lighten(color: string, amount: number) { export const lighten = (color: string, amount: number) => {
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color
amount = Math.trunc((255 * amount) / 100) amount = Math.trunc((255 * amount) / 100)
return `#${addLight(color.substring(0, 2), amount)}${addLight( return `#${addLight(color.substring(0, 2), amount)}${addLight(
@ -95,7 +95,7 @@ export function lighten(color: string, amount: number) {
* @param {number} amount The amount to change the color by * @param {number} amount The amount to change the color by
* @returns {string} The processed part of the color * @returns {string} The processed part of the color
*/ */
function addLight(color: string, amount: number) { const addLight = (color: string, amount: number) => {
const cc = parseInt(color, 16) + amount const cc = parseInt(color, 16) + amount
const c = cc > 255 ? 255 : cc const c = cc > 255 ? 255 : cc
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}` return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
@ -107,7 +107,7 @@ function addLight(color: string, amount: number) {
* @param {number} g green * @param {number} g green
* @param {number} b blue * @param {number} b blue
*/ */
function luminanace(r: number, g: number, b: number) { const luminanace = (r: number, g: number, b: number) => {
const a = [r, g, b].map((v) => { const a = [r, g, b].map((v) => {
v /= 255 v /= 255
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4) return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)
@ -120,7 +120,7 @@ function luminanace(r: number, g: number, b: number) {
* @param {string} rgb1 rgb color 1 * @param {string} rgb1 rgb color 1
* @param {string} rgb2 rgb color 2 * @param {string} rgb2 rgb color 2
*/ */
function contrast(rgb1: string[], rgb2: number[]) { const contrast = (rgb1: string[], rgb2: number[]) => {
return ( return (
(luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / (luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) /
(luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05) (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05)
@ -131,7 +131,7 @@ function contrast(rgb1: string[], rgb2: number[]) {
* Determines what the best text color is (black or white) based con the contrast with the background * Determines what the best text color is (black or white) based con the contrast with the background
* @param hexColor - Last selected color by the user * @param hexColor - Last selected color by the user
*/ */
export function calculateBestTextColor(hexColor: string) { export const calculateBestTextColor = (hexColor: string) => {
const rgbColor = hexToRGB(hexColor.substring(1)) const rgbColor = hexToRGB(hexColor.substring(1))
const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0]) const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0])
@ -144,7 +144,7 @@ export function calculateBestTextColor(hexColor: string) {
* @param {number} amount The amount to change the color by * @param {number} amount The amount to change the color by
* @returns {string} The processed part of the color * @returns {string} The processed part of the color
*/ */
function subtractLight(color: string, amount: number) { const subtractLight = (color: string, amount: number) => {
const cc = parseInt(color, 16) - amount const cc = parseInt(color, 16) - amount
const c = cc < 0 ? 0 : cc const c = cc < 0 ? 0 : cc
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}` return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`

View File

@ -21,7 +21,7 @@ export const withInstall = <T>(component: T, alias?: string) => {
* @param str 线 * @param str 线
* @returns 线 * @returns 线
*/ */
export function humpToUnderline(str: string): string { export const humpToUnderline = (str: string): string => {
return str.replace(/([A-Z])/g, '-$1').toLowerCase() return str.replace(/([A-Z])/g, '-$1').toLowerCase()
} }
@ -29,12 +29,12 @@ export function humpToUnderline(str: string): string {
* @param str 线 * @param str 线
* @returns * @returns
*/ */
export function underlineToHump(str: string): string { export const underlineToHump = (str: string): string => {
return str.replace(/\-(\w)/g, function (_, letter: string) { return str.replace(/\-(\w)/g, (_, letter: string) => {
return letter.toUpperCase() return letter.toUpperCase()
}) })
} }
export function 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)
} }

View File

@ -2,23 +2,23 @@
const toString = Object.prototype.toString const toString = Object.prototype.toString
export function is(val: unknown, type: string) { export const is = (val: unknown, type: string) => {
return toString.call(val) === `[object ${type}]` return toString.call(val) === `[object ${type}]`
} }
export function isDef<T = unknown>(val?: T): val is T { export const isDef = <T = unknown>(val?: T): val is T => {
return typeof val !== 'undefined' return typeof val !== 'undefined'
} }
export function isUnDef<T = unknown>(val?: T): val is T { export const isUnDef = <T = unknown>(val?: T): val is T => {
return !isDef(val) return !isDef(val)
} }
export function isObject(val: any): val is Record<any, any> { export const isObject = (val: any): val is Record<any, any> => {
return val !== null && is(val, 'Object') return val !== null && is(val, 'Object')
} }
export function isEmpty<T = unknown>(val: T): val is T { export const isEmpty = <T = unknown>(val: T): val is T => {
if (isArray(val) || isString(val)) { if (isArray(val) || isString(val)) {
return val.length === 0 return val.length === 0
} }
@ -34,59 +34,59 @@ export function isEmpty<T = unknown>(val: T): val is T {
return false return false
} }
export function isDate(val: unknown): val is Date { export const isDate = (val: unknown): val is Date => {
return is(val, 'Date') return is(val, 'Date')
} }
export function isNull(val: unknown): val is null { export const isNull = (val: unknown): val is null => {
return val === null return val === null
} }
export function isNullAndUnDef(val: unknown): val is null | undefined { export const isNullAndUnDef = (val: unknown): val is null | undefined => {
return isUnDef(val) && isNull(val) return isUnDef(val) && isNull(val)
} }
export function isNullOrUnDef(val: unknown): val is null | undefined { export const isNullOrUnDef = (val: unknown): val is null | undefined => {
return isUnDef(val) || isNull(val) return isUnDef(val) || isNull(val)
} }
export function isNumber(val: unknown): val is number { export const isNumber = (val: unknown): val is number => {
return is(val, 'Number') return is(val, 'Number')
} }
export function isPromise<T = any>(val: unknown): val is Promise<T> { export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch) return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch)
} }
export function isString(val: unknown): val is string { export const isString = (val: unknown): val is string => {
return is(val, 'String') return is(val, 'String')
} }
export function isFunction(val: unknown): val is Function { export const isFunction = (val: unknown): val is Function => {
return typeof val === 'function' return typeof val === 'function'
} }
export function isBoolean(val: unknown): val is boolean { export const isBoolean = (val: unknown): val is boolean => {
return is(val, 'Boolean') return is(val, 'Boolean')
} }
export function isRegExp(val: unknown): val is RegExp { export const isRegExp = (val: unknown): val is RegExp => {
return is(val, 'RegExp') return is(val, 'RegExp')
} }
export function isArray(val: any): val is Array<any> { export const isArray = (val: any): val is Array<any> => {
return val && Array.isArray(val) return val && Array.isArray(val)
} }
export function isWindow(val: any): val is Window { export const isWindow = (val: any): val is Window => {
return typeof window !== 'undefined' && is(val, 'Window') return typeof window !== 'undefined' && is(val, 'Window')
} }
export function isElement(val: unknown): val is Element { export const isElement = (val: unknown): val is Element => {
return isObject(val) && !!val.tagName return isObject(val) && !!val.tagName
} }
export function isMap(val: unknown): val is Map<any, any> { export const isMap = (val: unknown): val is Map<any, any> => {
return is(val, 'Map') return is(val, 'Map')
} }
@ -94,12 +94,12 @@ export const isServer = typeof window === 'undefined'
export const isClient = !isServer export const isClient = !isServer
export function isUrl(path: string): boolean { export const isUrl = (path: string): boolean => {
const reg = const reg =
/(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/ /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/
return reg.test(path) return reg.test(path)
} }
export function isDark(): boolean { export const isDark = (): boolean => {
return window.matchMedia('(prefers-color-scheme: dark)').matches return window.matchMedia('(prefers-color-scheme: dark)').matches
} }

View File

@ -1,5 +1,5 @@
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import type { Router, RouteLocationNormalized, RouteRecordNormalized } from 'vue-router' import type { Router, RouteLocationNormalized, RouteRecordNormalized, RouteMeta } from 'vue-router'
import { isUrl } from '@/utils/is' import { isUrl } from '@/utils/is'
import { useCache } from '@/hooks/web/useCache' import { useCache } from '@/hooks/web/useCache'
import { useAppStoreWithOut } from '@/store/modules/app' import { useAppStoreWithOut } from '@/store/modules/app'
@ -23,7 +23,7 @@ export const getParentLayout = () => {
}) })
} }
export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormalized { export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormalized => {
if (!route) return route if (!route) return route
const { matched, ...opt } = route const { matched, ...opt } = route
return { return {
@ -39,15 +39,16 @@ export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormal
} }
// 前端控制路由生成 // 前端控制路由生成
export function generateRoutesFn1( export const generateRoutesFn1 = (
routes: AppRouteRecordRaw[], routes: AppRouteRecordRaw[],
basePath = '/' basePath = '/'
): AppRouteRecordRaw[] { ): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = [] const res: AppRouteRecordRaw[] = []
for (const route of routes) { for (const route of routes) {
const meta = route.meta as RouteMeta
// skip some route // skip some route
if (route.meta && route.meta.hidden && !route.meta.showMainRoute) { if (meta.hidden && !meta.showMainRoute) {
continue continue
} }
@ -55,7 +56,7 @@ export function generateRoutesFn1(
let onlyOneChild: Nullable<string> = null let onlyOneChild: Nullable<string> = null
if (route.children && route.children.length === 1 && !route.meta.alwaysShow) { if (route.children && route.children.length === 1 && !meta.alwaysShow) {
onlyOneChild = ( onlyOneChild = (
isUrl(route.children[0].path) isUrl(route.children[0].path)
? route.children[0].path ? route.children[0].path
@ -72,7 +73,7 @@ export function generateRoutesFn1(
data = Object.assign({}, route) data = Object.assign({}, route)
} else { } else {
const routePath = pathResolve(basePath, onlyOneChild || route.path) const routePath = pathResolve(basePath, onlyOneChild || route.path)
if (routePath === item.path || (route.meta && route.meta.followRoute === item.path)) { if (routePath === item.path || meta.followRoute === item.path) {
data = Object.assign({}, route) data = Object.assign({}, route)
} }
} }
@ -90,7 +91,7 @@ export function generateRoutesFn1(
} }
// 后端控制路由生成 // 后端控制路由生成
export function generateRoutesFn2(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] { export const generateRoutesFn2 = (routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = [] const res: AppRouteRecordRaw[] = []
for (const route of routes) { for (const route of routes) {
@ -121,13 +122,13 @@ export function generateRoutesFn2(routes: AppRouteRecordRaw[]): AppRouteRecordRa
return res return res
} }
export function pathResolve(parentPath: string, path: string) { export const pathResolve = (parentPath: string, path: string) => {
const childPath = path.startsWith('/') || !path ? path : `/${path}` const childPath = path.startsWith('/') || !path ? path : `/${path}`
return `${parentPath}${childPath}` return `${parentPath}${childPath}`
} }
// 路由降级 // 路由降级
export function flatMultiLevelRoutes(routes: AppRouteRecordRaw[]) { export const flatMultiLevelRoutes = (routes: AppRouteRecordRaw[]) => {
const modules: AppRouteRecordRaw[] = cloneDeep(routes) const modules: AppRouteRecordRaw[] = cloneDeep(routes)
for (let index = 0; index < modules.length; index++) { for (let index = 0; index < modules.length; index++) {
const route = modules[index] const route = modules[index]
@ -140,7 +141,7 @@ export function flatMultiLevelRoutes(routes: AppRouteRecordRaw[]) {
} }
// 层级是否大于2 // 层级是否大于2
function isMultipleRoute(route: AppRouteRecordRaw) { const isMultipleRoute = (route: AppRouteRecordRaw) => {
if (!route || !Reflect.has(route, 'children') || !route.children?.length) { if (!route || !Reflect.has(route, 'children') || !route.children?.length) {
return false return false
} }
@ -159,7 +160,7 @@ function isMultipleRoute(route: AppRouteRecordRaw) {
} }
// 生成二级路由 // 生成二级路由
function promoteRouteLevel(route: AppRouteRecordRaw) { const promoteRouteLevel = (route: AppRouteRecordRaw) => {
let router: Router | null = createRouter({ let router: Router | null = createRouter({
routes: [route as unknown as RouteRecordNormalized], routes: [route as unknown as RouteRecordNormalized],
history: createWebHashHistory() history: createWebHashHistory()
@ -173,11 +174,11 @@ function promoteRouteLevel(route: AppRouteRecordRaw) {
} }
// 添加所有子菜单 // 添加所有子菜单
function addToChildren( const addToChildren = (
routes: RouteRecordNormalized[], routes: RouteRecordNormalized[],
children: AppRouteRecordRaw[], children: AppRouteRecordRaw[],
routeModule: AppRouteRecordRaw routeModule: AppRouteRecordRaw
) { ) => {
for (let index = 0; index < children.length; index++) { for (let index = 0; index < children.length; index++) {
const child = children[index] const child = children[index]
const route = routes.find((item) => item.name === child.name) const route = routes.find((item) => item.name === child.name)

207
src/utils/tree.ts Normal file
View File

@ -0,0 +1,207 @@
interface TreeHelperConfig {
id: string
children: string
pid: string
}
const DEFAULT_CONFIG: TreeHelperConfig = {
id: 'id',
children: 'children',
pid: 'pid'
}
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
// tree from list
export const listToTree = <T = any>(list: any[], config: Partial<TreeHelperConfig> = {}): T[] => {
const conf = getConfig(config) as TreeHelperConfig
const nodeMap = new Map()
const result: T[] = []
const { id, children, pid } = conf
for (const node of list) {
node[children] = node[children] || []
nodeMap.set(node[id], node)
}
for (const node of list) {
const parent = nodeMap.get(node[pid])
;(parent ? parent.children : result).push(node)
}
return result
}
export const treeToList = <T = any>(tree: any, config: Partial<TreeHelperConfig> = {}): T => {
config = getConfig(config)
const { children } = config
const result: any = [...tree]
for (let i = 0; i < result.length; i++) {
if (!result[i][children!]) continue
result.splice(i + 1, 0, ...result[i][children!])
}
return result
}
export const findNode = <T = any>(
tree: any,
func: Fn,
config: Partial<TreeHelperConfig> = {}
): T | null => {
config = getConfig(config)
const { children } = config
const list = [...tree]
for (const node of list) {
if (func(node)) return node
node[children!] && list.push(...node[children!])
}
return null
}
export const findNodeAll = <T = any>(
tree: any,
func: Fn,
config: Partial<TreeHelperConfig> = {}
): T[] => {
config = getConfig(config)
const { children } = config
const list = [...tree]
const result: T[] = []
for (const node of list) {
func(node) && result.push(node)
node[children!] && list.push(...node[children!])
}
return result
}
export const findPath = <T = any>(
tree: any,
func: Fn,
config: Partial<TreeHelperConfig> = {}
): T | T[] | null => {
config = getConfig(config)
const path: T[] = []
const list = [...tree]
const visitedSet = new Set()
const { children } = config
while (list.length) {
const node = list[0]
if (visitedSet.has(node)) {
path.pop()
list.shift()
} else {
visitedSet.add(node)
node[children!] && list.unshift(...node[children!])
path.push(node)
if (func(node)) {
return path
}
}
}
return null
}
export const findPathAll = (tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}) => {
config = getConfig(config)
const path: any[] = []
const list = [...tree]
const result: any[] = []
const visitedSet = new Set(),
{ children } = config
while (list.length) {
const node = list[0]
if (visitedSet.has(node)) {
path.pop()
list.shift()
} else {
visitedSet.add(node)
node[children!] && list.unshift(...node[children!])
path.push(node)
func(node) && result.push([...path])
}
}
return result
}
export const filter = <T = any>(
tree: T[],
func: (n: T) => boolean,
config: Partial<TreeHelperConfig> = {}
): T[] => {
config = getConfig(config)
const children = config.children as string
function listFilter(list: T[]) {
return list
.map((node: any) => ({ ...node }))
.filter((node) => {
node[children] = node[children] && listFilter(node[children])
return func(node) || (node[children] && node[children].length)
})
}
return listFilter(tree)
}
export const forEach = <T = any>(
tree: T[],
func: (n: T) => any,
config: Partial<TreeHelperConfig> = {}
): void => {
config = getConfig(config)
const list: any[] = [...tree]
const { children } = config
for (let i = 0; i < list.length; i++) {
//func 返回true就终止遍历避免大量节点场景下无意义循环引起浏览器卡顿
if (func(list[i])) {
return
}
children && list[i][children] && list.splice(i + 1, 0, ...list[i][children])
}
}
/**
* @description: Extract tree specified structure
*/
export const treeMap = <T = any>(
treeData: T[],
opt: { children?: string; conversion: Fn }
): T[] => {
return treeData.map((item) => treeMapEach(item, opt))
}
/**
* @description: Extract tree specified structure
*/
export const treeMapEach = (
data: any,
{ children = 'children', conversion }: { children?: string; conversion: Fn }
) => {
const haveChildren = Array.isArray(data[children]) && data[children].length > 0
const conversionData = conversion(data) || {}
if (haveChildren) {
return {
...conversionData,
[children]: data[children].map((i: number) =>
treeMapEach(i, {
children,
conversion
})
)
}
} else {
return {
...conversionData
}
}
}
/**
*
* @param treeDatas
* @param callBack
* @param parentNode
*/
export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => {
treeDatas.forEach((element) => {
const newNode = callBack(element, parentNode) || element
if (element.children) {
eachTree(element.children, callBack, newNode)
}
})
}

View File

@ -1,7 +1,7 @@
import { Slots } from 'vue' import { Slots } from 'vue'
import { isFunction } from '@/utils/is' import { isFunction } from '@/utils/is'
export function getSlot(slots: Slots, slot = 'default', data?: Recordable) { export const getSlot = (slots: Slots, slot = 'default', data?: Recordable) => {
// Reflect.has 判断一个对象是否存在某个属性 // Reflect.has 判断一个对象是否存在某个属性
if (!slots || !Reflect.has(slots, slot)) { if (!slots || !Reflect.has(slots, slot)) {
return null return null

View File

@ -111,7 +111,7 @@ watch(
) )
// //
async function signIn() { const signIn = async () => {
const formRef = unref(elFormRef) const formRef = unref(elFormRef)
const validate = await formRef?.validate()?.catch(() => {}) const validate = await formRef?.validate()?.catch(() => {})
if (validate) { if (validate) {