
134 changed files with 3594 additions and 3390 deletions
-
23.eslintrc.js
-
3.gitignore
-
4.vscode/extensions.json
-
3.vscode/settings.json
-
2README.en-US.md
-
6README.md
-
2build/index.ts
-
59build/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
-
6src/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
-
110src/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
-
392src/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
-
14src/plugins/i18n.ts
-
6src/plugins/vxe-table/index.scss
-
114src/plugins/vxe-table/index.ts
-
102src/router/index.ts
-
5src/router/modules/error.ts
-
9src/router/modules/home.ts
-
7src/router/modules/remaining.ts
-
1src/router/types.ts
-
53src/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
-
171src/style/dark.scss
-
9src/style/element-plus.scss
-
9src/style/index.scss
-
12src/style/mixin.scss
-
227src/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
-
35src/utils/resize/index.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"; |
|||
|
|||
type Result = { |
|||
code: number; |
|||
info: Array<any>; |
|||
}; |
|||
|
|||
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 { 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 { 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; |
@ -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"] { |
|||
@import "element-plus/theme-chalk/src/dark/css-vars.scss"; |
|||
|
|||
/* 暗黑模式适配 */ |
|||
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; |
|||
} |
|||
|
|||
img, |
|||
.icon-svg, |
|||
.login-container { |
|||
filter: invert(1) hue-rotate(180deg); |
|||
/* 标签页 */ |
|||
.tags-view { |
|||
.arrow-left, |
|||
.arrow-right { |
|||
box-shadow: none; |
|||
} |
|||
|
|||
// 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; |
|||
.arrow-right { |
|||
border-left: 1px solid $border-style; |
|||
} |
|||
|
|||
.arrow-left, |
|||
.arrow-right, |
|||
.right-button li { |
|||
border-right: 1px solid $border-style; |
|||
} |
|||
} |
|||
|
|||
.el-overlay { |
|||
background-color: rgb(0 0 0 / 5%) !important; |
|||
/* vxe-table */ |
|||
.vxe-table--header-wrapper, |
|||
.vxe-table--body-wrapper { |
|||
color: var(--el-text-color-primary); |
|||
background: var(--el-bg-color) !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%); |
|||
.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; |
|||
} |
|||
|
|||
.vxe-modal--title, |
|||
.vxe-button--content { |
|||
color: var(--el-text-color-primary); |
|||
} |
|||
|
|||
.vxe-button.type--button.size--medium:hover { |
|||
background: var(--el-color-primary) !important; |
|||
} |
|||
|
|||
/* 项目配置面板 */ |
|||
.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; |
|||
}); |
|||
}; |
|||
} |
@ -1,35 +0,0 @@ |
|||
import ResizeObserver from "resize-observer-polyfill"; |
|||
|
|||
const isServer = typeof window === "undefined"; |
|||
|
|||
const resizeHandler = (entries: any[]): void => { |
|||
for (const entry of entries) { |
|||
const listeners = entry.target.__resizeListeners__ || []; |
|||
if (listeners.length) { |
|||
listeners.forEach((fn: () => any) => { |
|||
fn(); |
|||
}); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
export const addResizeListener = (element: any, fn: () => any): any => { |
|||
if (isServer) return; |
|||
if (!element.__resizeListeners__) { |
|||
element.__resizeListeners__ = []; |
|||
element.__ro__ = new ResizeObserver(resizeHandler); |
|||
element.__ro__.observe(element); |
|||
} |
|||
element.__resizeListeners__.push(fn); |
|||
}; |
|||
|
|||
export const removeResizeListener = (element: any, fn: () => any): any => { |
|||
if (!element || !element.__resizeListeners__) return; |
|||
element.__resizeListeners__.splice( |
|||
element.__resizeListeners__.indexOf(fn), |
|||
1 |
|||
); |
|||
if (!element.__resizeListeners__.length) { |
|||
element.__ro__.disconnect(); |
|||
} |
|||
}; |
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue