mirror of
https://github.com/pure-admin/pure-admin-thin.git
synced 2025-12-14 14:20:31 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51fd06c6a1 | ||
|
|
4bb8647990 | ||
|
|
a43d5ce865 | ||
|
|
aea8605a60 | ||
|
|
eb9d1e8238 | ||
|
|
138e0fd2e4 | ||
|
|
526023e0b0 | ||
|
|
885cbf2d9f |
85
build/info.ts
Normal file
85
build/info.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { readdir, stat } from "fs";
|
||||
import type { Plugin } from "vite";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import { sum } from "lodash-unified";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import { green, blue, bold } from "picocolors";
|
||||
dayjs.extend(duration);
|
||||
|
||||
const staticPath = "dist";
|
||||
const fileListTotal: number[] = [];
|
||||
|
||||
const recursiveDirectory = (folder: string, callback: Function): void => {
|
||||
readdir(folder, (err, files: string[]) => {
|
||||
if (err) throw err;
|
||||
let count = 0;
|
||||
const checkEnd = () => {
|
||||
++count == files.length && callback();
|
||||
};
|
||||
files.forEach((item: string) => {
|
||||
stat(folder + "/" + item, async (err, stats) => {
|
||||
if (err) throw err;
|
||||
if (stats.isFile()) {
|
||||
fileListTotal.push(stats.size);
|
||||
checkEnd();
|
||||
} else if (stats.isDirectory()) {
|
||||
recursiveDirectory(`${staticPath}/${item}/`, checkEnd);
|
||||
}
|
||||
});
|
||||
});
|
||||
files.length === 0 && callback();
|
||||
});
|
||||
};
|
||||
|
||||
const formatBytes = (a: number, b?: number): string => {
|
||||
if (0 == a) return "0 Bytes";
|
||||
const c = 1024,
|
||||
d = b || 2,
|
||||
e = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
|
||||
f = Math.floor(Math.log(a) / Math.log(c));
|
||||
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + " " + e[f];
|
||||
};
|
||||
|
||||
export function viteBuildInfo(): Plugin {
|
||||
let config: { command: string };
|
||||
let startTime: Dayjs;
|
||||
let endTime: Dayjs;
|
||||
return {
|
||||
name: "vite:buildInfo",
|
||||
configResolved(resolvedConfig: { command: string }) {
|
||||
config = resolvedConfig;
|
||||
},
|
||||
buildStart() {
|
||||
if (config.command === "build") {
|
||||
startTime = dayjs(new Date());
|
||||
}
|
||||
},
|
||||
closeBundle() {
|
||||
if (config.command === "build") {
|
||||
console.log(
|
||||
bold(
|
||||
green(
|
||||
`👏欢迎使用${blue(
|
||||
"[vue-pure-admin]"
|
||||
)},如果您感觉不错,记得点击后面链接给个star哦💖 https://github.com/xiaoxian521/vue-pure-admin`
|
||||
)
|
||||
)
|
||||
);
|
||||
endTime = dayjs(new Date());
|
||||
recursiveDirectory(staticPath, () => {
|
||||
console.log(
|
||||
bold(
|
||||
green(
|
||||
`恭喜打包完成🎉(总用时${dayjs
|
||||
.duration(endTime.diff(startTime))
|
||||
.format("mm分ss秒")},打包后的大小为${formatBytes(
|
||||
sum(fileListTotal)
|
||||
)})`
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { viteBuildInfo } from "./info";
|
||||
import svgLoader from "vite-svg-loader";
|
||||
import legacy from "@vitejs/plugin-legacy";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
@@ -20,6 +21,7 @@ export function getPluginsList(command, VITE_LEGACY) {
|
||||
WindiCSS(),
|
||||
// 线上环境删除console
|
||||
removeConsole(),
|
||||
viteBuildInfo(),
|
||||
// 修改layout文件夹下的文件时自动重载浏览器 解决 https://github.com/xiaoxian521/vue-pure-admin/issues/170
|
||||
liveReload(["src/layout/**/*", "src/router/**/*"]),
|
||||
// 自定义主题
|
||||
|
||||
14
index.html
14
index.html
@@ -13,20 +13,16 @@
|
||||
<body>
|
||||
<div id="app">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
font-family: "Reggae One", cursive;
|
||||
}
|
||||
|
||||
.loader,
|
||||
@@ -52,6 +48,8 @@
|
||||
transform: translateZ(0);
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
top: 0;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.loader:before,
|
||||
@@ -95,7 +93,7 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="loader">Loading...</div>
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
22
package.json
22
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pure-admin-thin",
|
||||
"version": "3.0",
|
||||
"version": "3.1.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">= 16",
|
||||
@@ -36,10 +36,12 @@
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^0.25.0",
|
||||
"css-color-function": "^1.3.3",
|
||||
"element-plus": "^2.0.2",
|
||||
"dayjs": "^1.10.7",
|
||||
"element-plus": "^2.0.3",
|
||||
"element-resize-detector": "^1.2.3",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lodash-unified": "^1.0.2",
|
||||
"mitt": "^3.0.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
@@ -51,7 +53,7 @@
|
||||
"rgb-hex": "^4.0.0",
|
||||
"vue": "^3.2.31",
|
||||
"vue-i18n": "^9.2.0-beta.30",
|
||||
"vue-router": "^4.0.12",
|
||||
"vue-router": "^4.0.13",
|
||||
"vue-types": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -64,6 +66,7 @@
|
||||
"@iconify/vue": "^3.1.3",
|
||||
"@types/element-resize-detector": "1.1.3",
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/mockjs": "1.0.3",
|
||||
"@types/node": "14.14.14",
|
||||
"@types/nprogress": "0.2.0",
|
||||
@@ -71,8 +74,8 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"@typescript-eslint/parser": "^5.10.2",
|
||||
"@vitejs/plugin-legacy": "^1.7.1",
|
||||
"@vitejs/plugin-vue": "^2.2.0",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.7",
|
||||
"@vitejs/plugin-vue": "^2.2.4",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.8",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^10.0.0",
|
||||
"@zougt/vite-plugin-theme-preprocessor": "^1.4.4",
|
||||
@@ -83,6 +86,7 @@
|
||||
"eslint-plugin-vue": "^8.4.1",
|
||||
"husky": "7.0.2",
|
||||
"lint-staged": "11.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss": "^8.4.6",
|
||||
"postcss-html": "^1.3.0",
|
||||
"postcss-import": "14.0.0",
|
||||
@@ -90,7 +94,7 @@
|
||||
"prettier": "^2.5.1",
|
||||
"pretty-quick": "3.1.1",
|
||||
"rimraf": "3.0.2",
|
||||
"rollup-plugin-visualizer": "^5.5.4",
|
||||
"rollup-plugin-visualizer": "^5.6.0",
|
||||
"sass": "^1.49.0",
|
||||
"sass-loader": "^12.4.0",
|
||||
"stylelint": "^14.3.0",
|
||||
@@ -101,15 +105,15 @@
|
||||
"stylelint-order": "^5.0.0",
|
||||
"typescript": "^4.5.5",
|
||||
"unplugin-element-plus": "^0.2.0",
|
||||
"vite": "2.7.13",
|
||||
"vite": "^2.8.6",
|
||||
"vite-plugin-live-reload": "^2.1.0",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-remove-console": "^0.0.6",
|
||||
"vite-plugin-style-import": "1.4.1",
|
||||
"vite-plugin-windicss": "^1.7.0",
|
||||
"vite-plugin-windicss": "^1.8.2",
|
||||
"vite-svg-loader": "2.2.0",
|
||||
"vue-eslint-parser": "^8.2.0",
|
||||
"windicss": "^3.4.3"
|
||||
"windicss": "^3.5.1"
|
||||
},
|
||||
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git",
|
||||
"author": "xiaoxian521",
|
||||
|
||||
460
pnpm-lock.yaml
generated
460
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Version": "3.0",
|
||||
"Version": "3.1.0",
|
||||
"Title": "PureAdmin",
|
||||
"FixedHeader": true,
|
||||
"HiddenSideBar": false,
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { iconType } from "./types";
|
||||
import { h, defineComponent, Component } from "vue";
|
||||
import { IconifyIconOffline, FontIcon } from "../index";
|
||||
|
||||
// 支持fontawesome4、5+、iconfont、remixicon、element-plus的icons、自定义svg
|
||||
export function useRenderIcon(icon: string): Component {
|
||||
/**
|
||||
* 支持fontawesome4、5+、iconfont、remixicon、element-plus的icons、自定义svg
|
||||
* @param icon 必传 string 图标
|
||||
* @param attrs 可选 iconType 属性
|
||||
* @returns Component
|
||||
*/
|
||||
export function useRenderIcon(icon: string, attrs?: iconType): Component {
|
||||
// iconfont
|
||||
const ifReg = /^IF-/;
|
||||
// typeof icon === "function" 属于SVG
|
||||
@@ -19,7 +25,8 @@ export function useRenderIcon(icon: string): Component {
|
||||
render() {
|
||||
return h(FontIcon, {
|
||||
icon: iconName,
|
||||
iconType
|
||||
iconType,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -31,7 +38,8 @@ export function useRenderIcon(icon: string): Component {
|
||||
name: "Icon",
|
||||
render() {
|
||||
return h(IconifyIconOffline, {
|
||||
icon: icon
|
||||
icon: icon,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -51,9 +51,11 @@ addIcon("notebook", Notebook);
|
||||
import arrowRightSLine from "@iconify-icons/ri/arrow-right-s-line";
|
||||
import arrowLeftSLine from "@iconify-icons/ri/arrow-left-s-line";
|
||||
import logoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
||||
import nodeTree from "@iconify-icons/ri/node-tree";
|
||||
addIcon("arrow-right-s-line", arrowRightSLine);
|
||||
addIcon("arrow-left-s-line", arrowLeftSLine);
|
||||
addIcon("logout-circle-r-line", logoutCircleRLine);
|
||||
addIcon("node-tree", nodeTree);
|
||||
|
||||
// Font Awesome 4
|
||||
import faUser from "@iconify-icons/fa/user";
|
||||
|
||||
18
src/components/ReIcon/src/types.ts
Normal file
18
src/components/ReIcon/src/types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface iconType {
|
||||
// iconify (https://docs.iconify.design/icon-components/vue/#properties)
|
||||
inline?: boolean;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
horizontalFlip?: boolean;
|
||||
verticalFlip?: boolean;
|
||||
flip?: string;
|
||||
rotate?: number | string;
|
||||
color?: string;
|
||||
horizontalAlign?: boolean;
|
||||
verticalAlign?: boolean;
|
||||
align?: string;
|
||||
onLoad?: Function;
|
||||
|
||||
// all icon
|
||||
style?: object;
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
defineComponent,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import { RouterView } from "vue-router";
|
||||
import backTop from "/@/assets/svg/back_top.svg?component";
|
||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import rgbHex from "rgb-hex";
|
||||
import { find } from "lodash-es";
|
||||
import { find } from "lodash-unified";
|
||||
import { getConfig } from "/@/config";
|
||||
import { useRouter } from "vue-router";
|
||||
import panel from "../panel/index.vue";
|
||||
@@ -111,7 +111,7 @@ function storageConfigureChange<T>(key: string, val: T): void {
|
||||
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
|
||||
const targetEl = target || document.body;
|
||||
let { className } = targetEl;
|
||||
className = className.replace(clsName, "");
|
||||
className = className.replace(clsName, "").trim();
|
||||
targetEl.className = flag ? `${className} ${clsName} ` : className;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { isEqual } from "lodash-es";
|
||||
import { isEqual } from "lodash-unified";
|
||||
import { transformI18n } from "/@/plugins/i18n";
|
||||
import { getParentPaths, findRouteByPath } from "/@/router/utils";
|
||||
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
|
||||
|
||||
@@ -45,6 +45,13 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
menuSelect(route.path, routers);
|
||||
}
|
||||
);
|
||||
|
||||
function translationCh() {
|
||||
instance.locale = { locale: "zh" };
|
||||
locale.value = "zh";
|
||||
|
||||
@@ -111,6 +111,10 @@ function hasOneShowingChild(
|
||||
return true;
|
||||
});
|
||||
|
||||
if (showingChildren[0]?.meta?.showParent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (showingChildren.length === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,10 @@ onBeforeMount(() => {
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => getSubMenuData(route.path)
|
||||
() => {
|
||||
getSubMenuData(route.path);
|
||||
menuSelect(route.path, routers);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ import closeRight from "/@/assets/svg/close_right.svg?component";
|
||||
|
||||
import { emitter } from "/@/utils/mitt";
|
||||
import { $t as t } from "/@/plugins/i18n";
|
||||
import { isEqual, isEmpty } from "lodash-es";
|
||||
import { transformI18n } from "/@/plugins/i18n";
|
||||
import { storageLocal } from "/@/utils/storage";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { isEqual, isEmpty } from "lodash-unified";
|
||||
import { RouteConfigs, tagsViewsType } from "../../types";
|
||||
import { useSettingStoreHook } from "/@/store/modules/settings";
|
||||
import { handleAliveRoute, delAliveRoutes } from "/@/router/utils";
|
||||
|
||||
63
src/layout/frameView.vue
Normal file
63
src/layout/frameView.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="frame" v-loading="loading">
|
||||
<iframe :src="frameSrc" class="frame-iframe" ref="frameRef"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref, unref, onMounted, nextTick } from "vue";
|
||||
|
||||
const loading = ref(false);
|
||||
const currentRoute = useRoute();
|
||||
const frameSrc = ref<string>("");
|
||||
const frameRef = ref<HTMLElement | null>(null);
|
||||
|
||||
if (unref(currentRoute.meta)?.frameSrc) {
|
||||
frameSrc.value = unref(currentRoute.meta)?.frameSrc as string;
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function init() {
|
||||
nextTick(() => {
|
||||
const iframe = unref(frameRef);
|
||||
if (!iframe) return;
|
||||
const _frame = iframe as any;
|
||||
if (_frame.attachEvent) {
|
||||
_frame.attachEvent("onload", () => {
|
||||
hideLoading();
|
||||
});
|
||||
} else {
|
||||
iframe.onload = () => {
|
||||
hideLoading();
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = true;
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.frame {
|
||||
height: 100vh;
|
||||
z-index: 998;
|
||||
|
||||
.frame-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -5,6 +5,7 @@ import { routeMetaType } from "../types";
|
||||
import { transformI18n } from "/@/plugins/i18n";
|
||||
import { storageSession } from "/@/utils/storage";
|
||||
import { useAppStoreHook } from "/@/store/modules/app";
|
||||
import { remainingPaths } from "/@/router/modules/index";
|
||||
import { Title } from "../../../public/serverConfig.json";
|
||||
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
|
||||
|
||||
@@ -67,6 +68,7 @@ export function useNav() {
|
||||
}
|
||||
|
||||
function menuSelect(indexPath: string, routers): void {
|
||||
if (isRemaining(indexPath)) return;
|
||||
let parentPath = "";
|
||||
const parentPathIndex = indexPath.lastIndexOf("/");
|
||||
if (parentPathIndex > 0) {
|
||||
@@ -93,6 +95,11 @@ export function useNav() {
|
||||
findCurrentRoute(indexPath, routers);
|
||||
}
|
||||
|
||||
// 判断路径是否参与菜单
|
||||
function isRemaining(path: string): boolean {
|
||||
return remainingPaths.includes(path);
|
||||
}
|
||||
|
||||
return {
|
||||
logout,
|
||||
backHome,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<router-view>
|
||||
<template #default="{ Component, route }">
|
||||
<transition appear name="fade-transform" mode="out-in">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</transition>
|
||||
</template>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "layoutParentView"
|
||||
};
|
||||
</script>
|
||||
@@ -67,6 +67,7 @@ export type childrenType = {
|
||||
icon?: string;
|
||||
title?: string;
|
||||
i18n?: boolean;
|
||||
showParent?: boolean;
|
||||
extraIcon?: {
|
||||
svg?: boolean;
|
||||
name?: string;
|
||||
|
||||
@@ -35,9 +35,10 @@ app.component("IconifyIconOnline", IconifyIconOnline);
|
||||
app.component("FontIcon", FontIcon);
|
||||
|
||||
getServerConfig(app).then(async config => {
|
||||
app.use(router);
|
||||
await router.isReady();
|
||||
injectResponsiveStorage(app, config);
|
||||
setupStore(app);
|
||||
app.use(router).use(MotionPlugin).use(useElementPlus).use(usI18n);
|
||||
await router.isReady();
|
||||
app.use(MotionPlugin).use(useElementPlus).use(usI18n);
|
||||
app.mount("#app");
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// 多组件库的国际化和本地项目国际化兼容
|
||||
import { App } from "vue";
|
||||
import { set } from "lodash-es";
|
||||
import { set } from "lodash-unified";
|
||||
import { createI18n } from "vue-i18n";
|
||||
import { localesConfigs } from "./config";
|
||||
import { storageLocal } from "/@/utils/storage";
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { isUrl } from "/@/utils/is";
|
||||
import { toRouteType } from "./types";
|
||||
import { openLink } from "/@/utils/link";
|
||||
import NProgress from "/@/utils/progress";
|
||||
import { constantRoutes } from "./modules";
|
||||
import { split, findIndex } from "lodash-es";
|
||||
import { findIndex } from "lodash-unified";
|
||||
import { transformI18n } from "/@/plugins/i18n";
|
||||
import remainingRouter from "./modules/remaining";
|
||||
import { storageSession } from "/@/utils/storage";
|
||||
@@ -52,7 +53,7 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
||||
}
|
||||
const name = storageSession.getItem("info");
|
||||
NProgress.start();
|
||||
const externalLink = to?.redirectedFrom?.fullPath;
|
||||
const externalLink = isUrl(to?.name);
|
||||
if (!externalLink)
|
||||
to.matched.some(item => {
|
||||
if (!item.meta.title) return "";
|
||||
@@ -65,9 +66,9 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
||||
});
|
||||
if (name) {
|
||||
if (_from?.name) {
|
||||
// 如果路由包含http 则是超链接 反之是普通路由
|
||||
if (externalLink && externalLink.includes("http")) {
|
||||
openLink(`http${split(externalLink, "http")[1]}`);
|
||||
// name为超链接
|
||||
if (externalLink) {
|
||||
openLink(to?.name);
|
||||
NProgress.done();
|
||||
} else {
|
||||
next();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const errorRouter = {
|
||||
path: "/error",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const externalLink = {
|
||||
path: "/external",
|
||||
name: "external",
|
||||
path: "/externals",
|
||||
component: Layout,
|
||||
meta: {
|
||||
icon: "link",
|
||||
@@ -13,11 +12,11 @@ const externalLink = {
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "https://github.com/xiaoxian521/vue-pure-admin",
|
||||
path: "/external",
|
||||
name: "https://pure-admin-doc.vercel.app",
|
||||
meta: {
|
||||
title: $t("menus.externalLink"),
|
||||
i18n: true,
|
||||
rank: 191
|
||||
i18n: true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const homeRouter = {
|
||||
path: "/",
|
||||
|
||||
@@ -24,3 +24,8 @@ export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
|
||||
export const constantMenus: Array<RouteComponent> = ascending(routes).concat(
|
||||
...remainingRouter
|
||||
);
|
||||
|
||||
// 不参与菜单的路由
|
||||
export const remainingPaths = Object.keys(remainingRouter).map(v => {
|
||||
return remainingRouter[v].path;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const remainingRouter = [
|
||||
{
|
||||
|
||||
@@ -8,10 +8,12 @@ import {
|
||||
} from "vue-router";
|
||||
import { router } from "./index";
|
||||
import { loadEnv } from "../../build";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
import { useTimeoutFn } from "@vueuse/core";
|
||||
import { RouteConfigs } from "/@/layout/types";
|
||||
import { buildHierarchyTree } from "/@/utils/tree";
|
||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
const IFrame = () => import("/@/layout/frameView.vue");
|
||||
// https://cn.vitejs.dev/guide/features.html#glob-import
|
||||
const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}");
|
||||
|
||||
@@ -125,6 +127,10 @@ function initRouter(name: string) {
|
||||
// 最终路由进行升序
|
||||
ascending(router.options.routes[0].children);
|
||||
if (!router.hasRoute(v?.name)) router.addRoute(v);
|
||||
const flattenRouters = router
|
||||
.getRoutes()
|
||||
.find(n => n.path === "/");
|
||||
router.addRoute(flattenRouters);
|
||||
}
|
||||
resolve(router);
|
||||
}
|
||||
@@ -146,14 +152,15 @@ function initRouter(name: string) {
|
||||
*/
|
||||
function formatFlatteningRoutes(routesList: RouteRecordRaw[]) {
|
||||
if (routesList.length === 0) return routesList;
|
||||
for (let i = 0; i < routesList.length; i++) {
|
||||
if (routesList[i].children) {
|
||||
routesList = routesList
|
||||
let hierarchyList = buildHierarchyTree(routesList);
|
||||
for (let i = 0; i < hierarchyList.length; i++) {
|
||||
if (hierarchyList[i].children) {
|
||||
hierarchyList = hierarchyList
|
||||
.slice(0, i + 1)
|
||||
.concat(routesList[i].children, routesList.slice(i + 1));
|
||||
.concat(hierarchyList[i].children, hierarchyList.slice(i + 1));
|
||||
}
|
||||
}
|
||||
return routesList;
|
||||
return hierarchyList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,6 +223,8 @@ function addAsyncRoutes(arrRoutes: Array<RouteRecordRaw>) {
|
||||
arrRoutes.forEach((v: RouteRecordRaw) => {
|
||||
if (v.redirect) {
|
||||
v.component = Layout;
|
||||
} else if (v.meta?.frameSrc) {
|
||||
v.component = IFrame;
|
||||
} else {
|
||||
const index = modulesRoutesKeys.findIndex(ev => ev.includes(v.path));
|
||||
v.component = modulesRoutes[modulesRoutesKeys[index]];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { store } from "/@/store";
|
||||
import { isEqual } from "lodash-es";
|
||||
import { isUrl } from "/@/utils/is";
|
||||
import { isEqual } from "lodash-unified";
|
||||
import { storageLocal } from "/@/utils/storage";
|
||||
import { multiType, positionType } from "./types";
|
||||
|
||||
@@ -54,6 +55,7 @@ export const useMultiTagsStore = defineStore({
|
||||
case "push":
|
||||
{
|
||||
const tagVal = value as multiType;
|
||||
if (isUrl(tagVal?.name)) return;
|
||||
const tagPath = tagVal?.path;
|
||||
// 判断tag是否已存在
|
||||
const tagHasExits = this.multiTags.some(tag => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { store } from "/@/store";
|
||||
import { cacheType } from "./types";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { cloneDeep } from "lodash-unified";
|
||||
import { RouteConfigs } from "/@/layout/types";
|
||||
import { constantMenus } from "/@/router/modules";
|
||||
import { ascending, filterTree } from "/@/router/utils";
|
||||
|
||||
@@ -38,14 +38,16 @@
|
||||
|
||||
/* 动态改变cssvar 用于主题切换 https://github.com/element-plus/element-plus/issues/4856#issuecomment-1000174357 */
|
||||
.el-button--primary {
|
||||
--el-button-bg-color: var(--el-color-primary) !important;
|
||||
--el-button-border-color: var(--el-color-primary) !important;
|
||||
--el-button-hover-bg-color: var(--el-color-primary-light-2) !important;
|
||||
--el-button-hover-border-color: var(--el-color-primary-light-2) !important;
|
||||
--el-button-active-bg-color: var(--el-color-primary-active) !important;
|
||||
--el-button-active-border-color: var(--el-color-primary-active) !important;
|
||||
}
|
||||
|
||||
/* button--primary plain */
|
||||
.el-button--primary.is-plain {
|
||||
--el-button-active-bg-color: var(--el-color-primary) !important;
|
||||
--el-button-active-border-color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
/* nprogress适配ep的primary */
|
||||
#nprogress {
|
||||
& .bar {
|
||||
|
||||
@@ -12,8 +12,8 @@ body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizelegibility;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
|
||||
Microsoft YaHei, Arial, sans-serif;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
|
||||
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -22,6 +22,11 @@ html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@@ -23,16 +23,6 @@
|
||||
margin-left: $sideBarWidth;
|
||||
position: relative;
|
||||
background: #f0f2f5;
|
||||
@media screen and (min-width: 150px) and (max-width: 420px) {
|
||||
.app-main .el-scrollbar__view:first-child {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 420px) {
|
||||
.app-main .el-scrollbar__view:first-child {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
resultType,
|
||||
PureHttpError,
|
||||
RequestMethods,
|
||||
PureHttpResoponse,
|
||||
PureHttpResponse,
|
||||
PureHttpRequestConfig
|
||||
} from "./types.d";
|
||||
import qs from "qs";
|
||||
@@ -91,7 +91,7 @@ class PureHttp {
|
||||
private httpInterceptorsResponse(): void {
|
||||
const instance = PureHttp.axiosInstance;
|
||||
instance.interceptors.response.use(
|
||||
(response: PureHttpResoponse) => {
|
||||
(response: PureHttpResponse) => {
|
||||
const $config = response.config;
|
||||
// 关闭进度条动画
|
||||
NProgress.done();
|
||||
@@ -145,21 +145,21 @@ class PureHttp {
|
||||
}
|
||||
|
||||
// 单独抽离的post工具函数
|
||||
public post<T>(
|
||||
public post<T, P>(
|
||||
url: string,
|
||||
params?: T,
|
||||
config?: PureHttpRequestConfig
|
||||
): Promise<T> {
|
||||
return this.request<T>("post", url, params, config);
|
||||
): Promise<P> {
|
||||
return this.request<P>("post", url, params, config);
|
||||
}
|
||||
|
||||
// 单独抽离的get工具函数
|
||||
public get<T>(
|
||||
public get<T, P>(
|
||||
url: string,
|
||||
params?: T,
|
||||
config?: PureHttpRequestConfig
|
||||
): Promise<T> {
|
||||
return this.request<T>("get", url, params, config);
|
||||
): Promise<P> {
|
||||
return this.request<P>("get", url, params, config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
src/utils/http/types.d.ts
vendored
16
src/utils/http/types.d.ts
vendored
@@ -18,13 +18,13 @@ export interface PureHttpError extends AxiosError {
|
||||
isCancelRequest?: boolean;
|
||||
}
|
||||
|
||||
export interface PureHttpResoponse extends AxiosResponse {
|
||||
export interface PureHttpResponse extends AxiosResponse {
|
||||
config: PureHttpRequestConfig;
|
||||
}
|
||||
|
||||
export interface PureHttpRequestConfig extends AxiosRequestConfig {
|
||||
beforeRequestCallback?: (request: PureHttpRequestConfig) => void;
|
||||
beforeResponseCallback?: (response: PureHttpResoponse) => void;
|
||||
beforeResponseCallback?: (response: PureHttpResponse) => void;
|
||||
}
|
||||
|
||||
export default class PureHttp {
|
||||
@@ -34,6 +34,14 @@ export default class PureHttp {
|
||||
param?: AxiosRequestConfig,
|
||||
axiosConfig?: PureHttpRequestConfig
|
||||
): Promise<T>;
|
||||
post<T>(url: string, params?: T, config?: PureHttpRequestConfig): Promise<T>;
|
||||
get<T>(url: string, params?: T, config?: PureHttpRequestConfig): Promise<T>;
|
||||
post<T, P>(
|
||||
url: string,
|
||||
params?: T,
|
||||
config?: PureHttpRequestConfig
|
||||
): Promise<P>;
|
||||
get<T, P>(
|
||||
url: string,
|
||||
params?: T,
|
||||
config?: PureHttpRequestConfig
|
||||
): Promise<P>;
|
||||
}
|
||||
|
||||
@@ -94,8 +94,9 @@ export const isServer = typeof window === "undefined";
|
||||
|
||||
export const isClient = !isServer;
|
||||
|
||||
export function isUrl(path: string): boolean {
|
||||
export function isUrl<T>(path: T): boolean {
|
||||
const reg =
|
||||
/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
|
||||
// @ts-expect-error
|
||||
return reg.test(path);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const openLink = (link: string) => {
|
||||
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");
|
||||
|
||||
54
src/utils/loaders/index.ts
Normal file
54
src/utils/loaders/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
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,3 +1,5 @@
|
||||
import type { FunctionArgs } from "@vueuse/core";
|
||||
|
||||
export const hasClass = (ele: RefType<any>, cls: string): any => {
|
||||
return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)"));
|
||||
};
|
||||
@@ -40,3 +42,16 @@ export const toggleClass = (
|
||||
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;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
226
src/utils/print.ts
Normal file
226
src/utils/print.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
interface PrintFunction {
|
||||
extendOptions: Function;
|
||||
getStyle: Function;
|
||||
setDomHeight: Function;
|
||||
toPrint: Function;
|
||||
}
|
||||
|
||||
const Print = function (dom, options?: object): PrintFunction {
|
||||
options = options || {};
|
||||
// @ts-expect-error
|
||||
if (!(this instanceof Print)) return new Print(dom, options);
|
||||
this.conf = {
|
||||
styleStr: "",
|
||||
// Elements that need to dynamically get and set the height
|
||||
setDomHeightArr: [],
|
||||
// Echart dom List
|
||||
echartDomArr: [],
|
||||
// Callback before printing
|
||||
printBeforeFn: null,
|
||||
// Callback after printing
|
||||
printDoneCallBack: null
|
||||
};
|
||||
for (const key in this.conf) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (key && options.hasOwnProperty(key)) {
|
||||
this.conf[key] = options[key];
|
||||
}
|
||||
}
|
||||
if (typeof dom === "string") {
|
||||
this.dom = document.querySelector(dom);
|
||||
} else {
|
||||
this.dom = this.isDOM(dom) ? dom : dom.$el;
|
||||
}
|
||||
if (this.conf.setDomHeightArr && this.conf.setDomHeightArr.length) {
|
||||
this.setDomHeight(this.conf.setDomHeightArr);
|
||||
}
|
||||
this.init();
|
||||
};
|
||||
|
||||
Print.prototype = {
|
||||
/**
|
||||
* init
|
||||
*/
|
||||
init: function (): void {
|
||||
const content = this.getStyle() + this.getHtml();
|
||||
this.writeIframe(content);
|
||||
},
|
||||
/**
|
||||
* Configuration property extension
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj2
|
||||
*/
|
||||
extendOptions: function <T>(obj, obj2: T): T {
|
||||
for (const k in obj2) {
|
||||
obj[k] = obj2[k];
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
/**
|
||||
Copy all styles of the original page
|
||||
*/
|
||||
getStyle: function (): string {
|
||||
let str = "";
|
||||
const styles: NodeListOf<Element> = document.querySelectorAll("style,link");
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
str += styles[i].outerHTML;
|
||||
}
|
||||
str += `<style>.no-print{display:none;}${this.conf.styleStr}</style>`;
|
||||
return str;
|
||||
},
|
||||
// form assignment
|
||||
getHtml: function (): Element {
|
||||
const inputs = document.querySelectorAll("input");
|
||||
const selects = document.querySelectorAll("select");
|
||||
const textareas = document.querySelectorAll("textarea");
|
||||
for (let k = 0; k < inputs.length; k++) {
|
||||
if (inputs[k].type == "checkbox" || inputs[k].type == "radio") {
|
||||
if (inputs[k].checked == true) {
|
||||
inputs[k].setAttribute("checked", "checked");
|
||||
} else {
|
||||
inputs[k].removeAttribute("checked");
|
||||
}
|
||||
} else if (inputs[k].type == "text") {
|
||||
inputs[k].setAttribute("value", inputs[k].value);
|
||||
} else {
|
||||
inputs[k].setAttribute("value", inputs[k].value);
|
||||
}
|
||||
}
|
||||
|
||||
for (let k2 = 0; k2 < textareas.length; k2++) {
|
||||
if (textareas[k2].type == "textarea") {
|
||||
textareas[k2].innerHTML = textareas[k2].value;
|
||||
}
|
||||
}
|
||||
|
||||
for (let k3 = 0; k3 < selects.length; k3++) {
|
||||
if (selects[k3].type == "select-one") {
|
||||
const child = selects[k3].children;
|
||||
for (const i in child) {
|
||||
if (child[i].tagName == "OPTION") {
|
||||
// @ts-ignore
|
||||
if (child[i].selected == true) {
|
||||
child[i].setAttribute("selected", "selected");
|
||||
} else {
|
||||
child[i].removeAttribute("selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.dom.outerHTML;
|
||||
},
|
||||
/**
|
||||
create iframe
|
||||
*/
|
||||
writeIframe: function (content) {
|
||||
let w: Document | Window;
|
||||
let doc: Document;
|
||||
const iframe: HTMLIFrameElement = document.createElement("iframe");
|
||||
const f: HTMLIFrameElement = document.body.appendChild(iframe);
|
||||
iframe.id = "myIframe";
|
||||
iframe.setAttribute(
|
||||
"style",
|
||||
"position:absolute;width:0;height:0;top:-10px;left:-10px;"
|
||||
);
|
||||
// eslint-disable-next-line prefer-const
|
||||
w = f.contentWindow || f.contentDocument;
|
||||
// eslint-disable-next-line prefer-const
|
||||
doc = f.contentDocument || f.contentWindow.document;
|
||||
doc.open();
|
||||
doc.write(content);
|
||||
doc.close();
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const _this = this;
|
||||
iframe.onload = function (): void {
|
||||
// Before popping, callback
|
||||
if (_this.conf.printBeforeFn) {
|
||||
_this.conf.printBeforeFn({ doc });
|
||||
}
|
||||
|
||||
_this.drawEchartImg(doc).then(() => {
|
||||
_this.toPrint(w);
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(iframe);
|
||||
// After popup, callback
|
||||
if (_this.conf.printDoneCallBack) {
|
||||
_this.conf.printDoneCallBack();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
};
|
||||
},
|
||||
/**
|
||||
* echarts printing
|
||||
* @param {Object} doc iframe window
|
||||
*/
|
||||
drawEchartImg(doc): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
if (this.conf.echartDomArr && this.conf.echartDomArr.length > 0) {
|
||||
this.conf.echartDomArr.forEach(e => {
|
||||
const dom = doc.querySelector("#" + e.$el.id);
|
||||
const img = new Image();
|
||||
const w = dom.offsetWidth + "px";
|
||||
const H = dom.offsetHeight + "px";
|
||||
|
||||
img.style.width = w;
|
||||
img.style.height = H;
|
||||
img.src = e.imgSrc;
|
||||
dom.innerHTML = "";
|
||||
dom.appendChild(img);
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
/**
|
||||
Print
|
||||
*/
|
||||
toPrint: function (frameWindow): void {
|
||||
try {
|
||||
setTimeout(function () {
|
||||
frameWindow.focus();
|
||||
try {
|
||||
if (!frameWindow.document.execCommand("print", false, null)) {
|
||||
frameWindow.print();
|
||||
}
|
||||
} catch (e) {
|
||||
frameWindow.print();
|
||||
}
|
||||
frameWindow.close();
|
||||
}, 10);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
isDOM:
|
||||
typeof HTMLElement === "object"
|
||||
? function (obj) {
|
||||
return obj instanceof HTMLElement;
|
||||
}
|
||||
: function (obj) {
|
||||
return (
|
||||
obj &&
|
||||
typeof obj === "object" &&
|
||||
obj.nodeType === 1 &&
|
||||
typeof obj.nodeName === "string"
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Set the height of the specified dom element by getting the existing height of the dom element and setting
|
||||
* @param {Array} arr
|
||||
*/
|
||||
setDomHeight(arr) {
|
||||
if (arr && arr.length) {
|
||||
arr.forEach(name => {
|
||||
const domArr = document.querySelectorAll(name);
|
||||
domArr.forEach(dom => {
|
||||
dom.style.height = dom.offsetHeight + "px";
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default Print;
|
||||
35
src/utils/resize/index.ts
Normal file
35
src/utils/resize/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
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();
|
||||
}
|
||||
};
|
||||
116
src/utils/watermark.ts
Normal file
116
src/utils/watermark.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
ref,
|
||||
Ref,
|
||||
unref,
|
||||
shallowRef,
|
||||
onBeforeUnmount,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import { isDef } from "/@/utils/is";
|
||||
import { useRafThrottle } from "/@/utils/operate";
|
||||
import { addResizeListener, removeResizeListener } from "/@/utils/resize";
|
||||
|
||||
const domSymbol = Symbol("watermark-dom");
|
||||
|
||||
type attr = {
|
||||
font?: string;
|
||||
fillStyle?: string;
|
||||
};
|
||||
|
||||
export function useWatermark(
|
||||
appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement>
|
||||
) {
|
||||
const func = useRafThrottle(function () {
|
||||
const el = unref(appendEl);
|
||||
if (!el) return;
|
||||
const { clientHeight: height, clientWidth: width } = el;
|
||||
updateWatermark({ height, width });
|
||||
});
|
||||
const id = domSymbol.toString();
|
||||
const watermarkEl = shallowRef<HTMLElement>();
|
||||
|
||||
const clear = () => {
|
||||
const domId = unref(watermarkEl);
|
||||
watermarkEl.value = undefined;
|
||||
const el = unref(appendEl);
|
||||
if (!el) return;
|
||||
domId && el.removeChild(domId);
|
||||
removeResizeListener(el, func);
|
||||
};
|
||||
|
||||
function createBase64(str: string, attr?: attr) {
|
||||
const can = document.createElement("canvas");
|
||||
const width = 300;
|
||||
const height = 240;
|
||||
Object.assign(can, { width, height });
|
||||
|
||||
const cans = can.getContext("2d");
|
||||
if (cans) {
|
||||
cans.rotate((-20 * Math.PI) / 120);
|
||||
cans.font = attr?.font ?? "15px Reggae One";
|
||||
cans.fillStyle = attr?.fillStyle ?? "rgba(0, 0, 0, 0.15)";
|
||||
cans.textAlign = "left";
|
||||
cans.textBaseline = "middle";
|
||||
cans.fillText(str, width / 20, height);
|
||||
}
|
||||
return can.toDataURL("image/png");
|
||||
}
|
||||
|
||||
function updateWatermark(
|
||||
options: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
str?: string;
|
||||
attr?: attr;
|
||||
} = {}
|
||||
) {
|
||||
const el = unref(watermarkEl);
|
||||
if (!el) return;
|
||||
if (isDef(options.width)) {
|
||||
el.style.width = `${options.width}px`;
|
||||
}
|
||||
if (isDef(options.height)) {
|
||||
el.style.height = `${options.height}px`;
|
||||
}
|
||||
if (isDef(options.str)) {
|
||||
el.style.background = `url(${createBase64(
|
||||
options.str,
|
||||
options.attr
|
||||
)}) left top repeat`;
|
||||
}
|
||||
}
|
||||
|
||||
const createWatermark = (str: string, attr?: attr) => {
|
||||
if (unref(watermarkEl)) {
|
||||
updateWatermark({ str, attr });
|
||||
return id;
|
||||
}
|
||||
const div = document.createElement("div");
|
||||
watermarkEl.value = div;
|
||||
div.id = id;
|
||||
div.style.pointerEvents = "none";
|
||||
div.style.top = "0px";
|
||||
div.style.left = "0px";
|
||||
div.style.position = "absolute";
|
||||
div.style.zIndex = "100000";
|
||||
const el = unref(appendEl);
|
||||
if (!el) return id;
|
||||
const { clientHeight: height, clientWidth: width } = el;
|
||||
updateWatermark({ str, width, height, attr });
|
||||
el.appendChild(div);
|
||||
return id;
|
||||
};
|
||||
|
||||
function setWatermark(str: string, attr?: attr) {
|
||||
createWatermark(str, attr);
|
||||
addResizeListener(document.documentElement, func);
|
||||
const instance = getCurrentInstance();
|
||||
if (instance) {
|
||||
onBeforeUnmount(() => {
|
||||
clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { setWatermark, clear };
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export default {
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from "vue";
|
||||
import { storageSession } from "/@/utils/storage";
|
||||
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
|
||||
|
||||
let purview = ref<string>(storageSession.getItem("info").username);
|
||||
|
||||
@@ -36,6 +37,11 @@ function changRole() {
|
||||
查看左侧菜单变化(系统管理),模拟后台根据不同角色返回对应路由
|
||||
</p>
|
||||
</h4>
|
||||
<el-button type="primary" @click="changRole">切换角色</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="changRole"
|
||||
:icon="useRenderIcon('user', { color: '#fff' })"
|
||||
>切换角色</el-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
9
types/global.d.ts
vendored
9
types/global.d.ts
vendored
@@ -16,6 +16,15 @@ declare module "vue" {
|
||||
}
|
||||
|
||||
declare global {
|
||||
const __APP_INFO__: {
|
||||
pkg: {
|
||||
name: string;
|
||||
version: string;
|
||||
dependencies: Recordable<string>;
|
||||
devDependencies: Recordable<string>;
|
||||
};
|
||||
lastBuildTime: string;
|
||||
};
|
||||
interface Window {
|
||||
// Global vue app instance
|
||||
__APP__: App<Element>;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import dayjs from "dayjs";
|
||||
import { resolve } from "path";
|
||||
import pkg from "./package.json";
|
||||
import { warpperEnv, regExps } from "./build";
|
||||
import { getPluginsList } from "./build/plugins";
|
||||
import { UserConfigExport, ConfigEnv, loadEnv } from "vite";
|
||||
@@ -19,6 +21,12 @@ const alias: Record<string, string> = {
|
||||
"vue-i18n": "vue-i18n/dist/vue-i18n.cjs.js"
|
||||
};
|
||||
|
||||
const { dependencies, devDependencies, name, version } = pkg;
|
||||
const __APP_INFO__ = {
|
||||
pkg: { dependencies, devDependencies, name, version },
|
||||
lastBuildTime: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss")
|
||||
};
|
||||
|
||||
export default ({ command, mode }: ConfigEnv): UserConfigExport => {
|
||||
const {
|
||||
VITE_PORT,
|
||||
@@ -90,7 +98,8 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
|
||||
chunkSizeWarningLimit: 2000
|
||||
},
|
||||
define: {
|
||||
__INTLIFY_PROD_DEVTOOLS__: false
|
||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||
__APP_INFO__: JSON.stringify(__APP_INFO__)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user