feat: 修改代码规则,新增Layout动态布局,新增系统设置

This commit is contained in:
戴业伟 2024-02-05 10:28:35 +08:00
parent 764c88451c
commit 815f46af31
164 changed files with 11055 additions and 1520 deletions

View File

@ -1,14 +1,14 @@
module.exports = {
// (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
arrowParens: "always",
arrowParens: 'always',
// 开始标签的右尖括号是否跟随在最后一行属性末尾默认false
bracketSameLine: false,
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
bracketSpacing: true,
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
embeddedLanguageFormatting: "auto",
embeddedLanguageFormatting: 'auto',
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
htmlWhitespaceSensitivity: "css",
htmlWhitespaceSensitivity: 'css',
// 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记默认false
insertPragma: false,
// 在 JSX 中使用单引号替代双引号默认false
@ -16,9 +16,9 @@ module.exports = {
// 每行最多字符数量,超出换行(默认80)
printWidth: 80,
// 超出打印宽度 (always | never | preserve )
proseWrap: "preserve",
proseWrap: 'preserve',
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
quoteProps: "as-needed",
quoteProps: 'as-needed',
// 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件默认false
requirePragma: false,
// 结尾添加分号
@ -28,18 +28,18 @@ module.exports = {
// 缩进空格数默认2个空格
tabWidth: 2,
// 元素末尾是否加逗号默认es5: ES5中的 objects, arrays 等会添加逗号TypeScript 中的 type 后不加逗号
trailingComma: "es5",
trailingComma: 'es5',
// 指定缩进方式空格或tab默认false即使用空格
useTabs: false,
// vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
vueIndentScriptAndStyle: false,
endOfLine: "auto",
endOfLine: 'auto',
overrides: [
{
files: "*.html",
files: '*.html',
options: {
parser: "html",
parser: 'html',
},
},
],

View File

@ -1 +1 @@
# Vue 3 + TypeScript + Vite
# ZS-Naiveui-Admin

View File

@ -1,5 +1,5 @@
{
"name": "iec104",
"name": "zs-naiveui-admin",
"version": "0.0.1",
"private": true,
"scripts": {
@ -41,16 +41,18 @@
]
},
"dependencies": {
"@types/crypto-js": "^4.1.1",
"@vicons/antd": "^0.12.0",
"@vicons/ionicons5": "^0.12.0",
"@vitejs/plugin-vue": "^4.4.0",
"@vueuse/core": "^10.5.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "5.1.10",
"@types/crypto-js": "^4.1.1",
"axios": "^1.6.0",
"crypto-js": "^4.2.0",
"echarts": "^5.4.3",
"element-resize-detector": "^1.2.4",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"naive-ui": "^2.36.0",
"path-browserify": "^1.0.1",
@ -61,7 +63,7 @@
"vue-router": "^4.2.5",
"vue-types": "^5.1.1",
"vue3-seamless-scroll": "^2.0.1",
"vuedraggable": "^2.24.3"
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@commitlint/cli": "^17.8.1",
@ -84,9 +86,6 @@
"husky": "^8.0.3",
"lint-staged": "^13.3.0",
"postcss": "^8.4.31",
"postcss-html": "^1.5.0",
"postcss-px-to-viewport": "^1.1.1",
"postcss-scss": "^4.0.9",
"prettier": "^2.8.8",
"sass": "^1.69.5",
"stylelint": "^15.11.0",
@ -96,6 +95,7 @@
"stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-scss": "^11.1.0",
"tailwindcss": "^3.3.3",
"typescript": "^5.3.2",
"unplugin-auto-import": "^0.15.3",
"unplugin-icons": "^0.16.6",
@ -107,4 +107,4 @@
"engines": {
"node": ">=16.0.0"
}
}
}

View File

@ -1,20 +1,27 @@
// module.exports = {
// plugins: {
// autoprefixer: {}, //// 用来给不同的浏览器自动添加相应前缀,如-webkit--moz-等等
// "postcss-px-to-viewport": {
// unitToConvert: "px", // 要转化的单位
// viewportWidth: 1920, // UI设计稿的宽度
// viewportHeight: 1080,
// unitPrecision: 10, // 转换后的精度,即小数点位数
// propList: ["*"], // 指定转换的css属性的单位*代表全部css属性的单位都进行转换
// viewportUnit: "vw", // 指定需要转换成的视窗单位默认vw
// fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位默认vw
// selectorBlackList: [".ignore"], // 指定不转换为视窗单位的类名,
// minPixelValue: 1, // 默认值1小于或等于1px则不进行转换
// mediaQuery: false, // 是否在媒体查询的css代码中也进行转换默认false
// replace: false, // 是否转换后直接更换属性值
// exclude: /(\/|\\)(node_modules)(\/|\\)/, // 设置忽略文件,用正则做目录名匹配
// // landscape: true // 是否处理横屏情况,
// },
// },
// };
module.exports = {
plugins: {
autoprefixer: {}, //// 用来给不同的浏览器自动添加相应前缀,如-webkit--moz-等等
"postcss-px-to-viewport": {
unitToConvert: "px", // 要转化的单位
viewportWidth: 1920, // UI设计稿的宽度
viewportHeight: 1080,
unitPrecision: 6, // 转换后的精度,即小数点位数
propList: ["*"], // 指定转换的css属性的单位*代表全部css属性的单位都进行转换
viewportUnit: "vw", // 指定需要转换成的视窗单位默认vw
fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位默认vw
selectorBlackList: [".ignore"], // 指定不转换为视窗单位的类名,
minPixelValue: 1, // 默认值1小于或等于1px则不进行转换
mediaQuery: false, // 是否在媒体查询的css代码中也进行转换默认false
replace: true, // 是否转换后直接更换属性值
exclude: /(\/|\\)(node_modules)(\/|\\)/, // 设置忽略文件,用正则做目录名匹配
// landscape: true // 是否处理横屏情况,
},
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -1,21 +1,89 @@
<script setup lang="ts">
import { NConfigProvider } from "naive-ui";
import { zhCN, dateZhCN } from "naive-ui";
const themeOverrides = {
common: {
primaryColor: "#646CFF",
primaryColorHover: "rgba(100,108,255,.8)",
primaryColorPressed: "rgba(100,108,255,.8)",
},
<template>
<NConfigProvider
v-if="!isLock"
:locale="zhCN"
:theme="getDarkTheme"
:theme-overrides="getThemeOverrides"
:date-locale="dateZhCN"
>
<AppProvider>
<RouterView />
</AppProvider>
</NConfigProvider>
<transition v-if="isLock && $route.name !== 'login'" name="slide-up">
<LockScreen />
</transition>
</template>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted } from "vue";
import { zhCN, dateZhCN, darkTheme } from "naive-ui";
import { LockScreen } from "@/components/Lockscreen";
import { AppProvider } from "@/components/Application";
import { useScreenLockStore } from "@/store/modules/screenLock.js";
import { useRoute } from "vue-router";
import { useDesignSettingStore } from "@/store/modules/designSetting";
import { lighten } from "@/utils";
const route = useRoute();
const useScreenLock = useScreenLockStore();
const designStore = useDesignSettingStore();
const isLock = computed(() => useScreenLock.isLocked);
const lockTime = computed(() => useScreenLock.lockTime);
/**
* @type import('naive-ui').GlobalThemeOverrides
*/
const getThemeOverrides = computed(() => {
const appTheme = designStore.appTheme;
const lightenStr = lighten(designStore.appTheme, 8);
return {
common: {
primaryColor: appTheme,
primaryColorHover: lightenStr,
primaryColorPressed: lightenStr,
primaryColorSuppl: appTheme,
},
LoadingBar: {
colorLoading: appTheme,
},
};
});
const getDarkTheme = computed(() =>
designStore.darkTheme ? darkTheme : undefined
);
let timer: any;
const timekeeping = () => {
clearInterval(timer);
if (route.name == "login" || isLock.value) return;
//
useScreenLock.setLock(false);
//
useScreenLock.setLockTime();
timer = setInterval(() => {
//
useScreenLock.setLockTime(lockTime.value - 1);
if (lockTime.value <= 0) {
//
useScreenLock.setLock(true);
return clearInterval(timer);
}
}, 1000);
};
onMounted(() => {
document.addEventListener("mousedown", timekeeping);
});
onUnmounted(() => {
document.removeEventListener("mousedown", timekeeping);
});
</script>
<template>
<n-config-provider
:locale="zhCN"
:date-locale="dateZhCN"
:theme-overrides="themeOverrides"
>
<router-view />
</n-config-provider>
</template>
<style lang="scss">
@import "styles/index";
</style>

View File

@ -1,9 +1,9 @@
import axios, { AxiosResponse, AxiosRequestConfig } from "axios";
import { ResultEnum } from "@/enums/httpEnum";
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios';
import { ResultEnum } from '@/enums/httpEnum';
// import { ErrorPageNameMap } from "@/enums/pageEnum";
// import { redirectErrorPage } from "@/utils";
import { storage } from "@/utils";
import { StorageEnum } from "@/enums/storageEnum";
import { storage } from '@/utils';
import { StorageEnum } from '@/enums/storageEnum';
const axiosInstance = axios.create({
baseURL: import.meta.env.DEV
@ -15,7 +15,8 @@ const axiosInstance = axios.create({
// 请求拦截器
axiosInstance.interceptors.request.use(
(config) => {
config.headers.Authorization = storage.get(StorageEnum.ZS_ACCESS_TOKEN);
const TOKEN = storage.get(StorageEnum.ZS_ACCESS_TOKEN);
config.headers.Authorization = TOKEN;
return config;
},
(error: AxiosRequestConfig) => {
@ -29,17 +30,17 @@ axiosInstance.interceptors.response.use(
const { message, success } = res.data as IResponse;
// 如果是文件流,直接过
if (res.config.responseType === "blob") return Promise.resolve(res.data);
if (res.config.responseType === 'blob') return Promise.resolve(res.data);
if (success) return Promise.resolve(res.data);
// 如果 success 为 false显示服务器返回的错误消息
window["$message"].error(message || "系统错误");
window['$message'].error(message || '系统错误');
// 重定向
// if (ErrorPageNameMap.get(status)) redirectErrorPage(status);
return Promise.resolve(res.data);
},
(err: AxiosResponse) => {
window["$message"].error("接口异常,请检查");
window['$message'].error('接口异常,请检查');
return Promise.reject(err);
}
);

View File

@ -1,8 +1,8 @@
import axiosInstance from "./axios";
import axiosInstance from './axios';
import type {
RequestGlobalConfigType,
RequestConfigType,
} from "@/enums/httpEnum";
} from '@/enums/httpEnum';
import {
RequestHttpEnum,
ContentTypeEnum,
@ -10,7 +10,7 @@ import {
RequestDataTypeEnum,
RequestContentTypeEnum,
RequestParamsObjType,
} from "@/enums/httpEnum";
} from '@/enums/httpEnum';
export const get = (url: string, params?: object) => {
return axiosInstance({
@ -26,7 +26,7 @@ export const post = (url: string, data?: object, headersType?: string) => {
method: RequestHttpEnum.POST,
data: data,
headers: {
"Content-Type": headersType || ContentTypeEnum.JSON,
'Content-Type': headersType || ContentTypeEnum.JSON,
},
});
};
@ -37,7 +37,7 @@ export const patch = (url: string, data?: object, headersType?: string) => {
method: RequestHttpEnum.PATCH,
data: data,
headers: {
"Content-Type": headersType || ContentTypeEnum.JSON,
'Content-Type': headersType || ContentTypeEnum.JSON,
},
});
};
@ -52,7 +52,7 @@ export const put = (
method: RequestHttpEnum.PUT,
data: data,
headers: {
"Content-Type": headersType || ContentTypeEnum.JSON,
'Content-Type': headersType || ContentTypeEnum.JSON,
},
});
};
@ -147,7 +147,7 @@ export const customizeHttp = (
const params: RequestParamsObjType = targetRequestParams.Params;
// form 类型处理
const formData: FormData = new FormData();
formData.set("default", "defaultData");
formData.set('default', 'defaultData');
// 类型处理
switch (requestParamsBodyType) {
@ -155,28 +155,28 @@ export const customizeHttp = (
break;
case RequestBodyEnum.JSON:
headers["Content-Type"] = ContentTypeEnum.JSON;
data = JSON.parse(targetRequestParams.Body["json"]);
headers['Content-Type'] = ContentTypeEnum.JSON;
data = JSON.parse(targetRequestParams.Body['json']);
// json 赋值给 data
break;
case RequestBodyEnum.XML:
headers["Content-Type"] = ContentTypeEnum.XML;
headers['Content-Type'] = ContentTypeEnum.XML;
// xml 字符串赋值给 data
data = targetRequestParams.Body["xml"];
data = targetRequestParams.Body['xml'];
break;
case RequestBodyEnum.X_WWW_FORM_URLENCODED:
headers["Content-Type"] = ContentTypeEnum.FORM_URLENCODED;
const bodyFormData = targetRequestParams.Body["x-www-form-urlencoded"];
headers['Content-Type'] = ContentTypeEnum.FORM_URLENCODED;
const bodyFormData = targetRequestParams.Body['x-www-form-urlencoded'];
for (const i in bodyFormData) formData.set(i, bodyFormData[i]);
// FormData 赋值给 data
data = formData;
break;
case RequestBodyEnum.FORM_DATA:
headers["Content-Type"] = ContentTypeEnum.FORM_DATA;
const bodyFormUrlencoded = targetRequestParams.Body["form-data"];
headers['Content-Type'] = ContentTypeEnum.FORM_DATA;
const bodyFormUrlencoded = targetRequestParams.Body['form-data'];
for (const i in bodyFormUrlencoded) {
formData.set(i, bodyFormUrlencoded[i]);
}
@ -187,7 +187,7 @@ export const customizeHttp = (
// sql 处理
if (requestContentType === RequestContentTypeEnum.SQL) {
headers["Content-Type"] = ContentTypeEnum.JSON;
headers['Content-Type'] = ContentTypeEnum.JSON;
data = requestSQLContent;
}
// 如果定义了 requestInterval 且请求次数未达到某个上限,则使用 setInterval 实现轮询

View File

@ -1,6 +1,6 @@
import { get, post } from "@/api/http";
import { get, post } from '@/api/http';
const fix = "/menu";
const fix = '/menu';
const url = {
insert: `${fix}/insert`, // 分页查询

View File

@ -1,6 +1,6 @@
import { get, post } from "@/api/http";
import { get, post } from '@/api/http';
const fix = "/role";
const fix = '/role';
const url = {
insert: `${fix}/insert`,

View File

@ -1,6 +1,6 @@
import { get, post } from "@/api/http";
import { get, post } from '@/api/http';
const fix = "/roleMenu";
const fix = '/roleMenu';
const url = {
queryMenuByRole: `${fix}/queryMenuByRole`, // 根据角色查询菜单

View File

@ -1,6 +1,6 @@
import { get, post } from "@/api/http";
import { get, post } from '@/api/http';
const fix = "/user";
const fix = '/user';
const url = {
login: `${fix}/login`,
@ -9,6 +9,8 @@ const url = {
page: `${fix}/page`,
update: `${fix}/update`,
delete: `${fix}/delete`,
updateSystemSetting: `/userStyleSetting/update`,
getUserStyleSetting: `/userStyleSetting/get`,
};
export const login = (params: Object) => {
@ -34,3 +36,11 @@ export const updateUser = (params: Object) => {
export const deleteUser = (params: Object) => {
return post(url.delete, params);
};
export const setSystemSetting = (params: Object) => {
return post(url.updateSystemSetting, params);
};
export const getUserStyleSetting = () => {
return get(url.getUserStyleSetting);
};

View File

@ -1,6 +1,6 @@
// 用户角色关系
import { get, post } from "@/api/http";
const fix = "/userRole";
import { get, post } from '@/api/http';
const fix = '/userRole';
const url = {
save: `${fix}/save`,
queryMenuTree: `${fix}/queryMenuTree `,

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox"
id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix"
in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox"
id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix"
in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#303648" mask="url(#mask-3)" x="-1" y="0" width="49"
height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16"
height="44"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="52px" height="45px" viewBox="0 0 52 45" enable-background="new 0 0 52 45" xml:space="preserve"> <image id="image0" width="52" height="45" x="0" y="0"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAtCAMAAADWf7iKAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAAdVBMVEX///8AAABkbnc9RVY7
QE9fY3GIiJZjbHk9QFOCi5QAAAA9QlZfZHMAAAA4QFEAAADt7/Lf5OTt7/KChoYAAADu8PTf3+Nw
c3MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyNUkyOEn////w8vWhURXFAAAAI3RS
TlMAAE/2/uZJUP45AvfkBP4F9LrwPwP0vUkOAREsNjk0JwYHCLrjEiIAAAABYktHRACIBR1IAAAA
CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5QgGAhE5kB5L+gAAAIZJREFUSMft1rsSgjAQhWEW
FTUYIopKlPui7/+IZqFhbMxmhm7//qvPiaKgAOIN+rbdJQCE9qO3cR2OhFTKMYgn5ZDmGcy0Q4aJ
0AhaoPdPnz8JEiRIkKCV0DkE5YQ0D12uBQ01B93uj9LSJdDPV1V71rRlMf0Iq7p+8Kw3aj4fAJYR
TCigL0lMJ5P4y7LRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTA4LTA2VDAyOjE3OjU2KzAwOjAw
Kbo8/wAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wOC0wNlQwMjoxNzo1NiswMDowMFjnhEMAAAAA
SUVORK5CYII=" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="52px" height="45px"
viewBox="0 0 52 45" enable-background="new 0 0 52 45" xml:space="preserve"> <image id="image0"
width="52"
height="45"
x="0" y="0"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAtCAMAAADWf7iKAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAkFBMVEX+/v78/Pz6+vr39/f0
9PTx8fHv7+/u7u729vbHyc1ESVsxNkgwNkg7QFFscH3T1NfGyMwxN0k9QlPa3N5vdID4+Pjz8/Pr
6+s6QVLw8PDm5ubk5OTy8vLj4+Pt7e3u8PPw8vXu8PTn5+fw8fXs7Oz5+fnp6ene3t7b29vc3Nzf
39/q6ur7+/vo6Oj9/f3///855aJlAAAAAWJLR0QvI9QgEQAAAAd0SU1FB+UHAxEtKCKzaD0AAAC8
SURBVEjH7dbLGoIgEIbhEUHI6JyYgmWWnTzd/92FTzcws2vBt38f2P0DELGYi0SiSgSPWQTeqEW6
1MhW643yiqntDkvm9gfFIBYpxWh9FBmYHP23X6dcQVHSjNYlB2mpyDqQFRVVDs7/ji41rYACCiig
gHBIWiqyxo/alYga7ufzRkR35YeaaGrX+pOgeFTPF673x3Yi7ueDxcluKDE1Qy5N1o8AY98qbgQm
Z+Yrx5sJxqhnrEXF2PzM9AV+UvDCWyYgmAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNy0wM1Qw
OTo0NTo0MCswODowMOjZqbAAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDctMDNUMDk6NDU6NDAr
MDg6MDCZhBEMAAAAIHRFWHRzb2Z0d2FyZQBodHRwczovL2ltYWdlbWFnaWNrLm9yZ7zPHZ0AAAAY
dEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAXdEVYdFRodW1iOjpJbWFnZTo6SGVp
Z2h0ADQ1+dH7kAAAABZ0RVh0VGh1bWI6OkltYWdlOjpXaWR0aAA1MoYBn/8AAAAZdEVYdFRodW1i
OjpNaW1ldHlwZQBpbWFnZS9wbmc/slZOAAAAF3RFWHRUaHVtYjo6TVRpbWUAMTYyNTI3Njc0MKmy
pv8AAAASdEVYdFRodW1iOjpTaXplADEzMzJCQoe7yB0AAABGdEVYdFRodW1iOjpVUkkAZmlsZTov
Ly9hcHAvdG1wL2ltYWdlbGMvaW1ndmlldzJfOV8xNjIzOTEyMDA2MDA1NDY4Ml8yMl9bMF2ZTW7W
AAAAAElFTkSuQmCC"></image>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox"
id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix"
in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox"
id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix"
in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49"
height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16"
height="44"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox"
id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix"
in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox"
id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix"
in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16"
height="44"></rect>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49"
height="10"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 212 KiB

2887
src/assets/svgs/logo.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 211 KiB

View File

@ -0,0 +1,30 @@
<template>
<n-dialog-provider>
<n-notification-provider>
<n-message-provider>
<slot name="default"></slot>
</n-message-provider>
</n-notification-provider>
</n-dialog-provider>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import {
NDialogProvider,
NNotificationProvider,
NMessageProvider,
} from 'naive-ui';
export default defineComponent({
name: 'Application',
components: {
NDialogProvider,
NNotificationProvider,
NMessageProvider,
},
setup() {
return {};
},
});
</script>

View File

@ -0,0 +1,3 @@
import AppProvider from './Application.vue';
export { AppProvider };

View File

@ -1,29 +1,29 @@
<script setup lang="ts">
import * as echarts from "echarts";
import * as echarts from 'echarts';
const props = defineProps({
id: {
type: String,
default: "histogram",
default: 'histogram',
},
});
let xLabel = ["09/01", "09/02", "09/03", "09/04", "09/05"];
let xLabel = ['09/01', '09/02', '09/03', '09/04', '09/05'];
let yLabel = [20, 80, 100, 40, 34, 90, 60];
const options = {
// animation: false,
grid: {
top: "18%",
bottom: "20%", //leftright
top: '18%',
bottom: '20%', //leftright
},
xAxis: {
data: xLabel,
axisLine: {
show: true, //X线
lineStyle: {
color: "#11417a",
color: '#11417a',
},
},
axisTick: {
@ -34,13 +34,13 @@ const options = {
// margin: 14,
fontSize: 12,
textStyle: {
color: "#fff", //X
color: '#fff', //X
},
},
},
yAxis: [
{
type: "value",
type: 'value',
gridIndex: 0,
min: 0,
max: 100,
@ -49,8 +49,8 @@ const options = {
splitLine: {
show: true,
lineStyle: {
color: "rgba(255, 255, 255, .2)",
type: "dashed",
color: 'rgba(255, 255, 255, .2)',
type: 'dashed',
},
},
axisTick: {
@ -59,7 +59,7 @@ const options = {
axisLine: {
show: true,
lineStyle: {
color: "#63273242",
color: '#63273242',
},
},
axisLabel: {
@ -67,26 +67,26 @@ const options = {
// margin: 14,
fontSize: 12,
textStyle: {
color: "#fff", //X
color: '#fff', //X
},
},
},
],
series: [
{
name: "",
type: "bar",
name: '',
type: 'bar',
barWidth: 20,
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 1,
color: "rgba(79, 203, 255, 1)",
color: 'rgba(79, 203, 255, 1)',
},
{
offset: 0,
color: "rgba(140, 225, 251,1)",
color: 'rgba(140, 225, 251,1)',
},
]),
},
@ -96,27 +96,27 @@ const options = {
zlevel: 0,
label: {
show: true,
position: "top",
position: 'top',
// distance: 10,
fontSize: 12,
color: "#fff",
color: '#fff',
// backgroundColor: "#0f375f",
},
},
{
//
type: "pictorialBar",
type: 'pictorialBar',
itemStyle: {
normal: {
color: "#0F375F",
color: '#0F375F',
},
},
symbolRepeat: "fixed",
symbolRepeat: 'fixed',
symbolMargin: 6,
symbol: "rect",
symbol: 'rect',
symbolClip: true,
symbolSize: [20, 2],
symbolPosition: "start",
symbolPosition: 'start',
symbolOffset: [0, -1],
// symbolBoundingData: this.total,
data: yLabel,
@ -126,7 +126,7 @@ const options = {
},
],
};
const chart = ref<any>("");
const chart = ref<any>('');
onMounted(() => {
//
@ -137,7 +137,7 @@ onMounted(() => {
chart.value.setOption(options);
//
window.addEventListener("resize", () => {
window.addEventListener('resize', () => {
chart.value.resize();
});
});

View File

@ -1,56 +1,56 @@
<script setup lang="ts">
import * as echarts from "echarts";
import * as echarts from 'echarts';
const props = defineProps({
id: {
type: String,
default: "lineChart",
default: 'lineChart',
},
});
const colors = "RGBA(30, 214, 255,";
const colors = 'RGBA(30, 214, 255,';
const options = {
color: [colors + "1)"],
color: [colors + '1)'],
tooltip: {
trigger: "axis",
backgroundColor: "#4B4F52",
borderColor: "#4B4F52",
trigger: 'axis',
backgroundColor: '#4B4F52',
borderColor: '#4B4F52',
padding: 8,
textStyle: {
color: "#fff",
color: '#fff',
},
},
grid: {
left: "3%",
right: "4%",
bottom: "10%",
top: "10%",
left: '3%',
right: '4%',
bottom: '10%',
top: '10%',
containLabel: true,
},
xAxis: {
type: "category",
type: 'category',
boundaryGap: false,
axisLabel: {
textStyle: {
color: "#ffffff", //
color: '#ffffff', //
fontSize: 12, //
// fontFamily: "SourceHanSansCN",
},
},
data: ["00:00", "01:00", "02:00", "03:00"],
data: ['00:00', '01:00', '02:00', '03:00'],
},
yAxis: {
splitLine: {
show: true,
lineStyle: {
color: "rgba(255, 255, 255, .2)",
type: "dashed",
color: 'rgba(255, 255, 255, .2)',
type: 'dashed',
},
},
axisLabel: {
textStyle: {
color: "#ffffff", //
color: '#ffffff', //
fontSize: 12, //
// fontFamily: "SourceHanSansCN",
},
@ -58,19 +58,19 @@ const options = {
},
series: [
{
name: "",
type: "line",
name: '',
type: 'line',
showSymbol: false,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: colors + "0.6)",
color: colors + '0.6)',
},
{
offset: 1,
color: colors + "0)",
color: colors + '0)',
},
]),
},
@ -79,7 +79,7 @@ const options = {
},
],
};
const chart = ref<any>("");
const chart = ref<any>('');
onMounted(() => {
//
@ -90,7 +90,7 @@ onMounted(() => {
chart.value.setOption(options);
//
window.addEventListener("resize", () => {
window.addEventListener('resize', () => {
chart.value.resize();
});
});

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import * as echarts from "echarts";
import * as echarts from 'echarts';
interface ChartDataType {
name: string;
@ -11,14 +11,14 @@ const chartObj = reactive({
online: 0,
offline: 0,
rate: 0,
onlineIcon: "",
offlineIcon: "",
onlineIcon: '',
offlineIcon: '',
});
const props = defineProps({
id: {
type: String,
default: "barChart",
default: 'barChart',
},
chartData: {
@ -44,29 +44,29 @@ watch(
}
);
const isColor = ["rgba(79, 203, 255, 1)", "rgba(140, 225, 251,1)"];
const isColor = ['rgba(79, 203, 255, 1)', 'rgba(140, 225, 251,1)'];
const options = {
legend: {
show: false,
},
title: {
text: "{a|" + chartObj.rate + "}{c|%}",
x: "40.5%",
y: "40%",
text: '{a|' + chartObj.rate + '}{c|%}',
x: '40.5%',
y: '40%',
textStyle: {
rich: {
a: {
fontSize: 29, //
color: "#dbe2ea",
fontWeight: "bold",
fontFamily: "SourceHanSansCN",
color: '#dbe2ea',
fontWeight: 'bold',
fontFamily: 'SourceHanSansCN',
},
c: {
fontSize: 15,
color: "#dbe2ea",
fontWeight: "500",
fontFamily: "SourceHanSansCN",
color: '#dbe2ea',
fontWeight: '500',
fontFamily: 'SourceHanSansCN',
},
},
},
@ -74,10 +74,10 @@ const options = {
series: [
{
legendHoverLink: false, // false
center: ["50.1%", "50%"],
name: "",
type: "pie",
radius: ["85%", "75%"],
center: ['50.1%', '50%'],
name: '',
type: 'pie',
radius: ['85%', '75%'],
silent: true,
clockwise: true,
startAngle: 90,
@ -85,13 +85,13 @@ const options = {
zlevel: 0,
label: {
normal: {
position: "center",
position: 'center',
},
},
data: [
{
value: chartObj.online,
name: "在线",
name: '在线',
label: {
normal: {
show: false,
@ -117,7 +117,7 @@ const options = {
},
{
value: chartObj.offline,
name: "离线",
name: '离线',
label: {
normal: {
show: false,
@ -125,7 +125,7 @@ const options = {
},
itemStyle: {
normal: {
color: "rgba(86, 118, 139, 1)",
color: 'rgba(86, 118, 139, 1)',
},
},
},
@ -133,7 +133,7 @@ const options = {
},
],
};
const chart = ref<any>("");
const chart = ref<any>('');
onMounted(() => {
//
@ -144,7 +144,7 @@ onMounted(() => {
chart.value.setOption(options);
//
window.addEventListener("resize", () => {
window.addEventListener('resize', () => {
chart.value.resize();
});
});

View File

@ -1,4 +1,4 @@
export { default as BasicForm } from "./src/BasicForm2.vue";
export { useForm } from "./src/hooks/useForm";
export * from "./src/types/form";
export * from "./src/types/index";
export { default as BasicForm } from './src/BasicForm2.vue';
export { useForm } from './src/hooks/useForm';
export * from './src/types/form';
export * from './src/types/index';

View File

@ -117,7 +117,7 @@
<UpOutlined />
</n-icon>
</template>
{{ overflow ? "展开" : "收起" }}
{{ overflow ? '展开' : '收起' }}
</n-button>
</n-space>
</n-gi>
@ -134,27 +134,27 @@ import {
unref,
onMounted,
watch,
} from "vue";
import { createPlaceholderMessage } from "./helper";
import { useFormEvents } from "./hooks/useFormEvents";
import { useFormValues } from "./hooks/useFormValues";
} from 'vue';
import { createPlaceholderMessage } from './helper';
import { useFormEvents } from './hooks/useFormEvents';
import { useFormValues } from './hooks/useFormValues';
import { basicProps } from "./props";
import { DownOutlined, UpOutlined, QuestionCircleOutlined } from "@vicons/antd";
import { basicProps } from './props';
import { DownOutlined, UpOutlined, QuestionCircleOutlined } from '@vicons/antd';
import type { Ref } from "vue";
import type { GridProps } from "naive-ui/lib/grid";
import type { FormSchema, FormProps, FormActionType } from "./types/form";
import type { Ref } from 'vue';
import type { GridProps } from 'naive-ui/lib/grid';
import type { FormSchema, FormProps, FormActionType } from './types/form';
import { isArray, deepMerge } from "@/utils";
import { isArray, deepMerge } from '@/utils';
export default defineComponent({
name: "BasicForm",
name: 'BasicForm',
components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
props: {
...basicProps,
},
emits: ["reset", "submit", "register"],
emits: ['reset', 'submit', 'register'],
setup(props, { emit, attrs }) {
const defaultFormModel = ref<any>({});
const formModel = reactive<any>({});
@ -169,7 +169,7 @@ export default defineComponent({
return Object.assign(
{
size: props.size,
type: "primary",
type: 'primary',
},
props.submitButtonOptions
);
@ -179,7 +179,7 @@ export default defineComponent({
return Object.assign(
{
size: props.size,
type: "default",
type: 'default',
},
props.resetButtonOptions
);
@ -211,7 +211,7 @@ export default defineComponent({
const isInline = computed(() => {
const { layout } = unref(getProps);
return layout === "inline";
return layout === 'inline';
});
const getGrid = computed((): GridProps => {
@ -219,7 +219,7 @@ export default defineComponent({
return {
...gridProps,
collapsed: isInline.value ? gridCollapsed.value : false,
responsive: "screen",
responsive: 'screen',
};
});
@ -298,7 +298,7 @@ export default defineComponent({
onMounted(() => {
initDefault();
emit("register", formActionType);
emit('register', formActionType);
});
return {

View File

@ -1,13 +1,13 @@
<script setup lang="ts">
import { createPlaceholderMessage } from "./helper";
import { useFormEvents } from "./hooks/useFormEvents";
import { useFormValues } from "./hooks/useFormValues";
import { basicProps } from "./props";
import { DownOutlined, UpOutlined, QuestionCircleOutlined } from "@vicons/antd";
import { isArray, deepMerge } from "@/utils";
import type { FormSchema, FormProps, FormActionType } from "./types/form";
import { createPlaceholderMessage } from './helper';
import { useFormEvents } from './hooks/useFormEvents';
import { useFormValues } from './hooks/useFormValues';
import { basicProps } from './props';
import { DownOutlined, UpOutlined, QuestionCircleOutlined } from '@vicons/antd';
import { isArray, deepMerge } from '@/utils';
import type { FormSchema, FormProps, FormActionType } from './types/form';
const props = defineProps(basicProps);
const emit = defineEmits(["reset", "submit", "register"]);
const emit = defineEmits(['reset', 'submit', 'register']);
const defaultFormModel = ref({});
const formModel = reactive({});
@ -22,7 +22,7 @@ const getSubmitBtnOptions = computed(() => {
return Object.assign(
{
size: props.size,
type: "primary",
type: 'primary',
},
props.submitButtonOptions
);
@ -32,7 +32,7 @@ const getResetBtnOptions = computed(() => {
return Object.assign(
{
size: props.size,
type: "default",
type: 'default',
},
props.resetButtonOptions
);
@ -64,7 +64,7 @@ const getProps = computed(() => {
const isInline = computed(() => {
const { layout } = unref(getProps);
return layout === "inline";
return layout === 'inline';
});
const getGrid = computed(() => {
@ -72,7 +72,7 @@ const getGrid = computed(() => {
return {
...gridProps,
collapsed: isInline.value ? gridCollapsed.value : false,
responsive: "screen",
responsive: 'screen',
};
});
@ -149,7 +149,7 @@ watch(
onMounted(() => {
initDefault();
emit("register", formActionType);
emit('register', formActionType);
});
</script>
@ -279,7 +279,7 @@ onMounted(() => {
<UpOutlined />
</n-icon>
</template>
{{ overflow ? "展开" : "收起" }}
{{ overflow ? '展开' : '收起' }}
</n-button>
</n-space>
</n-gi>

View File

@ -1,29 +1,29 @@
import { ComponentType } from "./types/index";
import { ComponentType } from './types/index';
/**
* @description: placeholder
*/
export function createPlaceholderMessage(component: ComponentType) {
if (component === "NInput") return "请输入";
if (component === 'NInput') return '请输入';
if (
[
"NPicker",
"NSelect",
"NCheckbox",
"NRadio",
"NSwitch",
"NDatePicker",
"NTimePicker",
'NPicker',
'NSelect',
'NCheckbox',
'NRadio',
'NSwitch',
'NDatePicker',
'NTimePicker',
].includes(component)
)
return "请选择";
return "";
return '请选择';
return '';
}
const DATE_TYPE = ["DatePicker", "MonthPicker", "WeekPicker", "TimePicker"];
const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'];
function genType() {
return [...DATE_TYPE, "RangePicker"];
return [...DATE_TYPE, 'RangePicker'];
}
/**
@ -32,17 +32,17 @@ function genType() {
export const dateItemType = genType();
export function defaultType(component) {
if (component === "NInput") return "";
if (component === "NInputNumber") return null;
if (component === 'NInput') return '';
if (component === 'NInputNumber') return null;
return [
"NPicker",
"NSelect",
"NCheckbox",
"NRadio",
"NSwitch",
"NDatePicker",
"NTimePicker",
'NPicker',
'NSelect',
'NCheckbox',
'NRadio',
'NSwitch',
'NDatePicker',
'NTimePicker',
].includes(component)
? ""
? ''
: undefined;
}

View File

@ -2,11 +2,11 @@ import type {
FormProps,
FormActionType,
UseFormReturnType,
} from "../types/form";
import type { ComputedRef, Ref } from "vue";
import { ref, onUnmounted, unref, nextTick, watch } from "vue";
import { isProdMode } from "@/utils/env";
import { getDynamicProps } from "@/utils";
} from '../types/form';
import type { ComputedRef, Ref } from 'vue';
import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
import { isProdMode } from '@/utils/env';
import { getDynamicProps } from '@/utils';
type DynamicProps<T> = {
[P in keyof T]: Ref<T[P]> | T[P] | ComputedRef<T[P]>;
@ -22,7 +22,7 @@ export function useForm(props?: Props): UseFormReturnType {
const form = unref(formRef);
if (!form) {
console.error(
"The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!"
'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!'
);
}
await nextTick();

View File

@ -1,6 +1,6 @@
import { provide, inject } from "vue";
import { provide, inject } from 'vue';
const key = Symbol("formElRef");
const key = Symbol('formElRef');
export function createFormContext(instance) {
provide(key, instance);

View File

@ -1,7 +1,7 @@
import type { ComputedRef, Ref } from "vue";
import type { FormProps, FormSchema, FormActionType } from "../types/form";
import { unref, toRaw } from "vue";
import { isFunction } from "@/utils";
import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchema, FormActionType } from '../types/form';
import { unref, toRaw } from 'vue';
import { isFunction } from '@/utils';
declare type EmitType = (event: string, ...args: any[]) => void;
@ -47,10 +47,10 @@ export function useFormEvents({
await validate();
const values = getFieldsValue();
loadingSub.value = false;
emit("submit", values);
emit('submit', values);
return values;
} catch (error: any) {
emit("submit", false);
emit('submit', false);
loadingSub.value = false;
console.error(error);
return false;
@ -75,7 +75,7 @@ export function useFormEvents({
});
await clearValidate();
const fromValues = handleFormValues(toRaw(unref(formModel)));
emit("reset", fromValues);
emit('reset', fromValues);
submitOnReset && (await handleSubmit());
}

View File

@ -4,11 +4,11 @@ import {
isObject,
isString,
isNullOrUnDef,
} from "@/utils/is";
import { unref } from "vue";
import type { Ref, ComputedRef } from "vue";
import type { FormSchema } from "../types/form";
import { set } from "lodash-es";
} from '@/utils/is';
import { unref } from 'vue';
import type { Ref, ComputedRef } from 'vue';
import type { FormSchema } from '../types/form';
import { set } from 'lodash-es';
interface UseFormValuesContext {
defaultFormModel: Ref<any>;

View File

@ -1,8 +1,8 @@
import type { CSSProperties, PropType } from "vue";
import { FormSchema } from "./types/form";
import type { GridProps, GridItemProps } from "naive-ui/lib/grid";
import type { ButtonProps } from "naive-ui/lib/button";
import { propTypes } from "@/utils/propTypes";
import type { CSSProperties, PropType } from 'vue';
import { FormSchema } from './types/form';
import type { GridProps, GridItemProps } from 'naive-ui/lib/grid';
import type { ButtonProps } from 'naive-ui/lib/button';
import { propTypes } from '@/utils/propTypes';
export const basicProps = {
// 标签宽度 固定宽度
labelWidth: {
@ -17,7 +17,7 @@ export const basicProps = {
//布局方式
layout: {
type: String,
default: "inline",
default: 'inline',
},
//是否展示为行内表单
inline: {
@ -27,12 +27,12 @@ export const basicProps = {
//大小
size: {
type: String,
default: "medium",
default: 'medium',
},
//标签位置
labelPlacement: {
type: String,
default: "left",
default: 'left',
},
//组件是否width 100%
isFull: {
@ -54,12 +54,12 @@ export const basicProps = {
// 确认按钮文字
submitButtonText: {
type: String,
default: "查询",
default: '查询',
},
//重置按钮文字
resetButtonText: {
type: String,
default: "重置",
default: '重置',
},
//grid 配置
gridProps: Object as PropType<GridProps>,

View File

@ -1,7 +1,7 @@
import { ComponentType } from "./index";
import type { CSSProperties } from "vue";
import type { GridProps, GridItemProps } from "naive-ui/lib/grid";
import type { ButtonProps } from "naive-ui/lib/button";
import { ComponentType } from './index';
import type { CSSProperties } from 'vue';
import type { GridProps, GridItemProps } from 'naive-ui/lib/grid';
import type { ButtonProps } from 'naive-ui/lib/button';
export interface FormSchema {
field: string;

View File

@ -1,28 +1,28 @@
export type ComponentType =
| "NInput"
| "NInputGroup"
| "NInputPassword"
| "NInputSearch"
| "NInputTextArea"
| "NInputNumber"
| "NInputCountDown"
| "NSelect"
| "NTreeSelect"
| "NRadioButtonGroup"
| "NRadioGroup"
| "NCheckbox"
| "NCheckboxGroup"
| "NAutoComplete"
| "NCascader"
| "NDatePicker"
| "NMonthPicker"
| "NRangePicker"
| "NWeekPicker"
| "NTimePicker"
| "NSwitch"
| "NStrengthMeter"
| "NUpload"
| "NIconPicker"
| "NRender"
| "NSlider"
| "NRate";
| 'NInput'
| 'NInputGroup'
| 'NInputPassword'
| 'NInputSearch'
| 'NInputTextArea'
| 'NInputNumber'
| 'NInputCountDown'
| 'NSelect'
| 'NTreeSelect'
| 'NRadioButtonGroup'
| 'NRadioGroup'
| 'NCheckbox'
| 'NCheckboxGroup'
| 'NAutoComplete'
| 'NCascader'
| 'NDatePicker'
| 'NMonthPicker'
| 'NRangePicker'
| 'NWeekPicker'
| 'NTimePicker'
| 'NSwitch'
| 'NStrengthMeter'
| 'NUpload'
| 'NIconPicker'
| 'NRender'
| 'NSlider'
| 'NRate';

View File

@ -0,0 +1,303 @@
<template>
<div
:class="{ onLockLogin: showLogin }"
class="lockscreen"
@keyup="onLockLogin(true)"
@mousedown.stop
@contextmenu.prevent
>
<template v-if="!showLogin">
<div class="lock-box">
<div class="lock">
<span class="lock-icon" title="解锁屏幕" @click="onLockLogin(true)">
<n-icon>
<lock-outlined />
</n-icon>
</span>
</div>
</div>
<!--充电-->
<recharge
:battery="battery"
:battery-status="batteryStatus"
:calc-discharging-time="calcDischargingTime"
:calc-charging-time="calcChargingTime"
/>
<div class="local-time">
<div class="time">{{ hour }}:{{ minute }}</div>
<div class="date">{{ month }}{{ day }}星期{{ week }}</div>
</div>
<div class="computer-status">
<span :class="{ offline: !online }" class="network">
<wifi-outlined class="network" />
</span>
<api-outlined />
</div>
</template>
<!--登录-->
<template v-if="showLogin">
<div class="login-box">
<n-avatar :size="128">
<n-icon>
<user-outlined />
</n-icon>
</n-avatar>
<div class="username">{{ loginParams.username }}</div>
<n-input
type="password"
autofocus
v-model:value="loginParams.password"
@keyup.enter="onLogin"
placeholder="请输入登录密码"
>
<template #suffix>
<n-icon @click="onLogin" style="cursor: pointer">
<LoadingOutlined v-if="loginLoading" />
<arrow-right-outlined v-else />
</n-icon>
</template>
</n-input>
<div class="flex w-full" v-if="isLoginError">
<span class="text-red-500">{{ errorMsg }}</span>
</div>
<div class="flex justify-around w-full mt-1">
<div><a @click="showLogin = false">返回</a></div>
<div><a @click="goLogin">重新登录</a></div>
<div><a @click="onLogin">进入系统</a></div>
</div>
</div>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue';
import { ResultEnum } from '@/enums/httpEnum';
import recharge from './Recharge.vue';
import {
LockOutlined,
LoadingOutlined,
UserOutlined,
ApiOutlined,
ArrowRightOutlined,
WifiOutlined,
} from '@vicons/antd';
import { useRouter, useRoute } from 'vue-router';
import { useOnline } from '@/hooks/useOnline';
import { useTime } from '@/hooks/useTime';
import { useBattery } from '@/hooks/useBattery';
import { useScreenLockStore } from '@/store/modules/screenLock';
import { UserInfoType, useUserStore } from '@/store/modules/user';
export default defineComponent({
name: 'ScreenLock',
components: {
LockOutlined,
LoadingOutlined,
UserOutlined,
ArrowRightOutlined,
ApiOutlined,
WifiOutlined,
recharge,
},
setup() {
const useScreenLock = useScreenLockStore();
const userStore = useUserStore();
//
const { month, day, hour, minute, second, week } = useTime();
const { online } = useOnline();
const router = useRouter();
const route = useRoute();
const { battery, batteryStatus, calcDischargingTime, calcChargingTime } =
useBattery();
const userInfo: UserInfoType = userStore.getUserInfo || {};
const username = userInfo['username'] || '';
const state = reactive({
showLogin: false,
loginLoading: false, //
isLoginError: false, //
errorMsg: '密码错误',
loginParams: {
username: username || '',
password: '',
},
});
//
const onLockLogin = (value: boolean) => (state.showLogin = value);
//
const onLogin = async () => {
if (!state.loginParams.password.trim()) {
return;
}
const params = {
isLock: true,
...state.loginParams,
};
state.loginLoading = true;
const { status } = await userStore.login(params);
if (status === ResultEnum.STATUS) {
onLockLogin(false);
useScreenLock.setLock(false);
} else {
state.errorMsg = '密码错误';
state.isLoginError = true;
}
state.loginLoading = false;
};
//
const goLogin = () => {
onLockLogin(false);
useScreenLock.setLock(false);
router.replace({
path: '/login',
query: {
redirect: route.fullPath,
},
});
};
return {
...toRefs(state),
online,
month,
day,
hour,
minute,
second,
week,
battery,
batteryStatus,
calcDischargingTime,
calcChargingTime,
onLockLogin,
onLogin,
goLogin,
};
},
});
</script>
<style lang="scss" scoped>
.lockscreen {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
overflow: hidden;
color: white;
background: #000;
&.onLockLogin {
background-color: rgb(25 28 34 / 88%);
backdrop-filter: blur(7px);
}
.login-box {
position: absolute;
top: 45%;
left: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transform: translate(-50%, -50%);
> * {
margin-bottom: 14px;
}
.username {
font-size: 30px;
}
}
.lock-box {
position: absolute;
top: 20px;
left: 50%;
z-index: 100;
font-size: 34px;
transform: translateX(-50%);
.tips {
color: white;
cursor: text;
}
.lock {
display: flex;
justify-content: center;
.lock-icon {
cursor: pointer;
.anticon-unlock {
display: none;
}
&:hover .anticon-unlock {
display: initial;
}
&:hover .anticon-lock {
display: none;
}
}
}
}
.local-time {
position: absolute;
bottom: 60px;
left: 60px;
font-family: helvetica;
.time {
font-size: 70px;
}
.date {
font-size: 40px;
}
}
.computer-status {
position: absolute;
right: 60px;
bottom: 60px;
font-size: 24px;
> * {
margin-left: 14px;
}
.network {
position: relative;
&.offline::before {
position: absolute;
top: 50%;
left: 50%;
z-index: 10;
width: 2px;
height: 28px;
content: '';
background-color: red;
transform: translate(-50%, -50%) rotate(45deg);
}
}
}
}
</style>

View File

@ -0,0 +1,174 @@
<template>
<div class="container">
<div class="number">{{ battery.level }}%</div>
<div class="contrast">
<div class="circle"></div>
<ul class="bubbles">
<li v-for="i in 15" :key="i"></li>
</ul>
</div>
<div class="charging">
<div>{{ batteryStatus }}</div>
<div
v-show="
Number.isFinite(battery.dischargingTime) &&
battery.dischargingTime != 0
"
>
剩余可使用时间{{ calcDischargingTime }}
</div>
<span
v-show="
Number.isFinite(battery.chargingTime) && battery.chargingTime != 0
"
>
距离电池充满需要{{ calcChargingTime }}
</span>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'HuaweiCharge',
// props: ['batteryStatus', 'battery', 'calcDischargingTime'],
props: {
battery: {
//
type: Object,
default: () => ({}),
},
calcDischargingTime: {
//
type: String,
default: '',
},
calcChargingTime: {
type: String,
default: '',
},
batteryStatus: {
//
type: String,
validator: (val: string) =>
['充电中', '已充满', '已断开电源'].includes(val),
},
},
});
</script>
<style lang="scss" scoped>
.container {
position: absolute;
bottom: 20vh;
left: 50vw;
width: 300px;
height: 500px;
transform: translateX(-50%);
.number {
position: absolute;
top: 20%;
z-index: 10;
width: 300px;
font-size: 32px;
color: #fff;
text-align: center;
}
.contrast {
width: 300px;
height: 400px;
overflow: hidden;
background-color: #000;
filter: contrast(15) hue-rotate(0);
animation: hueRotate 10s infinite linear;
.circle {
position: relative;
box-sizing: border-box;
width: 300px;
height: 300px;
filter: blur(8px);
&::after {
position: absolute;
top: 40%;
left: 50%;
width: 200px;
height: 200px;
content: '';
background-color: #00ff6f;
border-radius: 42% 38% 62% 49% / 45%;
transform: translate(-50%, -50%) rotate(0);
animation: rotate 10s infinite linear;
}
&::before {
position: absolute;
top: 40%;
left: 50%;
z-index: 10;
width: 176px;
height: 176px;
content: '';
background-color: #000;
border-radius: 50%;
transform: translate(-50%, -50%);
}
}
.bubbles {
position: absolute;
bottom: 0;
left: 50%;
width: 100px;
height: 40px;
background-color: #00ff6f;
filter: blur(5px);
border-radius: 100px 100px 0 0;
transform: translate(-50%, 0);
li {
position: absolute;
background: #00ff6f;
border-radius: 50%;
}
}
}
.charging {
font-size: 20px;
text-align: center;
}
}
@keyframes rotate {
50% {
border-radius: 45% / 42% 38% 58% 49%;
}
100% {
transform: translate(-50%, -50%) rotate(720deg);
}
}
@keyframes moveToTop {
90% {
opacity: 1;
}
100% {
opacity: 0.1;
transform: translate(-50%, -180px);
}
}
@keyframes hueRotate {
100% {
filter: contrast(15) hue-rotate(360deg);
}
}
</style>

View File

@ -0,0 +1,3 @@
import LockScreen from './Lockscreen.vue';
export { LockScreen };

View File

@ -1,45 +1,41 @@
<template>
<svg
aria-hidden="true"
class="svg-icon"
:style="'width:' + size + ';height:' + size"
>
<svg aria-hidden="true" class="svg-icon">
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script setup lang="ts">
const props = defineProps({
prefix: {
type: String,
default: "icon",
<script>
import { defineComponent, computed } from 'vue';
export default defineComponent({
name: 'SvgIcon',
props: {
// 使svgsvg
name: {
type: String,
required: true,
},
prefix: {
type: String,
default: 'icon',
},
color: {
type: String,
default: '#333',
},
},
iconClass: {
type: String,
required: false,
default: "",
},
color: {
type: String,
default: "",
},
size: {
type: String,
default: "1em",
setup(props) {
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
return { symbolId };
},
});
const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
</script>
<style scoped>
<style scope>
.svg-icon {
display: inline-block;
width: 1em;
height: 1em;
overflow: hidden;
vertical-align: -0.15em; /* 因icon大小被设置为和字体大小一致而span等标签的下边缘会和字体的基线对齐故需设置一个往下的偏移比例来纠正视觉上的未对齐效果 */
outline: none;
fill: currentcolor; /* 定义元素的颜色currentColor是一个变量这个变量的值就表示当前元素的color值如果当前元素未设置color值则从父元素继承 */
width: 16px;
height: 16px;
/* color: #333; */
fill: currentcolor;
}
</style>

View File

@ -1,4 +1,4 @@
export { default as BasicTable } from "./src/Table.vue";
export { default as TableAction } from "./src/components/TableAction.vue";
export * from "./src/types/table";
export * from "./src/types/tableAction";
export { default as BasicTable } from './src/Table.vue';
export { default as TableAction } from './src/components/TableAction.vue';
export * from './src/types/table';
export * from './src/types/tableAction';

View File

@ -95,44 +95,44 @@ import {
toRefs,
onMounted,
nextTick,
} from "vue";
} from 'vue';
import {
ReloadOutlined,
ColumnHeightOutlined,
QuestionCircleOutlined,
} from "@vicons/antd";
import { createTableContext } from "./hooks/useTableContext";
} from '@vicons/antd';
import { createTableContext } from './hooks/useTableContext';
import ColumnSetting from "./components/settings/ColumnSetting.vue";
import ColumnSetting from './components/settings/ColumnSetting.vue';
import { useLoading } from "./hooks/useLoading";
import { useColumns } from "./hooks/useColumns";
import { useDataSource } from "./hooks/useDataSource";
import { usePagination } from "./hooks/usePagination";
import { useLoading } from './hooks/useLoading';
import { useColumns } from './hooks/useColumns';
import { useDataSource } from './hooks/useDataSource';
import { usePagination } from './hooks/usePagination';
import { basicProps } from "./props";
import { basicProps } from './props';
import { BasicTableProps } from "./types/table";
import { BasicTableProps } from './types/table';
import { getViewportOffset } from "@/utils";
import { useWindowSizeFn } from "@/hooks/event/useWindowSizeFn";
import { isBoolean } from "@/utils";
import { getViewportOffset } from '@/utils';
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn';
import { isBoolean } from '@/utils';
const densityOptions = [
{
type: "menu",
label: "紧凑",
key: "small",
type: 'menu',
label: '紧凑',
key: 'small',
},
{
type: "menu",
label: "默认",
key: "medium",
type: 'menu',
label: '默认',
key: 'medium',
},
{
type: "menu",
label: "宽松",
key: "large",
type: 'menu',
label: '宽松',
key: 'large',
},
];
@ -147,13 +147,13 @@ export default defineComponent({
...basicProps,
},
emits: [
"fetch-success",
"fetch-error",
"update:checked-row-keys",
"edit-end",
"edit-cancel",
"edit-row-end",
"edit-change",
'fetch-success',
'fetch-error',
'update:checked-row-keys',
'edit-end',
'edit-cancel',
'edit-row-end',
'edit-change',
],
setup(props, { emit }) {
const deviceHeight = ref(150);
@ -193,7 +193,7 @@ export default defineComponent({
} = useColumns(getProps);
const state = reactive({
tableSize: unref(getProps as any).size || "medium",
tableSize: unref(getProps as any).size || 'medium',
isColumnSetting: false,
});
@ -216,7 +216,7 @@ export default defineComponent({
//
function updateCheckedRowKeys(rowKeys) {
emit("update:checked-row-keys", rowKeys);
emit('update:checked-row-keys', rowKeys);
}
//
@ -225,7 +225,7 @@ export default defineComponent({
//
const getBindValues = computed(() => {
const tableData = unref(getDataSourceRef);
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : "auto";
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
return {
...unref(getProps),
loading: unref(getLoading),
@ -234,7 +234,7 @@ export default defineComponent({
data: tableData,
size: unref(getTableSize),
remote: true,
"max-height": maxHeight,
'max-height': maxHeight,
};
});
@ -269,14 +269,14 @@ export default defineComponent({
if (!table) return;
if (!unref(getCanResize)) return;
const tableEl: any = table?.$el;
const headEl = tableEl.querySelector(".n-data-table-thead ");
const headEl = tableEl.querySelector('.n-data-table-thead ');
const { bottomIncludeBody } = getViewportOffset(headEl);
const headerH = 64;
let paginationH = 2;
let marginH = 24;
if (!isBoolean(unref(pagination))) {
paginationEl = tableEl.querySelector(
".n-data-table__pagination"
'.n-data-table__pagination'
) as HTMLElement;
if (paginationEl) {
const offsetHeight = paginationEl.offsetHeight;

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
import type { Component } from "vue";
import type { Component } from 'vue';
import {
NInput,
NSelect,
@ -8,28 +8,28 @@ import {
NSwitch,
NDatePicker,
NTimePicker,
} from "naive-ui";
import type { ComponentType } from "./types/componentType";
} from 'naive-ui';
import type { ComponentType } from './types/componentType';
export enum EventEnum {
NInput = "on-input",
NInputNumber = "on-input",
NSelect = "on-update:value",
NSwitch = "on-update:value",
NCheckbox = "on-update:value",
NDatePicker = "on-update:value",
NTimePicker = "on-update:value",
NInput = 'on-input',
NInputNumber = 'on-input',
NSelect = 'on-update:value',
NSwitch = 'on-update:value',
NCheckbox = 'on-update:value',
NDatePicker = 'on-update:value',
NTimePicker = 'on-update:value',
}
const componentMap = new Map<ComponentType, Component>();
componentMap.set("NInput", NInput);
componentMap.set("NInputNumber", NInputNumber);
componentMap.set("NSelect", NSelect);
componentMap.set("NSwitch", NSwitch);
componentMap.set("NCheckbox", NCheckbox);
componentMap.set("NDatePicker", NDatePicker);
componentMap.set("NTimePicker", NTimePicker);
componentMap.set('NInput', NInput);
componentMap.set('NInputNumber', NInputNumber);
componentMap.set('NSelect', NSelect);
componentMap.set('NSwitch', NSwitch);
componentMap.set('NCheckbox', NCheckbox);
componentMap.set('NDatePicker', NDatePicker);
componentMap.set('NTimePicker', NTimePicker);
export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component);

View File

@ -41,14 +41,14 @@
</template>
<script lang="ts">
import { defineComponent, PropType, computed, toRaw } from "vue";
import { ActionItem } from "@/components/Table";
import { defineComponent, PropType, computed, toRaw } from 'vue';
import { ActionItem } from '@/components/Table';
// import { usePermission } from "@/hooks/web/usePermission";
import { isBoolean, isFunction } from "@/utils";
import { DownOutlined } from "@vicons/antd";
import { isBoolean, isFunction } from '@/utils';
import { DownOutlined } from '@vicons/antd';
export default defineComponent({
name: "TableAction",
name: 'TableAction',
components: { DownOutlined },
props: {
actions: {
@ -62,7 +62,7 @@ export default defineComponent({
},
style: {
type: String as PropType<String>,
default: "button",
default: 'button',
},
select: {
type: Function as PropType<Function>,
@ -71,15 +71,15 @@ export default defineComponent({
},
setup(props) {
const actionType =
props.style === "button"
? "default"
: props.style === "text"
? "primary"
: "default";
props.style === 'button'
? 'default'
: props.style === 'text'
? 'primary'
: 'default';
const actionText =
props.style === "button"
props.style === 'button'
? undefined
: props.style === "text"
: props.style === 'text'
? true
: undefined;
@ -87,7 +87,7 @@ export default defineComponent({
return {
text: actionText,
type: actionType,
size: "small",
size: 'small',
};
});
@ -99,7 +99,7 @@ export default defineComponent({
.map((action) => {
const { popConfirm } = action;
return {
size: "small",
size: 'small',
text: actionText,
type: actionType,
...action,
@ -133,7 +133,7 @@ export default defineComponent({
const { popConfirm } = action;
//
return {
size: "small",
size: 'small',
text: actionText,
type: actionType,
...action,

View File

@ -1,10 +1,10 @@
import type { FunctionalComponent, defineComponent } from "vue";
import type { ComponentType } from "../../types/componentType";
import { componentMap } from "@/components/Table/src/componentMap";
import type { FunctionalComponent, defineComponent } from 'vue';
import type { ComponentType } from '../../types/componentType';
import { componentMap } from '@/components/Table/src/componentMap';
import { h } from "vue";
import { h } from 'vue';
import { NPopover } from "naive-ui";
import { NPopover } from 'naive-ui';
export interface ComponentProps {
component: ComponentType;
@ -15,7 +15,7 @@ export interface ComponentProps {
export const CellComponent: FunctionalComponent = (
{
component = "NInput",
component = 'NInput',
rule = true,
ruleMessage,
popoverVisible,
@ -30,17 +30,17 @@ export const CellComponent: FunctionalComponent = (
}
return h(
NPopover,
{ "display-directive": "show", show: !!popoverVisible, manual: "manual" },
{ 'display-directive': 'show', show: !!popoverVisible, manual: 'manual' },
{
trigger: () => DefaultComp,
default: () =>
h(
"span",
'span',
{
style: {
color: "red",
width: "90px",
display: "inline-block",
color: 'red',
width: '90px',
display: 'inline-block',
},
},
{

View File

@ -36,9 +36,9 @@
</div>
</template>
<script lang="ts">
import type { PropType } from "vue";
import type { BasicColumn } from "../../types/table";
import type { EditRecordRow } from "./index";
import type { PropType } from 'vue';
import type { BasicColumn } from '../../types/table';
import type { EditRecordRow } from './index';
import {
defineComponent,
@ -48,24 +48,24 @@ import {
computed,
watchEffect,
toRaw,
} from "vue";
import { FormOutlined, CloseOutlined, CheckOutlined } from "@vicons/antd";
import { CellComponent } from "./CellComponent";
} from 'vue';
import { FormOutlined, CloseOutlined, CheckOutlined } from '@vicons/antd';
import { CellComponent } from './CellComponent';
import { useTableContext } from "../../hooks/useTableContext";
import { useTableContext } from '../../hooks/useTableContext';
import clickOutside from "@/directives/clickOutside";
import clickOutside from '@/directives/clickOutside';
import { propTypes } from "@/utils/propTypes";
import { isString, isBoolean, isFunction, isNumber, isArray } from "@/utils";
import { createPlaceholderMessage } from "./helper";
import { set, omit } from "lodash-es";
import { EventEnum } from "@/components/Table/src/componentMap";
import { propTypes } from '@/utils/propTypes';
import { isString, isBoolean, isFunction, isNumber, isArray } from '@/utils';
import { createPlaceholderMessage } from './helper';
import { set, omit } from 'lodash-es';
import { EventEnum } from '@/components/Table/src/componentMap';
import { parseISO, format } from "date-fns";
import { parseISO, format } from 'date-fns';
export default defineComponent({
name: "EditableCell",
name: 'EditableCell',
components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent },
directives: {
clickOutside,
@ -75,7 +75,7 @@ export default defineComponent({
type: [String, Number, Boolean, Object] as PropType<
string | number | boolean
>,
default: "",
default: '',
},
record: {
type: Object as PropType<EditRecordRow>,
@ -91,7 +91,7 @@ export default defineComponent({
const isEdit = ref(false);
const elRef = ref();
const ruleVisible = ref(false);
const ruleMessage = ref("");
const ruleMessage = ref('');
const optionsRef = ref<LabelValueOptions>([]);
const currentValueRef = ref<any>(props.value);
const defaultValueRef = ref<any>(props.value);
@ -99,7 +99,7 @@ export default defineComponent({
// const { prefixCls } = useDesign('editable-cell');
const getComponent = computed(
() => props.column?.editComponent || "NInput"
() => props.column?.editComponent || 'NInput'
);
const getRule = computed(() => props.column?.editRule);
@ -109,7 +109,7 @@ export default defineComponent({
const getIsCheckComp = computed(() => {
const component = unref(getComponent);
return ["NCheckbox", "NRadio"].includes(component);
return ['NCheckbox', 'NRadio'].includes(component);
});
const getComponentProps = computed(() => {
@ -120,7 +120,7 @@ export default defineComponent({
const isCheckValue = unref(getIsCheckComp);
let valueField = isCheckValue ? "checked" : "value";
let valueField = isCheckValue ? 'checked' : 'value';
const val = unref(currentValueRef);
let value = isCheckValue
@ -130,16 +130,16 @@ export default defineComponent({
: val;
//TODO NDatePicker
if (component === "NDatePicker") {
if (component === 'NDatePicker') {
if (isString(value)) {
if (compProps.valueFormat) {
valueField = "formatted-value";
valueField = 'formatted-value';
} else {
value = parseISO(value as any).getTime();
}
} else if (isArray(value)) {
if (compProps.valueFormat) {
valueField = "formatted-value";
valueField = 'formatted-value';
} else {
value = value.map((item) => parseISO(item).getTime());
}
@ -151,7 +151,7 @@ export default defineComponent({
return {
placeholder: createPlaceholderMessage(unref(getComponent)),
...apiSelectProps,
...omit(compProps, "onChange"),
...omit(compProps, 'onChange'),
[onEvent]: handleChange,
[valueField]: value,
};
@ -167,7 +167,7 @@ export default defineComponent({
}
const component = unref(getComponent);
if (!component.includes("NSelect")) {
if (!component.includes('NSelect')) {
return value;
}
@ -179,7 +179,7 @@ export default defineComponent({
});
const getWrapperClass = computed(() => {
const { align = "center" } = props.column;
const { align = 'center' } = props.column;
return `edit-cell-align-${align}`;
});
@ -201,7 +201,7 @@ export default defineComponent({
function handleEdit() {
if (unref(getRowEditable) || unref(props.column?.editRow)) return;
ruleMessage.value = "";
ruleMessage.value = '';
isEdit.value = true;
nextTick(() => {
const el = unref(elRef);
@ -214,16 +214,16 @@ export default defineComponent({
const compProps = props.column?.editComponentProps ?? {};
if (!e) {
currentValueRef.value = e;
} else if (e?.target && Reflect.has(e.target, "value")) {
} else if (e?.target && Reflect.has(e.target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value;
} else if (component === "NCheckbox") {
} else if (component === 'NCheckbox') {
currentValueRef.value = (e as ChangeEvent).target.checked;
} else if (isString(e) || isBoolean(e) || isNumber(e)) {
currentValueRef.value = e;
}
//TODO NDatePicker
if (component === "NDatePicker") {
if (component === 'NDatePicker') {
if (isNumber(currentValueRef.value)) {
if (compProps.valueFormat) {
currentValueRef.value = format(
@ -243,7 +243,7 @@ export default defineComponent({
const onChange = props.column?.editComponentProps?.onChange;
if (onChange && isFunction(onChange)) onChange(...arguments);
table.emit?.("edit-change", {
table.emit?.('edit-change', {
column: props.column,
value: unref(currentValueRef),
record: toRaw(props.record),
@ -270,12 +270,12 @@ export default defineComponent({
ruleVisible.value = true;
return false;
} else {
ruleMessage.value = "";
ruleMessage.value = '';
return true;
}
}
}
ruleMessage.value = "";
ruleMessage.value = '';
return true;
}
@ -295,7 +295,7 @@ export default defineComponent({
set(record, dataKey, value);
//const record = await table.updateTableData(index, dataKey, value);
needEmit && table.emit?.("edit-end", { record, index, key, value });
needEmit && table.emit?.('edit-end', { record, index, key, value });
isEdit.value = false;
}
@ -312,8 +312,8 @@ export default defineComponent({
const { column, index, record } = props;
const { key } = column;
ruleVisible.value = true;
ruleMessage.value = "";
table.emit?.("edit-cancel", {
ruleMessage.value = '';
table.emit?.('edit-cancel', {
record,
index,
key: key,
@ -327,7 +327,7 @@ export default defineComponent({
}
const component = unref(getComponent);
if (component.includes("NInput")) {
if (component.includes('NInput')) {
handleCancel();
}
}
@ -337,7 +337,7 @@ export default defineComponent({
optionsRef.value = options;
}
function initCbs(cbs: "submitCbs" | "validCbs" | "cancelCbs", handle: Fn) {
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
if (props.record) {
/* eslint-disable */
isArray(props.record[cbs])

View File

@ -1,21 +1,21 @@
import { ComponentType } from "../../types/componentType";
import { ComponentType } from '../../types/componentType';
/**
* @description: placeholder
*/
export function createPlaceholderMessage(component: ComponentType) {
if (component === "NInput") return "请输入";
if (component === 'NInput') return '请输入';
if (
[
"NPicker",
"NSelect",
"NCheckbox",
"NRadio",
"NSwitch",
"NDatePicker",
"NTimePicker",
'NPicker',
'NSelect',
'NCheckbox',
'NRadio',
'NSwitch',
'NDatePicker',
'NTimePicker',
].includes(component)
)
return "请选择";
return "";
return '请选择';
return '';
}

View File

@ -1,7 +1,7 @@
import type { BasicColumn } from "@/components/Table/src/types/table";
import { h, Ref } from "vue";
import type { BasicColumn } from '@/components/Table/src/types/table';
import { h, Ref } from 'vue';
import EditableCell from "./EditableCell.vue";
import EditableCell from './EditableCell.vue';
export function renderEditCell(column: BasicColumn) {
return (record, index) => {

View File

@ -118,25 +118,25 @@ import {
computed,
toRefs,
watchEffect,
} from "vue";
import { useTableContext } from "../../hooks/useTableContext";
import { cloneDeep } from "lodash-es";
} from 'vue';
import { useTableContext } from '../../hooks/useTableContext';
import { cloneDeep } from 'lodash-es';
import {
SettingOutlined,
DragOutlined,
VerticalRightOutlined,
VerticalLeftOutlined,
} from "@vicons/antd";
import Draggable from "vuedraggable";
} from '@vicons/antd';
import Draggable from 'vuedraggable';
interface Options {
title: string;
key: string;
fixed?: boolean | "left" | "right";
fixed?: boolean | 'left' | 'right';
}
export default defineComponent({
name: "ColumnSetting",
name: 'ColumnSetting',
components: {
SettingOutlined,
DragOutlined,
@ -174,7 +174,7 @@ export default defineComponent({
state.checkList = checkList;
state.defaultCheckList = checkList;
const newColumns = columns.filter(
(item) => item.key != "action" && item.title != "操作"
(item) => item.key != 'action' && item.title != '操作'
);
if (!columnsList.value.length) {
columnsList.value = cloneDeep(newColumns);
@ -185,7 +185,7 @@ export default defineComponent({
//
function onChange(checkList) {
if (state.selection) {
checkList.unshift("selection");
checkList.unshift('selection');
}
setColumns(checkList);
}
@ -242,7 +242,7 @@ export default defineComponent({
function onSelection(e) {
let checkList = table.getCacheColumns();
if (e) {
checkList.unshift({ type: "selection", key: "selection" });
checkList.unshift({ type: 'selection', key: 'selection' });
setColumns(checkList);
} else {
checkList.splice(0, 1);
@ -257,7 +257,7 @@ export default defineComponent({
//
function fixedColumn(item: any, fixed: any) {
if (!state.checkList.includes(item.key)) return;
if (!state.checkList.includes(item.key as never)) return;
let columns = getColumns();
const isFixed = item.fixed === fixed ? undefined : fixed;
let index = columns.findIndex((res) => res.key === item.key);

View File

@ -3,15 +3,15 @@
const table = {
apiSetting: {
// 当前页的字段名
pageField: "current",
pageField: 'current',
// 每页数量字段名
sizeField: "size",
sizeField: 'size',
// 接口返回的数据字段名
listField: "records",
listField: 'records',
// 接口返回总页数字段名
totalField: "pages",
totalField: 'pages',
//总数字段名
countField: "total",
countField: 'total',
},
//默认分页数量
defaultPageSize: 10,

View File

@ -1,7 +1,7 @@
import { ref, Ref, ComputedRef, unref, computed, watch, toRaw } from "vue";
import type { BasicColumn, BasicTableProps } from "../types/table";
import { isEqual, cloneDeep } from "lodash-es";
import { isArray, isString } from "@/utils";
import { ref, Ref, ComputedRef, unref, computed, watch, toRaw } from 'vue';
import type { BasicColumn, BasicTableProps } from '../types/table';
import { isEqual, cloneDeep } from 'lodash-es';
import { isArray, isString } from '@/utils';
// import { usePermission } from "@/hooks/web/usePermission";
// import { ActionItem } from "@/components/Table";
// import { renderEditCell } from "../components/editable";
@ -65,7 +65,7 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
) {
const { actionColumn } = unref(propsRef);
if (!actionColumn) return;
!columns.find((col) => col.key === "action") &&
!columns.find((col) => col.key === 'action') &&
columns.push({
...(actionColumn as any),
});

View File

@ -1,7 +1,7 @@
import type { BasicTableProps } from "../types/table";
import type { PaginationProps } from "../types/pagination";
import { isBoolean, isFunction } from "@/utils/is";
import { APISETTING } from "../const";
import type { BasicTableProps } from '../types/table';
import type { PaginationProps } from '../types/pagination';
import { isBoolean, isFunction } from '@/utils/is';
import { APISETTING } from '../const';
export function useDataSource(
propsRef: ComputedRef<BasicTableProps>,
@ -30,7 +30,7 @@ export function useDataSource(
return rowKey
? rowKey
: () => {
return "key";
return 'key';
};
});
@ -115,13 +115,13 @@ export function useDataSource(
page: opt[pageField] || 1,
});
}
emit("fetch-success", {
emit('fetch-success', {
items: unref(resultInfo),
resultTotal,
});
} catch (error) {
console.error(error);
emit("fetch-error", error);
emit('fetch-error', error);
dataSourceRef.value = [];
setPagination({
pageCount: 0,

View File

@ -1,5 +1,5 @@
import { ref, ComputedRef, unref, computed, watch } from "vue";
import type { BasicTableProps } from "../types/table";
import { ref, ComputedRef, unref, computed, watch } from 'vue';
import type { BasicTableProps } from '../types/table';
export function useLoading(props: ComputedRef<BasicTableProps>) {
const loadingRef = ref(unref(props).loading);

View File

@ -1,9 +1,9 @@
import type { PaginationProps } from "../types/pagination";
import type { BasicTableProps } from "../types/table";
import { computed, unref, ref, ComputedRef, watch } from "vue";
import type { PaginationProps } from '../types/pagination';
import type { BasicTableProps } from '../types/table';
import { computed, unref, ref, ComputedRef, watch } from 'vue';
import { isBoolean } from "@/utils/is";
import { DEFAULTPAGESIZE, PAGESIZES } from "../const";
import { isBoolean } from '@/utils/is';
import { DEFAULTPAGESIZE, PAGESIZES } from '../const';
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
const configRef = ref<PaginationProps>({});

View File

@ -1,15 +1,15 @@
import type { Ref } from "vue";
import type { BasicTableProps, TableActionType } from "../types/table";
import { provide, inject, ComputedRef } from "vue";
import type { Ref } from 'vue';
import type { BasicTableProps, TableActionType } from '../types/table';
import { provide, inject, ComputedRef } from 'vue';
const key = Symbol("s-table");
const key = Symbol('s-table');
type Instance = TableActionType & {
wrapRef: Ref<Nullable<HTMLElement>>;
getBindValues: ComputedRef<any>;
};
type RetInstance = Omit<Instance, "getBindValues"> & {
type RetInstance = Omit<Instance, 'getBindValues'> & {
getBindValues: ComputedRef<BasicTableProps>;
};

View File

@ -1,7 +1,7 @@
import type { PropType } from "vue";
import { propTypes } from "@/utils/propTypes";
import { BasicColumn } from "./types/table";
import { NDataTable } from "naive-ui";
import type { PropType } from 'vue';
import { propTypes } from '@/utils/propTypes';
import { BasicColumn } from './types/table';
import { NDataTable } from 'naive-ui';
export const basicProps = {
...NDataTable.props, // 这里继承原 UI 组件的 props
title: {
@ -14,7 +14,7 @@ export const basicProps = {
},
size: {
type: String,
default: "medium",
default: 'medium',
},
dataSource: {
type: [Object],
@ -48,7 +48,7 @@ export const basicProps = {
//废弃
showPagination: {
type: [String, Boolean],
default: "auto",
default: 'auto',
},
actionColumn: {
type: Object as PropType<BasicColumn>,

View File

@ -1,9 +1,9 @@
export type ComponentType =
| "NInput"
| "NInputNumber"
| "NSelect"
| "NCheckbox"
| "NSwitch"
| "NDatePicker"
| "NTimePicker"
| "NCascader";
| 'NInput'
| 'NInputNumber'
| 'NSelect'
| 'NCheckbox'
| 'NSwitch'
| 'NDatePicker'
| 'NTimePicker'
| 'NCascader';

View File

@ -1,8 +1,8 @@
import type {
InternalRowData,
TableBaseColumn,
} from "naive-ui/lib/data-table/src/interface";
import { ComponentType } from "./componentType";
} from 'naive-ui/lib/data-table/src/interface';
import { ComponentType } from './componentType';
export interface BasicColumn<T = InternalRowData> extends TableBaseColumn<T> {
//编辑表格
edit?: boolean;

View File

@ -1,11 +1,11 @@
import { NButton } from "naive-ui";
import type { Component } from "vue";
import { NButton } from 'naive-ui';
import type { Component } from 'vue';
// import { PermissionsEnum } from "@/enums/permissionsEnum";
import { Fn } from "@vueuse/core";
import { Fn } from '@vueuse/core';
export interface ActionItem extends Partial<InstanceType<typeof NButton>> {
onClick?: Fn;
label?: string;
type?: "success" | "error" | "warning" | "info" | "primary" | "default";
type?: 'success' | 'error' | 'warning' | 'info' | 'primary' | 'default';
// 设定 color 后会覆盖 type 的样式
color?: string;
icon?: Component;

View File

@ -1,4 +1,4 @@
import logoImage from "@/assets/images/logo.png";
import logoImage from '@/assets/svgs/logo.svg';
interface WebsiteConfig {
title: string;
logo: string;
@ -6,8 +6,8 @@ interface WebsiteConfig {
loginDesc: string;
}
export const websiteConfig: WebsiteConfig = Object.freeze({
title: "中盛起元基础框架",
title: 'ZS-Naiveui-Admin',
logo: logoImage,
loginImage: logoImage,
loginDesc: "中盛起元基础框架",
loginDesc: 'ZS-Naiveui-Admin',
});

View File

@ -1,10 +1,10 @@
import { on, isServer } from "@/utils";
import { on, isServer } from '@/utils';
// import { isServer } from "@/utils";
import type {
ComponentPublicInstance,
DirectiveBinding,
ObjectDirective,
} from "vue";
} from 'vue';
type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void;
@ -21,8 +21,8 @@ const nodeList: FlushList = new Map();
let startClick: MouseEvent;
if (!isServer) {
on(document, "mousedown", (e: MouseEvent) => (startClick = e));
on(document, "mouseup", (e: MouseEvent) => {
on(document, 'mousedown', (e: MouseEvent) => (startClick = e));
on(document, 'mouseup', (e: MouseEvent) => {
for (const { documentHandler } of nodeList.values()) {
documentHandler(e, startClick);
}

View File

@ -3,7 +3,7 @@
*
* string类型/Ref<string>/Reactive<string>
*/
import type { Directive, DirectiveBinding } from "vue";
import type { Directive, DirectiveBinding } from 'vue';
interface ElType extends HTMLElement {
copyData: string | number;
@ -12,23 +12,23 @@ interface ElType extends HTMLElement {
const copy: Directive = {
mounted(el: ElType, binding: DirectiveBinding) {
el.copyData = binding.value;
el.addEventListener("click", handleClick);
el.addEventListener('click', handleClick);
},
updated(el: ElType, binding: DirectiveBinding) {
el.copyData = binding.value;
},
beforeUnmount(el: ElType) {
el.removeEventListener("click", el.__handleClick__);
el.removeEventListener('click', el.__handleClick__);
},
};
function handleClick(this: any) {
const input = document.createElement("input");
const input = document.createElement('input');
input.value = this.copyData.toLocaleString();
document.body.appendChild(input);
input.select();
document.execCommand("Copy");
document.execCommand('Copy');
document.body.removeChild(input);
console.log("复制成功", this.copyData);
console.log('复制成功', this.copyData);
}
export default copy;

View File

@ -3,14 +3,14 @@
* input
* function类型
*/
import type { Directive, DirectiveBinding } from "vue";
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";
if (typeof binding.value !== 'function') {
throw 'callback must be a function';
}
let timer: NodeJS.Timeout | null = null;
el.__handleClick__ = function () {
@ -21,10 +21,10 @@ const debounce: Directive = {
binding.value();
}, 500);
};
el.addEventListener("click", el.__handleClick__);
el.addEventListener('click', el.__handleClick__);
},
beforeUnmount(el: ElType) {
el.removeEventListener("click", el.__handleClick__);
el.removeEventListener('click', el.__handleClick__);
},
};

View File

@ -10,14 +10,14 @@
使 Dom v-draggable
<div class="dialog-model" v-draggable></div>
*/
import type { Directive } from "vue";
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.style.cursor = 'move';
el.style.position = 'absolute';
el.onmousedown = function (e) {
const disX = e.pageX - el.offsetLeft;
const disY = e.pageY - el.offsetTop;
@ -37,8 +37,8 @@ const draggable: Directive = {
} else if (y > maxY) {
y = maxY;
}
el.style.left = x + "px";
el.style.top = y + "px";
el.style.left = x + 'px';
el.style.top = y + 'px';
};
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null;

View File

@ -2,19 +2,19 @@
* v-longpress
*
*/
import type { Directive, DirectiveBinding } from "vue";
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";
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) {
if (e.type === 'click' && e.button !== 0) {
return;
}
}
@ -36,13 +36,13 @@ const directive: Directive = {
binding.value(e);
};
// 添加事件监听器
el.addEventListener("mousedown", start);
el.addEventListener("touchstart", start);
el.addEventListener('mousedown', start);
el.addEventListener('touchstart', start);
// 取消计时器
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
el.addEventListener("touchend", cancel);
el.addEventListener("touchcancel", cancel);
el.addEventListener('click', cancel);
el.addEventListener('mouseout', cancel);
el.addEventListener('touchend', cancel);
el.addEventListener('touchcancel', cancel);
},
};

View File

@ -8,15 +8,15 @@
使 Dom v-throttle
<button v-throttle="debounceClick"></button>
*/
import type { Directive, DirectiveBinding } from "vue";
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";
if (typeof binding.value !== 'function') {
throw 'callback must be a function';
}
let timer: NodeJS.Timeout | null = null;
el.__handleClick__ = function () {
@ -31,10 +31,10 @@ const throttle: Directive = {
}, 1000);
}
};
el.addEventListener("click", el.__handleClick__);
el.addEventListener('click', el.__handleClick__);
},
beforeUnmount(el: ElType) {
el.removeEventListener("click", el.__handleClick__);
el.removeEventListener('click', el.__handleClick__);
},
};

View File

@ -31,11 +31,11 @@ export enum RequestContentTypeEnum {
* @description:
*/
export enum RequestHttpEnum {
GET = "get",
POST = "post",
PATCH = "patch",
PUT = "put",
DELETE = "delete",
GET = 'get',
POST = 'post',
PATCH = 'patch',
PUT = 'put',
DELETE = 'delete',
}
/**
@ -43,34 +43,34 @@ export enum RequestHttpEnum {
*/
export enum RequestHttpIntervalEnum {
// 秒
SECOND = "second",
SECOND = 'second',
// 分
MINUTE = "minute",
MINUTE = 'minute',
// 时
HOUR = "hour",
HOUR = 'hour',
// 天
DAY = "day",
DAY = 'day',
}
/**
* @description:
*/
export const SelectHttpTimeNameObj = {
[RequestHttpIntervalEnum.SECOND]: "秒",
[RequestHttpIntervalEnum.MINUTE]: "分",
[RequestHttpIntervalEnum.HOUR]: "时",
[RequestHttpIntervalEnum.DAY]: "天",
[RequestHttpIntervalEnum.SECOND]: '秒',
[RequestHttpIntervalEnum.MINUTE]: '分',
[RequestHttpIntervalEnum.HOUR]: '时',
[RequestHttpIntervalEnum.DAY]: '天',
};
/**
* @description:
*/
export enum RequestBodyEnum {
NONE = "none",
FORM_DATA = "form-data",
X_WWW_FORM_URLENCODED = "x-www-form-urlencoded",
JSON = "json",
XML = "xml",
NONE = 'none',
FORM_DATA = 'form-data',
X_WWW_FORM_URLENCODED = 'x-www-form-urlencoded',
JSON = 'json',
XML = 'xml',
}
/**
@ -88,9 +88,9 @@ export const RequestBodyEnumList = [
* @description:
*/
export enum RequestParamsTypeEnum {
PARAMS = "Params",
BODY = "Body",
HEADER = "Header",
PARAMS = 'Params',
BODY = 'Body',
HEADER = 'Header',
}
/**
@ -115,15 +115,15 @@ export type RequestParams = {
*/
export enum ContentTypeEnum {
// json
JSON = "application/json;charset=UTF-8",
JSON = 'application/json;charset=UTF-8',
// text
TEXT = "text/plain;charset=UTF-8",
TEXT = 'text/plain;charset=UTF-8',
// xml
XML = "application/xml;charset=UTF-8",
XML = 'application/xml;charset=UTF-8',
// application/x-www-form-urlencoded 一般配合qs
FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8",
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
// form-data 上传
FORM_DATA = "multipart/form-data;charset=UTF-8",
FORM_DATA = 'multipart/form-data;charset=UTF-8',
}
//

View File

@ -1,18 +1,20 @@
import { ResultEnum } from "@/enums/httpEnum";
import { ResultEnum } from '@/enums/httpEnum';
export enum PageEnum {
// 登录
BASE_LOGIN = "/login",
BASE_LOGIN_NAME = "lOGIN",
BASE_LOGIN = '/login',
BASE_LOGIN_NAME = 'lOGIN',
//重定向
REDIRECT = "/redirect",
REDIRECT_NAME = "Redirect",
REDIRECT = '/redirect',
REDIRECT_NAME = 'Redirect',
// 首页
BASE_HOME = "/system/role",
BASE_HOME = '/system/role',
// 错误
ERROR_PAGE_NAME_403 = "ErrorPage403",
ERROR_PAGE_NAME_404 = "ErrorPage404",
ERROR_PAGE_NAME_403 = 'ErrorPage403',
ERROR_PAGE_NAME_404 = 'ErrorPage404',
// ERROR_PAGE_NAME_500 = "ErrorPage500",
// 错误
ERROR_PAGE_NAME = 'ErrorPage',
}
export const ErrorPageNameMap = new Map([

View File

@ -1,6 +1,6 @@
export enum DialogEnum {
DELETE = "delete",
WARNING = "warning",
ERROR = "error",
SUCCESS = "success",
DELETE = 'delete',
WARNING = 'warning',
ERROR = 'error',
SUCCESS = 'success',
}

View File

@ -1,10 +1,12 @@
export enum StorageEnum {
// 全局设置
ZS_SYSTEM_SETTING_STORE = "YSTEM_SETTING",
// token 等信息
ZS_ACCESS_TOKEN = "ACCESS_TOKEN",
ZS_ACCESS_TOKEN = 'ACCESS_TOKEN',
// 登录信息
ZS_LOGIN_INFO_STORE = "LOGIN_INFO",
ZS_LOGIN_INFO_STORE = 'LOGIN_INFO',
// 当前用户信息
ZS_CURRENT_USER = "CURRENT_USER",
ZS_CURRENT_USER = 'CURRENT_USER',
// 标签页
ZS_TABS_ROUTES = 'TABS_ROUTES',
// 是否锁屏
IS_SCREENLOCKED = 'IS_SCREENLOCKED',
}

View File

@ -1,5 +1,5 @@
import { tryOnMounted, tryOnUnmounted } from "@vueuse/core";
import { useDebounceFn } from "@vueuse/core";
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core';
import { useDebounceFn } from '@vueuse/core';
interface WindowSizeOptions {
once?: boolean;
@ -22,11 +22,11 @@ export function useWindowSizeFn(
if (options && options.immediate) {
handler();
}
window.addEventListener("resize", handler);
window.addEventListener('resize', handler);
};
const stop = () => {
window.removeEventListener("resize", handler);
window.removeEventListener('resize', handler);
};
tryOnMounted(() => {

View File

@ -0,0 +1,18 @@
import { computed } from 'vue';
import { useDesignSettingStore } from '@/store/modules/designSetting';
export function useDesignSetting() {
const designStore = useDesignSettingStore();
const getDarkTheme = computed(() => designStore.darkTheme);
const getAppTheme = computed(() => designStore.appTheme);
const getAppThemeList = computed(() => designStore.appThemeList);
return {
getDarkTheme,
getAppTheme,
getAppThemeList,
};
}

View File

@ -0,0 +1,42 @@
import { computed } from 'vue';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
export function useProjectSetting() {
const projectStore = useProjectSettingStore();
const navMode = computed(() => projectStore.navMode);
const navTheme = computed(() => projectStore.navTheme);
const isMobile = computed(() => projectStore.isMobile);
const headerSetting = computed(() => projectStore.headerSetting);
const multiTabsSetting = computed(() => projectStore.multiTabsSetting);
const menuSetting = computed(() => projectStore.menuSetting);
const crumbsSetting = computed(() => projectStore.crumbsSetting);
// const permissionMode = computed(() => projectStore.permissionMode);
const showFooter = computed(() => projectStore.showFooter);
const isPageAnimate = computed(() => projectStore.isPageAnimate);
const pageAnimateType = computed(() => projectStore.pageAnimateType);
return {
navMode,
navTheme,
isMobile,
headerSetting,
multiTabsSetting,
menuSetting,
crumbsSetting,
// permissionMode,
showFooter,
isPageAnimate,
pageAnimateType,
};
}

95
src/hooks/useBattery.ts Normal file
View File

@ -0,0 +1,95 @@
import { computed, onMounted, reactive, toRefs } from 'vue';
interface Battery {
charging: boolean; // 当前电池是否正在充电
chargingTime: number; // 距离充电完毕还需多少秒如果为0则充电完毕
dischargingTime: number; // 代表距离电池耗电至空且挂起需要多少秒
level: number; // 代表电量的放大等级,这个值在 0.0 至 1.0 之间
[key: string]: any;
}
export const useBattery = () => {
const state = reactive({
battery: {
charging: false,
chargingTime: 0,
dischargingTime: 0,
level: 100,
},
});
// 更新电池使用状态
const updateBattery = (target) => {
for (const key in state.battery) {
state.battery[key] = target[key];
}
state.battery.level = state.battery.level * 100;
};
// 计算电池剩余可用时间
const calcDischargingTime = computed(() => {
const hour = state.battery.dischargingTime / 3600;
const minute = (state.battery.dischargingTime / 60) % 60;
return `${~~hour}小时${~~minute}分钟`;
});
// 计算电池充满剩余时间
const calcChargingTime = computed(() => {
console.log(state.battery);
const hour = state.battery.chargingTime / 3600;
const minute = (state.battery.chargingTime / 60) % 60;
return `${~~hour}小时${~~minute}分钟`;
});
// 电池状态
const batteryStatus = computed(() => {
if (state.battery.charging && state.battery.level >= 100) {
return '已充满';
} else if (state.battery.charging) {
return '充电中';
} else {
return '已断开电源';
}
});
onMounted(async () => {
const BatteryManager: Battery = await (
window.navigator as any
).getBattery();
updateBattery(BatteryManager);
// 电池充电状态更新时被调用
BatteryManager.onchargingchange = ({ target }) => {
updateBattery(target);
};
// 电池充电时间更新时被调用
BatteryManager.onchargingtimechange = ({ target }) => {
updateBattery(target);
};
// 电池断开充电时间更新时被调用
BatteryManager.ondischargingtimechange = ({ target }) => {
updateBattery(target);
};
// 电池电量更新时被调用
BatteryManager.onlevelchange = ({ target }) => {
updateBattery(target);
};
// new Intl.DateTimeFormat('zh', {
// year: 'numeric',
// month: '2-digit',
// day: '2-digit',
// hour: '2-digit',
// minute: '2-digit',
// second: '2-digit',
// hour12: false
// }).format(new Date())
});
return {
...toRefs(state),
batteryStatus,
calcDischargingTime,
calcChargingTime,
};
};

30
src/hooks/useOnline.ts Normal file
View File

@ -0,0 +1,30 @@
import { ref, onMounted, onUnmounted } from 'vue';
/**
* @description
* */
export function useOnline() {
const online = ref(true);
const showStatus = (val) => {
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 };
}

59
src/hooks/useTime.ts Normal file
View File

@ -0,0 +1,59 @@
import { ref, onMounted, onUnmounted } from 'vue';
/**
* @description
*/
export function useTime() {
let timer; // 定时器
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(0); // 秒
// 更新时间
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();
};
// 原生时间格式化
// new Intl.DateTimeFormat('zh', {
// year: 'numeric',
// month: '2-digit',
// day: '2-digit',
// hour: '2-digit',
// minute: '2-digit',
// second: '2-digit',
// hour12: false
// }).format(new Date())
updateTime();
onMounted(() => {
clearInterval(timer);
timer = setInterval(() => updateTime(), 1000);
});
onUnmounted(() => {
clearInterval(timer);
});
return { month, day, hour, minute, second, week };
}

70
src/hooks/web/usePage.ts Normal file
View File

@ -0,0 +1,70 @@
import type { RouteLocationRaw, Router } from 'vue-router';
import { PageEnum } from '@/enums/pageEnum';
import { RedirectName } from '@/router/constant';
import { useRouter } from 'vue-router';
import { isString } from '@/utils/is';
import { unref } from 'vue';
export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & {
path: PageEnum;
};
function handleError(e: Error) {
console.error(e);
}
/**
*
*/
export function useGo(_router?: Router) {
let router;
if (!_router) {
router = useRouter();
}
const { push, replace } = _router || router;
function go(
opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME,
isReplace = false
) {
if (!opt) {
return;
}
if (isString(opt)) {
isReplace
? replace(opt).catch(handleError)
: push(opt).catch(handleError);
} else {
const o = opt as RouteLocationRaw;
isReplace ? replace(o).catch(handleError) : push(o).catch(handleError);
}
}
return go;
}
/**
*
*/
export const useRedo = (_router?: Router) => {
const { push, currentRoute } = _router || useRouter();
const { query, params = {}, name, fullPath } = unref(currentRoute.value);
function redo(): Promise<boolean> {
return new Promise((resolve) => {
if (name === RedirectName) {
resolve(false);
return;
}
if (name && Object.keys(params).length > 0) {
params['_redirect_type'] = 'name';
params['path'] = String(name);
} else {
params['_redirect_type'] = 'path';
params['path'] = fullPath;
}
push({ name: RedirectName, params, query }).then(() => resolve(true));
});
}
return redo;
};

View File

@ -0,0 +1,52 @@
// import { useUserStore } from '@/store/modules/user';
// export function usePermission() {
// const userStore = useUserStore();
// /**
// * 检查权限
// * @param accesses
// */
// function _somePermissions(accesses: string[]) {
// return userStore.getPermissions.some((item) => {
// const { value }: any = item;
// return accesses.includes(value);
// });
// }
// /**
// * 判断是否存在权限
// * 可用于 v-if 显示逻辑
// * */
// function hasPermission(accesses: string[]): boolean {
// if (!accesses || !accesses.length) return true;
// return _somePermissions(accesses);
// }
// /**
// * 是否包含指定的所有权限
// * @param accesses
// */
// function hasEveryPermission(accesses: string[]): boolean {
// const permissionsList = userStore.getPermissions;
// if (Array.isArray(accesses)) {
// return permissionsList.every((access: any) => accesses.includes(access.value));
// }
// throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
// }
// /**
// * 是否包含其中某个权限
// * @param accesses
// * @param accessMap
// */
// function hasSomePermission(accesses: string[]): boolean {
// const permissionsList = userStore.getPermissions;
// if (Array.isArray(accesses)) {
// return permissionsList.some((access: any) => accesses.includes(access.value));
// }
// throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
// }
// return { hasPermission, hasEveryPermission, hasSomePermission };
// }

View File

@ -0,0 +1,452 @@
<template>
<n-drawer v-model:show="isDrawer" :width="width" :placement="placement">
<n-drawer-content :title="title" :native-scrollbar="false">
<div class="drawer">
<n-divider title-placement="center">主题</n-divider>
<div class="justify-center drawer-setting-item dark-switch">
<n-tooltip placement="bottom">
<template #trigger>
<n-switch
v-model:value="designStore.darkTheme"
class="dark-theme-switch"
>
<template #checked>
<n-icon size="14" color="#ffd93b">
<SunnySharp />
</n-icon>
</template>
<template #unchecked>
<n-icon size="14" color="#ffd93b">
<Moon />
</n-icon>
</template>
</n-switch>
</template>
<span>{{ designStore.darkTheme ? '深' : '浅' }}色主题</span>
</n-tooltip>
</div>
<n-divider title-placement="center">系统主题</n-divider>
<div class="drawer-setting-item align-items-top">
<span
class="theme-item"
v-for="(item, index) in appThemeList"
:key="index"
:style="{ 'background-color': item }"
@click="togTheme(item)"
>
<n-icon size="12" v-if="item === designStore.appTheme">
<CheckOutlined />
</n-icon>
</span>
</div>
<n-divider title-placement="center">导航栏模式</n-divider>
<div class="drawer-setting-item align-items-top">
<div class="drawer-setting-item-style align-items-top">
<n-tooltip placement="top">
<template #trigger>
<img
src="~@/assets/images/nav-theme-dark.svg"
@click="togNavMode('vertical')"
alt="左侧菜单模式"
/>
</template>
<span>左侧菜单模式</span>
</n-tooltip>
<n-badge
dot
color="#19be6b"
v-show="settingStore.navMode === 'vertical'"
/>
</div>
<div class="drawer-setting-item-style">
<n-tooltip placement="top">
<template #trigger>
<img
src="~@/assets/images/nav-horizontal.svg"
alt="顶部菜单模式"
@click="togNavMode('horizontal')"
/>
</template>
<span>顶部菜单模式</span>
</n-tooltip>
<n-badge
dot
color="#19be6b"
v-show="settingStore.navMode === 'horizontal'"
/>
</div>
<div class="drawer-setting-item-style">
<n-tooltip placement="top">
<template #trigger>
<img
src="~@/assets/images/nav-horizontal-mix.svg"
@click="togNavMode('horizontal-mix')"
alt="顶部菜单混合模式"
/>
</template>
<span>顶部菜单混合模式</span>
</n-tooltip>
<n-badge
dot
color="#19be6b"
v-show="settingStore.navMode === 'horizontal-mix'"
/>
</div>
</div>
<n-divider title-placement="center">导航栏风格</n-divider>
<div class="drawer-setting-item align-items-top">
<div class="drawer-setting-item-style align-items-top">
<n-tooltip placement="top">
<template #trigger>
<img
src="~@/assets/images/nav-theme-dark.svg"
alt="暗色侧边栏"
@click="togNavTheme('dark')"
/>
</template>
<span>暗色侧边栏</span>
</n-tooltip>
<n-badge
dot
color="#19be6b"
v-if="settingStore.navTheme === 'dark'"
/>
</div>
<div class="drawer-setting-item-style">
<n-tooltip placement="top">
<template #trigger>
<img
src="~@/assets/images/nav-theme-light.svg"
alt="白色侧边栏"
@click="togNavTheme('light')"
/>
</template>
<span>白色侧边栏</span>
</n-tooltip>
<n-badge
dot
color="#19be6b"
v-if="settingStore.navTheme === 'light'"
/>
</div>
<div class="drawer-setting-item-style">
<n-tooltip placement="top">
<template #trigger>
<img
src="~@/assets/images/header-theme-dark.svg"
@click="togNavTheme('header-dark')"
alt="暗色顶栏"
/>
</template>
<span>暗色顶栏</span>
</n-tooltip>
<n-badge
dot
color="#19be6b"
v-if="settingStore.navTheme === 'header-dark'"
/>
</div>
</div>
<n-divider title-placement="center">界面功能</n-divider>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">分割菜单</div>
<div class="drawer-setting-item-action">
<n-switch
:disabled="settingStore.navMode !== 'horizontal-mix'"
v-model:value="settingStore.menuSetting.mixMenu"
/>
</div>
</div>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">固定顶栏</div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.headerSetting.fixed" />
</div>
</div>
<!-- <div class="drawer-setting-item">-->
<!-- <div class="drawer-setting-item-title">-->
<!-- 固定侧边栏-->
<!-- </div>-->
<!-- <div class="drawer-setting-item-action">-->
<!-- <n-switch v-model:value="settingStore.menuSetting.fixed" />-->
<!-- </div>-->
<!-- </div>-->
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">固定多页签</div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.multiTabsSetting.fixed" />
</div>
</div>
<n-divider title-placement="center">界面显示</n-divider>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">显示重载页面按钮</div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.headerSetting.isReload" />
</div>
</div>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">显示面包屑导航</div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.crumbsSetting.show" />
</div>
</div>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">显示面包屑显示图标</div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.crumbsSetting.showIcon" />
</div>
</div>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">显示多页签</div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.multiTabsSetting.show" />
</div>
</div>
<!--1.15废弃没啥用占用操作空间-->
<!-- <div class="drawer-setting-item">-->
<!-- <div class="drawer-setting-item-title"> 显示页脚 </div>-->
<!-- <div class="drawer-setting-item-action">-->
<!-- <n-switch v-model:value="settingStore.showFooter" />-->
<!-- </div>-->
<!-- </div>-->
<n-divider title-placement="center">动画</n-divider>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">禁用动画</div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.isPageAnimate" />
</div>
</div>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">动画类型</div>
<div class="drawer-setting-item-select">
<n-select
v-model:value="settingStore.pageAnimateType"
:options="animateOptions"
/>
</div>
</div>
<!-- <div class="drawer-setting-item">
<n-alert type="warning" :showIcon="false">
<p>{{ alertText }}</p>
</n-alert>
</div> -->
</div>
<template #footer>
<n-space>
<n-button type="primary" @click="submitSetting">提交</n-button>
<n-button>重置</n-button>
</n-space>
</template>
</n-drawer-content>
</n-drawer>
</template>
<script lang="ts">
import { useProjectSettingStore } from '@/store/modules/projectSetting';
import { useDesignSettingStore } from '@/store/modules/designSetting';
import { CheckOutlined } from '@vicons/antd';
import { Moon, SunnySharp } from '@vicons/ionicons5';
import { darkTheme } from 'naive-ui';
import { animates as animateOptions } from '@/settings/animateSetting';
import { setSystemSetting } from '@/api/system/user';
export default defineComponent({
name: 'ProjectSetting',
components: { CheckOutlined, Moon, SunnySharp },
props: {
title: {
type: String,
default: '项目配置',
},
width: {
type: Number,
default: 280,
},
},
setup(props) {
const settingStore = useProjectSettingStore();
const designStore = useDesignSettingStore();
const state = reactive({
width: props.width,
title: props.title,
isDrawer: false,
placement: 'right',
alertText: '',
appThemeList: designStore.appThemeList,
});
watch(
() => designStore.darkTheme,
(to) => {
settingStore.navTheme = to ? 'header-dark' : 'dark';
}
);
const directionsOptions = computed(() => {
return animateOptions.find(
(item) => item.value == unref(settingStore.pageAnimateType)
);
});
function openDrawer() {
state.isDrawer = true;
}
function closeDrawer() {
state.isDrawer = false;
}
function togNavTheme(theme) {
settingStore.navTheme = theme;
if (settingStore.navMode === 'horizontal' && ['light'].includes(theme)) {
settingStore.navTheme = 'dark';
}
}
function togTheme(color) {
designStore.appTheme = color;
}
function togNavMode(mode) {
settingStore.navMode = mode;
settingStore.menuSetting.mixMenu = false;
}
async function submitSetting() {
const {
menuSetting,
navMode,
navTheme,
isMobile,
headerSetting,
showFooter,
multiTabsSetting,
crumbsSetting,
isPageAnimate,
pageAnimateType,
} = settingStore;
const newSettingObject = {
menuSetting,
navMode,
navTheme,
isMobile,
headerSetting,
showFooter,
multiTabsSetting,
crumbsSetting,
isPageAnimate,
pageAnimateType,
appTheme: designStore.appTheme,
};
const req = JSON.stringify(newSettingObject, null, 2);
const data = await setSystemSetting(req);
if (data.status === 200) window['$message'].success('设置成功!');
}
return {
...toRefs(state),
settingStore,
designStore,
togNavTheme,
togNavMode,
togTheme,
darkTheme,
openDrawer,
closeDrawer,
animateOptions,
directionsOptions,
submitSetting,
};
},
});
</script>
<style lang="scss" scoped>
.drawer {
.n-divider:not(.n-divider--vertical) {
margin: 10px 0;
}
&-setting-item {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 12px 0;
&-style {
position: relative;
display: inline-block;
margin-right: 16px;
text-align: center;
cursor: pointer;
}
&-title {
flex: 1 1;
font-size: 14px;
}
&-action {
flex: 0 0 auto;
}
&-select {
flex: 1;
}
.theme-item {
width: 20px;
min-width: 20px;
height: 20px;
margin: 0 5px 5px 0;
line-height: 14px;
text-align: center;
cursor: pointer;
border: 1px solid #eee;
border-radius: 2px;
.n-icon {
color: #fff;
}
}
}
.align-items-top {
align-items: flex-start;
padding: 2px 0;
}
.justify-center {
justify-content: center;
}
.dark-switch .n-switch {
::v-deep(.n-switch__rail) {
background-color: #000e1c;
}
}
}
</style>

View File

@ -0,0 +1,31 @@
import {
SettingOutlined,
SearchOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
PoweroffOutlined,
GithubOutlined,
LockOutlined,
ReloadOutlined,
LogoutOutlined,
UserOutlined,
CheckOutlined,
} from '@vicons/antd';
export default {
SettingOutlined,
LockOutlined,
GithubOutlined,
SearchOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
PoweroffOutlined,
ReloadOutlined,
LogoutOutlined,
UserOutlined,
CheckOutlined,
};

View File

@ -1,3 +1,3 @@
import Header from "./index.vue";
import PageHeader from './index.vue';
export { Header };
export { PageHeader };

View File

@ -1,32 +1,105 @@
<script setup lang="ts">
import { UserOutlined } from "@vicons/antd";
import { useUserStore } from "@/store/modules/user";
const userStore = useUserStore();
//
const avatarSelect = (key) => {
switch (key) {
case 1:
doLogout();
break;
}
};
const avatarOptions = [
{
label: "退出登录",
key: 1,
},
];
// 退
const doLogout = () => {
userStore.logout();
};
</script>
<template>
<div class="layout-header">
<!--顶部菜单-->
<div
class="layout-header-left"
v-if="
navMode === 'horizontal' || (navMode === 'horizontal-mix' && mixMenu)
"
>
<div class="logo" v-if="navMode === 'horizontal'">
<img :src="websiteConfig.logo" alt="" />
<h2 v-show="!collapsed" class="title">{{ websiteConfig.title }}</h2>
</div>
<AsideMenu
v-model:collapsed="collapse"
v-model:location="getMenuLocation"
:inverted="getInverted"
mode="horizontal"
/>
</div>
<!--左侧菜单-->
<div class="layout-header-left" v-else>
<!-- 菜单收起 -->
<div
class="ml-1 layout-header-trigger layout-header-trigger-min"
@click="() => $emit('update:collapsed', !collapsed)"
>
<n-icon size="18" v-if="collapsed">
<MenuUnfoldOutlined />
</n-icon>
<n-icon size="18" v-else>
<MenuFoldOutlined />
</n-icon>
</div>
<!-- 刷新 -->
<div
class="mr-1 layout-header-trigger layout-header-trigger-min"
v-if="headerSetting.isReload"
@click="reloadPage"
>
<n-icon size="18">
<ReloadOutlined />
</n-icon>
</div>
<!-- 面包屑 -->
<n-breadcrumb v-if="crumbsSetting.show">
<template
v-for="routeItem in breadcrumbList"
:key="routeItem.name === 'Redirect' ? void 0 : routeItem.name"
>
<n-breadcrumb-item v-if="routeItem.meta.title">
<n-dropdown
v-if="routeItem.children.length"
:options="routeItem.children"
@select="dropdownSelect"
>
<span class="link-text">
<component
v-if="crumbsSetting.showIcon && routeItem.meta.icon"
:is="routeItem.meta.icon"
/>
{{ routeItem.meta.title }}
</span>
</n-dropdown>
<span class="link-text" v-else>
<component
v-if="crumbsSetting.showIcon && routeItem.meta.icon"
:is="routeItem.meta.icon"
/>
{{ routeItem.meta.title }}
</span>
</n-breadcrumb-item>
</template>
</n-breadcrumb>
</div>
<div class="layout-header-right">
<div
class="layout-header-trigger layout-header-trigger-min"
v-for="item in iconList"
:key="item.icon"
>
<n-tooltip placement="bottom">
<template #trigger>
<n-icon size="18">
<component :is="item.icon" v-on="item.eventObject || {}" />
</n-icon>
</template>
<span>{{ item.tips }}</span>
</n-tooltip>
</div>
<!--切换全屏-->
<div class="layout-header-trigger layout-header-trigger-min">
<n-tooltip placement="bottom">
<template #trigger>
<n-icon size="18">
<component :is="fullscreenIcon" @click="toggleFullScreen" />
</n-icon>
</template>
<span>全屏</span>
</n-tooltip>
</div>
<!-- 个人中心 -->
<div class="layout-header-trigger layout-header-trigger-min">
<n-dropdown
trigger="hover"
@ -35,7 +108,7 @@ const doLogout = () => {
>
<div class="avatar">
<n-avatar round>
admin
{{ username }}
<template #icon>
<UserOutlined />
</template>
@ -43,22 +116,361 @@ const doLogout = () => {
</div>
</n-dropdown>
</div>
<!--设置-->
<div
class="layout-header-trigger layout-header-trigger-min"
@click="openSetting"
>
<n-tooltip placement="bottom-end">
<template #trigger>
<n-icon size="18" style="font-weight: bold">
<SettingOutlined />
</n-icon>
</template>
<span>项目配置</span>
</n-tooltip>
</div>
</div>
</div>
<!--项目配置-->
<ProjectSetting ref="drawerSetting" />
</template>
<style scoped lang="scss">
<script lang="ts">
import { defineComponent, reactive, toRefs, ref, computed, unref } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import components from './components';
import { StorageEnum } from '@/enums/storageEnum';
import { useUserStore } from '@/store/modules/user';
import { useScreenLockStore } from '@/store/modules/screenLock';
import ProjectSetting from './ProjectSetting.vue';
import { AsideMenu } from '@/layout/components/Sider';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
import { websiteConfig } from '@/config/website.config';
import { zsDialog } from '@/utils';
import { DialogEnum } from '@/enums/pluginEnum';
export default defineComponent({
name: 'PageHeader',
components: { ...components, ProjectSetting, AsideMenu },
props: {
collapsed: {
type: Boolean,
},
inverted: {
type: Boolean,
},
},
setup(props) {
const userStore = useUserStore();
const useLockscreen = useScreenLockStore();
const { navMode, navTheme, headerSetting, menuSetting, crumbsSetting } =
useProjectSetting();
const { name } = userStore?.info || {};
const drawerSetting = ref();
const state = reactive({
username: name ?? '',
fullscreenIcon: 'FullscreenOutlined',
navMode,
navTheme,
headerSetting,
crumbsSetting,
});
const getInverted = computed(() => {
return ['light', 'header-dark'].includes(unref(navTheme))
? props.inverted
: !props.inverted;
});
const mixMenu = computed(() => {
return unref(menuSetting).mixMenu;
});
const getChangeStyle = computed(() => {
const { collapsed } = props;
const { minMenuWidth, menuWidth } = unref(menuSetting);
return {
left: collapsed ? `${minMenuWidth}px` : `${menuWidth}px`,
width: `calc(100% - ${
collapsed ? `${minMenuWidth}px` : `${menuWidth}px`
})`,
};
});
const getMenuLocation = computed(() => {
return 'header';
});
const router = useRouter();
const route = useRoute();
const generator: any = (routerMap) => {
return routerMap.map((item) => {
const currentMenu = {
...item,
label: item.meta.title,
key: item.name,
disabled: item.path === '/',
};
//
if (item.children && item.children.length > 0) {
// Recursion
currentMenu.children = generator(item.children, currentMenu);
}
return currentMenu;
});
};
const breadcrumbList = computed(() => {
return generator(route.matched);
});
const dropdownSelect = (key) => {
router.push({ name: key });
};
//
const reloadPage = () => {
router.push({
path: '/redirect' + unref(route).fullPath,
});
};
// 退
const doLogout = () => {
zsDialog({
message: '您确定要退出登录吗',
type: DialogEnum.WARNING,
positiveButtonProps: { type: 'primary', ghost: false },
onPositiveCallback: () => {
userStore.logout().then(() => {
window['$message'].success('成功退出登录');
//
localStorage.removeItem(StorageEnum.ZS_TABS_ROUTES);
router
.replace({
name: 'Login',
query: {
redirect: route.fullPath,
},
})
.finally(() => location.reload());
});
},
onNegativeClick: () => {},
});
};
//
const toggleFullscreenIcon = () =>
(state.fullscreenIcon =
document.fullscreenElement !== null
? 'FullscreenExitOutlined'
: 'FullscreenOutlined');
//
document.addEventListener('fullscreenchange', toggleFullscreenIcon);
//
const toggleFullScreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
};
//
const iconList = [
{
icon: 'LockOutlined',
tips: '锁屏',
eventObject: {
click: () => useLockscreen.setLock(true),
},
},
];
const avatarOptions = [
{
label: '个人设置',
key: 1,
},
{
label: '退出登录',
key: 2,
},
];
//
const avatarSelect = (key) => {
switch (key) {
case 1:
router.push({ name: 'Setting' });
break;
case 2:
doLogout();
break;
}
};
function openSetting() {
const { openDrawer } = drawerSetting.value;
openDrawer();
}
return {
...toRefs(state),
iconList,
toggleFullScreen,
doLogout,
route,
dropdownSelect,
avatarOptions,
getChangeStyle,
avatarSelect,
breadcrumbList,
reloadPage,
drawerSetting,
openSetting,
getInverted,
getMenuLocation,
mixMenu,
websiteConfig,
};
},
});
</script>
<style lang="scss" scoped>
.layout-header {
z-index: 11;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 50px;
height: 64px;
padding: 0;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
transition: all 0.2s ease-in-out;
&-left {
display: flex;
align-items: center;
.logo {
display: flex;
align-items: center;
justify-content: center;
height: 64px;
padding-left: 10px;
overflow: hidden;
line-height: 64px;
white-space: nowrap;
img {
width: auto;
height: 32px;
margin-right: 10px;
}
.title {
margin-bottom: 0;
}
}
::v-deep(.ant-breadcrumb span:last-child .link-text) {
color: #515a6e;
}
.n-breadcrumb {
display: inline-block;
}
&-menu {
color: var(--text-color);
}
}
&-right {
position: absolute;
right: 20px;
display: flex;
align-items: center;
margin-right: 20px;
.avatar {
display: flex;
align-items: center;
height: 64px;
}
> * {
cursor: pointer;
}
}
&-trigger {
display: inline-block;
width: 64px;
height: 64px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease-in-out;
.n-icon {
display: flex;
align-items: center;
height: 64px;
line-height: 64px;
}
&:hover {
background: hsl(0deg 0% 100% / 8%);
}
.anticon {
font-size: 16px;
color: #515a6e;
}
}
&-trigger-min {
width: auto;
padding: 0 12px;
}
}
.layout-header-light {
color: #515a6e;
background: #fff;
.n-icon {
color: #515a6e;
}
.layout-header-left {
::v-deep(
.n-breadcrumb .n-breadcrumb-item:last-child .n-breadcrumb-item__link
) {
color: #515a6e;
}
}
.layout-header-trigger {
&:hover {
background: #f8f8f9;
}
}
}
.layout-header-fix {
position: fixed;
top: 0;
right: 0;
left: 200px;
z-index: 11;
}
</style>

View File

@ -0,0 +1,3 @@
import Logo from './index.vue';
export { Logo };

View File

@ -0,0 +1,59 @@
<template>
<div class="logo">
<!-- <img :src="websiteConfig.logo" alt="" :class="{ 'mr-2': !collapsed }" /> -->
<SvgIcon
name="logo"
:class="{ 'mr-2': !props.collapsed }"
class="logo-svgs"
:color="designStore.appTheme"
/>
<h2
v-show="!props.collapsed"
class="title"
:style="{ color: designStore.appTheme }"
>
{{ websiteConfig.title }}
</h2>
</div>
</template>
<script lang="ts" setup>
import { useDesignSettingStore } from '@/store/modules/designSetting';
import { websiteConfig } from '../../../config/website.config';
const designStore = useDesignSettingStore();
const props = defineProps({
collapsed: {
type: Boolean,
},
});
</script>
<style lang="scss" scoped>
.logo {
display: flex;
align-items: center;
justify-content: center;
height: 64px;
overflow: hidden;
line-height: 64px;
white-space: nowrap;
&-svgs {
width: 32px;
height: 32px;
// color: var(--n-item-text-color-child-active) !important;
}
img {
width: auto;
height: 32px;
}
.title {
margin: 0;
}
}
</style>

View File

@ -1,3 +1,3 @@
import Main from "./index.vue";
import MainView from './index.vue';
export { Main };
export { MainView };

View File

@ -1,7 +1,53 @@
<template>
<router-view>
<RouterView>
<template #default="{ Component, route }">
<component :is="Component" :key="route.fullPath" />
<transition :name="getTransitionName" mode="out-in" appear>
<keep-alive
v-if="keepAliveComponents.length"
:include="keepAliveComponents"
>
<component :is="Component" :key="route.fullPath" />
</keep-alive>
<component v-else :is="Component" :key="route.fullPath" />
</transition>
</template>
</router-view>
</RouterView>
</template>
<script>
import { defineComponent, computed, unref } from 'vue';
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
export default defineComponent({
name: 'MainView',
components: {},
props: {
notNeedKey: {
type: Boolean,
default: false,
},
animate: {
type: Boolean,
default: true,
},
},
setup() {
const { isPageAnimate, pageAnimateType } = useProjectSetting();
const asyncRouteStore = useAsyncRouteStore();
//
const keepAliveComponents = computed(
() => asyncRouteStore.keepAliveComponents
);
const getTransitionName = computed(() => {
return unref(isPageAnimate) ? unref(pageAnimateType) : '';
});
return {
keepAliveComponents,
getTransitionName,
};
},
});
</script>

View File

@ -0,0 +1,3 @@
import AsideMenu from './index.vue';
export { AsideMenu };

View File

@ -1,126 +1,186 @@
<template>
<n-layout-sider
bordered
collapse-mode="width"
show-trigger="bar"
:native-scrollbar="false"
:collapsed-width="60"
:width="200"
@collapse="true"
@expand="false"
>
<div class="sider-flex">
<aside>
<n-space vertical class="sider-top"> 中盛起元基础框架 </n-space>
<n-menu
:options="menus"
:collapsed-width="64"
:collapsed-icon-size="20"
:indent="24"
:expanded-keys="state.openKeys"
:value="getSelectedKeys"
@update:value="clickMenuItem"
@update:expanded-keys="menuExpanded"
/>
</aside>
</div>
</n-layout-sider>
<NMenu
:options="menus"
:inverted="inverted"
:mode="mode"
:collapsed="collapsed"
:collapsed-width="64"
:collapsed-icon-size="20"
:indent="24"
:expanded-keys="openKeys"
:value="getSelectedKeys"
@update:value="clickMenuItem"
@update:expanded-keys="menuExpanded"
/>
</template>
<script setup lang="ts">
import { useRoute } from "vue-router";
import { routerTurnByName, generatorMenu } from "@/utils";
import { useAsyncRouteStore } from "@/store/modules/asyncRoute";
<script lang="ts">
import { useRoute, useRouter } from 'vue-router';
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
import { generatorMenu, generatorMenuMix } from '@/utils';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
//
const currentRoute = useRoute();
//
const matched = currentRoute.matched;
const getOpenKeys =
matched && matched.length ? matched.map((item) => item.name) : [];
export default defineComponent({
name: 'AppMenu',
components: {},
props: {
mode: {
//
type: String,
default: 'vertical',
},
collapsed: {
//
type: Boolean,
},
//
location: {
type: String,
default: 'left',
},
},
emits: ['update:collapsed', 'clickMenuItem'],
setup(props, { emit }) {
//
const currentRoute = useRoute();
const router = useRouter();
const asyncRouteStore = useAsyncRouteStore();
const settingStore = useProjectSettingStore();
const menus = ref<any[]>([]);
const selectedKeys = ref<string>(currentRoute.name as string);
const headerMenuSelectKey = ref<string>('');
const asyncRouteStore = useAsyncRouteStore();
const menus = ref<any[]>([]);
const selectedKeys = ref<string>(currentRoute.name as string);
const { navMode } = useProjectSetting();
const state = reactive({
openKeys: getOpenKeys,
});
//
const matched = currentRoute.matched;
//
watch(
() => currentRoute.fullPath,
() => {
console.log(123);
updateMenu();
}
);
const getOpenKeys =
matched && matched.length ? matched.map((item) => item.name) : [];
function updateSelectedKeys() {
const matched = currentRoute.matched;
state.openKeys = matched.map((item) => item.name);
const activeMenu: string = (currentRoute.meta?.activeMenu as string) || "";
selectedKeys.value = activeMenu
? (activeMenu as string)
: (currentRoute.name as string);
const state = reactive({
openKeys: getOpenKeys,
});
console.log(selectedKeys.value);
}
const inverted = computed(() => {
return ['dark', 'header-dark'].includes(settingStore.navTheme);
});
function updateMenu() {
menus.value = generatorMenu(asyncRouteStore.getMenus);
updateSelectedKeys();
}
const getSelectedKeys = computed(() => {
let location = props.location;
return location === 'left' ||
(location === 'header' && unref(navMode) === 'horizontal')
? unref(selectedKeys)
: unref(headerMenuSelectKey);
});
const getSelectedKeys = computed(() => {
return unref(selectedKeys);
});
//
watch(
() => settingStore.menuSetting.mixMenu,
() => {
updateMenu();
if (props.collapsed) {
emit('update:collapsed', !props.collapsed);
}
}
);
const clickMenuItem = (key: string) => {
if (/http(s)?:/.test(key)) {
window.open(key);
} else {
routerTurnByName(key);
}
};
//
// watch(
// () => props.collapsed,
// (newVal) => {
// }
// );
//
function menuExpanded(openKeys: string[]) {
if (!openKeys) return;
const latestOpenKey = openKeys.find(
(key) => state.openKeys.indexOf(key) === -1
);
const isExistChildren = findChildrenLen(latestOpenKey as string);
state.openKeys = isExistChildren
? latestOpenKey
? [latestOpenKey]
: []
: openKeys;
}
//
watch(
() => currentRoute.fullPath,
() => {
updateMenu();
}
);
//
function findChildrenLen(key: string) {
if (!key) return false;
const subRouteChildren: string[] = [];
for (const { children, key } of unref(menus)) {
if (children && children.length) {
subRouteChildren.push(key as string);
function updateSelectedKeys() {
const matched = currentRoute.matched;
state.openKeys = matched.map((item) => item.name);
const activeMenu: string =
(currentRoute.meta?.activeMenu as string) || '';
selectedKeys.value = activeMenu
? (activeMenu as string)
: (currentRoute.name as string);
}
}
return subRouteChildren.includes(key);
}
onMounted(() => {
updateMenu();
function updateMenu() {
if (!settingStore.menuSetting.mixMenu) {
menus.value = generatorMenu(asyncRouteStore.getMenus);
} else {
//
const firstRouteName: string =
(currentRoute.matched[0].name as string) || '';
menus.value = generatorMenuMix(
asyncRouteStore.getMenus,
firstRouteName,
props.location
);
const activeMenu: string = currentRoute?.matched[0].meta
?.activeMenu as string;
headerMenuSelectKey.value =
(activeMenu ? activeMenu : firstRouteName) || '';
}
updateSelectedKeys();
}
//
function clickMenuItem(key: string) {
if (/http(s)?:/.test(key)) {
window.open(key);
} else {
router.push({ name: key });
}
emit('clickMenuItem' as any, key);
}
//
function menuExpanded(openKeys: string[]) {
if (!openKeys) return;
const latestOpenKey = openKeys.find(
(key) => state.openKeys.indexOf(key) === -1
);
const isExistChildren = findChildrenLen(latestOpenKey as string);
state.openKeys = isExistChildren
? latestOpenKey
? [latestOpenKey]
: []
: openKeys;
}
//
function findChildrenLen(key: string) {
if (!key) return false;
const subRouteChildren: string[] = [];
for (const { children, key } of unref(menus)) {
if (children && children.length) {
subRouteChildren.push(key as string);
}
}
return subRouteChildren.includes(key);
}
onMounted(() => {
updateMenu();
});
return {
...toRefs(state),
inverted,
menus,
selectedKeys,
headerMenuSelectKey,
getSelectedKeys,
clickMenuItem,
menuExpanded,
};
},
});
</script>
<style lang="scss" scoped>
.sider-top {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
margin-top: 10px;
}
</style>

View File

@ -0,0 +1,3 @@
import TabsView from './index.vue';
export { TabsView };

View File

@ -0,0 +1,641 @@
<template>
<div
class="box-border tabs-view"
:class="{
'tabs-view-fix': state.multiTabsSetting.fixed,
'tabs-view-fixed-header': state.isMultiHeaderFixed,
'tabs-view-default-background': getDarkTheme === false,
'tabs-view-dark-background': getDarkTheme === true,
}"
:style="getChangeStyle"
>
<div class="tabs-view-main">
<div
ref="navWrap"
class="tabs-card"
:class="{ 'tabs-card-scrollable': state.scrollable }"
>
<span
class="tabs-card-prev"
:class="{ 'tabs-card-prev-hide': !state.scrollable }"
@click="scrollPrev"
>
<n-icon size="16" color="#515a6e">
<LeftOutlined />
</n-icon>
</span>
<span
class="tabs-card-next"
:class="{ 'tabs-card-next-hide': !state.scrollable }"
@click="scrollNext"
>
<n-icon size="16" color="#515a6e">
<RightOutlined />
</n-icon>
</span>
<div ref="navScroll" class="tabs-card-scroll">
<Draggable
:list="tabsList"
animation="300"
item-key="fullPath"
class="flex"
>
<template #item="{ element }">
<div
:id="`tag${element.fullPath.split('/').join('\/')}`"
class="tabs-card-scroll-item"
:class="{ 'active-item': state.activeKey === element.fullPath }"
@click.stop="goPage(element)"
@contextmenu="handleContextMenu($event, element)"
>
<span>{{ element.meta.title }}</span>
<n-icon
size="14"
@click.stop="closeTabItem(element)"
v-if="!element.meta.affix"
>
<CloseOutlined />
</n-icon>
</div>
</template>
</Draggable>
</div>
</div>
<div class="tabs-close">
<n-dropdown
trigger="hover"
@select="closeHandleSelect"
placement="bottom-end"
:options="TabsMenuOptions"
>
<div class="tabs-close-btn">
<n-icon size="16" color="#515a6e">
<DownOutlined />
</n-icon>
</div>
</n-dropdown>
</div>
<n-dropdown
:show="state.showDropdown"
:x="state.dropdownX"
:y="state.dropdownY"
@clickoutside="onClickOutside"
placement="bottom-start"
@select="closeHandleSelect"
:options="TabsMenuOptions"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRoute, useRouter } from 'vue-router';
import { storage } from '@/utils';
import { useTabsViewStore } from '@/store/modules/tabsView';
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
import { RouteItem } from '@/store/modules/tabsView';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
import Draggable from 'vuedraggable';
import { PageEnum } from '@/enums/pageEnum';
import {
DownOutlined,
ReloadOutlined,
CloseOutlined,
ColumnWidthOutlined,
MinusOutlined,
LeftOutlined,
RightOutlined,
} from '@vicons/antd';
import { renderIcon } from '@/utils';
// import elementResizeDetectorMaker from "element-resize-detector";
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
import { useThemeVars } from 'naive-ui';
import { useGo } from '@/hooks/web/usePage';
import { StorageEnum } from '@/enums/storageEnum';
const props = defineProps({
collapsed: {
type: Boolean,
},
});
const { getDarkTheme, getAppTheme } = useDesignSetting();
const { navMode, headerSetting, menuSetting, multiTabsSetting, isMobile } =
useProjectSetting();
const settingStore = useProjectSettingStore();
const route = useRoute();
const router = useRouter();
const tabsViewStore = useTabsViewStore();
const asyncRouteStore = useAsyncRouteStore();
const navScroll: any = ref(null);
const navWrap: any = ref(null);
const isCurrent = ref(false);
const go = useGo();
const themeVars = useThemeVars();
const getCardColor = computed(() => {
return themeVars.value.cardColor;
});
const getBaseColor = computed(() => {
return themeVars.value.textColor1;
});
const state = reactive({
activeKey: route.fullPath,
scrollable: false,
dropdownX: 0,
dropdownY: 0,
showDropdown: false,
isMultiHeaderFixed: false,
multiTabsSetting: multiTabsSetting,
});
//
const getSimpleRoute = (route): RouteItem => {
const { fullPath, hash, meta, name, params, path, query } = route;
return { fullPath, hash, meta, name, params, path, query };
};
const isMixMenuNoneSub = computed(() => {
const mixMenu = settingStore.menuSetting.mixMenu;
const currentRoute = useRoute();
if (navMode.value != 'horizontal-mix') return true;
return !(
navMode.value === 'horizontal-mix' &&
mixMenu &&
currentRoute.meta.isRoot
);
});
//
const getChangeStyle = computed(() => {
const { collapsed } = props;
const { minMenuWidth, menuWidth }: any = menuSetting.value;
const { fixed }: any = multiTabsSetting.value;
let lenNum =
navMode.value === 'horizontal' || !isMixMenuNoneSub.value
? '0px'
: collapsed
? `${minMenuWidth}px`
: `${menuWidth}px`;
if (isMobile.value) {
return {
left: '0px',
width: '100%',
};
}
return {
left: lenNum,
width: `calc(100% - ${!fixed ? '0px' : lenNum})`,
};
});
//tags
const TabsMenuOptions = computed(() => {
const isDisabled = tabsList.value.length <= 1;
return [
{
label: '刷新当前',
key: '1',
icon: renderIcon(ReloadOutlined),
},
{
label: `关闭当前`,
key: '2',
disabled: isCurrent.value || isDisabled,
icon: renderIcon(CloseOutlined),
},
{
label: '关闭其他',
key: '3',
disabled: isDisabled,
icon: renderIcon(ColumnWidthOutlined),
},
{
label: '关闭全部',
key: '4',
disabled: isDisabled,
icon: renderIcon(MinusOutlined),
},
];
});
let cacheRoutes: RouteItem[] = [];
const simpleRoute = getSimpleRoute(route);
try {
const routesStr = storage.get(StorageEnum.ZS_TABS_ROUTES) as
| string
| null
| undefined;
cacheRoutes = routesStr ? JSON.parse(routesStr) : [simpleRoute];
} catch (e) {
cacheRoutes = [simpleRoute];
}
// localStorage
const routes = router.getRoutes();
cacheRoutes.forEach((cacheRoute) => {
const route = routes.find((route) => route.path === cacheRoute.path);
if (route) {
cacheRoute.meta = route.meta || cacheRoute.meta;
cacheRoute.name = (route.name || cacheRoute.name) as string;
}
});
//
tabsViewStore.initTabs(cacheRoutes);
//
function onScroll(e) {
let scrollTop =
e.target.scrollTop ||
document.documentElement.scrollTop ||
window.pageYOffset ||
document.body.scrollTop; //
state.isMultiHeaderFixed = !!(
!headerSetting.value.fixed &&
multiTabsSetting.value.fixed &&
scrollTop >= 64
);
}
window.addEventListener('scroll', onScroll, true);
//
const delKeepAliveCompName = () => {
if (route.meta.keepAlive) {
const name = router.currentRoute.value.matched.find(
(item) => item.name == route.name
)?.components?.default.name;
if (name) {
asyncRouteStore.keepAliveComponents =
asyncRouteStore.keepAliveComponents.filter((item) => item != name);
}
}
};
//
const tabsList: any = computed(() => tabsViewStore.tabsList);
const whiteList: string[] = [
PageEnum.BASE_LOGIN_NAME,
PageEnum.REDIRECT_NAME,
PageEnum.ERROR_PAGE_NAME,
];
watch(
() => route.fullPath,
(to) => {
if (whiteList.includes(route.name as string)) return;
state.activeKey = to;
tabsViewStore.addTab(getSimpleRoute(route));
updateNavScroll(true);
},
{ immediate: true }
);
//
window.addEventListener('beforeunload', () => {
storage.set(StorageEnum.ZS_TABS_ROUTES, JSON.stringify(tabsList.value));
});
//
const removeTab = (route) => {
if (tabsList.value.length === 1) {
return window['$message'].warning('这已经是最后一页,不能再关闭了!');
}
delKeepAliveCompName();
tabsViewStore.closeCurrentTab(route);
//
if (state.activeKey === route.fullPath) {
const currentRoute = tabsList.value[Math.max(0, tabsList.value.length - 1)];
state.activeKey = currentRoute.fullPath;
router.push(currentRoute);
}
updateNavScroll();
};
//
const reloadPage = () => {
delKeepAliveCompName();
router.push({
path: '/redirect' + route.fullPath,
});
};
//
provide('reloadPage', reloadPage);
//
const closeLeft = (route) => {
tabsViewStore.closeLeftTabs(route);
state.activeKey = route.fullPath;
router.replace(route.fullPath);
updateNavScroll();
};
//
const closeRight = (route) => {
tabsViewStore.closeRightTabs(route);
state.activeKey = route.fullPath;
router.replace(route.fullPath);
updateNavScroll();
};
//
const closeOther = (route) => {
tabsViewStore.closeOtherTabs(route);
state.activeKey = route.fullPath;
router.replace(route.fullPath);
updateNavScroll();
};
//
const closeAll = () => {
tabsViewStore.closeAllTabs();
router.replace(PageEnum.BASE_HOME);
updateNavScroll();
};
//tab
const closeHandleSelect = (key) => {
switch (key) {
//
case '1':
reloadPage();
break;
//
case '2':
removeTab(route);
break;
//
case '3':
closeOther(route);
break;
//
case '4':
closeAll();
break;
}
updateNavScroll();
state.showDropdown = false;
};
/**
* @param value 要滚动到的位置
* @param amplitude 每次滚动的长度
*/
function scrollTo(value: number, amplitude: number) {
const currentScroll = navScroll.value.scrollLeft;
const scrollWidth =
(amplitude > 0 && currentScroll + amplitude >= value) ||
(amplitude < 0 && currentScroll + amplitude <= value)
? value
: currentScroll + amplitude;
navScroll.value && navScroll.value.scrollTo(scrollWidth, 0);
if (scrollWidth === value) return;
return window.requestAnimationFrame(() => scrollTo(value, amplitude));
}
function scrollPrev() {
const containerWidth = navScroll.value.offsetWidth;
const currentScroll = navScroll.value.scrollLeft;
if (!currentScroll) return;
const scrollLeft =
currentScroll > containerWidth ? currentScroll - containerWidth : 0;
scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20);
}
function scrollNext() {
const containerWidth = navScroll.value.offsetWidth;
const navWidth = navScroll.value.scrollWidth;
const currentScroll = navScroll.value.scrollLeft;
if (navWidth - currentScroll <= containerWidth) return;
const scrollLeft =
navWidth - currentScroll > containerWidth * 2
? currentScroll + containerWidth
: navWidth - containerWidth;
scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20);
}
/**
* @param autoScroll 是否开启自动滚动功能
*/
async function updateNavScroll(autoScroll?: boolean) {
await nextTick();
if (!navScroll.value) return;
const containerWidth = navScroll.value.offsetWidth;
const navWidth = navScroll.value.scrollWidth;
if (containerWidth < navWidth) {
state.scrollable = true;
if (autoScroll) {
let tagList =
navScroll.value.querySelectorAll('.tabs-card-scroll-item') || [];
[...tagList].forEach((tag: HTMLElement) => {
// fix SyntaxError
if (tag.id === `tag${state.activeKey.split('/').join('\/')}`) {
tag.scrollIntoView && tag.scrollIntoView();
}
});
}
} else {
state.scrollable = false;
}
}
function handleResize() {
updateNavScroll(true);
}
function handleContextMenu(e, item) {
e.preventDefault();
isCurrent.value = PageEnum.BASE_HOME === item.path;
state.showDropdown = false;
nextTick().then(() => {
state.showDropdown = true;
state.dropdownX = e.clientX;
state.dropdownY = e.clientY;
});
}
function onClickOutside() {
state.showDropdown = false;
}
//tags
function goPage(e) {
const { fullPath } = e;
if (fullPath === route.fullPath) return;
state.activeKey = fullPath;
go(e, true);
}
//tab
function closeTabItem(e) {
const { fullPath } = e;
const routeInfo = tabsList.value.find((item) => item.fullPath == fullPath);
removeTab(routeInfo);
}
onMounted(() => {
// onElementResize();
});
// function onElementResize() {
// let observer;
// observer = elementResizeDetectorMaker();
// observer.listenTo(navWrap.value, handleResize);
// }
</script>
<style lang="scss" scoped>
.tabs-view {
display: flex;
width: 100%;
padding: 6px 0;
transition: all 0.2s ease-in-out;
&-main {
display: flex;
min-width: 100%;
max-width: 100%;
height: 32px;
.tabs-card {
position: relative;
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
-webkit-box-flex: 1;
.tabs-card-prev,
.tabs-card-next {
position: absolute;
width: 32px;
line-height: 32px;
text-align: center;
cursor: pointer;
.n-icon {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
}
}
.tabs-card-prev {
left: 0;
}
.tabs-card-next {
right: 0;
}
.tabs-card-next-hide,
.tabs-card-prev-hide {
display: none;
}
&-scroll {
overflow: hidden;
white-space: nowrap;
&-item {
position: relative;
display: inline-block;
flex: 0 0 auto;
height: 32px;
padding: 6px 16px 4px;
margin-right: 6px;
color: v-bind(getbasecolor);
cursor: pointer;
background: v-bind(getcardcolor);
border-radius: 3px;
span {
float: left;
vertical-align: middle;
}
&:hover {
color: #515a6e;
}
.n-icon {
position: relative;
width: 21px;
height: 22px;
margin-right: -6px;
color: #808695;
text-align: center;
vertical-align: middle;
&:hover {
color: #515a6e !important;
}
svg {
display: inline-block;
height: 21px;
}
}
}
.active-item {
color: v-bind(getapptheme);
}
}
}
.tabs-card-scrollable {
padding: 0 32px;
overflow: hidden;
}
}
.tabs-close {
width: 32px;
min-width: 32px;
height: 32px;
line-height: 32px;
text-align: center;
cursor: pointer;
background: var(--color);
border-radius: 2px;
&-btn {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: var(--color);
}
}
}
.tabs-view-default-background {
background: #f5f7f9;
}
.tabs-view-dark-background {
background: #101014;
}
.tabs-view-fix {
position: fixed;
left: 200px;
z-index: 5;
padding: 6px 10px;
}
.tabs-view-fixed-header {
top: 0;
}
</style>

View File

@ -1,3 +0,0 @@
import TransitionMain from "./index.vue";
export { TransitionMain };

View File

@ -1,14 +0,0 @@
<template>
<router-view v-slot="{ Component, route }">
<transition name="fade" mode="out-in" appear>
<component
v-if="route.noKeepAlive"
:is="Component"
:key="route.fullPath"
/>
<keep-alive v-else>
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</transition>
</router-view>
</template>

View File

@ -1,33 +1,283 @@
<script setup lang="ts"></script>
<template>
<n-layout class="layout" position="absolute" has-sider>
<Sider />
<n-layout class="layout" :position="fixedMenu" has-sider>
<n-layout-sider
v-if="
!isMobile &&
isMixMenuNoneSub &&
(navMode === 'vertical' || navMode === 'horizontal-mix')
"
show-trigger="bar"
@collapse="collapsed = true"
:position="fixedMenu"
@expand="collapsed = false"
:collapsed="collapsed"
collapse-mode="width"
:collapsed-width="64"
:width="leftMenuWidth"
:native-scrollbar="false"
:inverted="inverted"
class="layout-sider"
>
<Logo :collapsed="collapsed" />
<AsideMenu
v-model:collapsed="collapsed"
v-model:location="getMenuLocation"
/>
</n-layout-sider>
<n-layout>
<n-layout-header>
<Header />
<n-drawer
v-model:show="showSideDrawer"
:width="menuWidth"
:placement="'left'"
class="layout-side-drawer"
>
<n-layout-sider
:position="fixedMenu"
:collapsed="false"
:width="menuWidth"
:native-scrollbar="false"
:inverted="inverted"
class="layout-sider"
>
<Logo :collapsed="collapsed" />
<AsideMenu v-model:location="getMenuLocation" />
</n-layout-sider>
</n-drawer>
<n-layout :inverted="inverted">
<n-layout-header :inverted="getHeaderInverted" :position="fixedHeader">
<PageHeader v-model:collapsed="collapsed" :inverted="inverted" />
</n-layout-header>
<n-layout-content>
<div class="layout-content-main">
<Main />
<n-layout-content
class="layout-content"
:class="{ 'layout-default-background': getDarkTheme === false }"
>
<div
class="layout-content-main"
:class="{
'layout-content-main-fix': fixedMulti,
'fluid-header': fixedHeader === 'static',
}"
>
<TabsView v-if="isMultiTabs" v-model:collapsed="collapsed" />
<div
class="main-view"
:class="{
'main-view-fix': fixedMulti,
noMultiTabs: !isMultiTabs,
'mt-3': !isMultiTabs,
}"
>
<MainView />
</div>
</div>
<!--1.15废弃没啥用占用操作空间-->
<!-- <NLayoutFooter v-if="getShowFooter">-->
<!-- <PageFooter />-->
<!-- </NLayoutFooter>-->
</n-layout-content>
<n-back-top :right="100" />
</n-layout>
</n-layout>
</template>
<style scoped lang="scss">
<script lang="ts" setup>
import { ref, unref, computed, onMounted } from 'vue';
import { Logo } from './components/Logo';
import { TabsView } from './components/TagsView';
import { MainView } from './components/Main';
import { AsideMenu } from './components/Sider';
import { PageHeader } from './components/Header';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
import { useRoute } from 'vue-router';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
const { getDarkTheme } = useDesignSetting();
const {
// showFooter,
navMode,
navTheme,
headerSetting,
menuSetting,
multiTabsSetting,
} = useProjectSetting();
const settingStore = useProjectSettingStore();
const collapsed = ref<boolean>(false);
const { mobileWidth, menuWidth } = unref(menuSetting);
const isMobile = computed<boolean>({
get: () => settingStore.getIsMobile,
set: (val) => settingStore.setIsMobile(val),
});
const fixedHeader = computed(() => {
const { fixed } = unref(headerSetting);
return fixed ? 'absolute' : 'static';
});
const isMixMenuNoneSub = computed(() => {
const mixMenu = unref(menuSetting).mixMenu;
const currentRoute = useRoute();
if (unref(navMode) != 'horizontal-mix') return true;
if (
unref(navMode) === 'horizontal-mix' &&
mixMenu &&
currentRoute.meta.isRoot
) {
return false;
}
return true;
});
const fixedMenu = computed(() => {
const { fixed } = unref(headerSetting);
return fixed ? 'absolute' : 'static';
});
const isMultiTabs = computed(() => {
return unref(multiTabsSetting).show;
});
const fixedMulti = computed(() => {
return unref(multiTabsSetting).fixed;
});
const inverted = computed(() => {
return ['dark', 'header-dark'].includes(unref(navTheme));
});
const getHeaderInverted = computed(() => {
return ['light', 'header-dark'].includes(unref(navTheme))
? unref(inverted)
: !unref(inverted);
});
const leftMenuWidth = computed(() => {
const { minMenuWidth, menuWidth } = unref(menuSetting);
return collapsed.value ? minMenuWidth : menuWidth;
});
const getMenuLocation = computed(() => {
return 'left';
});
//
const showSideDrawer = computed({
get: () => isMobile.value && collapsed.value,
set: (val) => (collapsed.value = val),
});
//
const checkMobileMode = () => {
if (document.body.clientWidth <= mobileWidth) {
isMobile.value = true;
} else {
isMobile.value = false;
}
collapsed.value = false;
};
const watchWidth = () => {
const Width = document.body.clientWidth;
if (Width <= 950) {
collapsed.value = true;
} else collapsed.value = false;
checkMobileMode();
};
onMounted(() => {
checkMobileMode();
window.addEventListener('resize', watchWidth);
});
</script>
<style lang="scss">
.layout-side-drawer {
background-color: rgb(0 20 40);
.layout-sider {
position: relative;
z-index: 13;
min-height: 100vh;
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
transition: all 0.2s ease-in-out;
}
}
</style>
<style lang="scss" scoped>
.layout {
display: flex;
flex: auto;
flex-direction: row;
&-content-main {
flex: auto;
min-height: calc(100vh - 50px);
padding: 10px;
&-default-background {
background: #f5f7f9;
}
.layout-sider {
position: relative;
z-index: 13;
min-height: 100vh;
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
transition: all 0.2s ease-in-out;
}
.layout-sider-fix {
position: fixed;
top: 0;
left: 0;
}
.ant-layout {
overflow: hidden;
}
.layout-right-fix {
min-height: 100vh;
padding-left: 200px;
overflow-x: hidden;
transition: all 0.2s ease-in-out;
}
.layout-content {
flex: auto;
min-height: 100vh;
}
.n-layout-header.n-layout-header--absolute-positioned {
z-index: 11;
}
.n-layout-footer {
background: none;
}
}
.layout-content-main {
position: relative;
padding-top: 64px;
margin: 0 10px 10px;
}
.layout-content-main-fix {
padding-top: 64px;
}
.fluid-header {
padding-top: 0;
}
.main-view-fix {
padding-top: 44px;
}
.noMultiTabs {
padding-top: 0;
}
</style>

View File

@ -1,11 +1,12 @@
import { createApp } from "vue";
import App from "./App.vue";
import router, { setupRouter } from "@/router";
import { setupStore } from "@/store";
import { setupNaive, setupNaiveDiscreteApi, setupDirectives } from "@/plugins";
import './styles/tailwind.css';
import { createApp } from 'vue';
import App from './App.vue';
import router, { setupRouter } from '@/router';
import { setupStore } from '@/store';
import { setupNaive, setupNaiveDiscreteApi, setupDirectives } from '@/plugins';
import SvgIcon from '@/components/SvgIcon/index.vue';
// 样式
import "@/styles/index.scss";
import '@/styles/index.scss';
async function appInit() {
const app = createApp(App);
@ -24,15 +25,17 @@ async function appInit() {
// 挂载路由
setupRouter(app);
app.component('SvgIcon', SvgIcon);
// 路由准备就绪后挂载APP实例
await router.isReady();
const meta = document.createElement("meta");
meta.name = "naive-ui-style";
const meta = document.createElement('meta');
meta.name = 'naive-ui-style';
document.head.appendChild(meta);
// 挂载到页面
app.mount("#app", true);
app.mount('#app', true);
}
void appInit();

1
src/plugins/assets.ts Normal file
View File

@ -0,0 +1 @@
import 'virtual:svg-icons-register';

View File

@ -1,11 +1,11 @@
import { App } from "vue";
import { App } from 'vue';
import draggable from "@/directives/draggable";
import draggable from '@/directives/draggable';
/**
*
* @param app
*/
export function setupDirectives(app: App) {
// 拖拽指令
app.directive("draggable", draggable);
app.directive('draggable', draggable);
}

View File

@ -7,7 +7,7 @@ import {
InformationCircleOutline as InformationCircleIcon,
BagOutline as BagOutlineIcon,
BagCheckOutline as BagCheckOutlineIcon,
} from "@vicons/ionicons5";
} from '@vicons/ionicons5';
const ionicons5 = {
AlbumsOutlineIcon,

View File

@ -1,4 +1,5 @@
export { setupNaive } from "@/plugins/naive";
export { icons } from "@/plugins/icon";
export { setupNaiveDiscreteApi } from "@/plugins/naiveDiscreteApi";
export { setupDirectives } from "@/plugins/directives";
export { setupNaive } from '@/plugins/naive';
export { icons } from '@/plugins/icon';
export { setupNaiveDiscreteApi } from '@/plugins/naiveDiscreteApi';
export { setupDirectives } from '@/plugins/directives';
export * from '@/plugins/assets';

View File

@ -1,4 +1,4 @@
import type { App } from "vue";
import type { App } from 'vue';
import {
create,
NA,
@ -100,7 +100,7 @@ import {
NWatermark,
NEmpty,
NCollapseTransition,
} from "naive-ui";
} from 'naive-ui';
const naive = create({
components: [

View File

@ -1,5 +1,7 @@
import * as NaiveUI from "naive-ui";
import { computed } from "vue";
import * as NaiveUI from 'naive-ui';
import { computed } from 'vue';
import { useDesignSetting } from '@/store/modules/designSetting';
import { lighten } from '@/utils';
/**
* Naive-ui API
@ -8,29 +10,30 @@ import { computed } from "vue";
*/
export function setupNaiveDiscreteApi() {
const designStore = useDesignSetting();
const configProviderPropsRef = computed(() => ({
// theme: NaiveUI.darkTheme,
// themeOverrides: {
// common: {
// primaryColor: designStore.appTheme,
// primaryColorHover: designStore.appTheme,
// primaryColorPressed: designStore.appTheme,
// },
// LoadingBar: {
// colorLoading: designStore.appTheme,
// },
// },
theme: designStore.darkTheme ? NaiveUI.darkTheme : undefined,
themeOverrides: {
common: {
primaryColor: designStore.appTheme,
primaryColorHover: lighten(designStore.appTheme, 6),
primaryColorPressed: lighten(designStore.appTheme, 6),
},
LoadingBar: {
colorLoading: designStore.appTheme,
},
},
}));
const { message, dialog, notification, loadingBar } =
NaiveUI.createDiscreteApi(
["message", "dialog", "notification", "loadingBar"],
['message', 'dialog', 'notification', 'loadingBar'],
{
configProviderProps: configProviderPropsRef,
}
);
(window as any)["$message"] = message;
(window as any)["$dialog"] = dialog;
(window as any)["$notification"] = notification;
(window as any)["$loading"] = loadingBar;
(window as any)['$message'] = message;
(window as any)['$dialog'] = dialog;
(window as any)['$notification'] = notification;
(window as any)['$loading'] = loadingBar;
}

View File

@ -1,21 +1,21 @@
import { RouteRecordRaw } from "vue-router";
import type { AppRouteRecordRaw } from "@/router/types";
import { ErrorPage404, ErrorPage403, Layout } from "@/router/constant";
import { PageEnum } from "@/enums/pageEnum";
import { RouteRecordRaw } from 'vue-router';
import type { AppRouteRecordRaw } from '@/router/types';
import { ErrorPage404, ErrorPage403, Layout } from '@/router/constant';
import { PageEnum } from '@/enums/pageEnum';
// import { GoReload } from "@/components/GoReload";
export const LoginRoute: RouteRecordRaw = {
path: PageEnum.BASE_LOGIN,
name: PageEnum.BASE_LOGIN_NAME,
component: () => import("@/views/login/index.vue"),
component: () => import('@/views/login/index.vue'),
meta: {
title: "登录",
title: '登录',
},
};
export const HttpErrorPage: RouteRecordRaw[] = [
{
path: "/error/404",
path: '/error/404',
name: PageEnum.ERROR_PAGE_NAME_404,
component: ErrorPage404,
meta: {
@ -23,7 +23,7 @@ export const HttpErrorPage: RouteRecordRaw[] = [
},
},
{
path: "/error/403",
path: '/error/403',
name: PageEnum.ERROR_PAGE_NAME_403,
component: ErrorPage403,
meta: {
@ -41,20 +41,20 @@ export const HttpErrorPage: RouteRecordRaw[] = [
];
export const ErrorPageRoute: RouteRecordRaw = {
path: "/:path(.*)*",
name: "ErrorPage",
path: '/:path(.*)*',
name: 'ErrorPage',
component: Layout,
meta: {
title: "ErrorPage",
title: 'ErrorPage',
// hideBreadcrumb: true,
},
children: [
{
path: "/:path(.*)*",
name: "ErrorPageSon",
path: '/:path(.*)*',
name: 'ErrorPageSon',
component: ErrorPage404,
meta: {
title: "ErrorPage",
title: 'ErrorPage',
// hideBreadcrumb: true,
},
},
@ -80,9 +80,9 @@ export const RedirectRoute: AppRouteRecordRaw = {
},
children: [
{
path: "/redirect/:path(.*)",
path: '/redirect/:path(.*)',
name: PageEnum.REDIRECT_NAME,
component: () => import("@/views/redirect/index.vue"),
component: () => import('@/views/redirect/index.vue'),
meta: {
title: PageEnum.REDIRECT_NAME,
hideBreadcrumb: true,

Some files were not shown because too many files have changed in this diff Show More