feat(utils): Add color utils
This commit is contained in:
parent
b218ccc9cc
commit
71dfba21c5
|
@ -13,7 +13,8 @@
|
||||||
"name": "pnpm",
|
"name": "pnpm",
|
||||||
"message": "butterfly-admin@3.0.0 clean: `npx rimraf docs/node_modules && npx rimraf node_modules`\nExit status 1",
|
"message": "butterfly-admin@3.0.0 clean: `npx rimraf docs/node_modules && npx rimraf node_modules`\nExit status 1",
|
||||||
"code": "ELIFECYCLE",
|
"code": "ELIFECYCLE",
|
||||||
"stack": "pnpm: butterfly-admin@3.0.0 clean: `npx rimraf docs/node_modules && npx rimraf node_modules`\nExit status 1\n at EventEmitter.<anonymous> (C:\\Users\\Saber\\AppData\\Roaming\\npm\\pnpm-global\\5\\node_modules\\.pnpm\\registry.npmmirror.com+pnpm@6.24.4\\node_modules\\pnpm\\dist\\pnpm.cjs:103873:20)\n at EventEmitter.emit (node:events:365:28)\n at ChildProcess.<anonymous> (C:\\Users\\Saber\\AppData\\Roaming\\npm\\pnpm-global\\5\\node_modules\\.pnpm\\registry.npmmirror.com+pnpm@6.24.4\\node_modules\\pnpm\\dist\\pnpm.cjs:91802:18)\n at ChildProcess.emit (node:events:365:28)\n at maybeClose (node:internal/child_process:1067:16)\n at Process.ChildProcess._handle.onexit (node:internal/child_process:301:5)"
|
"stack": "pnpm: butterfly-admin@3.0.0 clean: `npx rimraf docs/node_modules && npx rimraf node_modules`\nExit status 1\n at EventEmitter.<anonymous> (C:\\Users\\admin\\AppData\\Roaming\\npm\\pnpm-global\\5\\node_modules\\.pnpm\\registry.npmmirror.com+pnpm@6.25.1\\node_modules\\pnpm\\dist\\pnpm.cjs:104843:20)\n at EventEmitter.emit (events.js:315:20)\n at ChildProcess.<anonymous> (C:\\Users\\admin\\AppData\\Roaming\\npm\\pnpm-global\\5\\node_modules\\.pnpm\\registry.npmmirror.com+pnpm@6.25.1\\node_modules\\pnpm\\dist\\pnpm.cjs:91921:18)\n at ChildProcess.emit (events.js:315:20)\n at maybeClose (internal/child_process.js:1048:16)\n at Process.ChildProcess._handle.onexit (internal/child_process.js:288:5)"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"2 warn pnpm:global": " Local package.json exists, but node_modules missing, did you mean to install?"
|
||||||
}
|
}
|
14
package.json
14
package.json
|
@ -30,12 +30,12 @@
|
||||||
"@zxcvbn-ts/core": "^1.2.0",
|
"@zxcvbn-ts/core": "^1.2.0",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"element-plus": "1.3.0-beta.2",
|
"element-plus": "1.3.0-beta.5",
|
||||||
"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",
|
||||||
"pinia": "^2.0.9",
|
"pinia": "^2.0.9",
|
||||||
"qs": "^6.10.2",
|
"qs": "^6.10.3",
|
||||||
"vue": "3.2.26",
|
"vue": "3.2.26",
|
||||||
"vue-i18n": "9.1.9",
|
"vue-i18n": "9.1.9",
|
||||||
"vue-router": "^4.0.12",
|
"vue-router": "^4.0.12",
|
||||||
|
@ -43,17 +43,17 @@
|
||||||
"web-storage-cache": "^1.1.1"
|
"web-storage-cache": "^1.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^16.0.1",
|
"@commitlint/cli": "^16.0.2",
|
||||||
"@commitlint/config-conventional": "^16.0.0",
|
"@commitlint/config-conventional": "^16.0.0",
|
||||||
"@iconify/json": "^1.1.453",
|
"@iconify/json": "^1.1.454",
|
||||||
"@intlify/vite-plugin-vue-i18n": "^3.2.1",
|
"@intlify/vite-plugin-vue-i18n": "^3.2.1",
|
||||||
"@purge-icons/generated": "^0.7.0",
|
"@purge-icons/generated": "^0.7.0",
|
||||||
"@types/lodash-es": "^4.17.5",
|
"@types/lodash-es": "^4.17.5",
|
||||||
"@types/node": "^17.0.8",
|
"@types/node": "^17.0.8",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/qs": "^6.9.7",
|
"@types/qs": "^6.9.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.9.0",
|
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||||
"@typescript-eslint/parser": "^5.9.0",
|
"@typescript-eslint/parser": "^5.9.1",
|
||||||
"@vitejs/plugin-vue": "^2.0.1",
|
"@vitejs/plugin-vue": "^2.0.1",
|
||||||
"@vitejs/plugin-vue-jsx": "^1.3.3",
|
"@vitejs/plugin-vue-jsx": "^1.3.3",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
"vite-plugin-purge-icons": "^0.7.0",
|
"vite-plugin-purge-icons": "^0.7.0",
|
||||||
"vite-plugin-style-import": "^1.4.1",
|
"vite-plugin-style-import": "^1.4.1",
|
||||||
"vite-plugin-svg-icons": "^1.1.0",
|
"vite-plugin-svg-icons": "^1.1.0",
|
||||||
"vite-plugin-windicss": "^1.6.1",
|
"vite-plugin-windicss": "^1.6.2",
|
||||||
"vue-tsc": "^0.30.2",
|
"vue-tsc": "^0.30.2",
|
||||||
"windicss": "^3.4.2",
|
"windicss": "^3.4.2",
|
||||||
"windicss-analysis": "^0.3.5"
|
"windicss-analysis": "^0.3.5"
|
||||||
|
|
1088
pnpm-lock.yaml
1088
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,6 @@
|
||||||
import { computed, unref } from 'vue'
|
import { computed, unref } from 'vue'
|
||||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
|
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
|
||||||
import { useLocaleStore } from '@/store/modules/locale'
|
import { useLocaleStore } from '@/store/modules/locale'
|
||||||
import { useCssVar } from '@vueuse/core'
|
|
||||||
import { useLocale } from '@/hooks/web/useLocale'
|
import { useLocale } from '@/hooks/web/useLocale'
|
||||||
|
|
||||||
const localeStore = useLocaleStore()
|
const localeStore = useLocaleStore()
|
||||||
|
@ -11,8 +10,6 @@ const langMap = computed(() => localeStore.getLocaleMap)
|
||||||
|
|
||||||
const currentLang = computed(() => localeStore.getLocale)
|
const currentLang = computed(() => localeStore.getLocale)
|
||||||
|
|
||||||
const textColor = useCssVar('--el-text-color-primary', document.documentElement)
|
|
||||||
|
|
||||||
function setLang(lang: LocaleType) {
|
function setLang(lang: LocaleType) {
|
||||||
if (lang === unref(currentLang).lang) return
|
if (lang === unref(currentLang).lang) return
|
||||||
// 需要重新加载页面让整个语言多初始化
|
// 需要重新加载页面让整个语言多初始化
|
||||||
|
@ -29,7 +26,7 @@ function setLang(lang: LocaleType) {
|
||||||
<ElDropdown trigger="click" @command="setLang">
|
<ElDropdown trigger="click" @command="setLang">
|
||||||
<Icon
|
<Icon
|
||||||
icon="ion:language-sharp"
|
icon="ion:language-sharp"
|
||||||
:color="textColor"
|
color="var(--el-text-color-primary)"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
:class="$attrs.class"
|
:class="$attrs.class"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Menu from './src/Menu.vue'
|
||||||
|
|
||||||
|
export { Menu }
|
|
@ -0,0 +1,143 @@
|
||||||
|
<script lang="tsx">
|
||||||
|
import { computed, defineComponent } from 'vue'
|
||||||
|
import { ElMenu, ElScrollbar } from 'element-plus'
|
||||||
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
|
import { useAppStore } from '@/store/modules/app'
|
||||||
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
|
import type { LayoutType } from '@/config/app'
|
||||||
|
import { useRenderMenuItem } from './components/useRenderMenuItem'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { isUrl } from '@/utils/is'
|
||||||
|
import { lighten } from '@/utils/color'
|
||||||
|
console.log(lighten('#001529', 6))
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Menu',
|
||||||
|
setup() {
|
||||||
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
const { push, currentRoute } = useRouter()
|
||||||
|
|
||||||
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
|
const preFixCls = getPrefixCls('menu')
|
||||||
|
|
||||||
|
const menuMode = computed(() => {
|
||||||
|
// 水平模式
|
||||||
|
const vertical: LayoutType[] = ['classic']
|
||||||
|
|
||||||
|
if (vertical.includes(appStore.getLayout)) {
|
||||||
|
return 'vertical'
|
||||||
|
} else {
|
||||||
|
return 'horizontal'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const routers = computed(() => permissionStore.getRouters)
|
||||||
|
|
||||||
|
const collapse = computed(() => appStore.getCollapse)
|
||||||
|
|
||||||
|
const activeMenu = computed(() => {
|
||||||
|
const { meta, path } = currentRoute.value
|
||||||
|
// if set path, the sidebar will highlight the path you set
|
||||||
|
if (meta.activeMenu) {
|
||||||
|
return meta.activeMenu as string
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
})
|
||||||
|
|
||||||
|
function menuSelect(index: string) {
|
||||||
|
if (isUrl(index)) {
|
||||||
|
window.open(index)
|
||||||
|
} else {
|
||||||
|
push(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
preFixCls,
|
||||||
|
'h-[100%] overflow-hidden',
|
||||||
|
appStore.getCollapse
|
||||||
|
? 'w-[var(--left-menu-min-width)]'
|
||||||
|
: 'w-[var(--left-menu-max-width)]',
|
||||||
|
'bg-[var(--left-menu-bg-color)]'
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<ElScrollbar>
|
||||||
|
<ElMenu
|
||||||
|
defaultActive={activeMenu.value}
|
||||||
|
mode={menuMode.value}
|
||||||
|
collapse={collapse.value}
|
||||||
|
backgroundColor="var(--left-menu-bg-color)"
|
||||||
|
textColor="var(--left-menu-text-color)"
|
||||||
|
activeTextColor="var(--left-menu-text-active-color)"
|
||||||
|
onSelect={menuSelect}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
default: () => {
|
||||||
|
const { renderMenuItem } = useRenderMenuItem(routers.value)
|
||||||
|
return renderMenuItem()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</ElMenu>
|
||||||
|
</ElScrollbar>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@prefix-cls: ~'@{namespace}-menu';
|
||||||
|
|
||||||
|
@menuBgColor: var(--left-menu-bg-color);
|
||||||
|
|
||||||
|
.@{prefix-cls} {
|
||||||
|
:deep(.el-menu) {
|
||||||
|
border-right: none;
|
||||||
|
|
||||||
|
// 设置选中时子标题的颜色
|
||||||
|
.is-active {
|
||||||
|
& > .el-sub-menu__title {
|
||||||
|
color: var(--left-menu-text-active-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置子菜单悬停的高亮和背景色
|
||||||
|
.el-sub-menu__title,
|
||||||
|
.el-menu-item {
|
||||||
|
&:hover {
|
||||||
|
color: var(--left-menu-text-active-color) !important;
|
||||||
|
background-color: var(--left-menu-bg-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置选中时的高亮背景和高亮颜色
|
||||||
|
.el-sub-menu.is-active,
|
||||||
|
.el-menu-item.is-active {
|
||||||
|
color: var(--left-menu-text-active-color) !important;
|
||||||
|
background-color: var(--left-menu-bg-active-color) !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--left-menu-bg-active-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置子菜单的背景颜色
|
||||||
|
.el-menu {
|
||||||
|
.el-sub-menu__title,
|
||||||
|
.el-menu-item:not(.is-active) {
|
||||||
|
background-color: var(--left-menu-bg-light-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-menu--collapse) {
|
||||||
|
width: var(--left-menu-min-width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { ElSubMenu, ElMenuItem } from 'element-plus'
|
||||||
|
import type { RouteMeta } from 'vue-router'
|
||||||
|
import { getAllParentPath, hasOneShowingChild } from '../helper'
|
||||||
|
import { isUrl } from '@/utils/is'
|
||||||
|
import { useRenderMenuTitle } from './useRenderMenuTitle'
|
||||||
|
|
||||||
|
export function useRenderMenuItem(allRouters: AppRouteRecordRaw[] = []) {
|
||||||
|
function renderMenuItem(routers?: AppRouteRecordRaw[]) {
|
||||||
|
return (routers || allRouters).map((v) => {
|
||||||
|
const meta = (v.meta ?? {}) as RouteMeta
|
||||||
|
if (!meta.hidden) {
|
||||||
|
const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
|
||||||
|
|
||||||
|
const fullPath = isUrl(v.path)
|
||||||
|
? v.path
|
||||||
|
: getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
|
||||||
|
|
||||||
|
const { renderMenuTitle } = useRenderMenuTitle()
|
||||||
|
|
||||||
|
if (
|
||||||
|
oneShowingChild &&
|
||||||
|
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
|
||||||
|
!meta?.alwaysShow
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<ElMenuItem index={fullPath}>
|
||||||
|
{{
|
||||||
|
default: () => renderMenuTitle(meta)
|
||||||
|
}}
|
||||||
|
</ElMenuItem>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<ElSubMenu index={fullPath}>
|
||||||
|
{{
|
||||||
|
title: () => renderMenuTitle(meta),
|
||||||
|
default: () => renderMenuItem(v.children)
|
||||||
|
}}
|
||||||
|
</ElSubMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
renderMenuItem
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import type { RouteMeta } from 'vue-router'
|
||||||
|
import { Icon } from '@/components/Icon'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
|
||||||
|
export function useRenderMenuTitle() {
|
||||||
|
function renderMenuTitle(meta: RouteMeta) {
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { title = 'Please set title', icon } = meta
|
||||||
|
|
||||||
|
return icon ? (
|
||||||
|
<>
|
||||||
|
<Icon icon={meta.icon}></Icon>
|
||||||
|
{t(title as string)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
t(title as string)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
renderMenuTitle
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
import type { RouteMeta } from 'vue-router'
|
||||||
|
import { ref, unref } from 'vue'
|
||||||
|
|
||||||
|
interface TreeConfig {
|
||||||
|
id: string
|
||||||
|
children: string
|
||||||
|
pid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnlyOneChildType = AppRouteRecordRaw & { noShowingChildren?: boolean }
|
||||||
|
|
||||||
|
interface HasOneShowingChild {
|
||||||
|
oneShowingChild?: boolean
|
||||||
|
onlyOneChild?: OnlyOneChildType
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: TreeConfig = {
|
||||||
|
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[]
|
||||||
|
return (menuList || []).map((item) => item.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findPath<T = any>(
|
||||||
|
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[] = [],
|
||||||
|
parent: AppRouteRecordRaw
|
||||||
|
): HasOneShowingChild {
|
||||||
|
const onlyOneChild = ref<OnlyOneChildType>()
|
||||||
|
|
||||||
|
const showingChildren = children.filter((v) => {
|
||||||
|
const meta = (v.meta ?? {}) as RouteMeta
|
||||||
|
if (meta.hidden) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
// Temp set(will be used if only has one showing child)
|
||||||
|
onlyOneChild.value = v
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// When there is only one child router, the child router is displayed by default
|
||||||
|
if (showingChildren.length === 1) {
|
||||||
|
return {
|
||||||
|
oneShowingChild: true,
|
||||||
|
onlyOneChild: unref(onlyOneChild)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show parent if there are no child router to display
|
||||||
|
if (!showingChildren.length) {
|
||||||
|
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
|
||||||
|
return {
|
||||||
|
oneShowingChild: true,
|
||||||
|
onlyOneChild: unref(onlyOneChild)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
oneShowingChild: false,
|
||||||
|
onlyOneChild: unref(onlyOneChild)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
|
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { useCssVar } from '@vueuse/core'
|
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
@ -10,8 +9,6 @@ const appStore = useAppStore()
|
||||||
|
|
||||||
const sizeMap = computed(() => appStore.sizeMap)
|
const sizeMap = computed(() => appStore.sizeMap)
|
||||||
|
|
||||||
const textColor = useCssVar('--el-text-color-primary', document.documentElement)
|
|
||||||
|
|
||||||
function setSize(size: ElememtPlusSzie) {
|
function setSize(size: ElememtPlusSzie) {
|
||||||
appStore.setSize(size)
|
appStore.setSize(size)
|
||||||
}
|
}
|
||||||
|
@ -19,7 +16,7 @@ function setSize(size: ElememtPlusSzie) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ElDropdown trigger="click" @command="setSize">
|
<ElDropdown trigger="click" @command="setSize">
|
||||||
<Icon icon="mdi:format-size" :color="textColor" class="cursor-pointer" />
|
<Icon icon="mdi:format-size" color="var(--el-text-color-primary)" class="cursor-pointer" />
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<ElDropdownMenu>
|
<ElDropdownMenu>
|
||||||
<ElDropdownItem v-for="item in sizeMap" :key="item" :command="item">
|
<ElDropdownItem v-for="item in sizeMap" :key="item" :command="item">
|
||||||
|
|
|
@ -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 { useCssVar } from '@vueuse/core'
|
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { useIcon } from '@/hooks/web/useIcon'
|
import { useIcon } from '@/hooks/web/useIcon'
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ const { getPrefixCls } = useDesign()
|
||||||
const prefixCls = getPrefixCls('theme-switch')
|
const prefixCls = getPrefixCls('theme-switch')
|
||||||
|
|
||||||
// 设置switch的背景颜色
|
// 设置switch的背景颜色
|
||||||
const blackColor = useCssVar('--el-color-black', document.documentElement)
|
const blackColor = 'var(--el-color-black)'
|
||||||
|
|
||||||
function themeChange(val: boolean) {
|
function themeChange(val: boolean) {
|
||||||
appStore.setIsDark(val)
|
appStore.setIsDark(val)
|
||||||
|
|
|
@ -2,10 +2,10 @@ import { useCache } from '@/hooks/web/useCache'
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
export type LayoutType = 'Classic' | 'LeftTop' | 'Top' | 'Test'
|
export type LayoutType = 'classic' | 'leftTop' | 'top' | 'test'
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
collapsed: boolean
|
collapse: boolean
|
||||||
showTags: boolean
|
showTags: boolean
|
||||||
showLogo: boolean
|
showLogo: boolean
|
||||||
showNavbar: boolean
|
showNavbar: boolean
|
||||||
|
@ -27,12 +27,12 @@ export interface AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appModules: AppState = {
|
export const appModules: AppState = {
|
||||||
collapsed: false, // 菜单栏是否栏缩收
|
collapse: false, // 菜单栏是否栏缩收
|
||||||
showLogo: true, // 是否显示logo
|
showLogo: true, // 是否显示logo
|
||||||
showTags: true, // 是否显示标签栏
|
showTags: true, // 是否显示标签栏
|
||||||
showNavbar: true, // 是否显示navbar
|
showNavbar: true, // 是否显示navbar
|
||||||
fixedHeader: true, // 是否固定header
|
fixedHeader: true, // 是否固定header
|
||||||
layout: 'Classic', // layout布局
|
layout: 'classic', // layout布局
|
||||||
showBreadcrumb: true, // 是否显示面包屑
|
showBreadcrumb: true, // 是否显示面包屑
|
||||||
showHamburger: true, // 是否显示侧边栏缩收按钮
|
showHamburger: true, // 是否显示侧边栏缩收按钮
|
||||||
showScreenfull: true, // 是否全屏按钮
|
showScreenfull: true, // 是否全屏按钮
|
||||||
|
|
|
@ -1,16 +1,43 @@
|
||||||
<script setup lang="ts">
|
<script lang="tsx">
|
||||||
// import { computed } from 'vue'
|
import { computed, defineComponent, KeepAlive } from 'vue'
|
||||||
// const getCaches = computed((): string[] => {
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
// return []
|
import { useAppStore } from '@/store/modules/app'
|
||||||
// })
|
import { Menu } from '@/components/Menu'
|
||||||
</script>
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
|
|
||||||
<template>
|
const tagsViewStore = useTagsViewStore()
|
||||||
<RouterView>
|
|
||||||
<template #default="{ Component, route }">
|
const getCaches = computed((): string[] => {
|
||||||
<KeepAlive>
|
return tagsViewStore.getCachedViews
|
||||||
<Component :is="Component" :key="route.fullPath" />
|
})
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
|
console.log(appStore)
|
||||||
|
const classSuffix = computed(() => appStore.getLayout)
|
||||||
|
|
||||||
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
|
const perFixCls = getPrefixCls('app')
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Layout',
|
||||||
|
setup() {
|
||||||
|
return () => (
|
||||||
|
<section
|
||||||
|
class={[perFixCls, `${perFixCls}__${classSuffix.value}`, 'w-[100%] h-[100%] relative']}
|
||||||
|
>
|
||||||
|
<Menu></Menu>
|
||||||
|
<router-view class="absolute top-0 right-0 ">
|
||||||
|
{{
|
||||||
|
default: ({ Component, route }) => (
|
||||||
|
<KeepAlive include={getCaches.value}>
|
||||||
|
<Component is={Component} key={route.fullPath}></Component>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
</template>
|
)
|
||||||
</RouterView>
|
}}
|
||||||
</template>
|
</router-view>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import { getParentLayout } from '@/utils/routerHelper'
|
import { Layout, getParentLayout } from '@/utils/routerHelper'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
/* Layout */
|
|
||||||
const Layout = () => import('@/layout/Layout.vue')
|
|
||||||
|
|
||||||
export const constantRouterMap: AppRouteRecordRaw[] = [
|
export const constantRouterMap: AppRouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/redirect',
|
path: '/redirect',
|
||||||
|
@ -45,7 +42,8 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
||||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||||
name: 'Level',
|
name: 'Level',
|
||||||
meta: {
|
meta: {
|
||||||
title: t('router.level')
|
title: t('router.level'),
|
||||||
|
icon: 'carbon:skill-level-advanced'
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,8 +10,8 @@ export const useAppStore = defineStore({
|
||||||
id: 'app',
|
id: 'app',
|
||||||
state: (): AppState => appModules,
|
state: (): AppState => appModules,
|
||||||
getters: {
|
getters: {
|
||||||
getCollapsed(): boolean {
|
getCollapse(): boolean {
|
||||||
return this.collapsed
|
return this.collapse
|
||||||
},
|
},
|
||||||
getShowLogo(): boolean {
|
getShowLogo(): boolean {
|
||||||
return this.showLogo
|
return this.showLogo
|
||||||
|
@ -69,8 +69,8 @@ export const useAppStore = defineStore({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setCollapsed(collapsed: boolean) {
|
setCollapse(collapse: boolean) {
|
||||||
this.collapsed = collapsed
|
this.collapse = collapse
|
||||||
},
|
},
|
||||||
setShowLogo(showLogo: boolean) {
|
setShowLogo(showLogo: boolean) {
|
||||||
this.showLogo = showLogo
|
this.showLogo = showLogo
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
// import router from '@/router'
|
||||||
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
|
import { getRawRoute } from '@/utils/routerHelper'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { store } from '../index'
|
||||||
|
|
||||||
|
export interface TagsViewState {
|
||||||
|
visitedViews: RouteLocationNormalizedLoaded[]
|
||||||
|
cachedViews: Set<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTagsViewStore = defineStore({
|
||||||
|
id: 'tagsView',
|
||||||
|
state: (): TagsViewState => ({
|
||||||
|
visitedViews: [],
|
||||||
|
cachedViews: new Set()
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
getVisitedViews(): RouteLocationNormalizedLoaded[] {
|
||||||
|
return this.visitedViews
|
||||||
|
},
|
||||||
|
getCachedViews(): string[] {
|
||||||
|
return Array.from(this.cachedViews)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
ADD_VISITED_VIEW(view: RouteLocationNormalizedLoaded): void {
|
||||||
|
if (this.visitedViews.some((v: RouteLocationNormalizedLoaded) => v.path === view.path)) return
|
||||||
|
if (view.meta?.noTagsView) return
|
||||||
|
this.visitedViews.push(
|
||||||
|
Object.assign({}, view, {
|
||||||
|
title: view.meta.title || 'no-name'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
SET_CACHED_VIEW(): void {
|
||||||
|
const cacheMap: Set<string> = new Set()
|
||||||
|
|
||||||
|
for (const v of this.visitedViews) {
|
||||||
|
const item = getRawRoute(v)
|
||||||
|
const needCache = !item.meta?.noCache
|
||||||
|
if (!needCache) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const name = item.name as string
|
||||||
|
cacheMap.add(name)
|
||||||
|
}
|
||||||
|
this.cachedViews = cacheMap
|
||||||
|
},
|
||||||
|
DEL_VISITED_VIEW(view: RouteLocationNormalizedLoaded): void {
|
||||||
|
for (const [i, v] of this.visitedViews.entries()) {
|
||||||
|
if (v.path === view.path) {
|
||||||
|
this.visitedViews.splice(i, 1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DEL_CACHED_VIEW(): void {
|
||||||
|
// const route = router.currentRoute.value
|
||||||
|
// for (const [key, value] of this.cachedViews) {
|
||||||
|
// const index = value.findIndex((item: string) => item === (route.name as string))
|
||||||
|
// if (index === -1) {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// if (value.length === 1) {
|
||||||
|
// this.cachedViews.delete(key)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// value.splice(index, 1)
|
||||||
|
// this.cachedViews.set(key, value)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
DEL_OTHERS_VISITED_VIEWS(view: RouteLocationNormalizedLoaded): void {
|
||||||
|
this.visitedViews = this.visitedViews.filter((v) => {
|
||||||
|
return v.meta.affix || v.path === view.path
|
||||||
|
})
|
||||||
|
},
|
||||||
|
DEL_ALL_VISITED_VIEWS(): void {
|
||||||
|
// keep affix tags
|
||||||
|
const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
|
||||||
|
this.visitedViews = affixTags
|
||||||
|
},
|
||||||
|
UPDATE_VISITED_VIEW(view: RouteLocationNormalizedLoaded): void {
|
||||||
|
for (let v of this.visitedViews) {
|
||||||
|
if (v.path === view.path) {
|
||||||
|
v = Object.assign(v, view)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addView(view: RouteLocationNormalizedLoaded): void {
|
||||||
|
this.addVisitedView(view)
|
||||||
|
this.addCachedView()
|
||||||
|
},
|
||||||
|
addVisitedView(view: RouteLocationNormalizedLoaded): void {
|
||||||
|
this.ADD_VISITED_VIEW(view)
|
||||||
|
},
|
||||||
|
addCachedView(): void {
|
||||||
|
this.SET_CACHED_VIEW()
|
||||||
|
},
|
||||||
|
delView(view: RouteLocationNormalizedLoaded): Promise<unknown> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.delVisitedView(view)
|
||||||
|
this.SET_CACHED_VIEW()
|
||||||
|
resolve({
|
||||||
|
visitedViews: [...this.visitedViews],
|
||||||
|
cachedViews: [...this.cachedViews]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delVisitedView(view: RouteLocationNormalizedLoaded): Promise<unknown> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.DEL_VISITED_VIEW(view)
|
||||||
|
resolve([...this.visitedViews])
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delCachedView(): Promise<unknown> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.DEL_CACHED_VIEW()
|
||||||
|
resolve([...this.cachedViews])
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delOthersViews(view: RouteLocationNormalizedLoaded): Promise<unknown> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.delOthersVisitedViews(view)
|
||||||
|
this.SET_CACHED_VIEW()
|
||||||
|
resolve({
|
||||||
|
visitedViews: [...this.visitedViews],
|
||||||
|
cachedViews: [...this.cachedViews]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delOthersVisitedViews(view: RouteLocationNormalizedLoaded): Promise<unknown> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.DEL_OTHERS_VISITED_VIEWS(view)
|
||||||
|
resolve([...this.visitedViews])
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delOthersCachedViews(): Promise<unknown> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.SET_CACHED_VIEW()
|
||||||
|
resolve([...this.cachedViews])
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delAllViews(): Promise<unknown> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.delAllVisitedViews()
|
||||||
|
this.SET_CACHED_VIEW()
|
||||||
|
resolve({
|
||||||
|
visitedViews: [...this.visitedViews],
|
||||||
|
cachedViews: [...this.cachedViews]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delAllVisitedViews(): Promise<unknown> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.DEL_ALL_VISITED_VIEWS()
|
||||||
|
resolve([...this.visitedViews])
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delAllCachedViews(): Promise<unknown> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.SET_CACHED_VIEW()
|
||||||
|
resolve([...this.cachedViews])
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateVisitedView(view: RouteLocationNormalizedLoaded): void {
|
||||||
|
this.UPDATE_VISITED_VIEW(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useTagsViewStoreWithOut() {
|
||||||
|
return useTagsViewStore(store)
|
||||||
|
}
|
|
@ -1,3 +1,17 @@
|
||||||
:root {
|
:root {
|
||||||
--dark-bg-color: #293146;
|
--dark-bg-color: #293146;
|
||||||
|
|
||||||
|
--left-menu-max-width: 200px;
|
||||||
|
|
||||||
|
--left-menu-min-width: 64px;
|
||||||
|
|
||||||
|
--left-menu-bg-color: #001529;
|
||||||
|
|
||||||
|
--left-menu-bg-light-color: #0f2438;
|
||||||
|
|
||||||
|
--left-menu-bg-active-color: var(--el-color-primary);
|
||||||
|
|
||||||
|
--left-menu-text-color: #bfcbd9;
|
||||||
|
|
||||||
|
--left-menu-text-active-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
/**
|
||||||
|
* 判断是否 十六进制颜色值.
|
||||||
|
* 输入形式可为 #fff000 #f00
|
||||||
|
*
|
||||||
|
* @param String color 十六进制颜色值
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
export function isHexColor(color: string) {
|
||||||
|
const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/
|
||||||
|
return reg.test(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RGB 颜色值转换为 十六进制颜色值.
|
||||||
|
* r, g, 和 b 需要在 [0, 255] 范围内
|
||||||
|
*
|
||||||
|
* @return String 类似#ff00ff
|
||||||
|
* @param r
|
||||||
|
* @param g
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
export function rgbToHex(r: number, g: number, b: number) {
|
||||||
|
// tslint:disable-next-line:no-bitwise
|
||||||
|
const hex = ((r << 16) | (g << 8) | b).toString(16)
|
||||||
|
return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a HEX color to its RGB representation
|
||||||
|
* @param {string} hex The color to transform
|
||||||
|
* @returns The RGB representation of the passed color
|
||||||
|
*/
|
||||||
|
export function hexToRGB(hex: string) {
|
||||||
|
let sHex = hex.toLowerCase()
|
||||||
|
if (isHexColor(hex)) {
|
||||||
|
if (sHex.length === 4) {
|
||||||
|
let sColorNew = '#'
|
||||||
|
for (let i = 1; i < 4; i += 1) {
|
||||||
|
sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1))
|
||||||
|
}
|
||||||
|
sHex = sColorNew
|
||||||
|
}
|
||||||
|
const sColorChange: number[] = []
|
||||||
|
for (let i = 1; i < 7; i += 2) {
|
||||||
|
sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2)))
|
||||||
|
}
|
||||||
|
return 'RGB(' + sColorChange.join(',') + ')'
|
||||||
|
}
|
||||||
|
return sHex
|
||||||
|
}
|
||||||
|
|
||||||
|
export function colorIsDark(color: string) {
|
||||||
|
if (!isHexColor(color)) return
|
||||||
|
const [r, g, b] = hexToRGB(color)
|
||||||
|
.replace(/(?:\(|\)|rgb|RGB)*/g, '')
|
||||||
|
.split(',')
|
||||||
|
.map((item) => Number(item))
|
||||||
|
return r * 0.299 + g * 0.578 + b * 0.114 < 192
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Darkens a HEX color given the passed percentage
|
||||||
|
* @param {string} color The color to process
|
||||||
|
* @param {number} amount The amount to change the color by
|
||||||
|
* @returns {string} The HEX representation of the processed color
|
||||||
|
*/
|
||||||
|
export function darken(color: string, amount: number) {
|
||||||
|
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color
|
||||||
|
amount = Math.trunc((255 * amount) / 100)
|
||||||
|
return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight(
|
||||||
|
color.substring(2, 4),
|
||||||
|
amount
|
||||||
|
)}${subtractLight(color.substring(4, 6), amount)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightens a 6 char HEX color according to the passed percentage
|
||||||
|
* @param {string} color The color to change
|
||||||
|
* @param {number} amount The amount to change the color by
|
||||||
|
* @returns {string} The processed color represented as HEX
|
||||||
|
*/
|
||||||
|
export function lighten(color: string, amount: number) {
|
||||||
|
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color
|
||||||
|
amount = Math.trunc((255 * amount) / 100)
|
||||||
|
return `#${addLight(color.substring(0, 2), amount)}${addLight(
|
||||||
|
color.substring(2, 4),
|
||||||
|
amount
|
||||||
|
)}${addLight(color.substring(4, 6), amount)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */
|
||||||
|
/**
|
||||||
|
* Sums the passed percentage to the R, G or B of a HEX color
|
||||||
|
* @param {string} color The color to change
|
||||||
|
* @param {number} amount The amount to change the color by
|
||||||
|
* @returns {string} The processed part of the color
|
||||||
|
*/
|
||||||
|
function addLight(color: string, amount: number) {
|
||||||
|
const cc = parseInt(color, 16) + amount
|
||||||
|
const c = cc > 255 ? 255 : cc
|
||||||
|
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates luminance of an rgb color
|
||||||
|
* @param {number} r red
|
||||||
|
* @param {number} g green
|
||||||
|
* @param {number} b blue
|
||||||
|
*/
|
||||||
|
function luminanace(r: number, g: number, b: number) {
|
||||||
|
const a = [r, g, b].map((v) => {
|
||||||
|
v /= 255
|
||||||
|
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)
|
||||||
|
})
|
||||||
|
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates contrast between two rgb colors
|
||||||
|
* @param {string} rgb1 rgb color 1
|
||||||
|
* @param {string} rgb2 rgb color 2
|
||||||
|
*/
|
||||||
|
function contrast(rgb1: string[], rgb2: number[]) {
|
||||||
|
return (
|
||||||
|
(luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) /
|
||||||
|
(luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
export function calculateBestTextColor(hexColor: string) {
|
||||||
|
const rgbColor = hexToRGB(hexColor.substring(1))
|
||||||
|
const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0])
|
||||||
|
|
||||||
|
return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts the indicated percentage to the R, G or B of a HEX color
|
||||||
|
* @param {string} color The color to change
|
||||||
|
* @param {number} amount The amount to change the color by
|
||||||
|
* @returns {string} The processed part of the color
|
||||||
|
*/
|
||||||
|
function subtractLight(color: string, amount: number) {
|
||||||
|
const cc = parseInt(color, 16) - amount
|
||||||
|
const c = cc < 0 ? 0 : cc
|
||||||
|
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCssVar(prop: string, val: any, dom = document.documentElement) {
|
||||||
|
dom.style.setProperty(prop, val)
|
||||||
|
}
|
|
@ -34,23 +34,3 @@ export function underlineToHump(str: string): string {
|
||||||
return letter.toUpperCase()
|
return letter.toUpperCase()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 对象数组深拷贝
|
|
||||||
* @param {Array,Object} source 需要深拷贝的对象数组
|
|
||||||
* @param {Array} noClone 不需要深拷贝的属性集合
|
|
||||||
*/
|
|
||||||
export function deepClone(source: any, noClone: string[] = []): any {
|
|
||||||
if (!source && typeof source !== 'object') {
|
|
||||||
throw new Error('error arguments deepClone')
|
|
||||||
}
|
|
||||||
const targetObj: any = source.constructor === Array ? [] : {}
|
|
||||||
Object.keys(source).forEach((keys: string) => {
|
|
||||||
if (source[keys] && typeof source[keys] === 'object' && noClone.indexOf(keys) === -1) {
|
|
||||||
targetObj[keys] = deepClone(source[keys], noClone)
|
|
||||||
} else {
|
|
||||||
targetObj[keys] = source[keys]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return targetObj
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ const { wsCache } = useCache()
|
||||||
const modules = import.meta.glob('../../views/**/*.{vue,tsx}')
|
const modules = import.meta.glob('../../views/**/*.{vue,tsx}')
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
const Layout = () => import('@/layout/index.vue')
|
export const Layout = () => import('@/layout/Layout.vue')
|
||||||
|
|
||||||
export const getParentLayout = () => {
|
export const getParentLayout = () => {
|
||||||
return () =>
|
return () =>
|
||||||
|
@ -23,7 +23,7 @@ export const getParentLayout = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized {
|
export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormalized {
|
||||||
if (!route) return route
|
if (!route) return route
|
||||||
const { matched, ...opt } = route
|
const { matched, ...opt } = route
|
||||||
return {
|
return {
|
||||||
|
@ -101,14 +101,16 @@ export function generateRoutesFn2(routes: AppRouteRecordRaw[]): AppRouteRecordRa
|
||||||
meta: route.meta
|
meta: route.meta
|
||||||
}
|
}
|
||||||
if (route.component) {
|
if (route.component) {
|
||||||
|
const comModule =
|
||||||
|
modules[`../../${route.component}.vue`] || modules[`../../${route.component}.tsx`]
|
||||||
|
if (comModule) {
|
||||||
// 动态加载路由文件,可根据实际情况进行自定义逻辑
|
// 动态加载路由文件,可根据实际情况进行自定义逻辑
|
||||||
const component = route.component as string
|
const component = route.component as string
|
||||||
data.component =
|
data.component =
|
||||||
component === '#'
|
component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule
|
||||||
? Layout
|
} else {
|
||||||
: component.includes('##')
|
console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`)
|
||||||
? getParentLayout()
|
}
|
||||||
: modules[`../../${route.component}.vue`] || modules[`../../${route.component}.tsx`]
|
|
||||||
}
|
}
|
||||||
// recursive child routes
|
// recursive child routes
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
|
@ -155,7 +157,7 @@ function isMultipleRoute(route: AppRouteRecordRaw) {
|
||||||
return flag
|
return flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// 路由降级
|
// 生成二级路由
|
||||||
function promoteRouteLevel(route: AppRouteRecordRaw) {
|
function 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],
|
||||||
|
|
|
@ -61,7 +61,6 @@ declare global {
|
||||||
name: string
|
name: string
|
||||||
meta: RouteMeta
|
meta: RouteMeta
|
||||||
component?: Component | string
|
component?: Component | string
|
||||||
components?: Component
|
|
||||||
children?: AppRouteRecordRaw[]
|
children?: AppRouteRecordRaw[]
|
||||||
props?: Recordable
|
props?: Recordable
|
||||||
fullPath?: string
|
fullPath?: string
|
||||||
|
|
Loading…
Reference in New Issue