feat: 项目初始化

This commit is contained in:
inc904 2023-03-17 11:59:52 +08:00
parent eae44feb59
commit c94d2e5737
112 changed files with 2886 additions and 2659 deletions

View File

@ -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
}
]
}
};
}

View File

@ -1,6 +1,7 @@
module.exports = {
bracketSpacing: true,
singleQuote: false,
arrowParens: "avoid",
trailingComma: "none"
};
// bracketSpacing: true,
singleQuote: true,
semi: false,
arrowParens: 'avoid',
trailingComma: 'none'
}

View File

@ -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",

View File

@ -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.

View File

@ -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: 敬请期待。

View File

@ -11,7 +11,7 @@ const permissionRouter = {
path: "/permission",
meta: {
title: "menus.permission",
icon: "lollipop",
icon: "informationLine",
rank: 10
},
children: [

View File

@ -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>

View File

@ -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')
}

View File

@ -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 })
}

View File

@ -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

View File

@ -1,5 +1,5 @@
import auth from "./src/auth";
import auth from './src/auth'
const Auth = auth;
const Auth = auth
export { Auth };
export { Auth }

View File

@ -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
}
});
}
})

View File

@ -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 }

View File

@ -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
});
})
}
});
})
}
}

View File

@ -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
});
})
}
}
});
})

View File

@ -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: () => []
}
);
)
}
});
})

View File

@ -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: () => []
}
);
)
}
});
})

View File

@ -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)

View File

@ -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
}

View File

@ -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 }

View File

@ -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']\"")
}
}
}
};

View File

@ -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)
}
}
};

View File

@ -1,2 +1,2 @@
export * from "./auth";
export * from "./elResizeDetector";
export * from './auth'
export * from './elResizeDetector'

View File

@ -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>

View File

@ -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>

View File

@ -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'
}
]
}
];
]

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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.resolvepath.resolve windows使electron
return path.posix.resolve(props.basePath, routePath);
return path.posix.resolve(props.basePath, routePath)
}
}
</script>

View File

@ -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>

View File

@ -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>

View File

@ -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')
// tagtag
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>

View File

@ -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>

View File

@ -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
};
}
}

View File

@ -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
};
}
}

View File

@ -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
};
}
}

View File

@ -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
};
}
}

View File

@ -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
};
}
}

View File

@ -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
};
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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')
})

View File

@ -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)
}

View File

@ -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

View File

@ -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)
})
}

View File

@ -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)
}

View File

@ -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

View 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')
}
}
]
}

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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
};
}

View File

@ -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 }

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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 {

View File

@ -1,6 +1,6 @@
@mixin clearfix {
&::after {
content: "";
content: '';
display: table;
clear: both;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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()
}
/** 格式化tokenjwt格式 */
export const formatToken = (token: string): string => {
return "Bearer " + token;
};
return 'Bearer ' + token
}

View File

@ -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 {}

View File

@ -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()

View File

@ -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>
}

View File

@ -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 }

View File

@ -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>()

View File

@ -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

View File

@ -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

View File

@ -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
});
})
}
}

View File

@ -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 })
}

View File

@ -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
}
})();
})()

View File

@ -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为1children并自动组建唯一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
}

View File

@ -0,0 +1,10 @@
<script setup lang="ts">
defineOptions({
// name name
name: 'GameData'
})
</script>
<template>
<h1>牌局数据</h1>
</template>

View 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>

View File

@ -0,0 +1,10 @@
<script setup lang="ts">
defineOptions({
// name name
name: 'ClubData'
})
</script>
<template>
<h1>俱乐部数据</h1>
</template>

View File

@ -0,0 +1,10 @@
<script setup lang="ts">
defineOptions({
// name name
name: 'QueryClub'
})
</script>
<template>
<h1>搜索俱乐部</h1>
</template>

View 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