feat(utils): Add color utils

This commit is contained in:
陈凯龙 2022-01-12 16:44:57 +08:00
parent b218ccc9cc
commit 71dfba21c5
21 changed files with 1268 additions and 635 deletions

View File

@ -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?"
} }

View File

@ -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"

File diff suppressed because it is too large Load Diff

View File

@ -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"
/> />

View File

@ -0,0 +1,3 @@
import Menu from './src/Menu.vue'
export { Menu }

View File

@ -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>

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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">

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 { 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)

View File

@ -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, // 是否全屏按钮

View File

@ -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>

View File

@ -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: [
{ {

View File

@ -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

View File

@ -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)
}

View File

@ -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;
} }

155
src/utils/color.ts Normal file
View File

@ -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)
}

View File

@ -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
}

View File

@ -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],

1
types/router.d.ts vendored
View File

@ -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