
134 changed files with 3594 additions and 3390 deletions
-
2.env.production
-
23.eslintrc.js
-
3.gitignore
-
4.vscode/extensions.json
-
3.vscode/settings.json
-
2README.en-US.md
-
6README.md
-
2build/index.ts
-
77build/info.ts
-
4build/plugins.ts
-
9index.html
-
40locales/en.yaml
-
44locales/zh-CN.yaml
-
5mock/asyncRoutes.ts
-
100package.json
-
1214pnpm-lock.yaml
-
2public/serverConfig.json
-
7src/api/routes.ts
-
8src/api/user.ts
-
2src/assets/login/illustration.svg
-
15src/components/ReIcon/index.ts
-
12src/components/ReIcon/src/hooks.ts
-
2src/components/ReIcon/src/iconfont.ts
-
13src/components/ReIcon/src/iconifyIconOffline.ts
-
7src/components/ReIcon/src/iconifyIconOnline.ts
-
2src/components/ReIcon/src/types.ts
-
13src/components/ReImageVerify/index.ts
-
10src/components/ReImageVerify/src/index.vue
-
9src/components/ReQrcode/index.ts
-
6src/components/ReQrcode/src/index.tsx
-
8src/directives/elResizeDetector/index.ts
-
25src/layout/components/appMain.vue
-
128src/layout/components/navbar.vue
-
5src/layout/components/notice/index.vue
-
15src/layout/components/notice/noticeItem.vue
-
12src/layout/components/panel/index.vue
-
17src/layout/components/screenfull/index.vue
-
3src/layout/components/search/components/SearchFooter.vue
-
11src/layout/components/search/components/SearchModal.vue
-
53src/layout/components/search/components/SearchResult.vue
-
22src/layout/components/search/index.vue
-
232src/layout/components/setting/index.vue
-
112src/layout/components/sidebar/breadCrumb.vue
-
63src/layout/components/sidebar/hamBurger.vue
-
89src/layout/components/sidebar/horizontal.vue
-
47src/layout/components/sidebar/leftCollapse.vue
-
6src/layout/components/sidebar/logo.vue
-
149src/layout/components/sidebar/mixNav.vue
-
64src/layout/components/sidebar/sidebarItem.vue
-
30src/layout/components/sidebar/topCollapse.vue
-
38src/layout/components/sidebar/vertical.vue
-
16src/layout/components/tag/index.scss
-
400src/layout/components/tag/index.vue
-
28src/layout/frameView.vue
-
2src/layout/hooks/useBoolean.ts
-
116src/layout/hooks/useDataThemeChange.ts
-
60src/layout/hooks/useLayout.ts
-
57src/layout/hooks/useNav.ts
-
218src/layout/hooks/useTag.ts
-
37src/layout/hooks/useTranslationLang.ts
-
92src/layout/index.vue
-
4src/layout/redirect.vue
-
84src/layout/theme/element-plus.ts
-
2src/layout/theme/element.scss
-
59src/layout/theme/index.ts
-
5src/layout/types.ts
-
12src/main.ts
-
4src/mockProdServer.ts
-
41src/plugins/echarts/index.ts
-
58src/plugins/element-plus/index.ts
-
18src/plugins/i18n.ts
-
6src/plugins/vxe-table/index.scss
-
114src/plugins/vxe-table/index.ts
-
114src/router/index.ts
-
5src/router/modules/error.ts
-
9src/router/modules/home.ts
-
7src/router/modules/remaining.ts
-
1src/router/types.ts
-
61src/router/utils.ts
-
24src/store/modules/app.ts
-
12src/store/modules/epTheme.ts
-
45src/store/modules/multiTags.ts
-
5src/store/modules/types.ts
-
16src/store/modules/user.ts
-
173src/style/dark.scss
-
9src/style/element-plus.scss
-
9src/style/index.scss
-
12src/style/mixin.scss
-
229src/style/sidebar.scss
-
13src/style/transition.scss
-
5src/utils/README.md
-
39src/utils/debounce/index.ts
-
37src/utils/deviceDetection/index.ts
-
118src/utils/is.ts
-
13src/utils/link.ts
-
54src/utils/loaders/index.ts
-
38src/utils/message/index.ts
-
2src/utils/mitt.ts
-
57src/utils/operate/index.ts
-
3src/utils/print.ts
1214
pnpm-lock.yaml
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,5 +1,10 @@ |
|||||
import { http } from "../utils/http"; |
import { http } from "../utils/http"; |
||||
|
|
||||
|
type Result = { |
||||
|
code: number; |
||||
|
info: Array<any>; |
||||
|
}; |
||||
|
|
||||
export const getAsyncRoutes = (params?: object) => { |
export const getAsyncRoutes = (params?: object) => { |
||||
return http.request("get", "/getAsyncRoutes", { params }); |
|
||||
|
return http.request<Result>("get", "/getAsyncRoutes", { params }); |
||||
}; |
}; |
2
src/assets/login/illustration.svg
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,12 +1,7 @@ |
|||||
import { App } from "vue"; |
|
||||
import reImageVerify from "./src/index.vue"; |
import reImageVerify from "./src/index.vue"; |
||||
|
import { withInstall } from "@pureadmin/utils"; |
||||
|
|
||||
export const ReImageVerify = Object.assign(reImageVerify, { |
|
||||
install(app: App) { |
|
||||
app.component(reImageVerify.name, reImageVerify); |
|
||||
} |
|
||||
}); |
|
||||
|
/** 图形验证码组件 */ |
||||
|
export const ReImageVerify = withInstall(reImageVerify); |
||||
|
|
||||
export default { |
|
||||
ReImageVerify |
|
||||
}; |
|
||||
|
export default ReImageVerify; |
@ -1,10 +1,7 @@ |
|||||
import { App } from "vue"; |
|
||||
import reQrcode from "./src/index"; |
import reQrcode from "./src/index"; |
||||
|
import { withInstall } from "@pureadmin/utils"; |
||||
|
|
||||
export const ReQrcode = Object.assign(reQrcode, { |
|
||||
install(app: App) { |
|
||||
app.component(reQrcode.name, reQrcode); |
|
||||
} |
|
||||
}); |
|
||||
|
/** 二维码组件 */ |
||||
|
export const ReQrcode = withInstall(reQrcode); |
||||
|
|
||||
export default ReQrcode; |
export default ReQrcode; |
@ -1,63 +0,0 @@ |
|||||
<script setup lang="ts"> |
|
||||
import { ref } from "vue"; |
|
||||
import { useEpThemeStoreHook } from "/@/store/modules/epTheme"; |
|
||||
export interface Props { |
|
||||
isActive: boolean; |
|
||||
} |
|
||||
|
|
||||
const props = withDefaults(defineProps<Props>(), { |
|
||||
isActive: false |
|
||||
}); |
|
||||
|
|
||||
const fillColor = ref<string>(""); |
|
||||
|
|
||||
const emit = defineEmits<{ |
|
||||
(e: "toggleClick"): void; |
|
||||
}>(); |
|
||||
|
|
||||
const toggleClick = () => { |
|
||||
emit("toggleClick"); |
|
||||
}; |
|
||||
</script> |
|
||||
|
|
||||
<template> |
|
||||
<div |
|
||||
:class="classes.container" |
|
||||
:title="props.isActive ? '点击折叠' : '点击展开'" |
|
||||
@click="toggleClick" |
|
||||
@mouseenter="fillColor = useEpThemeStoreHook().epThemeColor" |
|
||||
@mouseleave="fillColor = ''" |
|
||||
> |
|
||||
<svg |
|
||||
:fill="fillColor" |
|
||||
:class="['hamburger', props.isActive ? 'is-active' : '']" |
|
||||
viewBox="0 0 1024 1024" |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
width="64" |
|
||||
height="64" |
|
||||
> |
|
||||
<path |
|
||||
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" |
|
||||
/> |
|
||||
</svg> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<style module="classes" scoped> |
|
||||
.container { |
|
||||
padding: 0 15px; |
|
||||
} |
|
||||
</style> |
|
||||
|
|
||||
<style scoped> |
|
||||
.hamburger { |
|
||||
display: inline-block; |
|
||||
vertical-align: middle; |
|
||||
width: 20px; |
|
||||
height: 20px; |
|
||||
} |
|
||||
|
|
||||
.is-active { |
|
||||
transform: rotate(180deg); |
|
||||
} |
|
||||
</style> |
|
@ -0,0 +1,47 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { useDark } from "@pureadmin/utils"; |
||||
|
|
||||
|
interface Props { |
||||
|
isActive: boolean; |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<Props>(), { |
||||
|
isActive: false |
||||
|
}); |
||||
|
const { isDark } = useDark(); |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
(e: "toggleClick"): void; |
||||
|
}>(); |
||||
|
|
||||
|
const toggleClick = () => { |
||||
|
emit("toggleClick"); |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="container"> |
||||
|
<el-tooltip |
||||
|
placement="right" |
||||
|
:effect="isDark ? 'dark' : 'light'" |
||||
|
:content="props.isActive ? '点击折叠' : '点击展开'" |
||||
|
> |
||||
|
<IconifyIconOffline |
||||
|
:icon="props.isActive ? 'menu-fold' : 'menu-unfold'" |
||||
|
class="cursor-pointer inline-block align-middle color-primary hover:color-primary !dark:hover:color-white w-16px h-16px ml-4 mb-1" |
||||
|
@click="toggleClick" |
||||
|
/> |
||||
|
</el-tooltip> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.container { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
width: 100%; |
||||
|
height: 40px; |
||||
|
line-height: 40px; |
||||
|
box-shadow: 0 0 6px -2px var(--el-color-primary); |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,30 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
interface Props { |
||||
|
isActive: boolean; |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<Props>(), { |
||||
|
isActive: false |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
(e: "toggleClick"): void; |
||||
|
}>(); |
||||
|
|
||||
|
const toggleClick = () => { |
||||
|
emit("toggleClick"); |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div |
||||
|
class="px-3 mr-1 navbar-bg-hover" |
||||
|
:title="props.isActive ? '点击折叠' : '点击展开'" |
||||
|
@click="toggleClick" |
||||
|
> |
||||
|
<IconifyIconOffline |
||||
|
:icon="props.isActive ? 'menu-fold' : 'menu-unfold'" |
||||
|
class="inline-block align-middle hover:color-primary !dark:hover:color-white" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
@ -0,0 +1,116 @@ |
|||||
|
import { ref } from "vue"; |
||||
|
import { getConfig } from "/@/config"; |
||||
|
import { find } from "lodash-unified"; |
||||
|
import { useLayout } from "./useLayout"; |
||||
|
import { themeColorsType } from "../types"; |
||||
|
import { TinyColor } from "@ctrl/tinycolor"; |
||||
|
import { useGlobal } from "@pureadmin/utils"; |
||||
|
import { useEpThemeStoreHook } from "/@/store/modules/epTheme"; |
||||
|
import { |
||||
|
darken, |
||||
|
lighten, |
||||
|
toggleTheme |
||||
|
} from "@pureadmin/theme/dist/browser-utils"; |
||||
|
|
||||
|
export function useDataThemeChange() { |
||||
|
const { layoutTheme, layout } = useLayout(); |
||||
|
const themeColors = ref<Array<themeColorsType>>([ |
||||
|
/* 道奇蓝(默认) */ |
||||
|
{ color: "#1b2a47", themeColor: "default" }, |
||||
|
/* 亮白色 */ |
||||
|
{ color: "#ffffff", themeColor: "light" }, |
||||
|
/* 猩红色 */ |
||||
|
{ color: "#f5222d", themeColor: "dusk" }, |
||||
|
/* 橙红色 */ |
||||
|
{ color: "#fa541c", themeColor: "volcano" }, |
||||
|
/* 金色 */ |
||||
|
{ color: "#fadb14", themeColor: "yellow" }, |
||||
|
/* 绿宝石 */ |
||||
|
{ color: "#13c2c2", themeColor: "mingQing" }, |
||||
|
/* 酸橙绿 */ |
||||
|
{ color: "#52c41a", themeColor: "auroraGreen" }, |
||||
|
/* 深粉色 */ |
||||
|
{ color: "#eb2f96", themeColor: "pink" }, |
||||
|
/* 深紫罗兰色 */ |
||||
|
{ color: "#722ed1", themeColor: "saucePurple" } |
||||
|
]); |
||||
|
|
||||
|
const { $storage } = useGlobal<GlobalPropertiesApi>(); |
||||
|
const dataTheme = ref<boolean>($storage?.layout?.darkMode); |
||||
|
const body = document.documentElement as HTMLElement; |
||||
|
|
||||
|
/** 设置导航主题色 */ |
||||
|
function setLayoutThemeColor(theme = "default") { |
||||
|
layoutTheme.value.theme = theme; |
||||
|
toggleTheme({ |
||||
|
scopeName: `layout-theme-${theme}` |
||||
|
}); |
||||
|
$storage.layout = { |
||||
|
layout: layout.value, |
||||
|
theme, |
||||
|
darkMode: dataTheme.value, |
||||
|
sidebarStatus: $storage.layout?.sidebarStatus, |
||||
|
epThemeColor: $storage.layout?.epThemeColor |
||||
|
}; |
||||
|
|
||||
|
if (theme === "default" || theme === "light") { |
||||
|
setEpThemeColor(getConfig().EpThemeColor); |
||||
|
} else { |
||||
|
const colors = find(themeColors.value, { themeColor: theme }); |
||||
|
setEpThemeColor(colors.color); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @description 自动计算hover和active颜色 |
||||
|
* @see {@link https://element-plus.org/zh-CN/component/button.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%A2%9C%E8%89%B2}
|
||||
|
*/ |
||||
|
const shadeBgColor = (color: string): string => { |
||||
|
return new TinyColor(color).shade(10).toString(); |
||||
|
}; |
||||
|
|
||||
|
/** 设置ep主题色 */ |
||||
|
const setEpThemeColor = (color: string) => { |
||||
|
useEpThemeStoreHook().setEpThemeColor(color); |
||||
|
body.style.setProperty("--el-color-primary-active", shadeBgColor(color)); |
||||
|
document.documentElement.style.setProperty("--el-color-primary", color); |
||||
|
for (let i = 1; i <= 9; i++) { |
||||
|
document.documentElement.style.setProperty( |
||||
|
`--el-color-primary-light-${i}`, |
||||
|
lighten(color, i / 10) |
||||
|
); |
||||
|
} |
||||
|
for (let i = 1; i <= 2; i++) { |
||||
|
document.documentElement.style.setProperty( |
||||
|
`--el-color-primary-dark-${i}`, |
||||
|
darken(color, i / 10) |
||||
|
); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** 日间、夜间主题切换 */ |
||||
|
function dataThemeChange() { |
||||
|
/* 如果当前是light夜间主题,默认切换到default主题 */ |
||||
|
if (useEpThemeStoreHook().epTheme === "light" && dataTheme.value) { |
||||
|
setLayoutThemeColor("default"); |
||||
|
} else { |
||||
|
setLayoutThemeColor(useEpThemeStoreHook().epTheme); |
||||
|
} |
||||
|
|
||||
|
if (dataTheme.value) { |
||||
|
document.documentElement.classList.add("dark"); |
||||
|
} else { |
||||
|
document.documentElement.classList.remove("dark"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
body, |
||||
|
dataTheme, |
||||
|
layoutTheme, |
||||
|
themeColors, |
||||
|
dataThemeChange, |
||||
|
setEpThemeColor, |
||||
|
setLayoutThemeColor |
||||
|
}; |
||||
|
} |
@ -0,0 +1,60 @@ |
|||||
|
import { computed } from "vue"; |
||||
|
import { useI18n } from "vue-i18n"; |
||||
|
import { routerArrays } from "../types"; |
||||
|
import { useGlobal } from "@pureadmin/utils"; |
||||
|
import { useMultiTagsStore } from "/@/store/modules/multiTags"; |
||||
|
|
||||
|
export function useLayout() { |
||||
|
const { $storage, $config } = useGlobal<GlobalPropertiesApi>(); |
||||
|
|
||||
|
const initStorage = () => { |
||||
|
/** 路由 */ |
||||
|
if ( |
||||
|
useMultiTagsStore().multiTagsCache && |
||||
|
(!$storage.tags || $storage.tags.length === 0) |
||||
|
) { |
||||
|
$storage.tags = routerArrays; |
||||
|
} |
||||
|
/** 国际化 */ |
||||
|
if (!$storage.locale) { |
||||
|
$storage.locale = { locale: $config?.Locale ?? "zh" }; |
||||
|
useI18n().locale.value = $config?.Locale ?? "zh"; |
||||
|
} |
||||
|
/** 导航 */ |
||||
|
if (!$storage.layout) { |
||||
|
$storage.layout = { |
||||
|
layout: $config?.Layout ?? "vertical", |
||||
|
theme: $config?.Theme ?? "default", |
||||
|
darkMode: $config?.DarkMode ?? false, |
||||
|
sidebarStatus: $config?.SidebarStatus ?? true, |
||||
|
epThemeColor: $config?.EpThemeColor ?? "#409EFF" |
||||
|
}; |
||||
|
} |
||||
|
/** 灰色模式、色弱模式、隐藏标签页 */ |
||||
|
if (!$storage.configure) { |
||||
|
$storage.configure = { |
||||
|
grey: $config?.Grey ?? false, |
||||
|
weak: $config?.Weak ?? false, |
||||
|
hideTabs: $config?.HideTabs ?? false, |
||||
|
showLogo: $config?.ShowLogo ?? true, |
||||
|
showModel: $config?.ShowModel ?? "smart", |
||||
|
multiTagsCache: $config?.MultiTagsCache ?? false |
||||
|
}; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** 清空缓存后从serverConfig.json读取默认配置并赋值到storage中 */ |
||||
|
const layout = computed(() => { |
||||
|
return $storage?.layout.layout; |
||||
|
}); |
||||
|
|
||||
|
const layoutTheme = computed(() => { |
||||
|
return $storage.layout; |
||||
|
}); |
||||
|
|
||||
|
return { |
||||
|
layout, |
||||
|
layoutTheme, |
||||
|
initStorage |
||||
|
}; |
||||
|
} |
@ -0,0 +1,218 @@ |
|||||
|
import { |
||||
|
ref, |
||||
|
unref, |
||||
|
watch, |
||||
|
computed, |
||||
|
reactive, |
||||
|
onMounted, |
||||
|
CSSProperties, |
||||
|
getCurrentInstance |
||||
|
} from "vue"; |
||||
|
import { tagsViewsType } from "../types"; |
||||
|
import { isEqual } from "lodash-unified"; |
||||
|
import type { StorageConfigs } from "/#/index"; |
||||
|
import { useEventListener } from "@vueuse/core"; |
||||
|
import { useRoute, useRouter } from "vue-router"; |
||||
|
import { transformI18n, $t } from "/@/plugins/i18n"; |
||||
|
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags"; |
||||
|
import { storageLocal, toggleClass, hasClass } from "@pureadmin/utils"; |
||||
|
|
||||
|
import close from "/@/assets/svg/close.svg?component"; |
||||
|
import refresh from "/@/assets/svg/refresh.svg?component"; |
||||
|
import closeAll from "/@/assets/svg/close_all.svg?component"; |
||||
|
import closeLeft from "/@/assets/svg/close_left.svg?component"; |
||||
|
import closeOther from "/@/assets/svg/close_other.svg?component"; |
||||
|
import closeRight from "/@/assets/svg/close_right.svg?component"; |
||||
|
|
||||
|
export function useTags() { |
||||
|
const route = useRoute(); |
||||
|
const router = useRouter(); |
||||
|
const instance = getCurrentInstance(); |
||||
|
|
||||
|
const buttonTop = ref(0); |
||||
|
const buttonLeft = ref(0); |
||||
|
const translateX = ref(0); |
||||
|
const visible = ref(false); |
||||
|
const activeIndex = ref(-1); |
||||
|
// 当前右键选中的路由信息
|
||||
|
const currentSelect = ref({}); |
||||
|
|
||||
|
/** 显示模式,默认灵动模式 */ |
||||
|
const showModel = ref( |
||||
|
storageLocal.getItem<StorageConfigs>("responsive-configure")?.showModel || |
||||
|
"smart" |
||||
|
); |
||||
|
/** 是否隐藏标签页,默认显示 */ |
||||
|
const showTags = |
||||
|
ref( |
||||
|
storageLocal.getItem<StorageConfigs>("responsive-configure").hideTabs |
||||
|
) ?? ref("false"); |
||||
|
const multiTags: any = computed(() => { |
||||
|
return useMultiTagsStoreHook().multiTags; |
||||
|
}); |
||||
|
|
||||
|
const tagsViews = reactive<Array<tagsViewsType>>([ |
||||
|
{ |
||||
|
icon: refresh, |
||||
|
text: $t("buttons.hsreload"), |
||||
|
divided: false, |
||||
|
disabled: false, |
||||
|
show: true |
||||
|
}, |
||||
|
{ |
||||
|
icon: close, |
||||
|
text: $t("buttons.hscloseCurrentTab"), |
||||
|
divided: false, |
||||
|
disabled: multiTags.value.length > 1 ? false : true, |
||||
|
show: true |
||||
|
}, |
||||
|
{ |
||||
|
icon: closeLeft, |
||||
|
text: $t("buttons.hscloseLeftTabs"), |
||||
|
divided: true, |
||||
|
disabled: multiTags.value.length > 1 ? false : true, |
||||
|
show: true |
||||
|
}, |
||||
|
{ |
||||
|
icon: closeRight, |
||||
|
text: $t("buttons.hscloseRightTabs"), |
||||
|
divided: false, |
||||
|
disabled: multiTags.value.length > 1 ? false : true, |
||||
|
show: true |
||||
|
}, |
||||
|
{ |
||||
|
icon: closeOther, |
||||
|
text: $t("buttons.hscloseOtherTabs"), |
||||
|
divided: true, |
||||
|
disabled: multiTags.value.length > 2 ? false : true, |
||||
|
show: true |
||||
|
}, |
||||
|
{ |
||||
|
icon: closeAll, |
||||
|
text: $t("buttons.hscloseAllTabs"), |
||||
|
divided: false, |
||||
|
disabled: multiTags.value.length > 1 ? false : true, |
||||
|
show: true |
||||
|
} |
||||
|
]); |
||||
|
|
||||
|
function conditionHandle(item, previous, next) { |
||||
|
if ( |
||||
|
Object.keys(route.query).length === 0 && |
||||
|
Object.keys(route.params).length === 0 |
||||
|
) { |
||||
|
return route.path === item.path ? previous : next; |
||||
|
} else if (Object.keys(route.query).length > 0) { |
||||
|
return isEqual(route.query, item.query) ? previous : next; |
||||
|
} else { |
||||
|
return isEqual(route.params, item.params) ? previous : next; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const iconIsActive = computed(() => { |
||||
|
return (item, index) => { |
||||
|
if (index === 0) return; |
||||
|
return conditionHandle(item, true, false); |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
const linkIsActive = computed(() => { |
||||
|
return item => { |
||||
|
return conditionHandle(item, "is-active", ""); |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
const scheduleIsActive = computed(() => { |
||||
|
return item => { |
||||
|
return conditionHandle(item, "schedule-active", ""); |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
const getTabStyle = computed((): CSSProperties => { |
||||
|
return { |
||||
|
transform: `translateX(${translateX.value}px)` |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
const getContextMenuStyle = computed((): CSSProperties => { |
||||
|
return { left: buttonLeft.value + "px", top: buttonTop.value + "px" }; |
||||
|
}); |
||||
|
|
||||
|
const closeMenu = () => { |
||||
|
visible.value = false; |
||||
|
}; |
||||
|
|
||||
|
/** 鼠标移入添加激活样式 */ |
||||
|
function onMouseenter(index) { |
||||
|
if (index) activeIndex.value = index; |
||||
|
if (unref(showModel) === "smart") { |
||||
|
if (hasClass(instance.refs["schedule" + index][0], "schedule-active")) |
||||
|
return; |
||||
|
toggleClass(true, "schedule-in", instance.refs["schedule" + index][0]); |
||||
|
toggleClass(false, "schedule-out", instance.refs["schedule" + index][0]); |
||||
|
} else { |
||||
|
if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return; |
||||
|
toggleClass(true, "card-in", instance.refs["dynamic" + index][0]); |
||||
|
toggleClass(false, "card-out", instance.refs["dynamic" + index][0]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** 鼠标移出恢复默认样式 */ |
||||
|
function onMouseleave(index) { |
||||
|
activeIndex.value = -1; |
||||
|
if (unref(showModel) === "smart") { |
||||
|
if (hasClass(instance.refs["schedule" + index][0], "schedule-active")) |
||||
|
return; |
||||
|
toggleClass(false, "schedule-in", instance.refs["schedule" + index][0]); |
||||
|
toggleClass(true, "schedule-out", instance.refs["schedule" + index][0]); |
||||
|
} else { |
||||
|
if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return; |
||||
|
toggleClass(false, "card-in", instance.refs["dynamic" + index][0]); |
||||
|
toggleClass(true, "card-out", instance.refs["dynamic" + index][0]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMounted(() => { |
||||
|
if (!showModel.value) { |
||||
|
const configure = storageLocal.getItem<StorageConfigs>( |
||||
|
"responsive-configure" |
||||
|
); |
||||
|
configure.showModel = "card"; |
||||
|
storageLocal.setItem("responsive-configure", configure); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
watch( |
||||
|
() => visible.value, |
||||
|
() => { |
||||
|
useEventListener(document, "click", closeMenu); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
return { |
||||
|
route, |
||||
|
router, |
||||
|
visible, |
||||
|
showTags, |
||||
|
instance, |
||||
|
multiTags, |
||||
|
showModel, |
||||
|
tagsViews, |
||||
|
buttonTop, |
||||
|
buttonLeft, |
||||
|
translateX, |
||||
|
activeIndex, |
||||
|
getTabStyle, |
||||
|
iconIsActive, |
||||
|
linkIsActive, |
||||
|
currentSelect, |
||||
|
scheduleIsActive, |
||||
|
getContextMenuStyle, |
||||
|
$t, |
||||
|
closeMenu, |
||||
|
onMounted, |
||||
|
onMouseenter, |
||||
|
onMouseleave, |
||||
|
transformI18n |
||||
|
}; |
||||
|
} |
@ -0,0 +1,37 @@ |
|||||
|
import { useNav } from "./useNav"; |
||||
|
import { useI18n } from "vue-i18n"; |
||||
|
import { useRoute } from "vue-router"; |
||||
|
import { watch, type Ref } from "vue"; |
||||
|
|
||||
|
export function useTranslationLang(ref?: Ref) { |
||||
|
const { $storage, changeTitle, handleResize } = useNav(); |
||||
|
const { locale, t } = useI18n(); |
||||
|
const route = useRoute(); |
||||
|
|
||||
|
function translationCh() { |
||||
|
$storage.locale = { locale: "zh" }; |
||||
|
locale.value = "zh"; |
||||
|
ref && handleResize(ref.value); |
||||
|
} |
||||
|
|
||||
|
function translationEn() { |
||||
|
$storage.locale = { locale: "en" }; |
||||
|
locale.value = "en"; |
||||
|
ref && handleResize(ref.value); |
||||
|
} |
||||
|
|
||||
|
watch( |
||||
|
() => locale.value, |
||||
|
() => { |
||||
|
changeTitle(route.meta); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
return { |
||||
|
t, |
||||
|
route, |
||||
|
locale, |
||||
|
translationCh, |
||||
|
translationEn |
||||
|
}; |
||||
|
} |
@ -1,84 +0,0 @@ |
|||||
/* 动态改变element-plus主题色 */ |
|
||||
import rgbHex from "rgb-hex"; |
|
||||
import epCss from "./element.scss"; |
|
||||
import { TinyColor } from "@ctrl/tinycolor"; |
|
||||
import { convert } from "css-color-function"; |
|
||||
|
|
||||
// 色值表
|
|
||||
const formula = { |
|
||||
"shade-1": "color(primary shade(10%))", |
|
||||
"light-1": "color(primary tint(10%))", |
|
||||
"light-2": "color(primary tint(20%))", |
|
||||
"light-3": "color(primary tint(30%))", |
|
||||
"light-4": "color(primary tint(40%))", |
|
||||
"light-5": "color(primary tint(50%))", |
|
||||
"light-6": "color(primary tint(60%))", |
|
||||
"light-7": "color(primary tint(70%))", |
|
||||
"light-8": "color(primary tint(80%))", |
|
||||
"light-9": "color(primary tint(90%))" |
|
||||
}; |
|
||||
|
|
||||
// 把生成的样式表写入到style中
|
|
||||
export const writeNewStyle = (newStyle: string): void => { |
|
||||
const style = window.document.createElement("style"); |
|
||||
style.innerText = newStyle; |
|
||||
window.document.head.appendChild(style); |
|
||||
}; |
|
||||
|
|
||||
// 根据主题色,生成最新的样式表
|
|
||||
export const createNewStyle = ( |
|
||||
primaryStyle: Record<string, any> |
|
||||
): Record<string, any> => { |
|
||||
// 根据主色生成色值表
|
|
||||
const colors = createColors(primaryStyle); |
|
||||
// 在当前ep的默认样式表中标记需要替换的色值
|
|
||||
let cssText = getStyleTemplate(epCss); |
|
||||
// 遍历生成的色值表,在 默认样式表 进行全局替换
|
|
||||
Object.keys(colors).forEach(key => { |
|
||||
cssText = cssText.replace( |
|
||||
new RegExp("(:|\\s+)" + key, "g"), |
|
||||
"$1" + colors[key] |
|
||||
); |
|
||||
}); |
|
||||
return cssText; |
|
||||
}; |
|
||||
|
|
||||
export const createColors = ( |
|
||||
primary: Record<string, any> |
|
||||
): Record<string, any> => { |
|
||||
if (!primary) return; |
|
||||
const colors = { |
|
||||
primary |
|
||||
}; |
|
||||
Object.keys(formula).forEach(key => { |
|
||||
const value = formula[key].replace(/primary/, primary); |
|
||||
colors[key] = "#" + rgbHex(convert(value)); |
|
||||
}); |
|
||||
return colors; |
|
||||
}; |
|
||||
|
|
||||
const getStyleTemplate = (data: Record<string, any>): Record<string, any> => { |
|
||||
const colorMap = { |
|
||||
"#3a8ee6": "shade-1", |
|
||||
"#409eff": "primary", |
|
||||
"#53a8ff": "light-1", |
|
||||
"#66b1ff": "light-2", |
|
||||
"#79bbff": "light-3", |
|
||||
"#8cc5ff": "light-4", |
|
||||
"#a0cfff": "light-5", |
|
||||
"#b3d8ff": "light-6", |
|
||||
"#c6e2ff": "light-7", |
|
||||
"#d9ecff": "light-8", |
|
||||
"#ecf5ff": "light-9" |
|
||||
}; |
|
||||
Object.keys(colorMap).forEach(key => { |
|
||||
const value = colorMap[key]; |
|
||||
data = data.replace(new RegExp(key, "ig"), value); |
|
||||
}); |
|
||||
return data; |
|
||||
}; |
|
||||
|
|
||||
// 自动计算hover和active颜色 https://element-plus.gitee.io/zh-CN/component/button.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%A2%9C%E8%89%B2-%E6%B5%8B%E8%AF%95%E7%89%88
|
|
||||
export const shadeBgColor = (color: string): string => { |
|
||||
return new TinyColor(color).shade(10).toString(); |
|
||||
}; |
|
@ -1,2 +0,0 @@ |
|||||
/* 通过scss模块本地导入element-plus的全局样式文件,解决vite2.7.13版本后使用 import epCss from "element-plus/dist/index.css",打包后加载不到样式的问题 */ |
|
||||
@import "element-plus/dist/index.css"; |
|
@ -0,0 +1,41 @@ |
|||||
|
import type { App } from "vue"; |
||||
|
import * as echarts from "echarts/core"; |
||||
|
import { SVGRenderer } from "echarts/renderers"; |
||||
|
import { PieChart, BarChart, LineChart } from "echarts/charts"; |
||||
|
import { |
||||
|
GridComponent, |
||||
|
TitleComponent, |
||||
|
LegendComponent, |
||||
|
GraphicComponent, |
||||
|
ToolboxComponent, |
||||
|
TooltipComponent, |
||||
|
DataZoomComponent, |
||||
|
VisualMapComponent |
||||
|
} from "echarts/components"; |
||||
|
|
||||
|
const { use } = echarts; |
||||
|
|
||||
|
use([ |
||||
|
PieChart, |
||||
|
BarChart, |
||||
|
LineChart, |
||||
|
SVGRenderer, |
||||
|
GridComponent, |
||||
|
TitleComponent, |
||||
|
LegendComponent, |
||||
|
GraphicComponent, |
||||
|
ToolboxComponent, |
||||
|
TooltipComponent, |
||||
|
DataZoomComponent, |
||||
|
VisualMapComponent |
||||
|
]); |
||||
|
|
||||
|
/** |
||||
|
* @description 按需引入echarts |
||||
|
* @see {@link https://echarts.apache.org/handbook/zh/basics/import#%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5-echarts-%E5%9B%BE%E8%A1%A8%E5%92%8C%E7%BB%84%E4%BB%B6}
|
||||
|
*/ |
||||
|
export function useEcharts(app: App) { |
||||
|
app.config.globalProperties.$echarts = echarts; |
||||
|
} |
||||
|
|
||||
|
export default echarts; |
@ -0,0 +1,6 @@ |
|||||
|
@import "vxe-table/styles/variable.scss"; |
||||
|
@import "vxe-table/styles/modules.scss"; |
||||
|
|
||||
|
i { |
||||
|
border-color: initial; |
||||
|
} |
@ -0,0 +1,114 @@ |
|||||
|
import "xe-utils"; |
||||
|
import "./index.scss"; |
||||
|
import XEUtils from "xe-utils"; |
||||
|
import { App, unref } from "vue"; |
||||
|
import { i18n } from "/@/plugins/i18n"; |
||||
|
import "font-awesome/css/font-awesome.min.css"; |
||||
|
import zh from "vxe-table/lib/locale/lang/zh-CN"; |
||||
|
import en from "vxe-table/lib/locale/lang/en-US"; |
||||
|
|
||||
|
import { |
||||
|
// 核心
|
||||
|
VXETable, |
||||
|
// 表格功能
|
||||
|
Icon, |
||||
|
Filter, |
||||
|
Edit, |
||||
|
Menu, |
||||
|
Export, |
||||
|
Keyboard, |
||||
|
Validator, |
||||
|
// 可选组件
|
||||
|
Column, |
||||
|
Colgroup, |
||||
|
Grid, |
||||
|
Tooltip, |
||||
|
Toolbar, |
||||
|
Pager, |
||||
|
Form, |
||||
|
FormItem, |
||||
|
FormGather, |
||||
|
Checkbox, |
||||
|
CheckboxGroup, |
||||
|
Radio, |
||||
|
RadioGroup, |
||||
|
RadioButton, |
||||
|
Switch, |
||||
|
Input, |
||||
|
Select, |
||||
|
Optgroup, |
||||
|
Option, |
||||
|
Textarea, |
||||
|
Button, |
||||
|
Modal, |
||||
|
List, |
||||
|
Pulldown, |
||||
|
// 表格
|
||||
|
Table |
||||
|
} from "vxe-table"; |
||||
|
|
||||
|
// 全局默认参数
|
||||
|
VXETable.setup({ |
||||
|
size: "medium", |
||||
|
version: 0, |
||||
|
zIndex: 1002, |
||||
|
table: { |
||||
|
// 自动监听父元素的变化去重新计算表格
|
||||
|
autoResize: true, |
||||
|
// 鼠标移到行是否要高亮显示
|
||||
|
highlightHoverRow: true |
||||
|
}, |
||||
|
input: { |
||||
|
clearable: true |
||||
|
}, |
||||
|
i18n: (key, args) => { |
||||
|
return unref(i18n.global.locale) === "zh" |
||||
|
? XEUtils.toFormatString(XEUtils.get(zh, key), args) |
||||
|
: XEUtils.toFormatString(XEUtils.get(en, key), args); |
||||
|
}, |
||||
|
translate(key) { |
||||
|
const NAMESPACED = ["el.", "buttons."]; |
||||
|
if (key && NAMESPACED.findIndex(v => key.includes(v)) !== -1) { |
||||
|
return i18n.global.t.call(i18n.global.locale, key); |
||||
|
} |
||||
|
return key; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
export function useTable(app: App) { |
||||
|
app |
||||
|
.use(Icon) |
||||
|
.use(Filter) |
||||
|
.use(Edit) |
||||
|
.use(Menu) |
||||
|
.use(Export) |
||||
|
.use(Keyboard) |
||||
|
.use(Validator) |
||||
|
// 可选组件
|
||||
|
.use(Column) |
||||
|
.use(Colgroup) |
||||
|
.use(Grid) |
||||
|
.use(Tooltip) |
||||
|
.use(Toolbar) |
||||
|
.use(Pager) |
||||
|
.use(Form) |
||||
|
.use(FormItem) |
||||
|
.use(FormGather) |
||||
|
.use(Checkbox) |
||||
|
.use(CheckboxGroup) |
||||
|
.use(Radio) |
||||
|
.use(RadioGroup) |
||||
|
.use(RadioButton) |
||||
|
.use(Switch) |
||||
|
.use(Input) |
||||
|
.use(Select) |
||||
|
.use(Optgroup) |
||||
|
.use(Option) |
||||
|
.use(Textarea) |
||||
|
.use(Button) |
||||
|
.use(Modal) |
||||
|
.use(List) |
||||
|
.use(Pulldown) |
||||
|
// 安装表格
|
||||
|
.use(Table); |
||||
|
} |
@ -1,28 +1,163 @@ |
|||||
/* 暗黑模式 */ |
|
||||
[data-theme="dark"] { |
|
||||
filter: invert(0.9) hue-rotate(180deg); |
|
||||
|
@import "element-plus/theme-chalk/src/dark/css-vars.scss"; |
||||
|
|
||||
img, |
|
||||
.icon-svg, |
|
||||
.login-container { |
|
||||
filter: invert(1) hue-rotate(180deg); |
|
||||
|
/* 暗黑模式适配 */ |
||||
|
html.dark { |
||||
|
/* 自定义深色背景颜色 */ |
||||
|
// --el-bg-color: #020409; |
||||
|
$border-style: #303030; |
||||
|
$color-white: #fff; |
||||
|
|
||||
|
.navbar, |
||||
|
.tags-view, |
||||
|
.contextmenu, |
||||
|
.sidebar-container, |
||||
|
.horizontal-header, |
||||
|
.sidebar-logo-container, |
||||
|
.horizontal-header .el-sub-menu__title, |
||||
|
.horizontal-header .submenu-title-noDropdown { |
||||
|
background: var(--el-bg-color) !important; |
||||
|
} |
||||
|
|
||||
|
.app-main { |
||||
|
background: #020409 !important; |
||||
|
} |
||||
|
|
||||
|
.frame { |
||||
|
filter: invert(0.9) hue-rotate(180deg); |
||||
|
} |
||||
|
|
||||
|
.ant-tabs { |
||||
|
background: var(--el-bg-color); |
||||
|
color: $color-white; |
||||
|
} |
||||
|
|
||||
|
/* 标签页 */ |
||||
|
.tags-view { |
||||
|
.arrow-left, |
||||
|
.arrow-right { |
||||
|
box-shadow: none; |
||||
|
} |
||||
|
|
||||
|
.arrow-right { |
||||
|
border-left: 1px solid $border-style; |
||||
|
} |
||||
|
|
||||
|
.arrow-left, |
||||
|
.arrow-right, |
||||
|
.right-button li { |
||||
|
border-right: 1px solid $border-style; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* vxe-table */ |
||||
|
.vxe-table--header-wrapper, |
||||
|
.vxe-table--body-wrapper { |
||||
|
color: var(--el-text-color-primary); |
||||
|
background: var(--el-bg-color) !important; |
||||
|
} |
||||
|
|
||||
|
.vxe-table--render-default.border--full .vxe-header--column, |
||||
|
.vxe-table--render-default.border--full .vxe-body--column, |
||||
|
.vxe-table--render-default.border--full .vxe-footer--column { |
||||
|
background-image: linear-gradient( |
||||
|
var(--el-border-color-lighter), |
||||
|
var(--el-border-color-lighter) |
||||
|
), |
||||
|
linear-gradient( |
||||
|
var(--el-border-color-lighter), |
||||
|
var(--el-border-color-lighter) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/* 表头 */ |
||||
|
.vxe-table--header-wrapper { |
||||
|
background: #262727 !important; |
||||
|
} |
||||
|
|
||||
|
.vxe-table--render-wrapper, |
||||
|
.vxe-table--main-wrapper { |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.vxe-pager.is--perfect, |
||||
|
.vxe-table--render-default .vxe-table--border-line { |
||||
|
border: 1px solid var(--el-border-color-lighter); |
||||
|
} |
||||
|
|
||||
|
.vxe-table--header-border-line { |
||||
|
border-bottom: 1px solid var(--el-border-color-lighter) !important; |
||||
|
} |
||||
|
|
||||
|
.vxe-body--row.row--hover, |
||||
|
.vxe-pager { |
||||
|
background-color: #262727; |
||||
|
} |
||||
|
|
||||
|
.vxe-input--inner, |
||||
|
.vxe-pager .vxe-pager--jump-prev, |
||||
|
.vxe-pager .vxe-pager--prev-btn, |
||||
|
.vxe-pager .vxe-pager--next-btn, |
||||
|
.vxe-pager .vxe-pager--jump-next, |
||||
|
.vxe-pager .vxe-pager--num-btn, |
||||
|
.vxe-pager .vxe-pager--jump .vxe-pager--goto { |
||||
|
background-color: transparent; |
||||
|
color: var(--el-text-color-primary); |
||||
|
// outline: none !important; |
||||
|
} |
||||
|
|
||||
|
.vxe-select-option--wrapper { |
||||
|
background: var(--el-bg-color) !important; |
||||
|
} |
||||
|
|
||||
|
.vxe-select-option:not(.is--disabled).is--hover { |
||||
|
background: var(--el-color-primary-light-6) !important; |
||||
|
} |
||||
|
|
||||
|
.vxe-modal--wrapper.type--modal .vxe-modal--box, |
||||
|
.vxe-modal--wrapper.type--alert .vxe-modal--box, |
||||
|
.vxe-modal--wrapper.type--confirm .vxe-modal--box, |
||||
|
.vxe-form { |
||||
|
background: var(--el-bg-color) !important; |
||||
|
} |
||||
|
|
||||
|
.vxe-modal--box, |
||||
|
.vxe-modal--header { |
||||
|
border: none; |
||||
|
background: var(--el-bg-color) !important; |
||||
} |
} |
||||
|
|
||||
// element plus |
|
||||
.el-radio-button__original-radio:checked + .el-radio-button__inner, |
|
||||
.el-image-viewer__close, |
|
||||
.el-image-viewer__actions__inner, |
|
||||
.el-image-viewer__next, |
|
||||
.el-image-viewer__prev { |
|
||||
color: #000 !important; |
|
||||
|
.vxe-modal--title, |
||||
|
.vxe-button--content { |
||||
|
color: var(--el-text-color-primary); |
||||
} |
} |
||||
|
|
||||
.el-overlay { |
|
||||
background-color: rgb(0 0 0 / 5%) !important; |
|
||||
|
.vxe-button.type--button.size--medium:hover { |
||||
|
background: var(--el-color-primary) !important; |
||||
} |
} |
||||
|
|
||||
.el-drawer { |
|
||||
box-shadow: 0 8px 10px -5px rgb(0 0 0 / 1%), 0 16px 24px 2px rgb(0 0 0 / 2%), |
|
||||
0 6px 30px 5px rgb(0 0 0 / 1%); |
|
||||
|
/* 项目配置面板 */ |
||||
|
.right-panel-items { |
||||
|
.el-divider__text { |
||||
|
--el-bg-color: var(--el-bg-color); |
||||
|
} |
||||
|
.el-divider--horizontal { |
||||
|
border-top: none; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* element-plus */ |
||||
|
.el-table__cell { |
||||
|
background: var(--el-bg-color); |
||||
|
} |
||||
|
.el-card { |
||||
|
--el-card-bg-color: var(--el-bg-color); |
||||
|
// border: none !important; |
||||
|
} |
||||
|
.el-backtop { |
||||
|
--el-backtop-bg-color: var(--el-color-primary-light-9); |
||||
|
--el-backtop-hover-bg-color: var(--el-color-primary); |
||||
|
} |
||||
|
.el-dropdown-menu__item:not(.is-disabled):hover { |
||||
|
background: transparent; |
||||
} |
} |
||||
} |
} |
@ -0,0 +1,5 @@ |
|||||
|
### 注意 |
||||
|
|
||||
|
- [文档](https://pure-admin-utils-docs.vercel.app/) |
||||
|
- [npm](https://www.npmjs.com/package/@pureadmin/utils) |
||||
|
- vue-pure-admin 从 3.3.0 版本之后(不包括 3.3.0 版本),大部分工具和 hooks 都集成到了[@pureadmin/utils](https://pure-admin-utils-docs.vercel.app/) |
@ -1,39 +0,0 @@ |
|||||
import { unref } from "vue"; |
|
||||
import type { Ref } from "vue"; |
|
||||
|
|
||||
type FunctionArgs<Args extends any[] = any[], Return = void> = ( |
|
||||
...args: Args |
|
||||
) => Return; |
|
||||
|
|
||||
type MaybeRef<T> = T | Ref<T>; |
|
||||
|
|
||||
// 延迟函数
|
|
||||
export const delay = (timeout: number) => |
|
||||
new Promise(resolve => setTimeout(resolve, timeout)); |
|
||||
|
|
||||
/** |
|
||||
* 防抖函数 |
|
||||
* @param fn 函数 |
|
||||
* @param timeout 延迟时间 |
|
||||
* @param immediate 是否立即执行 |
|
||||
* @returns |
|
||||
*/ |
|
||||
export const debounce = <T extends FunctionArgs>( |
|
||||
fn: T, |
|
||||
timeout: MaybeRef<number> = 200, |
|
||||
immediate = false |
|
||||
) => { |
|
||||
let timmer: TimeoutHandle; |
|
||||
const wait = unref(timeout); |
|
||||
return () => { |
|
||||
timmer && clearTimeout(timmer); |
|
||||
if (immediate) { |
|
||||
if (!timmer) { |
|
||||
fn(); |
|
||||
} |
|
||||
timmer = setTimeout(() => (timmer = null), wait); |
|
||||
} else { |
|
||||
timmer = setTimeout(fn, wait); |
|
||||
} |
|
||||
}; |
|
||||
}; |
|
@ -1,37 +0,0 @@ |
|||||
interface deviceInter { |
|
||||
match: Fn; |
|
||||
} |
|
||||
|
|
||||
interface BrowserInter { |
|
||||
browser: string; |
|
||||
version: string; |
|
||||
} |
|
||||
|
|
||||
// 检测设备类型(手机返回true,反之)
|
|
||||
export const deviceDetection = () => { |
|
||||
const sUserAgent: deviceInter = navigator.userAgent.toLowerCase(); |
|
||||
// const bIsIpad = sUserAgent.match(/ipad/i) == "ipad";
|
|
||||
const bIsIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os"; |
|
||||
const bIsMidp = sUserAgent.match(/midp/i) == "midp"; |
|
||||
const bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4"; |
|
||||
const bIsUc = sUserAgent.match(/ucweb/i) == "ucweb"; |
|
||||
const bIsAndroid = sUserAgent.match(/android/i) == "android"; |
|
||||
const bIsCE = sUserAgent.match(/windows ce/i) == "windows ce"; |
|
||||
const bIsWM = sUserAgent.match(/windows mobile/i) == "windows mobile"; |
|
||||
return ( |
|
||||
bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
// 获取浏览器型号以及版本
|
|
||||
export const getBrowserInfo = () => { |
|
||||
const ua = navigator.userAgent.toLowerCase(); |
|
||||
const re = /(msie|firefox|chrome|opera|version).*?([\d.]+)/; |
|
||||
const m = ua.match(re); |
|
||||
const Sys: BrowserInter = { |
|
||||
browser: m[1].replace(/version/, "'safari"), |
|
||||
version: m[2] |
|
||||
}; |
|
||||
|
|
||||
return Sys; |
|
||||
}; |
|
@ -1,118 +0,0 @@ |
|||||
const toString = Object.prototype.toString; |
|
||||
|
|
||||
export function is(val: unknown, type: string) { |
|
||||
return toString.call(val) === `[object ${type}]`; |
|
||||
} |
|
||||
|
|
||||
export function isDef<T = unknown>(val?: T): val is T { |
|
||||
return typeof val !== "undefined"; |
|
||||
} |
|
||||
|
|
||||
export function isUnDef<T = unknown>(val?: T): val is T { |
|
||||
return !isDef(val); |
|
||||
} |
|
||||
|
|
||||
export function isObject(val: any): val is Record<any, any> { |
|
||||
return val !== null && is(val, "Object"); |
|
||||
} |
|
||||
|
|
||||
export function isEmpty<T = unknown>(val: T): val is T { |
|
||||
if (isArray(val) || isString(val)) { |
|
||||
return val.length === 0; |
|
||||
} |
|
||||
|
|
||||
if (val instanceof Map || val instanceof Set) { |
|
||||
return val.size === 0; |
|
||||
} |
|
||||
|
|
||||
if (isObject(val)) { |
|
||||
return Object.keys(val).length === 0; |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
export function isDate(val: unknown): val is Date { |
|
||||
return is(val, "Date"); |
|
||||
} |
|
||||
|
|
||||
export function isNull(val: unknown): val is null { |
|
||||
return val === null; |
|
||||
} |
|
||||
|
|
||||
export function isNullAndUnDef(val: unknown): val is null | undefined { |
|
||||
return isUnDef(val) && isNull(val); |
|
||||
} |
|
||||
|
|
||||
export function isNullOrUnDef(val: unknown): val is null | undefined { |
|
||||
return isUnDef(val) || isNull(val); |
|
||||
} |
|
||||
|
|
||||
export function isNumber(val: unknown): val is number { |
|
||||
return is(val, "Number"); |
|
||||
} |
|
||||
|
|
||||
export function isPromise<T = any>(val: unknown): val is Promise<T> { |
|
||||
return ( |
|
||||
is(val, "Promise") && |
|
||||
isObject(val) && |
|
||||
isFunction(val.then) && |
|
||||
isFunction(val.catch) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
export function isString(val: unknown): val is string { |
|
||||
return is(val, "String"); |
|
||||
} |
|
||||
|
|
||||
export function isFunction(val: unknown): val is Function { |
|
||||
return typeof val === "function"; |
|
||||
} |
|
||||
|
|
||||
export function isBoolean(val: unknown): val is boolean { |
|
||||
return is(val, "Boolean"); |
|
||||
} |
|
||||
|
|
||||
export function isRegExp(val: unknown): val is RegExp { |
|
||||
return is(val, "RegExp"); |
|
||||
} |
|
||||
|
|
||||
export function isArray(val: any): val is Array<any> { |
|
||||
return val && Array.isArray(val); |
|
||||
} |
|
||||
|
|
||||
export function isWindow(val: any): val is Window { |
|
||||
return typeof window !== "undefined" && is(val, "Window"); |
|
||||
} |
|
||||
|
|
||||
export function isElement(val: unknown): val is Element { |
|
||||
return isObject(val) && !!val.tagName; |
|
||||
} |
|
||||
|
|
||||
export const isServer = typeof window === "undefined"; |
|
||||
|
|
||||
export const isClient = !isServer; |
|
||||
|
|
||||
/** url链接正则 */ |
|
||||
export function isUrl<T>(value: T): boolean { |
|
||||
const reg = |
|
||||
// eslint-disable-next-line no-useless-escape
|
|
||||
/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; |
|
||||
// @ts-expect-error
|
|
||||
return reg.test(value); |
|
||||
} |
|
||||
|
|
||||
/** 手机号码正则 */ |
|
||||
export function isPhone<T>(value: T): boolean { |
|
||||
const reg = |
|
||||
/^[1](([3][0-9])|([4][0,1,4-9])|([5][0-3,5-9])|([6][2,5,6,7])|([7][0-8])|([8][0-9])|([9][0-3,5-9]))[0-9]{8}$/; |
|
||||
// @ts-expect-error
|
|
||||
return reg.test(value); |
|
||||
} |
|
||||
|
|
||||
/** 邮箱正则 */ |
|
||||
export function isEmail<T>(value: T): boolean { |
|
||||
const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/; |
|
||||
// @ts-expect-error
|
|
||||
return reg.test(value); |
|
||||
} |
|
@ -1,13 +0,0 @@ |
|||||
export const openLink = <T>(link: T): void => { |
|
||||
const $a: HTMLElement = document.createElement("a"); |
|
||||
// @ts-expect-error
|
|
||||
$a.setAttribute("href", link); |
|
||||
$a.setAttribute("target", "_blank"); |
|
||||
$a.setAttribute("rel", "noreferrer noopener"); |
|
||||
$a.setAttribute("id", "external"); |
|
||||
document.getElementById("external") && |
|
||||
document.body.removeChild(document.getElementById("external")); |
|
||||
document.body.appendChild($a); |
|
||||
$a.click(); |
|
||||
$a.remove(); |
|
||||
}; |
|
@ -1,54 +0,0 @@ |
|||||
interface ProxyLoader { |
|
||||
loadCss(src: string): any; |
|
||||
loadScript(src: string): Promise<any>; |
|
||||
loadScriptConcurrent(src: Array<string>): Promise<any>; |
|
||||
} |
|
||||
|
|
||||
class loaderProxy implements ProxyLoader { |
|
||||
constructor() {} |
|
||||
|
|
||||
protected scriptLoaderCache: Array<string> = []; |
|
||||
|
|
||||
public loadCss = (src: string): any => { |
|
||||
const element: HTMLLinkElement = document.createElement("link"); |
|
||||
element.rel = "stylesheet"; |
|
||||
element.href = src; |
|
||||
document.body.appendChild(element); |
|
||||
}; |
|
||||
|
|
||||
public loadScript = async (src: string): Promise<any> => { |
|
||||
if (this.scriptLoaderCache.includes(src)) { |
|
||||
return src; |
|
||||
} else { |
|
||||
const element: HTMLScriptElement = document.createElement("script"); |
|
||||
element.src = src; |
|
||||
document.body.appendChild(element); |
|
||||
element.onload = () => { |
|
||||
return this.scriptLoaderCache.push(src); |
|
||||
}; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
public loadScriptConcurrent = async ( |
|
||||
srcList: Array<string> |
|
||||
): Promise<any> => { |
|
||||
if (Array.isArray(srcList)) { |
|
||||
const len: number = srcList.length; |
|
||||
if (len > 0) { |
|
||||
let count = 0; |
|
||||
srcList.map(src => { |
|
||||
if (src) { |
|
||||
this.loadScript(src).then(() => { |
|
||||
count++; |
|
||||
if (count === len) { |
|
||||
return; |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
export const loader = new loaderProxy(); |
|
@ -1,38 +0,0 @@ |
|||||
import { ElMessage } from "element-plus"; |
|
||||
|
|
||||
// 消息
|
|
||||
const Message = (message: string): any => { |
|
||||
return ElMessage({ |
|
||||
showClose: true, |
|
||||
message |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
// 成功
|
|
||||
const successMessage = (message: string): any => { |
|
||||
return ElMessage({ |
|
||||
showClose: true, |
|
||||
message, |
|
||||
type: "success" |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
// 警告
|
|
||||
const warnMessage = (message: string): any => { |
|
||||
return ElMessage({ |
|
||||
showClose: true, |
|
||||
message, |
|
||||
type: "warning" |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
// 失败
|
|
||||
const errorMessage = (message: string): any => { |
|
||||
return ElMessage({ |
|
||||
showClose: true, |
|
||||
message, |
|
||||
type: "error" |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
export { Message, successMessage, warnMessage, errorMessage }; |
|
@ -1,57 +0,0 @@ |
|||||
import type { FunctionArgs } from "@vueuse/core"; |
|
||||
|
|
||||
export const hasClass = (ele: RefType<any>, cls: string): any => { |
|
||||
return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)")); |
|
||||
}; |
|
||||
|
|
||||
export const addClass = ( |
|
||||
ele: RefType<any>, |
|
||||
cls: string, |
|
||||
extracls?: string |
|
||||
): any => { |
|
||||
if (!hasClass(ele, cls)) ele.className += " " + cls; |
|
||||
if (extracls) { |
|
||||
if (!hasClass(ele, extracls)) ele.className += " " + extracls; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
export const removeClass = ( |
|
||||
ele: RefType<any>, |
|
||||
cls: string, |
|
||||
extracls?: string |
|
||||
): any => { |
|
||||
if (hasClass(ele, cls)) { |
|
||||
const reg = new RegExp("(\\s|^)" + cls + "(\\s|$)"); |
|
||||
ele.className = ele.className.replace(reg, " ").trim(); |
|
||||
} |
|
||||
if (extracls) { |
|
||||
if (hasClass(ele, extracls)) { |
|
||||
const regs = new RegExp("(\\s|^)" + extracls + "(\\s|$)"); |
|
||||
ele.className = ele.className.replace(regs, " ").trim(); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
export const toggleClass = ( |
|
||||
flag: boolean, |
|
||||
clsName: string, |
|
||||
target?: RefType<any> |
|
||||
): any => { |
|
||||
const targetEl = target || document.body; |
|
||||
let { className } = targetEl; |
|
||||
className = className.replace(clsName, ""); |
|
||||
targetEl.className = flag ? `${className} ${clsName} ` : className; |
|
||||
}; |
|
||||
|
|
||||
export function useRafThrottle<T extends FunctionArgs>(fn: T): T { |
|
||||
let locked = false; |
|
||||
// @ts-ignore
|
|
||||
return function (...args) { |
|
||||
if (locked) return; |
|
||||
locked = true; |
|
||||
window.requestAnimationFrame(() => { |
|
||||
fn.apply(this, args); |
|
||||
locked = false; |
|
||||
}); |
|
||||
}; |
|
||||
} |
|
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue