mirror of
https://github.com/pure-admin/pure-admin-thin.git
synced 2025-04-25 07:57:18 +08:00
feat: 项目初始化
This commit is contained in:
parent
eae44feb59
commit
c94d2e5737
152
.eslintrc.js
152
.eslintrc.js
@ -5,116 +5,128 @@ module.exports = {
|
||||
},
|
||||
globals: {
|
||||
// Ref sugar (take 2)
|
||||
$: "readonly",
|
||||
$$: "readonly",
|
||||
$ref: "readonly",
|
||||
$shallowRef: "readonly",
|
||||
$computed: "readonly",
|
||||
$: 'readonly',
|
||||
$$: 'readonly',
|
||||
$ref: 'readonly',
|
||||
$shallowRef: 'readonly',
|
||||
$computed: 'readonly',
|
||||
|
||||
// index.d.ts
|
||||
// global.d.ts
|
||||
Fn: "readonly",
|
||||
PromiseFn: "readonly",
|
||||
RefType: "readonly",
|
||||
LabelValueOptions: "readonly",
|
||||
EmitType: "readonly",
|
||||
TargetContext: "readonly",
|
||||
ComponentElRef: "readonly",
|
||||
ComponentRef: "readonly",
|
||||
ElRef: "readonly",
|
||||
global: "readonly",
|
||||
ForDataType: "readonly",
|
||||
ComponentRoutes: "readonly",
|
||||
Fn: 'readonly',
|
||||
PromiseFn: 'readonly',
|
||||
RefType: 'readonly',
|
||||
LabelValueOptions: 'readonly',
|
||||
EmitType: 'readonly',
|
||||
TargetContext: 'readonly',
|
||||
ComponentElRef: 'readonly',
|
||||
ComponentRef: 'readonly',
|
||||
ElRef: 'readonly',
|
||||
global: 'readonly',
|
||||
ForDataType: 'readonly',
|
||||
ComponentRoutes: 'readonly',
|
||||
|
||||
// script setup
|
||||
defineProps: "readonly",
|
||||
defineEmits: "readonly",
|
||||
defineExpose: "readonly",
|
||||
withDefaults: "readonly"
|
||||
defineProps: 'readonly',
|
||||
defineEmits: 'readonly',
|
||||
defineExpose: 'readonly',
|
||||
withDefaults: 'readonly'
|
||||
},
|
||||
extends: [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"@vue/prettier",
|
||||
"@vue/eslint-config-typescript"
|
||||
],
|
||||
parser: "vue-eslint-parser",
|
||||
extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/typescript/recommended', '@vue/prettier', '@vue/eslint-config-typescript'],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser",
|
||||
parser: '@typescript-eslint/parser',
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
jsxPragma: "React",
|
||||
sourceType: 'module',
|
||||
jsxPragma: 'React',
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
}
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.ts", "*.vue"],
|
||||
files: ['*.ts', '*.vue'],
|
||||
rules: {
|
||||
"no-undef": "off"
|
||||
'no-undef': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["*.vue"],
|
||||
parser: "vue-eslint-parser",
|
||||
files: ['*.vue'],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser",
|
||||
extraFileExtensions: [".vue"],
|
||||
ecmaVersion: "latest",
|
||||
parser: '@typescript-eslint/parser',
|
||||
extraFileExtensions: ['.vue'],
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "off"
|
||||
'no-undef': 'off'
|
||||
}
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
"vue/no-v-html": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/require-explicit-emits": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off", // any
|
||||
"no-debugger": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off", // setup()
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"vue/html-self-closing": [
|
||||
"error",
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/require-explicit-emits': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off', // any
|
||||
'no-debugger': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off', // setup()
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
quotes: [
|
||||
2,
|
||||
'single',
|
||||
{
|
||||
avoidEscape: true,
|
||||
allowTemplateLiterals: true
|
||||
}
|
||||
],
|
||||
semi: [2, 'never'],
|
||||
'semi-spacing': [
|
||||
2,
|
||||
{
|
||||
before: false,
|
||||
after: true
|
||||
}
|
||||
],
|
||||
'vue/html-self-closing': [
|
||||
'error',
|
||||
{
|
||||
html: {
|
||||
void: "always",
|
||||
normal: "always",
|
||||
component: "always"
|
||||
void: 'always',
|
||||
normal: 'always',
|
||||
component: 'always'
|
||||
},
|
||||
svg: "always",
|
||||
math: "always"
|
||||
svg: 'always',
|
||||
math: 'always'
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_"
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_'
|
||||
}
|
||||
],
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_"
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_'
|
||||
}
|
||||
],
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
endOfLine: "auto"
|
||||
endOfLine: 'auto',
|
||||
singleQuote: true,
|
||||
semi: false
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
module.exports = {
|
||||
bracketSpacing: true,
|
||||
singleQuote: false,
|
||||
arrowParens: "avoid",
|
||||
trailingComma: "none"
|
||||
};
|
||||
// bracketSpacing: true,
|
||||
singleQuote: true,
|
||||
semi: false,
|
||||
arrowParens: 'avoid',
|
||||
trailingComma: 'none'
|
||||
}
|
||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -7,7 +7,7 @@
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.guides.bracketPairs": "active",
|
||||
"files.autoSave": "afterDelay",
|
||||
"files.autoSave": "onFocusChange",
|
||||
"git.confirmSync": false,
|
||||
"workbench.startupEditor": "newUntitledFile",
|
||||
"editor.suggestSelection": "first",
|
||||
|
@ -29,6 +29,13 @@ menus:
|
||||
hsfourZeroFour: "404"
|
||||
hsfourZeroOne: "403"
|
||||
hsFive: "500"
|
||||
basicData: Basic Data
|
||||
liveData: Live Data
|
||||
userData: User Data
|
||||
searchUser: Search User
|
||||
gameData: Game Data
|
||||
clubData: Club Data
|
||||
searchClub: Search Club
|
||||
permission: Permission Manage
|
||||
permissionPage: Page Permission
|
||||
permissionButton: Button Permission
|
||||
@ -41,3 +48,6 @@ login:
|
||||
usernameReg: Please enter username
|
||||
passwordReg: Please enter password
|
||||
passwordRuleReg: The password format should be any combination of 8-18 digits
|
||||
others:
|
||||
backHome: Back Home
|
||||
comingSoon: Coming soon.
|
||||
|
@ -29,6 +29,13 @@ menus:
|
||||
hsfourZeroFour: "404"
|
||||
hsfourZeroOne: "403"
|
||||
hsFive: "500"
|
||||
basicData: "基础数据"
|
||||
liveData: "实时数据"
|
||||
userData: "用户数据"
|
||||
searchUser: "搜索用户"
|
||||
gameData: "牌局数据"
|
||||
clubData: "俱乐部数据"
|
||||
searchClub: "搜索俱乐部"
|
||||
permission: 权限管理
|
||||
permissionPage: 页面权限
|
||||
permissionButton: 按钮权限
|
||||
@ -41,3 +48,7 @@ login:
|
||||
usernameReg: 请输入账号
|
||||
passwordReg: 请输入密码
|
||||
passwordRuleReg: 密码格式应为8-18位数字、字母、符号的任意两种组合
|
||||
others:
|
||||
backHome: 返回首页
|
||||
comingSoon: 敬请期待。
|
||||
|
||||
|
@ -11,7 +11,7 @@ const permissionRouter = {
|
||||
path: "/permission",
|
||||
meta: {
|
||||
title: "menus.permission",
|
||||
icon: "lollipop",
|
||||
icon: "informationLine",
|
||||
rank: 10
|
||||
},
|
||||
children: [
|
||||
|
14
src/App.vue
14
src/App.vue
@ -5,19 +5,19 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { ElConfigProvider } from "element-plus";
|
||||
import zhCn from "element-plus/lib/locale/lang/zh-cn";
|
||||
import en from "element-plus/lib/locale/lang/en";
|
||||
import { defineComponent } from 'vue'
|
||||
import { ElConfigProvider } from 'element-plus'
|
||||
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
|
||||
import en from 'element-plus/lib/locale/lang/en'
|
||||
export default defineComponent({
|
||||
name: "app",
|
||||
name: 'app',
|
||||
components: {
|
||||
[ElConfigProvider.name]: ElConfigProvider
|
||||
},
|
||||
computed: {
|
||||
currentLocale() {
|
||||
return this.$storage.locale?.locale === "zh" ? zhCn : en;
|
||||
return this.$storage.locale?.locale === 'zh' ? zhCn : en
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { http } from "@/utils/http";
|
||||
import { http } from '@/utils/http'
|
||||
|
||||
type Result = {
|
||||
success: boolean;
|
||||
data: Array<any>;
|
||||
};
|
||||
success: boolean
|
||||
data: Array<any>
|
||||
}
|
||||
|
||||
export const getAsyncRoutes = () => {
|
||||
return http.request<Result>("get", "/getAsyncRoutes");
|
||||
};
|
||||
return http.request<Result>('get', '/getAsyncRoutes')
|
||||
}
|
||||
|
@ -1,39 +1,39 @@
|
||||
import { http } from "@/utils/http";
|
||||
import { http } from '@/utils/http'
|
||||
|
||||
export type UserResult = {
|
||||
success: boolean;
|
||||
success: boolean
|
||||
data: {
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
username: string
|
||||
/** 当前登陆用户的角色 */
|
||||
roles: Array<string>;
|
||||
roles: Array<string>
|
||||
/** `token` */
|
||||
accessToken: string;
|
||||
accessToken: string
|
||||
/** 用于调用刷新`accessToken`的接口时所需的`token` */
|
||||
refreshToken: string;
|
||||
refreshToken: string
|
||||
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
|
||||
expires: Date;
|
||||
};
|
||||
};
|
||||
expires: Date
|
||||
}
|
||||
}
|
||||
|
||||
export type RefreshTokenResult = {
|
||||
success: boolean;
|
||||
success: boolean
|
||||
data: {
|
||||
/** `token` */
|
||||
accessToken: string;
|
||||
accessToken: string
|
||||
/** 用于调用刷新`accessToken`的接口时所需的`token` */
|
||||
refreshToken: string;
|
||||
refreshToken: string
|
||||
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
|
||||
expires: Date;
|
||||
};
|
||||
};
|
||||
expires: Date
|
||||
}
|
||||
}
|
||||
|
||||
/** 登录 */
|
||||
export const getLogin = (data?: object) => {
|
||||
return http.request<UserResult>("post", "/login", { data });
|
||||
};
|
||||
return http.request<UserResult>('post', '/login', { data })
|
||||
}
|
||||
|
||||
/** 刷新token */
|
||||
export const refreshTokenApi = (data?: object) => {
|
||||
return http.request<RefreshTokenResult>("post", "/refreshToken", { data });
|
||||
};
|
||||
return http.request<RefreshTokenResult>('post', '/refreshToken', { data })
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2208059 */
|
||||
src: url("iconfont.woff2?t=1671895108120") format("woff2"),
|
||||
url("iconfont.woff?t=1671895108120") format("woff"),
|
||||
url("iconfont.ttf?t=1671895108120") format("truetype");
|
||||
font-family: 'iconfont'; /* Project id 2208059 */
|
||||
src: url('iconfont.woff2?t=1671895108120') format('woff2'),
|
||||
url('iconfont.woff?t=1671895108120') format('woff'),
|
||||
url('iconfont.ttf?t=1671895108120') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-family: 'iconfont' !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@ -14,13 +14,13 @@
|
||||
}
|
||||
|
||||
.pure-iconfont-tabs:before {
|
||||
content: "\e63e";
|
||||
content: '\e63e';
|
||||
}
|
||||
|
||||
.pure-iconfont-logo:before {
|
||||
content: "\e620";
|
||||
content: '\e620';
|
||||
}
|
||||
|
||||
.pure-iconfont-new:before {
|
||||
content: "\e615";
|
||||
content: '\e615';
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
import auth from "./src/auth";
|
||||
import auth from './src/auth'
|
||||
|
||||
const Auth = auth;
|
||||
const Auth = auth
|
||||
|
||||
export { Auth };
|
||||
export { Auth }
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { defineComponent, Fragment } from "vue";
|
||||
import { hasAuth } from "@/router/utils";
|
||||
import { defineComponent, Fragment } from 'vue'
|
||||
import { hasAuth } from '@/router/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: "Auth",
|
||||
name: 'Auth',
|
||||
props: {
|
||||
value: {
|
||||
type: undefined,
|
||||
@ -11,10 +11,10 @@ export default defineComponent({
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
return () => {
|
||||
if (!slots) return null;
|
||||
if (!slots) return null
|
||||
return hasAuth(props.value) ? (
|
||||
<Fragment>{slots.default?.()}</Fragment>
|
||||
) : null;
|
||||
};
|
||||
) : null
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
@ -1,12 +1,12 @@
|
||||
import iconifyIconOffline from "./src/iconifyIconOffline";
|
||||
import iconifyIconOnline from "./src/iconifyIconOnline";
|
||||
import fontIcon from "./src/iconfont";
|
||||
import iconifyIconOffline from './src/iconifyIconOffline'
|
||||
import iconifyIconOnline from './src/iconifyIconOnline'
|
||||
import fontIcon from './src/iconfont'
|
||||
|
||||
/** 本地图标组件 */
|
||||
const IconifyIconOffline = iconifyIconOffline;
|
||||
const IconifyIconOffline = iconifyIconOffline
|
||||
/** 在线图标组件 */
|
||||
const IconifyIconOnline = iconifyIconOnline;
|
||||
const IconifyIconOnline = iconifyIconOnline
|
||||
/** iconfont组件 */
|
||||
const FontIcon = fontIcon;
|
||||
const FontIcon = fontIcon
|
||||
|
||||
export { IconifyIconOffline, IconifyIconOnline, FontIcon };
|
||||
export { IconifyIconOffline, IconifyIconOnline, FontIcon }
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { iconType } from "./types";
|
||||
import { h, defineComponent, Component } from "vue";
|
||||
import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
|
||||
import { iconType } from './types'
|
||||
import { h, defineComponent, Component } from 'vue'
|
||||
import { IconifyIconOnline, IconifyIconOffline, FontIcon } from '../index'
|
||||
|
||||
/**
|
||||
* 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标
|
||||
@ -11,51 +11,51 @@ import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
|
||||
*/
|
||||
export function useRenderIcon(icon: any, attrs?: iconType): Component {
|
||||
// iconfont
|
||||
const ifReg = /^IF-/;
|
||||
const ifReg = /^IF-/
|
||||
// typeof icon === "function" 属于SVG
|
||||
if (ifReg.test(icon)) {
|
||||
// iconfont
|
||||
const name = icon.split(ifReg)[1];
|
||||
const name = icon.split(ifReg)[1]
|
||||
const iconName = name.slice(
|
||||
0,
|
||||
name.indexOf(" ") == -1 ? name.length : name.indexOf(" ")
|
||||
);
|
||||
const iconType = name.slice(name.indexOf(" ") + 1, name.length);
|
||||
name.indexOf(' ') == -1 ? name.length : name.indexOf(' ')
|
||||
)
|
||||
const iconType = name.slice(name.indexOf(' ') + 1, name.length)
|
||||
return defineComponent({
|
||||
name: "FontIcon",
|
||||
name: 'FontIcon',
|
||||
render() {
|
||||
return h(FontIcon, {
|
||||
icon: iconName,
|
||||
iconType,
|
||||
...attrs
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
} else if (typeof icon === "function" || typeof icon?.render === "function") {
|
||||
})
|
||||
} else if (typeof icon === 'function' || typeof icon?.render === 'function') {
|
||||
// svg
|
||||
return icon;
|
||||
} else if (typeof icon === "object") {
|
||||
return icon
|
||||
} else if (typeof icon === 'object') {
|
||||
return defineComponent({
|
||||
name: "OfflineIcon",
|
||||
name: 'OfflineIcon',
|
||||
render() {
|
||||
return h(IconifyIconOffline, {
|
||||
icon: icon,
|
||||
...attrs
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
} else {
|
||||
// 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之
|
||||
return defineComponent({
|
||||
name: "Icon",
|
||||
name: 'Icon',
|
||||
render() {
|
||||
const IconifyIcon =
|
||||
icon && icon.includes(":") ? IconifyIconOnline : IconifyIconOffline;
|
||||
icon && icon.includes(':') ? IconifyIconOnline : IconifyIconOffline
|
||||
return h(IconifyIcon, {
|
||||
icon: icon,
|
||||
...attrs
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +1,48 @@
|
||||
import { h, defineComponent } from "vue";
|
||||
import { h, defineComponent } from 'vue'
|
||||
|
||||
// 封装iconfont组件,默认`font-class`引用模式,支持`unicode`引用、`font-class`引用、`symbol`引用 (https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.20&helptype=code)
|
||||
export default defineComponent({
|
||||
name: "FontIcon",
|
||||
name: 'FontIcon',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ""
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const attrs = this.$attrs;
|
||||
if (Object.keys(attrs).includes("uni") || attrs?.iconType === "uni") {
|
||||
const attrs = this.$attrs
|
||||
if (Object.keys(attrs).includes('uni') || attrs?.iconType === 'uni') {
|
||||
return h(
|
||||
"i",
|
||||
'i',
|
||||
{
|
||||
class: "iconfont",
|
||||
class: 'iconfont',
|
||||
...attrs
|
||||
},
|
||||
this.icon
|
||||
);
|
||||
)
|
||||
} else if (
|
||||
Object.keys(attrs).includes("svg") ||
|
||||
attrs?.iconType === "svg"
|
||||
Object.keys(attrs).includes('svg') ||
|
||||
attrs?.iconType === 'svg'
|
||||
) {
|
||||
return h(
|
||||
"svg",
|
||||
'svg',
|
||||
{
|
||||
class: "icon-svg",
|
||||
"aria-hidden": true
|
||||
class: 'icon-svg',
|
||||
'aria-hidden': true
|
||||
},
|
||||
{
|
||||
default: () => [
|
||||
h("use", {
|
||||
"xlink:href": `#${this.icon}`
|
||||
h('use', {
|
||||
'xlink:href': `#${this.icon}`
|
||||
})
|
||||
]
|
||||
}
|
||||
);
|
||||
)
|
||||
} else {
|
||||
return h("i", {
|
||||
return h('i', {
|
||||
class: `iconfont ${this.icon}`,
|
||||
...attrs
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { h, defineComponent } from "vue";
|
||||
import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline";
|
||||
import { h, defineComponent } from 'vue'
|
||||
import { Icon as IconifyIcon, addIcon } from '@iconify/vue/dist/offline'
|
||||
|
||||
// Iconify Icon在Vue里本地使用(用于内网环境)https://docs.iconify.design/icon-components/vue/offline.html
|
||||
export default defineComponent({
|
||||
name: "IconifyIconOffline",
|
||||
name: 'IconifyIconOffline',
|
||||
components: { IconifyIcon },
|
||||
props: {
|
||||
icon: {
|
||||
@ -11,20 +11,20 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
render() {
|
||||
if (typeof this.icon === "object") addIcon(this.icon, this.icon);
|
||||
const attrs = this.$attrs;
|
||||
if (typeof this.icon === 'object') addIcon(this.icon, this.icon)
|
||||
const attrs = this.$attrs
|
||||
return h(
|
||||
IconifyIcon,
|
||||
{
|
||||
icon: this.icon,
|
||||
style: attrs?.style
|
||||
? Object.assign(attrs.style, { outline: "none" })
|
||||
: { outline: "none" },
|
||||
? Object.assign(attrs.style, { outline: 'none' })
|
||||
: { outline: 'none' },
|
||||
...attrs
|
||||
},
|
||||
{
|
||||
default: () => []
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
@ -1,30 +1,30 @@
|
||||
import { h, defineComponent } from "vue";
|
||||
import { Icon as IconifyIcon } from "@iconify/vue";
|
||||
import { h, defineComponent } from 'vue'
|
||||
import { Icon as IconifyIcon } from '@iconify/vue'
|
||||
|
||||
// Iconify Icon在Vue里在线使用(用于外网环境)
|
||||
export default defineComponent({
|
||||
name: "IconifyIconOnline",
|
||||
name: 'IconifyIconOnline',
|
||||
components: { IconifyIcon },
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ""
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const attrs = this.$attrs;
|
||||
const attrs = this.$attrs
|
||||
return h(
|
||||
IconifyIcon,
|
||||
{
|
||||
icon: `${this.icon}`,
|
||||
style: attrs?.style
|
||||
? Object.assign(attrs.style, { outline: "none" })
|
||||
: { outline: "none" },
|
||||
? Object.assign(attrs.style, { outline: 'none' })
|
||||
: { outline: 'none' },
|
||||
...attrs
|
||||
},
|
||||
{
|
||||
default: () => []
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
@ -1,14 +1,26 @@
|
||||
import { addIcon } from "@iconify/vue/dist/offline";
|
||||
import { addIcon } from '@iconify/vue/dist/offline'
|
||||
|
||||
/**
|
||||
* 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载
|
||||
*/
|
||||
|
||||
// 本地菜单图标,后端在路由的icon中返回对应的图标字符串并且前端在此处使用addIcon添加即可渲染菜单图标
|
||||
import HomeFilled from "@iconify-icons/ep/home-filled";
|
||||
import InformationLine from "@iconify-icons/ri/information-line";
|
||||
import Lollipop from "@iconify-icons/ep/lollipop";
|
||||
import homeSmileLine from '@iconify-icons/ri/home-smile-line'
|
||||
import InformationLine from '@iconify-icons/ri/information-line'
|
||||
import signalWifiErrorLine from '@iconify-icons/ri/signal-wifi-error-line'
|
||||
import lineChartLine from '@iconify-icons/ri/line-chart-line'
|
||||
import barChart2Line from '@iconify-icons/ri/bar-chart-2-line'
|
||||
import volumeUpLine from '@iconify-icons/ri/volume-up-line'
|
||||
import userFollowLine from '@iconify-icons/ri/user-follow-line'
|
||||
import arrowUpCircleLine from '@iconify-icons/ri/arrow-up-circle-line'
|
||||
import settings4Line from '@iconify-icons/ri/settings-4-line'
|
||||
|
||||
addIcon("homeFilled", HomeFilled);
|
||||
addIcon("informationLine", InformationLine);
|
||||
addIcon("lollipop", Lollipop);
|
||||
addIcon('homeSmileLine', homeSmileLine)
|
||||
addIcon('informationLine', InformationLine)
|
||||
addIcon('signalWifiErrorLine', signalWifiErrorLine)
|
||||
addIcon('lineChartLine', lineChartLine)
|
||||
addIcon('barChart2Line', barChart2Line)
|
||||
addIcon('volumeUpLine', volumeUpLine)
|
||||
addIcon('userFollowLine', userFollowLine)
|
||||
addIcon('arrowUpCircleLine', arrowUpCircleLine)
|
||||
addIcon('settings4Line', settings4Line)
|
||||
|
@ -1,19 +1,19 @@
|
||||
export interface iconType {
|
||||
// iconify (https://docs.iconify.design/icon-components/vue/#properties)
|
||||
inline?: boolean;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
horizontalFlip?: boolean;
|
||||
verticalFlip?: boolean;
|
||||
flip?: string;
|
||||
rotate?: number | string;
|
||||
color?: string;
|
||||
horizontalAlign?: boolean;
|
||||
verticalAlign?: boolean;
|
||||
align?: string;
|
||||
onLoad?: Function;
|
||||
includes?: Function;
|
||||
inline?: boolean
|
||||
width?: string | number
|
||||
height?: string | number
|
||||
horizontalFlip?: boolean
|
||||
verticalFlip?: boolean
|
||||
flip?: string
|
||||
rotate?: number | string
|
||||
color?: string
|
||||
horizontalAlign?: boolean
|
||||
verticalAlign?: boolean
|
||||
align?: string
|
||||
onLoad?: Function
|
||||
includes?: Function
|
||||
|
||||
// all icon
|
||||
style?: object;
|
||||
style?: object
|
||||
}
|
||||
|
@ -1,52 +1,52 @@
|
||||
import { App } from "vue";
|
||||
import axios from "axios";
|
||||
import { App } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
let config: object = {};
|
||||
const { VITE_PUBLIC_PATH } = import.meta.env;
|
||||
let config: object = {}
|
||||
const { VITE_PUBLIC_PATH } = import.meta.env
|
||||
|
||||
const setConfig = (cfg?: unknown) => {
|
||||
config = Object.assign(config, cfg);
|
||||
};
|
||||
config = Object.assign(config, cfg)
|
||||
}
|
||||
|
||||
const getConfig = (key?: string): ServerConfigs => {
|
||||
if (typeof key === "string") {
|
||||
const arr = key.split(".");
|
||||
if (typeof key === 'string') {
|
||||
const arr = key.split('.')
|
||||
if (arr && arr.length) {
|
||||
let data = config;
|
||||
let data = config
|
||||
arr.forEach(v => {
|
||||
if (data && typeof data[v] !== "undefined") {
|
||||
data = data[v];
|
||||
if (data && typeof data[v] !== 'undefined') {
|
||||
data = data[v]
|
||||
} else {
|
||||
data = null;
|
||||
data = null
|
||||
}
|
||||
});
|
||||
return data;
|
||||
})
|
||||
return data
|
||||
}
|
||||
}
|
||||
return config;
|
||||
};
|
||||
return config
|
||||
}
|
||||
|
||||
/** 获取项目动态全局配置 */
|
||||
export const getServerConfig = async (app: App): Promise<undefined> => {
|
||||
app.config.globalProperties.$config = getConfig();
|
||||
app.config.globalProperties.$config = getConfig()
|
||||
return axios({
|
||||
method: "get",
|
||||
method: 'get',
|
||||
url: `${VITE_PUBLIC_PATH}serverConfig.json`
|
||||
})
|
||||
.then(({ data: config }) => {
|
||||
let $config = app.config.globalProperties.$config;
|
||||
let $config = app.config.globalProperties.$config
|
||||
// 自动注入项目配置
|
||||
if (app && $config && typeof config === "object") {
|
||||
$config = Object.assign($config, config);
|
||||
app.config.globalProperties.$config = $config;
|
||||
if (app && $config && typeof config === 'object') {
|
||||
$config = Object.assign($config, config)
|
||||
app.config.globalProperties.$config = $config
|
||||
// 设置全局配置
|
||||
setConfig($config);
|
||||
setConfig($config)
|
||||
}
|
||||
return $config;
|
||||
return $config
|
||||
})
|
||||
.catch(() => {
|
||||
throw "请在public文件夹下添加serverConfig.json配置文件";
|
||||
});
|
||||
};
|
||||
throw '请在public文件夹下添加serverConfig.json配置文件'
|
||||
})
|
||||
}
|
||||
|
||||
export { getConfig, setConfig };
|
||||
export { getConfig, setConfig }
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { hasAuth } from "@/router/utils";
|
||||
import { Directive, type DirectiveBinding } from "vue";
|
||||
import { hasAuth } from '@/router/utils'
|
||||
import { Directive, type DirectiveBinding } from 'vue'
|
||||
|
||||
export const auth: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
const { value } = binding;
|
||||
const { value } = binding
|
||||
if (value) {
|
||||
!hasAuth(value) && el.parentNode?.removeChild(el);
|
||||
!hasAuth(value) && el.parentNode?.removeChild(el)
|
||||
} else {
|
||||
throw new Error("need auths! Like v-auth=\"['btn.add','btn.edit']\"");
|
||||
throw new Error("need auths! Like v-auth=\"['btn.add','btn.edit']\"")
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,27 +1,27 @@
|
||||
import { Directive, type DirectiveBinding, type VNode } from "vue";
|
||||
import elementResizeDetectorMaker from "element-resize-detector";
|
||||
import type { Erd } from "element-resize-detector";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import { Directive, type DirectiveBinding, type VNode } from 'vue'
|
||||
import elementResizeDetectorMaker from 'element-resize-detector'
|
||||
import type { Erd } from 'element-resize-detector'
|
||||
import { emitter } from '@/utils/mitt'
|
||||
|
||||
const erd: Erd = elementResizeDetectorMaker({
|
||||
strategy: "scroll"
|
||||
});
|
||||
strategy: 'scroll'
|
||||
})
|
||||
|
||||
export const resize: Directive = {
|
||||
mounted(el: HTMLElement, binding?: DirectiveBinding, vnode?: VNode) {
|
||||
erd.listenTo(el, elem => {
|
||||
const width = elem.offsetWidth;
|
||||
const height = elem.offsetHeight;
|
||||
const width = elem.offsetWidth
|
||||
const height = elem.offsetHeight
|
||||
if (binding?.instance) {
|
||||
emitter.emit("resize", { detail: { width, height } });
|
||||
emitter.emit('resize', { detail: { width, height } })
|
||||
} else {
|
||||
vnode.el.dispatchEvent(
|
||||
new CustomEvent("resize", { detail: { width, height } })
|
||||
);
|
||||
new CustomEvent('resize', { detail: { width, height } })
|
||||
)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
unmounted(el: HTMLElement) {
|
||||
erd.uninstall(el);
|
||||
erd.uninstall(el)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from "./auth";
|
||||
export * from "./elResizeDetector";
|
||||
export * from './auth'
|
||||
export * from './elResizeDetector'
|
||||
|
@ -1,42 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { useGlobal } from "@pureadmin/utils";
|
||||
import backTop from "@/assets/svg/back_top.svg?component";
|
||||
import { h, computed, Transition, defineComponent } from "vue";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import { useGlobal } from '@pureadmin/utils'
|
||||
import backTop from '@/assets/svg/back_top.svg?component'
|
||||
import { h, computed, Transition, defineComponent } from 'vue'
|
||||
import { usePermissionStoreHook } from '@/store/modules/permission'
|
||||
|
||||
const props = defineProps({
|
||||
fixedHeader: Boolean
|
||||
});
|
||||
})
|
||||
|
||||
const { $storage, $config } = useGlobal<GlobalPropertiesApi>();
|
||||
const { $storage, $config } = useGlobal<GlobalPropertiesApi>()
|
||||
|
||||
const keepAlive = computed(() => {
|
||||
return $config?.KeepAlive;
|
||||
});
|
||||
return $config?.KeepAlive
|
||||
})
|
||||
|
||||
const transitions = computed(() => {
|
||||
return route => {
|
||||
return route.meta.transition;
|
||||
};
|
||||
});
|
||||
return route.meta.transition
|
||||
}
|
||||
})
|
||||
|
||||
const hideTabs = computed(() => {
|
||||
return $storage?.configure.hideTabs;
|
||||
});
|
||||
return $storage?.configure.hideTabs
|
||||
})
|
||||
|
||||
const layout = computed(() => {
|
||||
return $storage?.layout.layout === "vertical";
|
||||
});
|
||||
return $storage?.layout.layout === 'vertical'
|
||||
})
|
||||
|
||||
const getSectionStyle = computed(() => {
|
||||
return [
|
||||
hideTabs.value && layout ? "padding-top: 48px;" : "",
|
||||
!hideTabs.value && layout ? "padding-top: 85px;" : "",
|
||||
hideTabs.value && !layout.value ? "padding-top: 48px" : "",
|
||||
!hideTabs.value && !layout.value ? "padding-top: 85px;" : "",
|
||||
props.fixedHeader ? "" : "padding-top: 0;"
|
||||
];
|
||||
});
|
||||
hideTabs.value && layout ? 'padding-top: 48px;' : '',
|
||||
!hideTabs.value && layout ? 'padding-top: 85px;' : '',
|
||||
hideTabs.value && !layout.value ? 'padding-top: 48px' : '',
|
||||
!hideTabs.value && !layout.value ? 'padding-top: 85px;' : '',
|
||||
props.fixedHeader ? '' : 'padding-top: 0;'
|
||||
]
|
||||
})
|
||||
|
||||
const transitionMain = defineComponent({
|
||||
render() {
|
||||
@ -46,23 +46,23 @@ const transitionMain = defineComponent({
|
||||
name:
|
||||
transitions.value(this.route) &&
|
||||
this.route.meta.transition.enterTransition
|
||||
? "pure-classes-transition"
|
||||
? 'pure-classes-transition'
|
||||
: (transitions.value(this.route) &&
|
||||
this.route.meta.transition.name) ||
|
||||
"fade-transform",
|
||||
'fade-transform',
|
||||
enterActiveClass:
|
||||
transitions.value(this.route) &&
|
||||
`animate__animated ${this.route.meta.transition.enterTransition}`,
|
||||
leaveActiveClass:
|
||||
transitions.value(this.route) &&
|
||||
`animate__animated ${this.route.meta.transition.leaveTransition}`,
|
||||
mode: "out-in",
|
||||
mode: 'out-in',
|
||||
appear: true
|
||||
},
|
||||
{
|
||||
default: () => [this.$slots.default()]
|
||||
}
|
||||
);
|
||||
)
|
||||
},
|
||||
props: {
|
||||
route: {
|
||||
@ -70,7 +70,7 @@ const transitionMain = defineComponent({
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import Search from "./search/index.vue";
|
||||
import Notice from "./notice/index.vue";
|
||||
import mixNav from "./sidebar/mixNav.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import Breadcrumb from "./sidebar/breadCrumb.vue";
|
||||
import topCollapse from "./sidebar/topCollapse.vue";
|
||||
import { useTranslationLang } from "../hooks/useTranslationLang";
|
||||
import globalization from "@/assets/svg/globalization.svg?component";
|
||||
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
||||
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||
import Check from "@iconify-icons/ep/check";
|
||||
import Search from './search/index.vue'
|
||||
import Notice from './notice/index.vue'
|
||||
import mixNav from './sidebar/mixNav.vue'
|
||||
import { useNav } from '@/layout/hooks/useNav'
|
||||
import Breadcrumb from './sidebar/breadCrumb.vue'
|
||||
import topCollapse from './sidebar/topCollapse.vue'
|
||||
import { useTranslationLang } from '../hooks/useTranslationLang'
|
||||
import globalization from '@/assets/svg/globalization.svg?component'
|
||||
import LogoutCircleRLine from '@iconify-icons/ri/logout-circle-r-line'
|
||||
import Setting from '@iconify-icons/ri/settings-3-line'
|
||||
import Check from '@iconify-icons/ep/check'
|
||||
|
||||
const {
|
||||
layout,
|
||||
@ -22,9 +22,9 @@ const {
|
||||
toggleSideBar,
|
||||
getDropdownItemStyle,
|
||||
getDropdownItemClass
|
||||
} = useNav();
|
||||
} = useNav()
|
||||
|
||||
const { t, locale, translationCh, translationEn } = useTranslationLang();
|
||||
const { t, locale, translationCh, translationEn } = useTranslationLang()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -98,7 +98,7 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
|
||||
:icon="LogoutCircleRLine"
|
||||
style="margin: 5px"
|
||||
/>
|
||||
{{ t("buttons.hsLoginOut") }}
|
||||
{{ t('buttons.hsLoginOut') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
|
@ -1,146 +1,146 @@
|
||||
export interface ListItem {
|
||||
avatar: string;
|
||||
title: string;
|
||||
datetime: string;
|
||||
type: string;
|
||||
description: string;
|
||||
status?: "" | "success" | "warning" | "info" | "danger";
|
||||
extra?: string;
|
||||
avatar: string
|
||||
title: string
|
||||
datetime: string
|
||||
type: string
|
||||
description: string
|
||||
status?: '' | 'success' | 'warning' | 'info' | 'danger'
|
||||
extra?: string
|
||||
}
|
||||
|
||||
export interface TabItem {
|
||||
key: string;
|
||||
name: string;
|
||||
list: ListItem[];
|
||||
key: string
|
||||
name: string
|
||||
list: ListItem[]
|
||||
}
|
||||
|
||||
export const noticesData: TabItem[] = [
|
||||
{
|
||||
key: "1",
|
||||
name: "通知",
|
||||
key: '1',
|
||||
name: '通知',
|
||||
list: [
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
|
||||
title: "你收到了 12 份新周报",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '你收到了 12 份新周报',
|
||||
datetime: '一年前',
|
||||
description: '',
|
||||
type: '1'
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
|
||||
title: "你推荐的 前端高手 已通过第三轮面试",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
|
||||
title: '你推荐的 前端高手 已通过第三轮面试',
|
||||
datetime: '一年前',
|
||||
description: '',
|
||||
type: '1'
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png",
|
||||
title: "这种模板可以区分多种通知类型",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
|
||||
title: '这种模板可以区分多种通知类型',
|
||||
datetime: '一年前',
|
||||
description: '',
|
||||
type: '1'
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
|
||||
title:
|
||||
"展示标题内容超过一行后的处理方式,如果内容超过1行将自动截断并支持tooltip显示完整标题。",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
'展示标题内容超过一行后的处理方式,如果内容超过1行将自动截断并支持tooltip显示完整标题。',
|
||||
datetime: '一年前',
|
||||
description: '',
|
||||
type: '1'
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
|
||||
title: "左侧图标用于区分不同的类型",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
|
||||
title: '左侧图标用于区分不同的类型',
|
||||
datetime: '一年前',
|
||||
description: '',
|
||||
type: '1'
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
|
||||
title: "左侧图标用于区分不同的类型",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
|
||||
title: '左侧图标用于区分不同的类型',
|
||||
datetime: '一年前',
|
||||
description: '',
|
||||
type: '1'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
name: "消息",
|
||||
key: '2',
|
||||
name: '消息',
|
||||
list: [
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
|
||||
title: "李白 评论了你",
|
||||
description: "长风破浪会有时,直挂云帆济沧海",
|
||||
datetime: "一年前",
|
||||
type: "2"
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '李白 评论了你',
|
||||
description: '长风破浪会有时,直挂云帆济沧海',
|
||||
datetime: '一年前',
|
||||
type: '2'
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
|
||||
title: "李白 回复了你",
|
||||
description: "行路难,行路难,多歧路,今安在。",
|
||||
datetime: "一年前",
|
||||
type: "2"
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '李白 回复了你',
|
||||
description: '行路难,行路难,多歧路,今安在。',
|
||||
datetime: '一年前',
|
||||
type: '2'
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
|
||||
title: "标题",
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '标题',
|
||||
description:
|
||||
"请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容",
|
||||
datetime: "一年前",
|
||||
type: "2"
|
||||
'请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容',
|
||||
datetime: '一年前',
|
||||
type: '2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
name: "代办",
|
||||
key: '3',
|
||||
name: '代办',
|
||||
list: [
|
||||
{
|
||||
avatar: "",
|
||||
title: "任务名称",
|
||||
description: "任务需要在 2022-11-16 20:00 前启动",
|
||||
datetime: "",
|
||||
extra: "未开始",
|
||||
status: "info",
|
||||
type: "3"
|
||||
avatar: '',
|
||||
title: '任务名称',
|
||||
description: '任务需要在 2022-11-16 20:00 前启动',
|
||||
datetime: '',
|
||||
extra: '未开始',
|
||||
status: 'info',
|
||||
type: '3'
|
||||
},
|
||||
{
|
||||
avatar: "",
|
||||
title: "第三方紧急代码变更",
|
||||
avatar: '',
|
||||
title: '第三方紧急代码变更',
|
||||
description:
|
||||
"一拳提交于 2022-11-16,需在 2022-11-18 前完成代码变更任务",
|
||||
datetime: "",
|
||||
extra: "马上到期",
|
||||
status: "danger",
|
||||
type: "3"
|
||||
'一拳提交于 2022-11-16,需在 2022-11-18 前完成代码变更任务',
|
||||
datetime: '',
|
||||
extra: '马上到期',
|
||||
status: 'danger',
|
||||
type: '3'
|
||||
},
|
||||
{
|
||||
avatar: "",
|
||||
title: "信息安全考试",
|
||||
description: "指派小仙于 2022-12-12 前完成更新并发布",
|
||||
datetime: "",
|
||||
extra: "已耗时 8 天",
|
||||
status: "warning",
|
||||
type: "3"
|
||||
avatar: '',
|
||||
title: '信息安全考试',
|
||||
description: '指派小仙于 2022-12-12 前完成更新并发布',
|
||||
datetime: '',
|
||||
extra: '已耗时 8 天',
|
||||
status: 'warning',
|
||||
type: '3'
|
||||
},
|
||||
{
|
||||
avatar: "",
|
||||
title: "vue-pure-admin 版本发布",
|
||||
description: "vue-pure-admin 版本发布",
|
||||
datetime: "",
|
||||
extra: "进行中",
|
||||
type: "3"
|
||||
avatar: '',
|
||||
title: 'vue-pure-admin 版本发布',
|
||||
description: 'vue-pure-admin 版本发布',
|
||||
datetime: '',
|
||||
extra: '进行中',
|
||||
type: '3'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
]
|
||||
|
@ -1,14 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { noticesData } from "./data";
|
||||
import NoticeList from "./noticeList.vue";
|
||||
import Bell from "@iconify-icons/ep/bell";
|
||||
import { ref } from 'vue'
|
||||
import { noticesData } from './data'
|
||||
import NoticeList from './noticeList.vue'
|
||||
import Bell from '@iconify-icons/ep/bell'
|
||||
|
||||
const noticesNum = ref(0);
|
||||
const notices = ref(noticesData);
|
||||
const activeKey = ref(noticesData[0].key);
|
||||
const noticesNum = ref(0)
|
||||
const notices = ref(noticesData)
|
||||
const activeKey = ref(noticesData[0].key)
|
||||
|
||||
notices.value.map(v => (noticesNum.value += v.list.length));
|
||||
notices.value.map(v => (noticesNum.value += v.list.length))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,49 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { ListItem } from "./data";
|
||||
import { ref, PropType, nextTick } from "vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { deviceDetection } from "@pureadmin/utils";
|
||||
import { ListItem } from './data'
|
||||
import { ref, PropType, nextTick } from 'vue'
|
||||
import { useNav } from '@/layout/hooks/useNav'
|
||||
import { deviceDetection } from '@pureadmin/utils'
|
||||
|
||||
const props = defineProps({
|
||||
noticeItem: {
|
||||
type: Object as PropType<ListItem>,
|
||||
default: () => {}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const titleRef = ref(null);
|
||||
const titleTooltip = ref(false);
|
||||
const descriptionRef = ref(null);
|
||||
const descriptionTooltip = ref(false);
|
||||
const { tooltipEffect } = useNav();
|
||||
const isMobile = deviceDetection();
|
||||
const titleRef = ref(null)
|
||||
const titleTooltip = ref(false)
|
||||
const descriptionRef = ref(null)
|
||||
const descriptionTooltip = ref(false)
|
||||
const { tooltipEffect } = useNav()
|
||||
const isMobile = deviceDetection()
|
||||
|
||||
function hoverTitle() {
|
||||
nextTick(() => {
|
||||
titleRef.value?.scrollWidth > titleRef.value?.clientWidth
|
||||
? (titleTooltip.value = true)
|
||||
: (titleTooltip.value = false);
|
||||
});
|
||||
: (titleTooltip.value = false)
|
||||
})
|
||||
}
|
||||
|
||||
function hoverDescription(event, description) {
|
||||
// currentWidth 为文本在页面中所占的宽度,创建标签,加入到页面,获取currentWidth ,最后在移除
|
||||
const tempTag = document.createElement("span");
|
||||
tempTag.innerText = description;
|
||||
tempTag.className = "getDescriptionWidth";
|
||||
document.querySelector("body").appendChild(tempTag);
|
||||
const tempTag = document.createElement('span')
|
||||
tempTag.innerText = description
|
||||
tempTag.className = 'getDescriptionWidth'
|
||||
document.querySelector('body').appendChild(tempTag)
|
||||
const currentWidth = (
|
||||
document.querySelector(".getDescriptionWidth") as HTMLSpanElement
|
||||
).offsetWidth;
|
||||
document.querySelector(".getDescriptionWidth").remove();
|
||||
document.querySelector('.getDescriptionWidth') as HTMLSpanElement
|
||||
).offsetWidth
|
||||
document.querySelector('.getDescriptionWidth').remove()
|
||||
|
||||
// cellWidth为容器的宽度
|
||||
const cellWidth = event.target.offsetWidth;
|
||||
const cellWidth = event.target.offsetWidth
|
||||
|
||||
// 当文本宽度大于容器宽度两倍时,代表文本显示超过两行
|
||||
currentWidth > 2 * cellWidth
|
||||
? (descriptionTooltip.value = true)
|
||||
: (descriptionTooltip.value = false);
|
||||
: (descriptionTooltip.value = false)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import { ListItem } from "./data";
|
||||
import NoticeItem from "./noticeItem.vue";
|
||||
import { PropType } from 'vue'
|
||||
import { ListItem } from './data'
|
||||
import NoticeItem from './noticeItem.vue'
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array as PropType<Array<ListItem>>,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,35 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import Close from "@iconify-icons/ep/close";
|
||||
import { ref, computed } from 'vue'
|
||||
import { emitter } from '@/utils/mitt'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import Close from '@iconify-icons/ep/close'
|
||||
|
||||
const target = ref(null);
|
||||
const show = ref<Boolean>(false);
|
||||
const target = ref(null)
|
||||
const show = ref<Boolean>(false)
|
||||
|
||||
const iconClass = computed(() => {
|
||||
return [
|
||||
"mr-[20px]",
|
||||
"outline-none",
|
||||
"width-[20px]",
|
||||
"height-[20px]",
|
||||
"rounded-[4px]",
|
||||
"cursor-pointer",
|
||||
"transition-colors",
|
||||
"hover:bg-[#0000000f]",
|
||||
"dark:hover:bg-[#ffffff1f]",
|
||||
"dark:hover:text-[#ffffffd9]"
|
||||
];
|
||||
});
|
||||
'mr-[20px]',
|
||||
'outline-none',
|
||||
'width-[20px]',
|
||||
'height-[20px]',
|
||||
'rounded-[4px]',
|
||||
'cursor-pointer',
|
||||
'transition-colors',
|
||||
'hover:bg-[#0000000f]',
|
||||
'dark:hover:bg-[#ffffff1f]',
|
||||
'dark:hover:text-[#ffffffd9]'
|
||||
]
|
||||
})
|
||||
|
||||
onClickOutside(target, (event: any) => {
|
||||
if (event.clientX > target.value.offsetLeft) return;
|
||||
show.value = false;
|
||||
});
|
||||
if (event.clientX > target.value.offsetLeft) return
|
||||
show.value = false
|
||||
})
|
||||
|
||||
emitter.on("openPanel", () => {
|
||||
show.value = true;
|
||||
});
|
||||
emitter.on('openPanel', () => {
|
||||
show.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -17,10 +17,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
|
||||
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
|
||||
import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
|
||||
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
||||
import ArrowUpLine from '@iconify-icons/ri/arrow-up-line'
|
||||
import ArrowDownLine from '@iconify-icons/ri/arrow-down-line'
|
||||
import mdiKeyboardEsc from '@/assets/svg/keyboard_esc.svg?component'
|
||||
import enterOutlined from '@/assets/svg/enter_outlined.svg?component'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -1,127 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from "vue-router";
|
||||
import { cloneDeep } from "@pureadmin/utils";
|
||||
import SearchResult from "./SearchResult.vue";
|
||||
import SearchFooter from "./SearchFooter.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { ref, computed, shallowRef } from "vue";
|
||||
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import Search from "@iconify-icons/ep/search";
|
||||
import { useRouter } from 'vue-router'
|
||||
import { cloneDeep } from '@pureadmin/utils'
|
||||
import SearchResult from './SearchResult.vue'
|
||||
import SearchFooter from './SearchFooter.vue'
|
||||
import { useNav } from '@/layout/hooks/useNav'
|
||||
import { transformI18n } from '@/plugins/i18n'
|
||||
import { ref, computed, shallowRef } from 'vue'
|
||||
import { useDebounceFn, onKeyStroke } from '@vueuse/core'
|
||||
import { usePermissionStoreHook } from '@/store/modules/permission'
|
||||
import Search from '@iconify-icons/ep/search'
|
||||
|
||||
interface Props {
|
||||
/** 弹窗显隐 */
|
||||
value: boolean;
|
||||
value: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update:value", val: boolean): void;
|
||||
(e: 'update:value', val: boolean): void
|
||||
}
|
||||
|
||||
const { device } = useNav();
|
||||
const emit = defineEmits<Emits>();
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
const router = useRouter();
|
||||
const { device } = useNav()
|
||||
const emit = defineEmits<Emits>()
|
||||
const props = withDefaults(defineProps<Props>(), {})
|
||||
const router = useRouter()
|
||||
|
||||
const keyword = ref("");
|
||||
const activePath = ref("");
|
||||
const inputRef = ref<HTMLInputElement | null>(null);
|
||||
const resultOptions = shallowRef([]);
|
||||
const handleSearch = useDebounceFn(search, 300);
|
||||
const keyword = ref('')
|
||||
const activePath = ref('')
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
const resultOptions = shallowRef([])
|
||||
const handleSearch = useDebounceFn(search, 300)
|
||||
|
||||
/** 菜单树形结构 */
|
||||
const menusData = computed(() => {
|
||||
return cloneDeep(usePermissionStoreHook().wholeMenus);
|
||||
});
|
||||
return cloneDeep(usePermissionStoreHook().wholeMenus)
|
||||
})
|
||||
|
||||
const show = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
return props.value
|
||||
},
|
||||
set(val: boolean) {
|
||||
emit("update:value", val);
|
||||
emit('update:value', val)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
/** 将菜单树形结构扁平化为一维数组,用于菜单查询 */
|
||||
function flatTree(arr) {
|
||||
const res = [];
|
||||
const res = []
|
||||
function deep(arr) {
|
||||
arr.forEach(item => {
|
||||
res.push(item);
|
||||
item.children && deep(item.children);
|
||||
});
|
||||
res.push(item)
|
||||
item.children && deep(item.children)
|
||||
})
|
||||
}
|
||||
deep(arr);
|
||||
return res;
|
||||
deep(arr)
|
||||
return res
|
||||
}
|
||||
|
||||
/** 查询 */
|
||||
function search() {
|
||||
const flatMenusData = flatTree(menusData.value);
|
||||
const flatMenusData = flatTree(menusData.value)
|
||||
resultOptions.value = flatMenusData.filter(
|
||||
menu =>
|
||||
keyword.value &&
|
||||
transformI18n(menu.meta?.title)
|
||||
.toLocaleLowerCase()
|
||||
.includes(keyword.value.toLocaleLowerCase().trim())
|
||||
);
|
||||
)
|
||||
if (resultOptions.value?.length > 0) {
|
||||
activePath.value = resultOptions.value[0].path;
|
||||
activePath.value = resultOptions.value[0].path
|
||||
} else {
|
||||
activePath.value = "";
|
||||
activePath.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
show.value = false;
|
||||
show.value = false
|
||||
/** 延时处理防止用户看到某些操作 */
|
||||
setTimeout(() => {
|
||||
resultOptions.value = [];
|
||||
keyword.value = "";
|
||||
}, 200);
|
||||
resultOptions.value = []
|
||||
keyword.value = ''
|
||||
}, 200)
|
||||
}
|
||||
|
||||
/** key up */
|
||||
function handleUp() {
|
||||
const { length } = resultOptions.value;
|
||||
if (length === 0) return;
|
||||
const { length } = resultOptions.value
|
||||
if (length === 0) return
|
||||
const index = resultOptions.value.findIndex(
|
||||
item => item.path === activePath.value
|
||||
);
|
||||
)
|
||||
if (index === 0) {
|
||||
activePath.value = resultOptions.value[length - 1].path;
|
||||
activePath.value = resultOptions.value[length - 1].path
|
||||
} else {
|
||||
activePath.value = resultOptions.value[index - 1].path;
|
||||
activePath.value = resultOptions.value[index - 1].path
|
||||
}
|
||||
}
|
||||
|
||||
/** key down */
|
||||
function handleDown() {
|
||||
const { length } = resultOptions.value;
|
||||
if (length === 0) return;
|
||||
const { length } = resultOptions.value
|
||||
if (length === 0) return
|
||||
const index = resultOptions.value.findIndex(
|
||||
item => item.path === activePath.value
|
||||
);
|
||||
)
|
||||
if (index + 1 === length) {
|
||||
activePath.value = resultOptions.value[0].path;
|
||||
activePath.value = resultOptions.value[0].path
|
||||
} else {
|
||||
activePath.value = resultOptions.value[index + 1].path;
|
||||
activePath.value = resultOptions.value[index + 1].path
|
||||
}
|
||||
}
|
||||
|
||||
/** key enter */
|
||||
function handleEnter() {
|
||||
const { length } = resultOptions.value;
|
||||
if (length === 0 || activePath.value === "") return;
|
||||
router.push(activePath.value);
|
||||
handleClose();
|
||||
const { length } = resultOptions.value
|
||||
if (length === 0 || activePath.value === '') return
|
||||
router.push(activePath.value)
|
||||
handleClose()
|
||||
}
|
||||
|
||||
onKeyStroke("Enter", handleEnter);
|
||||
onKeyStroke("ArrowUp", handleUp);
|
||||
onKeyStroke("ArrowDown", handleDown);
|
||||
onKeyStroke('Enter', handleEnter)
|
||||
onKeyStroke('ArrowUp', handleUp)
|
||||
onKeyStroke('ArrowDown', handleDown)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,61 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
||||
import Bookmark2Line from "@iconify-icons/ri/bookmark-2-line";
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useEpThemeStoreHook } from '@/store/modules/epTheme'
|
||||
import { useRenderIcon } from '@/components/ReIcon/src/hooks'
|
||||
import enterOutlined from '@/assets/svg/enter_outlined.svg?component'
|
||||
import Bookmark2Line from '@iconify-icons/ri/bookmark-2-line'
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n()
|
||||
|
||||
interface optionsItem {
|
||||
path: string;
|
||||
path: string
|
||||
meta?: {
|
||||
icon?: string;
|
||||
title?: string;
|
||||
};
|
||||
icon?: string
|
||||
title?: string
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
options: Array<optionsItem>;
|
||||
value: string
|
||||
options: Array<optionsItem>
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update:value", val: string): void;
|
||||
(e: "enter"): void;
|
||||
(e: 'update:value', val: string): void
|
||||
(e: 'enter'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
const emit = defineEmits<Emits>();
|
||||
const props = withDefaults(defineProps<Props>(), {})
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const itemStyle = computed(() => {
|
||||
return item => {
|
||||
return {
|
||||
background:
|
||||
item?.path === active.value ? useEpThemeStoreHook().epThemeColor : "",
|
||||
color: item.path === active.value ? "#fff" : "",
|
||||
fontSize: item.path === active.value ? "16px" : "14px"
|
||||
};
|
||||
};
|
||||
});
|
||||
item?.path === active.value ? useEpThemeStoreHook().epThemeColor : '',
|
||||
color: item.path === active.value ? '#fff' : '',
|
||||
fontSize: item.path === active.value ? '16px' : '14px'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const active = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
return props.value
|
||||
},
|
||||
set(val: string) {
|
||||
emit("update:value", val);
|
||||
emit('update:value', val)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
/** 鼠标移入 */
|
||||
async function handleMouse(item) {
|
||||
active.value = item.path;
|
||||
active.value = item.path
|
||||
}
|
||||
|
||||
function handleTo() {
|
||||
emit("enter");
|
||||
emit('enter')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
import SearchModal from "./SearchModal.vue";
|
||||
import SearchModal from './SearchModal.vue'
|
||||
|
||||
export { SearchModal };
|
||||
export { SearchModal }
|
||||
|
@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { SearchModal } from "./components";
|
||||
import { useBoolean } from "../../hooks/useBoolean";
|
||||
import Search from "@iconify-icons/ep/search";
|
||||
import { SearchModal } from './components'
|
||||
import { useBoolean } from '../../hooks/useBoolean'
|
||||
import Search from '@iconify-icons/ep/search'
|
||||
|
||||
const { bool: show, toggle } = useBoolean();
|
||||
const { bool: show, toggle } = useBoolean()
|
||||
function handleSearch() {
|
||||
toggle();
|
||||
toggle()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -7,40 +7,40 @@ import {
|
||||
computed,
|
||||
nextTick,
|
||||
onBeforeMount
|
||||
} from "vue";
|
||||
} from 'vue'
|
||||
import {
|
||||
useDark,
|
||||
debounce,
|
||||
useGlobal,
|
||||
storageLocal,
|
||||
storageSession
|
||||
} from "@pureadmin/utils";
|
||||
import { getConfig } from "@/config";
|
||||
import { useRouter } from "vue-router";
|
||||
import panel from "../panel/index.vue";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import { resetRouter } from "@/router";
|
||||
import { removeToken } from "@/utils/auth";
|
||||
import { routerArrays } from "@/layout/types";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { useAppStoreHook } from "@/store/modules/app";
|
||||
import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
||||
} from '@pureadmin/utils'
|
||||
import { getConfig } from '@/config'
|
||||
import { useRouter } from 'vue-router'
|
||||
import panel from '../panel/index.vue'
|
||||
import { emitter } from '@/utils/mitt'
|
||||
import { resetRouter } from '@/router'
|
||||
import { removeToken } from '@/utils/auth'
|
||||
import { routerArrays } from '@/layout/types'
|
||||
import { useNav } from '@/layout/hooks/useNav'
|
||||
import { useAppStoreHook } from '@/store/modules/app'
|
||||
import { toggleTheme } from '@pureadmin/theme/dist/browser-utils'
|
||||
import { useMultiTagsStoreHook } from '@/store/modules/multiTags'
|
||||
import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange'
|
||||
|
||||
import dayIcon from "@/assets/svg/day.svg?component";
|
||||
import darkIcon from "@/assets/svg/dark.svg?component";
|
||||
import Check from "@iconify-icons/ep/check";
|
||||
import Logout from "@iconify-icons/ri/logout-circle-r-line";
|
||||
import dayIcon from '@/assets/svg/day.svg?component'
|
||||
import darkIcon from '@/assets/svg/dark.svg?component'
|
||||
import Check from '@iconify-icons/ep/check'
|
||||
import Logout from '@iconify-icons/ri/logout-circle-r-line'
|
||||
|
||||
const router = useRouter();
|
||||
const { isDark } = useDark();
|
||||
const { device, tooltipEffect } = useNav();
|
||||
const { $storage } = useGlobal<GlobalPropertiesApi>();
|
||||
const router = useRouter()
|
||||
const { isDark } = useDark()
|
||||
const { device, tooltipEffect } = useNav()
|
||||
const { $storage } = useGlobal<GlobalPropertiesApi>()
|
||||
|
||||
const mixRef = ref();
|
||||
const verticalRef = ref();
|
||||
const horizontalRef = ref();
|
||||
const mixRef = ref()
|
||||
const verticalRef = ref()
|
||||
const horizontalRef = ref()
|
||||
|
||||
const {
|
||||
dataTheme,
|
||||
@ -49,22 +49,22 @@ const {
|
||||
dataThemeChange,
|
||||
setEpThemeColor,
|
||||
setLayoutThemeColor
|
||||
} = useDataThemeChange();
|
||||
} = useDataThemeChange()
|
||||
|
||||
/* body添加layout属性,作用于src/style/sidebar.scss */
|
||||
if (unref(layoutTheme)) {
|
||||
const layout = unref(layoutTheme).layout;
|
||||
const theme = unref(layoutTheme).theme;
|
||||
const layout = unref(layoutTheme).layout
|
||||
const theme = unref(layoutTheme).theme
|
||||
toggleTheme({
|
||||
scopeName: `layout-theme-${theme}`
|
||||
});
|
||||
setLayoutModel(layout);
|
||||
})
|
||||
setLayoutModel(layout)
|
||||
}
|
||||
|
||||
/** 默认灵动模式 */
|
||||
const markValue = ref($storage.configure?.showModel ?? "smart");
|
||||
const markValue = ref($storage.configure?.showModel ?? 'smart')
|
||||
|
||||
const logoVal = ref($storage.configure?.showLogo ?? true);
|
||||
const logoVal = ref($storage.configure?.showLogo ?? true)
|
||||
|
||||
const settings = reactive({
|
||||
greyVal: $storage.configure.grey,
|
||||
@ -73,95 +73,91 @@ const settings = reactive({
|
||||
showLogo: $storage.configure.showLogo,
|
||||
showModel: $storage.configure.showModel,
|
||||
multiTagsCache: $storage.configure.multiTagsCache
|
||||
});
|
||||
})
|
||||
|
||||
const getThemeColorStyle = computed(() => {
|
||||
return color => {
|
||||
return { background: color };
|
||||
};
|
||||
});
|
||||
return { background: color }
|
||||
}
|
||||
})
|
||||
|
||||
/** 当网页为暗黑模式时不显示亮白色切换选项 */
|
||||
const showThemeColors = computed(() => {
|
||||
return themeColor => {
|
||||
return themeColor === "light" && isDark.value ? false : true;
|
||||
};
|
||||
});
|
||||
return themeColor === 'light' && isDark.value ? false : true
|
||||
}
|
||||
})
|
||||
|
||||
function storageConfigureChange<T>(key: string, val: T): void {
|
||||
const storageConfigure = $storage.configure;
|
||||
storageConfigure[key] = val;
|
||||
$storage.configure = storageConfigure;
|
||||
const storageConfigure = $storage.configure
|
||||
storageConfigure[key] = val
|
||||
$storage.configure = storageConfigure
|
||||
}
|
||||
|
||||
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
|
||||
const targetEl = target || document.body;
|
||||
let { className } = targetEl;
|
||||
className = className.replace(clsName, "").trim();
|
||||
targetEl.className = flag ? `${className} ${clsName} ` : className;
|
||||
const targetEl = target || document.body
|
||||
let { className } = targetEl
|
||||
className = className.replace(clsName, '').trim()
|
||||
targetEl.className = flag ? `${className} ${clsName} ` : className
|
||||
}
|
||||
|
||||
/** 灰色模式设置 */
|
||||
const greyChange = (value): void => {
|
||||
toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
|
||||
storageConfigureChange("grey", value);
|
||||
};
|
||||
toggleClass(settings.greyVal, 'html-grey', document.querySelector('html'))
|
||||
storageConfigureChange('grey', value)
|
||||
}
|
||||
|
||||
/** 色弱模式设置 */
|
||||
const weekChange = (value): void => {
|
||||
toggleClass(
|
||||
settings.weakVal,
|
||||
"html-weakness",
|
||||
document.querySelector("html")
|
||||
);
|
||||
storageConfigureChange("weak", value);
|
||||
};
|
||||
toggleClass(settings.weakVal, 'html-weakness', document.querySelector('html'))
|
||||
storageConfigureChange('weak', value)
|
||||
}
|
||||
|
||||
const tagsChange = () => {
|
||||
const showVal = settings.tabsVal;
|
||||
storageConfigureChange("hideTabs", showVal);
|
||||
emitter.emit("tagViewsChange", showVal as unknown as string);
|
||||
};
|
||||
const showVal = settings.tabsVal
|
||||
storageConfigureChange('hideTabs', showVal)
|
||||
emitter.emit('tagViewsChange', showVal as unknown as string)
|
||||
}
|
||||
|
||||
const multiTagsCacheChange = () => {
|
||||
const multiTagsCache = settings.multiTagsCache;
|
||||
storageConfigureChange("multiTagsCache", multiTagsCache);
|
||||
useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
|
||||
};
|
||||
const multiTagsCache = settings.multiTagsCache
|
||||
storageConfigureChange('multiTagsCache', multiTagsCache)
|
||||
useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache)
|
||||
}
|
||||
|
||||
/** 清空缓存并返回登录页 */
|
||||
function onReset() {
|
||||
removeToken();
|
||||
storageLocal().clear();
|
||||
storageSession().clear();
|
||||
const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
|
||||
useAppStoreHook().setLayout(Layout);
|
||||
setEpThemeColor(EpThemeColor);
|
||||
useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
|
||||
toggleClass(Grey, "html-grey", document.querySelector("html"));
|
||||
toggleClass(Weak, "html-weakness", document.querySelector("html"));
|
||||
router.push("/login");
|
||||
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
|
||||
resetRouter();
|
||||
removeToken()
|
||||
storageLocal().clear()
|
||||
storageSession().clear()
|
||||
const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig()
|
||||
useAppStoreHook().setLayout(Layout)
|
||||
setEpThemeColor(EpThemeColor)
|
||||
useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache)
|
||||
toggleClass(Grey, 'html-grey', document.querySelector('html'))
|
||||
toggleClass(Weak, 'html-weakness', document.querySelector('html'))
|
||||
router.push('/login')
|
||||
useMultiTagsStoreHook().handleTags('equal', [...routerArrays])
|
||||
resetRouter()
|
||||
}
|
||||
|
||||
function onChange(label) {
|
||||
storageConfigureChange("showModel", label);
|
||||
emitter.emit("tagViewsShowModel", label);
|
||||
storageConfigureChange('showModel', label)
|
||||
emitter.emit('tagViewsShowModel', label)
|
||||
}
|
||||
|
||||
/** 侧边栏Logo */
|
||||
function logoChange() {
|
||||
unref(logoVal)
|
||||
? storageConfigureChange("showLogo", true)
|
||||
: storageConfigureChange("showLogo", false);
|
||||
emitter.emit("logoChange", unref(logoVal));
|
||||
? storageConfigureChange('showLogo', true)
|
||||
: storageConfigureChange('showLogo', false)
|
||||
emitter.emit('logoChange', unref(logoVal))
|
||||
}
|
||||
|
||||
function setFalse(Doms): any {
|
||||
Doms.forEach(v => {
|
||||
toggleClass(false, "is-select", unref(v));
|
||||
});
|
||||
toggleClass(false, 'is-select', unref(v))
|
||||
})
|
||||
}
|
||||
|
||||
/** 主题色 激活选择项 */
|
||||
@ -169,65 +165,65 @@ const getThemeColor = computed(() => {
|
||||
return current => {
|
||||
if (
|
||||
current === layoutTheme.value.theme &&
|
||||
layoutTheme.value.theme !== "light"
|
||||
layoutTheme.value.theme !== 'light'
|
||||
) {
|
||||
return "#fff";
|
||||
return '#fff'
|
||||
} else if (
|
||||
current === layoutTheme.value.theme &&
|
||||
layoutTheme.value.theme === "light"
|
||||
layoutTheme.value.theme === 'light'
|
||||
) {
|
||||
return "#1d2b45";
|
||||
return '#1d2b45'
|
||||
} else {
|
||||
return "transparent";
|
||||
return 'transparent'
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
/** 设置导航模式 */
|
||||
function setLayoutModel(layout: string) {
|
||||
layoutTheme.value.layout = layout;
|
||||
window.document.body.setAttribute("layout", layout);
|
||||
layoutTheme.value.layout = layout
|
||||
window.document.body.setAttribute('layout', layout)
|
||||
$storage.layout = {
|
||||
layout,
|
||||
theme: layoutTheme.value.theme,
|
||||
darkMode: $storage.layout?.darkMode,
|
||||
sidebarStatus: $storage.layout?.sidebarStatus,
|
||||
epThemeColor: $storage.layout?.epThemeColor
|
||||
};
|
||||
useAppStoreHook().setLayout(layout);
|
||||
}
|
||||
useAppStoreHook().setLayout(layout)
|
||||
}
|
||||
|
||||
watch($storage, ({ layout }) => {
|
||||
switch (layout["layout"]) {
|
||||
case "vertical":
|
||||
toggleClass(true, "is-select", unref(verticalRef));
|
||||
debounce(setFalse([horizontalRef]), 50);
|
||||
debounce(setFalse([mixRef]), 50);
|
||||
break;
|
||||
case "horizontal":
|
||||
toggleClass(true, "is-select", unref(horizontalRef));
|
||||
debounce(setFalse([verticalRef]), 50);
|
||||
debounce(setFalse([mixRef]), 50);
|
||||
break;
|
||||
case "mix":
|
||||
toggleClass(true, "is-select", unref(mixRef));
|
||||
debounce(setFalse([verticalRef]), 50);
|
||||
debounce(setFalse([horizontalRef]), 50);
|
||||
break;
|
||||
switch (layout['layout']) {
|
||||
case 'vertical':
|
||||
toggleClass(true, 'is-select', unref(verticalRef))
|
||||
debounce(setFalse([horizontalRef]), 50)
|
||||
debounce(setFalse([mixRef]), 50)
|
||||
break
|
||||
case 'horizontal':
|
||||
toggleClass(true, 'is-select', unref(horizontalRef))
|
||||
debounce(setFalse([verticalRef]), 50)
|
||||
debounce(setFalse([mixRef]), 50)
|
||||
break
|
||||
case 'mix':
|
||||
toggleClass(true, 'is-select', unref(mixRef))
|
||||
debounce(setFalse([verticalRef]), 50)
|
||||
debounce(setFalse([horizontalRef]), 50)
|
||||
break
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
dataThemeChange();
|
||||
dataThemeChange()
|
||||
/* 初始化项目配置 */
|
||||
nextTick(() => {
|
||||
settings.greyVal &&
|
||||
document.querySelector("html")?.setAttribute("class", "html-grey");
|
||||
document.querySelector('html')?.setAttribute('class', 'html-grey')
|
||||
settings.weakVal &&
|
||||
document.querySelector("html")?.setAttribute("class", "html-weakness");
|
||||
settings.tabsVal && tagsChange();
|
||||
});
|
||||
});
|
||||
document.querySelector('html')?.setAttribute('class', 'html-weakness')
|
||||
settings.tabsVal && tagsChange()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,98 +1,98 @@
|
||||
<script setup lang="ts">
|
||||
import { isEqual } from "@pureadmin/utils";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { ref, watch, onMounted, toRaw } from "vue";
|
||||
import { getParentPaths, findRouteByPath } from "@/router/utils";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { useRoute, useRouter, RouteLocationMatched } from "vue-router";
|
||||
import { isEqual } from '@pureadmin/utils'
|
||||
import { transformI18n } from '@/plugins/i18n'
|
||||
import { ref, watch, onMounted, toRaw } from 'vue'
|
||||
import { getParentPaths, findRouteByPath } from '@/router/utils'
|
||||
import { useMultiTagsStoreHook } from '@/store/modules/multiTags'
|
||||
import { useRoute, useRouter, RouteLocationMatched } from 'vue-router'
|
||||
|
||||
const route = useRoute();
|
||||
const levelList = ref([]);
|
||||
const router = useRouter();
|
||||
const routes: any = router.options.routes;
|
||||
const multiTags: any = useMultiTagsStoreHook().multiTags;
|
||||
const route = useRoute()
|
||||
const levelList = ref([])
|
||||
const router = useRouter()
|
||||
const routes: any = router.options.routes
|
||||
const multiTags: any = useMultiTagsStoreHook().multiTags
|
||||
|
||||
const isDashboard = (route: RouteLocationMatched): boolean | string => {
|
||||
const name = route && (route.name as string);
|
||||
if (!name) return false;
|
||||
return name.trim().toLocaleLowerCase() === "Welcome".toLocaleLowerCase();
|
||||
};
|
||||
const name = route && (route.name as string)
|
||||
if (!name) return false
|
||||
return name.trim().toLocaleLowerCase() === 'Welcome'.toLocaleLowerCase()
|
||||
}
|
||||
|
||||
const getBreadcrumb = (): void => {
|
||||
// 当前路由信息
|
||||
let currentRoute;
|
||||
let currentRoute
|
||||
|
||||
if (Object.keys(route.query).length > 0) {
|
||||
multiTags.forEach(item => {
|
||||
if (isEqual(route.query, item?.query)) {
|
||||
currentRoute = toRaw(item);
|
||||
currentRoute = toRaw(item)
|
||||
}
|
||||
});
|
||||
})
|
||||
} else if (Object.keys(route.params).length > 0) {
|
||||
multiTags.forEach(item => {
|
||||
if (isEqual(route.params, item?.params)) {
|
||||
currentRoute = toRaw(item);
|
||||
currentRoute = toRaw(item)
|
||||
}
|
||||
});
|
||||
})
|
||||
} else {
|
||||
currentRoute = findRouteByPath(router.currentRoute.value.path, multiTags);
|
||||
currentRoute = findRouteByPath(router.currentRoute.value.path, multiTags)
|
||||
}
|
||||
// 当前路由的父级路径组成的数组
|
||||
const parentRoutes = getParentPaths(router.currentRoute.value.path, routes);
|
||||
const parentRoutes = getParentPaths(router.currentRoute.value.path, routes)
|
||||
// 存放组成面包屑的数组
|
||||
let matched = [];
|
||||
let matched = []
|
||||
// 获取每个父级路径对应的路由信息
|
||||
parentRoutes.forEach(path => {
|
||||
if (path !== "/") matched.push(findRouteByPath(path, routes));
|
||||
});
|
||||
if (path !== '/') matched.push(findRouteByPath(path, routes))
|
||||
})
|
||||
|
||||
if (currentRoute?.path !== "/welcome") matched.push(currentRoute);
|
||||
if (currentRoute?.path !== '/welcome') matched.push(currentRoute)
|
||||
|
||||
if (!isDashboard(matched[0])) {
|
||||
matched = [
|
||||
{
|
||||
path: "/welcome",
|
||||
parentPath: "/",
|
||||
meta: { title: "menus.hshome" }
|
||||
path: '/welcome',
|
||||
parentPath: '/',
|
||||
meta: { title: 'menus.hshome' }
|
||||
} as unknown as RouteLocationMatched
|
||||
].concat(matched);
|
||||
].concat(matched)
|
||||
}
|
||||
|
||||
matched.forEach((item, index) => {
|
||||
if (currentRoute?.query || currentRoute?.params) return;
|
||||
if (currentRoute?.query || currentRoute?.params) return
|
||||
if (item?.children) {
|
||||
item.children.forEach(v => {
|
||||
if (v?.meta?.title === item?.meta?.title) {
|
||||
matched.splice(index, 1);
|
||||
matched.splice(index, 1)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
levelList.value = matched.filter(
|
||||
item => item?.meta && item?.meta.title !== false
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const handleLink = (item: RouteLocationMatched): void => {
|
||||
const { redirect, path } = item;
|
||||
const { redirect, path } = item
|
||||
if (redirect) {
|
||||
router.push(redirect as any);
|
||||
router.push(redirect as any)
|
||||
} else {
|
||||
router.push(path);
|
||||
router.push(path)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getBreadcrumb();
|
||||
});
|
||||
getBreadcrumb()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
getBreadcrumb();
|
||||
getBreadcrumb()
|
||||
}
|
||||
);
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,13 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { toRaw } from "vue";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { toRaw } from 'vue'
|
||||
import { useRenderIcon } from '@/components/ReIcon/src/hooks'
|
||||
|
||||
const props = defineProps({
|
||||
extraIcon: {
|
||||
type: String,
|
||||
default: ""
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,20 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import Search from "../search/index.vue";
|
||||
import Notice from "../notice/index.vue";
|
||||
import { ref, watch, nextTick } from "vue";
|
||||
import SidebarItem from "./sidebarItem.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { useTranslationLang } from "../../hooks/useTranslationLang";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import globalization from "@/assets/svg/globalization.svg?component";
|
||||
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
||||
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||
import Check from "@iconify-icons/ep/check";
|
||||
import Search from '../search/index.vue'
|
||||
import Notice from '../notice/index.vue'
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import SidebarItem from './sidebarItem.vue'
|
||||
import { useNav } from '@/layout/hooks/useNav'
|
||||
import { useTranslationLang } from '../../hooks/useTranslationLang'
|
||||
import { usePermissionStoreHook } from '@/store/modules/permission'
|
||||
import globalization from '@/assets/svg/globalization.svg?component'
|
||||
import LogoutCircleRLine from '@iconify-icons/ri/logout-circle-r-line'
|
||||
import Setting from '@iconify-icons/ri/settings-3-line'
|
||||
import Check from '@iconify-icons/ep/check'
|
||||
|
||||
const menuRef = ref();
|
||||
const menuRef = ref()
|
||||
|
||||
const { t, route, locale, translationCh, translationEn } =
|
||||
useTranslationLang(menuRef);
|
||||
useTranslationLang(menuRef)
|
||||
const {
|
||||
title,
|
||||
routers,
|
||||
@ -26,18 +26,18 @@ const {
|
||||
avatarsStyle,
|
||||
getDropdownItemStyle,
|
||||
getDropdownItemClass
|
||||
} = useNav();
|
||||
} = useNav()
|
||||
|
||||
nextTick(() => {
|
||||
menuRef.value?.handleResize();
|
||||
});
|
||||
menuRef.value?.handleResize()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
menuSelect(route.path, routers);
|
||||
menuSelect(route.path, routers)
|
||||
}
|
||||
);
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -115,7 +115,7 @@ watch(
|
||||
:icon="LogoutCircleRLine"
|
||||
style="margin: 5px"
|
||||
/>
|
||||
{{ t("buttons.hsLoginOut") }}
|
||||
{{ t('buttons.hsLoginOut') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
|
@ -1,42 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import MenuFold from "@iconify-icons/ri/menu-fold-fill";
|
||||
import { ref, computed } from 'vue'
|
||||
import { useNav } from '@/layout/hooks/useNav'
|
||||
import MenuFold from '@iconify-icons/ri/menu-fold-fill'
|
||||
|
||||
interface Props {
|
||||
isActive: boolean;
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isActive: false
|
||||
});
|
||||
})
|
||||
|
||||
const visible = ref(false);
|
||||
const { tooltipEffect } = useNav();
|
||||
const visible = ref(false)
|
||||
const { tooltipEffect } = useNav()
|
||||
|
||||
const iconClass = computed(() => {
|
||||
return [
|
||||
"ml-4",
|
||||
"mb-1",
|
||||
"w-[16px]",
|
||||
"h-[16px]",
|
||||
"inline-block",
|
||||
"align-middle",
|
||||
"text-primary",
|
||||
"cursor-pointer",
|
||||
"duration-[100ms]",
|
||||
"hover:text-primary",
|
||||
"dark:hover:!text-white"
|
||||
];
|
||||
});
|
||||
'ml-4',
|
||||
'mb-1',
|
||||
'w-[16px]',
|
||||
'h-[16px]',
|
||||
'inline-block',
|
||||
'align-middle',
|
||||
'text-primary',
|
||||
'cursor-pointer',
|
||||
'duration-[100ms]',
|
||||
'hover:text-primary',
|
||||
'dark:hover:!text-white'
|
||||
]
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "toggleClick"): void;
|
||||
}>();
|
||||
(e: 'toggleClick'): void
|
||||
}>()
|
||||
|
||||
const toggleClick = () => {
|
||||
emit("toggleClick");
|
||||
};
|
||||
emit('toggleClick')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { useNav } from '@/layout/hooks/useNav'
|
||||
|
||||
const props = defineProps({
|
||||
collapse: Boolean
|
||||
});
|
||||
})
|
||||
|
||||
const { title } = useNav();
|
||||
const { title } = useNav()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,24 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import extraIcon from "./extraIcon.vue";
|
||||
import Search from "../search/index.vue";
|
||||
import Notice from "../notice/index.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { ref, toRaw, watch, onMounted, nextTick } from "vue";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { getParentPaths, findRouteByPath } from "@/router/utils";
|
||||
import { useTranslationLang } from "../../hooks/useTranslationLang";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import globalization from "@/assets/svg/globalization.svg?component";
|
||||
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
||||
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||
import Check from "@iconify-icons/ep/check";
|
||||
import extraIcon from './extraIcon.vue'
|
||||
import Search from '../search/index.vue'
|
||||
import Notice from '../notice/index.vue'
|
||||
import { useNav } from '@/layout/hooks/useNav'
|
||||
import { transformI18n } from '@/plugins/i18n'
|
||||
import { ref, toRaw, watch, onMounted, nextTick } from 'vue'
|
||||
import { useRenderIcon } from '@/components/ReIcon/src/hooks'
|
||||
import { getParentPaths, findRouteByPath } from '@/router/utils'
|
||||
import { useTranslationLang } from '../../hooks/useTranslationLang'
|
||||
import { usePermissionStoreHook } from '@/store/modules/permission'
|
||||
import globalization from '@/assets/svg/globalization.svg?component'
|
||||
import LogoutCircleRLine from '@iconify-icons/ri/logout-circle-r-line'
|
||||
import Setting from '@iconify-icons/ri/settings-3-line'
|
||||
import Check from '@iconify-icons/ep/check'
|
||||
|
||||
const menuRef = ref();
|
||||
const defaultActive = ref(null);
|
||||
const menuRef = ref()
|
||||
const defaultActive = ref(null)
|
||||
|
||||
const { t, route, locale, translationCh, translationEn } =
|
||||
useTranslationLang(menuRef);
|
||||
useTranslationLang(menuRef)
|
||||
const {
|
||||
device,
|
||||
routers,
|
||||
@ -31,32 +31,32 @@ const {
|
||||
avatarsStyle,
|
||||
getDropdownItemStyle,
|
||||
getDropdownItemClass
|
||||
} = useNav();
|
||||
} = useNav()
|
||||
|
||||
function getDefaultActive(routePath) {
|
||||
const wholeMenus = usePermissionStoreHook().wholeMenus;
|
||||
const wholeMenus = usePermissionStoreHook().wholeMenus
|
||||
/** 当前路由的父级路径 */
|
||||
const parentRoutes = getParentPaths(routePath, wholeMenus)[0];
|
||||
const parentRoutes = getParentPaths(routePath, wholeMenus)[0]
|
||||
defaultActive.value = findRouteByPath(
|
||||
parentRoutes,
|
||||
wholeMenus
|
||||
)?.children[0]?.path;
|
||||
)?.children[0]?.path
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDefaultActive(route.path);
|
||||
});
|
||||
getDefaultActive(route.path)
|
||||
})
|
||||
|
||||
nextTick(() => {
|
||||
menuRef.value?.handleResize();
|
||||
});
|
||||
menuRef.value?.handleResize()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [route.path, usePermissionStoreHook().wholeMenus],
|
||||
() => {
|
||||
getDefaultActive(route.path);
|
||||
getDefaultActive(route.path)
|
||||
}
|
||||
);
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -147,7 +147,7 @@ watch(
|
||||
:icon="LogoutCircleRLine"
|
||||
style="margin: 5px"
|
||||
/>
|
||||
{{ t("buttons.hsLoginOut") }}
|
||||
{{ t('buttons.hsLoginOut') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
|
@ -1,19 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import path from "path";
|
||||
import { getConfig } from "@/config";
|
||||
import extraIcon from "./extraIcon.vue";
|
||||
import { childrenType } from "../../types";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { ref, toRaw, PropType, nextTick, computed, CSSProperties } from "vue";
|
||||
import path from 'path'
|
||||
import { getConfig } from '@/config'
|
||||
import extraIcon from './extraIcon.vue'
|
||||
import { childrenType } from '../../types'
|
||||
import { useNav } from '@/layout/hooks/useNav'
|
||||
import { transformI18n } from '@/plugins/i18n'
|
||||
import { useRenderIcon } from '@/components/ReIcon/src/hooks'
|
||||
import { ref, toRaw, PropType, nextTick, computed, CSSProperties } from 'vue'
|
||||
|
||||
import ArrowUp from "@iconify-icons/ep/arrow-up-bold";
|
||||
import EpArrowDown from "@iconify-icons/ep/arrow-down-bold";
|
||||
import ArrowLeft from "@iconify-icons/ep/arrow-left-bold";
|
||||
import ArrowRight from "@iconify-icons/ep/arrow-right-bold";
|
||||
import ArrowUp from '@iconify-icons/ep/arrow-up-bold'
|
||||
import EpArrowDown from '@iconify-icons/ep/arrow-down-bold'
|
||||
import ArrowLeft from '@iconify-icons/ep/arrow-left-bold'
|
||||
import ArrowRight from '@iconify-icons/ep/arrow-right-bold'
|
||||
|
||||
const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav();
|
||||
const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav()
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
@ -25,102 +25,102 @@ const props = defineProps({
|
||||
},
|
||||
basePath: {
|
||||
type: String,
|
||||
default: ""
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const getSpanStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
width: "100%",
|
||||
textAlign: "center"
|
||||
};
|
||||
});
|
||||
width: '100%',
|
||||
textAlign: 'center'
|
||||
}
|
||||
})
|
||||
|
||||
const getNoDropdownStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
};
|
||||
});
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
}
|
||||
})
|
||||
|
||||
const getMenuTextStyle = computed(() => {
|
||||
return {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
outline: "none"
|
||||
};
|
||||
});
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
outline: 'none'
|
||||
}
|
||||
})
|
||||
|
||||
const getsubMenuIconStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
margin:
|
||||
layout.value === "horizontal"
|
||||
? "0 5px 0 0"
|
||||
layout.value === 'horizontal'
|
||||
? '0 5px 0 0'
|
||||
: isCollapse.value
|
||||
? "0 auto"
|
||||
: "0 5px 0 0"
|
||||
};
|
||||
});
|
||||
? '0 auto'
|
||||
: '0 5px 0 0'
|
||||
}
|
||||
})
|
||||
|
||||
const getSubTextStyle = computed((): CSSProperties => {
|
||||
if (!isCollapse.value) {
|
||||
return {
|
||||
width: "210px",
|
||||
display: "inline-block",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
};
|
||||
width: '210px',
|
||||
display: 'inline-block',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
width: ""
|
||||
};
|
||||
width: ''
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const getSubMenuDivStyle = computed((): any => {
|
||||
return item => {
|
||||
return !isCollapse.value
|
||||
? {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
overflow: "hidden"
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
: {
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
textAlign:
|
||||
item?.parentId === null
|
||||
? "center"
|
||||
: layout.value === "mix" && item?.pathList?.length === 2
|
||||
? "center"
|
||||
: ""
|
||||
};
|
||||
};
|
||||
});
|
||||
? 'center'
|
||||
: layout.value === 'mix' && item?.pathList?.length === 2
|
||||
? 'center'
|
||||
: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const expandCloseIcon = computed(() => {
|
||||
if (!getConfig()?.MenuArrowIconNoTransition) return "";
|
||||
if (!getConfig()?.MenuArrowIconNoTransition) return ''
|
||||
return {
|
||||
"expand-close-icon": useRenderIcon(EpArrowDown),
|
||||
"expand-open-icon": useRenderIcon(ArrowUp),
|
||||
"collapse-close-icon": useRenderIcon(ArrowRight),
|
||||
"collapse-open-icon": useRenderIcon(ArrowLeft)
|
||||
};
|
||||
});
|
||||
'expand-close-icon': useRenderIcon(EpArrowDown),
|
||||
'expand-open-icon': useRenderIcon(ArrowUp),
|
||||
'collapse-close-icon': useRenderIcon(ArrowRight),
|
||||
'collapse-open-icon': useRenderIcon(ArrowLeft)
|
||||
}
|
||||
})
|
||||
|
||||
const onlyOneChild: childrenType = ref(null);
|
||||
const onlyOneChild: childrenType = ref(null)
|
||||
// 存放菜单是否存在showTooltip属性标识
|
||||
const hoverMenuMap = new WeakMap();
|
||||
const hoverMenuMap = new WeakMap()
|
||||
// 存储菜单文本dom元素
|
||||
const menuTextRef = ref(null);
|
||||
const menuTextRef = ref(null)
|
||||
|
||||
function hoverMenu(key) {
|
||||
// 如果当前菜单showTooltip属性已存在,退出计算
|
||||
if (hoverMenuMap.get(key)) return;
|
||||
if (hoverMenuMap.get(key)) return
|
||||
|
||||
nextTick(() => {
|
||||
// 如果文本内容的整体宽度大于其可视宽度,则文本溢出
|
||||
@ -130,23 +130,23 @@ function hoverMenu(key) {
|
||||
})
|
||||
: Object.assign(key, {
|
||||
showTooltip: false
|
||||
});
|
||||
hoverMenuMap.set(key, true);
|
||||
});
|
||||
})
|
||||
hoverMenuMap.set(key, true)
|
||||
})
|
||||
}
|
||||
|
||||
// 左侧菜单折叠后,当菜单没有图标时只显示第一个文字并加上省略号
|
||||
function overflowSlice(text, item?: any) {
|
||||
const newText =
|
||||
(text?.length > 1 ? text.toString().slice(0, 1) : text) + "...";
|
||||
(text?.length > 1 ? text.toString().slice(0, 1) : text) + '...'
|
||||
if (item && !(isCollapse.value && item?.parentId === null)) {
|
||||
return layout.value === "mix" &&
|
||||
return layout.value === 'mix' &&
|
||||
item?.pathList?.length === 2 &&
|
||||
isCollapse.value
|
||||
? newText
|
||||
: text;
|
||||
: text
|
||||
}
|
||||
return newText;
|
||||
return newText
|
||||
}
|
||||
|
||||
function hasOneShowingChild(
|
||||
@ -154,32 +154,32 @@ function hasOneShowingChild(
|
||||
parent: childrenType
|
||||
) {
|
||||
const showingChildren = children.filter((item: any) => {
|
||||
onlyOneChild.value = item;
|
||||
return true;
|
||||
});
|
||||
onlyOneChild.value = item
|
||||
return true
|
||||
})
|
||||
|
||||
if (showingChildren[0]?.meta?.showParent) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
if (showingChildren.length === 1) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
if (showingChildren.length === 0) {
|
||||
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
|
||||
return true;
|
||||
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
|
||||
return true
|
||||
}
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
function resolvePath(routePath) {
|
||||
const httpReg = /^http(s?):\/\//;
|
||||
const httpReg = /^http(s?):\/\//
|
||||
if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
|
||||
return routePath || props.basePath;
|
||||
return routePath || props.basePath
|
||||
} else {
|
||||
// 使用path.posix.resolve替代path.resolve 避免windows环境下使用electron出现盘符问题
|
||||
return path.posix.resolve(props.basePath, routePath);
|
||||
return path.posix.resolve(props.basePath, routePath)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,22 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import MenuFold from "@iconify-icons/ri/menu-fold-fill";
|
||||
import MenuUnfold from "@iconify-icons/ri/menu-unfold-fill";
|
||||
import MenuFold from '@iconify-icons/ri/menu-fold-fill'
|
||||
import MenuUnfold from '@iconify-icons/ri/menu-unfold-fill'
|
||||
|
||||
interface Props {
|
||||
isActive: boolean;
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isActive: false
|
||||
});
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "toggleClick"): void;
|
||||
}>();
|
||||
(e: 'toggleClick'): void
|
||||
}>()
|
||||
|
||||
const toggleClick = () => {
|
||||
emit("toggleClick");
|
||||
};
|
||||
emit('toggleClick')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,62 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import Logo from "./logo.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import SidebarItem from "./sidebarItem.vue";
|
||||
import leftCollapse from "./leftCollapse.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { ref, computed, watch, onBeforeMount } from "vue";
|
||||
import { findRouteByPath, getParentPaths } from "@/router/utils";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import Logo from './logo.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { emitter } from '@/utils/mitt'
|
||||
import SidebarItem from './sidebarItem.vue'
|
||||
import leftCollapse from './leftCollapse.vue'
|
||||
import { useNav } from '@/layout/hooks/useNav'
|
||||
import { storageLocal } from '@pureadmin/utils'
|
||||
import { ref, computed, watch, onBeforeMount } from 'vue'
|
||||
import { findRouteByPath, getParentPaths } from '@/router/utils'
|
||||
import { usePermissionStoreHook } from '@/store/modules/permission'
|
||||
|
||||
const route = useRoute();
|
||||
const route = useRoute()
|
||||
const showLogo = ref(
|
||||
storageLocal().getItem<StorageConfigs>("responsive-configure")?.showLogo ??
|
||||
storageLocal().getItem<StorageConfigs>('responsive-configure')?.showLogo ??
|
||||
true
|
||||
);
|
||||
)
|
||||
|
||||
const { routers, device, pureApp, isCollapse, menuSelect, toggleSideBar } =
|
||||
useNav();
|
||||
useNav()
|
||||
|
||||
const subMenuData = ref([]);
|
||||
const subMenuData = ref([])
|
||||
|
||||
const menuData = computed(() => {
|
||||
return pureApp.layout === "mix" && device.value !== "mobile"
|
||||
return pureApp.layout === 'mix' && device.value !== 'mobile'
|
||||
? subMenuData.value
|
||||
: usePermissionStoreHook().wholeMenus;
|
||||
});
|
||||
: usePermissionStoreHook().wholeMenus
|
||||
})
|
||||
|
||||
function getSubMenuData(path: string) {
|
||||
// path的上级路由组成的数组
|
||||
const parentPathArr = getParentPaths(
|
||||
path,
|
||||
usePermissionStoreHook().wholeMenus
|
||||
);
|
||||
)
|
||||
// 当前路由的父级路由信息
|
||||
const parenetRoute = findRouteByPath(
|
||||
parentPathArr[0] || path,
|
||||
usePermissionStoreHook().wholeMenus
|
||||
);
|
||||
if (!parenetRoute?.children) return;
|
||||
subMenuData.value = parenetRoute?.children;
|
||||
)
|
||||
if (!parenetRoute?.children) return
|
||||
subMenuData.value = parenetRoute?.children
|
||||
}
|
||||
|
||||
getSubMenuData(route.path);
|
||||
getSubMenuData(route.path)
|
||||
|
||||
onBeforeMount(() => {
|
||||
emitter.on("logoChange", key => {
|
||||
showLogo.value = key;
|
||||
});
|
||||
});
|
||||
emitter.on('logoChange', key => {
|
||||
showLogo.value = key
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [route.path, usePermissionStoreHook().wholeMenus],
|
||||
() => {
|
||||
getSubMenuData(route.path);
|
||||
menuSelect(route.path, routers);
|
||||
getSubMenuData(route.path)
|
||||
menuSelect(route.path, routers)
|
||||
}
|
||||
);
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,22 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { $t } from "@/plugins/i18n";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import { RouteConfigs } from "../../types";
|
||||
import { useTags } from "../../hooks/useTag";
|
||||
import { routerArrays } from "@/layout/types";
|
||||
import { isEqual, isAllEmpty } from "@pureadmin/utils";
|
||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||
import { ref, watch, unref, nextTick, onBeforeMount } from "vue";
|
||||
import { handleAliveRoute, delAliveRoutes } from "@/router/utils";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { useResizeObserver, useDebounceFn, useFullscreen } from "@vueuse/core";
|
||||
import { $t } from '@/plugins/i18n'
|
||||
import { emitter } from '@/utils/mitt'
|
||||
import { RouteConfigs } from '../../types'
|
||||
import { useTags } from '../../hooks/useTag'
|
||||
import { routerArrays } from '@/layout/types'
|
||||
import { isEqual, isAllEmpty } from '@pureadmin/utils'
|
||||
import { useSettingStoreHook } from '@/store/modules/settings'
|
||||
import { ref, watch, unref, nextTick, onBeforeMount } from 'vue'
|
||||
import { handleAliveRoute, delAliveRoutes } from '@/router/utils'
|
||||
import { useMultiTagsStoreHook } from '@/store/modules/multiTags'
|
||||
import { useResizeObserver, useDebounceFn, useFullscreen } from '@vueuse/core'
|
||||
|
||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
|
||||
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
|
||||
import ArrowDown from "@iconify-icons/ri/arrow-down-s-line";
|
||||
import ArrowRightSLine from "@iconify-icons/ri/arrow-right-s-line";
|
||||
import ArrowLeftSLine from "@iconify-icons/ri/arrow-left-s-line";
|
||||
import CloseBold from "@iconify-icons/ep/close-bold";
|
||||
import ExitFullscreen from '@iconify-icons/ri/fullscreen-exit-fill'
|
||||
import Fullscreen from '@iconify-icons/ri/fullscreen-fill'
|
||||
import ArrowDown from '@iconify-icons/ri/arrow-down-s-line'
|
||||
import ArrowRightSLine from '@iconify-icons/ri/arrow-right-s-line'
|
||||
import ArrowLeftSLine from '@iconify-icons/ri/arrow-left-s-line'
|
||||
import CloseBold from '@iconify-icons/ep/close-bold'
|
||||
|
||||
const {
|
||||
route,
|
||||
@ -44,52 +44,52 @@ const {
|
||||
onMouseleave,
|
||||
transformI18n,
|
||||
onContentFullScreen
|
||||
} = useTags();
|
||||
} = useTags()
|
||||
|
||||
const tabDom = ref();
|
||||
const containerDom = ref();
|
||||
const scrollbarDom = ref();
|
||||
const isShowArrow = ref(false);
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
const tabDom = ref()
|
||||
const containerDom = ref()
|
||||
const scrollbarDom = ref()
|
||||
const isShowArrow = ref(false)
|
||||
const { isFullscreen, toggle } = useFullscreen()
|
||||
|
||||
const dynamicTagView = () => {
|
||||
const index = multiTags.value.findIndex(item => {
|
||||
if (item.query) {
|
||||
return isEqual(route.query, item.query);
|
||||
return isEqual(route.query, item.query)
|
||||
} else if (item.params) {
|
||||
return isEqual(route.params, item.params);
|
||||
return isEqual(route.params, item.params)
|
||||
} else {
|
||||
return item.path === route.path;
|
||||
return item.path === route.path
|
||||
}
|
||||
});
|
||||
moveToView(index);
|
||||
};
|
||||
})
|
||||
moveToView(index)
|
||||
}
|
||||
|
||||
const moveToView = async (index: number): Promise<void> => {
|
||||
const tabNavPadding = 10;
|
||||
if (!instance.refs["dynamic" + index]) return;
|
||||
const tabItemEl = instance.refs["dynamic" + index][0];
|
||||
const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
|
||||
const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
|
||||
const tabNavPadding = 10
|
||||
if (!instance.refs['dynamic' + index]) return
|
||||
const tabItemEl = instance.refs['dynamic' + index][0]
|
||||
const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft
|
||||
const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth
|
||||
// 标签页导航栏可视长度(不包含溢出部分)
|
||||
const scrollbarDomWidth = scrollbarDom.value
|
||||
? scrollbarDom.value?.offsetWidth
|
||||
: 0;
|
||||
: 0
|
||||
|
||||
// 获取视图更新后dom
|
||||
await nextTick();
|
||||
await nextTick()
|
||||
|
||||
// 已有标签页总长度(包含溢出部分)
|
||||
const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
|
||||
const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0
|
||||
|
||||
scrollbarDomWidth <= tabDomWidth
|
||||
? (isShowArrow.value = true)
|
||||
: (isShowArrow.value = false);
|
||||
: (isShowArrow.value = false)
|
||||
if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
|
||||
translateX.value = 0;
|
||||
translateX.value = 0
|
||||
} else if (tabItemElOffsetLeft < -translateX.value) {
|
||||
// 标签在可视区域左侧
|
||||
translateX.value = -tabItemElOffsetLeft + tabNavPadding;
|
||||
translateX.value = -tabItemElOffsetLeft + tabNavPadding
|
||||
} else if (
|
||||
tabItemElOffsetLeft > -translateX.value &&
|
||||
tabItemElOffsetLeft + tabItemOffsetWidth <
|
||||
@ -102,89 +102,89 @@ const moveToView = async (index: number): Promise<void> => {
|
||||
tabItemOffsetWidth -
|
||||
tabItemElOffsetLeft -
|
||||
tabNavPadding
|
||||
);
|
||||
)
|
||||
} else {
|
||||
// 标签在可视区域右侧
|
||||
translateX.value = -(
|
||||
tabItemElOffsetLeft -
|
||||
(scrollbarDomWidth - tabNavPadding - tabItemOffsetWidth)
|
||||
);
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleScroll = (offset: number): void => {
|
||||
const scrollbarDomWidth = scrollbarDom.value
|
||||
? scrollbarDom.value?.offsetWidth
|
||||
: 0;
|
||||
const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0;
|
||||
: 0
|
||||
const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0
|
||||
if (offset > 0) {
|
||||
translateX.value = Math.min(0, translateX.value + offset);
|
||||
translateX.value = Math.min(0, translateX.value + offset)
|
||||
} else {
|
||||
if (scrollbarDomWidth < tabDomWidth) {
|
||||
if (translateX.value >= -(tabDomWidth - scrollbarDomWidth)) {
|
||||
translateX.value = Math.max(
|
||||
translateX.value + offset,
|
||||
scrollbarDomWidth - tabDomWidth
|
||||
);
|
||||
)
|
||||
}
|
||||
} else {
|
||||
translateX.value = 0;
|
||||
translateX.value = 0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function dynamicRouteTag(value: string, parentPath: string): void {
|
||||
const hasValue = multiTags.value.some(item => {
|
||||
return item.path === value;
|
||||
});
|
||||
return item.path === value
|
||||
})
|
||||
|
||||
function concatPath(arr: object[], value: string, parentPath: string) {
|
||||
if (!hasValue) {
|
||||
arr.forEach((arrItem: any) => {
|
||||
const pathConcat = parentPath + arrItem.path;
|
||||
const pathConcat = parentPath + arrItem.path
|
||||
if (arrItem.path === value || pathConcat === value) {
|
||||
useMultiTagsStoreHook().handleTags("push", {
|
||||
useMultiTagsStoreHook().handleTags('push', {
|
||||
path: value,
|
||||
parentPath: `/${parentPath.split("/")[1]}`,
|
||||
parentPath: `/${parentPath.split('/')[1]}`,
|
||||
meta: arrItem.meta,
|
||||
name: arrItem.name
|
||||
});
|
||||
})
|
||||
} else {
|
||||
if (arrItem.children && arrItem.children.length > 0) {
|
||||
concatPath(arrItem.children, value, parentPath);
|
||||
concatPath(arrItem.children, value, parentPath)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
concatPath(router.options.routes as any, value, parentPath);
|
||||
concatPath(router.options.routes as any, value, parentPath)
|
||||
}
|
||||
|
||||
/** 刷新路由 */
|
||||
function onFresh() {
|
||||
const { fullPath, query } = unref(route);
|
||||
const { fullPath, query } = unref(route)
|
||||
router.replace({
|
||||
path: "/redirect" + fullPath,
|
||||
path: '/redirect' + fullPath,
|
||||
query: query
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
// 存放被删除的缓存路由
|
||||
let delAliveRouteList = [];
|
||||
let delAliveRouteList = []
|
||||
const valueIndex: number = multiTags.value.findIndex((item: any) => {
|
||||
if (item.query) {
|
||||
if (item.path === obj.path) {
|
||||
return item.query === obj.query;
|
||||
return item.query === obj.query
|
||||
}
|
||||
} else if (item.params) {
|
||||
if (item.path === obj.path) {
|
||||
return item.params === obj.params;
|
||||
return item.params === obj.params
|
||||
}
|
||||
} else {
|
||||
return item.path === obj.path;
|
||||
return item.path === obj.path
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const spliceRoute = (
|
||||
startIndex?: number,
|
||||
@ -192,64 +192,64 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
other?: boolean
|
||||
): void => {
|
||||
if (other) {
|
||||
useMultiTagsStoreHook().handleTags("equal", [routerArrays[0], obj]);
|
||||
useMultiTagsStoreHook().handleTags('equal', [routerArrays[0], obj])
|
||||
} else {
|
||||
delAliveRouteList = useMultiTagsStoreHook().handleTags("splice", "", {
|
||||
delAliveRouteList = useMultiTagsStoreHook().handleTags('splice', '', {
|
||||
startIndex,
|
||||
length
|
||||
}) as any;
|
||||
}) as any
|
||||
}
|
||||
dynamicTagView()
|
||||
}
|
||||
dynamicTagView();
|
||||
};
|
||||
|
||||
if (tag === "other") {
|
||||
spliceRoute(1, 1, true);
|
||||
} else if (tag === "left") {
|
||||
spliceRoute(1, valueIndex - 1);
|
||||
} else if (tag === "right") {
|
||||
spliceRoute(valueIndex + 1, multiTags.value.length);
|
||||
if (tag === 'other') {
|
||||
spliceRoute(1, 1, true)
|
||||
} else if (tag === 'left') {
|
||||
spliceRoute(1, valueIndex - 1)
|
||||
} else if (tag === 'right') {
|
||||
spliceRoute(valueIndex + 1, multiTags.value.length)
|
||||
} else {
|
||||
// 从当前匹配到的路径中删除
|
||||
spliceRoute(valueIndex, 1);
|
||||
spliceRoute(valueIndex, 1)
|
||||
}
|
||||
const newRoute = useMultiTagsStoreHook().handleTags("slice");
|
||||
const newRoute = useMultiTagsStoreHook().handleTags('slice')
|
||||
if (current === route.path) {
|
||||
// 删除缓存路由
|
||||
tag
|
||||
? delAliveRoutes(delAliveRouteList)
|
||||
: handleAliveRoute(route.matched, "delete");
|
||||
: handleAliveRoute(route.matched, 'delete')
|
||||
// 如果删除当前激活tag就自动切换到最后一个tag
|
||||
if (tag === "left") return;
|
||||
if (tag === 'left') return
|
||||
if (newRoute[0]?.query) {
|
||||
router.push({ name: newRoute[0].name, query: newRoute[0].query });
|
||||
router.push({ name: newRoute[0].name, query: newRoute[0].query })
|
||||
} else if (newRoute[0]?.params) {
|
||||
router.push({ name: newRoute[0].name, params: newRoute[0].params });
|
||||
router.push({ name: newRoute[0].name, params: newRoute[0].params })
|
||||
} else {
|
||||
router.push({ path: newRoute[0].path });
|
||||
router.push({ path: newRoute[0].path })
|
||||
}
|
||||
} else {
|
||||
// 删除缓存路由
|
||||
tag ? delAliveRoutes(delAliveRouteList) : delAliveRoutes([obj]);
|
||||
if (!multiTags.value.length) return;
|
||||
if (multiTags.value.some(item => item.path === route.path)) return;
|
||||
tag ? delAliveRoutes(delAliveRouteList) : delAliveRoutes([obj])
|
||||
if (!multiTags.value.length) return
|
||||
if (multiTags.value.some(item => item.path === route.path)) return
|
||||
if (newRoute[0]?.query) {
|
||||
router.push({ name: newRoute[0].name, query: newRoute[0].query });
|
||||
router.push({ name: newRoute[0].name, query: newRoute[0].query })
|
||||
} else if (newRoute[0]?.params) {
|
||||
router.push({ name: newRoute[0].name, params: newRoute[0].params });
|
||||
router.push({ name: newRoute[0].name, params: newRoute[0].params })
|
||||
} else {
|
||||
router.push({ path: newRoute[0].path });
|
||||
router.push({ path: newRoute[0].path })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteMenu(item, tag?: string) {
|
||||
deleteDynamicTag(item, item.path, tag);
|
||||
deleteDynamicTag(item, item.path, tag)
|
||||
}
|
||||
|
||||
function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
||||
if (item && item.disabled) return;
|
||||
if (item && item.disabled) return
|
||||
|
||||
let selectTagRoute;
|
||||
let selectTagRoute
|
||||
if (selectRoute) {
|
||||
selectTagRoute = {
|
||||
path: selectRoute.path,
|
||||
@ -257,93 +257,93 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
||||
name: selectRoute.name,
|
||||
query: selectRoute?.query,
|
||||
params: selectRoute?.params
|
||||
};
|
||||
}
|
||||
} else {
|
||||
selectTagRoute = { path: route.path, meta: route.meta };
|
||||
selectTagRoute = { path: route.path, meta: route.meta }
|
||||
}
|
||||
|
||||
// 当前路由信息
|
||||
switch (key) {
|
||||
case 0:
|
||||
// 刷新路由
|
||||
onFresh();
|
||||
break;
|
||||
onFresh()
|
||||
break
|
||||
case 1:
|
||||
// 关闭当前标签页
|
||||
deleteMenu(selectTagRoute);
|
||||
break;
|
||||
deleteMenu(selectTagRoute)
|
||||
break
|
||||
case 2:
|
||||
// 关闭左侧标签页
|
||||
deleteMenu(selectTagRoute, "left");
|
||||
break;
|
||||
deleteMenu(selectTagRoute, 'left')
|
||||
break
|
||||
case 3:
|
||||
// 关闭右侧标签页
|
||||
deleteMenu(selectTagRoute, "right");
|
||||
break;
|
||||
deleteMenu(selectTagRoute, 'right')
|
||||
break
|
||||
case 4:
|
||||
// 关闭其他标签页
|
||||
deleteMenu(selectTagRoute, "other");
|
||||
break;
|
||||
deleteMenu(selectTagRoute, 'other')
|
||||
break
|
||||
case 5:
|
||||
// 关闭全部标签页
|
||||
useMultiTagsStoreHook().handleTags("splice", "", {
|
||||
useMultiTagsStoreHook().handleTags('splice', '', {
|
||||
startIndex: 1,
|
||||
length: multiTags.value.length
|
||||
});
|
||||
router.push("/welcome");
|
||||
break;
|
||||
})
|
||||
router.push('/welcome')
|
||||
break
|
||||
case 6:
|
||||
// 整体页面全屏
|
||||
toggle();
|
||||
toggle()
|
||||
setTimeout(() => {
|
||||
if (isFullscreen.value) {
|
||||
tagsViews[6].icon = ExitFullscreen;
|
||||
tagsViews[6].text = $t("buttons.hswholeExitFullScreen");
|
||||
tagsViews[6].icon = ExitFullscreen
|
||||
tagsViews[6].text = $t('buttons.hswholeExitFullScreen')
|
||||
} else {
|
||||
tagsViews[6].icon = Fullscreen;
|
||||
tagsViews[6].text = $t("buttons.hswholeFullScreen");
|
||||
tagsViews[6].icon = Fullscreen
|
||||
tagsViews[6].text = $t('buttons.hswholeFullScreen')
|
||||
}
|
||||
}, 100);
|
||||
break;
|
||||
}, 100)
|
||||
break
|
||||
case 7:
|
||||
// 内容区全屏
|
||||
onContentFullScreen();
|
||||
onContentFullScreen()
|
||||
setTimeout(() => {
|
||||
if (pureSetting.hiddenSideBar) {
|
||||
tagsViews[7].icon = ExitFullscreen;
|
||||
tagsViews[7].text = $t("buttons.hscontentExitFullScreen");
|
||||
tagsViews[7].icon = ExitFullscreen
|
||||
tagsViews[7].text = $t('buttons.hscontentExitFullScreen')
|
||||
} else {
|
||||
tagsViews[7].icon = Fullscreen;
|
||||
tagsViews[7].text = $t("buttons.hscontentFullScreen");
|
||||
tagsViews[7].icon = Fullscreen
|
||||
tagsViews[7].text = $t('buttons.hscontentFullScreen')
|
||||
}
|
||||
}, 100);
|
||||
break;
|
||||
}, 100)
|
||||
break
|
||||
}
|
||||
setTimeout(() => {
|
||||
showMenuModel(route.fullPath, route.query);
|
||||
});
|
||||
showMenuModel(route.fullPath, route.query)
|
||||
})
|
||||
}
|
||||
|
||||
function handleCommand(command: any) {
|
||||
const { key, item } = command;
|
||||
onClickDrop(key, item);
|
||||
const { key, item } = command
|
||||
onClickDrop(key, item)
|
||||
}
|
||||
|
||||
/** 触发右键中菜单的点击事件 */
|
||||
function selectTag(key, item) {
|
||||
onClickDrop(key, item, currentSelect.value);
|
||||
onClickDrop(key, item, currentSelect.value)
|
||||
}
|
||||
|
||||
function showMenus(value: boolean) {
|
||||
Array.of(1, 2, 3, 4, 5).forEach(v => {
|
||||
tagsViews[v].show = value;
|
||||
});
|
||||
tagsViews[v].show = value
|
||||
})
|
||||
}
|
||||
|
||||
function disabledMenus(value: boolean) {
|
||||
Array.of(1, 2, 3, 4, 5).forEach(v => {
|
||||
tagsViews[v].disabled = value;
|
||||
});
|
||||
tagsViews[v].disabled = value
|
||||
})
|
||||
}
|
||||
|
||||
/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是首页,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
|
||||
@ -352,19 +352,19 @@ function showMenuModel(
|
||||
query: object = {},
|
||||
refresh = false
|
||||
) {
|
||||
const allRoute = multiTags.value;
|
||||
const routeLength = multiTags.value.length;
|
||||
let currentIndex = -1;
|
||||
const allRoute = multiTags.value
|
||||
const routeLength = multiTags.value.length
|
||||
let currentIndex = -1
|
||||
if (isAllEmpty(query)) {
|
||||
currentIndex = allRoute.findIndex(v => v.path === currentPath);
|
||||
currentIndex = allRoute.findIndex(v => v.path === currentPath)
|
||||
} else {
|
||||
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
|
||||
currentIndex = allRoute.findIndex(v => isEqual(v.query, query))
|
||||
}
|
||||
|
||||
showMenus(true);
|
||||
showMenus(true)
|
||||
|
||||
if (refresh) {
|
||||
tagsViews[0].show = true;
|
||||
tagsViews[0].show = true
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,142 +373,142 @@ function showMenuModel(
|
||||
*/
|
||||
if (currentIndex === 1 && routeLength !== 2) {
|
||||
// 左侧的菜单是首页,右侧存在别的菜单
|
||||
tagsViews[2].show = false;
|
||||
tagsViews[2].show = false
|
||||
Array.of(1, 3, 4, 5).forEach(v => {
|
||||
tagsViews[v].disabled = false;
|
||||
});
|
||||
tagsViews[2].disabled = true;
|
||||
tagsViews[v].disabled = false
|
||||
})
|
||||
tagsViews[2].disabled = true
|
||||
} else if (currentIndex === 1 && routeLength === 2) {
|
||||
disabledMenus(false);
|
||||
disabledMenus(false)
|
||||
// 左侧的菜单是首页,右侧不存在别的菜单
|
||||
Array.of(2, 3, 4).forEach(v => {
|
||||
tagsViews[v].show = false;
|
||||
tagsViews[v].disabled = true;
|
||||
});
|
||||
tagsViews[v].show = false
|
||||
tagsViews[v].disabled = true
|
||||
})
|
||||
} else if (routeLength - 1 === currentIndex && currentIndex !== 0) {
|
||||
// 当前路由是所有路由中的最后一个
|
||||
tagsViews[3].show = false;
|
||||
tagsViews[3].show = false
|
||||
Array.of(1, 2, 4, 5).forEach(v => {
|
||||
tagsViews[v].disabled = false;
|
||||
});
|
||||
tagsViews[3].disabled = true;
|
||||
} else if (currentIndex === 0 || currentPath === "/redirect/welcome") {
|
||||
tagsViews[v].disabled = false
|
||||
})
|
||||
tagsViews[3].disabled = true
|
||||
} else if (currentIndex === 0 || currentPath === '/redirect/welcome') {
|
||||
// 当前路由为首页
|
||||
disabledMenus(true);
|
||||
disabledMenus(true)
|
||||
} else {
|
||||
disabledMenus(false);
|
||||
disabledMenus(false)
|
||||
}
|
||||
}
|
||||
|
||||
function openMenu(tag, e) {
|
||||
closeMenu();
|
||||
if (tag.path === "/welcome") {
|
||||
closeMenu()
|
||||
if (tag.path === '/welcome') {
|
||||
// 右键菜单为首页,只显示刷新
|
||||
showMenus(false);
|
||||
tagsViews[0].show = true;
|
||||
showMenus(false)
|
||||
tagsViews[0].show = true
|
||||
} else if (route.path !== tag.path && route.name !== tag.name) {
|
||||
// 右键菜单不匹配当前路由,隐藏刷新
|
||||
tagsViews[0].show = false;
|
||||
showMenuModel(tag.path, tag.query);
|
||||
tagsViews[0].show = false
|
||||
showMenuModel(tag.path, tag.query)
|
||||
} else if (
|
||||
// eslint-disable-next-line no-dupe-else-if
|
||||
multiTags.value.length === 2 &&
|
||||
route.path !== tag.path
|
||||
) {
|
||||
showMenus(true);
|
||||
showMenus(true)
|
||||
// 只有两个标签时不显示关闭其他标签页
|
||||
tagsViews[4].show = false;
|
||||
tagsViews[4].show = false
|
||||
} else if (route.path === tag.path) {
|
||||
// 右键当前激活的菜单
|
||||
showMenuModel(tag.path, tag.query, true);
|
||||
showMenuModel(tag.path, tag.query, true)
|
||||
}
|
||||
|
||||
currentSelect.value = tag;
|
||||
const menuMinWidth = 105;
|
||||
const offsetLeft = unref(containerDom).getBoundingClientRect().left;
|
||||
const offsetWidth = unref(containerDom).offsetWidth;
|
||||
const maxLeft = offsetWidth - menuMinWidth;
|
||||
const left = e.clientX - offsetLeft + 5;
|
||||
currentSelect.value = tag
|
||||
const menuMinWidth = 105
|
||||
const offsetLeft = unref(containerDom).getBoundingClientRect().left
|
||||
const offsetWidth = unref(containerDom).offsetWidth
|
||||
const maxLeft = offsetWidth - menuMinWidth
|
||||
const left = e.clientX - offsetLeft + 5
|
||||
if (left > maxLeft) {
|
||||
buttonLeft.value = maxLeft;
|
||||
buttonLeft.value = maxLeft
|
||||
} else {
|
||||
buttonLeft.value = left;
|
||||
buttonLeft.value = left
|
||||
}
|
||||
useSettingStoreHook().hiddenSideBar
|
||||
? (buttonTop.value = e.clientY)
|
||||
: (buttonTop.value = e.clientY - 40);
|
||||
: (buttonTop.value = e.clientY - 40)
|
||||
nextTick(() => {
|
||||
visible.value = true;
|
||||
});
|
||||
visible.value = true
|
||||
})
|
||||
}
|
||||
|
||||
/** 触发tags标签切换 */
|
||||
function tagOnClick(item) {
|
||||
const { name, path } = item;
|
||||
const { name, path } = item
|
||||
if (name) {
|
||||
if (item.query) {
|
||||
router.push({
|
||||
name,
|
||||
query: item.query
|
||||
});
|
||||
})
|
||||
} else if (item.params) {
|
||||
router.push({
|
||||
name,
|
||||
params: item.params
|
||||
});
|
||||
})
|
||||
} else {
|
||||
router.push({ name });
|
||||
router.push({ name })
|
||||
}
|
||||
} else {
|
||||
router.push({ path });
|
||||
router.push({ path })
|
||||
}
|
||||
// showMenuModel(item?.path, item?.query);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (!instance) return;
|
||||
if (!instance) return
|
||||
|
||||
// 根据当前路由初始化操作标签页的禁用状态
|
||||
showMenuModel(route.fullPath);
|
||||
showMenuModel(route.fullPath)
|
||||
|
||||
// 触发隐藏标签页
|
||||
emitter.on("tagViewsChange", (key: any) => {
|
||||
if (unref(showTags as any) === key) return;
|
||||
(showTags as any).value = key;
|
||||
});
|
||||
emitter.on('tagViewsChange', (key: any) => {
|
||||
if (unref(showTags as any) === key) return
|
||||
;(showTags as any).value = key
|
||||
})
|
||||
|
||||
// 改变标签风格
|
||||
emitter.on("tagViewsShowModel", key => {
|
||||
showModel.value = key;
|
||||
});
|
||||
emitter.on('tagViewsShowModel', key => {
|
||||
showModel.value = key
|
||||
})
|
||||
|
||||
// 接收侧边栏切换传递过来的参数
|
||||
emitter.on("changLayoutRoute", ({ indexPath, parentPath }) => {
|
||||
dynamicRouteTag(indexPath, parentPath);
|
||||
emitter.on('changLayoutRoute', ({ indexPath, parentPath }) => {
|
||||
dynamicRouteTag(indexPath, parentPath)
|
||||
setTimeout(() => {
|
||||
showMenuModel(indexPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
showMenuModel(indexPath)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
watch([route], () => {
|
||||
activeIndex.value = -1;
|
||||
dynamicTagView();
|
||||
});
|
||||
activeIndex.value = -1
|
||||
dynamicTagView()
|
||||
})
|
||||
|
||||
watch(isFullscreen, () => {
|
||||
tagsViews[6].icon = Fullscreen;
|
||||
tagsViews[6].text = $t("buttons.hswholeFullScreen");
|
||||
});
|
||||
tagsViews[6].icon = Fullscreen
|
||||
tagsViews[6].text = $t('buttons.hswholeFullScreen')
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
useResizeObserver(
|
||||
scrollbarDom,
|
||||
useDebounceFn(() => {
|
||||
dynamicTagView();
|
||||
dynamicTagView()
|
||||
}, 200)
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -609,5 +609,5 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./index.scss";
|
||||
@import './index.scss';
|
||||
</style>
|
||||
|
@ -1,47 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref, unref, onMounted, nextTick } from "vue";
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, unref, onMounted, nextTick } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: "FrameView"
|
||||
});
|
||||
name: 'FrameView'
|
||||
})
|
||||
|
||||
const { t } = useI18n();
|
||||
const loading = ref(true);
|
||||
const currentRoute = useRoute();
|
||||
const frameSrc = ref<string>("");
|
||||
const frameRef = ref<HTMLElement | null>(null);
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
const currentRoute = useRoute()
|
||||
const frameSrc = ref<string>('')
|
||||
const frameRef = ref<HTMLElement | null>(null)
|
||||
|
||||
if (unref(currentRoute.meta)?.frameSrc) {
|
||||
frameSrc.value = unref(currentRoute.meta)?.frameSrc as string;
|
||||
frameSrc.value = unref(currentRoute.meta)?.frameSrc as string
|
||||
}
|
||||
unref(currentRoute.meta)?.frameLoading === false && hideLoading();
|
||||
unref(currentRoute.meta)?.frameLoading === false && hideLoading()
|
||||
|
||||
function hideLoading() {
|
||||
loading.value = false;
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function init() {
|
||||
nextTick(() => {
|
||||
const iframe = unref(frameRef);
|
||||
if (!iframe) return;
|
||||
const _frame = iframe as any;
|
||||
const iframe = unref(frameRef)
|
||||
if (!iframe) return
|
||||
const _frame = iframe as any
|
||||
if (_frame.attachEvent) {
|
||||
_frame.attachEvent("onload", () => {
|
||||
hideLoading();
|
||||
});
|
||||
_frame.attachEvent('onload', () => {
|
||||
hideLoading()
|
||||
})
|
||||
} else {
|
||||
iframe.onload = () => {
|
||||
hideLoading();
|
||||
};
|
||||
hideLoading()
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { ref } from "vue";
|
||||
import { ref } from 'vue'
|
||||
|
||||
export function useBoolean(initValue = false) {
|
||||
const bool = ref(initValue);
|
||||
const bool = ref(initValue)
|
||||
|
||||
function setBool(value: boolean) {
|
||||
bool.value = value;
|
||||
bool.value = value
|
||||
}
|
||||
function setTrue() {
|
||||
setBool(true);
|
||||
setBool(true)
|
||||
}
|
||||
function setFalse() {
|
||||
setBool(false);
|
||||
setBool(false)
|
||||
}
|
||||
function toggle() {
|
||||
setBool(!bool.value);
|
||||
setBool(!bool.value)
|
||||
}
|
||||
|
||||
return {
|
||||
@ -22,5 +22,5 @@ export function useBoolean(initValue = false) {
|
||||
setTrue,
|
||||
setFalse,
|
||||
toggle
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,61 +1,61 @@
|
||||
import { ref } from "vue";
|
||||
import { getConfig } from "@/config";
|
||||
import { useLayout } from "./useLayout";
|
||||
import { themeColorsType } from "../types";
|
||||
import { useGlobal } from "@pureadmin/utils";
|
||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||
import { ref } from 'vue'
|
||||
import { getConfig } from '@/config'
|
||||
import { useLayout } from './useLayout'
|
||||
import { themeColorsType } from '../types'
|
||||
import { useGlobal } from '@pureadmin/utils'
|
||||
import { useEpThemeStoreHook } from '@/store/modules/epTheme'
|
||||
import {
|
||||
darken,
|
||||
lighten,
|
||||
toggleTheme
|
||||
} from "@pureadmin/theme/dist/browser-utils";
|
||||
} from '@pureadmin/theme/dist/browser-utils'
|
||||
|
||||
export function useDataThemeChange() {
|
||||
const { layoutTheme, layout } = useLayout();
|
||||
const { layoutTheme, layout } = useLayout()
|
||||
const themeColors = ref<Array<themeColorsType>>([
|
||||
/* 道奇蓝(默认) */
|
||||
{ color: "#1b2a47", themeColor: "default" },
|
||||
{ color: '#1b2a47', themeColor: 'default' },
|
||||
/* 亮白色 */
|
||||
{ color: "#ffffff", themeColor: "light" },
|
||||
{ color: '#ffffff', themeColor: 'light' },
|
||||
/* 猩红色 */
|
||||
{ color: "#f5222d", themeColor: "dusk" },
|
||||
{ color: '#f5222d', themeColor: 'dusk' },
|
||||
/* 橙红色 */
|
||||
{ color: "#fa541c", themeColor: "volcano" },
|
||||
{ color: '#fa541c', themeColor: 'volcano' },
|
||||
/* 金色 */
|
||||
{ color: "#fadb14", themeColor: "yellow" },
|
||||
{ color: '#fadb14', themeColor: 'yellow' },
|
||||
/* 绿宝石 */
|
||||
{ color: "#13c2c2", themeColor: "mingQing" },
|
||||
{ color: '#13c2c2', themeColor: 'mingQing' },
|
||||
/* 酸橙绿 */
|
||||
{ color: "#52c41a", themeColor: "auroraGreen" },
|
||||
{ color: '#52c41a', themeColor: 'auroraGreen' },
|
||||
/* 深粉色 */
|
||||
{ color: "#eb2f96", themeColor: "pink" },
|
||||
{ color: '#eb2f96', themeColor: 'pink' },
|
||||
/* 深紫罗兰色 */
|
||||
{ color: "#722ed1", themeColor: "saucePurple" }
|
||||
]);
|
||||
{ color: '#722ed1', themeColor: 'saucePurple' }
|
||||
])
|
||||
|
||||
const { $storage } = useGlobal<GlobalPropertiesApi>();
|
||||
const dataTheme = ref<boolean>($storage?.layout?.darkMode);
|
||||
const body = document.documentElement as HTMLElement;
|
||||
const { $storage } = useGlobal<GlobalPropertiesApi>()
|
||||
const dataTheme = ref<boolean>($storage?.layout?.darkMode)
|
||||
const body = document.documentElement as HTMLElement
|
||||
|
||||
/** 设置导航主题色 */
|
||||
function setLayoutThemeColor(theme = getConfig().Theme ?? "default") {
|
||||
layoutTheme.value.theme = theme;
|
||||
function setLayoutThemeColor(theme = getConfig().Theme ?? 'default') {
|
||||
layoutTheme.value.theme = theme
|
||||
toggleTheme({
|
||||
scopeName: `layout-theme-${theme}`
|
||||
});
|
||||
})
|
||||
$storage.layout = {
|
||||
layout: layout.value,
|
||||
theme,
|
||||
darkMode: dataTheme.value,
|
||||
sidebarStatus: $storage.layout?.sidebarStatus,
|
||||
epThemeColor: $storage.layout?.epThemeColor
|
||||
};
|
||||
}
|
||||
|
||||
if (theme === "default" || theme === "light") {
|
||||
setEpThemeColor(getConfig().EpThemeColor);
|
||||
if (theme === 'default' || theme === 'light') {
|
||||
setEpThemeColor(getConfig().EpThemeColor)
|
||||
} else {
|
||||
const colors = themeColors.value.find(v => v.themeColor === theme);
|
||||
setEpThemeColor(colors.color);
|
||||
const colors = themeColors.value.find(v => v.themeColor === theme)
|
||||
setEpThemeColor(colors.color)
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,34 +63,34 @@ export function useDataThemeChange() {
|
||||
document.documentElement.style.setProperty(
|
||||
`--el-color-primary-${mode}-${i}`,
|
||||
dataTheme.value ? darken(color, i / 10) : lighten(color, i / 10)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
/** 设置 `element-plus` 主题色 */
|
||||
const setEpThemeColor = (color: string) => {
|
||||
useEpThemeStoreHook().setEpThemeColor(color);
|
||||
document.documentElement.style.setProperty("--el-color-primary", color);
|
||||
useEpThemeStoreHook().setEpThemeColor(color)
|
||||
document.documentElement.style.setProperty('--el-color-primary', color)
|
||||
for (let i = 1; i <= 2; i++) {
|
||||
setPropertyPrimary("dark", i, color);
|
||||
setPropertyPrimary('dark', i, color)
|
||||
}
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
setPropertyPrimary("light", i, color);
|
||||
setPropertyPrimary('light', i, color)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 日间、夜间主题切换 */
|
||||
function dataThemeChange() {
|
||||
/* 如果当前是light夜间主题,默认切换到default主题 */
|
||||
if (useEpThemeStoreHook().epTheme === "light" && dataTheme.value) {
|
||||
setLayoutThemeColor("default");
|
||||
if (useEpThemeStoreHook().epTheme === 'light' && dataTheme.value) {
|
||||
setLayoutThemeColor('default')
|
||||
} else {
|
||||
setLayoutThemeColor(useEpThemeStoreHook().epTheme);
|
||||
setLayoutThemeColor(useEpThemeStoreHook().epTheme)
|
||||
}
|
||||
|
||||
if (dataTheme.value) {
|
||||
document.documentElement.classList.add("dark");
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,5 +102,5 @@ export function useDataThemeChange() {
|
||||
dataThemeChange,
|
||||
setEpThemeColor,
|
||||
setLayoutThemeColor
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { routerArrays } from "../types";
|
||||
import { useGlobal } from "@pureadmin/utils";
|
||||
import { useMultiTagsStore } from "@/store/modules/multiTags";
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { routerArrays } from '../types'
|
||||
import { useGlobal } from '@pureadmin/utils'
|
||||
import { useMultiTagsStore } from '@/store/modules/multiTags'
|
||||
|
||||
export function useLayout() {
|
||||
const { $storage, $config } = useGlobal<GlobalPropertiesApi>();
|
||||
const { $storage, $config } = useGlobal<GlobalPropertiesApi>()
|
||||
|
||||
const initStorage = () => {
|
||||
/** 路由 */
|
||||
@ -13,22 +13,22 @@ export function useLayout() {
|
||||
useMultiTagsStore().multiTagsCache &&
|
||||
(!$storage.tags || $storage.tags.length === 0)
|
||||
) {
|
||||
$storage.tags = routerArrays;
|
||||
$storage.tags = routerArrays
|
||||
}
|
||||
/** 国际化 */
|
||||
if (!$storage.locale) {
|
||||
$storage.locale = { locale: $config?.Locale ?? "zh" };
|
||||
useI18n().locale.value = $config?.Locale ?? "zh";
|
||||
$storage.locale = { locale: $config?.Locale ?? 'zh' }
|
||||
useI18n().locale.value = $config?.Locale ?? 'zh'
|
||||
}
|
||||
/** 导航 */
|
||||
if (!$storage.layout) {
|
||||
$storage.layout = {
|
||||
layout: $config?.Layout ?? "vertical",
|
||||
theme: $config?.Theme ?? "default",
|
||||
layout: $config?.Layout ?? 'vertical',
|
||||
theme: $config?.Theme ?? 'default',
|
||||
darkMode: $config?.DarkMode ?? false,
|
||||
sidebarStatus: $config?.SidebarStatus ?? true,
|
||||
epThemeColor: $config?.EpThemeColor ?? "#409EFF"
|
||||
};
|
||||
epThemeColor: $config?.EpThemeColor ?? '#409EFF'
|
||||
}
|
||||
}
|
||||
/** 灰色模式、色弱模式、隐藏标签页 */
|
||||
if (!$storage.configure) {
|
||||
@ -37,24 +37,24 @@ export function useLayout() {
|
||||
weak: $config?.Weak ?? false,
|
||||
hideTabs: $config?.HideTabs ?? false,
|
||||
showLogo: $config?.ShowLogo ?? true,
|
||||
showModel: $config?.ShowModel ?? "smart",
|
||||
showModel: $config?.ShowModel ?? 'smart',
|
||||
multiTagsCache: $config?.MultiTagsCache ?? false
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** 清空缓存后从serverConfig.json读取默认配置并赋值到storage中 */
|
||||
const layout = computed(() => {
|
||||
return $storage?.layout.layout;
|
||||
});
|
||||
return $storage?.layout.layout
|
||||
})
|
||||
|
||||
const layoutTheme = computed(() => {
|
||||
return $storage.layout;
|
||||
});
|
||||
return $storage.layout
|
||||
})
|
||||
|
||||
return {
|
||||
layout,
|
||||
layoutTheme,
|
||||
initStorage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,150 +1,150 @@
|
||||
import { storeToRefs } from "pinia";
|
||||
import { getConfig } from "@/config";
|
||||
import { useRouter } from "vue-router";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import { routeMetaType } from "../types";
|
||||
import { useGlobal } from "@pureadmin/utils";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { router, remainingPaths } from "@/router";
|
||||
import { computed, type CSSProperties } from "vue";
|
||||
import { useAppStoreHook } from "@/store/modules/app";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { getConfig } from '@/config'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { emitter } from '@/utils/mitt'
|
||||
import { routeMetaType } from '../types'
|
||||
import { useGlobal } from '@pureadmin/utils'
|
||||
import { transformI18n } from '@/plugins/i18n'
|
||||
import { router, remainingPaths } from '@/router'
|
||||
import { computed, type CSSProperties } from 'vue'
|
||||
import { useAppStoreHook } from '@/store/modules/app'
|
||||
import { useUserStoreHook } from '@/store/modules/user'
|
||||
import { useEpThemeStoreHook } from '@/store/modules/epTheme'
|
||||
import { usePermissionStoreHook } from '@/store/modules/permission'
|
||||
|
||||
const errorInfo = "当前路由配置不正确,请检查配置";
|
||||
const errorInfo = '当前路由配置不正确,请检查配置'
|
||||
|
||||
export function useNav() {
|
||||
const pureApp = useAppStoreHook();
|
||||
const routers = useRouter().options.routes;
|
||||
const { wholeMenus } = storeToRefs(usePermissionStoreHook());
|
||||
const pureApp = useAppStoreHook()
|
||||
const routers = useRouter().options.routes
|
||||
const { wholeMenus } = storeToRefs(usePermissionStoreHook())
|
||||
/** 平台`layout`中所有`el-tooltip`的`effect`配置,默认`light` */
|
||||
const tooltipEffect = getConfig()?.TooltipEffect ?? "light";
|
||||
const tooltipEffect = getConfig()?.TooltipEffect ?? 'light'
|
||||
|
||||
const getDivStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
overflow: "hidden"
|
||||
};
|
||||
});
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
})
|
||||
|
||||
/** 用户名 */
|
||||
const username = computed(() => {
|
||||
return useUserStoreHook()?.username;
|
||||
});
|
||||
return useUserStoreHook()?.username
|
||||
})
|
||||
|
||||
/** 设置国际化选中后的样式 */
|
||||
const getDropdownItemStyle = computed(() => {
|
||||
return (locale, t) => {
|
||||
return {
|
||||
background: locale === t ? useEpThemeStoreHook().epThemeColor : "",
|
||||
color: locale === t ? "#f4f4f5" : "#000"
|
||||
};
|
||||
};
|
||||
});
|
||||
background: locale === t ? useEpThemeStoreHook().epThemeColor : '',
|
||||
color: locale === t ? '#f4f4f5' : '#000'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const getDropdownItemClass = computed(() => {
|
||||
return (locale, t) => {
|
||||
return locale === t ? "" : "dark:hover:!text-primary";
|
||||
};
|
||||
});
|
||||
return locale === t ? '' : 'dark:hover:!text-primary'
|
||||
}
|
||||
})
|
||||
|
||||
const avatarsStyle = computed(() => {
|
||||
return username.value ? { marginRight: "10px" } : "";
|
||||
});
|
||||
return username.value ? { marginRight: '10px' } : ''
|
||||
})
|
||||
|
||||
const isCollapse = computed(() => {
|
||||
return !pureApp.getSidebarStatus;
|
||||
});
|
||||
return !pureApp.getSidebarStatus
|
||||
})
|
||||
|
||||
const device = computed(() => {
|
||||
return pureApp.getDevice;
|
||||
});
|
||||
return pureApp.getDevice
|
||||
})
|
||||
|
||||
const { $storage, $config } = useGlobal<GlobalPropertiesApi>();
|
||||
const { $storage, $config } = useGlobal<GlobalPropertiesApi>()
|
||||
const layout = computed(() => {
|
||||
return $storage?.layout?.layout;
|
||||
});
|
||||
return $storage?.layout?.layout
|
||||
})
|
||||
|
||||
const title = computed(() => {
|
||||
return $config.Title;
|
||||
});
|
||||
return $config.Title
|
||||
})
|
||||
|
||||
/** 动态title */
|
||||
function changeTitle(meta: routeMetaType) {
|
||||
const Title = getConfig().Title;
|
||||
if (Title) document.title = `${transformI18n(meta.title)} | ${Title}`;
|
||||
else document.title = transformI18n(meta.title);
|
||||
const Title = getConfig().Title
|
||||
if (Title) document.title = `${transformI18n(meta.title)} | ${Title}`
|
||||
else document.title = transformI18n(meta.title)
|
||||
}
|
||||
|
||||
/** 退出登录 */
|
||||
function logout() {
|
||||
useUserStoreHook().logOut();
|
||||
useUserStoreHook().logOut()
|
||||
}
|
||||
|
||||
function backHome() {
|
||||
router.push("/welcome");
|
||||
router.push('/welcome')
|
||||
}
|
||||
|
||||
function onPanel() {
|
||||
emitter.emit("openPanel");
|
||||
emitter.emit('openPanel')
|
||||
}
|
||||
|
||||
function toggleSideBar() {
|
||||
pureApp.toggleSideBar();
|
||||
pureApp.toggleSideBar()
|
||||
}
|
||||
|
||||
function handleResize(menuRef) {
|
||||
menuRef?.handleResize();
|
||||
menuRef?.handleResize()
|
||||
}
|
||||
|
||||
function resolvePath(route) {
|
||||
if (!route.children) return console.error(errorInfo);
|
||||
const httpReg = /^http(s?):\/\//;
|
||||
const routeChildPath = route.children[0]?.path;
|
||||
if (!route.children) return console.error(errorInfo)
|
||||
const httpReg = /^http(s?):\/\//
|
||||
const routeChildPath = route.children[0]?.path
|
||||
if (httpReg.test(routeChildPath)) {
|
||||
return route.path + "/" + routeChildPath;
|
||||
return route.path + '/' + routeChildPath
|
||||
} else {
|
||||
return routeChildPath;
|
||||
return routeChildPath
|
||||
}
|
||||
}
|
||||
|
||||
function menuSelect(indexPath: string, routers): void {
|
||||
if (wholeMenus.value.length === 0) return;
|
||||
if (isRemaining(indexPath)) return;
|
||||
let parentPath = "";
|
||||
const parentPathIndex = indexPath.lastIndexOf("/");
|
||||
if (wholeMenus.value.length === 0) return
|
||||
if (isRemaining(indexPath)) return
|
||||
let parentPath = ''
|
||||
const parentPathIndex = indexPath.lastIndexOf('/')
|
||||
if (parentPathIndex > 0) {
|
||||
parentPath = indexPath.slice(0, parentPathIndex);
|
||||
parentPath = indexPath.slice(0, parentPathIndex)
|
||||
}
|
||||
/** 找到当前路由的信息 */
|
||||
function findCurrentRoute(indexPath: string, routes) {
|
||||
if (!routes) return console.error(errorInfo);
|
||||
if (!routes) return console.error(errorInfo)
|
||||
return routes.map(item => {
|
||||
if (item.path === indexPath) {
|
||||
if (item.redirect) {
|
||||
findCurrentRoute(item.redirect, item.children);
|
||||
findCurrentRoute(item.redirect, item.children)
|
||||
} else {
|
||||
/** 切换左侧菜单 通知标签页 */
|
||||
emitter.emit("changLayoutRoute", {
|
||||
emitter.emit('changLayoutRoute', {
|
||||
indexPath,
|
||||
parentPath
|
||||
});
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (item.children) findCurrentRoute(indexPath, item.children);
|
||||
if (item.children) findCurrentRoute(indexPath, item.children)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
findCurrentRoute(indexPath, routers);
|
||||
findCurrentRoute(indexPath, routers)
|
||||
}
|
||||
|
||||
/** 判断路径是否参与菜单 */
|
||||
function isRemaining(path: string): boolean {
|
||||
return remainingPaths.includes(path);
|
||||
return remainingPaths.includes(path)
|
||||
}
|
||||
|
||||
return {
|
||||
@ -169,5 +169,5 @@ export function useNav() {
|
||||
tooltipEffect,
|
||||
getDropdownItemStyle,
|
||||
getDropdownItemClass
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -7,213 +7,213 @@ import {
|
||||
onMounted,
|
||||
CSSProperties,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import { tagsViewsType } from "../types";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { transformI18n, $t } from "@/plugins/i18n";
|
||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
} from 'vue'
|
||||
import { tagsViewsType } from '../types'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { transformI18n, $t } from '@/plugins/i18n'
|
||||
import { useSettingStoreHook } from '@/store/modules/settings'
|
||||
import { useMultiTagsStoreHook } from '@/store/modules/multiTags'
|
||||
import {
|
||||
isEqual,
|
||||
isBoolean,
|
||||
storageLocal,
|
||||
toggleClass,
|
||||
hasClass
|
||||
} from "@pureadmin/utils";
|
||||
} from '@pureadmin/utils'
|
||||
|
||||
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
|
||||
import CloseAllTags from "@iconify-icons/ri/subtract-line";
|
||||
import CloseOtherTags from "@iconify-icons/ri/text-spacing";
|
||||
import CloseRightTags from "@iconify-icons/ri/text-direction-l";
|
||||
import CloseLeftTags from "@iconify-icons/ri/text-direction-r";
|
||||
import RefreshRight from "@iconify-icons/ep/refresh-right";
|
||||
import Close from "@iconify-icons/ep/close";
|
||||
import Fullscreen from '@iconify-icons/ri/fullscreen-fill'
|
||||
import CloseAllTags from '@iconify-icons/ri/subtract-line'
|
||||
import CloseOtherTags from '@iconify-icons/ri/text-spacing'
|
||||
import CloseRightTags from '@iconify-icons/ri/text-direction-l'
|
||||
import CloseLeftTags from '@iconify-icons/ri/text-direction-r'
|
||||
import RefreshRight from '@iconify-icons/ep/refresh-right'
|
||||
import Close from '@iconify-icons/ep/close'
|
||||
|
||||
export function useTags() {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const instance = getCurrentInstance();
|
||||
const pureSetting = useSettingStoreHook();
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const instance = getCurrentInstance()
|
||||
const pureSetting = useSettingStoreHook()
|
||||
|
||||
const buttonTop = ref(0);
|
||||
const buttonLeft = ref(0);
|
||||
const translateX = ref(0);
|
||||
const visible = ref(false);
|
||||
const activeIndex = ref(-1);
|
||||
const buttonTop = ref(0)
|
||||
const buttonLeft = ref(0)
|
||||
const translateX = ref(0)
|
||||
const visible = ref(false)
|
||||
const activeIndex = ref(-1)
|
||||
// 当前右键选中的路由信息
|
||||
const currentSelect = ref({});
|
||||
const currentSelect = ref({})
|
||||
|
||||
/** 显示模式,默认灵动模式 */
|
||||
const showModel = ref(
|
||||
storageLocal().getItem<StorageConfigs>("responsive-configure")?.showModel ||
|
||||
"smart"
|
||||
);
|
||||
storageLocal().getItem<StorageConfigs>('responsive-configure')?.showModel ||
|
||||
'smart'
|
||||
)
|
||||
/** 是否隐藏标签页,默认显示 */
|
||||
const showTags =
|
||||
ref(
|
||||
storageLocal().getItem<StorageConfigs>("responsive-configure").hideTabs
|
||||
) ?? ref("false");
|
||||
storageLocal().getItem<StorageConfigs>('responsive-configure').hideTabs
|
||||
) ?? ref('false')
|
||||
const multiTags: any = computed(() => {
|
||||
return useMultiTagsStoreHook().multiTags;
|
||||
});
|
||||
return useMultiTagsStoreHook().multiTags
|
||||
})
|
||||
|
||||
const tagsViews = reactive<Array<tagsViewsType>>([
|
||||
{
|
||||
icon: RefreshRight,
|
||||
text: $t("buttons.hsreload"),
|
||||
text: $t('buttons.hsreload'),
|
||||
divided: false,
|
||||
disabled: false,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: Close,
|
||||
text: $t("buttons.hscloseCurrentTab"),
|
||||
text: $t('buttons.hscloseCurrentTab'),
|
||||
divided: false,
|
||||
disabled: multiTags.value.length > 1 ? false : true,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: CloseLeftTags,
|
||||
text: $t("buttons.hscloseLeftTabs"),
|
||||
text: $t('buttons.hscloseLeftTabs'),
|
||||
divided: true,
|
||||
disabled: multiTags.value.length > 1 ? false : true,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: CloseRightTags,
|
||||
text: $t("buttons.hscloseRightTabs"),
|
||||
text: $t('buttons.hscloseRightTabs'),
|
||||
divided: false,
|
||||
disabled: multiTags.value.length > 1 ? false : true,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: CloseOtherTags,
|
||||
text: $t("buttons.hscloseOtherTabs"),
|
||||
text: $t('buttons.hscloseOtherTabs'),
|
||||
divided: true,
|
||||
disabled: multiTags.value.length > 2 ? false : true,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: CloseAllTags,
|
||||
text: $t("buttons.hscloseAllTabs"),
|
||||
text: $t('buttons.hscloseAllTabs'),
|
||||
divided: false,
|
||||
disabled: multiTags.value.length > 1 ? false : true,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: Fullscreen,
|
||||
text: $t("buttons.hswholeFullScreen"),
|
||||
text: $t('buttons.hswholeFullScreen'),
|
||||
divided: true,
|
||||
disabled: false,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: Fullscreen,
|
||||
text: $t("buttons.hscontentFullScreen"),
|
||||
text: $t('buttons.hscontentFullScreen'),
|
||||
divided: false,
|
||||
disabled: false,
|
||||
show: true
|
||||
}
|
||||
]);
|
||||
])
|
||||
|
||||
function conditionHandle(item, previous, next) {
|
||||
if (isBoolean(route?.meta?.showLink) && route?.meta?.showLink === false) {
|
||||
if (Object.keys(route.query).length > 0) {
|
||||
return isEqual(route.query, item.query) ? previous : next;
|
||||
return isEqual(route.query, item.query) ? previous : next
|
||||
} else {
|
||||
return isEqual(route.params, item.params) ? previous : next;
|
||||
return isEqual(route.params, item.params) ? previous : next
|
||||
}
|
||||
} else {
|
||||
return route.path === item.path ? previous : next;
|
||||
return route.path === item.path ? previous : next
|
||||
}
|
||||
}
|
||||
|
||||
const iconIsActive = computed(() => {
|
||||
return (item, index) => {
|
||||
if (index === 0) return;
|
||||
return conditionHandle(item, true, false);
|
||||
};
|
||||
});
|
||||
if (index === 0) return
|
||||
return conditionHandle(item, true, false)
|
||||
}
|
||||
})
|
||||
|
||||
const linkIsActive = computed(() => {
|
||||
return item => {
|
||||
return conditionHandle(item, "is-active", "");
|
||||
};
|
||||
});
|
||||
return conditionHandle(item, 'is-active', '')
|
||||
}
|
||||
})
|
||||
|
||||
const scheduleIsActive = computed(() => {
|
||||
return item => {
|
||||
return conditionHandle(item, "schedule-active", "");
|
||||
};
|
||||
});
|
||||
return conditionHandle(item, 'schedule-active', '')
|
||||
}
|
||||
})
|
||||
|
||||
const getTabStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
transform: `translateX(${translateX.value}px)`
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const getContextMenuStyle = computed((): CSSProperties => {
|
||||
return { left: buttonLeft.value + "px", top: buttonTop.value + "px" };
|
||||
});
|
||||
return { left: buttonLeft.value + 'px', top: buttonTop.value + 'px' }
|
||||
})
|
||||
|
||||
const closeMenu = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
/** 鼠标移入添加激活样式 */
|
||||
function onMouseenter(index) {
|
||||
if (index) activeIndex.value = index;
|
||||
if (unref(showModel) === "smart") {
|
||||
if (hasClass(instance.refs["schedule" + index][0], "schedule-active"))
|
||||
return;
|
||||
toggleClass(true, "schedule-in", instance.refs["schedule" + index][0]);
|
||||
toggleClass(false, "schedule-out", instance.refs["schedule" + index][0]);
|
||||
if (index) activeIndex.value = index
|
||||
if (unref(showModel) === 'smart') {
|
||||
if (hasClass(instance.refs['schedule' + index][0], 'schedule-active'))
|
||||
return
|
||||
toggleClass(true, 'schedule-in', instance.refs['schedule' + index][0])
|
||||
toggleClass(false, 'schedule-out', instance.refs['schedule' + index][0])
|
||||
} else {
|
||||
if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return;
|
||||
toggleClass(true, "card-in", instance.refs["dynamic" + index][0]);
|
||||
toggleClass(false, "card-out", instance.refs["dynamic" + index][0]);
|
||||
if (hasClass(instance.refs['dynamic' + index][0], 'card-active')) return
|
||||
toggleClass(true, 'card-in', instance.refs['dynamic' + index][0])
|
||||
toggleClass(false, 'card-out', instance.refs['dynamic' + index][0])
|
||||
}
|
||||
}
|
||||
|
||||
/** 鼠标移出恢复默认样式 */
|
||||
function onMouseleave(index) {
|
||||
activeIndex.value = -1;
|
||||
if (unref(showModel) === "smart") {
|
||||
if (hasClass(instance.refs["schedule" + index][0], "schedule-active"))
|
||||
return;
|
||||
toggleClass(false, "schedule-in", instance.refs["schedule" + index][0]);
|
||||
toggleClass(true, "schedule-out", instance.refs["schedule" + index][0]);
|
||||
activeIndex.value = -1
|
||||
if (unref(showModel) === 'smart') {
|
||||
if (hasClass(instance.refs['schedule' + index][0], 'schedule-active'))
|
||||
return
|
||||
toggleClass(false, 'schedule-in', instance.refs['schedule' + index][0])
|
||||
toggleClass(true, 'schedule-out', instance.refs['schedule' + index][0])
|
||||
} else {
|
||||
if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return;
|
||||
toggleClass(false, "card-in", instance.refs["dynamic" + index][0]);
|
||||
toggleClass(true, "card-out", instance.refs["dynamic" + index][0]);
|
||||
if (hasClass(instance.refs['dynamic' + index][0], 'card-active')) return
|
||||
toggleClass(false, 'card-in', instance.refs['dynamic' + index][0])
|
||||
toggleClass(true, 'card-out', instance.refs['dynamic' + index][0])
|
||||
}
|
||||
}
|
||||
|
||||
function onContentFullScreen() {
|
||||
pureSetting.hiddenSideBar
|
||||
? pureSetting.changeSetting({ key: "hiddenSideBar", value: false })
|
||||
: pureSetting.changeSetting({ key: "hiddenSideBar", value: true });
|
||||
? pureSetting.changeSetting({ key: 'hiddenSideBar', value: false })
|
||||
: pureSetting.changeSetting({ key: 'hiddenSideBar', value: true })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!showModel.value) {
|
||||
const configure = storageLocal().getItem<StorageConfigs>(
|
||||
"responsive-configure"
|
||||
);
|
||||
configure.showModel = "card";
|
||||
storageLocal().setItem("responsive-configure", configure);
|
||||
'responsive-configure'
|
||||
)
|
||||
configure.showModel = 'card'
|
||||
storageLocal().setItem('responsive-configure', configure)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
() => {
|
||||
useEventListener(document, "click", closeMenu);
|
||||
useEventListener(document, 'click', closeMenu)
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
return {
|
||||
route,
|
||||
@ -242,5 +242,5 @@ export function useTags() {
|
||||
onMouseleave,
|
||||
transformI18n,
|
||||
onContentFullScreen
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,31 @@
|
||||
import { useNav } from "./useNav";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { watch, type Ref } from "vue";
|
||||
import { useNav } from './useNav'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { watch, type Ref } from 'vue'
|
||||
|
||||
export function useTranslationLang(ref?: Ref) {
|
||||
const { $storage, changeTitle, handleResize } = useNav();
|
||||
const { locale, t } = useI18n();
|
||||
const route = useRoute();
|
||||
const { $storage, changeTitle, handleResize } = useNav()
|
||||
const { locale, t } = useI18n()
|
||||
const route = useRoute()
|
||||
|
||||
function translationCh() {
|
||||
$storage.locale = { locale: "zh" };
|
||||
locale.value = "zh";
|
||||
ref && handleResize(ref.value);
|
||||
$storage.locale = { locale: 'zh' }
|
||||
locale.value = 'zh'
|
||||
ref && handleResize(ref.value)
|
||||
}
|
||||
|
||||
function translationEn() {
|
||||
$storage.locale = { locale: "en" };
|
||||
locale.value = "en";
|
||||
ref && handleResize(ref.value);
|
||||
$storage.locale = { locale: 'en' }
|
||||
locale.value = 'en'
|
||||
ref && handleResize(ref.value)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => locale.value,
|
||||
() => {
|
||||
changeTitle(route.meta);
|
||||
changeTitle(route.meta)
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
return {
|
||||
t,
|
||||
@ -33,5 +33,5 @@ export function useTranslationLang(ref?: Ref) {
|
||||
locale,
|
||||
translationCh,
|
||||
translationEn
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import "animate.css";
|
||||
import 'animate.css'
|
||||
// 引入 src/components/ReIcon/src/offlineIcon.ts 文件中所有使用addIcon添加过的本地图标
|
||||
import "@/components/ReIcon/src/offlineIcon";
|
||||
import { setType } from "./types";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import { useLayout } from "./hooks/useLayout";
|
||||
import { useAppStoreHook } from "@/store/modules/app";
|
||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||
import { deviceDetection, useDark, useGlobal } from "@pureadmin/utils";
|
||||
import { h, reactive, computed, onMounted, defineComponent } from "vue";
|
||||
import '@/components/ReIcon/src/offlineIcon'
|
||||
import { setType } from './types'
|
||||
import { emitter } from '@/utils/mitt'
|
||||
import { useLayout } from './hooks/useLayout'
|
||||
import { useAppStoreHook } from '@/store/modules/app'
|
||||
import { useSettingStoreHook } from '@/store/modules/settings'
|
||||
import { deviceDetection, useDark, useGlobal } from '@pureadmin/utils'
|
||||
import { h, reactive, computed, onMounted, defineComponent } from 'vue'
|
||||
|
||||
import navbar from "./components/navbar.vue";
|
||||
import tag from "./components/tag/index.vue";
|
||||
import appMain from "./components/appMain.vue";
|
||||
import setting from "./components/setting/index.vue";
|
||||
import Vertical from "./components/sidebar/vertical.vue";
|
||||
import Horizontal from "./components/sidebar/horizontal.vue";
|
||||
import backTop from "@/assets/svg/back_top.svg?component";
|
||||
import navbar from './components/navbar.vue'
|
||||
import tag from './components/tag/index.vue'
|
||||
import appMain from './components/appMain.vue'
|
||||
import setting from './components/setting/index.vue'
|
||||
import Vertical from './components/sidebar/vertical.vue'
|
||||
import Horizontal from './components/sidebar/horizontal.vue'
|
||||
import backTop from '@/assets/svg/back_top.svg?component'
|
||||
|
||||
const { isDark } = useDark();
|
||||
const { layout } = useLayout();
|
||||
const isMobile = deviceDetection();
|
||||
const pureSetting = useSettingStoreHook();
|
||||
const { $storage } = useGlobal<GlobalPropertiesApi>();
|
||||
const { isDark } = useDark()
|
||||
const { layout } = useLayout()
|
||||
const isMobile = deviceDetection()
|
||||
const pureSetting = useSettingStoreHook()
|
||||
const { $storage } = useGlobal<GlobalPropertiesApi>()
|
||||
|
||||
const set: setType = reactive({
|
||||
sidebar: computed(() => {
|
||||
return useAppStoreHook().sidebar;
|
||||
return useAppStoreHook().sidebar
|
||||
}),
|
||||
|
||||
device: computed(() => {
|
||||
return useAppStoreHook().device;
|
||||
return useAppStoreHook().device
|
||||
}),
|
||||
|
||||
fixedHeader: computed(() => {
|
||||
return pureSetting.fixedHeader;
|
||||
return pureSetting.fixedHeader
|
||||
}),
|
||||
|
||||
classes: computed(() => {
|
||||
@ -42,95 +42,95 @@ const set: setType = reactive({
|
||||
hideSidebar: !set.sidebar.opened,
|
||||
openSidebar: set.sidebar.opened,
|
||||
withoutAnimation: set.sidebar.withoutAnimation,
|
||||
mobile: set.device === "mobile"
|
||||
};
|
||||
mobile: set.device === 'mobile'
|
||||
}
|
||||
}),
|
||||
|
||||
hideTabs: computed(() => {
|
||||
return $storage?.configure.hideTabs;
|
||||
return $storage?.configure.hideTabs
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
function setTheme(layoutModel: string) {
|
||||
window.document.body.setAttribute("layout", layoutModel);
|
||||
window.document.body.setAttribute('layout', layoutModel)
|
||||
$storage.layout = {
|
||||
layout: `${layoutModel}`,
|
||||
theme: $storage.layout?.theme,
|
||||
darkMode: $storage.layout?.darkMode,
|
||||
sidebarStatus: $storage.layout?.sidebarStatus,
|
||||
epThemeColor: $storage.layout?.epThemeColor
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function toggle(device: string, bool: boolean) {
|
||||
useAppStoreHook().toggleDevice(device);
|
||||
useAppStoreHook().toggleSideBar(bool, "resize");
|
||||
useAppStoreHook().toggleDevice(device)
|
||||
useAppStoreHook().toggleSideBar(bool, 'resize')
|
||||
}
|
||||
|
||||
// 判断是否可自动关闭菜单栏
|
||||
let isAutoCloseSidebar = true;
|
||||
let isAutoCloseSidebar = true
|
||||
|
||||
// 监听容器
|
||||
emitter.on("resize", ({ detail }) => {
|
||||
if (isMobile) return;
|
||||
const { width } = detail;
|
||||
width <= 760 ? setTheme("vertical") : setTheme(useAppStoreHook().layout);
|
||||
emitter.on('resize', ({ detail }) => {
|
||||
if (isMobile) return
|
||||
const { width } = detail
|
||||
width <= 760 ? setTheme('vertical') : setTheme(useAppStoreHook().layout)
|
||||
/** width app-wrapper类容器宽度
|
||||
* 0 < width <= 760 隐藏侧边栏
|
||||
* 760 < width <= 990 折叠侧边栏
|
||||
* width > 990 展开侧边栏
|
||||
*/
|
||||
if (width > 0 && width <= 760) {
|
||||
toggle("mobile", false);
|
||||
isAutoCloseSidebar = true;
|
||||
toggle('mobile', false)
|
||||
isAutoCloseSidebar = true
|
||||
} else if (width > 760 && width <= 990) {
|
||||
if (isAutoCloseSidebar) {
|
||||
toggle("desktop", false);
|
||||
isAutoCloseSidebar = false;
|
||||
toggle('desktop', false)
|
||||
isAutoCloseSidebar = false
|
||||
}
|
||||
} else if (width > 990) {
|
||||
if (!set.sidebar.isClickCollapse) {
|
||||
toggle("desktop", true);
|
||||
isAutoCloseSidebar = true;
|
||||
toggle('desktop', true)
|
||||
isAutoCloseSidebar = true
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (isMobile) {
|
||||
toggle("mobile", false);
|
||||
toggle('mobile', false)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const layoutHeader = defineComponent({
|
||||
render() {
|
||||
return h(
|
||||
"div",
|
||||
'div',
|
||||
{
|
||||
class: { "fixed-header": set.fixedHeader },
|
||||
class: { 'fixed-header': set.fixedHeader },
|
||||
style: [
|
||||
set.hideTabs && layout.value.includes("horizontal")
|
||||
set.hideTabs && layout.value.includes('horizontal')
|
||||
? isDark.value
|
||||
? "box-shadow: 0 1px 4px #0d0d0d"
|
||||
: "box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08)"
|
||||
: ""
|
||||
? 'box-shadow: 0 1px 4px #0d0d0d'
|
||||
: 'box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08)'
|
||||
: ''
|
||||
]
|
||||
},
|
||||
{
|
||||
default: () => [
|
||||
!pureSetting.hiddenSideBar &&
|
||||
(layout.value.includes("vertical") || layout.value.includes("mix"))
|
||||
(layout.value.includes('vertical') || layout.value.includes('mix'))
|
||||
? h(navbar)
|
||||
: null,
|
||||
!pureSetting.hiddenSideBar && layout.value.includes("horizontal")
|
||||
!pureSetting.hiddenSideBar && layout.value.includes('horizontal')
|
||||
? h(Horizontal)
|
||||
: null,
|
||||
h(tag)
|
||||
]
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -181,7 +181,7 @@ const layoutHeader = defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
@mixin clearfix {
|
||||
&::after {
|
||||
content: "";
|
||||
content: '';
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { unref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { unref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
defineOptions({
|
||||
name: "Redirect"
|
||||
});
|
||||
name: 'Redirect'
|
||||
})
|
||||
|
||||
const { currentRoute, replace } = useRouter();
|
||||
const { currentRoute, replace } = useRouter()
|
||||
|
||||
const { params, query } = unref(currentRoute);
|
||||
const { path } = params;
|
||||
const { params, query } = unref(currentRoute)
|
||||
const { path } = params
|
||||
|
||||
const _path = Array.isArray(path) ? path.join("/") : path;
|
||||
const _path = Array.isArray(path) ? path.join('/') : path
|
||||
|
||||
replace({
|
||||
path: "/" + _path,
|
||||
path: '/' + _path,
|
||||
query
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -2,116 +2,116 @@
|
||||
* @description ⚠️:此文件仅供主题插件使用,请不要在此文件中导出别的工具函数(仅在页面加载前运行)
|
||||
*/
|
||||
|
||||
import { type multipleScopeVarsOptions } from "@pureadmin/theme";
|
||||
import { type multipleScopeVarsOptions } from '@pureadmin/theme'
|
||||
|
||||
/** 预设主题色 */
|
||||
const themeColors = {
|
||||
default: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#001529",
|
||||
menuHover: "#4091f7",
|
||||
subMenuBg: "#0f0303",
|
||||
subMenuActiveBg: "#4091f7",
|
||||
menuText: "rgb(254 254 254 / 65%)",
|
||||
sidebarLogo: "#002140",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#4091f7"
|
||||
subMenuActiveText: '#fff',
|
||||
menuBg: '#001529',
|
||||
menuHover: '#4091f7',
|
||||
subMenuBg: '#0f0303',
|
||||
subMenuActiveBg: '#4091f7',
|
||||
menuText: 'rgb(254 254 254 / 65%)',
|
||||
sidebarLogo: '#002140',
|
||||
menuTitleHover: '#fff',
|
||||
menuActiveBefore: '#4091f7'
|
||||
},
|
||||
light: {
|
||||
subMenuActiveText: "#409eff",
|
||||
menuBg: "#fff",
|
||||
menuHover: "#e0ebf6",
|
||||
subMenuBg: "#fff",
|
||||
subMenuActiveBg: "#e0ebf6",
|
||||
menuText: "#7a80b4",
|
||||
sidebarLogo: "#fff",
|
||||
menuTitleHover: "#000",
|
||||
menuActiveBefore: "#4091f7"
|
||||
subMenuActiveText: '#409eff',
|
||||
menuBg: '#fff',
|
||||
menuHover: '#e0ebf6',
|
||||
subMenuBg: '#fff',
|
||||
subMenuActiveBg: '#e0ebf6',
|
||||
menuText: '#7a80b4',
|
||||
sidebarLogo: '#fff',
|
||||
menuTitleHover: '#000',
|
||||
menuActiveBefore: '#4091f7'
|
||||
},
|
||||
dusk: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#2a0608",
|
||||
menuHover: "#e13c39",
|
||||
subMenuBg: "#000",
|
||||
subMenuActiveBg: "#e13c39",
|
||||
menuText: "rgb(254 254 254 / 65.1%)",
|
||||
sidebarLogo: "#42090c",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#e13c39"
|
||||
subMenuActiveText: '#fff',
|
||||
menuBg: '#2a0608',
|
||||
menuHover: '#e13c39',
|
||||
subMenuBg: '#000',
|
||||
subMenuActiveBg: '#e13c39',
|
||||
menuText: 'rgb(254 254 254 / 65.1%)',
|
||||
sidebarLogo: '#42090c',
|
||||
menuTitleHover: '#fff',
|
||||
menuActiveBefore: '#e13c39'
|
||||
},
|
||||
volcano: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#2b0e05",
|
||||
menuHover: "#e85f33",
|
||||
subMenuBg: "#0f0603",
|
||||
subMenuActiveBg: "#e85f33",
|
||||
menuText: "rgb(254 254 254 / 65%)",
|
||||
sidebarLogo: "#441708",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#e85f33"
|
||||
subMenuActiveText: '#fff',
|
||||
menuBg: '#2b0e05',
|
||||
menuHover: '#e85f33',
|
||||
subMenuBg: '#0f0603',
|
||||
subMenuActiveBg: '#e85f33',
|
||||
menuText: 'rgb(254 254 254 / 65%)',
|
||||
sidebarLogo: '#441708',
|
||||
menuTitleHover: '#fff',
|
||||
menuActiveBefore: '#e85f33'
|
||||
},
|
||||
yellow: {
|
||||
subMenuActiveText: "#d25f00",
|
||||
menuBg: "#2b2503",
|
||||
menuHover: "#f6da4d",
|
||||
subMenuBg: "#0f0603",
|
||||
subMenuActiveBg: "#f6da4d",
|
||||
menuText: "rgb(254 254 254 / 65%)",
|
||||
sidebarLogo: "#443b05",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#f6da4d"
|
||||
subMenuActiveText: '#d25f00',
|
||||
menuBg: '#2b2503',
|
||||
menuHover: '#f6da4d',
|
||||
subMenuBg: '#0f0603',
|
||||
subMenuActiveBg: '#f6da4d',
|
||||
menuText: 'rgb(254 254 254 / 65%)',
|
||||
sidebarLogo: '#443b05',
|
||||
menuTitleHover: '#fff',
|
||||
menuActiveBefore: '#f6da4d'
|
||||
},
|
||||
mingQing: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#032121",
|
||||
menuHover: "#59bfc1",
|
||||
subMenuBg: "#000",
|
||||
subMenuActiveBg: "#59bfc1",
|
||||
menuText: "#7a80b4",
|
||||
sidebarLogo: "#053434",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#59bfc1"
|
||||
subMenuActiveText: '#fff',
|
||||
menuBg: '#032121',
|
||||
menuHover: '#59bfc1',
|
||||
subMenuBg: '#000',
|
||||
subMenuActiveBg: '#59bfc1',
|
||||
menuText: '#7a80b4',
|
||||
sidebarLogo: '#053434',
|
||||
menuTitleHover: '#fff',
|
||||
menuActiveBefore: '#59bfc1'
|
||||
},
|
||||
auroraGreen: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#0b1e15",
|
||||
menuHover: "#60ac80",
|
||||
subMenuBg: "#000",
|
||||
subMenuActiveBg: "#60ac80",
|
||||
menuText: "#7a80b4",
|
||||
sidebarLogo: "#112f21",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#60ac80"
|
||||
subMenuActiveText: '#fff',
|
||||
menuBg: '#0b1e15',
|
||||
menuHover: '#60ac80',
|
||||
subMenuBg: '#000',
|
||||
subMenuActiveBg: '#60ac80',
|
||||
menuText: '#7a80b4',
|
||||
sidebarLogo: '#112f21',
|
||||
menuTitleHover: '#fff',
|
||||
menuActiveBefore: '#60ac80'
|
||||
},
|
||||
pink: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#28081a",
|
||||
menuHover: "#d84493",
|
||||
subMenuBg: "#000",
|
||||
subMenuActiveBg: "#d84493",
|
||||
menuText: "#7a80b4",
|
||||
sidebarLogo: "#3f0d29",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#d84493"
|
||||
subMenuActiveText: '#fff',
|
||||
menuBg: '#28081a',
|
||||
menuHover: '#d84493',
|
||||
subMenuBg: '#000',
|
||||
subMenuActiveBg: '#d84493',
|
||||
menuText: '#7a80b4',
|
||||
sidebarLogo: '#3f0d29',
|
||||
menuTitleHover: '#fff',
|
||||
menuActiveBefore: '#d84493'
|
||||
},
|
||||
saucePurple: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#130824",
|
||||
menuHover: "#693ac9",
|
||||
subMenuBg: "#000",
|
||||
subMenuActiveBg: "#693ac9",
|
||||
menuText: "#7a80b4",
|
||||
sidebarLogo: "#1f0c38",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#693ac9"
|
||||
subMenuActiveText: '#fff',
|
||||
menuBg: '#130824',
|
||||
menuHover: '#693ac9',
|
||||
subMenuBg: '#000',
|
||||
subMenuActiveBg: '#693ac9',
|
||||
menuText: '#7a80b4',
|
||||
sidebarLogo: '#1f0c38',
|
||||
menuTitleHover: '#fff',
|
||||
menuActiveBefore: '#693ac9'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将预设主题色处理成主题插件所需格式
|
||||
*/
|
||||
export const genScssMultipleScopeVars = (): multipleScopeVarsOptions[] => {
|
||||
const result = [] as multipleScopeVarsOptions[];
|
||||
const result = [] as multipleScopeVarsOptions[]
|
||||
Object.keys(themeColors).forEach(key => {
|
||||
result.push({
|
||||
scopeName: `layout-theme-${key}`,
|
||||
@ -126,7 +126,7 @@ export const genScssMultipleScopeVars = (): multipleScopeVarsOptions[] => {
|
||||
$menuTitleHover: ${themeColors[key].menuTitleHover} !default;
|
||||
$menuActiveBefore: ${themeColors[key].menuActiveBefore} !default;
|
||||
`
|
||||
} as multipleScopeVarsOptions);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
} as multipleScopeVarsOptions)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
@ -1,86 +1,86 @@
|
||||
import type { IconifyIcon } from "@iconify/vue";
|
||||
import type { IconifyIcon } from '@iconify/vue'
|
||||
|
||||
export const routerArrays: Array<RouteConfigs> = [
|
||||
{
|
||||
path: "/welcome",
|
||||
parentPath: "/",
|
||||
path: '/welcome',
|
||||
parentPath: '/',
|
||||
meta: {
|
||||
title: "menus.hshome",
|
||||
icon: "homeFilled"
|
||||
title: 'menus.hshome',
|
||||
icon: 'homeFilled'
|
||||
}
|
||||
}
|
||||
];
|
||||
]
|
||||
|
||||
export type routeMetaType = {
|
||||
title?: string;
|
||||
icon?: string | IconifyIcon;
|
||||
showLink?: boolean;
|
||||
savedPosition?: boolean;
|
||||
auths?: Array<string>;
|
||||
};
|
||||
title?: string
|
||||
icon?: string | IconifyIcon
|
||||
showLink?: boolean
|
||||
savedPosition?: boolean
|
||||
auths?: Array<string>
|
||||
}
|
||||
|
||||
export type RouteConfigs = {
|
||||
path?: string;
|
||||
parentPath?: string;
|
||||
query?: object;
|
||||
params?: object;
|
||||
meta?: routeMetaType;
|
||||
children?: RouteConfigs[];
|
||||
name?: string;
|
||||
};
|
||||
path?: string
|
||||
parentPath?: string
|
||||
query?: object
|
||||
params?: object
|
||||
meta?: routeMetaType
|
||||
children?: RouteConfigs[]
|
||||
name?: string
|
||||
}
|
||||
|
||||
export type multiTagsType = {
|
||||
tags: Array<RouteConfigs>;
|
||||
};
|
||||
tags: Array<RouteConfigs>
|
||||
}
|
||||
|
||||
export type tagsViewsType = {
|
||||
icon: string | IconifyIcon;
|
||||
text: string;
|
||||
divided: boolean;
|
||||
disabled: boolean;
|
||||
show: boolean;
|
||||
};
|
||||
icon: string | IconifyIcon
|
||||
text: string
|
||||
divided: boolean
|
||||
disabled: boolean
|
||||
show: boolean
|
||||
}
|
||||
|
||||
export interface setType {
|
||||
sidebar: {
|
||||
opened: boolean;
|
||||
withoutAnimation: boolean;
|
||||
isClickCollapse: boolean;
|
||||
};
|
||||
device: string;
|
||||
fixedHeader: boolean;
|
||||
opened: boolean
|
||||
withoutAnimation: boolean
|
||||
isClickCollapse: boolean
|
||||
}
|
||||
device: string
|
||||
fixedHeader: boolean
|
||||
classes: {
|
||||
hideSidebar: boolean;
|
||||
openSidebar: boolean;
|
||||
withoutAnimation: boolean;
|
||||
mobile: boolean;
|
||||
};
|
||||
hideTabs: boolean;
|
||||
hideSidebar: boolean
|
||||
openSidebar: boolean
|
||||
withoutAnimation: boolean
|
||||
mobile: boolean
|
||||
}
|
||||
hideTabs: boolean
|
||||
}
|
||||
|
||||
export type childrenType = {
|
||||
path?: string;
|
||||
noShowingChildren?: boolean;
|
||||
children?: childrenType[];
|
||||
value: unknown;
|
||||
path?: string
|
||||
noShowingChildren?: boolean
|
||||
children?: childrenType[]
|
||||
value: unknown
|
||||
meta?: {
|
||||
icon?: string;
|
||||
title?: string;
|
||||
showParent?: boolean;
|
||||
extraIcon?: string;
|
||||
};
|
||||
showTooltip?: boolean;
|
||||
parentId?: number;
|
||||
pathList?: number[];
|
||||
};
|
||||
icon?: string
|
||||
title?: string
|
||||
showParent?: boolean
|
||||
extraIcon?: string
|
||||
}
|
||||
showTooltip?: boolean
|
||||
parentId?: number
|
||||
pathList?: number[]
|
||||
}
|
||||
|
||||
export type themeColorsType = {
|
||||
color: string;
|
||||
themeColor: string;
|
||||
};
|
||||
color: string
|
||||
themeColor: string
|
||||
}
|
||||
|
||||
export interface scrollbarDomType extends HTMLElement {
|
||||
wrap?: {
|
||||
offsetWidth: number;
|
||||
};
|
||||
offsetWidth: number
|
||||
}
|
||||
}
|
||||
|
64
src/main.ts
64
src/main.ts
@ -1,58 +1,58 @@
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import { setupStore } from "@/store";
|
||||
import ElementPlus from "element-plus";
|
||||
import { useI18n } from "@/plugins/i18n";
|
||||
import { getServerConfig } from "./config";
|
||||
import { createApp, Directive } from "vue";
|
||||
import { MotionPlugin } from "@vueuse/motion";
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { setupStore } from '@/store'
|
||||
import ElementPlus from 'element-plus'
|
||||
import { useI18n } from '@/plugins/i18n'
|
||||
import { getServerConfig } from './config'
|
||||
import { createApp, Directive } from 'vue'
|
||||
import { MotionPlugin } from '@vueuse/motion'
|
||||
// import { useEcharts } from "@/plugins/echarts";
|
||||
import { injectResponsiveStorage } from "@/utils/responsive";
|
||||
import { injectResponsiveStorage } from '@/utils/responsive'
|
||||
|
||||
// import Table from "@pureadmin/table";
|
||||
// import PureDescriptions from "@pureadmin/descriptions";
|
||||
|
||||
// 引入重置样式
|
||||
import "./style/reset.scss";
|
||||
import './style/reset.scss'
|
||||
// 导入公共样式
|
||||
import "./style/index.scss";
|
||||
import './style/index.scss'
|
||||
// 一定要在main.ts中导入tailwind.css,防止vite每次hmr都会请求src/style/index.scss整体css文件导致热更新慢的问题
|
||||
import "./style/tailwind.css";
|
||||
import "element-plus/dist/index.css";
|
||||
import './style/tailwind.css'
|
||||
import 'element-plus/dist/index.css'
|
||||
// 导入字体图标
|
||||
import "./assets/iconfont/iconfont.js";
|
||||
import "./assets/iconfont/iconfont.css";
|
||||
import './assets/iconfont/iconfont.js'
|
||||
import './assets/iconfont/iconfont.css'
|
||||
|
||||
const app = createApp(App);
|
||||
const app = createApp(App)
|
||||
|
||||
// 自定义指令
|
||||
import * as directives from "@/directives";
|
||||
import * as directives from '@/directives'
|
||||
Object.keys(directives).forEach(key => {
|
||||
app.directive(key, (directives as { [key: string]: Directive })[key]);
|
||||
});
|
||||
app.directive(key, (directives as { [key: string]: Directive })[key])
|
||||
})
|
||||
|
||||
// 全局注册`@iconify/vue`图标库
|
||||
import {
|
||||
IconifyIconOffline,
|
||||
IconifyIconOnline,
|
||||
FontIcon
|
||||
} from "./components/ReIcon";
|
||||
app.component("IconifyIconOffline", IconifyIconOffline);
|
||||
app.component("IconifyIconOnline", IconifyIconOnline);
|
||||
app.component("FontIcon", FontIcon);
|
||||
} from './components/ReIcon'
|
||||
app.component('IconifyIconOffline', IconifyIconOffline)
|
||||
app.component('IconifyIconOnline', IconifyIconOnline)
|
||||
app.component('FontIcon', FontIcon)
|
||||
|
||||
// 全局注册按钮级别权限组件
|
||||
import { Auth } from "@/components/ReAuth";
|
||||
app.component("Auth", Auth);
|
||||
import { Auth } from '@/components/ReAuth'
|
||||
app.component('Auth', Auth)
|
||||
|
||||
getServerConfig(app).then(async config => {
|
||||
app.use(router);
|
||||
await router.isReady();
|
||||
injectResponsiveStorage(app, config);
|
||||
setupStore(app);
|
||||
app.use(MotionPlugin).use(useI18n).use(ElementPlus);
|
||||
app.use(router)
|
||||
await router.isReady()
|
||||
injectResponsiveStorage(app, config)
|
||||
setupStore(app)
|
||||
app.use(MotionPlugin).use(useI18n).use(ElementPlus)
|
||||
// .use(useEcharts);
|
||||
// .use(Table);
|
||||
// .use(PureDescriptions);
|
||||
app.mount("#app");
|
||||
});
|
||||
app.mount('#app')
|
||||
})
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { createProdMockServer } from "vite-plugin-mock/es/createProdMockServer";
|
||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
|
||||
|
||||
const modules: Record<string, any> = import.meta.glob("../mock/*.ts", {
|
||||
const modules: Record<string, any> = import.meta.glob('../mock/*.ts', {
|
||||
eager: true
|
||||
});
|
||||
const mockModules = [];
|
||||
})
|
||||
const mockModules = []
|
||||
|
||||
Object.keys(modules).forEach(key => {
|
||||
mockModules.push(...modules[key].default);
|
||||
});
|
||||
mockModules.push(...modules[key].default)
|
||||
})
|
||||
|
||||
export function setupProdMockServer() {
|
||||
createProdMockServer(mockModules);
|
||||
createProdMockServer(mockModules)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { App } from "vue";
|
||||
import * as echarts from "echarts/core";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import { PieChart, BarChart, LineChart } from "echarts/charts";
|
||||
import type { App } from 'vue'
|
||||
import * as echarts from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { PieChart, BarChart, LineChart } from 'echarts/charts'
|
||||
import {
|
||||
GridComponent,
|
||||
TitleComponent,
|
||||
@ -11,9 +11,9 @@ import {
|
||||
TooltipComponent,
|
||||
DataZoomComponent,
|
||||
VisualMapComponent
|
||||
} from "echarts/components";
|
||||
} from 'echarts/components'
|
||||
|
||||
const { use } = echarts;
|
||||
const { use } = echarts
|
||||
|
||||
use([
|
||||
PieChart,
|
||||
@ -28,7 +28,7 @@ use([
|
||||
TooltipComponent,
|
||||
DataZoomComponent,
|
||||
VisualMapComponent
|
||||
]);
|
||||
])
|
||||
|
||||
/**
|
||||
* @description 按需引入echarts
|
||||
@ -36,7 +36,7 @@ use([
|
||||
* @see 温馨提示:必须将 `$echarts` 添加到全局 `globalProperties` ,为了配合 https://pure-admin-utils.netlify.app/hooks/useEcharts/useEcharts.html 使用
|
||||
*/
|
||||
export function useEcharts(app: App) {
|
||||
app.config.globalProperties.$echarts = echarts;
|
||||
app.config.globalProperties.$echarts = echarts
|
||||
}
|
||||
|
||||
export default echarts;
|
||||
export default echarts
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { App, Component } from "vue";
|
||||
import { App, Component } from 'vue'
|
||||
import {
|
||||
ElTag,
|
||||
ElAffix,
|
||||
@ -62,10 +62,10 @@ import {
|
||||
// 指令
|
||||
ElLoading,
|
||||
ElInfiniteScroll
|
||||
} from "element-plus";
|
||||
} from 'element-plus'
|
||||
|
||||
// Directives
|
||||
const plugins = [ElLoading, ElInfiniteScroll];
|
||||
const plugins = [ElLoading, ElInfiniteScroll]
|
||||
|
||||
const components = [
|
||||
ElTag,
|
||||
@ -127,15 +127,15 @@ const components = [
|
||||
ElResult,
|
||||
ElSteps,
|
||||
ElStep
|
||||
];
|
||||
]
|
||||
|
||||
export function useElementPlus(app: App) {
|
||||
// 注册组件
|
||||
components.forEach((component: Component) => {
|
||||
app.component(component.name, component);
|
||||
});
|
||||
app.component(component.name, component)
|
||||
})
|
||||
// 注册指令
|
||||
plugins.forEach(plugin => {
|
||||
app.use(plugin);
|
||||
});
|
||||
app.use(plugin)
|
||||
})
|
||||
}
|
||||
|
@ -1,73 +1,73 @@
|
||||
// 多组件库的国际化和本地项目国际化兼容
|
||||
import { App, WritableComputedRef } from "vue";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { type I18n, createI18n } from "vue-i18n";
|
||||
import { App, WritableComputedRef } from 'vue'
|
||||
import { storageLocal } from '@pureadmin/utils'
|
||||
import { type I18n, createI18n } from 'vue-i18n'
|
||||
|
||||
// element-plus国际化
|
||||
import enLocale from "element-plus/lib/locale/lang/en";
|
||||
import zhLocale from "element-plus/lib/locale/lang/zh-cn";
|
||||
import enLocale from 'element-plus/lib/locale/lang/en'
|
||||
import zhLocale from 'element-plus/lib/locale/lang/zh-cn'
|
||||
|
||||
function siphonI18n(prefix = "zh-CN") {
|
||||
function siphonI18n(prefix = 'zh-CN') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(
|
||||
import.meta.glob("../../locales/*.y(a)?ml", { eager: true })
|
||||
import.meta.glob('../../locales/*.y(a)?ml', { eager: true })
|
||||
).map(([key, value]: any) => {
|
||||
const matched = key.match(/([A-Za-z0-9-_]+)\./i)[1];
|
||||
return [matched, value.default];
|
||||
const matched = key.match(/([A-Za-z0-9-_]+)\./i)[1]
|
||||
return [matched, value.default]
|
||||
})
|
||||
)[prefix];
|
||||
)[prefix]
|
||||
}
|
||||
|
||||
export const localesConfigs = {
|
||||
zh: {
|
||||
...siphonI18n("zh-CN"),
|
||||
...siphonI18n('zh-CN'),
|
||||
...zhLocale
|
||||
},
|
||||
en: {
|
||||
...siphonI18n("en"),
|
||||
...siphonI18n('en'),
|
||||
...enLocale
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化转换工具函数(自动读取根目录locales文件夹下文件进行国际化匹配)
|
||||
* @param message message
|
||||
* @returns 转化后的message
|
||||
*/
|
||||
export function transformI18n(message: any = "") {
|
||||
export function transformI18n(message: any = '') {
|
||||
if (!message) {
|
||||
return "";
|
||||
return ''
|
||||
}
|
||||
|
||||
// 处理存储动态路由的title,格式 {zh:"",en:""}
|
||||
if (typeof message === "object") {
|
||||
if (typeof message === 'object') {
|
||||
const locale: string | WritableComputedRef<string> | any =
|
||||
i18n.global.locale;
|
||||
return message[locale?.value];
|
||||
i18n.global.locale
|
||||
return message[locale?.value]
|
||||
}
|
||||
|
||||
const key = message.match(/(\S*)\./)?.[1];
|
||||
if (key && Object.keys(siphonI18n("zh-CN")).includes(key)) {
|
||||
return i18n.global.t.call(i18n.global.locale, message);
|
||||
} else if (!key && Object.keys(siphonI18n("zh-CN")).includes(message)) {
|
||||
const key = message.match(/(\S*)\./)?.[1]
|
||||
if (key && Object.keys(siphonI18n('zh-CN')).includes(key)) {
|
||||
return i18n.global.t.call(i18n.global.locale, message)
|
||||
} else if (!key && Object.keys(siphonI18n('zh-CN')).includes(message)) {
|
||||
// 兼容非嵌套形式的国际化写法
|
||||
return i18n.global.t.call(i18n.global.locale, message);
|
||||
return i18n.global.t.call(i18n.global.locale, message)
|
||||
} else {
|
||||
return message;
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
/** 此函数只是配合i18n Ally插件来进行国际化智能提示,并无实际意义(只对提示起作用),如果不需要国际化可删除 */
|
||||
export const $t = (key: string) => key;
|
||||
export const $t = (key: string) => key
|
||||
|
||||
export const i18n: I18n = createI18n({
|
||||
legacy: false,
|
||||
locale:
|
||||
storageLocal().getItem<StorageConfigs>("responsive-locale")?.locale ?? "zh",
|
||||
fallbackLocale: "en",
|
||||
storageLocal().getItem<StorageConfigs>('responsive-locale')?.locale ?? 'zh',
|
||||
fallbackLocale: 'en',
|
||||
messages: localesConfigs
|
||||
});
|
||||
})
|
||||
|
||||
export function useI18n(app: App) {
|
||||
app.use(i18n);
|
||||
app.use(i18n)
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
// import "@/utils/sso";
|
||||
import { getConfig } from "@/config";
|
||||
import NProgress from "@/utils/progress";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { sessionKey, type DataInfo } from "@/utils/auth";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import { getConfig } from '@/config'
|
||||
import NProgress from '@/utils/progress'
|
||||
import { transformI18n } from '@/plugins/i18n'
|
||||
import { sessionKey, type DataInfo } from '@/utils/auth'
|
||||
import { useMultiTagsStoreHook } from '@/store/modules/multiTags'
|
||||
import { usePermissionStoreHook } from '@/store/modules/permission'
|
||||
import {
|
||||
Router,
|
||||
createRouter,
|
||||
RouteRecordRaw,
|
||||
RouteComponent
|
||||
} from "vue-router";
|
||||
} from 'vue-router'
|
||||
import {
|
||||
ascending,
|
||||
initRouter,
|
||||
@ -20,44 +20,44 @@ import {
|
||||
handleAliveRoute,
|
||||
formatTwoStageRoutes,
|
||||
formatFlatteningRoutes
|
||||
} from "./utils";
|
||||
import { buildHierarchyTree } from "@/utils/tree";
|
||||
import { isUrl, openLink, storageSession } from "@pureadmin/utils";
|
||||
} from './utils'
|
||||
import { buildHierarchyTree } from '@/utils/tree'
|
||||
import { isUrl, openLink, storageSession } from '@pureadmin/utils'
|
||||
|
||||
import remainingRouter from "./modules/remaining";
|
||||
import remainingRouter from './modules/remaining'
|
||||
|
||||
/** 自动导入全部静态路由,无需再手动引入!匹配 src/router/modules 目录(任何嵌套级别)中具有 .ts 扩展名的所有文件,除了 remaining.ts 文件
|
||||
* 如何匹配所有文件请看:https://github.com/mrmlnc/fast-glob#basic-syntax
|
||||
* 如何排除文件请看:https://cn.vitejs.dev/guide/features.html#negative-patterns
|
||||
*/
|
||||
const modules: Record<string, any> = import.meta.glob(
|
||||
["./modules/**/*.ts", "!./modules/**/remaining.ts"],
|
||||
['./modules/**/*.ts', '!./modules/**/remaining.ts'],
|
||||
{
|
||||
eager: true
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
/** 原始静态路由(未做任何处理) */
|
||||
const routes = [];
|
||||
const routes = []
|
||||
|
||||
Object.keys(modules).forEach(key => {
|
||||
routes.push(modules[key].default);
|
||||
});
|
||||
routes.push(modules[key].default)
|
||||
})
|
||||
|
||||
/** 导出处理后的静态路由(三级及以上的路由全部拍成二级) */
|
||||
export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
|
||||
formatFlatteningRoutes(buildHierarchyTree(ascending(routes)))
|
||||
);
|
||||
)
|
||||
|
||||
/** 用于渲染菜单,保持原始层级 */
|
||||
export const constantMenus: Array<RouteComponent> = ascending(routes).concat(
|
||||
...remainingRouter
|
||||
);
|
||||
)
|
||||
|
||||
/** 不参与菜单的路由 */
|
||||
export const remainingPaths = Object.keys(remainingRouter).map(v => {
|
||||
return remainingRouter[v].path;
|
||||
});
|
||||
return remainingRouter[v].path
|
||||
})
|
||||
|
||||
/** 创建路由实例 */
|
||||
export const router: Router = createRouter({
|
||||
@ -67,115 +67,114 @@ export const router: Router = createRouter({
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
return new Promise(resolve => {
|
||||
if (savedPosition) {
|
||||
return savedPosition;
|
||||
return savedPosition
|
||||
} else {
|
||||
if (from.meta.saveSrollTop) {
|
||||
const top: number =
|
||||
document.documentElement.scrollTop || document.body.scrollTop;
|
||||
resolve({ left: 0, top });
|
||||
document.documentElement.scrollTop || document.body.scrollTop
|
||||
resolve({ left: 0, top })
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
/** 重置路由 */
|
||||
export function resetRouter() {
|
||||
router.getRoutes().forEach(route => {
|
||||
const { name, meta } = route;
|
||||
const { name, meta } = route
|
||||
if (name && router.hasRoute(name) && meta?.backstage) {
|
||||
router.removeRoute(name);
|
||||
router.removeRoute(name)
|
||||
router.options.routes = formatTwoStageRoutes(
|
||||
formatFlatteningRoutes(buildHierarchyTree(ascending(routes)))
|
||||
);
|
||||
)
|
||||
}
|
||||
});
|
||||
usePermissionStoreHook().clearAllCachePage();
|
||||
})
|
||||
usePermissionStoreHook().clearAllCachePage()
|
||||
}
|
||||
|
||||
/** 路由白名单 */
|
||||
const whiteList = ["/login"];
|
||||
const whiteList = ['/login']
|
||||
|
||||
router.beforeEach((to: toRouteType, _from, next) => {
|
||||
if (to.meta?.keepAlive) {
|
||||
const newMatched = to.matched;
|
||||
handleAliveRoute(newMatched, "add");
|
||||
const newMatched = to.matched
|
||||
handleAliveRoute(newMatched, 'add')
|
||||
// 页面整体刷新和点击标签页刷新
|
||||
if (_from.name === undefined || _from.name === "Redirect") {
|
||||
handleAliveRoute(newMatched);
|
||||
if (_from.name === undefined || _from.name === 'Redirect') {
|
||||
handleAliveRoute(newMatched)
|
||||
}
|
||||
}
|
||||
const userInfo = storageSession().getItem<DataInfo<number>>(sessionKey);
|
||||
NProgress.start();
|
||||
const externalLink = isUrl(to?.name as string);
|
||||
const userInfo = storageSession().getItem<DataInfo<number>>(sessionKey)
|
||||
NProgress.start()
|
||||
const externalLink = isUrl(to?.name as string)
|
||||
if (!externalLink) {
|
||||
to.matched.some(item => {
|
||||
if (!item.meta.title) return "";
|
||||
const Title = getConfig().Title;
|
||||
if (Title)
|
||||
document.title = `${transformI18n(item.meta.title)} | ${Title}`;
|
||||
else document.title = transformI18n(item.meta.title);
|
||||
});
|
||||
if (!item.meta.title) return ''
|
||||
const Title = getConfig().Title
|
||||
if (Title) document.title = `${transformI18n(item.meta.title)} | ${Title}`
|
||||
else document.title = transformI18n(item.meta.title)
|
||||
})
|
||||
}
|
||||
/** 如果已经登录并存在登录信息后不能跳转到路由白名单,而是继续保持在当前页面 */
|
||||
function toCorrectRoute() {
|
||||
whiteList.includes(to.fullPath) ? next(_from.fullPath) : next();
|
||||
whiteList.includes(to.fullPath) ? next(_from.fullPath) : next()
|
||||
}
|
||||
if (userInfo) {
|
||||
// 无权限跳转403页面
|
||||
if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) {
|
||||
next({ path: "/error/403" });
|
||||
next({ path: '/error/403' })
|
||||
}
|
||||
if (_from?.name) {
|
||||
// name为超链接
|
||||
if (externalLink) {
|
||||
openLink(to?.name as string);
|
||||
NProgress.done();
|
||||
openLink(to?.name as string)
|
||||
NProgress.done()
|
||||
} else {
|
||||
toCorrectRoute();
|
||||
toCorrectRoute()
|
||||
}
|
||||
} else {
|
||||
// 刷新
|
||||
if (
|
||||
usePermissionStoreHook().wholeMenus.length === 0 &&
|
||||
to.path !== "/login"
|
||||
to.path !== '/login'
|
||||
) {
|
||||
initRouter().then((router: Router) => {
|
||||
if (!useMultiTagsStoreHook().getMultiTagsCache) {
|
||||
const { path } = to;
|
||||
const { path } = to
|
||||
const route = findRouteByPath(
|
||||
path,
|
||||
router.options.routes[0].children
|
||||
);
|
||||
)
|
||||
// query、params模式路由传参数的标签页不在此处处理
|
||||
if (route && route.meta?.title) {
|
||||
useMultiTagsStoreHook().handleTags("push", {
|
||||
useMultiTagsStoreHook().handleTags('push', {
|
||||
path: route.path,
|
||||
name: route.name,
|
||||
meta: route.meta
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
router.push(to.fullPath);
|
||||
});
|
||||
router.push(to.fullPath)
|
||||
})
|
||||
}
|
||||
toCorrectRoute();
|
||||
toCorrectRoute()
|
||||
}
|
||||
} else {
|
||||
if (to.path !== "/login") {
|
||||
if (to.path !== '/login') {
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
next();
|
||||
next()
|
||||
} else {
|
||||
next({ path: "/login" });
|
||||
next({ path: '/login' })
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
next()
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
NProgress.done()
|
||||
})
|
||||
|
||||
export default router;
|
||||
export default router
|
||||
|
62
src/router/modules/basicData.ts
Normal file
62
src/router/modules/basicData.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { $t } from '@/plugins/i18n'
|
||||
|
||||
// 最简代码,也就是这些字段必须有
|
||||
export default {
|
||||
path: '/basic',
|
||||
redirect: '/basic/liveData',
|
||||
meta: {
|
||||
title: $t('menus.basicData'),
|
||||
icon: 'lineChartLine'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/basic/liveData',
|
||||
name: 'LiveData',
|
||||
component: () => import('@/views/basicData/LiveData.vue'),
|
||||
meta: {
|
||||
title: $t('menus.liveData'),
|
||||
roles: ['admin']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/basic/userData',
|
||||
name: 'UserData',
|
||||
component: () => import('@/views/basicData/queryUser/index.vue'),
|
||||
meta: {
|
||||
title: $t('menus.userData')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/basic/queryUser',
|
||||
name: 'QueryUser',
|
||||
component: () => import('@/views/basicData/queryUser/queryUser.vue'),
|
||||
meta: {
|
||||
title: $t('menus.searchUser')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/basic/gameData',
|
||||
name: 'GameData',
|
||||
component: () => import('@/views/basicData/GameData.vue'),
|
||||
meta: {
|
||||
title: $t('menus.gameData')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/basic/clubData',
|
||||
name: 'ClubData',
|
||||
component: () => import('@/views/basicData/queryClub/index.vue'),
|
||||
meta: {
|
||||
title: $t('menus.clubData')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/basic/queryClub',
|
||||
name: 'QueryData',
|
||||
component: () => import('@/views/basicData/queryClub/queryClub.vue'),
|
||||
meta: {
|
||||
title: $t('menus.searchClub')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -1,38 +1,38 @@
|
||||
import { $t } from "@/plugins/i18n";
|
||||
import { $t } from '@/plugins/i18n'
|
||||
|
||||
export default {
|
||||
path: "/error",
|
||||
redirect: "/error/403",
|
||||
path: '/error',
|
||||
redirect: '/error/403',
|
||||
meta: {
|
||||
icon: "informationLine",
|
||||
title: $t("menus.hsabnormal"),
|
||||
icon: 'signalWifiErrorLine',
|
||||
title: $t('menus.hsabnormal'),
|
||||
// showLink: false,
|
||||
rank: 9
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/error/403",
|
||||
name: "403",
|
||||
component: () => import("@/views/error/403.vue"),
|
||||
path: '/error/403',
|
||||
name: '403',
|
||||
component: () => import('@/views/error/403.vue'),
|
||||
meta: {
|
||||
title: $t("menus.hsfourZeroOne")
|
||||
title: $t('menus.hsfourZeroOne')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/error/404",
|
||||
name: "404",
|
||||
component: () => import("@/views/error/404.vue"),
|
||||
path: '/error/404',
|
||||
name: '404',
|
||||
component: () => import('@/views/error/404.vue'),
|
||||
meta: {
|
||||
title: $t("menus.hsfourZeroFour")
|
||||
title: $t('menus.hsfourZeroFour')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/error/500",
|
||||
name: "500",
|
||||
component: () => import("@/views/error/500.vue"),
|
||||
path: '/error/500',
|
||||
name: '500',
|
||||
component: () => import('@/views/error/500.vue'),
|
||||
meta: {
|
||||
title: $t("menus.hsFive")
|
||||
title: $t('menus.hsFive')
|
||||
}
|
||||
}
|
||||
]
|
||||
} as RouteConfigsTable;
|
||||
} as RouteConfigsTable
|
||||
|
@ -1,24 +1,24 @@
|
||||
import { $t } from "@/plugins/i18n";
|
||||
const Layout = () => import("@/layout/index.vue");
|
||||
import { $t } from '@/plugins/i18n'
|
||||
const Layout = () => import('@/layout/index.vue')
|
||||
|
||||
export default {
|
||||
path: "/",
|
||||
name: "Home",
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: Layout,
|
||||
redirect: "/welcome",
|
||||
redirect: '/welcome',
|
||||
meta: {
|
||||
icon: "homeFilled",
|
||||
title: $t("menus.hshome"),
|
||||
icon: 'homeSmileLine',
|
||||
title: $t('menus.hshome'),
|
||||
rank: 0
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/welcome",
|
||||
name: "Welcome",
|
||||
component: () => import("@/views/welcome/index.vue"),
|
||||
path: '/welcome',
|
||||
name: 'Welcome',
|
||||
component: () => import('@/views/welcome/index.vue'),
|
||||
meta: {
|
||||
title: $t("menus.hshome")
|
||||
title: $t('menus.hshome')
|
||||
}
|
||||
}
|
||||
]
|
||||
} as RouteConfigsTable;
|
||||
} as RouteConfigsTable
|
||||
|
@ -1,32 +1,32 @@
|
||||
import { $t } from "@/plugins/i18n";
|
||||
const Layout = () => import("@/layout/index.vue");
|
||||
import { $t } from '@/plugins/i18n'
|
||||
const Layout = () => import('@/layout/index.vue')
|
||||
|
||||
export default [
|
||||
{
|
||||
path: "/login",
|
||||
name: "Login",
|
||||
component: () => import("@/views/login/index.vue"),
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
meta: {
|
||||
title: $t("menus.hslogin"),
|
||||
title: $t('menus.hslogin'),
|
||||
showLink: false,
|
||||
rank: 101
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/redirect",
|
||||
path: '/redirect',
|
||||
component: Layout,
|
||||
meta: {
|
||||
icon: "homeFilled",
|
||||
title: $t("menus.hshome"),
|
||||
icon: 'homeFilled',
|
||||
title: $t('menus.hshome'),
|
||||
showLink: false,
|
||||
rank: 102
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/redirect/:path(.*)",
|
||||
name: "Redirect",
|
||||
component: () => import("@/layout/redirect.vue")
|
||||
path: '/redirect/:path(.*)',
|
||||
name: 'Redirect',
|
||||
component: () => import('@/layout/redirect.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
] as Array<RouteConfigsTable>;
|
||||
] as Array<RouteConfigsTable>
|
||||
|
@ -5,11 +5,11 @@ import {
|
||||
createWebHistory,
|
||||
createWebHashHistory,
|
||||
RouteRecordNormalized
|
||||
} from "vue-router";
|
||||
import { router } from "./index";
|
||||
import { isProxy, toRaw } from "vue";
|
||||
import { useTimeoutFn } from "@vueuse/core";
|
||||
import { RouteConfigs } from "@/layout/types";
|
||||
} from 'vue-router'
|
||||
import { router } from './index'
|
||||
import { isProxy, toRaw } from 'vue'
|
||||
import { useTimeoutFn } from '@vueuse/core'
|
||||
import { RouteConfigs } from '@/layout/types'
|
||||
import {
|
||||
isString,
|
||||
cloneDeep,
|
||||
@ -17,59 +17,59 @@ import {
|
||||
intersection,
|
||||
storageSession,
|
||||
isIncludeAllChildren
|
||||
} from "@pureadmin/utils";
|
||||
import { getConfig } from "@/config";
|
||||
import { buildHierarchyTree } from "@/utils/tree";
|
||||
import { sessionKey, type DataInfo } from "@/utils/auth";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
const IFrame = () => import("@/layout/frameView.vue");
|
||||
} from '@pureadmin/utils'
|
||||
import { getConfig } from '@/config'
|
||||
import { buildHierarchyTree } from '@/utils/tree'
|
||||
import { sessionKey, type DataInfo } from '@/utils/auth'
|
||||
import { usePermissionStoreHook } from '@/store/modules/permission'
|
||||
const IFrame = () => import('@/layout/frameView.vue')
|
||||
// https://cn.vitejs.dev/guide/features.html#glob-import
|
||||
const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}");
|
||||
const modulesRoutes = import.meta.glob('/src/views/**/*.{vue,tsx}')
|
||||
|
||||
// 动态路由
|
||||
import { getAsyncRoutes } from "@/api/routes";
|
||||
import { getAsyncRoutes } from '@/api/routes'
|
||||
|
||||
function handRank(routeInfo: any) {
|
||||
const { name, path, parentId, meta } = routeInfo;
|
||||
const { name, path, parentId, meta } = routeInfo
|
||||
return isAllEmpty(parentId)
|
||||
? isAllEmpty(meta?.rank) ||
|
||||
(meta?.rank === 0 && name !== "Home" && path !== "/")
|
||||
(meta?.rank === 0 && name !== 'Home' && path !== '/')
|
||||
? true
|
||||
: false
|
||||
: false;
|
||||
: false
|
||||
}
|
||||
|
||||
/** 按照路由中meta下的rank等级升序来排序路由 */
|
||||
function ascending(arr: any[]) {
|
||||
arr.forEach((v, index) => {
|
||||
// 当rank不存在时,根据顺序自动创建,首页路由永远在第一位
|
||||
if (handRank(v)) v.meta.rank = index + 2;
|
||||
});
|
||||
if (handRank(v)) v.meta.rank = index + 2
|
||||
})
|
||||
return arr.sort(
|
||||
(a: { meta: { rank: number } }, b: { meta: { rank: number } }) => {
|
||||
return a?.meta.rank - b?.meta.rank;
|
||||
return a?.meta.rank - b?.meta.rank
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
/** 过滤meta中showLink为false的菜单 */
|
||||
function filterTree(data: RouteComponent[]) {
|
||||
const newTree = cloneDeep(data).filter(
|
||||
(v: { meta: { showLink: boolean } }) => v.meta?.showLink !== false
|
||||
);
|
||||
)
|
||||
newTree.forEach(
|
||||
(v: { children }) => v.children && (v.children = filterTree(v.children))
|
||||
);
|
||||
return newTree;
|
||||
)
|
||||
return newTree
|
||||
}
|
||||
|
||||
/** 过滤children长度为0的的目录,当目录下没有菜单时,会过滤此目录,目录没有赋予roles权限,当目录下只要有一个菜单有显示权限,那么此目录就会显示 */
|
||||
function filterChildrenTree(data: RouteComponent[]) {
|
||||
const newTree = cloneDeep(data).filter((v: any) => v?.children?.length !== 0);
|
||||
const newTree = cloneDeep(data).filter((v: any) => v?.children?.length !== 0)
|
||||
newTree.forEach(
|
||||
(v: { children }) => v.children && (v.children = filterTree(v.children))
|
||||
);
|
||||
return newTree;
|
||||
)
|
||||
return newTree
|
||||
}
|
||||
|
||||
/** 判断两个数组彼此是否存在相同值 */
|
||||
@ -78,30 +78,30 @@ function isOneOfArray(a: Array<string>, b: Array<string>) {
|
||||
? intersection(a, b).length > 0
|
||||
? true
|
||||
: false
|
||||
: true;
|
||||
: true
|
||||
}
|
||||
|
||||
/** 从sessionStorage里取出当前登陆用户的角色roles,过滤无权限的菜单 */
|
||||
function filterNoPermissionTree(data: RouteComponent[]) {
|
||||
const currentRoles =
|
||||
storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? [];
|
||||
storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? []
|
||||
const newTree = cloneDeep(data).filter((v: any) =>
|
||||
isOneOfArray(v.meta?.roles, currentRoles)
|
||||
);
|
||||
)
|
||||
newTree.forEach(
|
||||
(v: any) => v.children && (v.children = filterNoPermissionTree(v.children))
|
||||
);
|
||||
return filterChildrenTree(newTree);
|
||||
)
|
||||
return filterChildrenTree(newTree)
|
||||
}
|
||||
|
||||
/** 批量删除缓存路由(keepalive) */
|
||||
function delAliveRoutes(delAliveRouteList: Array<RouteConfigs>) {
|
||||
delAliveRouteList.forEach(route => {
|
||||
usePermissionStoreHook().cacheOperate({
|
||||
mode: "delete",
|
||||
mode: 'delete',
|
||||
name: route?.name
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** 通过path获取父级路径 */
|
||||
@ -109,60 +109,60 @@ function getParentPaths(path: string, routes: RouteRecordRaw[]) {
|
||||
// 深度遍历查找
|
||||
function dfs(routes: RouteRecordRaw[], path: string, parents: string[]) {
|
||||
for (let i = 0; i < routes.length; i++) {
|
||||
const item = routes[i];
|
||||
const item = routes[i]
|
||||
// 找到path则返回父级path
|
||||
if (item.path === path) return parents;
|
||||
if (item.path === path) return parents
|
||||
// children不存在或为空则不递归
|
||||
if (!item.children || !item.children.length) continue;
|
||||
if (!item.children || !item.children.length) continue
|
||||
// 往下查找时将当前path入栈
|
||||
parents.push(item.path);
|
||||
parents.push(item.path)
|
||||
|
||||
if (dfs(item.children, path, parents).length) return parents;
|
||||
if (dfs(item.children, path, parents).length) return parents
|
||||
// 深度遍历查找未找到时当前path 出栈
|
||||
parents.pop();
|
||||
parents.pop()
|
||||
}
|
||||
// 未找到时返回空数组
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
|
||||
return dfs(routes, path, []);
|
||||
return dfs(routes, path, [])
|
||||
}
|
||||
|
||||
/** 查找对应path的路由信息 */
|
||||
function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
|
||||
let res = routes.find((item: { path: string }) => item.path == path);
|
||||
let res = routes.find((item: { path: string }) => item.path == path)
|
||||
if (res) {
|
||||
return isProxy(res) ? toRaw(res) : res;
|
||||
return isProxy(res) ? toRaw(res) : res
|
||||
} else {
|
||||
for (let i = 0; i < routes.length; i++) {
|
||||
if (
|
||||
routes[i].children instanceof Array &&
|
||||
routes[i].children.length > 0
|
||||
) {
|
||||
res = findRouteByPath(path, routes[i].children);
|
||||
res = findRouteByPath(path, routes[i].children)
|
||||
if (res) {
|
||||
return isProxy(res) ? toRaw(res) : res;
|
||||
return isProxy(res) ? toRaw(res) : res
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function addPathMatch() {
|
||||
if (!router.hasRoute("pathMatch")) {
|
||||
if (!router.hasRoute('pathMatch')) {
|
||||
router.addRoute({
|
||||
path: "/:pathMatch(.*)",
|
||||
name: "pathMatch",
|
||||
redirect: "/error/404"
|
||||
});
|
||||
path: '/:pathMatch(.*)',
|
||||
name: 'pathMatch',
|
||||
redirect: '/error/404'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理动态路由(后端返回的路由) */
|
||||
function handleAsyncRoutes(routeList) {
|
||||
if (routeList.length === 0) {
|
||||
usePermissionStoreHook().handleWholeMenus(routeList);
|
||||
usePermissionStoreHook().handleWholeMenus(routeList)
|
||||
} else {
|
||||
formatFlatteningRoutes(addAsyncRoutes(routeList)).map(
|
||||
(v: RouteRecordRaw) => {
|
||||
@ -172,52 +172,52 @@ function handleAsyncRoutes(routeList) {
|
||||
value => value.path === v.path
|
||||
) !== -1
|
||||
) {
|
||||
return;
|
||||
return
|
||||
} else {
|
||||
// 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转
|
||||
router.options.routes[0].children.push(v);
|
||||
router.options.routes[0].children.push(v)
|
||||
// 最终路由进行升序
|
||||
ascending(router.options.routes[0].children);
|
||||
if (!router.hasRoute(v?.name)) router.addRoute(v);
|
||||
ascending(router.options.routes[0].children)
|
||||
if (!router.hasRoute(v?.name)) router.addRoute(v)
|
||||
const flattenRouters: any = router
|
||||
.getRoutes()
|
||||
.find(n => n.path === "/");
|
||||
router.addRoute(flattenRouters);
|
||||
.find(n => n.path === '/')
|
||||
router.addRoute(flattenRouters)
|
||||
}
|
||||
}
|
||||
);
|
||||
usePermissionStoreHook().handleWholeMenus(routeList);
|
||||
)
|
||||
usePermissionStoreHook().handleWholeMenus(routeList)
|
||||
}
|
||||
addPathMatch();
|
||||
addPathMatch()
|
||||
}
|
||||
|
||||
/** 初始化路由(`new Promise` 写法防止在异步请求中造成无限循环)*/
|
||||
function initRouter() {
|
||||
if (getConfig()?.CachingAsyncRoutes) {
|
||||
// 开启动态路由缓存本地sessionStorage
|
||||
const key = "async-routes";
|
||||
const asyncRouteList = storageSession().getItem(key) as any;
|
||||
const key = 'async-routes'
|
||||
const asyncRouteList = storageSession().getItem(key) as any
|
||||
if (asyncRouteList && asyncRouteList?.length > 0) {
|
||||
return new Promise(resolve => {
|
||||
handleAsyncRoutes(asyncRouteList);
|
||||
resolve(router);
|
||||
});
|
||||
handleAsyncRoutes(asyncRouteList)
|
||||
resolve(router)
|
||||
})
|
||||
} else {
|
||||
return new Promise(resolve => {
|
||||
getAsyncRoutes().then(({ data }) => {
|
||||
handleAsyncRoutes(cloneDeep(data));
|
||||
storageSession().setItem(key, data);
|
||||
resolve(router);
|
||||
});
|
||||
});
|
||||
handleAsyncRoutes(cloneDeep(data))
|
||||
storageSession().setItem(key, data)
|
||||
resolve(router)
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return new Promise(resolve => {
|
||||
getAsyncRoutes().then(({ data }) => {
|
||||
handleAsyncRoutes(cloneDeep(data));
|
||||
resolve(router);
|
||||
});
|
||||
});
|
||||
handleAsyncRoutes(cloneDeep(data))
|
||||
resolve(router)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,16 +227,16 @@ function initRouter() {
|
||||
* @returns 返回处理后的一维路由
|
||||
*/
|
||||
function formatFlatteningRoutes(routesList: RouteRecordRaw[]) {
|
||||
if (routesList.length === 0) return routesList;
|
||||
let hierarchyList = buildHierarchyTree(routesList);
|
||||
if (routesList.length === 0) return routesList
|
||||
let hierarchyList = buildHierarchyTree(routesList)
|
||||
for (let i = 0; i < hierarchyList.length; i++) {
|
||||
if (hierarchyList[i].children) {
|
||||
hierarchyList = hierarchyList
|
||||
.slice(0, i + 1)
|
||||
.concat(hierarchyList[i].children, hierarchyList.slice(i + 1));
|
||||
.concat(hierarchyList[i].children, hierarchyList.slice(i + 1))
|
||||
}
|
||||
}
|
||||
return hierarchyList;
|
||||
return hierarchyList
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,10 +246,10 @@ function formatFlatteningRoutes(routesList: RouteRecordRaw[]) {
|
||||
* @returns 返回将一维数组重新处理成规定路由的格式
|
||||
*/
|
||||
function formatTwoStageRoutes(routesList: RouteRecordRaw[]) {
|
||||
if (routesList.length === 0) return routesList;
|
||||
const newRoutesList: RouteRecordRaw[] = [];
|
||||
if (routesList.length === 0) return routesList
|
||||
const newRoutesList: RouteRecordRaw[] = []
|
||||
routesList.forEach((v: RouteRecordRaw) => {
|
||||
if (v.path === "/") {
|
||||
if (v.path === '/') {
|
||||
newRoutesList.push({
|
||||
component: v.component,
|
||||
name: v.name,
|
||||
@ -257,108 +257,108 @@ function formatTwoStageRoutes(routesList: RouteRecordRaw[]) {
|
||||
redirect: v.redirect,
|
||||
meta: v.meta,
|
||||
children: []
|
||||
});
|
||||
})
|
||||
} else {
|
||||
newRoutesList[0]?.children.push({ ...v });
|
||||
newRoutesList[0]?.children.push({ ...v })
|
||||
}
|
||||
});
|
||||
return newRoutesList;
|
||||
})
|
||||
return newRoutesList
|
||||
}
|
||||
|
||||
/** 处理缓存路由(添加、删除、刷新) */
|
||||
function handleAliveRoute(matched: RouteRecordNormalized[], mode?: string) {
|
||||
switch (mode) {
|
||||
case "add":
|
||||
case 'add':
|
||||
matched.forEach(v => {
|
||||
usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name });
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
usePermissionStoreHook().cacheOperate({ mode: 'add', name: v.name })
|
||||
})
|
||||
break
|
||||
case 'delete':
|
||||
usePermissionStoreHook().cacheOperate({
|
||||
mode: "delete",
|
||||
mode: 'delete',
|
||||
name: matched[matched.length - 1].name
|
||||
});
|
||||
break;
|
||||
})
|
||||
break
|
||||
default:
|
||||
usePermissionStoreHook().cacheOperate({
|
||||
mode: "delete",
|
||||
mode: 'delete',
|
||||
name: matched[matched.length - 1].name
|
||||
});
|
||||
})
|
||||
useTimeoutFn(() => {
|
||||
matched.forEach(v => {
|
||||
usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name });
|
||||
});
|
||||
}, 100);
|
||||
usePermissionStoreHook().cacheOperate({ mode: 'add', name: v.name })
|
||||
})
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
/** 过滤后端传来的动态路由 重新生成规范路由 */
|
||||
function addAsyncRoutes(arrRoutes: Array<RouteRecordRaw>) {
|
||||
if (!arrRoutes || !arrRoutes.length) return;
|
||||
const modulesRoutesKeys = Object.keys(modulesRoutes);
|
||||
if (!arrRoutes || !arrRoutes.length) return
|
||||
const modulesRoutesKeys = Object.keys(modulesRoutes)
|
||||
arrRoutes.forEach((v: RouteRecordRaw) => {
|
||||
// 将backstage属性加入meta,标识此路由为后端返回路由
|
||||
v.meta.backstage = true;
|
||||
v.meta.backstage = true
|
||||
// 父级的redirect属性取值:如果子级存在且父级的redirect属性不存在,默认取第一个子级的path;如果子级存在且父级的redirect属性存在,取存在的redirect属性,会覆盖默认值
|
||||
if (v?.children && v.children.length && !v.redirect)
|
||||
v.redirect = v.children[0].path;
|
||||
v.redirect = v.children[0].path
|
||||
// 父级的name属性取值:如果子级存在且父级的name属性不存在,默认取第一个子级的name;如果子级存在且父级的name属性存在,取存在的name属性,会覆盖默认值(注意:测试中发现父级的name不能和子级name重复,如果重复会造成重定向无效(跳转404),所以这里给父级的name起名的时候后面会自动加上`Parent`,避免重复)
|
||||
if (v?.children && v.children.length && !v.name)
|
||||
v.name = (v.children[0].name as string) + "Parent";
|
||||
v.name = (v.children[0].name as string) + 'Parent'
|
||||
if (v.meta?.frameSrc) {
|
||||
v.component = IFrame;
|
||||
v.component = IFrame
|
||||
} else {
|
||||
// 对后端传component组件路径和不传做兼容(如果后端传component组件路径,那么path可以随便写,如果不传,component组件路径会跟path保持一致)
|
||||
const index = v?.component
|
||||
? modulesRoutesKeys.findIndex(ev => ev.includes(v.component as any))
|
||||
: modulesRoutesKeys.findIndex(ev => ev.includes(v.path));
|
||||
v.component = modulesRoutes[modulesRoutesKeys[index]];
|
||||
: modulesRoutesKeys.findIndex(ev => ev.includes(v.path))
|
||||
v.component = modulesRoutes[modulesRoutesKeys[index]]
|
||||
}
|
||||
if (v?.children && v.children.length) {
|
||||
addAsyncRoutes(v.children);
|
||||
addAsyncRoutes(v.children)
|
||||
}
|
||||
});
|
||||
return arrRoutes;
|
||||
})
|
||||
return arrRoutes
|
||||
}
|
||||
|
||||
/** 获取路由历史模式 https://next.router.vuejs.org/zh/guide/essentials/history-mode.html */
|
||||
function getHistoryMode(routerHistory): RouterHistory {
|
||||
// len为1 代表只有历史模式 为2 代表历史模式中存在base参数 https://next.router.vuejs.org/zh/api/#%E5%8F%82%E6%95%B0-1
|
||||
const historyMode = routerHistory.split(",");
|
||||
const leftMode = historyMode[0];
|
||||
const rightMode = historyMode[1];
|
||||
const historyMode = routerHistory.split(',')
|
||||
const leftMode = historyMode[0]
|
||||
const rightMode = historyMode[1]
|
||||
// no param
|
||||
if (historyMode.length === 1) {
|
||||
if (leftMode === "hash") {
|
||||
return createWebHashHistory("");
|
||||
} else if (leftMode === "h5") {
|
||||
return createWebHistory("");
|
||||
if (leftMode === 'hash') {
|
||||
return createWebHashHistory('')
|
||||
} else if (leftMode === 'h5') {
|
||||
return createWebHistory('')
|
||||
}
|
||||
} //has param
|
||||
else if (historyMode.length === 2) {
|
||||
if (leftMode === "hash") {
|
||||
return createWebHashHistory(rightMode);
|
||||
} else if (leftMode === "h5") {
|
||||
return createWebHistory(rightMode);
|
||||
if (leftMode === 'hash') {
|
||||
return createWebHashHistory(rightMode)
|
||||
} else if (leftMode === 'h5') {
|
||||
return createWebHistory(rightMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取当前页面按钮级别的权限 */
|
||||
function getAuths(): Array<string> {
|
||||
return router.currentRoute.value.meta.auths as Array<string>;
|
||||
return router.currentRoute.value.meta.auths as Array<string>
|
||||
}
|
||||
|
||||
/** 是否有按钮级别的权限 */
|
||||
function hasAuth(value: string | Array<string>): boolean {
|
||||
if (!value) return false;
|
||||
if (!value) return false
|
||||
/** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */
|
||||
const metaAuths = getAuths();
|
||||
if (!metaAuths) return false;
|
||||
const metaAuths = getAuths()
|
||||
if (!metaAuths) return false
|
||||
const isAuths = isString(value)
|
||||
? metaAuths.includes(value)
|
||||
: isIncludeAllChildren(value, metaAuths);
|
||||
return isAuths ? true : false;
|
||||
: isIncludeAllChildren(value, metaAuths)
|
||||
return isAuths ? true : false
|
||||
}
|
||||
|
||||
export {
|
||||
@ -378,4 +378,4 @@ export {
|
||||
formatTwoStageRoutes,
|
||||
formatFlatteningRoutes,
|
||||
filterNoPermissionTree
|
||||
};
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
const store = createPinia();
|
||||
import type { App } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
const store = createPinia()
|
||||
|
||||
export function setupStore(app: App<Element>) {
|
||||
app.use(store);
|
||||
app.use(store)
|
||||
}
|
||||
|
||||
export { store };
|
||||
export { store }
|
||||
|
@ -1,65 +1,64 @@
|
||||
import { store } from "@/store";
|
||||
import { appType } from "./types";
|
||||
import { defineStore } from "pinia";
|
||||
import { getConfig } from "@/config";
|
||||
import { deviceDetection, storageLocal } from "@pureadmin/utils";
|
||||
import { store } from '@/store'
|
||||
import { appType } from './types'
|
||||
import { defineStore } from 'pinia'
|
||||
import { getConfig } from '@/config'
|
||||
import { deviceDetection, storageLocal } from '@pureadmin/utils'
|
||||
|
||||
export const useAppStore = defineStore({
|
||||
id: "pure-app",
|
||||
id: 'pure-app',
|
||||
state: (): appType => ({
|
||||
sidebar: {
|
||||
opened:
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout")
|
||||
storageLocal().getItem<StorageConfigs>('responsive-layout')
|
||||
?.sidebarStatus ?? getConfig().SidebarStatus,
|
||||
withoutAnimation: false,
|
||||
isClickCollapse: false
|
||||
},
|
||||
// 这里的layout用于监听容器拖拉后恢复对应的导航模式
|
||||
layout:
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout")?.layout ??
|
||||
storageLocal().getItem<StorageConfigs>('responsive-layout')?.layout ??
|
||||
getConfig().Layout,
|
||||
device: deviceDetection() ? "mobile" : "desktop"
|
||||
device: deviceDetection() ? 'mobile' : 'desktop'
|
||||
}),
|
||||
getters: {
|
||||
getSidebarStatus() {
|
||||
return this.sidebar.opened;
|
||||
return this.sidebar.opened
|
||||
},
|
||||
getDevice() {
|
||||
return this.device;
|
||||
return this.device
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
TOGGLE_SIDEBAR(opened?: boolean, resize?: string) {
|
||||
const layout =
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout");
|
||||
const layout = storageLocal().getItem<StorageConfigs>('responsive-layout')
|
||||
if (opened && resize) {
|
||||
this.sidebar.withoutAnimation = true;
|
||||
this.sidebar.opened = true;
|
||||
layout.sidebarStatus = true;
|
||||
this.sidebar.withoutAnimation = true
|
||||
this.sidebar.opened = true
|
||||
layout.sidebarStatus = true
|
||||
} else if (!opened && resize) {
|
||||
this.sidebar.withoutAnimation = true;
|
||||
this.sidebar.opened = false;
|
||||
layout.sidebarStatus = false;
|
||||
this.sidebar.withoutAnimation = true
|
||||
this.sidebar.opened = false
|
||||
layout.sidebarStatus = false
|
||||
} else if (!opened && !resize) {
|
||||
this.sidebar.withoutAnimation = false;
|
||||
this.sidebar.opened = !this.sidebar.opened;
|
||||
this.sidebar.isClickCollapse = !this.sidebar.opened;
|
||||
layout.sidebarStatus = this.sidebar.opened;
|
||||
this.sidebar.withoutAnimation = false
|
||||
this.sidebar.opened = !this.sidebar.opened
|
||||
this.sidebar.isClickCollapse = !this.sidebar.opened
|
||||
layout.sidebarStatus = this.sidebar.opened
|
||||
}
|
||||
storageLocal().setItem("responsive-layout", layout);
|
||||
storageLocal().setItem('responsive-layout', layout)
|
||||
},
|
||||
async toggleSideBar(opened?: boolean, resize?: string) {
|
||||
await this.TOGGLE_SIDEBAR(opened, resize);
|
||||
await this.TOGGLE_SIDEBAR(opened, resize)
|
||||
},
|
||||
toggleDevice(device: string) {
|
||||
this.device = device;
|
||||
this.device = device
|
||||
},
|
||||
setLayout(layout) {
|
||||
this.layout = layout;
|
||||
this.layout = layout
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
export function useAppStoreHook() {
|
||||
return useAppStore(store);
|
||||
return useAppStore(store)
|
||||
}
|
||||
|
@ -1,46 +1,45 @@
|
||||
import { store } from "@/store";
|
||||
import { defineStore } from "pinia";
|
||||
import { getConfig } from "@/config";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { store } from '@/store'
|
||||
import { defineStore } from 'pinia'
|
||||
import { getConfig } from '@/config'
|
||||
import { storageLocal } from '@pureadmin/utils'
|
||||
|
||||
export const useEpThemeStore = defineStore({
|
||||
id: "pure-epTheme",
|
||||
id: 'pure-epTheme',
|
||||
state: () => ({
|
||||
epThemeColor:
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout")
|
||||
storageLocal().getItem<StorageConfigs>('responsive-layout')
|
||||
?.epThemeColor ?? getConfig().EpThemeColor,
|
||||
epTheme:
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout")?.theme ??
|
||||
storageLocal().getItem<StorageConfigs>('responsive-layout')?.theme ??
|
||||
getConfig().Theme
|
||||
}),
|
||||
getters: {
|
||||
getEpThemeColor() {
|
||||
return this.epThemeColor;
|
||||
return this.epThemeColor
|
||||
},
|
||||
/** 用于mix导航模式下hamburger-svg的fill属性 */
|
||||
fill() {
|
||||
if (this.epTheme === "light") {
|
||||
return "#409eff";
|
||||
} else if (this.epTheme === "yellow") {
|
||||
return "#d25f00";
|
||||
if (this.epTheme === 'light') {
|
||||
return '#409eff'
|
||||
} else if (this.epTheme === 'yellow') {
|
||||
return '#d25f00'
|
||||
} else {
|
||||
return "#fff";
|
||||
return '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setEpThemeColor(newColor: string): void {
|
||||
const layout =
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout");
|
||||
this.epTheme = layout?.theme;
|
||||
this.epThemeColor = newColor;
|
||||
if (!layout) return;
|
||||
layout.epThemeColor = newColor;
|
||||
storageLocal().setItem("responsive-layout", layout);
|
||||
const layout = storageLocal().getItem<StorageConfigs>('responsive-layout')
|
||||
this.epTheme = layout?.theme
|
||||
this.epThemeColor = newColor
|
||||
if (!layout) return
|
||||
layout.epThemeColor = newColor
|
||||
storageLocal().setItem('responsive-layout', layout)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
export function useEpThemeStoreHook() {
|
||||
return useEpThemeStore(store);
|
||||
return useEpThemeStore(store)
|
||||
}
|
||||
|
@ -1,38 +1,38 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { store } from "@/store";
|
||||
import { routerArrays } from "@/layout/types";
|
||||
import { multiType, positionType } from "./types";
|
||||
import { isEqual, isBoolean, isUrl, storageLocal } from "@pureadmin/utils";
|
||||
import { defineStore } from 'pinia'
|
||||
import { store } from '@/store'
|
||||
import { routerArrays } from '@/layout/types'
|
||||
import { multiType, positionType } from './types'
|
||||
import { isEqual, isBoolean, isUrl, storageLocal } from '@pureadmin/utils'
|
||||
|
||||
export const useMultiTagsStore = defineStore({
|
||||
id: "pure-multiTags",
|
||||
id: 'pure-multiTags',
|
||||
state: () => ({
|
||||
// 存储标签页信息(路由信息)
|
||||
multiTags: storageLocal().getItem<StorageConfigs>("responsive-configure")
|
||||
multiTags: storageLocal().getItem<StorageConfigs>('responsive-configure')
|
||||
?.multiTagsCache
|
||||
? storageLocal().getItem<StorageConfigs>("responsive-tags")
|
||||
? storageLocal().getItem<StorageConfigs>('responsive-tags')
|
||||
: [...routerArrays],
|
||||
multiTagsCache: storageLocal().getItem<StorageConfigs>(
|
||||
"responsive-configure"
|
||||
'responsive-configure'
|
||||
)?.multiTagsCache
|
||||
}),
|
||||
getters: {
|
||||
getMultiTagsCache() {
|
||||
return this.multiTagsCache;
|
||||
return this.multiTagsCache
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
multiTagsCacheChange(multiTagsCache: boolean) {
|
||||
this.multiTagsCache = multiTagsCache;
|
||||
this.multiTagsCache = multiTagsCache
|
||||
if (multiTagsCache) {
|
||||
storageLocal().setItem("responsive-tags", this.multiTags);
|
||||
storageLocal().setItem('responsive-tags', this.multiTags)
|
||||
} else {
|
||||
storageLocal().removeItem("responsive-tags");
|
||||
storageLocal().removeItem('responsive-tags')
|
||||
}
|
||||
},
|
||||
tagsCache(multiTags) {
|
||||
this.getMultiTagsCache &&
|
||||
storageLocal().setItem("responsive-tags", multiTags);
|
||||
storageLocal().setItem('responsive-tags', multiTags)
|
||||
},
|
||||
handleTags<T>(
|
||||
mode: string,
|
||||
@ -40,42 +40,42 @@ export const useMultiTagsStore = defineStore({
|
||||
position?: positionType
|
||||
): T {
|
||||
switch (mode) {
|
||||
case "equal":
|
||||
this.multiTags = value;
|
||||
this.tagsCache(this.multiTags);
|
||||
break;
|
||||
case "push":
|
||||
case 'equal':
|
||||
this.multiTags = value
|
||||
this.tagsCache(this.multiTags)
|
||||
break
|
||||
case 'push':
|
||||
{
|
||||
const tagVal = value as multiType;
|
||||
const tagVal = value as multiType
|
||||
// 不添加到标签页
|
||||
if (tagVal?.meta?.hiddenTag) return;
|
||||
if (tagVal?.meta?.hiddenTag) return
|
||||
// 如果是外链无需添加信息到标签页
|
||||
if (isUrl(tagVal?.name)) return;
|
||||
if (isUrl(tagVal?.name)) return
|
||||
// 如果title为空拒绝添加空信息到标签页
|
||||
if (tagVal?.meta?.title.length === 0) return;
|
||||
if (tagVal?.meta?.title.length === 0) return
|
||||
// showLink:false 不添加到标签页
|
||||
if (isBoolean(tagVal?.meta?.showLink) && !tagVal?.meta?.showLink)
|
||||
return;
|
||||
const tagPath = tagVal.path;
|
||||
return
|
||||
const tagPath = tagVal.path
|
||||
// 判断tag是否已存在
|
||||
const tagHasExits = this.multiTags.some(tag => {
|
||||
return tag.path === tagPath;
|
||||
});
|
||||
return tag.path === tagPath
|
||||
})
|
||||
|
||||
// 判断tag中的query键值是否相等
|
||||
const tagQueryHasExits = this.multiTags.some(tag => {
|
||||
return isEqual(tag?.query, tagVal?.query);
|
||||
});
|
||||
return isEqual(tag?.query, tagVal?.query)
|
||||
})
|
||||
|
||||
// 判断tag中的params键值是否相等
|
||||
const tagParamsHasExits = this.multiTags.some(tag => {
|
||||
return isEqual(tag?.params, tagVal?.params);
|
||||
});
|
||||
return isEqual(tag?.params, tagVal?.params)
|
||||
})
|
||||
|
||||
if (tagHasExits && tagQueryHasExits && tagParamsHasExits) return;
|
||||
if (tagHasExits && tagQueryHasExits && tagParamsHasExits) return
|
||||
|
||||
// 动态路由可打开的最大数量
|
||||
const dynamicLevel = tagVal?.meta?.dynamicLevel ?? -1;
|
||||
const dynamicLevel = tagVal?.meta?.dynamicLevel ?? -1
|
||||
if (dynamicLevel > 0) {
|
||||
if (
|
||||
this.multiTags.filter(e => e?.path === tagPath).length >=
|
||||
@ -84,31 +84,31 @@ export const useMultiTagsStore = defineStore({
|
||||
// 如果当前已打开的动态路由数大于dynamicLevel,替换第一个动态路由标签
|
||||
const index = this.multiTags.findIndex(
|
||||
item => item?.path === tagPath
|
||||
);
|
||||
index !== -1 && this.multiTags.splice(index, 1);
|
||||
)
|
||||
index !== -1 && this.multiTags.splice(index, 1)
|
||||
}
|
||||
}
|
||||
this.multiTags.push(value);
|
||||
this.tagsCache(this.multiTags);
|
||||
this.multiTags.push(value)
|
||||
this.tagsCache(this.multiTags)
|
||||
}
|
||||
break;
|
||||
case "splice":
|
||||
break
|
||||
case 'splice':
|
||||
if (!position) {
|
||||
const index = this.multiTags.findIndex(v => v.path === value);
|
||||
if (index === -1) return;
|
||||
this.multiTags.splice(index, 1);
|
||||
const index = this.multiTags.findIndex(v => v.path === value)
|
||||
if (index === -1) return
|
||||
this.multiTags.splice(index, 1)
|
||||
} else {
|
||||
this.multiTags.splice(position?.startIndex, position?.length);
|
||||
this.multiTags.splice(position?.startIndex, position?.length)
|
||||
}
|
||||
this.tagsCache(this.multiTags);
|
||||
return this.multiTags;
|
||||
case "slice":
|
||||
return this.multiTags.slice(-1);
|
||||
this.tagsCache(this.multiTags)
|
||||
return this.multiTags
|
||||
case 'slice':
|
||||
return this.multiTags.slice(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
export function useMultiTagsStoreHook() {
|
||||
return useMultiTagsStore(store);
|
||||
return useMultiTagsStore(store)
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { store } from "@/store";
|
||||
import { cacheType } from "./types";
|
||||
import { constantMenus } from "@/router";
|
||||
import { ascending, filterTree, filterNoPermissionTree } from "@/router/utils";
|
||||
import { defineStore } from 'pinia'
|
||||
import { store } from '@/store'
|
||||
import { cacheType } from './types'
|
||||
import { constantMenus } from '@/router'
|
||||
import { ascending, filterTree, filterNoPermissionTree } from '@/router/utils'
|
||||
|
||||
export const usePermissionStore = defineStore({
|
||||
id: "pure-permission",
|
||||
id: 'pure-permission',
|
||||
state: () => ({
|
||||
// 静态路由生成的菜单
|
||||
constantMenus,
|
||||
@ -19,29 +19,29 @@ export const usePermissionStore = defineStore({
|
||||
handleWholeMenus(routes: any[]) {
|
||||
this.wholeMenus = filterNoPermissionTree(
|
||||
filterTree(ascending(this.constantMenus.concat(routes)))
|
||||
);
|
||||
)
|
||||
},
|
||||
cacheOperate({ mode, name }: cacheType) {
|
||||
switch (mode) {
|
||||
case "add":
|
||||
this.cachePageList.push(name);
|
||||
this.cachePageList = [...new Set(this.cachePageList)];
|
||||
break;
|
||||
case "delete":
|
||||
case 'add':
|
||||
this.cachePageList.push(name)
|
||||
this.cachePageList = [...new Set(this.cachePageList)]
|
||||
break
|
||||
case 'delete':
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const delIndex = this.cachePageList.findIndex(v => v === name);
|
||||
delIndex !== -1 && this.cachePageList.splice(delIndex, 1);
|
||||
break;
|
||||
const delIndex = this.cachePageList.findIndex(v => v === name)
|
||||
delIndex !== -1 && this.cachePageList.splice(delIndex, 1)
|
||||
break
|
||||
}
|
||||
},
|
||||
/** 清空缓存页面 */
|
||||
clearAllCachePage() {
|
||||
this.wholeMenus = [];
|
||||
this.cachePageList = [];
|
||||
this.wholeMenus = []
|
||||
this.cachePageList = []
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
export function usePermissionStoreHook() {
|
||||
return usePermissionStore(store);
|
||||
return usePermissionStore(store)
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { store } from "@/store";
|
||||
import { setType } from "./types";
|
||||
import { getConfig } from "@/config";
|
||||
import { defineStore } from 'pinia'
|
||||
import { store } from '@/store'
|
||||
import { setType } from './types'
|
||||
import { getConfig } from '@/config'
|
||||
|
||||
export const useSettingStore = defineStore({
|
||||
id: "pure-setting",
|
||||
id: 'pure-setting',
|
||||
state: (): setType => ({
|
||||
title: getConfig().Title,
|
||||
fixedHeader: getConfig().FixedHeader,
|
||||
@ -12,28 +12,28 @@ export const useSettingStore = defineStore({
|
||||
}),
|
||||
getters: {
|
||||
getTitle() {
|
||||
return this.title;
|
||||
return this.title
|
||||
},
|
||||
getFixedHeader() {
|
||||
return this.fixedHeader;
|
||||
return this.fixedHeader
|
||||
},
|
||||
getHiddenSideBar() {
|
||||
return this.HiddenSideBar;
|
||||
return this.HiddenSideBar
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
CHANGE_SETTING({ key, value }) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (this.hasOwnProperty(key)) {
|
||||
this[key] = value;
|
||||
this[key] = value
|
||||
}
|
||||
},
|
||||
changeSetting(data) {
|
||||
this.CHANGE_SETTING(data);
|
||||
this.CHANGE_SETTING(data)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
export function useSettingStoreHook() {
|
||||
return useSettingStore(store);
|
||||
return useSettingStore(store)
|
||||
}
|
||||
|
@ -1,44 +1,44 @@
|
||||
import { RouteRecordName } from "vue-router";
|
||||
import { RouteRecordName } from 'vue-router'
|
||||
|
||||
export type cacheType = {
|
||||
mode: string;
|
||||
name?: RouteRecordName;
|
||||
};
|
||||
mode: string
|
||||
name?: RouteRecordName
|
||||
}
|
||||
|
||||
export type positionType = {
|
||||
startIndex?: number;
|
||||
length?: number;
|
||||
};
|
||||
startIndex?: number
|
||||
length?: number
|
||||
}
|
||||
|
||||
export type appType = {
|
||||
sidebar: {
|
||||
opened: boolean;
|
||||
withoutAnimation: boolean;
|
||||
opened: boolean
|
||||
withoutAnimation: boolean
|
||||
// 判断是否手动点击Collapse
|
||||
isClickCollapse: boolean;
|
||||
};
|
||||
layout: string;
|
||||
device: string;
|
||||
};
|
||||
isClickCollapse: boolean
|
||||
}
|
||||
layout: string
|
||||
device: string
|
||||
}
|
||||
|
||||
export type multiType = {
|
||||
path: string;
|
||||
parentPath: string;
|
||||
name: string;
|
||||
meta: any;
|
||||
query?: object;
|
||||
params?: object;
|
||||
};
|
||||
path: string
|
||||
parentPath: string
|
||||
name: string
|
||||
meta: any
|
||||
query?: object
|
||||
params?: object
|
||||
}
|
||||
|
||||
export type setType = {
|
||||
title: string;
|
||||
fixedHeader: boolean;
|
||||
hiddenSideBar: boolean;
|
||||
};
|
||||
title: string
|
||||
fixedHeader: boolean
|
||||
hiddenSideBar: boolean
|
||||
}
|
||||
|
||||
export type userType = {
|
||||
username?: string;
|
||||
roles?: Array<string>;
|
||||
verifyCode?: string;
|
||||
currentPage?: number;
|
||||
};
|
||||
username?: string
|
||||
roles?: Array<string>
|
||||
verifyCode?: string
|
||||
currentPage?: number
|
||||
}
|
||||
|
@ -1,31 +1,31 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { store } from "@/store";
|
||||
import { userType } from "./types";
|
||||
import { routerArrays } from "@/layout/types";
|
||||
import { router, resetRouter } from "@/router";
|
||||
import { storageSession } from "@pureadmin/utils";
|
||||
import { getLogin, refreshTokenApi } from "@/api/user";
|
||||
import { UserResult, RefreshTokenResult } from "@/api/user";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { type DataInfo, setToken, removeToken, sessionKey } from "@/utils/auth";
|
||||
import { defineStore } from 'pinia'
|
||||
import { store } from '@/store'
|
||||
import { userType } from './types'
|
||||
import { routerArrays } from '@/layout/types'
|
||||
import { router, resetRouter } from '@/router'
|
||||
import { storageSession } from '@pureadmin/utils'
|
||||
import { getLogin, refreshTokenApi } from '@/api/user'
|
||||
import { UserResult, RefreshTokenResult } from '@/api/user'
|
||||
import { useMultiTagsStoreHook } from '@/store/modules/multiTags'
|
||||
import { type DataInfo, setToken, removeToken, sessionKey } from '@/utils/auth'
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: "pure-user",
|
||||
id: 'pure-user',
|
||||
state: (): userType => ({
|
||||
// 用户名
|
||||
username:
|
||||
storageSession().getItem<DataInfo<number>>(sessionKey)?.username ?? "",
|
||||
storageSession().getItem<DataInfo<number>>(sessionKey)?.username ?? '',
|
||||
// 页面级别权限
|
||||
roles: storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? []
|
||||
}),
|
||||
actions: {
|
||||
/** 存储用户名 */
|
||||
SET_USERNAME(username: string) {
|
||||
this.username = username;
|
||||
this.username = username
|
||||
},
|
||||
/** 存储角色 */
|
||||
SET_ROLES(roles: Array<string>) {
|
||||
this.roles = roles;
|
||||
this.roles = roles
|
||||
},
|
||||
/** 登入 */
|
||||
async loginByUsername(data) {
|
||||
@ -33,23 +33,23 @@ export const useUserStore = defineStore({
|
||||
getLogin(data)
|
||||
.then(data => {
|
||||
if (data) {
|
||||
setToken(data.data);
|
||||
resolve(data);
|
||||
setToken(data.data)
|
||||
resolve(data)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
/** 前端登出(不调用接口) */
|
||||
logOut() {
|
||||
this.username = "";
|
||||
this.roles = [];
|
||||
removeToken();
|
||||
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
|
||||
resetRouter();
|
||||
router.push("/login");
|
||||
this.username = ''
|
||||
this.roles = []
|
||||
removeToken()
|
||||
useMultiTagsStoreHook().handleTags('equal', [...routerArrays])
|
||||
resetRouter()
|
||||
router.push('/login')
|
||||
},
|
||||
/** 刷新`token` */
|
||||
async handRefreshToken(data) {
|
||||
@ -57,18 +57,18 @@ export const useUserStore = defineStore({
|
||||
refreshTokenApi(data)
|
||||
.then(data => {
|
||||
if (data) {
|
||||
setToken(data.data);
|
||||
resolve(data);
|
||||
setToken(data.data)
|
||||
resolve(data)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
export function useUserStoreHook() {
|
||||
return useUserStore(store);
|
||||
return useUserStore(store)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
@use "element-plus/theme-chalk/src/dark/css-vars.scss" as *;
|
||||
@use 'element-plus/theme-chalk/src/dark/css-vars.scss' as *;
|
||||
|
||||
/* 暗黑模式适配 */
|
||||
html.dark {
|
||||
|
@ -4,7 +4,7 @@
|
||||
}
|
||||
|
||||
.el-upload {
|
||||
input[type="file"] {
|
||||
input[type='file'] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@ -37,7 +37,7 @@
|
||||
}
|
||||
|
||||
/* 重置 el-button 中 icon 的 margin */
|
||||
.reset-margin [class*="el-icon"] + span {
|
||||
.reset-margin [class*='el-icon'] + span {
|
||||
margin-left: 2px !important;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
@import "./mixin.scss";
|
||||
@import "./transition.scss";
|
||||
@import "./element-plus.scss";
|
||||
@import "./sidebar.scss";
|
||||
@import "./dark.scss";
|
||||
@import './mixin.scss';
|
||||
@import './transition.scss';
|
||||
@import './element-plus.scss';
|
||||
@import './sidebar.scss';
|
||||
@import './dark.scss';
|
||||
|
||||
/* 自定义全局 CssVar */
|
||||
:root {
|
||||
|
@ -1,6 +1,6 @@
|
||||
@mixin clearfix {
|
||||
&::after {
|
||||
content: "";
|
||||
content: '';
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizelegibility;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
|
||||
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
|
||||
'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
}
|
||||
|
||||
hr {
|
||||
@ -59,7 +59,7 @@ kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
"Liberation Mono", "Courier New", monospace;
|
||||
'Liberation Mono', 'Courier New', monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
@ -108,9 +108,9 @@ select {
|
||||
}
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
@ -131,7 +131,7 @@ progress {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
[type='search'] {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ textarea::placeholder {
|
||||
}
|
||||
|
||||
button,
|
||||
[role="button"] {
|
||||
[role='button'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -252,7 +252,7 @@ div:focus {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
content: ' ';
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
|
@ -168,7 +168,7 @@
|
||||
}
|
||||
|
||||
.el-menu-item.is-active.nest-menu::before {
|
||||
content: "";
|
||||
content: '';
|
||||
clear: both;
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
@ -197,14 +197,14 @@
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background-color: $menuActiveBefore;
|
||||
content: "";
|
||||
content: '';
|
||||
clear: both;
|
||||
transition: all var(--pure-transition-duration) ease-in-out;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.el-menu--collapse .outer-most.el-sub-menu > .el-sub-menu__title::before {
|
||||
content: "";
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 0;
|
||||
@ -219,7 +219,7 @@
|
||||
color: #fff;
|
||||
}
|
||||
.is-active.submenu-title-noDropdown.outer-most::before {
|
||||
content: "";
|
||||
content: '';
|
||||
clear: both;
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
@ -289,7 +289,7 @@
|
||||
}
|
||||
|
||||
.el-menu-item.is-active.nest-menu::before {
|
||||
content: "";
|
||||
content: '';
|
||||
clear: both;
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
@ -381,7 +381,7 @@
|
||||
}
|
||||
|
||||
.el-menu-item.is-active.nest-menu::before {
|
||||
content: "";
|
||||
content: '';
|
||||
clear: both;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
@ -567,7 +567,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
body[layout="vertical"] {
|
||||
body[layout='vertical'] {
|
||||
$sideBarWidth: 210px;
|
||||
@include merge-style($sideBarWidth);
|
||||
|
||||
@ -641,7 +641,7 @@ body[layout="vertical"] {
|
||||
}
|
||||
}
|
||||
|
||||
body[layout="horizontal"] {
|
||||
body[layout='horizontal'] {
|
||||
$sideBarWidth: 0;
|
||||
@include merge-style($sideBarWidth);
|
||||
|
||||
@ -655,7 +655,7 @@ body[layout="horizontal"] {
|
||||
}
|
||||
}
|
||||
|
||||
body[layout="mix"] {
|
||||
body[layout='mix'] {
|
||||
$sideBarWidth: 210px;
|
||||
@include merge-style($sideBarWidth);
|
||||
|
||||
|
@ -1,29 +1,29 @@
|
||||
import Cookies from "js-cookie";
|
||||
import { storageSession } from "@pureadmin/utils";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import Cookies from 'js-cookie'
|
||||
import { storageSession } from '@pureadmin/utils'
|
||||
import { useUserStoreHook } from '@/store/modules/user'
|
||||
|
||||
export interface DataInfo<T> {
|
||||
/** token */
|
||||
accessToken: string;
|
||||
accessToken: string
|
||||
/** `accessToken`的过期时间(时间戳) */
|
||||
expires: T;
|
||||
expires: T
|
||||
/** 用于调用刷新accessToken的接口时所需的token */
|
||||
refreshToken: string;
|
||||
refreshToken: string
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
username?: string
|
||||
/** 当前登陆用户的角色 */
|
||||
roles?: Array<string>;
|
||||
roles?: Array<string>
|
||||
}
|
||||
|
||||
export const sessionKey = "user-info";
|
||||
export const TokenKey = "authorized-token";
|
||||
export const sessionKey = 'user-info'
|
||||
export const TokenKey = 'authorized-token'
|
||||
|
||||
/** 获取`token` */
|
||||
export function getToken(): DataInfo<number> {
|
||||
// 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错
|
||||
return Cookies.get(TokenKey)
|
||||
? JSON.parse(Cookies.get(TokenKey))
|
||||
: storageSession().getItem(sessionKey);
|
||||
: storageSession().getItem(sessionKey)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,47 +33,47 @@ export function getToken(): DataInfo<number> {
|
||||
* 将`username`、`roles`、`refreshToken`、`expires`这四条信息放在key值为`user-info`的sessionStorage里(浏览器关闭自动销毁)
|
||||
*/
|
||||
export function setToken(data: DataInfo<Date>) {
|
||||
let expires = 0;
|
||||
const { accessToken, refreshToken } = data;
|
||||
expires = new Date(data.expires).getTime(); // 如果后端直接设置时间戳,将此处代码改为expires = data.expires,然后把上面的DataInfo<Date>改成DataInfo<number>即可
|
||||
const cookieString = JSON.stringify({ accessToken, expires });
|
||||
let expires = 0
|
||||
const { accessToken, refreshToken } = data
|
||||
expires = new Date(data.expires).getTime() // 如果后端直接设置时间戳,将此处代码改为expires = data.expires,然后把上面的DataInfo<Date>改成DataInfo<number>即可
|
||||
const cookieString = JSON.stringify({ accessToken, expires })
|
||||
|
||||
expires > 0
|
||||
? Cookies.set(TokenKey, cookieString, {
|
||||
expires: (expires - Date.now()) / 86400000
|
||||
})
|
||||
: Cookies.set(TokenKey, cookieString);
|
||||
: Cookies.set(TokenKey, cookieString)
|
||||
|
||||
function setSessionKey(username: string, roles: Array<string>) {
|
||||
useUserStoreHook().SET_USERNAME(username);
|
||||
useUserStoreHook().SET_ROLES(roles);
|
||||
useUserStoreHook().SET_USERNAME(username)
|
||||
useUserStoreHook().SET_ROLES(roles)
|
||||
storageSession().setItem(sessionKey, {
|
||||
refreshToken,
|
||||
expires,
|
||||
username,
|
||||
roles
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
if (data.username && data.roles) {
|
||||
const { username, roles } = data;
|
||||
setSessionKey(username, roles);
|
||||
const { username, roles } = data
|
||||
setSessionKey(username, roles)
|
||||
} else {
|
||||
const username =
|
||||
storageSession().getItem<DataInfo<number>>(sessionKey)?.username ?? "";
|
||||
storageSession().getItem<DataInfo<number>>(sessionKey)?.username ?? ''
|
||||
const roles =
|
||||
storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? [];
|
||||
setSessionKey(username, roles);
|
||||
storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? []
|
||||
setSessionKey(username, roles)
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除`token`以及key值为`user-info`的session信息 */
|
||||
export function removeToken() {
|
||||
Cookies.remove(TokenKey);
|
||||
sessionStorage.clear();
|
||||
Cookies.remove(TokenKey)
|
||||
sessionStorage.clear()
|
||||
}
|
||||
|
||||
/** 格式化token(jwt格式) */
|
||||
export const formatToken = (token: string): string => {
|
||||
return "Bearer " + token;
|
||||
};
|
||||
return 'Bearer ' + token
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// 如果项目出现 `global is not defined` 报错,可能是您引入某个库的问题,比如 aws-sdk-js https://github.com/aws/aws-sdk-js
|
||||
// 解决办法就是将该文件引入 src/main.ts 即可 import "@/utils/globalPolyfills";
|
||||
if (typeof (window as any).global === "undefined") {
|
||||
(window as any).global = window;
|
||||
if (typeof (window as any).global === 'undefined') {
|
||||
;(window as any).global = window
|
||||
}
|
||||
|
||||
export {};
|
||||
export {}
|
||||
|
@ -2,59 +2,59 @@ import Axios, {
|
||||
AxiosInstance,
|
||||
AxiosRequestConfig,
|
||||
CustomParamsSerializer
|
||||
} from "axios";
|
||||
} from 'axios'
|
||||
import {
|
||||
PureHttpError,
|
||||
RequestMethods,
|
||||
PureHttpResponse,
|
||||
PureHttpRequestConfig
|
||||
} from "./types.d";
|
||||
import { stringify } from "qs";
|
||||
import NProgress from "../progress";
|
||||
import { getToken, formatToken } from "@/utils/auth";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
} from './types.d'
|
||||
import { stringify } from 'qs'
|
||||
import NProgress from '../progress'
|
||||
import { getToken, formatToken } from '@/utils/auth'
|
||||
import { useUserStoreHook } from '@/store/modules/user'
|
||||
|
||||
// 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1
|
||||
const defaultConfig: AxiosRequestConfig = {
|
||||
// 请求超时时间
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
Accept: "application/json, text/plain, */*",
|
||||
"Content-Type": "application/json",
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
// 数组格式参数序列化(https://github.com/axios/axios/issues/5142)
|
||||
paramsSerializer: {
|
||||
serialize: stringify as unknown as CustomParamsSerializer
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class PureHttp {
|
||||
constructor() {
|
||||
this.httpInterceptorsRequest();
|
||||
this.httpInterceptorsResponse();
|
||||
this.httpInterceptorsRequest()
|
||||
this.httpInterceptorsResponse()
|
||||
}
|
||||
|
||||
/** token过期后,暂存待执行的请求 */
|
||||
private static requests = [];
|
||||
private static requests = []
|
||||
|
||||
/** 防止重复刷新token */
|
||||
private static isRefreshing = false;
|
||||
private static isRefreshing = false
|
||||
|
||||
/** 初始化配置对象 */
|
||||
private static initConfig: PureHttpRequestConfig = {};
|
||||
private static initConfig: PureHttpRequestConfig = {}
|
||||
|
||||
/** 保存当前Axios实例对象 */
|
||||
private static axiosInstance: AxiosInstance = Axios.create(defaultConfig);
|
||||
private static axiosInstance: AxiosInstance = Axios.create(defaultConfig)
|
||||
|
||||
/** 重连原始请求 */
|
||||
private static retryOriginalRequest(config: PureHttpRequestConfig) {
|
||||
return new Promise(resolve => {
|
||||
PureHttp.requests.push((token: string) => {
|
||||
config.headers["Authorization"] = formatToken(token);
|
||||
resolve(config);
|
||||
});
|
||||
});
|
||||
config.headers['Authorization'] = formatToken(token)
|
||||
resolve(config)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** 请求拦截 */
|
||||
@ -62,87 +62,87 @@ class PureHttp {
|
||||
PureHttp.axiosInstance.interceptors.request.use(
|
||||
async (config: PureHttpRequestConfig) => {
|
||||
// 开启进度条动画
|
||||
NProgress.start();
|
||||
NProgress.start()
|
||||
// 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉
|
||||
if (typeof config.beforeRequestCallback === "function") {
|
||||
config.beforeRequestCallback(config);
|
||||
return config;
|
||||
if (typeof config.beforeRequestCallback === 'function') {
|
||||
config.beforeRequestCallback(config)
|
||||
return config
|
||||
}
|
||||
if (PureHttp.initConfig.beforeRequestCallback) {
|
||||
PureHttp.initConfig.beforeRequestCallback(config);
|
||||
return config;
|
||||
PureHttp.initConfig.beforeRequestCallback(config)
|
||||
return config
|
||||
}
|
||||
/** 请求白名单,放置一些不需要token的接口(通过设置请求白名单,防止token过期后再请求造成的死循环问题) */
|
||||
const whiteList = ["/refreshToken", "/login"];
|
||||
const whiteList = ['/refreshToken', '/login']
|
||||
return whiteList.some(v => config.url.indexOf(v) > -1)
|
||||
? config
|
||||
: new Promise(resolve => {
|
||||
const data = getToken();
|
||||
const data = getToken()
|
||||
if (data) {
|
||||
const now = new Date().getTime();
|
||||
const expired = parseInt(data.expires) - now <= 0;
|
||||
const now = new Date().getTime()
|
||||
const expired = parseInt(data.expires) - now <= 0
|
||||
if (expired) {
|
||||
if (!PureHttp.isRefreshing) {
|
||||
PureHttp.isRefreshing = true;
|
||||
PureHttp.isRefreshing = true
|
||||
// token过期刷新
|
||||
useUserStoreHook()
|
||||
.handRefreshToken({ refreshToken: data.refreshToken })
|
||||
.then(res => {
|
||||
const token = res.data.accessToken;
|
||||
config.headers["Authorization"] = formatToken(token);
|
||||
PureHttp.requests.forEach(cb => cb(token));
|
||||
PureHttp.requests = [];
|
||||
const token = res.data.accessToken
|
||||
config.headers['Authorization'] = formatToken(token)
|
||||
PureHttp.requests.forEach(cb => cb(token))
|
||||
PureHttp.requests = []
|
||||
})
|
||||
.finally(() => {
|
||||
PureHttp.isRefreshing = false;
|
||||
});
|
||||
PureHttp.isRefreshing = false
|
||||
})
|
||||
}
|
||||
resolve(PureHttp.retryOriginalRequest(config));
|
||||
resolve(PureHttp.retryOriginalRequest(config))
|
||||
} else {
|
||||
config.headers["Authorization"] = formatToken(
|
||||
config.headers['Authorization'] = formatToken(
|
||||
data.accessToken
|
||||
);
|
||||
resolve(config);
|
||||
)
|
||||
resolve(config)
|
||||
}
|
||||
} else {
|
||||
resolve(config);
|
||||
resolve(config)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error);
|
||||
return Promise.reject(error)
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
/** 响应拦截 */
|
||||
private httpInterceptorsResponse(): void {
|
||||
const instance = PureHttp.axiosInstance;
|
||||
const instance = PureHttp.axiosInstance
|
||||
instance.interceptors.response.use(
|
||||
(response: PureHttpResponse) => {
|
||||
const $config = response.config;
|
||||
const $config = response.config
|
||||
// 关闭进度条动画
|
||||
NProgress.done();
|
||||
NProgress.done()
|
||||
// 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉
|
||||
if (typeof $config.beforeResponseCallback === "function") {
|
||||
$config.beforeResponseCallback(response);
|
||||
return response.data;
|
||||
if (typeof $config.beforeResponseCallback === 'function') {
|
||||
$config.beforeResponseCallback(response)
|
||||
return response.data
|
||||
}
|
||||
if (PureHttp.initConfig.beforeResponseCallback) {
|
||||
PureHttp.initConfig.beforeResponseCallback(response);
|
||||
return response.data;
|
||||
PureHttp.initConfig.beforeResponseCallback(response)
|
||||
return response.data
|
||||
}
|
||||
return response.data;
|
||||
return response.data
|
||||
},
|
||||
(error: PureHttpError) => {
|
||||
const $error = error;
|
||||
$error.isCancelRequest = Axios.isCancel($error);
|
||||
const $error = error
|
||||
$error.isCancelRequest = Axios.isCancel($error)
|
||||
// 关闭进度条动画
|
||||
NProgress.done();
|
||||
NProgress.done()
|
||||
// 所有的响应异常 区分来源为取消请求/非取消请求
|
||||
return Promise.reject($error);
|
||||
return Promise.reject($error)
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
/** 通用请求工具函数 */
|
||||
@ -157,19 +157,19 @@ class PureHttp {
|
||||
url,
|
||||
...param,
|
||||
...axiosConfig
|
||||
} as PureHttpRequestConfig;
|
||||
} as PureHttpRequestConfig
|
||||
|
||||
// 单独处理自定义请求/响应回掉
|
||||
return new Promise((resolve, reject) => {
|
||||
PureHttp.axiosInstance
|
||||
.request(config)
|
||||
.then((response: undefined) => {
|
||||
resolve(response);
|
||||
resolve(response)
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** 单独抽离的post工具函数 */
|
||||
@ -178,7 +178,7 @@ class PureHttp {
|
||||
params?: AxiosRequestConfig<T>,
|
||||
config?: PureHttpRequestConfig
|
||||
): Promise<P> {
|
||||
return this.request<P>("post", url, params, config);
|
||||
return this.request<P>('post', url, params, config)
|
||||
}
|
||||
|
||||
/** 单独抽离的get工具函数 */
|
||||
@ -187,8 +187,8 @@ class PureHttp {
|
||||
params?: AxiosRequestConfig<T>,
|
||||
config?: PureHttpRequestConfig
|
||||
): Promise<P> {
|
||||
return this.request<P>("get", url, params, config);
|
||||
return this.request<P>('get', url, params, config)
|
||||
}
|
||||
}
|
||||
|
||||
export const http = new PureHttp();
|
||||
export const http = new PureHttp()
|
||||
|
28
src/utils/http/types.d.ts
vendored
28
src/utils/http/types.d.ts
vendored
@ -3,28 +3,28 @@ import Axios, {
|
||||
AxiosError,
|
||||
AxiosResponse,
|
||||
AxiosRequestConfig
|
||||
} from "axios";
|
||||
} from 'axios'
|
||||
|
||||
export type resultType = {
|
||||
accessToken?: string;
|
||||
};
|
||||
accessToken?: string
|
||||
}
|
||||
|
||||
export type RequestMethods = Extract<
|
||||
Method,
|
||||
"get" | "post" | "put" | "delete" | "patch" | "option" | "head"
|
||||
>;
|
||||
'get' | 'post' | 'put' | 'delete' | 'patch' | 'option' | 'head'
|
||||
>
|
||||
|
||||
export interface PureHttpError extends AxiosError {
|
||||
isCancelRequest?: boolean;
|
||||
isCancelRequest?: boolean
|
||||
}
|
||||
|
||||
export interface PureHttpResponse extends AxiosResponse {
|
||||
config: PureHttpRequestConfig;
|
||||
config: PureHttpRequestConfig
|
||||
}
|
||||
|
||||
export interface PureHttpRequestConfig extends AxiosRequestConfig {
|
||||
beforeRequestCallback?: (request: PureHttpRequestConfig) => void;
|
||||
beforeResponseCallback?: (response: PureHttpResponse) => void;
|
||||
beforeRequestCallback?: (request: PureHttpRequestConfig) => void
|
||||
beforeResponseCallback?: (response: PureHttpResponse) => void
|
||||
}
|
||||
|
||||
export default class PureHttp {
|
||||
@ -33,15 +33,11 @@ export default class PureHttp {
|
||||
url: string,
|
||||
param?: AxiosRequestConfig,
|
||||
axiosConfig?: PureHttpRequestConfig
|
||||
): Promise<T>;
|
||||
): Promise<T>
|
||||
post<T, P>(
|
||||
url: string,
|
||||
params?: T,
|
||||
config?: PureHttpRequestConfig
|
||||
): Promise<P>;
|
||||
get<T, P>(
|
||||
url: string,
|
||||
params?: T,
|
||||
config?: PureHttpRequestConfig
|
||||
): Promise<P>;
|
||||
): Promise<P>
|
||||
get<T, P>(url: string, params?: T, config?: PureHttpRequestConfig): Promise<P>
|
||||
}
|
||||
|
@ -1,33 +1,33 @@
|
||||
import { type VNode } from "vue";
|
||||
import { isFunction } from "@pureadmin/utils";
|
||||
import { type MessageHandler, ElMessage } from "element-plus";
|
||||
import { type VNode } from 'vue'
|
||||
import { isFunction } from '@pureadmin/utils'
|
||||
import { type MessageHandler, ElMessage } from 'element-plus'
|
||||
|
||||
type messageStyle = "el" | "antd";
|
||||
type messageTypes = "info" | "success" | "warning" | "error";
|
||||
type messageStyle = 'el' | 'antd'
|
||||
type messageTypes = 'info' | 'success' | 'warning' | 'error'
|
||||
|
||||
interface MessageParams {
|
||||
/** 消息类型,可选 `info` 、`success` 、`warning` 、`error` ,默认 `info` */
|
||||
type?: messageTypes;
|
||||
type?: messageTypes
|
||||
/** 自定义图标,该属性会覆盖 `type` 的图标 */
|
||||
icon?: any;
|
||||
icon?: any
|
||||
/** 是否将 `message` 属性作为 `HTML` 片段处理,默认 `false` */
|
||||
dangerouslyUseHTMLString?: boolean;
|
||||
dangerouslyUseHTMLString?: boolean
|
||||
/** 消息风格,可选 `el` 、`antd` ,默认 `antd` */
|
||||
customClass?: messageStyle;
|
||||
customClass?: messageStyle
|
||||
/** 显示时间,单位为毫秒。设为 `0` 则不会自动关闭,`element-plus` 默认是 `3000` ,平台改成默认 `2000` */
|
||||
duration?: number;
|
||||
duration?: number
|
||||
/** 是否显示关闭按钮,默认值 `false` */
|
||||
showClose?: boolean;
|
||||
showClose?: boolean
|
||||
/** 文字是否居中,默认值 `false` */
|
||||
center?: boolean;
|
||||
center?: boolean
|
||||
/** `Message` 距离窗口顶部的偏移量,默认 `20` */
|
||||
offset?: number;
|
||||
offset?: number
|
||||
/** 设置组件的根元素,默认 `document.body` */
|
||||
appendTo?: string | HTMLElement;
|
||||
appendTo?: string | HTMLElement
|
||||
/** 合并内容相同的消息,不支持 `VNode` 类型的消息,默认值 `false` */
|
||||
grouping?: boolean;
|
||||
grouping?: boolean
|
||||
/** 关闭时的回调函数, 参数为被关闭的 `message` 实例 */
|
||||
onClose?: Function | null;
|
||||
onClose?: Function | null
|
||||
}
|
||||
|
||||
/** 用法非常简单,参考 src/views/components/message/index.vue 文件 */
|
||||
@ -42,14 +42,14 @@ const message = (
|
||||
if (!params) {
|
||||
return ElMessage({
|
||||
message,
|
||||
customClass: "pure-message"
|
||||
});
|
||||
customClass: 'pure-message'
|
||||
})
|
||||
} else {
|
||||
const {
|
||||
icon,
|
||||
type = "info",
|
||||
type = 'info',
|
||||
dangerouslyUseHTMLString = false,
|
||||
customClass = "antd",
|
||||
customClass = 'antd',
|
||||
duration = 2000,
|
||||
showClose = false,
|
||||
center = false,
|
||||
@ -57,7 +57,7 @@ const message = (
|
||||
appendTo = document.body,
|
||||
grouping = false,
|
||||
onClose
|
||||
} = params;
|
||||
} = params
|
||||
|
||||
return ElMessage({
|
||||
message,
|
||||
@ -71,15 +71,15 @@ const message = (
|
||||
appendTo,
|
||||
grouping,
|
||||
// 全局搜 pure-message 即可知道该类的样式位置
|
||||
customClass: customClass === "antd" ? "pure-message" : "",
|
||||
customClass: customClass === 'antd' ? 'pure-message' : '',
|
||||
onClose: () => (isFunction(onClose) ? onClose() : null)
|
||||
});
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭所有 `Message` 消息提示函数
|
||||
*/
|
||||
const closeAllMessage = (): void => ElMessage.closeAll();
|
||||
const closeAllMessage = (): void => ElMessage.closeAll()
|
||||
|
||||
export { message, closeAllMessage };
|
||||
export { message, closeAllMessage }
|
||||
|
@ -1,21 +1,21 @@
|
||||
import type { Emitter } from "mitt";
|
||||
import mitt from "mitt";
|
||||
import type { Emitter } from 'mitt'
|
||||
import mitt from 'mitt'
|
||||
|
||||
type Events = {
|
||||
resize: {
|
||||
detail: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
};
|
||||
openPanel: string;
|
||||
tagViewsChange: string;
|
||||
tagViewsShowModel: string;
|
||||
logoChange: boolean;
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
}
|
||||
openPanel: string
|
||||
tagViewsChange: string
|
||||
tagViewsShowModel: string
|
||||
logoChange: boolean
|
||||
changLayoutRoute: {
|
||||
indexPath: string;
|
||||
parentPath: string;
|
||||
};
|
||||
};
|
||||
indexPath: string
|
||||
parentPath: string
|
||||
}
|
||||
}
|
||||
|
||||
export const emitter: Emitter<Events> = mitt<Events>();
|
||||
export const emitter: Emitter<Events> = mitt<Events>()
|
||||
|
@ -1,47 +1,47 @@
|
||||
interface PrintFunction {
|
||||
extendOptions: Function;
|
||||
getStyle: Function;
|
||||
setDomHeight: Function;
|
||||
toPrint: Function;
|
||||
extendOptions: Function
|
||||
getStyle: Function
|
||||
setDomHeight: Function
|
||||
toPrint: Function
|
||||
}
|
||||
|
||||
const Print = function (dom, options?: object): PrintFunction {
|
||||
options = options || {};
|
||||
options = options || {}
|
||||
// @ts-expect-error
|
||||
if (!(this instanceof Print)) return new Print(dom, options);
|
||||
if (!(this instanceof Print)) return new Print(dom, options)
|
||||
this.conf = {
|
||||
styleStr: "",
|
||||
styleStr: '',
|
||||
// Elements that need to dynamically get and set the height
|
||||
setDomHeightArr: [],
|
||||
// Callback before printing
|
||||
printBeforeFn: null,
|
||||
// Callback after printing
|
||||
printDoneCallBack: null
|
||||
};
|
||||
}
|
||||
for (const key in this.conf) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (key && options.hasOwnProperty(key)) {
|
||||
this.conf[key] = options[key];
|
||||
this.conf[key] = options[key]
|
||||
}
|
||||
}
|
||||
if (typeof dom === "string") {
|
||||
this.dom = document.querySelector(dom);
|
||||
if (typeof dom === 'string') {
|
||||
this.dom = document.querySelector(dom)
|
||||
} else {
|
||||
this.dom = this.isDOM(dom) ? dom : dom.$el;
|
||||
this.dom = this.isDOM(dom) ? dom : dom.$el
|
||||
}
|
||||
if (this.conf.setDomHeightArr && this.conf.setDomHeightArr.length) {
|
||||
this.setDomHeight(this.conf.setDomHeightArr);
|
||||
this.setDomHeight(this.conf.setDomHeightArr)
|
||||
}
|
||||
this.init();
|
||||
};
|
||||
this.init()
|
||||
}
|
||||
|
||||
Print.prototype = {
|
||||
/**
|
||||
* init
|
||||
*/
|
||||
init: function (): void {
|
||||
const content = this.getStyle() + this.getHtml();
|
||||
this.writeIframe(content);
|
||||
const content = this.getStyle() + this.getHtml()
|
||||
this.writeIframe(content)
|
||||
},
|
||||
/**
|
||||
* Configuration property extension
|
||||
@ -50,58 +50,58 @@ Print.prototype = {
|
||||
*/
|
||||
extendOptions: function <T>(obj, obj2: T): T {
|
||||
for (const k in obj2) {
|
||||
obj[k] = obj2[k];
|
||||
obj[k] = obj2[k]
|
||||
}
|
||||
return obj;
|
||||
return obj
|
||||
},
|
||||
/**
|
||||
Copy all styles of the original page
|
||||
*/
|
||||
getStyle: function (): string {
|
||||
let str = "";
|
||||
const styles: NodeListOf<Element> = document.querySelectorAll("style,link");
|
||||
let str = ''
|
||||
const styles: NodeListOf<Element> = document.querySelectorAll('style,link')
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
str += styles[i].outerHTML;
|
||||
str += styles[i].outerHTML
|
||||
}
|
||||
str += `<style>.no-print{display:none;}${this.conf.styleStr}</style>`;
|
||||
return str;
|
||||
str += `<style>.no-print{display:none;}${this.conf.styleStr}</style>`
|
||||
return str
|
||||
},
|
||||
// form assignment
|
||||
getHtml: function (): Element {
|
||||
const inputs = document.querySelectorAll("input");
|
||||
const selects = document.querySelectorAll("select");
|
||||
const textareas = document.querySelectorAll("textarea");
|
||||
const canvass = document.querySelectorAll("canvas");
|
||||
const inputs = document.querySelectorAll('input')
|
||||
const selects = document.querySelectorAll('select')
|
||||
const textareas = document.querySelectorAll('textarea')
|
||||
const canvass = document.querySelectorAll('canvas')
|
||||
|
||||
for (let k = 0; k < inputs.length; k++) {
|
||||
if (inputs[k].type == "checkbox" || inputs[k].type == "radio") {
|
||||
if (inputs[k].type == 'checkbox' || inputs[k].type == 'radio') {
|
||||
if (inputs[k].checked == true) {
|
||||
inputs[k].setAttribute("checked", "checked");
|
||||
inputs[k].setAttribute('checked', 'checked')
|
||||
} else {
|
||||
inputs[k].removeAttribute("checked");
|
||||
inputs[k].removeAttribute('checked')
|
||||
}
|
||||
} else if (inputs[k].type == "text") {
|
||||
inputs[k].setAttribute("value", inputs[k].value);
|
||||
} else if (inputs[k].type == 'text') {
|
||||
inputs[k].setAttribute('value', inputs[k].value)
|
||||
} else {
|
||||
inputs[k].setAttribute("value", inputs[k].value);
|
||||
inputs[k].setAttribute('value', inputs[k].value)
|
||||
}
|
||||
}
|
||||
|
||||
for (let k2 = 0; k2 < textareas.length; k2++) {
|
||||
if (textareas[k2].type == "textarea") {
|
||||
textareas[k2].innerHTML = textareas[k2].value;
|
||||
if (textareas[k2].type == 'textarea') {
|
||||
textareas[k2].innerHTML = textareas[k2].value
|
||||
}
|
||||
}
|
||||
|
||||
for (let k3 = 0; k3 < selects.length; k3++) {
|
||||
if (selects[k3].type == "select-one") {
|
||||
const child = selects[k3].children;
|
||||
if (selects[k3].type == 'select-one') {
|
||||
const child = selects[k3].children
|
||||
for (const i in child) {
|
||||
if (child[i].tagName == "OPTION") {
|
||||
if (child[i].tagName == 'OPTION') {
|
||||
if ((child[i] as any).selected == true) {
|
||||
child[i].setAttribute("selected", "selected");
|
||||
child[i].setAttribute('selected', 'selected')
|
||||
} else {
|
||||
child[i].removeAttribute("selected");
|
||||
child[i].removeAttribute('selected')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,58 +109,58 @@ Print.prototype = {
|
||||
}
|
||||
|
||||
for (let k4 = 0; k4 < canvass.length; k4++) {
|
||||
const imageURL = canvass[k4].toDataURL("image/png");
|
||||
const img = document.createElement("img");
|
||||
img.src = imageURL;
|
||||
img.setAttribute("style", "max-width: 100%;");
|
||||
img.className = "isNeedRemove";
|
||||
canvass[k4].parentNode.insertBefore(img, canvass[k4].nextElementSibling);
|
||||
const imageURL = canvass[k4].toDataURL('image/png')
|
||||
const img = document.createElement('img')
|
||||
img.src = imageURL
|
||||
img.setAttribute('style', 'max-width: 100%;')
|
||||
img.className = 'isNeedRemove'
|
||||
canvass[k4].parentNode.insertBefore(img, canvass[k4].nextElementSibling)
|
||||
}
|
||||
|
||||
return this.dom.outerHTML;
|
||||
return this.dom.outerHTML
|
||||
},
|
||||
/**
|
||||
create iframe
|
||||
*/
|
||||
writeIframe: function (content) {
|
||||
let w: Document | Window;
|
||||
let doc: Document;
|
||||
const iframe: HTMLIFrameElement = document.createElement("iframe");
|
||||
const f: HTMLIFrameElement = document.body.appendChild(iframe);
|
||||
iframe.id = "myIframe";
|
||||
let w: Document | Window
|
||||
let doc: Document
|
||||
const iframe: HTMLIFrameElement = document.createElement('iframe')
|
||||
const f: HTMLIFrameElement = document.body.appendChild(iframe)
|
||||
iframe.id = 'myIframe'
|
||||
iframe.setAttribute(
|
||||
"style",
|
||||
"position:absolute;width:0;height:0;top:-10px;left:-10px;"
|
||||
);
|
||||
'style',
|
||||
'position:absolute;width:0;height:0;top:-10px;left:-10px;'
|
||||
)
|
||||
// eslint-disable-next-line prefer-const
|
||||
w = f.contentWindow || f.contentDocument;
|
||||
w = f.contentWindow || f.contentDocument
|
||||
// eslint-disable-next-line prefer-const
|
||||
doc = f.contentDocument || f.contentWindow.document;
|
||||
doc.open();
|
||||
doc.write(content);
|
||||
doc.close();
|
||||
doc = f.contentDocument || f.contentWindow.document
|
||||
doc.open()
|
||||
doc.write(content)
|
||||
doc.close()
|
||||
|
||||
const removes = document.querySelectorAll(".isNeedRemove");
|
||||
const removes = document.querySelectorAll('.isNeedRemove')
|
||||
for (let k = 0; k < removes.length; k++) {
|
||||
removes[k].parentNode.removeChild(removes[k]);
|
||||
removes[k].parentNode.removeChild(removes[k])
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const _this = this;
|
||||
const _this = this
|
||||
iframe.onload = function (): void {
|
||||
// Before popping, callback
|
||||
if (_this.conf.printBeforeFn) {
|
||||
_this.conf.printBeforeFn({ doc });
|
||||
_this.conf.printBeforeFn({ doc })
|
||||
}
|
||||
_this.toPrint(w);
|
||||
_this.toPrint(w)
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(iframe);
|
||||
document.body.removeChild(iframe)
|
||||
// After popup, callback
|
||||
if (_this.conf.printDoneCallBack) {
|
||||
_this.conf.printDoneCallBack();
|
||||
_this.conf.printDoneCallBack()
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
},
|
||||
/**
|
||||
Print
|
||||
@ -168,32 +168,32 @@ Print.prototype = {
|
||||
toPrint: function (frameWindow): void {
|
||||
try {
|
||||
setTimeout(function () {
|
||||
frameWindow.focus();
|
||||
frameWindow.focus()
|
||||
try {
|
||||
if (!frameWindow.document.execCommand("print", false, null)) {
|
||||
frameWindow.print();
|
||||
if (!frameWindow.document.execCommand('print', false, null)) {
|
||||
frameWindow.print()
|
||||
}
|
||||
} catch (e) {
|
||||
frameWindow.print();
|
||||
frameWindow.print()
|
||||
}
|
||||
frameWindow.close();
|
||||
}, 10);
|
||||
frameWindow.close()
|
||||
}, 10)
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.error(err)
|
||||
}
|
||||
},
|
||||
isDOM:
|
||||
typeof HTMLElement === "object"
|
||||
typeof HTMLElement === 'object'
|
||||
? function (obj) {
|
||||
return obj instanceof HTMLElement;
|
||||
return obj instanceof HTMLElement
|
||||
}
|
||||
: function (obj) {
|
||||
return (
|
||||
obj &&
|
||||
typeof obj === "object" &&
|
||||
typeof obj === 'object' &&
|
||||
obj.nodeType === 1 &&
|
||||
typeof obj.nodeName === "string"
|
||||
);
|
||||
typeof obj.nodeName === 'string'
|
||||
)
|
||||
},
|
||||
/**
|
||||
* Set the height of the specified dom element by getting the existing height of the dom element and setting
|
||||
@ -202,13 +202,13 @@ Print.prototype = {
|
||||
setDomHeight(arr) {
|
||||
if (arr && arr.length) {
|
||||
arr.forEach(name => {
|
||||
const domArr = document.querySelectorAll(name);
|
||||
const domArr = document.querySelectorAll(name)
|
||||
domArr.forEach(dom => {
|
||||
dom.style.height = dom.offsetHeight + "px";
|
||||
});
|
||||
});
|
||||
dom.style.height = dom.offsetHeight + 'px'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default Print;
|
||||
export default Print
|
||||
|
@ -1,9 +1,9 @@
|
||||
import NProgress from "nprogress";
|
||||
import "nprogress/nprogress.css";
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
|
||||
NProgress.configure({
|
||||
// 动画方式
|
||||
easing: "ease",
|
||||
easing: 'ease',
|
||||
// 递增进度条的速度
|
||||
speed: 500,
|
||||
// 是否显示加载ico
|
||||
@ -12,6 +12,6 @@ NProgress.configure({
|
||||
trickleSpeed: 200,
|
||||
// 初始化时的最小百分比
|
||||
minimum: 0.3
|
||||
});
|
||||
})
|
||||
|
||||
export default NProgress;
|
||||
export default NProgress
|
||||
|
@ -1,17 +1,17 @@
|
||||
import type { CSSProperties, VNodeChild } from "vue";
|
||||
import type { CSSProperties, VNodeChild } from 'vue'
|
||||
import {
|
||||
createTypes,
|
||||
toValidableType,
|
||||
VueTypesInterface,
|
||||
VueTypeValidableDef
|
||||
} from "vue-types";
|
||||
} from 'vue-types'
|
||||
|
||||
export type VueNode = VNodeChild | JSX.Element;
|
||||
export type VueNode = VNodeChild | JSX.Element
|
||||
|
||||
type PropTypes = VueTypesInterface & {
|
||||
readonly style: VueTypeValidableDef<CSSProperties>;
|
||||
readonly VNodeChild: VueTypeValidableDef<VueNode>;
|
||||
};
|
||||
readonly style: VueTypeValidableDef<CSSProperties>
|
||||
readonly VNodeChild: VueTypeValidableDef<VueNode>
|
||||
}
|
||||
|
||||
const newPropTypes = createTypes({
|
||||
func: undefined,
|
||||
@ -20,20 +20,20 @@ const newPropTypes = createTypes({
|
||||
number: undefined,
|
||||
object: undefined,
|
||||
integer: undefined
|
||||
}) as PropTypes;
|
||||
}) as PropTypes
|
||||
|
||||
// 从 vue-types v5.0 开始,extend()方法已经废弃,当前已改为官方推荐的ES6+方法 https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method
|
||||
export default class propTypes extends newPropTypes {
|
||||
// a native-like validator that supports the `.validable` method
|
||||
static get style() {
|
||||
return toValidableType("style", {
|
||||
return toValidableType('style', {
|
||||
type: [String, Object]
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
static get VNodeChild() {
|
||||
return toValidableType("VNodeChild", {
|
||||
return toValidableType('VNodeChild', {
|
||||
type: undefined
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +1,41 @@
|
||||
// 响应式storage
|
||||
import { App } from "vue";
|
||||
import Storage from "responsive-storage";
|
||||
import { routerArrays } from "@/layout/types";
|
||||
import { App } from 'vue'
|
||||
import Storage from 'responsive-storage'
|
||||
import { routerArrays } from '@/layout/types'
|
||||
|
||||
const nameSpace = "responsive-";
|
||||
const nameSpace = 'responsive-'
|
||||
|
||||
export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
|
||||
const configObj = Object.assign(
|
||||
{
|
||||
// 国际化 默认中文zh
|
||||
locale: Storage.getData("locale", nameSpace) ?? {
|
||||
locale: config.Locale ?? "zh"
|
||||
locale: Storage.getData('locale', nameSpace) ?? {
|
||||
locale: config.Locale ?? 'zh'
|
||||
},
|
||||
// layout模式以及主题
|
||||
layout: Storage.getData("layout", nameSpace) ?? {
|
||||
layout: config.Layout ?? "vertical",
|
||||
theme: config.Theme ?? "default",
|
||||
layout: Storage.getData('layout', nameSpace) ?? {
|
||||
layout: config.Layout ?? 'vertical',
|
||||
theme: config.Theme ?? 'default',
|
||||
darkMode: config.DarkMode ?? false,
|
||||
sidebarStatus: config.SidebarStatus ?? true,
|
||||
epThemeColor: config.EpThemeColor ?? "#409EFF"
|
||||
epThemeColor: config.EpThemeColor ?? '#409EFF'
|
||||
},
|
||||
configure: Storage.getData("configure", nameSpace) ?? {
|
||||
configure: Storage.getData('configure', nameSpace) ?? {
|
||||
grey: config.Grey ?? false,
|
||||
weak: config.Weak ?? false,
|
||||
hideTabs: config.HideTabs ?? false,
|
||||
showLogo: config.ShowLogo ?? true,
|
||||
showModel: config.ShowModel ?? "smart",
|
||||
showModel: config.ShowModel ?? 'smart',
|
||||
multiTagsCache: config.MultiTagsCache ?? false
|
||||
}
|
||||
},
|
||||
config.MultiTagsCache
|
||||
? {
|
||||
// 默认显示首页tag
|
||||
tags: Storage.getData("tags", nameSpace) ?? routerArrays
|
||||
tags: Storage.getData('tags', nameSpace) ?? routerArrays
|
||||
}
|
||||
: {}
|
||||
);
|
||||
)
|
||||
|
||||
app.use(Storage, { nameSpace, memory: configObj });
|
||||
};
|
||||
app.use(Storage, { nameSpace, memory: configObj })
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { removeToken, setToken, type DataInfo } from "./auth";
|
||||
import { subBefore, getQueryMap } from "@pureadmin/utils";
|
||||
import { removeToken, setToken, type DataInfo } from './auth'
|
||||
import { subBefore, getQueryMap } from '@pureadmin/utils'
|
||||
|
||||
/**
|
||||
* 简版前端单点登录,根据实际业务自行编写
|
||||
@ -10,50 +10,50 @@ import { subBefore, getQueryMap } from "@pureadmin/utils";
|
||||
* 3.删除不需要显示在 url 的参数
|
||||
* 4.使用 window.location.replace 跳转正确页面
|
||||
*/
|
||||
(function () {
|
||||
;(function () {
|
||||
// 获取 url 中的参数
|
||||
const params = getQueryMap(location.href) as DataInfo<Date>;
|
||||
const must = ["username", "roles", "accessToken"];
|
||||
const mustLength = must.length;
|
||||
if (Object.keys(params).length !== mustLength) return;
|
||||
const params = getQueryMap(location.href) as DataInfo<Date>
|
||||
const must = ['username', 'roles', 'accessToken']
|
||||
const mustLength = must.length
|
||||
if (Object.keys(params).length !== mustLength) return
|
||||
|
||||
// url 参数满足 must 里的全部值,才判定为单点登录,避免非单点登录时刷新页面无限循环
|
||||
let sso = [];
|
||||
let start = 0;
|
||||
let sso = []
|
||||
let start = 0
|
||||
|
||||
while (start < mustLength) {
|
||||
if (Object.keys(params).includes(must[start]) && sso.length <= mustLength) {
|
||||
sso.push(must[start]);
|
||||
sso.push(must[start])
|
||||
} else {
|
||||
sso = [];
|
||||
sso = []
|
||||
}
|
||||
start++;
|
||||
start++
|
||||
}
|
||||
|
||||
if (sso.length === mustLength) {
|
||||
// 判定为单点登录
|
||||
|
||||
// 清空本地旧信息
|
||||
removeToken();
|
||||
removeToken()
|
||||
|
||||
// 保存新信息到本地
|
||||
setToken(params);
|
||||
setToken(params)
|
||||
|
||||
// 删除不需要显示在 url 的参数
|
||||
delete params["roles"];
|
||||
delete params["accessToken"];
|
||||
delete params['roles']
|
||||
delete params['accessToken']
|
||||
|
||||
const newUrl = `${location.origin}${location.pathname}${subBefore(
|
||||
location.hash,
|
||||
"?"
|
||||
'?'
|
||||
)}?${JSON.stringify(params)
|
||||
.replace(/["{}]/g, "")
|
||||
.replace(/:/g, "=")
|
||||
.replace(/,/g, "&")}`;
|
||||
.replace(/["{}]/g, '')
|
||||
.replace(/:/g, '=')
|
||||
.replace(/,/g, '&')}`
|
||||
|
||||
// 替换历史记录项
|
||||
window.location.replace(newUrl);
|
||||
window.location.replace(newUrl)
|
||||
} else {
|
||||
return;
|
||||
return
|
||||
}
|
||||
})();
|
||||
})()
|
||||
|
@ -5,20 +5,20 @@
|
||||
*/
|
||||
export const extractPathList = (tree: any[]): any => {
|
||||
if (!Array.isArray(tree)) {
|
||||
console.warn("tree must be an array");
|
||||
return [];
|
||||
console.warn('tree must be an array')
|
||||
return []
|
||||
}
|
||||
if (!tree || tree.length === 0) return [];
|
||||
const expandedPaths: Array<number | string> = [];
|
||||
if (!tree || tree.length === 0) return []
|
||||
const expandedPaths: Array<number | string> = []
|
||||
for (const node of tree) {
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
const hasChildren = node.children && node.children.length > 0
|
||||
if (hasChildren) {
|
||||
extractPathList(node.children);
|
||||
extractPathList(node.children)
|
||||
}
|
||||
expandedPaths.push(node.uniqueId);
|
||||
expandedPaths.push(node.uniqueId)
|
||||
}
|
||||
return expandedPaths;
|
||||
};
|
||||
return expandedPaths
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 如果父级下children的length为1,删除children并自动组建唯一uniqueId
|
||||
@ -28,24 +28,24 @@ export const extractPathList = (tree: any[]): any => {
|
||||
*/
|
||||
export const deleteChildren = (tree: any[], pathList = []): any => {
|
||||
if (!Array.isArray(tree)) {
|
||||
console.warn("menuTree must be an array");
|
||||
return [];
|
||||
console.warn('menuTree must be an array')
|
||||
return []
|
||||
}
|
||||
if (!tree || tree.length === 0) return [];
|
||||
if (!tree || tree.length === 0) return []
|
||||
for (const [key, node] of tree.entries()) {
|
||||
if (node.children && node.children.length === 1) delete node.children;
|
||||
node.id = key;
|
||||
node.parentId = pathList.length ? pathList[pathList.length - 1] : null;
|
||||
node.pathList = [...pathList, node.id];
|
||||
if (node.children && node.children.length === 1) delete node.children
|
||||
node.id = key
|
||||
node.parentId = pathList.length ? pathList[pathList.length - 1] : null
|
||||
node.pathList = [...pathList, node.id]
|
||||
node.uniqueId =
|
||||
node.pathList.length > 1 ? node.pathList.join("-") : node.pathList[0];
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
node.pathList.length > 1 ? node.pathList.join('-') : node.pathList[0]
|
||||
const hasChildren = node.children && node.children.length > 0
|
||||
if (hasChildren) {
|
||||
deleteChildren(node.children, node.pathList);
|
||||
deleteChildren(node.children, node.pathList)
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
return tree
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建层级关系
|
||||
@ -55,21 +55,21 @@ export const deleteChildren = (tree: any[], pathList = []): any => {
|
||||
*/
|
||||
export const buildHierarchyTree = (tree: any[], pathList = []): any => {
|
||||
if (!Array.isArray(tree)) {
|
||||
console.warn("tree must be an array");
|
||||
return [];
|
||||
console.warn('tree must be an array')
|
||||
return []
|
||||
}
|
||||
if (!tree || tree.length === 0) return [];
|
||||
if (!tree || tree.length === 0) return []
|
||||
for (const [key, node] of tree.entries()) {
|
||||
node.id = key;
|
||||
node.parentId = pathList.length ? pathList[pathList.length - 1] : null;
|
||||
node.pathList = [...pathList, node.id];
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
node.id = key
|
||||
node.parentId = pathList.length ? pathList[pathList.length - 1] : null
|
||||
node.pathList = [...pathList, node.id]
|
||||
const hasChildren = node.children && node.children.length > 0
|
||||
if (hasChildren) {
|
||||
buildHierarchyTree(node.children, node.pathList);
|
||||
buildHierarchyTree(node.children, node.pathList)
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
return tree
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 广度优先遍历,根据唯一uniqueId找当前节点信息
|
||||
@ -82,18 +82,18 @@ export const getNodeByUniqueId = (
|
||||
uniqueId: number | string
|
||||
): any => {
|
||||
if (!Array.isArray(tree)) {
|
||||
console.warn("menuTree must be an array");
|
||||
return [];
|
||||
console.warn('menuTree must be an array')
|
||||
return []
|
||||
}
|
||||
if (!tree || tree.length === 0) return [];
|
||||
const item = tree.find(node => node.uniqueId === uniqueId);
|
||||
if (item) return item;
|
||||
if (!tree || tree.length === 0) return []
|
||||
const item = tree.find(node => node.uniqueId === uniqueId)
|
||||
if (item) return item
|
||||
const childrenList = tree
|
||||
.filter(node => node.children)
|
||||
.map(i => i.children)
|
||||
.flat(1) as unknown;
|
||||
return getNodeByUniqueId(childrenList as any[], uniqueId);
|
||||
};
|
||||
.flat(1) as unknown
|
||||
return getNodeByUniqueId(childrenList as any[], uniqueId)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 向当前唯一uniqueId节点中追加字段
|
||||
@ -108,23 +108,23 @@ export const appendFieldByUniqueId = (
|
||||
fields: object
|
||||
): any => {
|
||||
if (!Array.isArray(tree)) {
|
||||
console.warn("menuTree must be an array");
|
||||
return [];
|
||||
console.warn('menuTree must be an array')
|
||||
return []
|
||||
}
|
||||
if (!tree || tree.length === 0) return [];
|
||||
if (!tree || tree.length === 0) return []
|
||||
for (const node of tree) {
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
const hasChildren = node.children && node.children.length > 0
|
||||
if (
|
||||
node.uniqueId === uniqueId &&
|
||||
Object.prototype.toString.call(fields) === "[object Object]"
|
||||
Object.prototype.toString.call(fields) === '[object Object]'
|
||||
)
|
||||
Object.assign(node, fields);
|
||||
Object.assign(node, fields)
|
||||
if (hasChildren) {
|
||||
appendFieldByUniqueId(node.children, uniqueId, fields);
|
||||
appendFieldByUniqueId(node.children, uniqueId, fields)
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
return tree
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 构造树型结构数据
|
||||
@ -141,48 +141,48 @@ export const handleTree = (
|
||||
children?: string
|
||||
): any => {
|
||||
if (!Array.isArray(data)) {
|
||||
console.warn("data must be an array");
|
||||
return [];
|
||||
console.warn('data must be an array')
|
||||
return []
|
||||
}
|
||||
const config = {
|
||||
id: id || "id",
|
||||
parentId: parentId || "parentId",
|
||||
childrenList: children || "children"
|
||||
};
|
||||
id: id || 'id',
|
||||
parentId: parentId || 'parentId',
|
||||
childrenList: children || 'children'
|
||||
}
|
||||
|
||||
const childrenListMap: any = {};
|
||||
const nodeIds: any = {};
|
||||
const tree = [];
|
||||
const childrenListMap: any = {}
|
||||
const nodeIds: any = {}
|
||||
const tree = []
|
||||
|
||||
for (const d of data) {
|
||||
const parentId = d[config.parentId];
|
||||
const parentId = d[config.parentId]
|
||||
if (childrenListMap[parentId] == null) {
|
||||
childrenListMap[parentId] = [];
|
||||
childrenListMap[parentId] = []
|
||||
}
|
||||
nodeIds[d[config.id]] = d;
|
||||
childrenListMap[parentId].push(d);
|
||||
nodeIds[d[config.id]] = d
|
||||
childrenListMap[parentId].push(d)
|
||||
}
|
||||
|
||||
for (const d of data) {
|
||||
const parentId = d[config.parentId];
|
||||
const parentId = d[config.parentId]
|
||||
if (nodeIds[parentId] == null) {
|
||||
tree.push(d);
|
||||
tree.push(d)
|
||||
}
|
||||
}
|
||||
|
||||
for (const t of tree) {
|
||||
adaptToChildrenList(t);
|
||||
adaptToChildrenList(t)
|
||||
}
|
||||
|
||||
function adaptToChildrenList(o: Record<string, any>) {
|
||||
if (childrenListMap[o[config.id]] !== null) {
|
||||
o[config.childrenList] = childrenListMap[o[config.id]];
|
||||
o[config.childrenList] = childrenListMap[o[config.id]]
|
||||
}
|
||||
if (o[config.childrenList]) {
|
||||
for (const c of o[config.childrenList]) {
|
||||
adaptToChildrenList(c);
|
||||
adaptToChildrenList(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
return tree
|
||||
}
|
||||
|
10
src/views/basicData/GameData.vue
Normal file
10
src/views/basicData/GameData.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
||||
name: 'GameData'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>牌局数据</h1>
|
||||
</template>
|
14
src/views/basicData/LiveData.vue
Normal file
14
src/views/basicData/LiveData.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import ComingSoon from '@/views/error/fight.vue'
|
||||
defineOptions({
|
||||
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
||||
name: 'ComingSoon'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>实时数据</h1>
|
||||
<ComingSoon />
|
||||
</div>
|
||||
</template>
|
10
src/views/basicData/queryClub/index.vue
Normal file
10
src/views/basicData/queryClub/index.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
||||
name: 'ClubData'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>俱乐部数据</h1>
|
||||
</template>
|
10
src/views/basicData/queryClub/queryClub.vue
Normal file
10
src/views/basicData/queryClub/queryClub.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
||||
name: 'QueryClub'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>搜索俱乐部</h1>
|
||||
</template>
|
10
src/views/basicData/queryUser/index.vue
Normal file
10
src/views/basicData/queryUser/index.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
||||
name: 'UserData'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>用户数据</h1>
|
||||
</template>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user