initHeader
11
frontend/.env
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# title
|
||||||
|
VITE_GLOB_APP_TITLE=自动检测平台
|
||||||
|
|
||||||
|
# 本地运行端口号
|
||||||
|
VITE_PORT=18091
|
||||||
|
|
||||||
|
# 启动时自动打开浏览器
|
||||||
|
VITE_OPEN=true
|
||||||
|
|
||||||
|
# 打包后是否生成包分析文件
|
||||||
|
VITE_REPORT=false
|
||||||
@@ -1,6 +1,21 @@
|
|||||||
NODE_ENV='development'
|
# 本地环境
|
||||||
|
VITE_USER_NODE_ENV=development
|
||||||
|
|
||||||
|
# 公共基础路径
|
||||||
|
VITE_PUBLIC_PATH=/
|
||||||
|
|
||||||
VITE_TITLE=""
|
|
||||||
# 路由模式
|
# 路由模式
|
||||||
# Optional: hash | history
|
# Optional: hash | history
|
||||||
VITE_ROUTER_MODE=hash
|
VITE_ROUTER_MODE=hash
|
||||||
|
|
||||||
|
# 打包时是否删除 console
|
||||||
|
VITE_DROP_CONSOLE=true
|
||||||
|
|
||||||
|
# 是否开启 VitePWA
|
||||||
|
VITE_PWA=false
|
||||||
|
|
||||||
|
# 开发环境接口地址
|
||||||
|
VITE_API_URL=/api
|
||||||
|
|
||||||
|
# 开发环境跨域代理,支持配置多个
|
||||||
|
VITE_PROXY=[["/api","http://192.168.1.125:18092/"]]
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
NODE_ENV='production'
|
# 线上环境
|
||||||
|
VITE_USER_NODE_ENV=production
|
||||||
|
|
||||||
|
# 公共基础路径
|
||||||
|
VITE_PUBLIC_PATH=/
|
||||||
|
|
||||||
VITE_TITLE=""
|
|
||||||
# 路由模式
|
# 路由模式
|
||||||
# Optional: hash | history
|
# Optional: hash | history
|
||||||
VITE_ROUTER_MODE=hash
|
VITE_ROUTER_MODE=hash
|
||||||
|
|
||||||
|
# 是否启用 gzip 或 brotli 压缩打包,如果需要多个压缩规则,可以使用 “,” 分隔
|
||||||
|
# Optional: gzip | brotli | none
|
||||||
|
VITE_BUILD_COMPRESS=none
|
||||||
|
|
||||||
|
# 打包压缩后是否删除源文件
|
||||||
|
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE=false
|
||||||
|
|
||||||
|
# 打包时是否删除 console
|
||||||
|
VITE_DROP_CONSOLE=true
|
||||||
|
|
||||||
|
# 是否开启 VitePWA
|
||||||
|
VITE_PWA=true
|
||||||
|
|
||||||
|
# 线上环境接口地址
|
||||||
|
VITE_API_URL="https://mock.mengxuegu.com/mock/629d727e6163854a32e8307e"
|
||||||
|
|||||||
46
frontend/build/getEnv.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export function isDevFn(mode: string): boolean {
|
||||||
|
return mode === "development";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isProdFn(mode: string): boolean {
|
||||||
|
return mode === "production";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTestFn(mode: string): boolean {
|
||||||
|
return mode === "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to generate package preview
|
||||||
|
*/
|
||||||
|
export function isReportMode(): boolean {
|
||||||
|
return process.env.VITE_REPORT === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all environment variable configuration files to process.env
|
||||||
|
export function wrapperEnv(envConf: Recordable): ViteEnv {
|
||||||
|
const ret: any = {};
|
||||||
|
|
||||||
|
for (const envName of Object.keys(envConf)) {
|
||||||
|
let realName = envConf[envName].replace(/\\n/g, "\n");
|
||||||
|
realName = realName === "true" ? true : realName === "false" ? false : realName;
|
||||||
|
if (envName === "VITE_PORT") realName = Number(realName);
|
||||||
|
if (envName === "VITE_PROXY") {
|
||||||
|
try {
|
||||||
|
realName = JSON.parse(realName);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
ret[envName] = realName;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user root directory
|
||||||
|
* @param dir file path
|
||||||
|
*/
|
||||||
|
export function getRootPath(...dir: string[]) {
|
||||||
|
return path.resolve(process.cwd(), ...dir);
|
||||||
|
}
|
||||||
108
frontend/build/plugins.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { resolve } from "path";
|
||||||
|
import { PluginOption } from "vite";
|
||||||
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
import { visualizer } from "rollup-plugin-visualizer";
|
||||||
|
import { createHtmlPlugin } from "vite-plugin-html";
|
||||||
|
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||||
|
import eslintPlugin from "vite-plugin-eslint";
|
||||||
|
import viteCompression from "vite-plugin-compression";
|
||||||
|
import vueSetupExtend from "unplugin-vue-setup-extend-plus/vite";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 vite 插件
|
||||||
|
* @param viteEnv
|
||||||
|
*/
|
||||||
|
export const createVitePlugins = (viteEnv: ViteEnv): (PluginOption | PluginOption[])[] => {
|
||||||
|
const { VITE_GLOB_APP_TITLE, VITE_REPORT, VITE_PWA } = viteEnv;
|
||||||
|
return [
|
||||||
|
vue(),
|
||||||
|
// vue 可以使用 jsx/tsx 语法
|
||||||
|
vueJsx(),
|
||||||
|
// esLint 报错信息显示在浏览器界面上
|
||||||
|
eslintPlugin(),
|
||||||
|
// name 可以写在 script 标签上
|
||||||
|
vueSetupExtend({}),
|
||||||
|
// 创建打包压缩配置
|
||||||
|
createCompression(viteEnv),
|
||||||
|
// 注入变量到 html 文件
|
||||||
|
createHtmlPlugin({
|
||||||
|
inject: {
|
||||||
|
data: { title: VITE_GLOB_APP_TITLE }
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
// 使用 svg 图标
|
||||||
|
createSvgIconsPlugin({
|
||||||
|
iconDirs: [resolve(process.cwd(), "src/assets/icons")],
|
||||||
|
symbolId: "icon-[dir]-[name]"
|
||||||
|
}),
|
||||||
|
// vitePWA
|
||||||
|
VITE_PWA && createVitePwa(viteEnv),
|
||||||
|
// 是否生成包预览,分析依赖包大小做优化处理
|
||||||
|
VITE_REPORT && (visualizer({ filename: "stats.html", gzipSize: true, brotliSize: true }) as PluginOption)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 根据 compress 配置,生成不同的压缩规则
|
||||||
|
* @param viteEnv
|
||||||
|
*/
|
||||||
|
const createCompression = (viteEnv: ViteEnv): PluginOption | PluginOption[] => {
|
||||||
|
const { VITE_BUILD_COMPRESS = "none", VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
|
||||||
|
const compressList = VITE_BUILD_COMPRESS.split(",");
|
||||||
|
const plugins: PluginOption[] = [];
|
||||||
|
if (compressList.includes("gzip")) {
|
||||||
|
plugins.push(
|
||||||
|
viteCompression({
|
||||||
|
ext: ".gz",
|
||||||
|
algorithm: "gzip",
|
||||||
|
deleteOriginFile: VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (compressList.includes("brotli")) {
|
||||||
|
plugins.push(
|
||||||
|
viteCompression({
|
||||||
|
ext: ".br",
|
||||||
|
algorithm: "brotliCompress",
|
||||||
|
deleteOriginFile: VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return plugins;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description VitePwa
|
||||||
|
* @param viteEnv
|
||||||
|
*/
|
||||||
|
const createVitePwa = (viteEnv: ViteEnv): PluginOption | PluginOption[] => {
|
||||||
|
const { VITE_GLOB_APP_TITLE } = viteEnv;
|
||||||
|
return VitePWA({
|
||||||
|
registerType: "autoUpdate",
|
||||||
|
manifest: {
|
||||||
|
name: VITE_GLOB_APP_TITLE,
|
||||||
|
short_name: VITE_GLOB_APP_TITLE,
|
||||||
|
theme_color: "#ffffff",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/logo.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/logo.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/logo.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
30
frontend/build/proxy.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { ProxyOptions } from "vite";
|
||||||
|
|
||||||
|
type ProxyItem = [string, string];
|
||||||
|
|
||||||
|
type ProxyList = ProxyItem[];
|
||||||
|
|
||||||
|
type ProxyTargetList = Record<string, ProxyOptions>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建代理,用于解析 .env.development 代理配置
|
||||||
|
* @param list
|
||||||
|
*/
|
||||||
|
export function createProxy(list: ProxyList = []) {
|
||||||
|
const ret: ProxyTargetList = {};
|
||||||
|
for (const [prefix, target] of list) {
|
||||||
|
const httpsRE = /^https:\/\//;
|
||||||
|
const isHttps = httpsRE.test(target);
|
||||||
|
|
||||||
|
// https://github.com/http-party/node-http-proxy#options
|
||||||
|
ret[prefix] = {
|
||||||
|
target: target,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewrite: path => path.replace(new RegExp(`^${prefix}`), ""),
|
||||||
|
// https is require secure=false
|
||||||
|
...(isHttps ? { secure: false } : {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -100,6 +100,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<script>
|
||||||
|
const globalState = JSON.parse(window.localStorage.getItem("cn-global"));
|
||||||
|
if (globalState) {
|
||||||
|
const dot = document.querySelectorAll(".dot i");
|
||||||
|
const html = document.querySelector("html");
|
||||||
|
dot.forEach(item => (item.style.background = globalState.primary));
|
||||||
|
if (globalState.isDark) html.style.background = "#141414";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host --port 18091",
|
"dev": "vite",
|
||||||
"serve": "vite --host --port 18091",
|
"serve": "vite",
|
||||||
"build-staging": "vite build --mode staging",
|
"build-staging": "vite build --mode staging",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
@@ -14,34 +14,69 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
|
"@vueuse/core": "^10.4.1",
|
||||||
|
"@wangeditor/editor": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
"axios": "^1.7.3",
|
"axios": "^1.7.3",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"dayjs": "^1.11.9",
|
||||||
|
"driver.js": "^1.3.0",
|
||||||
|
"echarts": "^5.4.3",
|
||||||
|
"echarts-liquidfill": "^3.1.0",
|
||||||
"element-plus": "^2.7.8",
|
"element-plus": "^2.7.8",
|
||||||
|
"md5": "^2.3.0",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^2.2.1",
|
"pinia": "^2.2.1",
|
||||||
"pinia-plugin-persistedstate": "^3.2.1",
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
|
"print-js": "^1.6.0",
|
||||||
|
"qs": "^6.11.2",
|
||||||
|
"screenfull": "^6.0.2",
|
||||||
|
"sortablejs": "^1.15.0",
|
||||||
"vue": "^3.4.29",
|
"vue": "^3.4.29",
|
||||||
"vue-router": "^4.3.3"
|
"vue-i18n": "^9.4.0",
|
||||||
|
"vue-router": "^4.3.3",
|
||||||
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.8.0",
|
"@rushstack/eslint-patch": "^1.8.0",
|
||||||
"@tsconfig/node20": "^20.1.4",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
|
"@types/md5": "^2.3.2",
|
||||||
"@types/node": "^20.14.14",
|
"@types/node": "^20.14.14",
|
||||||
|
"@types/nprogress": "^0.2.0",
|
||||||
|
"@types/qs": "^6.9.8",
|
||||||
|
"@types/sortablejs": "^1.15.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||||
|
"@typescript-eslint/parser": "^6.7.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.5",
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^3.0.2",
|
||||||
"@vue/eslint-config-typescript": "^13.0.0",
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^9.23.0",
|
"eslint-plugin-vue": "^9.23.0",
|
||||||
"npm-run-all2": "^6.2.0",
|
"npm-run-all2": "^6.2.0",
|
||||||
"postcss": "^8.4.41",
|
"prettier": "^3.0.3",
|
||||||
|
"rollup-plugin-visualizer": "^5.9.2",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
|
"standard-version": "^9.5.0",
|
||||||
"tailwindcss": "^3.4.7",
|
"tailwindcss": "^3.4.7",
|
||||||
"typescript": "~5.4.0",
|
"typescript": "~5.4.0",
|
||||||
"unplugin-auto-import": "^0.18.2",
|
"unplugin-auto-import": "^0.18.2",
|
||||||
"unplugin-vue-components": "^0.27.4",
|
"unplugin-vue-components": "^0.27.4",
|
||||||
|
"unplugin-vue-setup-extend-plus": "^1.0.0",
|
||||||
"vite": "^5.3.1",
|
"vite": "^5.3.1",
|
||||||
|
"vite-plugin-compression": "^0.5.1",
|
||||||
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
|
"vite-plugin-html": "^3.2.0",
|
||||||
"vite-plugin-node-polyfills": "^0.22.0",
|
"vite-plugin-node-polyfills": "^0.22.0",
|
||||||
|
"vite-plugin-pwa": "^0.16.5",
|
||||||
"vite-plugin-svg-icons": "^2.0.1",
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
"vue-tsc": "^2.0.21"
|
"vue-tsc": "^2.0.21"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<!--element-plus语言国际化,全局修改为中文-->
|
<!--element-plus语言国际化,全局修改为中文-->
|
||||||
<el-config-provider :locale='zhCn'>
|
<el-config-provider :locale='locale' :size="assemblySize" :button="buttonConfig">
|
||||||
<router-view />
|
<router-view />
|
||||||
</el-config-provider>
|
</el-config-provider>
|
||||||
</template>
|
</template>
|
||||||
@@ -9,8 +9,42 @@
|
|||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'App',
|
name: 'App',
|
||||||
})
|
})
|
||||||
import { ElConfigProvider } from 'element-plus'
|
import { onMounted, reactive, computed } from "vue";
|
||||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { getBrowserLang } from "@/utils";
|
||||||
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
|
import { ElConfigProvider } from "element-plus";
|
||||||
|
import { LanguageType } from "./stores/interface";
|
||||||
|
import { useGlobalStore } from "@/stores/modules/global";
|
||||||
|
import en from "element-plus/es/locale/lang/en";
|
||||||
|
import zhCn from "element-plus/es/locale/lang/zh-cn";
|
||||||
|
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
|
||||||
|
// init theme
|
||||||
|
const { initTheme } = useTheme();
|
||||||
|
initTheme();
|
||||||
|
|
||||||
|
// init language
|
||||||
|
const i18n = useI18n();
|
||||||
|
onMounted(() => {
|
||||||
|
const language = globalStore.language ?? getBrowserLang();
|
||||||
|
i18n.locale.value = language;
|
||||||
|
globalStore.setGlobalState("language", language as LanguageType);
|
||||||
|
});
|
||||||
|
|
||||||
|
// element language
|
||||||
|
const locale = computed(() => {
|
||||||
|
if (globalStore.language == "zh") return zhCn;
|
||||||
|
if (globalStore.language == "en") return en;
|
||||||
|
return getBrowserLang() == "zh" ? zhCn : en;
|
||||||
|
});
|
||||||
|
|
||||||
|
// element assemblySize
|
||||||
|
const assemblySize = computed(() => globalStore.assemblySize);
|
||||||
|
|
||||||
|
// element button config
|
||||||
|
const buttonConfig = reactive({ autoInsertSpace: false });
|
||||||
|
|
||||||
document.getElementById('loadingPage')?.remove()
|
document.getElementById('loadingPage')?.remove()
|
||||||
|
|
||||||
|
|||||||
2
frontend/src/api/config/servicePort.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// 后端微服务模块前缀
|
||||||
|
export const PORT1 = "/admin";
|
||||||
47
frontend/src/api/helper/axiosCancel.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// ? 暂未使用,目前使用全局 Loading 来控制重复请求
|
||||||
|
import { CustomAxiosRequestConfig } from "../index";
|
||||||
|
import qs from "qs";
|
||||||
|
|
||||||
|
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
|
||||||
|
let pendingMap = new Map<string, AbortController>();
|
||||||
|
|
||||||
|
// 序列化参数
|
||||||
|
export const getPendingUrl = (config: CustomAxiosRequestConfig) =>
|
||||||
|
[config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join("&");
|
||||||
|
|
||||||
|
export class AxiosCanceler {
|
||||||
|
/**
|
||||||
|
* @description: 添加请求
|
||||||
|
* @param {Object} config
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
addPending(config: CustomAxiosRequestConfig) {
|
||||||
|
// 在请求开始前,对之前的请求做检查取消操作
|
||||||
|
this.removePending(config);
|
||||||
|
const url = getPendingUrl(config);
|
||||||
|
const controller = new AbortController();
|
||||||
|
config.signal = controller.signal;
|
||||||
|
pendingMap.set(url, controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 移除请求
|
||||||
|
* @param {Object} config
|
||||||
|
*/
|
||||||
|
removePending(config: CustomAxiosRequestConfig) {
|
||||||
|
const url = getPendingUrl(config);
|
||||||
|
// 如果在 pending 中存在当前请求标识,需要取消当前请求
|
||||||
|
const controller = pendingMap.get(url);
|
||||||
|
controller && controller.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 清空所有pending
|
||||||
|
*/
|
||||||
|
removeAllPending() {
|
||||||
|
pendingMap.forEach(controller => {
|
||||||
|
controller && controller.abort();
|
||||||
|
});
|
||||||
|
pendingMap.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
frontend/src/api/helper/checkStatus.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 校验网络请求状态码
|
||||||
|
* @param {Number} status
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
export const checkStatus = (status: number) => {
|
||||||
|
switch (status) {
|
||||||
|
case 400:
|
||||||
|
ElMessage.error("请求失败!请您稍后重试");
|
||||||
|
break;
|
||||||
|
case 401:
|
||||||
|
ElMessage.error("登录失效!请您重新登录");
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
ElMessage.error("当前账号无权限访问!");
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
ElMessage.error("你所访问的资源不存在!");
|
||||||
|
break;
|
||||||
|
case 405:
|
||||||
|
ElMessage.error("请求方式错误!请您稍后重试");
|
||||||
|
break;
|
||||||
|
case 408:
|
||||||
|
ElMessage.error("请求超时!请您稍后重试");
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
ElMessage.error("服务异常!");
|
||||||
|
break;
|
||||||
|
case 502:
|
||||||
|
ElMessage.error("网关错误!");
|
||||||
|
break;
|
||||||
|
case 503:
|
||||||
|
ElMessage.error("服务不可用!");
|
||||||
|
break;
|
||||||
|
case 504:
|
||||||
|
ElMessage.error("网关超时!");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ElMessage.error("请求失败!");
|
||||||
|
}
|
||||||
|
};
|
||||||
110
frontend/src/api/index.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from "axios";
|
||||||
|
import { showFullScreenLoading, tryHideFullScreenLoading } from "@/components/Loading/fullScreen";
|
||||||
|
import { LOGIN_URL } from "@/config";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { ResultData } from "@/api/interface";
|
||||||
|
import { ResultEnum } from "@/enums/httpEnum";
|
||||||
|
import { checkStatus } from "./helper/checkStatus";
|
||||||
|
import { useUserStore } from "@/stores/modules/user";
|
||||||
|
import router from "@/routers";
|
||||||
|
|
||||||
|
export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
// 默认地址请求地址,可在 .env.** 文件中修改
|
||||||
|
baseURL: import.meta.env.VITE_API_URL as string,
|
||||||
|
// 设置超时时间
|
||||||
|
timeout: ResultEnum.TIMEOUT as number,
|
||||||
|
// 跨域时候允许携带凭证
|
||||||
|
withCredentials: true
|
||||||
|
};
|
||||||
|
|
||||||
|
class RequestHttp {
|
||||||
|
service: AxiosInstance;
|
||||||
|
public constructor(config: AxiosRequestConfig) {
|
||||||
|
// instantiation
|
||||||
|
this.service = axios.create(config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 请求拦截器
|
||||||
|
* 客户端发送请求 -> [请求拦截器] -> 服务器
|
||||||
|
* token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
|
||||||
|
*/
|
||||||
|
this.service.interceptors.request.use(
|
||||||
|
(config: CustomAxiosRequestConfig) => {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
// 当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { loading: false } 来控制
|
||||||
|
config.loading ?? (config.loading = true);
|
||||||
|
config.loading && showFullScreenLoading();
|
||||||
|
if (config.headers && typeof config.headers.set === "function") {
|
||||||
|
config.headers.set("x-access-token", userStore.token);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error: AxiosError) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 响应拦截器
|
||||||
|
* 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
|
||||||
|
*/
|
||||||
|
this.service.interceptors.response.use(
|
||||||
|
(response: AxiosResponse) => {
|
||||||
|
const { data } = response;
|
||||||
|
const userStore = useUserStore();
|
||||||
|
tryHideFullScreenLoading();
|
||||||
|
// 登陆失效
|
||||||
|
if (data.code == ResultEnum.OVERDUE) {
|
||||||
|
userStore.setToken("");
|
||||||
|
router.replace(LOGIN_URL);
|
||||||
|
ElMessage.error(data.message);
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
// 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
|
||||||
|
if (data.code && data.code !== ResultEnum.SUCCESS) {
|
||||||
|
ElMessage.error(data.message);
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
// 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
async (error: AxiosError) => {
|
||||||
|
const { response } = error;
|
||||||
|
tryHideFullScreenLoading();
|
||||||
|
// 请求超时 && 网络错误单独判断,没有 response
|
||||||
|
if (error.message.indexOf("timeout") !== -1) ElMessage.error("请求超时!请您稍后重试");
|
||||||
|
if (error.message.indexOf("Network Error") !== -1) ElMessage.error("网络错误!请您稍后重试");
|
||||||
|
// 根据服务器响应的错误状态码,做不同的处理
|
||||||
|
if (response) checkStatus(response.status);
|
||||||
|
// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
|
||||||
|
if (!window.navigator.onLine) router.replace("/500");
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 常用请求方法封装
|
||||||
|
*/
|
||||||
|
get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
|
||||||
|
return this.service.get(url, { params, ..._object });
|
||||||
|
}
|
||||||
|
post<T>(url: string, params?: object | string, _object = {}): Promise<ResultData<T>> {
|
||||||
|
return this.service.post(url, params, _object);
|
||||||
|
}
|
||||||
|
put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
|
||||||
|
return this.service.put(url, params, _object);
|
||||||
|
}
|
||||||
|
delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
|
||||||
|
return this.service.delete(url, { params, ..._object });
|
||||||
|
}
|
||||||
|
download(url: string, params?: object, _object = {}): Promise<BlobPart> {
|
||||||
|
return this.service.post(url, params, { ..._object, responseType: "blob" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new RequestHttp(config);
|
||||||
90
frontend/src/api/interface/index.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// 请求响应参数(不包含data)
|
||||||
|
export interface Result {
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求响应参数(包含data)
|
||||||
|
export interface ResultData<T = any> extends Result {
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页响应参数
|
||||||
|
export interface ResPage<T> {
|
||||||
|
list: T[];
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页请求参数
|
||||||
|
export interface ReqPage {
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件上传模块
|
||||||
|
export namespace Upload {
|
||||||
|
export interface ResFileUrl {
|
||||||
|
fileUrl: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录模块
|
||||||
|
export namespace Login {
|
||||||
|
export interface ReqLoginForm {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
export interface ResLogin {
|
||||||
|
accessToken: string;
|
||||||
|
}
|
||||||
|
export interface ResAuthButtons {
|
||||||
|
[key: string]: string[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户管理模块
|
||||||
|
export namespace User {
|
||||||
|
export interface ReqUserParams extends ReqPage {
|
||||||
|
username: string;
|
||||||
|
gender: number;
|
||||||
|
idCard: string;
|
||||||
|
email: string;
|
||||||
|
address: string;
|
||||||
|
createTime: string[];
|
||||||
|
status: number;
|
||||||
|
}
|
||||||
|
export interface ResUserList {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
gender: number;
|
||||||
|
user: { detail: { age: number } };
|
||||||
|
idCard: string;
|
||||||
|
email: string;
|
||||||
|
address: string;
|
||||||
|
createTime: string;
|
||||||
|
status: number;
|
||||||
|
avatar: string;
|
||||||
|
photo: any[];
|
||||||
|
children?: ResUserList[];
|
||||||
|
}
|
||||||
|
export interface ResStatus {
|
||||||
|
userLabel: string;
|
||||||
|
userValue: number;
|
||||||
|
}
|
||||||
|
export interface ResGender {
|
||||||
|
genderLabel: string;
|
||||||
|
genderValue: number;
|
||||||
|
}
|
||||||
|
export interface ResDepartment {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
children?: ResDepartment[];
|
||||||
|
}
|
||||||
|
export interface ResRole {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
children?: ResDepartment[];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* 主进程与渲染进程通信频道定义
|
|
||||||
* Definition of communication channels between main process and rendering process
|
|
||||||
*/
|
|
||||||
const ipcApiRoute = {
|
|
||||||
test: 'controller.example.test',
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ipcApiRoute }
|
|
||||||
26
frontend/src/api/modules/login.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Login } from "@/api/interface/index";
|
||||||
|
import { PORT1 } from "@/api/config/servicePort";
|
||||||
|
import http from "@/api";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name 登录模块
|
||||||
|
*/
|
||||||
|
// 用户登录
|
||||||
|
export const loginApi = (params: Login.ReqLoginForm) => {
|
||||||
|
return http.post<Login.ResLogin>(PORT1 + `/login`, params, { loading: false }); // 正常 post json 请求 ==> application/json
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取菜单列表
|
||||||
|
export const getAuthMenuListApi = () => {
|
||||||
|
return http.get<Menu.MenuOptions[]>(PORT1 + `/menu/list`, {}, { loading: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取按钮权限
|
||||||
|
export const getAuthButtonListApi = () => {
|
||||||
|
return http.get<Login.ResAuthButtons>(PORT1 + `/auth/buttons`, {}, { loading: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 用户退出登录
|
||||||
|
export const logoutApi = () => {
|
||||||
|
return http.post(PORT1 + `/logout`);
|
||||||
|
};
|
||||||
16
frontend/src/api/modules/upload.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Upload } from "@/api/interface/index";
|
||||||
|
import { PORT1 } from "@/api/config/servicePort";
|
||||||
|
import http from "@/api";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name 文件上传模块
|
||||||
|
*/
|
||||||
|
// 图片上传
|
||||||
|
export const uploadImg = (params: FormData) => {
|
||||||
|
return http.post<Upload.ResFileUrl>(PORT1 + `/file/upload/img`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 视频上传
|
||||||
|
export const uploadVideo = (params: FormData) => {
|
||||||
|
return http.post<Upload.ResFileUrl>(PORT1 + `/file/upload/video`, params);
|
||||||
|
};
|
||||||
71
frontend/src/api/modules/user.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { ResPage, User } from "@/api/interface/index";
|
||||||
|
import { PORT1 } from "@/api/config/servicePort";
|
||||||
|
import http from "@/api";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name 用户管理模块
|
||||||
|
*/
|
||||||
|
// 获取用户列表
|
||||||
|
export const getUserList = (params: User.ReqUserParams) => {
|
||||||
|
return http.post<ResPage<User.ResUserList>>(PORT1 + `/user/list`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取树形用户列表
|
||||||
|
export const getUserTreeList = (params: User.ReqUserParams) => {
|
||||||
|
return http.post<ResPage<User.ResUserList>>(PORT1 + `/user/tree/list`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新增用户
|
||||||
|
export const addUser = (params: { id: string }) => {
|
||||||
|
return http.post(PORT1 + `/user/add`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量添加用户
|
||||||
|
export const BatchAddUser = (params: FormData) => {
|
||||||
|
return http.post(PORT1 + `/user/import`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑用户
|
||||||
|
export const editUser = (params: { id: string }) => {
|
||||||
|
return http.post(PORT1 + `/user/edit`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除用户
|
||||||
|
export const deleteUser = (params: { id: string[] }) => {
|
||||||
|
return http.post(PORT1 + `/user/delete`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换用户状态
|
||||||
|
export const changeUserStatus = (params: { id: string; status: number }) => {
|
||||||
|
return http.post(PORT1 + `/user/change`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置用户密码
|
||||||
|
export const resetUserPassWord = (params: { id: string }) => {
|
||||||
|
return http.post(PORT1 + `/user/rest_password`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出用户数据
|
||||||
|
export const exportUserInfo = (params: User.ReqUserParams) => {
|
||||||
|
return http.download(PORT1 + `/user/export`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取用户状态字典
|
||||||
|
export const getUserStatus = () => {
|
||||||
|
return http.get<User.ResStatus[]>(PORT1 + `/user/status`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取用户性别字典
|
||||||
|
export const getUserGender = () => {
|
||||||
|
return http.get<User.ResGender[]>(PORT1 + `/user/gender`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取用户部门列表
|
||||||
|
export const getUserDepartment = () => {
|
||||||
|
return http.get<User.ResDepartment[]>(PORT1 + `/user/department`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取用户角色字典
|
||||||
|
export const getUserRole = () => {
|
||||||
|
return http.get<User.ResRole[]>(PORT1 + `/user/role`);
|
||||||
|
};
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import createAxios from '@/utils/http'
|
|
||||||
import { useUserInfoStore } from '@/stores/modules/user'
|
|
||||||
import { sm3Digest } from '@/assets/commjs/sm3.js'
|
|
||||||
import { sm2, encrypt } from '@/assets/commjs/sm2.js'
|
|
||||||
|
|
||||||
// 获取公钥
|
|
||||||
export const publicKey = (params?: any) => {
|
|
||||||
if (!params) {
|
|
||||||
const userInfo = useUserInfoStore()
|
|
||||||
params = {
|
|
||||||
loginName: encrypt(userInfo.loginName),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return createAxios({
|
|
||||||
url: '/user/generateSm2Key',
|
|
||||||
method: 'get',
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const pwdSm3 = async (pwd: any, loginName?: string) => {
|
|
||||||
let publicKeyStr = await publicKey(
|
|
||||||
loginName ? { loginName: encrypt(loginName) } : false,
|
|
||||||
)
|
|
||||||
let sm3Pwd = sm3Digest(pwd) //SM3加密
|
|
||||||
return sm2(sm3Pwd + '|' + pwd, publicKeyStr.data, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
//登录获取token
|
|
||||||
export const login = async (params: any) => {
|
|
||||||
params.password = await pwdSm3(params.password, params.username)
|
|
||||||
params.username = encrypt(params.username)
|
|
||||||
return createAxios({
|
|
||||||
url: '/pqs-auth/oauth/token',
|
|
||||||
method: 'post',
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//获取用户信息
|
|
||||||
export const getUserById = () => {
|
|
||||||
const userInfo = useUserInfoStore()
|
|
||||||
return createAxios({
|
|
||||||
url: '/user-boot/user/getUserById?id=' + userInfo.userIndex,
|
|
||||||
method: 'get',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新token
|
|
||||||
export function refreshToken(): Promise<any> {
|
|
||||||
const userInfo = useUserInfoStore()
|
|
||||||
return login({
|
|
||||||
grant_type: 'refresh_token',
|
|
||||||
refresh_token: userInfo.refresh_token,
|
|
||||||
username: userInfo.loginName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
/**
|
|
||||||
* 国密摘要算法(SM3)
|
|
||||||
* @param str:raw string
|
|
||||||
* @return the 256-bit hex string produced by SM3 from a raw string
|
|
||||||
*/
|
|
||||||
function sm3Digest(str) {
|
|
||||||
//1. 转换为二进制数组
|
|
||||||
var binArr = str2bin(str2rstr_utf8(str));
|
|
||||||
//2. 填充
|
|
||||||
var groupNum = alignSM3(binArr, str.length);
|
|
||||||
//3. 迭代压缩
|
|
||||||
var v = new Array(8);//初始值
|
|
||||||
v[0] = 0x7380166f;
|
|
||||||
v[1] = 0x4914b2b9;
|
|
||||||
v[2] = 0x172442d7;
|
|
||||||
v[3] = 0xda8a0600;
|
|
||||||
v[4] = 0xa96f30bc;
|
|
||||||
v[5] = 0x163138aa;
|
|
||||||
v[6] = 0xe38dee4d;
|
|
||||||
v[7] = 0xb0fb0e4e;
|
|
||||||
//按 512bit 分组进行压缩
|
|
||||||
for (var i = 0; i < groupNum; i++) {
|
|
||||||
v = compress(v, binArr, i);
|
|
||||||
}
|
|
||||||
return word2str(v, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将数组转换为字符串。数组长度不定,每个元素为 32bit 的数字。
|
|
||||||
* @param words:数组,每个元素为 32bit 的数字
|
|
||||||
* @param seperator:在每个数组元素转换得到的字符串之间的分隔符
|
|
||||||
*/
|
|
||||||
function word2str(words, seperator) {
|
|
||||||
var prefix = Array(8).join('0');
|
|
||||||
for (var i = 0; i < words.length; i++) {
|
|
||||||
//若 hex 不足 8 位,则高位补 0
|
|
||||||
words[i] = (prefix + (words[i] >>> 0).toString(16)).slice(-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
return words.join(seperator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将字符串转换为二进制数组,默认字符串编码为 UTF-8,且范围在 0x00~0xFF 内。
|
|
||||||
* 若某些字符的编码超过此范围,则会只保留最低字节。加密可正常进行,但加密结果有误。
|
|
||||||
* 每个数组元素包含 4 个字符,即 32 bit。
|
|
||||||
* @param 字符串
|
|
||||||
* @return 数组,长度为(字符串长度 / 4),每个元素为 32bit 的数字
|
|
||||||
*/
|
|
||||||
function str2bin(str) {
|
|
||||||
var binary = new Array(str.length >> 2);
|
|
||||||
for (var i = 0; i < str.length * 8; i += 8) {
|
|
||||||
binary[i >> 5] |= (str.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
|
|
||||||
}
|
|
||||||
return binary;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对明文的二进制串进行填充
|
|
||||||
* <pre>
|
|
||||||
* | 满足 mod 512 = 448 | 固定 64 位 |
|
|
||||||
* | 明文二进制 |填充部分|明文二进制串的长度的二进制表示|
|
|
||||||
* xxxxxxxxxxxx 10.....0 0...........................xx
|
|
||||||
* </pre>
|
|
||||||
* @param arr:数组,每个元素为 32bit 的数字
|
|
||||||
* @param strLen:明文字符串长度
|
|
||||||
* @return 数组,每个元素为 32bit 的数字,数组长度为 16 的倍数(包括 16)
|
|
||||||
*/
|
|
||||||
function alignSM3(arr, strLen) {
|
|
||||||
//在明文二进制串后面拼接 1000 0000
|
|
||||||
arr[strLen >> 2] |= 0x80 << (24 - strLen % 4 * 8);
|
|
||||||
var groupNum = ((strLen + 8) >> 6) + 1;//以 512bit 为一组,总的组数
|
|
||||||
var wordNum = groupNum * 16;//一个 word 32bit,总的 word 数
|
|
||||||
|
|
||||||
for (var i = (strLen >> 2) + 1; i < wordNum; i++) {
|
|
||||||
arr[i] = 0;
|
|
||||||
}
|
|
||||||
arr[wordNum - 1] = strLen * 8;//在末尾填上明文的二进制长度
|
|
||||||
|
|
||||||
return groupNum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 压缩函数中的置换函数
|
|
||||||
*/
|
|
||||||
function p0(x) {
|
|
||||||
return x ^ bitRol(x, 9) ^ bitRol(x, 17);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 压缩函数中的置换函数
|
|
||||||
*/
|
|
||||||
function p1(x) {
|
|
||||||
return x ^ bitRol(x, 15) ^ bitRol(x, 23);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 循环左移
|
|
||||||
*/
|
|
||||||
function bitRol(input, n) {
|
|
||||||
return (input << n) | (input >>> (32 - n));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 压缩函数
|
|
||||||
*/
|
|
||||||
function compress(v, binArr, i) {
|
|
||||||
//将消息分组扩展成 132 个字
|
|
||||||
var w1 = new Array(68);
|
|
||||||
var w2 = new Array(64);
|
|
||||||
for (var j = 0; j < 68; j++) {
|
|
||||||
if (j < 16) {
|
|
||||||
w1[j] = binArr[i * 16 + j];
|
|
||||||
} else {
|
|
||||||
w1[j] = p1(w1[j-16] ^ w1[j-9] ^ bitRol(w1[j-3], 15)) ^ bitRol(w1[j-13], 7) ^ w1[j-6];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var j = 0; j < 64; j++) {
|
|
||||||
w2[j] = w1[j] ^ w1[j+4];
|
|
||||||
}
|
|
||||||
|
|
||||||
//压缩
|
|
||||||
var a = v[0];
|
|
||||||
var b = v[1];
|
|
||||||
var c = v[2];
|
|
||||||
var d = v[3];
|
|
||||||
var e = v[4];
|
|
||||||
var f = v[5];
|
|
||||||
var g = v[6];
|
|
||||||
var h = v[7];
|
|
||||||
var ss1;
|
|
||||||
var ss2;
|
|
||||||
var tt1;
|
|
||||||
var tt2;
|
|
||||||
for (var j = 0; j < 64; j++) {
|
|
||||||
ss1 = bitRol(addAll(bitRol(a, 12) , e , bitRol(t(j), j)), 7);
|
|
||||||
ss2 = ss1 ^ bitRol(a, 12);
|
|
||||||
tt1 = addAll(ff(a, b, c, j) , d , ss2 , w2[j]);
|
|
||||||
tt2 = addAll(gg(e, f, g, j) , h , ss1 , w1[j]);
|
|
||||||
d = c;
|
|
||||||
c = bitRol(b, 9);
|
|
||||||
b = a;
|
|
||||||
a = tt1;
|
|
||||||
h = g;
|
|
||||||
g = bitRol(f, 19);
|
|
||||||
f = e;
|
|
||||||
e = p0(tt2);
|
|
||||||
}
|
|
||||||
v[0] ^= a;
|
|
||||||
v[1] ^= b;
|
|
||||||
v[2] ^= c;
|
|
||||||
v[3] ^= d;
|
|
||||||
v[4] ^= e;
|
|
||||||
v[5] ^= f;
|
|
||||||
v[6] ^= g;
|
|
||||||
v[7] ^= h;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 常量 T 随 j 的不同而不同
|
|
||||||
*/
|
|
||||||
function t(j) {
|
|
||||||
if (0 <= j && j < 16) {
|
|
||||||
return 0x79CC4519;
|
|
||||||
} else if (j < 64) {
|
|
||||||
return 0x7A879D8A;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 布尔函数,随 j 的变化取不同的表达式
|
|
||||||
*/
|
|
||||||
function ff(x, y, z, j) {
|
|
||||||
if (0 <= j && j < 16) {
|
|
||||||
return x ^ y ^ z;
|
|
||||||
} else if (j < 64) {
|
|
||||||
return (x & y) | (x & z) | (y & z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 布尔函数,随 j 的变化取不同的表达式
|
|
||||||
*/
|
|
||||||
function gg(x, y, z, j) {
|
|
||||||
if (0 <= j && j < 16) {
|
|
||||||
return x ^ y ^ z;
|
|
||||||
} else if (j < 64) {
|
|
||||||
return (x & y) | (~x & z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 两数相加
|
|
||||||
* 避免某些 js 引擎的 32 位加法的 bug
|
|
||||||
*/
|
|
||||||
function safe_add(x, y) {
|
|
||||||
var lsw = ( x & 0xFFFF ) + (y & 0xFFFF);
|
|
||||||
var msw = ( x >> 16 ) + (y >> 16) + (lsw >> 16);
|
|
||||||
return (msw << 16) | ( lsw & 0xFFFF );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将所有参数相加
|
|
||||||
*/
|
|
||||||
function addAll() {
|
|
||||||
var sum = 0;
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
sum = safe_add(sum, arguments[i]);
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UTF-16 --> UTF-8
|
|
||||||
*/
|
|
||||||
function str2rstr_utf8(input) {
|
|
||||||
var output = "" ;
|
|
||||||
var i = -1 ;
|
|
||||||
var x, y ;
|
|
||||||
|
|
||||||
while(++ i < input.length) {
|
|
||||||
//按 UTF-16 解码
|
|
||||||
x = input.charCodeAt(i);
|
|
||||||
y = i + 1 < input.length ? input .charCodeAt (i + 1) : 0 ;
|
|
||||||
if( 0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF ) {
|
|
||||||
x = 0x10000 + ((x & 0x03FF) << 10 ) + (y & 0x03FF);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
//按 UTF-8 编码
|
|
||||||
if( x <= 0x7F ) {
|
|
||||||
output += String.fromCharCode(x);
|
|
||||||
}
|
|
||||||
else if(x <= 0x7FF) {
|
|
||||||
output += String.fromCharCode(
|
|
||||||
0xC0 | ((x >>> 6 ) & 0x1F),
|
|
||||||
0x80 | ( x & 0x3F ));
|
|
||||||
} else if(x <= 0xFFFF) {
|
|
||||||
output += String.fromCharCode(
|
|
||||||
0xE0 | ((x >>> 12) & 0x0F ),
|
|
||||||
0x80 | ((x >>> 6 ) & 0x3F),
|
|
||||||
0x80 | ( x & 0x3F ));
|
|
||||||
} else if(x <= 0x1FFFFF) {
|
|
||||||
output += String.fromCharCode(
|
|
||||||
0xF0 | ((x >>> 18) & 0x07 ),
|
|
||||||
0x80 | ((x >>> 12) & 0x3F),
|
|
||||||
0x80 | ((x >>> 6 ) & 0x3F),
|
|
||||||
0x80 | ( x & 0x3F ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
sm3Digest
|
|
||||||
}
|
|
||||||
|
|
||||||
BIN
frontend/src/assets/fonts/DIN.otf
Normal file
BIN
frontend/src/assets/fonts/MetroDF.ttf
Normal file
BIN
frontend/src/assets/fonts/YouSheBiaoTiHei.ttf
Normal file
14
frontend/src/assets/fonts/font.scss
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: YouSheBiaoTiHei;
|
||||||
|
src: url("./YouSheBiaoTiHei.ttf");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: MetroDF;
|
||||||
|
src: url("./MetroDF.ttf");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: DIN;
|
||||||
|
src: url("./DIN.Otf");
|
||||||
|
}
|
||||||
48
frontend/src/assets/iconfont/iconfont.scss
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: iconfont; /* Project id 2667653 */
|
||||||
|
src: url("iconfont.ttf?t=1694681005434") format("truetype");
|
||||||
|
}
|
||||||
|
.iconfont {
|
||||||
|
font-family: iconfont !important;
|
||||||
|
font-size: 20px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.icon-yiwen::before {
|
||||||
|
font-size: 15px;
|
||||||
|
content: "\e693";
|
||||||
|
}
|
||||||
|
.icon-xiala::before {
|
||||||
|
content: "\e62b";
|
||||||
|
}
|
||||||
|
.icon-tuichu::before {
|
||||||
|
content: "\e645";
|
||||||
|
}
|
||||||
|
.icon-xiaoxi::before {
|
||||||
|
font-size: 21.2px;
|
||||||
|
content: "\e61f";
|
||||||
|
}
|
||||||
|
.icon-zhuti::before {
|
||||||
|
font-size: 22.4px;
|
||||||
|
content: "\e638";
|
||||||
|
}
|
||||||
|
.icon-sousuo::before {
|
||||||
|
content: "\e611";
|
||||||
|
}
|
||||||
|
.icon-contentright::before {
|
||||||
|
content: "\e8c9";
|
||||||
|
}
|
||||||
|
.icon-contentleft::before {
|
||||||
|
content: "\e8ca";
|
||||||
|
}
|
||||||
|
.icon-fangda::before {
|
||||||
|
content: "\e826";
|
||||||
|
}
|
||||||
|
.icon-suoxiao::before {
|
||||||
|
content: "\e641";
|
||||||
|
}
|
||||||
|
.icon-zhongyingwen::before {
|
||||||
|
content: "\e8cb";
|
||||||
|
}
|
||||||
BIN
frontend/src/assets/iconfont/iconfont.ttf
Normal file
@@ -1 +0,0 @@
|
|||||||
<svg t="1723085514271" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1125" width="200" height="200"><path d="M765.22 393.6H184.15c-8.28 0-15-6.72-15-15s6.72-15 15-15h581.07c37.38 0 67.8-30.41 67.8-67.8s-30.41-67.8-67.8-67.8-67.8 30.41-67.8 67.8c0 8.28-6.72 15-15 15s-15-6.72-15-15c0-53.93 43.87-97.8 97.8-97.8s97.8 43.87 97.8 97.8-43.87 97.8-97.8 97.8zM783.99 809.79c-43.58 0-79.03-35.45-79.03-79.03 0-8.28 6.72-15 15-15s15 6.72 15 15c0 27.04 22 49.03 49.03 49.03s49.03-22 49.03-49.03-22-49.03-49.03-49.03H297.67c-8.28 0-15-6.72-15-15s6.72-15 15-15h486.32c43.58 0 79.03 35.45 79.03 79.03s-35.45 79.03-79.03 79.03z" fill="#333333" p-id="1126"></path><path d="M481.66 306.63H312.79c-8.28 0-15-6.72-15-15s6.72-15 15-15h168.87c8.28 0 15 6.72 15 15s-6.72 15-15 15zM227.46 681.49h-43c-8.28 0-15-6.72-15-15s6.72-15 15-15h43c8.28 0 15 6.72 15 15s-6.72 15-15 15zM777.81 598.8h-43c-8.28 0-15-6.72-15-15s6.72-15 15-15h43c8.28 0 15 6.72 15 15s-6.72 15-15 15zM383.78 761H241.87c-8.28 0-15-6.72-15-15s6.72-15 15-15h141.91c8.28 0 15 6.72 15 15s-6.72 15-15 15zM848.02 452.99H613.98c-8.28 0-15-6.72-15-15s6.72-15 15-15h234.04c8.28 0 15 6.72 15 15s-6.72 15-15 15z" fill="#333333" p-id="1127"></path><path d="M492 535.33m-243.33 0a243.33 243.33 0 1 0 486.66 0 243.33 243.33 0 1 0-486.66 0Z" fill="#FFFFFF" p-id="1128"></path><path d="M492 793.67c-69 0-133.88-26.87-182.67-75.66-48.79-48.79-75.66-113.67-75.66-182.67s26.87-133.88 75.66-182.67C358.12 303.88 423 277.01 492 277.01s133.88 26.87 182.67 75.66c48.79 48.79 75.66 113.67 75.66 182.67s-26.87 133.88-75.66 182.67C625.88 766.8 561 793.67 492 793.67zM492 307c-60.99 0-118.33 23.75-161.46 66.88-43.13 43.13-66.88 100.47-66.88 161.46s23.75 118.33 66.88 161.46c43.13 43.13 100.47 66.88 161.46 66.88s118.33-23.75 161.46-66.88 66.88-100.47 66.88-161.46-23.75-118.33-66.88-161.46S552.99 307 492 307z" fill="#333333" p-id="1129"></path><path d="M583.85 517.56l-58.52-11.99 24.71-97.1c5.39-21.17-20.94-35.62-35.9-19.71L393.3 517.17c-11.35 12.06-5.06 31.89 11.16 35.22l58.52 11.99-24.71 97.1c-5.39 21.17 20.94 35.62 35.9 19.71l120.84-128.41c11.35-12.06 5.06-31.89-11.16-35.22z" fill="#52E217" p-id="1130"></path><path d="M458.92 702.95c-5.94 0-11.96-1.5-17.55-4.57-14.68-8.06-21.77-24.38-17.64-40.6l20.83-81.86-43.12-8.83c-13.02-2.67-23.19-11.82-27.21-24.49s-0.97-26.02 8.13-35.7L503.2 378.49c11.48-12.2 29.05-14.97 43.73-6.92 14.68 8.06 21.77 24.38 17.64 40.6l-20.83 81.86 43.12 8.83c13.02 2.67 23.19 11.82 27.21 24.49s0.97 26.02-8.13 35.7L485.1 691.46c-7.1 7.54-16.53 11.48-26.17 11.49z m70.43-305.96c-1.33 0-2.84 0.5-4.3 2.04L404.21 527.44c-2.15 2.29-1.79 4.79-1.38 6.07 0.41 1.29 1.55 3.54 4.63 4.17l58.52 11.99c4.02 0.82 7.52 3.25 9.7 6.73 2.18 3.48 2.84 7.69 1.83 11.66l-24.71 97.1c-1.02 4.02 1.44 6.05 3 6.91 1.56 0.86 4.6 1.84 7.44-1.18l120.84-128.41c2.15-2.29 1.79-4.79 1.38-6.07-0.41-1.29-1.55-3.54-4.63-4.17l-58.52-11.99c-4.02-0.82-7.52-3.25-9.7-6.73a14.993 14.993 0 0 1-1.83-11.66l24.71-97.1c1.02-4.02-1.44-6.05-3-6.91-0.76-0.42-1.87-0.87-3.14-0.87z" fill="#333333" p-id="1131"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.0 KiB |
1
frontend/src/assets/icons/xianxingdaoyu.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M574 342.8m-38.3 0a38.3 38.3 0 1 0 76.6 0 38.3 38.3 0 1 0-76.6 0Z" fill="#F2B843" /><path d="M627 697c15.2-20.7 45.3-294-45-383.3-3-6.1-0.4-13.5 5.7-16.5 6.2-3 13.5-0.5 16.5 5.7C689.8 370.2 719.9 573 705.1 697H627z" fill="#EA800C" /><path d="M617.8 307.4m-38.3 0a38.3 38.3 0 1 0 76.6 0 38.3 38.3 0 1 0-76.6 0Z" fill="#F2B843" /><path d="M608 272.5L461 502.8c-33.6-47.5-37.2-112.5-3.9-164.6 33.2-52.1 93.7-76.2 150.9-65.7z" fill="#3DC38A" /><path d="M742.5 132.3L569.9 299.8c-19.2-47.5-9.1-103.9 29.9-141.8 39.1-37.9 95.8-46.3 142.7-25.7z" fill="#2F9B77" /><path d="M608.7 289.2l-239.6 21.1c15.1-49 58.5-86.4 112.7-91.1 54.2-4.8 103.5 24.4 126.9 70z" fill="#2F9B77" /><path d="M594.7 269.9L408.5 168.4c35-28.6 85.2-34.8 127.3-11.9 42.1 23 64 68.6 58.9 113.4z" fill="#3DC38A" /><path d="M825.5 331.8l-271.4-31.4c28-51 84.9-82.7 146.3-75.6 61.3 7 109.5 51 125.1 107z" fill="#3DC38A" /><path d="M75.3 868.9c0-86.5 104.3-173 233-173s233 86.5 233 173h-466z" fill="#2F9B77" /><path d="M938.2 868.9c0-116.2-130.9-232.3-292.3-232.3S353.5 752.7 353.5 868.9h584.7z" fill="#3DC38A" /><path d="M858.9 701.5c-28.1-23.1-60.4-41.4-95.9-54.3-14.5-5.3-29.3-9.5-44.3-12.8 0.2-51.3-5.5-106.3-16.2-155.9-11.9-54.8-29.5-101.6-51.2-136.3 5.6-5.3 9.7-11.8 12.2-19.1l160.8 18.6c0.4 0 0.8 0.1 1.2 0.1 2.9 0 5.7-1.3 7.6-3.5 2.2-2.5 2.9-6 2-9.2-8.3-29.8-25.1-56.3-48.5-76.7-24-20.9-53.5-33.9-85.1-37.5-9.7-1.1-19.3-1.3-28.9-0.7l76.8-74.6c2.4-2.3 3.5-5.7 2.9-8.9-0.6-3.3-2.8-6-5.8-7.4-52.3-23-112.6-12.1-153.7 27.7-7.2 7-13.6 14.7-19.1 23.1-9.4-10.5-20.6-19.4-33.1-26.2-44.7-24.3-99-19.2-138.4 12.9-2.6 2.1-3.9 5.4-3.6 8.7s2.2 6.3 5.2 7.9l62.5 34c-50.2 9.8-91.2 46.2-106.6 96-1 3.2-0.3 6.6 1.8 9.2 1.9 2.4 4.8 3.7 7.8 3.7h0.9l94.5-8.3c-5.8 6.4-11 13.3-15.8 20.8-17.2 26.9-25.7 57.9-24.7 89.7 1 31 11 60.8 28.9 86 1.9 2.7 4.9 4.2 8.2 4.2h0.2c3.3-0.1 6.4-1.8 8.2-4.6L549 383.9c7.5 4.6 16.1 7.1 25.2 7.1 13.4 0 25.9-5.5 34.8-14.7 27.2 70.9 29.2 175.3 21.8 250.6-34.9 1.5-69.1 8.3-101.8 20.2-35.5 12.9-67.8 31.2-95.9 54.3-3.2 2.6-6.3 5.3-9.4 8.1-35.7-15.5-75.4-23.6-115.1-23.6-63.1 0-123.8 19.9-170.9 56.1-45.8 35.3-72.1 81.5-72.1 126.9 0 5.5 4.5 10 10 10h862.9c5.5 0 10-4.5 10-10-0.3-59.7-32.8-120.7-89.6-167.4z m-226.2-370c-3.3 2.1-7 3.4-10.9 3.9-1-6.4-3.2-12.5-6.5-17.9l27.6 3.2c-2.3 4.4-5.8 8.1-10.2 10.8z m66.6-96.8c27.6 3.2 53.3 14.5 74.3 32.7 16.6 14.5 29.4 32.4 37.5 52.6l-152.7-17.7c-0.4-0.1-0.8-0.2-1.2-0.2-0.4 0-0.8-0.1-1.2-0.1l-65.3-7.6c-1-0.3-2-0.4-2.9-0.3l-5.5-0.6c-0.1 0-0.2-0.1-0.3-0.1-0.7-0.1-1.3-0.2-2-0.2l-8.8-1c5.3-7.5 11.3-14.5 18-20.8 0.5-0.4 1-0.8 1.4-1.3 8.7-8 18.4-14.9 29.1-20.5 8-4.2 16.4-7.6 24.9-10.2 0.5-0.1 0.9-0.2 1.4-0.4 17-4.8 35.1-6.4 53.3-4.3z m-92.5-69.4c31.5-30.5 76.2-41.2 117.4-29l-87 84.4c-9.3 2.9-18.4 6.6-27.1 11.2-2.2 1.2-4.3 2.4-6.5 3.6-2.8-15.7-8.5-30.8-17-44.4 5.5-9.5 12.3-18.2 20.2-25.8z m-75.8 0.1c14.4 7.9 26.4 18.6 35.7 31.9 10.5 15.1 16.8 32.7 18.4 51-1.2 1-2.5 2-3.6 3l-74-40.3c-0.8-0.7-1.8-1.2-2.8-1.5l-77.2-42.1c31.3-18.8 70.6-20 103.5-2z m-48.2 63.8c5.2-0.5 10.3-0.6 15.4-0.4l68.2 37.1c-5.1 5.7-9.9 11.8-14.2 18.2l-60 5.3-108.1 9.5c17.8-39.1 55-66 98.7-69.7zM461.2 484c-24.3-43.9-23-97.5 4.5-140.6 8.5-13.4 19-24.9 31.3-34.4l48.3-4.2s0 0.1 0.1 0.1c1.5 3 4.4 5 7.7 5.3l17.8 2.1-32.1 50.3c-0.6 0.7-1 1.4-1.4 2.2L461.2 484zM574 371c-5.2 0-10.1-1.4-14.4-3.9l29.3-45.9 1.1-1.8c7.6 5.2 12.4 13.9 12.4 23.4v2.1c-0.1 1.8-0.5 3.8-1.1 6.1-0.2 0.6-0.4 1.1-0.6 1.7-4.2 10.9-14.8 18.3-26.7 18.3z m47.8-15.5c4.3-0.3 8.6-1.3 12.6-2.7 20.5 32.6 37.2 77.3 48.6 129.9 10.2 47.1 15.7 99.1 15.8 148-15.9-2.5-31.9-3.8-48.1-4 2.7-28.8 5.6-76.5 1.8-130.4-4-57.6-14.3-104.8-30.7-140.8zM149.6 757.9c43.6-33.5 99.9-52 158.7-52 34.2 0 68.3 6.5 99.4 18.8-38.4 40-61.1 87.2-63.9 134.2h-258c3.6-35.9 26.4-72.2 63.8-101z m391.7 101H363.8c3.4-50.5 32.8-101.8 81.7-142 26.4-21.7 56.6-38.8 90-50.9 35.4-12.8 72.5-19.4 110.4-19.4 37.9 0 75 6.5 110.3 19.4 33.4 12.1 63.6 29.3 90 50.9 48.9 40.2 78.2 91.5 81.6 142H541.3z" fill="#4D3500" /></svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
1
frontend/src/assets/icons/xianxingdiqiu.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
1
frontend/src/assets/icons/xianxingditu.svg
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
1
frontend/src/assets/icons/xianxingfanchuan.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M489.6 541.1V166l283.5 375.1H489.6" fill="#3DC38A" /><path d="M489.6 101.3l-323.2 491h323.2z" fill="#F2B843" /><path d="M489.6 715.7c-16.3 0-29.6-13.2-29.6-29.6V95c0-16.3 13.2-29.6 29.6-29.6 16.3 0 29.6 13.2 29.6 29.6v591.1c-0.1 16.3-13.3 29.6-29.6 29.6z" fill="#EA800C" /><path d="M489.6 608.4H145.3c-16.3 0-29.6-13.2-29.6-29.6 0-16.3 13.2-29.6 29.6-29.6h344.3c16.3 0 29.6 13.2 29.6 29.6-0.1 16.3-13.3 29.6-29.6 29.6z" fill="#EA800C" /><path d="M783.8 557.2H503.1c-16.3 0-29.6-13.2-29.6-29.6 0-16.3 13.2-29.6 29.6-29.6h280.7c16.3 0 29.6 13.2 29.6 29.6 0 16.3-13.3 29.6-29.6 29.6z" fill="#EA800C" /><path d="M752.4 759.8l-67-88.8c-7.9-10.5-20.3-16.7-33.5-16.7H302c-18.3 0-34.5 11.9-40 29.3l-25 76.2h515.4z" fill="#2F9B77" /><path d="M920.8 704.6c15.6-0.8 26.8 14.8 21.1 29.3l-54.5 138.8-28.6 72.8H133.7L81.5 775c-4.1-13.4 5.5-27 19.4-27.8l143.3-7.5 676.6-35.1z" fill="#3DC38A" /><path d="M802.3 791.8m-27 0a27 27 0 1 0 54 0 27 27 0 1 0-54 0Z" fill="#F2B843" /><path d="M947.5 707.7c-6.3-8.7-16.4-13.6-27.2-13.1l-196.8 10.2-30-39.9c-9.8-12.9-25.3-20.6-41.5-20.6H529.2v-77.1h254.7c21.8 0 39.6-17.8 39.6-39.6S805.7 488 783.9 488h-38.3L529.2 201.7V95c0-21.8-17.8-39.6-39.6-39.6S450 73.2 450 95v48.2L189.3 539.3h-44c-21.8 0-39.6 17.8-39.6 39.6s17.8 39.6 39.6 39.6H450v25.8H302c-22.7 0-42.6 14.6-49.5 36.2l-16.2 49.6-135.9 7.1c-9.7 0.6-18.5 5.5-24.1 13.5-5.6 8-7.1 17.9-4.3 27.2l52.2 170.5c1.3 4.2 5.2 7.1 9.6 7.1h725.1c4.1 0 7.8-2.5 9.3-6.3l28.6-72.8 54.5-138.8c3.8-10 2.4-21.2-3.8-29.9zM720.5 488H529.2V234.9L720.5 488zM450 179.6v359.7H213.3L450 179.6z m20 428.8c0-5.5-4.5-10-10-10-0.5 0-0.9 0-1.3 0.1H145.3c-10.8 0-19.6-8.8-19.6-19.6s8.8-19.6 19.6-19.6H460c5.5 0 10-4.5 10-10V95c0-10.8 8.8-19.6 19.6-19.6s19.6 8.8 19.6 19.6v403c0 5.5 4.5 10 10 10h264.7c10.8 0 19.6 8.8 19.6 19.6s-8.8 19.6-19.6 19.6H519.2c-5.5 0-10 4.5-10 10v87.1H470v-35.9z m-198.5 78.3s0-0.1 0 0c4.3-13.4 16.5-22.4 30.5-22.4h350c9.9 0 19.5 4.8 25.5 12.7l21.9 29.1L257.7 729l13.8-42.3z m661.1 43.5L878.1 869 852 935.5H141.1l-50-163.4c-1-3.4-0.5-7 1.6-9.9 2-2.9 5.3-4.8 8.8-5l141.5-7.4h0.6c0.6 0 1.1-0.1 1.7-0.1l473.6-24.6h0.7l201.7-10.5c4-0.2 7.6 1.5 9.9 4.8 2.3 3.2 2.8 7.2 1.4 10.8z" fill="#4D3500" /><path d="M802.3 754.8c-20.4 0-37 16.6-37 37s16.6 37 37 37 37-16.6 37-37-16.6-37-37-37z m0 54c-9.4 0-17-7.6-17-17s7.6-17 17-17 17 7.6 17 17-7.6 17-17 17z" fill="#4D3500" /></svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
1
frontend/src/assets/icons/xianxingfeiji.svg
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
1
frontend/src/assets/icons/xianxinglvhangriji.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M252.8 862.9h-73.4c-6.8 0-12.4-5.6-12.4-12.4V134.4c0-6.8 5.6-12.4 12.4-12.4h73.4l5.3 6.8v728.8l-5.3 5.3z" fill="#F2B843" /><path d="M338.5 942l-42.9-18.1-42.8 18.1v-79.1l7.5-5.3h71.3l7 5.3-0.1 79.1z" fill="#EA800C" /><path d="M844.6 327.9h-40.7l-5.3-5.8v-93.3l5.3-7.2h40.7c6.8 0 12.4 5.6 12.4 12.4v81.5c0 6.9-5.6 12.4-12.4 12.4z" fill="#F2B843" /><path d="M844.6 540.5h-40.7l-5.3-7.3v-90.6l5.3-8.4h40.7c6.8 0 12.4 5.6 12.4 12.4v81.5c0 6.8-5.6 12.4-12.4 12.4z" fill="#EA800C" /><path d="M844.6 753h-40.7l-5.3-6.7v-92.1l5.3-7.5h40.7c6.8 0 12.4 5.6 12.4 12.4v81.5c0 6.8-5.6 12.4-12.4 12.4z" fill="#2F9B77" /><path d="M791.8 862.9h-539V122h539.1c6.7 0 12 5.4 12 12v716.8c0 6.7-5.4 12.1-12.1 12.1z" fill="#3DC38A" /><path d="M680.3 661.1H343.7c-14 0-25.5-11.5-25.5-25.5s11.5-25.5 25.5-25.5h336.6c14 0 25.5 11.5 25.5 25.5s-11.5 25.5-25.5 25.5zM680.3 779.8H343.7c-14 0-25.5-11.5-25.5-25.5s11.5-25.5 25.5-25.5h336.6c14 0 25.5 11.5 25.5 25.5s-11.5 25.5-25.5 25.5z" fill="#2F9B77" /><path d="M594.8 511.1c-79.2 45.7-180.5 18.6-226.3-60.6S350 270 429.2 224.2s180.5-18.6 226.3 60.6 18.5 180.6-60.7 226.3z" fill="#3DC38A" /><path d="M523.7 318.1c-1.2 0.3-3.5 0.9-4.5 1.7-1.2 1 0.7 2.3 0 2.9-1.2 1-2.1 2.7-4.7 2.6-5.5-0.3-13.9-7.5-19-10.2 8.1-8.3-4.7-9.6-9.7-9.1-6.5 0.5-17.7 0-23.5 3.2-4.1 2.3-9.5 11.7-12.8 15.5-4.6 5.2-9.1 9.8-12.1 16-10.9 23 5.9 49.4 32.2 46.3 7.3-0.9 14.9-5.5 21.4 0.6 4.8 4.5 2.3 8.7 3.5 13.9 1.1 4.5 6.1 7.3 7.8 11.4 3.3 7.9 1.2 12.9-1.1 20.2-3.1 9.6 4.9 21 8 30.2 1.6 4.7 6.1 17.7 10.7 19.8 7.1 3.2 18.1-6.4 22.4-11.6 3.3-4.1 2.2-8.2 4.4-12 2.4-4.1 5.4-5.3 7.1-10.6 1.8-5.7 3.7-7.1 7.5-11.3 6.2-6.7 5.2-10.6 2.7-18.6-5.1-16.5 13.5-24.2 21.8-36.3 8.7-12.6-8.2-8.4-14.8-12.8-6.8-4.4-9.8-12.9-13.1-19.9s-6-17.5-11.4-23.3c-4.2-4.3-16.9-6.4-22.8-8.6zM609.2 428.8c-2.6 8.9-5.3 17.8-7.9 26.7-1.8 6.2-8.4 26.6-17.5 13.6-3.5-5-0.6-11.4 1.3-16 2.4-5.8-0.9-8.7 0.9-14.1 2-6.2 10-6.4 13.8-10.8 2.7-3 4.3-7.9 6.2-11.5 1 4 2.1 8.1 3.2 12.1z" fill="#F2B843" /><path d="M655.4 284.9c-28.5-49.4-78.7-78.5-131.6-82.4l-21.6 27.2-46.4 16.3-12.5 30.2 19.2 26.9 2-5.7c3.4-9.5 12.4-15.8 22.5-15.8h31.7l36.4 12.8 12.5 12v7.6l17.4 33.4 5.1-1.9c11-4 20.9-10.4 29.2-18.7l-4.2-6.3c-1.4-2 0.1-4.7 2.5-4.7h10.2c3 0 5.8 1.3 7.7 3.6l8.3 9.7c0.8 0.9 1.4 2 1.8 3.1l9.1 25.2 4.4-4.4c2.7-2.7 4.1-6.5 4.2-10.4 0-3.1 1.4-6.1 3.7-8.2l3.4-3c0.8-0.8 1.8-1.4 2.8-1.8-3.6-15.3-9.5-30.4-17.8-44.7zM407.6 291.3l7.9-8.5c5.8-6.2 7.4-15.2 4.2-23-3.4-8.3-10.9-13.6-19.2-14.8-29.2 26.5-47.4 62.2-52.6 100 6.2 5.4 12.6 11.2 12.6 11.7-0.1 1 23.2-2.9 23.2-2.9l-12-17.5 17.2-12.3 18.7-32.7zM423.8 456.4c7.5-2.4 11.6-10.4 9.1-17.9l-2.1-6.6c-0.9-2.9-0.9-5.9 0-8.8 2.7-8.1-2.4-16.8-10.8-18.4l-16.8-3.3-30.6-23.2-25.3 8.2c2.5 21.9 9.4 43.7 21.2 64.1 10.9 18.8 24.9 34.7 41 47.4l7.5-39.3 6.8-2.2z" fill="#2F9B77" /><path d="M844.6 337.9c12.4 0 22.4-10 22.4-22.4V234c0-12.4-10-22.4-22.4-22.4h-30.7V134c0-12.1-9.9-22-22-22H179.4c-12.4 0-22.4 10-22.4 22.4v716.1c0 12.4 10 22.4 22.4 22.4h63.4V942c0 3.4 1.7 6.5 4.5 8.3 2.8 1.9 6.3 2.2 9.4 0.9l38.9-16.5 39 16.5c1.2 0.5 2.6 0.8 3.9 0.8 1.9 0 3.9-0.6 5.5-1.7 2.8-1.9 4.5-5 4.5-8.3v-69.1h443.3c12.2 0 22.1-9.9 22.1-22.1V763h30.7c12.4 0 22.4-10 22.4-22.4v-81.5c0-12.4-10-22.4-22.4-22.4h-30.7v-86.2h30.7c12.4 0 22.4-10 22.4-22.4v-81.5c0-12.4-10-22.4-22.4-22.4h-30.7v-86.3h30.7z m0-106.3c1.3 0 2.4 1.1 2.4 2.4v81.5c0 1.3-1.1 2.4-2.4 2.4h-30.7v-86.3h30.7zM177 850.5V134.4c0-1.3 1.1-2.4 2.4-2.4h63.4v720.9h-63.4c-1.3 0-2.4-1.1-2.4-2.4z m151.5 76.4l-29-12.2c-2.5-1-5.3-1-7.8 0l-28.9 12.2v-54h65.7v54z m465.4-76.1c0 1.2-0.9 2.1-2.1 2.1h-529V132h529.1c1.1 0 2 0.9 2 2v716.8z m50.7-194.1c1.3 0 2.4 1.1 2.4 2.4v81.5c0 1.4-1 2.4-2.4 2.4h-30.7v-86.3h30.7z m0-212.5c1.3 0 2.4 1.1 2.4 2.4v81.5c0 1.3-1.1 2.4-2.4 2.4h-30.7v-86.3h30.7z" fill="#4D3500" /><path d="M680.3 600.1H343.7c-19.6 0-35.5 15.9-35.5 35.5s15.9 35.5 35.5 35.5h336.6c19.6 0 35.5-15.9 35.5-35.5s-15.9-35.5-35.5-35.5z m0 51H343.7c-8.5 0-15.5-7-15.5-15.5s7-15.5 15.5-15.5h336.6c8.5 0 15.5 7 15.5 15.5s-7 15.5-15.5 15.5zM680.3 718.8H343.7c-19.6 0-35.5 15.9-35.5 35.5s15.9 35.5 35.5 35.5h336.6c19.6 0 35.5-15.9 35.5-35.5s-15.9-35.5-35.5-35.5z m0 51H343.7c-8.5 0-15.5-7-15.5-15.5s7-15.5 15.5-15.5h336.6c8.5 0 15.5 7 15.5 15.5s-7 15.5-15.5 15.5zM512.3 543.2c29.8 0 59.9-7.6 87.5-23.5 40.6-23.4 69.7-61.3 81.9-106.7 12.2-45.4 6-92.7-17.5-133.3-23.5-40.6-61.4-69.7-106.7-81.8-45.3-12.1-92.7-5.9-133.3 17.6-40.6 23.5-69.7 61.4-81.9 106.7-12.2 45.3-6 92.7 17.5 133.3 32.6 56.3 91.7 87.7 152.5 87.7zM361.6 327.4c10.8-40.2 36.6-73.7 72.6-94.6 24-13.9 50.6-20.9 77.6-20.9 13.5 0 27.1 1.8 40.5 5.4 40.2 10.8 73.7 36.5 94.6 72.5 20.8 36 26.3 77.9 15.5 118.1-10.8 40.2-36.6 73.7-72.6 94.5-74.3 42.9-169.7 17.3-212.6-56.9-20.8-36-26.4-77.9-15.6-118.1z" fill="#4D3500" /></svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
1
frontend/src/assets/icons/xianxingtianqiyubao.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
frontend/src/assets/icons/xianxingxiangjipaizhao.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M72 440.5h880v286.2H72z" fill="#F2B843" /><path d="M72 726.6V834c0 19.8 16 35.8 35.8 35.8h808.5c19.8 0 35.8-16 35.8-35.8V726.6H72zM916.2 297.4H708.7L647 174.1c-6.1-12.1-18.5-19.8-32-19.8H408.9c-13.5 0-25.9 7.7-32 19.8l-61.7 123.3h-64.4v-35.8c0-19.8-16-35.8-35.8-35.8h-35.8c-19.8 0-35.8 16-35.8 35.8v35.8h-35.8c-19.8 0-35.8 16-35.8 35.8v107.3h880V333.1c0.2-19.7-15.8-35.7-35.6-35.7z" fill="#3DC38A" /><path d="M726.6 583.5c0 118.3-95.9 214.6-214.6 214.6-118.8 0-214.6-96.4-214.6-214.6 0-118.3 95.9-214.6 214.6-214.6 118.8 0 214.6 96.4 214.6 214.6z" fill="#EA800C" /><path d="M512 440.5c78.9 0 143.1 64.2 143.1 143.1S590.9 726.7 512 726.7s-143.1-64.2-143.1-143.1S433.1 440.5 512 440.5z" fill="#FFFFFF" /><path d="M773.1 386.8c9.9 0 17.9-8 17.9-17.9s-8-17.9-17.9-17.9-17.9 8-17.9 17.9c0.1 9.9 8.1 17.9 17.9 17.9zM565.7 207.9H458.3c-9.9 0-17.9 8-17.9 17.9s8 17.9 17.9 17.9h107.3c9.9 0 17.9-8 17.9-17.9s-8-17.9-17.8-17.9zM512 744.5c88.8 0 161-72.2 161-161s-72.2-161-161-161-161 72.2-161 161 72.2 161 161 161z m0-286.2c69 0 125.2 56.2 125.2 125.2S581 708.7 512 708.7s-125.2-56.2-125.2-125.2S443 458.3 512 458.3z" fill="#2F9B77" /><path d="M440.5 601.4c9.9 0 17.9-8 17.9-17.9 0-29.6 24.1-53.7 53.7-53.7 9.9 0 17.9-8 17.9-17.9s-8-17.9-17.9-17.9c-49.3 0-89.4 40.1-89.4 89.4-0.1 10 7.9 18 17.8 18z" fill="#3DC38A" /><path d="M844.7 386.8h35.8c9.9 0 17.9-8 17.9-17.9s-8-17.9-17.9-17.9h-35.8c-9.9 0-17.9 8-17.9 17.9s8 17.9 17.9 17.9z" fill="#2F9B77" /><path d="M773.1 396.8c15.4 0 27.9-12.5 27.9-27.9S788.5 341 773.1 341s-27.9 12.5-27.9 27.9v0.1c0.2 15.3 12.7 27.8 27.9 27.8z m0-35.8c4.4 0 7.9 3.5 7.9 7.9s-3.5 7.9-7.9 7.9c-4.3 0-7.8-3.6-7.9-8 0-4.3 3.6-7.8 7.9-7.8zM458.3 253.7h107.3c15.4 0 27.9-12.5 27.9-27.9s-12.5-27.9-27.8-27.9H458.3c-15.4 0-27.9 12.5-27.9 27.9s12.5 27.9 27.9 27.9z m0-35.8h107.4c4.3 0 7.8 3.5 7.8 7.9s-3.5 7.9-7.9 7.9H458.3c-4.4 0-7.9-3.5-7.9-7.9s3.5-7.9 7.9-7.9zM512 754.5c94.3 0 171-76.7 171-171s-76.7-171-171-171-171 76.7-171 171 76.7 171 171 171z m0-322c83.3 0 151 67.7 151 151s-67.7 151-151 151-151-67.7-151-151 67.7-151 151-151z" fill="#4D3500" /><path d="M512 718.7c74.5 0 135.2-60.7 135.2-135.2S586.5 448.3 512 448.3 376.8 509 376.8 583.5 437.5 718.7 512 718.7z m0-250.4c63.5 0 115.2 51.7 115.2 115.2S575.5 698.7 512 698.7 396.8 647 396.8 583.5 448.5 468.3 512 468.3z" fill="#4D3500" /><path d="M468.4 583.5c0-24.1 19.6-43.7 43.7-43.7 15.4 0 27.9-12.5 27.9-27.9S527.5 484 512.1 484c-54.8 0-99.4 44.6-99.4 99.4-0.1 7.5 2.8 14.5 8 19.8 5.3 5.3 12.3 8.2 19.8 8.2 15.4 0 27.9-12.5 27.9-27.9z m-35.7 0s0-0.1 0 0c0-43.9 35.6-79.5 79.4-79.5 4.4 0 7.9 3.5 7.9 7.9s-3.5 7.9-7.9 7.9c-35.1 0-63.7 28.6-63.7 63.7 0 4.4-3.5 7.9-7.9 7.9-2.1 0-4.1-0.8-5.6-2.3-1.4-1.5-2.2-3.5-2.2-5.6zM844.7 396.8h35.8c15.4 0 27.9-12.5 27.9-27.9S895.9 341 880.5 341h-35.8c-15.4 0-27.9 12.5-27.9 27.9s12.5 27.9 27.9 27.9z m0-35.8h35.8c4.4 0 7.9 3.5 7.9 7.9s-3.5 7.9-7.9 7.9h-35.8c-4.4 0-7.9-3.5-7.9-7.9s3.5-7.9 7.9-7.9z" fill="#4D3500" /><path d="M916.5 287.3H715.2l-58.9-117.8c-7.8-15.6-23.5-25.3-40.9-25.3H409.1c-17.4 0-33.1 9.7-40.9 25.3l-58.9 117.8H261v-25.8c0-25.3-20.5-45.8-45.8-45.8h-35.8c-25.3 0-45.8 20.5-45.8 45.8v25.8h-25.8c-25.3 0-45.8 20.5-45.8 45.8V834c0 25.1 20.5 45.7 45.8 45.8h808.4c25.3 0 45.8-20.5 45.8-45.8V442.8c0.2-0.8 0.3-1.6 0.3-2.4V333.1c0-25.3-20.5-45.8-45.8-45.8z m-737.1-51.6h35.8c14.2 0 25.8 11.6 25.8 25.8v25.9h-87.4v-25.9c0-14.2 11.6-25.8 25.8-25.8zM82 450.5h249.1c-27.5 37.3-43.7 83.3-43.7 133 0 49.8 16.3 95.8 43.8 133.1H82V450.5z m430-71.6c112.8 0 204.6 91.8 204.6 204.6S624.8 788.1 512 788.1s-204.6-91.8-204.6-204.6S399.2 378.9 512 378.9zM942 834c0 14.2-11.6 25.8-25.8 25.8H107.9C93.6 859.7 82 848.2 82 834v-97.4h265.8c41 44 99.4 71.5 164.2 71.5s123.1-27.5 164.2-71.5H942V834z m0-117.4H692.8c27.5-37.3 43.8-83.3 43.8-133.1 0-49.7-16.3-95.7-43.7-133H942v266.1z m0.3-286.2H676.2c-41-44-99.4-71.5-164.2-71.5s-123.2 27.6-164.3 71.6H82v-97.4c0-14.2 11.6-25.8 25.8-25.8h34.4c0.4 0.1 0.9 0.1 1.3 0.1h108c0.5 0 0.9 0 1.3-0.1h62.6c3.8 0 7.2-2.1 8.9-5.5L386 178.5c4.4-8.8 13.3-14.3 23.1-14.3h206.2c9.8 0 18.7 5.5 23.1 14.3l61.7 123.3c1.7 3.4 5.2 5.5 8.9 5.5h207.5c14.2 0 25.8 11.6 25.8 25.8v97.3z" fill="#4D3500" /></svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
1
frontend/src/assets/icons/xianxingxiarilengyin.svg
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
1
frontend/src/assets/icons/xianxingyoulun.svg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
1
frontend/src/assets/icons/xianxingzijiayou.svg
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
frontend/src/assets/images/403.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
frontend/src/assets/images/404.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
frontend/src/assets/images/500.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
frontend/src/assets/images/avatar.gif
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 300 KiB |
33
frontend/src/assets/images/login_bg.svg
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" width="100%" height="100%" viewBox="0 0 1400 800">
|
||||||
|
|
||||||
|
<rect x="1300" y="400" rx="40" ry="40" width="150" height="150" stroke="rgb(129, 201, 149)" fill="rgb(129, 201, 149)">
|
||||||
|
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="0 1450 550" to="360 1450 550" repeatCount="indefinite"/>
|
||||||
|
</rect>
|
||||||
|
|
||||||
|
<path d="M 100 350 A 150 150 0 1 1 400 350 Q400 370 380 370 L 250 370 L 120 370 Q100 370 100 350" fill="#a2b3ff">
|
||||||
|
<animateMotion path="M 800 -200 L 800 -300 L 800 -200" dur="20s" begin="0s" repeatCount="indefinite"/>
|
||||||
|
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="30s" type="rotate" values="0 210 530 ; -30 210 530 ; 0 210 530" keyTimes="0 ; 0.5 ; 1" repeatCount="indefinite"/>
|
||||||
|
</path>
|
||||||
|
|
||||||
|
<circle cx="150" cy="150" r="180" stroke="#85FFBD" fill="#85FFBD">
|
||||||
|
<animateMotion path="M 0 0 L 40 20 Z" dur="5s" repeatCount="indefinite"/>
|
||||||
|
</circle>
|
||||||
|
|
||||||
|
<!-- 三角形 -->
|
||||||
|
<path d="M 165 580 L 270 580 Q275 578 270 570 L 223 483 Q220 480 217 483 L 165 570 Q160 578 165 580" fill="#a2b3ff">
|
||||||
|
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="0 210 530" to="360 210 530" repeatCount="indefinite"/>
|
||||||
|
</path>
|
||||||
|
|
||||||
|
<!-- <circle cx="1200" cy="600" r="30" stroke="rgb(241, 243, 244)" fill="rgb(241, 243, 244)">-->
|
||||||
|
<!-- <animateMotion path="M 0 0 L -20 40 Z" dur="9s" repeatCount="indefinite"/>-->
|
||||||
|
<!-- </circle>-->
|
||||||
|
|
||||||
|
<path d="M 100 350 A 40 40 0 1 1 180 350 L 180 430 A 40 40 0 1 1 100 430 Z" fill="#3054EB">
|
||||||
|
<animateMotion path="M 140 390 L 180 360 L 140 390" dur="20s" begin="0s" repeatCount="indefinite"/>
|
||||||
|
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="30s" type="rotate" values="0 140 390; -60 140 390; 0 140 390" keyTimes="0 ; 0.5 ; 1" repeatCount="indefinite"/>
|
||||||
|
</path>
|
||||||
|
|
||||||
|
<rect x="400" y="600" rx="40" ry="40" width="100" height="100" stroke="rgb(129, 201, 149)" fill="#3054EB">
|
||||||
|
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="-30 550 750" to="330 550 750" repeatCount="indefinite"/>
|
||||||
|
</rect>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
BIN
frontend/src/assets/images/login_left.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
frontend/src/assets/images/login_left1.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
frontend/src/assets/images/login_left2.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
frontend/src/assets/images/login_left3.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
frontend/src/assets/images/login_left4.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
frontend/src/assets/images/login_left5.png
Normal file
|
After Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 12 KiB |
1
frontend/src/assets/images/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||||
|
After Width: | Height: | Size: 276 B |
BIN
frontend/src/assets/images/msg01.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
frontend/src/assets/images/msg02.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
frontend/src/assets/images/msg03.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
frontend/src/assets/images/msg04.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
frontend/src/assets/images/msg05.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
frontend/src/assets/images/notData.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
frontend/src/assets/images/welcome.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 33 KiB |
@@ -1,19 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* 申明字体为东方大楷 */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'DongFangDaKai';
|
|
||||||
src: url('@/assets/font/DongFangDaKai-Regular.woff') format('woff'),
|
|
||||||
url('@/assets/font/DongFangDaKai-Regular.woff2') format('woff2');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "MFBanHei"; /* Project id 1513211 */
|
|
||||||
src: url('@/assets/font/MFBanHei.ttf?t=1643094287456') format('truetype');
|
|
||||||
}
|
|
||||||
19
frontend/src/components/ErrorMessage/403.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div class="not-container">
|
||||||
|
<img src="@/assets/images/403.png" class="not-img" alt="403" />
|
||||||
|
<div class="not-detail">
|
||||||
|
<h2>403</h2>
|
||||||
|
<h4>抱歉,您无权访问该页面~🙅♂️🙅♀️</h4>
|
||||||
|
<el-button type="primary" @click="router.back"> 返回上一页 </el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="403">
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "./index.scss";
|
||||||
|
</style>
|
||||||
19
frontend/src/components/ErrorMessage/404.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div class="not-container">
|
||||||
|
<img src="@/assets/images/404.png" class="not-img" alt="404" />
|
||||||
|
<div class="not-detail">
|
||||||
|
<h2>404</h2>
|
||||||
|
<h4>抱歉,您访问的页面不存在~🤷♂️🤷♀️</h4>
|
||||||
|
<el-button type="primary" @click="router.back"> 返回上一页 </el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="404">
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "./index.scss";
|
||||||
|
</style>
|
||||||
19
frontend/src/components/ErrorMessage/500.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div class="not-container">
|
||||||
|
<img src="@/assets/images/500.png" class="not-img" alt="500" />
|
||||||
|
<div class="not-detail">
|
||||||
|
<h2>500</h2>
|
||||||
|
<h4>抱歉,您的网络不见了~🤦♂️🤦♀️</h4>
|
||||||
|
<el-button type="primary" @click="router.back"> 返回上一页 </el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="500">
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "./index.scss";
|
||||||
|
</style>
|
||||||
32
frontend/src/components/ErrorMessage/index.scss
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
.not-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.not-img {
|
||||||
|
margin-right: 120px;
|
||||||
|
}
|
||||||
|
.not-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
h2,
|
||||||
|
h4 {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 60px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
margin: 30px 0 20px;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
.el-button {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
frontend/src/components/Loading/fullScreen.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { ElLoading } from "element-plus";
|
||||||
|
|
||||||
|
/* 全局请求 loading */
|
||||||
|
let loadingInstance: ReturnType<typeof ElLoading.service>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 开启 Loading
|
||||||
|
* */
|
||||||
|
const startLoading = () => {
|
||||||
|
loadingInstance = ElLoading.service({
|
||||||
|
fullscreen: true,
|
||||||
|
lock: true,
|
||||||
|
text: "Loading",
|
||||||
|
background: "rgba(0, 0, 0, 0.7)"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 结束 Loading
|
||||||
|
* */
|
||||||
|
const endLoading = () => {
|
||||||
|
loadingInstance.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 显示全屏加载
|
||||||
|
* */
|
||||||
|
let needLoadingRequestCount = 0;
|
||||||
|
export const showFullScreenLoading = () => {
|
||||||
|
if (needLoadingRequestCount === 0) {
|
||||||
|
startLoading();
|
||||||
|
}
|
||||||
|
needLoadingRequestCount++;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 隐藏全屏加载
|
||||||
|
* */
|
||||||
|
export const tryHideFullScreenLoading = () => {
|
||||||
|
if (needLoadingRequestCount <= 0) return;
|
||||||
|
needLoadingRequestCount--;
|
||||||
|
if (needLoadingRequestCount === 0) {
|
||||||
|
endLoading();
|
||||||
|
}
|
||||||
|
};
|
||||||
67
frontend/src/components/Loading/index.scss
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
.loading-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.loading-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 98px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dot {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 32px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
animation: ant-rotate 1.2s infinite linear;
|
||||||
|
}
|
||||||
|
.dot i {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
border-radius: 100%;
|
||||||
|
opacity: 0.3;
|
||||||
|
transform: scale(0.75);
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
animation: ant-spin-move 1s infinite linear alternate;
|
||||||
|
}
|
||||||
|
.dot i:nth-child(1) {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.dot i:nth-child(2) {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
.dot i:nth-child(3) {
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
animation-delay: 0.8s;
|
||||||
|
}
|
||||||
|
.dot i:nth-child(4) {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
animation-delay: 1.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ant-rotate {
|
||||||
|
to {
|
||||||
|
transform: rotate(405deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ant-spin-move {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
frontend/src/components/Loading/index.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div class="loading-box">
|
||||||
|
<div class="loading-wrap">
|
||||||
|
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="Loading"></script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "./index.scss";
|
||||||
|
</style>
|
||||||
12
frontend/src/components/SwitchDark/index.vue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<el-switch v-model="globalStore.isDark" inline-prompt :active-icon="Sunny" :inactive-icon="Moon" @change="switchDark" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="SwitchDark">
|
||||||
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
|
import { useGlobalStore } from "@/stores/modules/global";
|
||||||
|
import { Sunny, Moon } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
|
const { switchDark } = useTheme();
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
</script>
|
||||||
19
frontend/src/config/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// ? 全局默认配置项
|
||||||
|
|
||||||
|
// 首页地址(默认)
|
||||||
|
export const HOME_URL: string = "/home/index";
|
||||||
|
|
||||||
|
// 登录页地址(默认)
|
||||||
|
export const LOGIN_URL: string = "/login";
|
||||||
|
|
||||||
|
// 默认主题颜色
|
||||||
|
export const DEFAULT_PRIMARY: string = "#003078";
|
||||||
|
|
||||||
|
// 路由白名单地址(本地存在的路由 staticRouter.ts 中)
|
||||||
|
export const ROUTER_WHITE_LIST: string[] = ["/500"];
|
||||||
|
|
||||||
|
// 高德地图 key
|
||||||
|
export const AMAP_MAP_KEY: string = "";
|
||||||
|
|
||||||
|
// 百度地图 key
|
||||||
|
export const BAIDU_MAP_KEY: string = "";
|
||||||
12
frontend/src/config/nprogress.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import NProgress from "nprogress";
|
||||||
|
import "nprogress/nprogress.css";
|
||||||
|
|
||||||
|
NProgress.configure({
|
||||||
|
easing: "ease", // 动画方式
|
||||||
|
speed: 500, // 递增进度条的速度
|
||||||
|
showSpinner: true, // 是否显示加载ico
|
||||||
|
trickleSpeed: 200, // 自动递增间隔
|
||||||
|
minimum: 0.3 // 初始化时的最小百分比
|
||||||
|
});
|
||||||
|
|
||||||
|
export default NProgress;
|
||||||
28
frontend/src/directives/index.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { App, Directive } from "vue";
|
||||||
|
import auth from "./modules/auth";
|
||||||
|
import copy from "./modules/copy";
|
||||||
|
import waterMarker from "./modules/waterMarker";
|
||||||
|
import draggable from "./modules/draggable";
|
||||||
|
import debounce from "./modules/debounce";
|
||||||
|
import throttle from "./modules/throttle";
|
||||||
|
import longpress from "./modules/longpress";
|
||||||
|
|
||||||
|
const directivesList: { [key: string]: Directive } = {
|
||||||
|
auth,
|
||||||
|
copy,
|
||||||
|
waterMarker,
|
||||||
|
draggable,
|
||||||
|
debounce,
|
||||||
|
throttle,
|
||||||
|
longpress
|
||||||
|
};
|
||||||
|
|
||||||
|
const directives = {
|
||||||
|
install: function (app: App<Element>) {
|
||||||
|
Object.keys(directivesList).forEach(key => {
|
||||||
|
app.directive(key, directivesList[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default directives;
|
||||||
22
frontend/src/directives/modules/auth.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* v-auth
|
||||||
|
* 按钮权限指令
|
||||||
|
*/
|
||||||
|
import { useAuthStore } from "@/stores/modules/auth";
|
||||||
|
import type { Directive, DirectiveBinding } from "vue";
|
||||||
|
|
||||||
|
const auth: Directive = {
|
||||||
|
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
const { value } = binding;
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const currentPageRoles = authStore.authButtonListGet[authStore.routeName] ?? [];
|
||||||
|
if (value instanceof Array && value.length) {
|
||||||
|
const hasPermission = value.every(item => currentPageRoles.includes(item));
|
||||||
|
if (!hasPermission) el.remove();
|
||||||
|
} else {
|
||||||
|
if (!currentPageRoles.includes(value)) el.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default auth;
|
||||||
37
frontend/src/directives/modules/copy.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* v-copy
|
||||||
|
* 复制某个值至剪贴板
|
||||||
|
* 接收参数:string类型/Ref<string>类型/Reactive<string>类型
|
||||||
|
*/
|
||||||
|
import type { Directive, DirectiveBinding } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
interface ElType extends HTMLElement {
|
||||||
|
copyData: string | number;
|
||||||
|
__handleClick__: any;
|
||||||
|
}
|
||||||
|
const copy: Directive = {
|
||||||
|
mounted(el: ElType, binding: DirectiveBinding) {
|
||||||
|
el.copyData = binding.value;
|
||||||
|
el.addEventListener("click", handleClick);
|
||||||
|
},
|
||||||
|
updated(el: ElType, binding: DirectiveBinding) {
|
||||||
|
el.copyData = binding.value;
|
||||||
|
},
|
||||||
|
beforeUnmount(el: ElType) {
|
||||||
|
el.removeEventListener("click", el.__handleClick__);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleClick(this: any) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(this.copyData);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("复制操作不被支持或失败: ", err);
|
||||||
|
}
|
||||||
|
ElMessage({
|
||||||
|
type: "success",
|
||||||
|
message: "复制成功"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default copy;
|
||||||
31
frontend/src/directives/modules/debounce.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* v-debounce
|
||||||
|
* 按钮防抖指令,可自行扩展至input
|
||||||
|
* 接收参数:function类型
|
||||||
|
*/
|
||||||
|
import type { Directive, DirectiveBinding } from "vue";
|
||||||
|
interface ElType extends HTMLElement {
|
||||||
|
__handleClick__: () => any;
|
||||||
|
}
|
||||||
|
const debounce: Directive = {
|
||||||
|
mounted(el: ElType, binding: DirectiveBinding) {
|
||||||
|
if (typeof binding.value !== "function") {
|
||||||
|
throw "callback must be a function";
|
||||||
|
}
|
||||||
|
let timer: NodeJS.Timeout | null = null;
|
||||||
|
el.__handleClick__ = function () {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
binding.value();
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
el.addEventListener("click", el.__handleClick__);
|
||||||
|
},
|
||||||
|
beforeUnmount(el: ElType) {
|
||||||
|
el.removeEventListener("click", el.__handleClick__);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default debounce;
|
||||||
49
frontend/src/directives/modules/draggable.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。
|
||||||
|
|
||||||
|
思路:
|
||||||
|
1、设置需要拖拽的元素为absolute,其父元素为relative。
|
||||||
|
2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
|
||||||
|
3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
|
||||||
|
4、鼠标松开(onmouseup)时完成一次拖拽
|
||||||
|
|
||||||
|
使用:在 Dom 上加上 v-draggable 即可
|
||||||
|
<div class="dialog-model" v-draggable></div>
|
||||||
|
*/
|
||||||
|
import type { Directive } from "vue";
|
||||||
|
interface ElType extends HTMLElement {
|
||||||
|
parentNode: any;
|
||||||
|
}
|
||||||
|
const draggable: Directive = {
|
||||||
|
mounted: function (el: ElType) {
|
||||||
|
el.style.cursor = "move";
|
||||||
|
el.style.position = "absolute";
|
||||||
|
el.onmousedown = function (e) {
|
||||||
|
let disX = e.pageX - el.offsetLeft;
|
||||||
|
let disY = e.pageY - el.offsetTop;
|
||||||
|
document.onmousemove = function (e) {
|
||||||
|
let x = e.pageX - disX;
|
||||||
|
let y = e.pageY - disY;
|
||||||
|
let maxX = el.parentNode.offsetWidth - el.offsetWidth;
|
||||||
|
let maxY = el.parentNode.offsetHeight - el.offsetHeight;
|
||||||
|
if (x < 0) {
|
||||||
|
x = 0;
|
||||||
|
} else if (x > maxX) {
|
||||||
|
x = maxX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y < 0) {
|
||||||
|
y = 0;
|
||||||
|
} else if (y > maxY) {
|
||||||
|
y = maxY;
|
||||||
|
}
|
||||||
|
el.style.left = x + "px";
|
||||||
|
el.style.top = y + "px";
|
||||||
|
};
|
||||||
|
document.onmouseup = function () {
|
||||||
|
document.onmousemove = document.onmouseup = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export default draggable;
|
||||||
49
frontend/src/directives/modules/longpress.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* v-longpress
|
||||||
|
* 长按指令,长按时触发事件
|
||||||
|
*/
|
||||||
|
import type { Directive, DirectiveBinding } from "vue";
|
||||||
|
|
||||||
|
const directive: Directive = {
|
||||||
|
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
if (typeof binding.value !== "function") {
|
||||||
|
throw "callback must be a function";
|
||||||
|
}
|
||||||
|
// 定义变量
|
||||||
|
let pressTimer: any = null;
|
||||||
|
// 创建计时器( 2秒后执行函数 )
|
||||||
|
const start = (e: any) => {
|
||||||
|
if (e.button) {
|
||||||
|
if (e.type === "click" && e.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pressTimer === null) {
|
||||||
|
pressTimer = setTimeout(() => {
|
||||||
|
handler(e);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 取消计时器
|
||||||
|
const cancel = () => {
|
||||||
|
if (pressTimer !== null) {
|
||||||
|
clearTimeout(pressTimer);
|
||||||
|
pressTimer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 运行函数
|
||||||
|
const handler = (e: MouseEvent | TouchEvent) => {
|
||||||
|
binding.value(e);
|
||||||
|
};
|
||||||
|
// 添加事件监听器
|
||||||
|
el.addEventListener("mousedown", start);
|
||||||
|
el.addEventListener("touchstart", start);
|
||||||
|
// 取消计时器
|
||||||
|
el.addEventListener("click", cancel);
|
||||||
|
el.addEventListener("mouseout", cancel);
|
||||||
|
el.addEventListener("touchend", cancel);
|
||||||
|
el.addEventListener("touchcancel", cancel);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default directive;
|
||||||
41
frontend/src/directives/modules/throttle.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。
|
||||||
|
|
||||||
|
思路:
|
||||||
|
1、第一次点击,立即调用方法并禁用按钮,等延迟结束再次激活按钮
|
||||||
|
2、将需要触发的方法绑定在指令上
|
||||||
|
|
||||||
|
使用:给 Dom 加上 v-throttle 及回调函数即可
|
||||||
|
<button v-throttle="debounceClick">节流提交</button>
|
||||||
|
*/
|
||||||
|
import type { Directive, DirectiveBinding } from "vue";
|
||||||
|
interface ElType extends HTMLElement {
|
||||||
|
__handleClick__: () => any;
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
const throttle: Directive = {
|
||||||
|
mounted(el: ElType, binding: DirectiveBinding) {
|
||||||
|
if (typeof binding.value !== "function") {
|
||||||
|
throw "callback must be a function";
|
||||||
|
}
|
||||||
|
let timer: NodeJS.Timeout | null = null;
|
||||||
|
el.__handleClick__ = function () {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
if (!el.disabled) {
|
||||||
|
el.disabled = true;
|
||||||
|
binding.value();
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
el.disabled = false;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
el.addEventListener("click", el.__handleClick__);
|
||||||
|
},
|
||||||
|
beforeUnmount(el: ElType) {
|
||||||
|
el.removeEventListener("click", el.__handleClick__);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default throttle;
|
||||||
36
frontend/src/directives/modules/waterMarker.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
需求:给整个页面添加背景水印。
|
||||||
|
|
||||||
|
思路:
|
||||||
|
1、使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
|
||||||
|
2、将其设置为背景图片,从而实现页面或组件水印效果
|
||||||
|
|
||||||
|
使用:设置水印文案,颜色,字体大小即可
|
||||||
|
<div v-waterMarker="{text:'版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Directive, DirectiveBinding } from "vue";
|
||||||
|
const addWaterMarker: Directive = (str: string, parentNode: any, font: any, textColor: string) => {
|
||||||
|
// 水印文字,父元素,字体,文字颜色
|
||||||
|
let can: HTMLCanvasElement = document.createElement("canvas");
|
||||||
|
parentNode.appendChild(can);
|
||||||
|
can.width = 205;
|
||||||
|
can.height = 140;
|
||||||
|
can.style.display = "none";
|
||||||
|
let cans = can.getContext("2d") as CanvasRenderingContext2D;
|
||||||
|
cans.rotate((-20 * Math.PI) / 180);
|
||||||
|
cans.font = font || "16px Microsoft JhengHei";
|
||||||
|
cans.fillStyle = textColor || "rgba(180, 180, 180, 0.3)";
|
||||||
|
cans.textAlign = "left";
|
||||||
|
cans.textBaseline = "Middle" as CanvasTextBaseline;
|
||||||
|
cans.fillText(str, can.width / 10, can.height / 2);
|
||||||
|
parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")";
|
||||||
|
};
|
||||||
|
|
||||||
|
const waterMarker = {
|
||||||
|
mounted(el: DirectiveBinding, binding: DirectiveBinding) {
|
||||||
|
addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default waterMarker;
|
||||||
35
frontend/src/enums/httpEnum.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* @description:请求配置
|
||||||
|
*/
|
||||||
|
export enum ResultEnum {
|
||||||
|
SUCCESS = "200",
|
||||||
|
ERROR = 500,
|
||||||
|
OVERDUE = "401",
|
||||||
|
TIMEOUT = 30000,
|
||||||
|
TYPE = "success"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description:请求方法
|
||||||
|
*/
|
||||||
|
export enum RequestEnum {
|
||||||
|
GET = "GET",
|
||||||
|
POST = "POST",
|
||||||
|
PATCH = "PATCH",
|
||||||
|
PUT = "PUT",
|
||||||
|
DELETE = "DELETE"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description:常用的 contentTyp 类型
|
||||||
|
*/
|
||||||
|
export enum ContentTypeEnum {
|
||||||
|
// json
|
||||||
|
JSON = "application/json;charset=UTF-8",
|
||||||
|
// text
|
||||||
|
TEXT = "text/plain;charset=UTF-8",
|
||||||
|
// form-data 一般配合qs
|
||||||
|
FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8",
|
||||||
|
// form-data 上传
|
||||||
|
FORM_DATA = "multipart/form-data;charset=UTF-8"
|
||||||
|
}
|
||||||
32
frontend/src/hooks/interface/index.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
export namespace Table {
|
||||||
|
export interface Pageable {
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
export interface StateProps {
|
||||||
|
tableData: any[];
|
||||||
|
pageable: Pageable;
|
||||||
|
searchParam: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
searchInitParam: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
totalParam: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
icon?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace HandleData {
|
||||||
|
export type MessageType = "" | "success" | "warning" | "info" | "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Theme {
|
||||||
|
export type ThemeType = "light" | "inverted" | "dark";
|
||||||
|
export type GreyOrWeakType = "grey" | "weak";
|
||||||
|
}
|
||||||
22
frontend/src/hooks/useAuthButtons.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { computed } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { useAuthStore } from "@/stores/modules/auth";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 页面按钮权限
|
||||||
|
* */
|
||||||
|
export const useAuthButtons = () => {
|
||||||
|
const route = useRoute();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const authButtons = authStore.authButtonListGet[route.name as string] || [];
|
||||||
|
|
||||||
|
const BUTTONS = computed(() => {
|
||||||
|
let currentPageAuthButton: { [key: string]: boolean } = {};
|
||||||
|
authButtons.forEach(item => (currentPageAuthButton[item] = true));
|
||||||
|
return currentPageAuthButton;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
BUTTONS
|
||||||
|
};
|
||||||
|
};
|
||||||
44
frontend/src/hooks/useDownload.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { ElNotification } from "element-plus";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 接收数据流生成 blob,创建链接,下载文件
|
||||||
|
* @param {Function} api 导出表格的api方法 (必传)
|
||||||
|
* @param {String} tempName 导出的文件名 (必传)
|
||||||
|
* @param {Object} params 导出的参数 (默认{})
|
||||||
|
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
|
||||||
|
* @param {String} fileType 导出的文件格式 (默认为.xlsx)
|
||||||
|
* */
|
||||||
|
export const useDownload = async (
|
||||||
|
api: (param: any) => Promise<any>,
|
||||||
|
tempName: string,
|
||||||
|
params: any = {},
|
||||||
|
isNotify: boolean = true,
|
||||||
|
fileType: string = ".xlsx"
|
||||||
|
) => {
|
||||||
|
if (isNotify) {
|
||||||
|
ElNotification({
|
||||||
|
title: "温馨提示",
|
||||||
|
message: "如果数据庞大会导致下载缓慢哦,请您耐心等待!",
|
||||||
|
type: "info",
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await api(params);
|
||||||
|
const blob = new Blob([res]);
|
||||||
|
// 兼容 edge 不支持 createObjectURL 方法
|
||||||
|
if ("msSaveOrOpenBlob" in navigator) return window.navigator.msSaveOrOpenBlob(blob, tempName + fileType);
|
||||||
|
const blobUrl = window.URL.createObjectURL(blob);
|
||||||
|
const exportFile = document.createElement("a");
|
||||||
|
exportFile.style.display = "none";
|
||||||
|
exportFile.download = `${tempName}${fileType}`;
|
||||||
|
exportFile.href = blobUrl;
|
||||||
|
document.body.appendChild(exportFile);
|
||||||
|
exportFile.click();
|
||||||
|
// 去除下载对 url 的影响
|
||||||
|
document.body.removeChild(exportFile);
|
||||||
|
window.URL.revokeObjectURL(blobUrl);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
34
frontend/src/hooks/useHandleData.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { ElMessageBox, ElMessage } from "element-plus";
|
||||||
|
import { HandleData } from "./interface";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 操作单条数据信息 (二次确认【删除、禁用、启用、重置密码】)
|
||||||
|
* @param {Function} api 操作数据接口的api方法 (必传)
|
||||||
|
* @param {Object} params 携带的操作数据参数 {id,params} (必传)
|
||||||
|
* @param {String} message 提示信息 (必传)
|
||||||
|
* @param {String} confirmType icon类型 (不必传,默认为 warning)
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export const useHandleData = (
|
||||||
|
api: (params: any) => Promise<any>,
|
||||||
|
params: any = {},
|
||||||
|
message: string,
|
||||||
|
confirmType: HandleData.MessageType = "warning"
|
||||||
|
) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ElMessageBox.confirm(`是否${message}?`, "温馨提示", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: confirmType,
|
||||||
|
draggable: true
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await api(params);
|
||||||
|
if (!res) return reject(false);
|
||||||
|
ElMessage({
|
||||||
|
type: "success",
|
||||||
|
message: `${message}成功!`
|
||||||
|
});
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
27
frontend/src/hooks/useOnline.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 网络是否可用
|
||||||
|
* */
|
||||||
|
export const useOnline = () => {
|
||||||
|
const online = ref(true);
|
||||||
|
const showStatus = (val: any) => {
|
||||||
|
online.value = typeof val == "boolean" ? val : val.target.online;
|
||||||
|
};
|
||||||
|
// 在页面加载后,设置正确的网络状态
|
||||||
|
navigator.onLine ? showStatus(true) : showStatus(false);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 开始监听网络状态的变化
|
||||||
|
window.addEventListener("online", showStatus);
|
||||||
|
window.addEventListener("offline", showStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 移除监听网络状态的变化
|
||||||
|
window.removeEventListener("online", showStatus);
|
||||||
|
window.removeEventListener("offline", showStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { online };
|
||||||
|
};
|
||||||
34
frontend/src/hooks/useSelection.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { ref, computed } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 表格多选数据操作
|
||||||
|
* @param {String} rowKey 当表格可以多选时,所指定的 id
|
||||||
|
* */
|
||||||
|
export const useSelection = (rowKey: string = "id") => {
|
||||||
|
const isSelected = ref<boolean>(false);
|
||||||
|
const selectedList = ref<{ [key: string]: any }[]>([]);
|
||||||
|
|
||||||
|
// 当前选中的所有 ids 数组
|
||||||
|
const selectedListIds = computed((): string[] => {
|
||||||
|
let ids: string[] = [];
|
||||||
|
selectedList.value.forEach(item => ids.push(item[rowKey]));
|
||||||
|
return ids;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 多选操作
|
||||||
|
* @param {Array} rowArr 当前选择的所有数据
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
const selectionChange = (rowArr: { [key: string]: any }[]) => {
|
||||||
|
rowArr.length ? (isSelected.value = true) : (isSelected.value = false);
|
||||||
|
selectedList.value = rowArr;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSelected,
|
||||||
|
selectedList,
|
||||||
|
selectedListIds,
|
||||||
|
selectionChange
|
||||||
|
};
|
||||||
|
};
|
||||||
154
frontend/src/hooks/useTable.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import { Table } from "./interface";
|
||||||
|
import { reactive, computed, toRefs } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description table 页面操作方法封装
|
||||||
|
* @param {Function} api 获取表格数据 api 方法 (必传)
|
||||||
|
* @param {Object} initParam 获取数据初始化参数 (非必传,默认为{})
|
||||||
|
* @param {Boolean} isPageable 是否有分页 (非必传,默认为true)
|
||||||
|
* @param {Function} dataCallBack 对后台返回的数据进行处理的方法 (非必传)
|
||||||
|
* */
|
||||||
|
export const useTable = (
|
||||||
|
api?: (params: any) => Promise<any>,
|
||||||
|
initParam: object = {},
|
||||||
|
isPageable: boolean = true,
|
||||||
|
dataCallBack?: (data: any) => any,
|
||||||
|
requestError?: (error: any) => void
|
||||||
|
) => {
|
||||||
|
const state = reactive<Table.StateProps>({
|
||||||
|
// 表格数据
|
||||||
|
tableData: [],
|
||||||
|
// 分页数据
|
||||||
|
pageable: {
|
||||||
|
// 当前页数
|
||||||
|
pageNum: 1,
|
||||||
|
// 每页显示条数
|
||||||
|
pageSize: 10,
|
||||||
|
// 总条数
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
// 查询参数(只包括查询)
|
||||||
|
searchParam: {},
|
||||||
|
// 初始化默认的查询参数
|
||||||
|
searchInitParam: {},
|
||||||
|
// 总参数(包含分页和查询参数)
|
||||||
|
totalParam: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 分页查询参数(只包括分页和表格字段排序,其他排序方式可自行配置)
|
||||||
|
* */
|
||||||
|
const pageParam = computed({
|
||||||
|
get: () => {
|
||||||
|
return {
|
||||||
|
pageNum: state.pageable.pageNum,
|
||||||
|
pageSize: state.pageable.pageSize
|
||||||
|
};
|
||||||
|
},
|
||||||
|
set: (newVal: any) => {
|
||||||
|
console.log("我是分页更新之后的值", newVal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取表格数据
|
||||||
|
* @return void
|
||||||
|
* */
|
||||||
|
const getTableList = async () => {
|
||||||
|
if (!api) return;
|
||||||
|
try {
|
||||||
|
// 先把初始化参数和分页参数放到总参数里面
|
||||||
|
Object.assign(state.totalParam, initParam, isPageable ? pageParam.value : {});
|
||||||
|
let { data } = await api({ ...state.searchInitParam, ...state.totalParam });
|
||||||
|
dataCallBack && (data = dataCallBack(data));
|
||||||
|
state.tableData = isPageable ? data.list : data;
|
||||||
|
// 解构后台返回的分页数据 (如果有分页更新分页信息)
|
||||||
|
if (isPageable) {
|
||||||
|
const { pageNum, pageSize, total } = data;
|
||||||
|
updatePageable({ pageNum, pageSize, total });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
requestError && requestError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 更新查询参数
|
||||||
|
* @return void
|
||||||
|
* */
|
||||||
|
const updatedTotalParam = () => {
|
||||||
|
state.totalParam = {};
|
||||||
|
// 处理查询参数,可以给查询参数加自定义前缀操作
|
||||||
|
let nowSearchParam: Table.StateProps["searchParam"] = {};
|
||||||
|
// 防止手动清空输入框携带参数(这里可以自定义查询参数前缀)
|
||||||
|
for (let key in state.searchParam) {
|
||||||
|
// 某些情况下参数为 false/0 也应该携带参数
|
||||||
|
if (state.searchParam[key] || state.searchParam[key] === false || state.searchParam[key] === 0) {
|
||||||
|
nowSearchParam[key] = state.searchParam[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.assign(state.totalParam, nowSearchParam, isPageable ? pageParam.value : {});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 更新分页信息
|
||||||
|
* @param {Object} pageable 后台返回的分页数据
|
||||||
|
* @return void
|
||||||
|
* */
|
||||||
|
const updatePageable = (pageable: Table.Pageable) => {
|
||||||
|
Object.assign(state.pageable, pageable);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 表格数据查询
|
||||||
|
* @return void
|
||||||
|
* */
|
||||||
|
const search = () => {
|
||||||
|
state.pageable.pageNum = 1;
|
||||||
|
updatedTotalParam();
|
||||||
|
getTableList();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 表格数据重置
|
||||||
|
* @return void
|
||||||
|
* */
|
||||||
|
const reset = () => {
|
||||||
|
state.pageable.pageNum = 1;
|
||||||
|
// 重置搜索表单的时,如果有默认搜索参数,则重置默认的搜索参数
|
||||||
|
state.searchParam = { ...state.searchInitParam };
|
||||||
|
updatedTotalParam();
|
||||||
|
getTableList();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 每页条数改变
|
||||||
|
* @param {Number} val 当前条数
|
||||||
|
* @return void
|
||||||
|
* */
|
||||||
|
const handleSizeChange = (val: number) => {
|
||||||
|
state.pageable.pageNum = 1;
|
||||||
|
state.pageable.pageSize = val;
|
||||||
|
getTableList();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 当前页改变
|
||||||
|
* @param {Number} val 当前页
|
||||||
|
* @return void
|
||||||
|
* */
|
||||||
|
const handleCurrentChange = (val: number) => {
|
||||||
|
state.pageable.pageNum = val;
|
||||||
|
getTableList();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
getTableList,
|
||||||
|
search,
|
||||||
|
reset,
|
||||||
|
handleSizeChange,
|
||||||
|
handleCurrentChange,
|
||||||
|
updatedTotalParam
|
||||||
|
};
|
||||||
|
};
|
||||||
111
frontend/src/hooks/useTheme.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { Theme } from "./interface";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { DEFAULT_PRIMARY } from "@/config";
|
||||||
|
import { useGlobalStore } from "@/stores/modules/global";
|
||||||
|
import { getLightColor, getDarkColor } from "@/utils/color";
|
||||||
|
import { menuTheme } from "@/styles/theme/menu";
|
||||||
|
import { asideTheme } from "@/styles/theme/aside";
|
||||||
|
import { headerTheme } from "@/styles/theme/header";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 全局主题 hooks
|
||||||
|
* */
|
||||||
|
export const useTheme = () => {
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
const { primary, isDark, isGrey, isWeak, layout, asideInverted, headerInverted } = storeToRefs(globalStore);
|
||||||
|
|
||||||
|
// 切换暗黑模式 ==> 同时修改主题颜色、侧边栏、头部颜色
|
||||||
|
const switchDark = () => {
|
||||||
|
const html = document.documentElement as HTMLElement;
|
||||||
|
if (isDark.value) html.setAttribute("class", "dark");
|
||||||
|
else html.setAttribute("class", "");
|
||||||
|
changePrimary(primary.value);
|
||||||
|
setAsideTheme();
|
||||||
|
setHeaderTheme();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改主题颜色
|
||||||
|
const changePrimary = (val: string | null) => {
|
||||||
|
if (!val) {
|
||||||
|
val = DEFAULT_PRIMARY;
|
||||||
|
ElMessage({ type: "success", message: `主题颜色已重置为 ${DEFAULT_PRIMARY}` });
|
||||||
|
}
|
||||||
|
// 计算主题颜色变化
|
||||||
|
document.documentElement.style.setProperty("--el-color-primary", val);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--el-color-primary-dark-2",
|
||||||
|
isDark.value ? `${getLightColor(val, 0.2)}` : `${getDarkColor(val, 0.3)}`
|
||||||
|
);
|
||||||
|
for (let i = 1; i <= 9; i++) {
|
||||||
|
const primaryColor = isDark.value ? `${getDarkColor(val, i / 10)}` : `${getLightColor(val, i / 10)}`;
|
||||||
|
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, primaryColor);
|
||||||
|
}
|
||||||
|
globalStore.setGlobalState("primary", val);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 灰色和弱色切换
|
||||||
|
const changeGreyOrWeak = (type: Theme.GreyOrWeakType, value: boolean) => {
|
||||||
|
const body = document.body as HTMLElement;
|
||||||
|
if (!value) return body.removeAttribute("style");
|
||||||
|
const styles: Record<Theme.GreyOrWeakType, string> = {
|
||||||
|
grey: "filter: grayscale(1)",
|
||||||
|
weak: "filter: invert(80%)"
|
||||||
|
};
|
||||||
|
body.setAttribute("style", styles[type]);
|
||||||
|
const propName = type === "grey" ? "isWeak" : "isGrey";
|
||||||
|
globalStore.setGlobalState(propName, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置菜单样式
|
||||||
|
const setMenuTheme = () => {
|
||||||
|
let type: Theme.ThemeType = "light";
|
||||||
|
if (layout.value === "transverse" && headerInverted.value) type = "inverted";
|
||||||
|
if (layout.value !== "transverse" && asideInverted.value) type = "inverted";
|
||||||
|
if (isDark.value) type = "dark";
|
||||||
|
const theme = menuTheme[type!];
|
||||||
|
for (const [key, value] of Object.entries(theme)) {
|
||||||
|
document.documentElement.style.setProperty(key, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置侧边栏样式
|
||||||
|
const setAsideTheme = () => {
|
||||||
|
let type: Theme.ThemeType = "light";
|
||||||
|
if (asideInverted.value) type = "inverted";
|
||||||
|
if (isDark.value) type = "dark";
|
||||||
|
const theme = asideTheme[type!];
|
||||||
|
for (const [key, value] of Object.entries(theme)) {
|
||||||
|
document.documentElement.style.setProperty(key, value);
|
||||||
|
}
|
||||||
|
setMenuTheme();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置头部样式
|
||||||
|
const setHeaderTheme = () => {
|
||||||
|
let type: Theme.ThemeType = "light";
|
||||||
|
if (headerInverted.value) type = "inverted";
|
||||||
|
if (isDark.value) type = "dark";
|
||||||
|
const theme = headerTheme[type!];
|
||||||
|
for (const [key, value] of Object.entries(theme)) {
|
||||||
|
document.documentElement.style.setProperty(key, value);
|
||||||
|
}
|
||||||
|
setMenuTheme();
|
||||||
|
};
|
||||||
|
|
||||||
|
// init theme
|
||||||
|
const initTheme = () => {
|
||||||
|
switchDark();
|
||||||
|
if (isGrey.value) changeGreyOrWeak("grey", true);
|
||||||
|
if (isWeak.value) changeGreyOrWeak("weak", true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
initTheme,
|
||||||
|
switchDark,
|
||||||
|
changePrimary,
|
||||||
|
changeGreyOrWeak,
|
||||||
|
setAsideTheme,
|
||||||
|
setHeaderTheme
|
||||||
|
};
|
||||||
|
};
|
||||||
38
frontend/src/hooks/useTime.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取本地时间
|
||||||
|
*/
|
||||||
|
export const useTime = () => {
|
||||||
|
const year = ref(0); // 年份
|
||||||
|
const month = ref(0); // 月份
|
||||||
|
const week = ref(""); // 星期几
|
||||||
|
const day = ref(0); // 天数
|
||||||
|
const hour = ref<number | string>(0); // 小时
|
||||||
|
const minute = ref<number | string>(0); // 分钟
|
||||||
|
const second = ref<number | string>(0); // 秒
|
||||||
|
const nowTime = ref<string>(""); // 当前时间
|
||||||
|
|
||||||
|
// 更新时间
|
||||||
|
const updateTime = () => {
|
||||||
|
const date = new Date();
|
||||||
|
year.value = date.getFullYear();
|
||||||
|
month.value = date.getMonth() + 1;
|
||||||
|
week.value = "日一二三四五六".charAt(date.getDay());
|
||||||
|
day.value = date.getDate();
|
||||||
|
hour.value =
|
||||||
|
(date.getHours() + "")?.padStart(2, "0") ||
|
||||||
|
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours());
|
||||||
|
minute.value =
|
||||||
|
(date.getMinutes() + "")?.padStart(2, "0") ||
|
||||||
|
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes());
|
||||||
|
second.value =
|
||||||
|
(date.getSeconds() + "")?.padStart(2, "0") ||
|
||||||
|
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getSeconds());
|
||||||
|
nowTime.value = `${year.value}年${month.value}月${day.value} ${hour.value}:${minute.value}:${second.value}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
updateTime();
|
||||||
|
|
||||||
|
return { year, month, day, hour, minute, second, week, nowTime };
|
||||||
|
};
|
||||||
18
frontend/src/languages/index.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { createI18n } from "vue-i18n";
|
||||||
|
import { getBrowserLang } from "@/utils";
|
||||||
|
|
||||||
|
import zh from "./modules/zh";
|
||||||
|
import en from "./modules/en";
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
// Use Composition API, Set to false
|
||||||
|
allowComposition: true,
|
||||||
|
legacy: false,
|
||||||
|
locale: getBrowserLang(),
|
||||||
|
messages: {
|
||||||
|
zh,
|
||||||
|
en
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
29
frontend/src/languages/modules/en.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export default {
|
||||||
|
home: {
|
||||||
|
welcome: "Welcome"
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
refresh: "Refresh",
|
||||||
|
maximize: "Maximize",
|
||||||
|
closeCurrent: "Close current",
|
||||||
|
closeLeft: "Close Left",
|
||||||
|
closeRight: "Close Right",
|
||||||
|
closeOther: "Close other",
|
||||||
|
closeAll: "Close All"
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
componentSize: "Component size",
|
||||||
|
language: "Language",
|
||||||
|
theme: "theme",
|
||||||
|
layoutConfig: "Layout config",
|
||||||
|
primary: "primary",
|
||||||
|
darkMode: "Dark Mode",
|
||||||
|
greyMode: "Grey mode",
|
||||||
|
weakMode: "Weak mode",
|
||||||
|
fullScreen: "Full Screen",
|
||||||
|
exitFullScreen: "Exit Full Screen",
|
||||||
|
personalData: "Personal Data",
|
||||||
|
changePassword: "Change Password",
|
||||||
|
logout: "Logout"
|
||||||
|
}
|
||||||
|
};
|
||||||
29
frontend/src/languages/modules/zh.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export default {
|
||||||
|
home: {
|
||||||
|
welcome: "欢迎使用"
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
refresh: "刷新",
|
||||||
|
maximize: "最大化",
|
||||||
|
closeCurrent: "关闭当前",
|
||||||
|
closeLeft: "关闭左侧",
|
||||||
|
closeRight: "关闭右侧",
|
||||||
|
closeOther: "关闭其它",
|
||||||
|
closeAll: "关闭所有"
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
componentSize: "组件大小",
|
||||||
|
language: "国际化",
|
||||||
|
theme: "全局主题",
|
||||||
|
layoutConfig: "布局设置",
|
||||||
|
primary: "primary",
|
||||||
|
darkMode: "暗黑模式",
|
||||||
|
greyMode: "灰色模式",
|
||||||
|
weakMode: "色弱模式",
|
||||||
|
fullScreen: "全屏",
|
||||||
|
exitFullScreen: "退出全屏",
|
||||||
|
personalData: "个人信息",
|
||||||
|
changePassword: "修改密码",
|
||||||
|
logout: "退出登录"
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
<!--主界面的头部,包含了logo、导航、用户信息-->
|
|
||||||
<template>
|
|
||||||
<el-menu
|
|
||||||
:default-active='activeIndex'
|
|
||||||
popper-effect='light'
|
|
||||||
mode='horizontal'
|
|
||||||
:router='false'
|
|
||||||
class='el-menu-njcn text-center'
|
|
||||||
:ellipsis='false'
|
|
||||||
background-color='var(--el-color-primary)'
|
|
||||||
text-color='#fff'
|
|
||||||
active-text-color='#ffd04b'
|
|
||||||
@select='handleSelect'
|
|
||||||
:style="{height:headerHeight+'px'}"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class='float-left'
|
|
||||||
:style="{height:headerHeight+'px'}"
|
|
||||||
src='@/assets/images/logo.png'
|
|
||||||
/>
|
|
||||||
<el-menu-item index='0'>
|
|
||||||
<el-icon><HomeFilled /></el-icon>
|
|
||||||
<span>运营管理</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-sub-menu index='1' :popper-offset="0">
|
|
||||||
<template #title>
|
|
||||||
<el-icon><Monitor /></el-icon>
|
|
||||||
台账管理
|
|
||||||
</template>
|
|
||||||
<el-menu-item index='/home1'>脚本检测管理</el-menu-item>
|
|
||||||
<el-menu-item index='/home1'>被检设备管理</el-menu-item>
|
|
||||||
<el-menu-item index='/home1'>误差体系管理</el-menu-item>
|
|
||||||
<el-menu-item index='/home1'>检测源管理</el-menu-item>
|
|
||||||
</el-sub-menu>
|
|
||||||
<el-sub-menu index='2' :popper-offset="0">
|
|
||||||
<template #title>
|
|
||||||
<el-icon><UserFilled /></el-icon>
|
|
||||||
权限管理
|
|
||||||
</template>
|
|
||||||
<el-menu-item index='/home2'>用户管理</el-menu-item>
|
|
||||||
<el-menu-item index='/home2'>角色管理</el-menu-item>
|
|
||||||
<el-menu-item index='/home2'>菜单管理</el-menu-item>
|
|
||||||
</el-sub-menu>
|
|
||||||
<el-sub-menu index='3' :popper-offset="0">
|
|
||||||
<template #title>
|
|
||||||
<el-icon><Setting /></el-icon>
|
|
||||||
系统配置
|
|
||||||
</template>
|
|
||||||
<el-menu-item index='/home3'>系统配置</el-menu-item>
|
|
||||||
<el-menu-item index='/home3'>数据字典</el-menu-item>
|
|
||||||
<el-menu-item index='/home3'>报告模板</el-menu-item>
|
|
||||||
<el-menu-item index='/home3'>版本注册</el-menu-item>
|
|
||||||
</el-sub-menu>
|
|
||||||
<el-menu-item index='0'>
|
|
||||||
<el-icon><Document /></el-icon>
|
|
||||||
日志管理
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index='0'>
|
|
||||||
<el-icon><DataLine /></el-icon>
|
|
||||||
统计分析
|
|
||||||
</el-menu-item>
|
|
||||||
</el-menu>
|
|
||||||
</template>
|
|
||||||
<script setup lang='ts'>
|
|
||||||
defineOptions({
|
|
||||||
name: 'customHeader',
|
|
||||||
})
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
|
|
||||||
const activeIndex = ref('1')
|
|
||||||
const handleSelect = (key: string, keyPath: string[]) => {
|
|
||||||
console.log(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
defineProps(['headerHeight'])
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
/* 浮动菜单的颜色 */
|
|
||||||
.el-menu-item:hover {
|
|
||||||
--el-menu-hover-bg-color: var(--el-color-primary-light-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu--horizontal > .el-menu-item:nth-child(1) {
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu--horizontal.el-menu {
|
|
||||||
border-bottom: solid 0 var(--el-menu-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
60
frontend/src/layouts/LayoutClassic/index.scss
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
.el-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
:deep(.el-header) {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 55px;
|
||||||
|
padding: 0 15px 0 0;
|
||||||
|
background-color: var(--el-header-bg-color);
|
||||||
|
border-bottom: 1px solid var(--el-header-border-color);
|
||||||
|
.header-lf {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
.logo {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 210px;
|
||||||
|
margin-right: 16px;
|
||||||
|
.logo-img {
|
||||||
|
width: 28px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.logo-text {
|
||||||
|
font-size: 21.5px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--el-header-logo-text-color);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.classic-content {
|
||||||
|
display: flex;
|
||||||
|
height: calc(100% - 55px);
|
||||||
|
:deep(.el-aside) {
|
||||||
|
width: auto;
|
||||||
|
background-color: var(--el-menu-bg-color);
|
||||||
|
border-right: 1px solid var(--el-aside-border-color);
|
||||||
|
.aside-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
.el-menu {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.classic-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
frontend/src/layouts/LayoutClassic/index.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<!-- 经典布局 -->
|
||||||
|
<template>
|
||||||
|
<el-container class="layout">
|
||||||
|
<el-header>
|
||||||
|
<div class="header-lf mask-image">
|
||||||
|
<div class="logo flx-center">
|
||||||
|
<img class="logo-img" src="@/assets/images/logo.svg" alt="logo" />
|
||||||
|
<span class="logo-text">{{ title }}</span>
|
||||||
|
</div>
|
||||||
|
<ToolBarLeft />
|
||||||
|
</div>
|
||||||
|
<div class="header-ri">
|
||||||
|
<ToolBarRight />
|
||||||
|
</div>
|
||||||
|
</el-header>
|
||||||
|
<el-container class="classic-content">
|
||||||
|
<el-aside>
|
||||||
|
<div class="aside-box" :style="{ width: isCollapse ? '65px' : '210px' }">
|
||||||
|
<el-scrollbar>
|
||||||
|
<el-menu
|
||||||
|
:router="false"
|
||||||
|
:default-active="activeMenu"
|
||||||
|
:collapse="isCollapse"
|
||||||
|
:unique-opened="accordion"
|
||||||
|
:collapse-transition="false"
|
||||||
|
>
|
||||||
|
<SubMenu :menu-list="menuList" />
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</el-aside>
|
||||||
|
<el-container class="classic-main">
|
||||||
|
<Main />
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="layoutClassic">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { useAuthStore } from "@/stores/modules/auth";
|
||||||
|
import { useGlobalStore } from "@/stores/modules/global";
|
||||||
|
import Main from "@/layouts/components/Main/index.vue";
|
||||||
|
import SubMenu from "@/layouts/components/Menu/SubMenu.vue";
|
||||||
|
import ToolBarLeft from "@/layouts/components/Header/ToolBarLeft.vue";
|
||||||
|
import ToolBarRight from "@/layouts/components/Header/ToolBarRight.vue";
|
||||||
|
|
||||||
|
const title = import.meta.env.VITE_GLOB_APP_TITLE;
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
const accordion = computed(() => globalStore.accordion);
|
||||||
|
const isCollapse = computed(() => globalStore.isCollapse);
|
||||||
|
const menuList = computed(() => authStore.showMenuListGet);
|
||||||
|
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "./index.scss";
|
||||||
|
</style>
|
||||||
95
frontend/src/layouts/LayoutColumns/index.scss
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
.el-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.aside-split {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 70px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--el-menu-bg-color);
|
||||||
|
border-right: 1px solid var(--el-aside-border-color);
|
||||||
|
.logo {
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 55px;
|
||||||
|
.logo-img {
|
||||||
|
width: 32px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-scrollbar {
|
||||||
|
height: calc(100% - 55px);
|
||||||
|
.split-list {
|
||||||
|
flex: 1;
|
||||||
|
.split-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 70px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
.el-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.el-icon,
|
||||||
|
.title {
|
||||||
|
color: var(--el-menu-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.split-active {
|
||||||
|
background-color: var(--el-color-primary) !important;
|
||||||
|
.el-icon,
|
||||||
|
.title {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.not-aside {
|
||||||
|
width: 0 !important;
|
||||||
|
border-right: none !important;
|
||||||
|
}
|
||||||
|
.el-aside {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--el-menu-bg-color);
|
||||||
|
border-right: 1px solid var(--el-aside-border-color);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
.el-scrollbar {
|
||||||
|
height: calc(100% - 55px);
|
||||||
|
.el-menu {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 55px;
|
||||||
|
.logo-text {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--el-aside-logo-text-color);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-header {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 55px;
|
||||||
|
padding: 0 15px;
|
||||||
|
background-color: var(--el-header-bg-color);
|
||||||
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
|
}
|
||||||
|
}
|
||||||