Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
有来技术
vue3-element-admin
提交
4ae629ab
V
vue3-element-admin
项目概览
有来技术
/
vue3-element-admin
通知
3
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vue3-element-admin
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
4ae629ab
编写于
3月 11, 2022
作者:
郝
郝先瑞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
refactor: pinia整合优化重构
上级
7bd9d704
变更
26
隐藏空白更改
内联
并排
Showing
26 changed file
with
678 addition
and
566 deletion
+678
-566
src/App.vue
src/App.vue
+4
-2
src/components/LangSelect/index.vue
src/components/LangSelect/index.vue
+4
-3
src/components/RightPanel/index.vue
src/components/RightPanel/index.vue
+5
-2
src/components/SizeSelect/index.vue
src/components/SizeSelect/index.vue
+20
-20
src/components/ThemePicker/index.vue
src/components/ThemePicker/index.vue
+3
-3
src/directive/permission/index.ts
src/directive/permission/index.ts
+11
-8
src/layout/components/AppMain.vue
src/layout/components/AppMain.vue
+8
-7
src/layout/components/Navbar.vue
src/layout/components/Navbar.vue
+57
-46
src/layout/components/Settings/index.vue
src/layout/components/Settings/index.vue
+55
-43
src/layout/components/Sidebar/Link.vue
src/layout/components/Sidebar/Link.vue
+7
-4
src/layout/components/Sidebar/SidebarItem.vue
src/layout/components/Sidebar/SidebarItem.vue
+1
-1
src/layout/components/Sidebar/index.vue
src/layout/components/Sidebar/index.vue
+9
-7
src/layout/components/TagsView/ScrollPane.vue
src/layout/components/TagsView/ScrollPane.vue
+68
-44
src/layout/components/TagsView/index.vue
src/layout/components/TagsView/index.vue
+142
-109
src/layout/index.vue
src/layout/index.vue
+35
-30
src/main.ts
src/main.ts
+2
-2
src/permission.ts
src/permission.ts
+13
-13
src/store/index.ts
src/store/index.ts
+17
-3
src/store/modules/app.ts
src/store/modules/app.ts
+2
-5
src/store/modules/permission.ts
src/store/modules/permission.ts
+4
-5
src/store/modules/settings.ts
src/store/modules/settings.ts
+1
-5
src/store/modules/tagsView.ts
src/store/modules/tagsView.ts
+4
-9
src/store/modules/user.ts
src/store/modules/user.ts
+2
-16
src/utils/request.ts
src/utils/request.ts
+11
-8
src/views/dashboard/index.vue
src/views/dashboard/index.vue
+50
-45
src/views/login/index.vue
src/views/login/index.vue
+143
-126
未找到文件。
src/App.vue
浏览文件 @
4ae629ab
...
...
@@ -7,17 +7,19 @@
<
script
setup
lang=
"ts"
>
import
{
computed
,
onMounted
,
ref
,
watch
}
from
"
vue
"
;
import
{
useAppStoreHook
}
from
"
@/store/modules/app
"
;
import
{
ElConfigProvider
}
from
'
element-plus
'
import
{
localStorage
}
from
"
@/utils/storage
"
;
import
useStore
from
"
@/store
"
;
//官方文档: https://element-plus.gitee.io/zh-CN/guide/i18n.html
// 导入 Element Plus 语言包
import
zhCn
from
'
element-plus/es/locale/lang/zh-cn
'
import
en
from
'
element-plus/es/locale/lang/en
'
const
language
=
computed
(()
=>
useAppStoreHook
().
language
)
const
{
app
}
=
useStore
()
const
language
=
computed
(()
=>
app
.
language
)
const
locale
=
ref
()
watch
(
language
,
(
value
)
=>
{
...
...
src/components/LangSelect/index.vue
浏览文件 @
4ae629ab
...
...
@@ -19,9 +19,10 @@
<
script
setup
lang=
"ts"
>
import
{
computed
}
from
"
vue
"
;
import
{
useAppStoreHook
}
from
"
@/store/modules/app
"
;
import
useStore
from
"
@/store
"
;
const
language
=
computed
(()
=>
useAppStoreHook
().
language
)
const
{
app
}
=
useStore
()
const
language
=
computed
(()
=>
app
.
language
)
import
{
useI18n
}
from
'
vue-i18n
'
import
{
ElMessage
}
from
'
element-plus
'
...
...
@@ -31,7 +32,7 @@ const {locale} = useI18n()
function
handleSetLanguage
(
lang
:
string
)
{
locale
.
value
=
lang
useAppStoreHook
()
.
setLanguage
(
lang
)
app
.
setLanguage
(
lang
)
if
(
lang
==
'
en
'
)
{
ElMessage
.
success
(
'
Switch Language Successful!
'
)
}
else
{
...
...
src/components/RightPanel/index.vue
浏览文件 @
4ae629ab
...
...
@@ -17,12 +17,15 @@
import
{
computed
,
onBeforeUnmount
,
onMounted
,
ref
,
watch
}
from
"
vue
"
;
import
{
addClass
,
removeClass
}
from
'
@/utils/index
'
import
{
useSettingStoreHook
}
from
"
@/store/modules/settings
"
;
import
useStore
from
"
@/store
"
;
// 图标依赖
import
{
Close
,
Setting
}
from
'
@element-plus/icons-vue
'
import
{
ElColorPicker
}
from
"
element-plus
"
;
const
{
setting
}
=
useStore
()
const
props
=
defineProps
({
buttonTop
:
{
default
:
250
,
...
...
@@ -30,7 +33,7 @@ const props = defineProps({
}
})
const
theme
=
computed
(()
=>
useSettingStoreHook
()
.
theme
)
const
theme
=
computed
(()
=>
setting
.
theme
)
const
show
=
ref
(
false
)
...
...
src/components/SizeSelect/index.vue
浏览文件 @
4ae629ab
<
template
>
<el-dropdown
class=
"size-select"
trigger=
"click"
@
command=
"handleSetSize"
>
<div
class=
"size-select__icon"
>
<svg-icon
class-name=
"size-icon"
icon-class=
"size"
/>
<svg-icon
class-name=
"size-icon"
icon-class=
"size"
/>
</div>
<template
#dropdown
>
<el-dropdown-menu>
<el-dropdown-item
v-for=
"item of sizeOptions"
:key=
"item.value"
:disabled=
"(size||'default')==item.value"
:command=
"item.value"
>
v-for=
"item of sizeOptions"
:key=
"item.value"
:disabled=
"(size || 'default') == item.value"
:command=
"item.value"
>
{{
item
.
label
}}
</el-dropdown-item>
</el-dropdown-menu>
...
...
@@ -18,29 +19,28 @@
</template>
<
script
setup
lang=
"ts"
>
import
{
ref
,
computed
}
from
"
vue
"
;
import
{
useRoute
,
useRouter
}
from
"
vue-router
"
;
import
{
ref
,
computed
}
from
"
vue
"
;
import
{
useRoute
,
useRouter
}
from
"
vue-router
"
import
{
ElMessage
}
from
"
element-plus
"
;
import
{
ElMessage
}
from
'
element-plus
'
import
useStore
from
"
@/store
"
;
import
SvgIcon
from
"
@/components/SvgIcon/index.vue
"
;
import
{
useAppStoreHook
}
from
'
@/store/modules/app
'
import
SvgIcon
from
'
@/components/SvgIcon/index.vue
'
const
size
=
computed
(()
=>
useAppStoreHook
().
size
)
const
{
app
}
=
useStore
();
const
size
=
computed
(()
=>
app
.
size
);
const
sizeOptions
=
ref
([
{
label
:
'
默认
'
,
value
:
'
default
'
},
{
label
:
'
大型
'
,
value
:
'
large
'
},
{
label
:
'
小型
'
,
value
:
'
small
'
}
])
{
label
:
"
默认
"
,
value
:
"
default
"
},
{
label
:
"
大型
"
,
value
:
"
large
"
},
{
label
:
"
小型
"
,
value
:
"
small
"
},
])
;
function
handleSetSize
(
size
:
string
)
{
useAppStoreHook
().
setSize
(
size
)
window
.
location
.
reload
()
ElMessage
.
success
(
'
切换布局大小成功
'
)
app
.
setSize
(
size
);
window
.
location
.
reload
()
;
ElMessage
.
success
(
"
切换布局大小成功
"
);
}
</
script
>
<
style
lang=
'scss'
scoped
>
...
...
src/components/ThemePicker/index.vue
浏览文件 @
4ae629ab
...
...
@@ -9,8 +9,7 @@
<
script
setup
lang=
"ts"
>
import
{
computed
,
nextTick
,
watch
}
from
"
vue
"
;
import
{
useSettingStoreHook
}
from
"
@/store/modules/settings
"
;
import
{
useTagsViewStoreHook
}
from
"
@/store/modules/tagsView
"
;
import
useStore
from
"
@/store
"
;
import
{
useRoute
,
useRouter
}
from
"
vue-router
"
;
import
{
localStorage
}
from
"
@/utils/storage
"
;
...
...
@@ -23,7 +22,8 @@ const mixBlack = "#000000";
const
node
=
document
.
documentElement
;
const
theme
=
computed
(()
=>
useSettingStoreHook
().
theme
)
const
{
setting
}
=
useStore
()
const
theme
=
computed
(()
=>
setting
.
theme
)
watch
(
theme
,
(
color
:
string
)
=>
{
node
.
style
.
setProperty
(
"
--el-color-primary
"
,
color
);
...
...
src/directive/permission/index.ts
浏览文件 @
4ae629ab
import
{
useUserStoreHook
}
from
"
@/store/modules/user
"
;
import
{
Directive
,
DirectiveBinding
}
from
"
vue
"
;
import
useStore
from
"
@/store
"
;
import
{
Directive
,
DirectiveBinding
}
from
"
vue
"
;
/**
* 按钮权限校验
...
...
@@ -7,16 +8,17 @@ import {Directive, DirectiveBinding} from "vue";
export
const
hasPerm
:
Directive
=
{
mounted
(
el
:
HTMLElement
,
binding
:
DirectiveBinding
)
{
// 「超级管理员」拥有所有的按钮权限
const
roles
=
useUserStoreHook
().
roles
;
const
{
user
}
=
useStore
()
const
roles
=
user
.
roles
;
if
(
roles
.
includes
(
'
ROOT
'
))
{
return
true
}
// 「其他角色」按钮权限校验
const
{
value
}
=
binding
;
const
{
value
}
=
binding
;
if
(
value
)
{
const
requiredPerms
=
value
;
// DOM绑定需要的按钮权限标识
const
hasPerm
=
use
UserStoreHook
()
.
perms
.
some
(
perm
=>
{
const
hasPerm
=
use
r
.
perms
.
some
(
perm
=>
{
return
requiredPerms
.
includes
(
perm
)
})
...
...
@@ -34,11 +36,12 @@ export const hasPerm: Directive = {
*/
export
const
hasRole
:
Directive
=
{
mounted
(
el
:
HTMLElement
,
binding
:
DirectiveBinding
)
{
const
{
value
}
=
binding
;
const
{
value
}
=
binding
;
if
(
value
)
{
const
requiredRoles
=
value
;
// DOM绑定需要的角色编码
const
hasRole
=
use
UserStoreHook
()
.
roles
.
some
(
perm
=>
{
const
{
user
}
=
useStore
()
const
hasRole
=
use
r
.
roles
.
some
(
perm
=>
{
return
requiredRoles
.
includes
(
perm
)
})
...
...
src/layout/components/AppMain.vue
浏览文件 @
4ae629ab
<
template
>
<section
class=
"app-main"
>
<router-view
v-slot=
"
{ Component
,route
}">
<router-view
v-slot=
"
{ Component
, route
}">
<transition
name=
"router-fade"
mode=
"out-in"
>
<keep-alive
:include=
"cachedViews"
>
<component
:is=
"Component"
:key=
"route.path"
/>
<component
:is=
"Component"
:key=
"route.path"
/>
</keep-alive>
</transition>
</router-view>
...
...
@@ -12,11 +12,12 @@
<
script
setup
lang=
"ts"
>
import
{
computed
}
from
"
vue
"
;
import
{
useTagsViewStoreHook
}
from
'
@/store/modules/tagsView
'
import
{
computed
}
from
"
vue
"
;
import
useStore
from
"
@/store
"
;
const
cachedViews
=
computed
(()
=>
useTagsViewStoreHook
().
cachedViews
);
const
{
tagsView
}
=
useStore
(
);
const
cachedViews
=
computed
(()
=>
tagsView
.
cachedViews
);
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
@@ -28,7 +29,7 @@ const cachedViews = computed(() => useTagsViewStoreHook().cachedViews);
overflow
:
hidden
;
}
.fixed-header
+
.app-main
{
.fixed-header
+
.app-main
{
padding-top
:
50px
;
}
...
...
@@ -38,7 +39,7 @@ const cachedViews = computed(() => useTagsViewStoreHook().cachedViews);
min-height
:
calc
(
100vh
-
84px
);
}
.fixed-header
+
.app-main
{
.fixed-header
+
.app-main
{
padding-top
:
84px
;
}
}
...
...
src/layout/components/Navbar.vue
浏览文件 @
4ae629ab
<
template
>
<div
class=
"navbar"
>
<hamburger
id=
"hamburger-container"
:is-active=
"sidebar.opened"
class=
"hamburger-container"
@
toggleClick=
"toggleSideBar"
/>
<hamburger
id=
"hamburger-container"
:is-active=
"sidebar.opened"
class=
"hamburger-container"
@
toggleClick=
"toggleSideBar"
/>
<breadcrumb
id=
"breadcrumb-container"
class=
"breadcrumb-container"
/>
<breadcrumb
id=
"breadcrumb-container"
class=
"breadcrumb-container"
/>
<div
class=
"right-menu"
>
<template
v-if=
"device
!==
'mobile'"
>
<template
v-if=
"device
!==
'mobile'"
>
<!--
<search
id=
"header-search"
class=
"right-menu-item"
/>
<error-log
class=
"errLog-container right-menu-item hover-effect"
/>
-->
<screenfull
id=
"screenfull"
class=
"right-menu-item hover-effect"
/>
<screenfull
id=
"screenfull"
class=
"right-menu-item hover-effect"
/>
<el-tooltip
content=
"布局大小"
effect=
"dark"
placement=
"bottom"
>
<size-select
id=
"size-select"
class=
"right-menu-item hover-effect"
/>
<size-select
id=
"size-select"
class=
"right-menu-item hover-effect"
/>
</el-tooltip>
<lang-select
class=
"right-menu-item hover-effect"
/>
</
template
>
<el-dropdown
class=
"avatar-container right-menu-item hover-effect"
trigger=
"click"
>
<el-dropdown
class=
"avatar-container right-menu-item hover-effect"
trigger=
"click"
>
<div
class=
"avatar-wrapper"
>
<img
:src=
"avatar
+'?imageView2/1/w/80/h/80'"
class=
"user-avatar"
>
<CaretBottom
style=
"width:
.6em; height: .6em;margin-left: 5px"
/>
<img
:src=
"avatar
+ '?imageView2/1/w/80/h/80'"
class=
"user-avatar"
/
>
<CaretBottom
style=
"width:
0.6em; height: 0.6em; margin-left: 5px"
/>
</div>
<
template
#dropdown
>
<el-dropdown-menu>
<router-link
to=
"/"
>
<el-dropdown-item>
{{
$t
(
'
navbar.dashboard
'
)
}}
</el-dropdown-item>
<el-dropdown-item>
{{
$t
(
"
navbar.dashboard
"
)
}}
</el-dropdown-item>
</router-link>
<a
target=
"_blank"
href=
"https://github.com/hxrui"
>
<el-dropdown-item>
Github
</el-dropdown-item>
</a>
<a
target=
"_blank"
href=
"https://gitee.com/haoxr"
>
<el-dropdown-item>
{{
$t
(
'
navbar.gitee
'
)
}}
</el-dropdown-item>
<el-dropdown-item>
{{
$t
(
"
navbar.gitee
"
)
}}
</el-dropdown-item>
</a>
<a
target=
"_blank"
href=
"https://www.cnblogs.com/haoxianrui/"
>
<el-dropdown-item>
{{
$t
(
'
navbar.document
'
)
}}
</el-dropdown-item>
<el-dropdown-item>
{{
$t
(
"
navbar.document
"
)
}}
</el-dropdown-item>
</a>
<el-dropdown-item
divided
@
click=
"logout"
>
{{
$t
(
'
navbar.logout
'
)
}}
{{
$t
(
"
navbar.logout
"
)
}}
</el-dropdown-item>
</el-dropdown-menu>
</
template
>
</el-dropdown>
</div>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
computed
}
from
"
vue
"
import
{
useRoute
,
useRouter
}
from
"
vue-router
"
import
{
ElMessageBox
}
from
'
element-plus
'
import
{
computed
}
from
"
vue
"
;
import
{
useRoute
,
useRouter
}
from
"
vue-router
"
;
import
{
ElMessageBox
}
from
"
element-plus
"
;
import
{
useAppStoreHook
}
from
'
@/store/modules/app
'
import
{
useUserStoreHook
}
from
'
@/store/modules/user
'
import
useStore
from
"
@/store
"
;
// 组件依赖
import
Breadcrumb
from
'
@/components/Breadcrumb/index.vue
'
import
Hamburger
from
'
@/components/Hamburger/index.vue
'
import
Screenfull
from
'
@/components/Screenfull/index.vue
'
import
SizeSelect
from
'
@/components/SizeSelect/index.vue
'
import
LangSelect
from
'
@/components/LangSelect/index.vue
'
import
SvgIcon
from
'
@/components/SvgIcon/index.vue
'
import
Breadcrumb
from
"
@/components/Breadcrumb/index.vue
"
;
import
Hamburger
from
"
@/components/Hamburger/index.vue
"
;
import
Screenfull
from
"
@/components/Screenfull/index.vue
"
;
import
SizeSelect
from
"
@/components/SizeSelect/index.vue
"
;
import
LangSelect
from
"
@/components/LangSelect/index.vue
"
;
import
SvgIcon
from
"
@/components/SvgIcon/index.vue
"
;
// 图标依赖
import
{
CaretBottom
}
from
'
@element-plus/icons-vue
'
import
{
CaretBottom
}
from
"
@element-plus/icons-vue
"
;
const
{
app
,
user
}
=
useStore
();
const
route
=
useRoute
()
const
router
=
useRouter
()
const
route
=
useRoute
()
;
const
router
=
useRouter
()
;
const
sidebar
=
computed
(()
=>
useAppStoreHook
().
sidebar
)
const
device
=
computed
(()
=>
useAppStoreHook
().
device
)
const
avatar
=
computed
(()
=>
use
UserStoreHook
().
avatar
)
const
sidebar
=
computed
(()
=>
app
.
sidebar
);
const
device
=
computed
(()
=>
app
.
device
);
const
avatar
=
computed
(()
=>
use
r
.
avatar
);
function
toggleSideBar
()
{
useAppStoreHook
().
toggleSidebar
()
app
.
toggleSidebar
();
}
function
logout
()
{
ElMessageBox
.
confirm
(
'
确定注销并退出系统吗?
'
,
'
提示
'
,
{
confirmButtonText
:
'
确定
'
,
cancelButtonText
:
'
取消
'
,
type
:
'
warning
'
ElMessageBox
.
confirm
(
"
确定注销并退出系统吗?
"
,
"
提示
"
,
{
confirmButtonText
:
"
确定
"
,
cancelButtonText
:
"
取消
"
,
type
:
"
warning
"
,
}).
then
(()
=>
{
use
UserStoreHook
()
.
logout
().
then
(()
=>
{
router
.
push
(
`/login?redirect=
${
route
.
fullPath
}
`
)
})
})
use
r
.
logout
().
then
(()
=>
{
router
.
push
(
`/login?redirect=
${
route
.
fullPath
}
`
)
;
})
;
})
;
}
</
script
>
<
style
lang=
"scss"
scoped
>
ul
{
list-style
:
none
;
margin
:
0
;
padding
:
0
;
}
ul
{
list-style
:
none
;
margin
:
0
;
padding
:
0
;
}
.navbar
{
height
:
50px
;
overflow
:
hidden
;
position
:
relative
;
background
:
#fff
;
box-shadow
:
0
1px
4px
rgba
(
0
,
21
,
41
,
.08
);
box-shadow
:
0
1px
4px
rgba
(
0
,
21
,
41
,
0
.08
);
.hamburger-container
{
line-height
:
46px
;
height
:
100%
;
float
:
left
;
cursor
:
pointer
;
transition
:
background
.3s
;
transition
:
background
0
.3s
;
-webkit-tap-highlight-color
:
transparent
;
&
:hover
{
background
:
rgba
(
0
,
0
,
0
,
.025
)
background
:
rgba
(
0
,
0
,
0
,
0
.025
);
}
}
...
...
@@ -134,10 +145,10 @@ ul { list-style: none; margin: 0; padding: 0; }
&
.hover-effect
{
cursor
:
pointer
;
transition
:
background
.3s
;
transition
:
background
0
.3s
;
&
:hover
{
background
:
rgba
(
0
,
0
,
0
,
.025
)
background
:
rgba
(
0
,
0
,
0
,
0
.025
);
}
}
}
...
...
src/layout/components/Settings/index.vue
浏览文件 @
4ae629ab
<
template
>
<div
class=
"drawer-container"
>
<h3
class=
"drawer-title"
>
系统布局配置
</h3>
<div
class=
"drawer-item"
>
<span>
主题颜色
</span>
<div
style=
"float: right;height: 26px;margin: -3px 8px 0 0;"
>
<theme-picker
@
change=
"themeChange"
/>
</div>
</div>
<div
class=
"drawer-item"
>
<span>
开启 Tags-View
</span>
<el-switch
v-model=
"tagsView"
class=
"drawer-switch"
/>
</div>
<div
class=
"drawer-item"
>
<span>
固定 Header
</span>
<el-switch
v-model=
"fixedHeader"
class=
"drawer-switch"
/>
</div>
<div
class=
"drawer-item"
>
<span>
侧边栏 Logo
</span>
<el-switch
v-model=
"sidebarLogo"
class=
"drawer-switch"
/>
<h3
class=
"drawer-title"
>
系统布局配置
</h3>
<div
class=
"drawer-item"
>
<span>
主题颜色
</span>
<div
style=
"float: right; height: 26px; margin: -3px 8px 0 0"
>
<theme-picker
@
change=
"themeChange"
/>
</div>
</div>
<div
class=
"drawer-item"
>
<span>
开启 Tags-View
</span>
<el-switch
v-model=
"tagsView"
class=
"drawer-switch"
/>
</div>
<div
class=
"drawer-item"
>
<span>
固定 Header
</span>
<el-switch
v-model=
"fixedHeader"
class=
"drawer-switch"
/>
</div>
<div
class=
"drawer-item"
>
<span>
侧边栏 Logo
</span>
<el-switch
v-model=
"sidebarLogo"
class=
"drawer-switch"
/>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
reactive
,
toRefs
,
watch
}
from
"
vue
"
;
import
{
useSettingStoreHook
}
from
"
@/store/modules/settings
"
;
import
ThemePicker
from
'
@/components/ThemePicker/index.vue
'
;
import
{
reactive
,
toRefs
,
watch
}
from
"
vue
"
;
import
ThemePicker
from
"
@/components/ThemePicker/index.vue
"
;
import
useStore
from
"
@/store
"
;
const
{
setting
}
=
useStore
();
const
state
=
reactive
({
fixedHeader
:
useSettingStoreHook
()
.
fixedHeader
,
tagsView
:
useSettingStoreHook
()
.
tagsView
,
sidebarLogo
:
useSettingStoreHook
().
sidebarLogo
})
fixedHeader
:
setting
.
fixedHeader
,
tagsView
:
setting
.
tagsView
,
sidebarLogo
:
setting
.
sidebarLogo
,
})
;
const
{
fixedHeader
,
tagsView
,
sidebarLogo
}
=
toRefs
(
state
)
const
{
fixedHeader
,
tagsView
,
sidebarLogo
}
=
toRefs
(
state
);
function
themeChange
(
val
:
any
)
{
useSettingStoreHook
().
changeSetting
({
key
:
'
theme
'
,
value
:
val
})
setting
.
changeSetting
({
key
:
"
theme
"
,
value
:
val
});
}
watch
(()
=>
state
.
fixedHeader
,
(
value
)
=>
{
useSettingStoreHook
().
changeSetting
({
key
:
'
fixedHeader
'
,
value
:
value
})
})
watch
(()
=>
state
.
tagsView
,
(
value
)
=>
{
useSettingStoreHook
().
changeSetting
({
key
:
'
tagsView
'
,
value
:
value
})
})
watch
(
()
=>
state
.
fixedHeader
,
(
value
)
=>
{
setting
.
changeSetting
({
key
:
"
fixedHeader
"
,
value
:
value
});
}
);
watch
(()
=>
state
.
sidebarLogo
,
(
value
)
=>
{
useSettingStoreHook
().
changeSetting
({
key
:
'
sidebarLogo
'
,
value
:
value
})
})
watch
(
()
=>
state
.
tagsView
,
(
value
)
=>
{
setting
.
changeSetting
({
key
:
"
tagsView
"
,
value
:
value
});
}
);
watch
(
()
=>
state
.
sidebarLogo
,
(
value
)
=>
{
setting
.
changeSetting
({
key
:
"
sidebarLogo
"
,
value
:
value
});
}
);
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
@@ -65,19 +77,19 @@ watch(() => state.sidebarLogo, (value) => {
.drawer-title
{
margin-bottom
:
12px
;
color
:
rgba
(
0
,
0
,
0
,
.85
);
color
:
rgba
(
0
,
0
,
0
,
0
.85
);
font-size
:
14px
;
line-height
:
22px
;
}
.drawer-item
{
color
:
rgba
(
0
,
0
,
0
,
.65
);
color
:
rgba
(
0
,
0
,
0
,
0
.65
);
font-size
:
14px
;
padding
:
12px
0
;
}
.drawer-switch
{
float
:
right
float
:
right
;
}
.job-link
{
...
...
src/layout/components/Sidebar/Link.vue
浏览文件 @
4ae629ab
...
...
@@ -19,10 +19,13 @@
import
{
computed
,
defineComponent
}
from
'
vue
'
import
{
isExternal
}
from
'
@/utils/validate
'
import
{
useRouter
}
from
'
vue-router
'
import
{
useAppStoreHook
}
from
"
@/store/modules/app
"
;
const
sidebar
=
computed
(()
=>
useAppStoreHook
().
sidebar
);
const
device
=
computed
(()
=>
useAppStoreHook
().
device
);
import
useStore
from
"
@/store
"
;
const
{
app
}
=
useStore
()
const
sidebar
=
computed
(()
=>
app
.
sidebar
);
const
device
=
computed
(()
=>
app
.
device
);
export
default
defineComponent
({
props
:
{
...
...
@@ -35,7 +38,7 @@ export default defineComponent({
const
router
=
useRouter
()
const
push
=
()
=>
{
if
(
device
.
value
===
'
mobile
'
&&
sidebar
.
value
.
opened
==
true
)
{
useAppStoreHook
()
.
closeSideBar
(
false
)
app
.
closeSideBar
(
false
)
}
router
.
push
(
props
.
to
).
catch
((
err
)
=>
{
console
.
log
(
err
)
...
...
src/layout/components/Sidebar/SidebarItem.vue
浏览文件 @
4ae629ab
...
...
@@ -39,8 +39,8 @@ import {isExternal} from '@/utils/validate'
import
AppLink
from
'
./Link.vue
'
import
{
RouteRecordRaw
}
from
"
vue-router
"
;
import
SvgIcon
from
'
@/components/SvgIcon/index.vue
'
;
import
{
generateTitle
}
from
'
@/utils/i18n
'
import
SvgIcon
from
'
@/components/SvgIcon/index.vue
'
;
const
props
=
defineProps
({
item
:
{
...
...
src/layout/components/Sidebar/index.vue
浏览文件 @
4ae629ab
...
...
@@ -24,20 +24,22 @@
</
template
>
<
script
setup
lang=
"ts"
>
import
{
computed
,
defineComponent
}
from
"
vue
"
;
import
{
useRoute
}
from
'
vue-router
'
import
SidebarItem
from
'
./SidebarItem.vue
'
import
Logo
from
'
./Logo.vue
'
import
variables
from
'
@/styles/variables.module.scss
'
import
{
useSettingStoreHook
}
from
"
@/store/modules/settings
"
;
import
{
useAppStoreHook
}
from
"
@/store/modules/app
"
;
import
{
usePermissionStoreHook
}
from
"
@/store/modules/permission
"
;
import
{
useRoute
}
from
'
vue-router
'
import
useStore
from
"
@/store
"
;
const
{
permission
,
setting
,
app
}
=
useStore
();
const
route
=
useRoute
()
const
routes
=
computed
(()
=>
usePermissionStoreHook
()
.
routes
)
const
showLogo
=
computed
(()
=>
useSettingStoreHook
()
.
sidebarLogo
)
const
isCollapse
=
computed
(()
=>
!
useAppStoreHook
()
.
sidebar
.
opened
)
const
routes
=
computed
(()
=>
permission
.
routes
)
const
showLogo
=
computed
(()
=>
setting
.
sidebarLogo
)
const
isCollapse
=
computed
(()
=>
!
app
.
sidebar
.
opened
)
const
activeMenu
=
computed
(()
=>
{
const
{
meta
,
path
}
=
route
...
...
src/layout/components/TagsView/ScrollPane.vue
浏览文件 @
4ae629ab
<
template
>
<el-scrollbar
ref=
"scrollContainerRef"
:vertical=
"false"
class=
"scroll-container"
@
wheel.prevent=
"handleScroll"
>
<slot/>
ref=
"scrollContainerRef"
:vertical=
"false"
class=
"scroll-container"
@
wheel.prevent=
"handleScroll"
>
<slot
/>
</el-scrollbar>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
computed
,
onMounted
,
onBeforeUnmount
,
getCurrentInstance
}
from
"
vue
"
;
import
{
useTagsViewStoreHook
}
from
"
@/store/modules/tagsView
"
import
{
TagView
}
from
"
@/store/interface
"
;
const
emits
=
defineEmits
()
import
{
ref
,
computed
,
onMounted
,
onBeforeUnmount
,
getCurrentInstance
,
}
from
"
vue
"
;
import
{
TagView
}
from
"
@/store/interface
"
;
import
useStore
from
"
@/store
"
;
const
tagAndTagSpacing
=
ref
(
4
)
const
scrollContainerRef
=
ref
(
null
)
const
visitedViews
=
computed
(()
=>
useTagsViewStoreHook
().
visitedViews
)
const
emits
=
defineEmits
();
const
tagAndTagSpacing
=
ref
(
4
);
const
scrollContainerRef
=
ref
(
null
);
const
{
tagsView
}
=
useStore
();
const
visitedViews
=
computed
(()
=>
tagsView
.
visitedViews
);
const
emitScroll
=
()
=>
{
(
emits
as
any
)(
'
scroll
'
)
}
(
emits
as
any
)(
"
scroll
"
);
}
;
const
{
ctx
}
=
getCurrentInstance
()
as
any
const
{
ctx
}
=
getCurrentInstance
()
as
any
;
const
scrollWrapper
=
computed
(()
=>
{
return
(
scrollContainerRef
.
value
as
any
).
$refs
.
wrap
as
HTMLElement
})
return
(
scrollContainerRef
.
value
as
any
).
$refs
.
wrap
as
HTMLElement
;
})
;
onMounted
(()
=>
{
//scrollWrapper.value.addEventListener('scroll', emitScroll, true);
})
})
;
onBeforeUnmount
(()
=>
{
// scrollWrapper.value.removeEventListener('scroll', emitScroll);
})
})
;
function
handleScroll
(
e
:
WheelEvent
)
{
const
eventDelta
=
(
e
as
any
).
wheelDelta
||
-
e
.
deltaY
*
40
scrollWrapper
.
value
.
scrollLeft
=
scrollWrapper
.
value
.
scrollLeft
+
eventDelta
/
4
const
eventDelta
=
(
e
as
any
).
wheelDelta
||
-
e
.
deltaY
*
40
;
scrollWrapper
.
value
.
scrollLeft
=
scrollWrapper
.
value
.
scrollLeft
+
eventDelta
/
4
;
}
function
moveToTarget
(
currentTag
:
TagView
)
{
const
$container
=
ctx
.
$refs
.
scrollContainer
.
$el
const
$containerWidth
=
$container
.
offsetWidth
function
moveToTarget
(
currentTag
:
TagView
)
{
const
$container
=
ctx
.
$refs
.
scrollContainer
.
$el
;
const
$containerWidth
=
$container
.
offsetWidth
;
const
$scrollWrapper
=
scrollWrapper
.
value
;
let
firstTag
=
null
let
lastTag
=
null
let
firstTag
=
null
;
let
lastTag
=
null
;
// find first tag and last tag
if
(
visitedViews
.
value
.
length
>
0
)
{
firstTag
=
visitedViews
.
value
[
0
]
lastTag
=
visitedViews
.
value
[
visitedViews
.
value
.
length
-
1
]
firstTag
=
visitedViews
.
value
[
0
]
;
lastTag
=
visitedViews
.
value
[
visitedViews
.
value
.
length
-
1
]
;
}
if
(
firstTag
===
currentTag
)
{
$scrollWrapper
.
scrollLeft
=
0
$scrollWrapper
.
scrollLeft
=
0
;
}
else
if
(
lastTag
===
currentTag
)
{
$scrollWrapper
.
scrollLeft
=
$scrollWrapper
.
scrollWidth
-
$containerWidth
$scrollWrapper
.
scrollLeft
=
$scrollWrapper
.
scrollWidth
-
$containerWidth
;
}
else
{
const
tagListDom
=
document
.
getElementsByClassName
(
'
tags-view-item
'
);
const
currentIndex
=
visitedViews
.
value
.
findIndex
(
item
=>
item
===
currentTag
)
let
prevTag
=
null
let
nextTag
=
null
const
tagListDom
=
document
.
getElementsByClassName
(
"
tags-view-item
"
);
const
currentIndex
=
visitedViews
.
value
.
findIndex
(
(
item
)
=>
item
===
currentTag
);
let
prevTag
=
null
;
let
nextTag
=
null
;
for
(
const
k
in
tagListDom
)
{
if
(
k
!==
'
length
'
&&
Object
.
hasOwnProperty
.
call
(
tagListDom
,
k
))
{
if
((
tagListDom
[
k
]
as
any
).
dataset
.
path
===
visitedViews
.
value
[
currentIndex
-
1
].
path
)
{
prevTag
=
tagListDom
[
k
]
;
if
(
k
!==
"
length
"
&&
Object
.
hasOwnProperty
.
call
(
tagListDom
,
k
))
{
if
(
(
tagListDom
[
k
]
as
any
).
dataset
.
path
===
visitedViews
.
value
[
currentIndex
-
1
].
path
)
{
prevTag
=
tagListDom
[
k
];
}
if
((
tagListDom
[
k
]
as
any
).
dataset
.
path
===
visitedViews
.
value
[
currentIndex
+
1
].
path
)
{
if
(
(
tagListDom
[
k
]
as
any
).
dataset
.
path
===
visitedViews
.
value
[
currentIndex
+
1
].
path
)
{
nextTag
=
tagListDom
[
k
];
}
}
}
// the tag's offsetLeft after of nextTag
const
afterNextTagOffsetLeft
=
(
nextTag
as
any
).
offsetLeft
+
(
nextTag
as
any
).
offsetWidth
+
tagAndTagSpacing
.
value
const
afterNextTagOffsetLeft
=
(
nextTag
as
any
).
offsetLeft
+
(
nextTag
as
any
).
offsetWidth
+
tagAndTagSpacing
.
value
;
// the tag's offsetLeft before of prevTag
const
beforePrevTagOffsetLeft
=
(
prevTag
as
any
).
offsetLeft
-
tagAndTagSpacing
.
value
const
beforePrevTagOffsetLeft
=
(
prevTag
as
any
).
offsetLeft
-
tagAndTagSpacing
.
value
;
if
(
afterNextTagOffsetLeft
>
$scrollWrapper
.
scrollLeft
+
$containerWidth
)
{
$scrollWrapper
.
scrollLeft
=
afterNextTagOffsetLeft
-
$containerWidth
$scrollWrapper
.
scrollLeft
=
afterNextTagOffsetLeft
-
$containerWidth
;
}
else
if
(
beforePrevTagOffsetLeft
<
$scrollWrapper
.
scrollLeft
)
{
$scrollWrapper
.
scrollLeft
=
beforePrevTagOffsetLeft
$scrollWrapper
.
scrollLeft
=
beforePrevTagOffsetLeft
;
}
}
}
defineExpose
({
moveToTarget
})
moveToTarget
,
})
;
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
src/layout/components/TagsView/index.vue
浏览文件 @
4ae629ab
<
template
>
<div
id=
"tags-view-container"
class=
"tags-view-container"
>
<scroll-pane
ref=
"scrollPaneRef"
class=
"tags-view-wrapper"
@
scroll=
"handleScroll"
>
<scroll-pane
ref=
"scrollPaneRef"
class=
"tags-view-wrapper"
@
scroll=
"handleScroll"
>
<router-link
v-for=
"tag in visitedViews"
:key=
"tag.path"
:class=
"isActive(tag)?'active':
''"
:to=
"
{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
class="tags-view-item"
@click.middle="!isAffix(tag)?closeSelectedTag(tag):
''"
@contextmenu.prevent="openMenu(tag,
$event)"
v-for=
"tag in visitedViews"
:key=
"tag.path"
:class=
"isActive(tag) ? 'active' :
''"
:to=
"
{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
class="tags-view-item"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) :
''"
@contextmenu.prevent="openMenu(tag,
$event)"
>
{{
generateTitle
(
tag
.
meta
.
title
)
}}
<span
v-if=
"!isAffix(tag)"
class=
"el-icon-close"
@
click.prevent.stop=
"closeSelectedTag(tag)"
>
<close
class=
"el-icon-close"
style=
"width: 1em; height: 1em;vertical-align: middle;"
/>
<span
v-if=
"!isAffix(tag)"
class=
"el-icon-close"
@
click.prevent.stop=
"closeSelectedTag(tag)"
>
<close
class=
"el-icon-close"
style=
"width: 1em; height: 1em; vertical-align: middle"
/>
</span>
</router-link>
</scroll-pane>
<ul
v-show=
"visible"
:style=
"
{left:left+'px',top:top+'px'}" class="contextmenu">
<ul
v-show=
"visible"
:style=
"
{ left: left + 'px', top: top + 'px' }"
class="contextmenu"
>
<li
@
click=
"refreshSelectedTag(selectedTag)"
>
<refresh-right
style=
"width: 1em; height: 1em
;"
/>
<refresh-right
style=
"width: 1em; height: 1em
"
/>
刷新
</li>
<li
v-if=
"!isAffix(selectedTag)"
@
click=
"closeSelectedTag(selectedTag)"
>
<close
style=
"width: 1em; height: 1em
;"
/>
<close
style=
"width: 1em; height: 1em
"
/>
关闭
</li>
<li
@
click=
"closeOtherTags"
>
<circle-close
style=
"width: 1em; height: 1em
;"
/>
<circle-close
style=
"width: 1em; height: 1em
"
/>
关闭其它
</li>
<li
v-if=
"!isFirstView()"
@
click=
"closeLeftTags"
>
<back
style=
"width: 1em; height: 1em
;"
/>
<back
style=
"width: 1em; height: 1em
"
/>
关闭左侧
</li>
<li
v-if=
"!isLastView()"
@
click=
"closeRightTags"
>
<right
style=
"width: 1em; height: 1em
;"
/>
<right
style=
"width: 1em; height: 1em
"
/>
关闭右侧
</li>
<li
@
click=
"closeAllTags(selectedTag)"
>
<circle-close
style=
"width: 1em; height: 1em
;"
/>
<circle-close
style=
"width: 1em; height: 1em
"
/>
关闭所有
</li>
</ul>
...
...
@@ -46,31 +61,39 @@
</
template
>
<
script
setup
lang=
"ts"
>
import
{
useTagsViewStoreHook
}
from
'
@/store/modules/tagsView
'
import
{
usePermissionStoreHook
}
from
'
@/store/modules/permission
'
import
path
from
'
path-browserify
'
import
{
computed
,
getCurrentInstance
,
nextTick
,
ref
,
watch
,
onMounted
onMounted
,
}
from
"
vue
"
;
import
{
RouteRecordRaw
,
useRoute
,
useRouter
}
from
'
vue-router
'
import
{
TagView
}
from
"
@/store/interface
"
;
import
ScrollPane
from
'
./ScrollPane.vue
'
import
{
Close
,
RefreshRight
,
CircleClose
,
Back
,
Right
}
from
'
@element-plus/icons-vue
'
import
{
generateTitle
}
from
'
@/utils/i18n
'
import
path
from
"
path-browserify
"
;
import
{
RouteRecordRaw
,
useRoute
,
useRouter
}
from
"
vue-router
"
;
import
{
TagView
}
from
"
@/store/interface
"
;
const
{
ctx
}
=
getCurrentInstance
()
as
any
const
router
=
useRouter
()
import
ScrollPane
from
"
./ScrollPane.vue
"
;
import
{
Close
,
RefreshRight
,
CircleClose
,
Back
,
Right
,
}
from
"
@element-plus/icons-vue
"
;
import
{
generateTitle
}
from
"
@/utils/i18n
"
;
import
useStore
from
"
@/store
"
;
const
{
tagsView
,
permission
}
=
useStore
();
const
{
ctx
}
=
getCurrentInstance
()
as
any
;
const
router
=
useRouter
();
const
route
=
useRoute
();
const
visitedViews
=
computed
<
any
>
(()
=>
useTagsViewStoreHook
().
visitedViews
)
const
routes
=
computed
<
any
>
(()
=>
usePermissionStoreHook
().
routes
)
const
visitedViews
=
computed
<
any
>
(()
=>
tagsView
.
visitedViews
);
const
routes
=
computed
<
any
>
(()
=>
permission
.
routes
);
const
affixTags
=
ref
([]);
const
visible
=
ref
(
false
);
...
...
@@ -80,197 +103,207 @@ const left = ref(0);
const
top
=
ref
(
0
);
watch
(
route
,
()
=>
{
addTags
()
moveToCurrentTag
()
})
addTags
()
;
moveToCurrentTag
()
;
})
;
watch
(
visible
,
(
value
)
=>
{
if
(
value
)
{
document
.
body
.
addEventListener
(
'
click
'
,
closeMenu
)
document
.
body
.
addEventListener
(
"
click
"
,
closeMenu
);
}
else
{
document
.
body
.
removeEventListener
(
'
click
'
,
closeMenu
)
document
.
body
.
removeEventListener
(
"
click
"
,
closeMenu
);
}
})
})
;
function
filterAffixTags
(
routes
:
RouteRecordRaw
[],
basePath
=
'
/
'
)
{
let
tags
:
TagView
[]
=
[]
function
filterAffixTags
(
routes
:
RouteRecordRaw
[],
basePath
=
"
/
"
)
{
let
tags
:
TagView
[]
=
[]
;
routes
.
forEach
(
route
=>
{
routes
.
forEach
(
(
route
)
=>
{
if
(
route
.
meta
&&
route
.
meta
.
affix
)
{
const
tagPath
=
path
.
resolve
(
basePath
,
route
.
path
)
const
tagPath
=
path
.
resolve
(
basePath
,
route
.
path
)
;
tags
.
push
({
fullPath
:
tagPath
,
path
:
tagPath
,
name
:
route
.
name
,
meta
:
{
...
route
.
meta
}
})
meta
:
{
...
route
.
meta
},
})
;
}
if
(
route
.
children
)
{
const
childTags
=
filterAffixTags
(
route
.
children
,
route
.
path
)
const
childTags
=
filterAffixTags
(
route
.
children
,
route
.
path
)
;
if
(
childTags
.
length
>=
1
)
{
tags
=
tags
.
concat
(
childTags
)
tags
=
tags
.
concat
(
childTags
)
;
}
}
})
return
tags
})
;
return
tags
;
}
function
initTags
()
{
const
res
=
filterAffixTags
(
routes
.
value
)
as
[]
affixTags
.
value
=
res
const
res
=
filterAffixTags
(
routes
.
value
)
as
[]
;
affixTags
.
value
=
res
;
for
(
const
tag
of
res
)
{
// Must have tag name
if
((
tag
as
TagView
).
name
)
{
useTagsViewStoreHook
().
addVisitedView
(
tag
)
tagsView
.
addVisitedView
(
tag
);
}
}
}
function
addTags
()
{
if
(
route
.
name
)
{
useTagsViewStoreHook
().
addView
(
route
)
tagsView
.
addView
(
route
);
}
return
false
return
false
;
}
function
moveToCurrentTag
()
{
const
tags
=
getCurrentInstance
()?.
refs
.
tag
as
any
[]
const
tags
=
getCurrentInstance
()?.
refs
.
tag
as
any
[]
;
nextTick
(()
=>
{
if
(
tags
===
null
||
tags
===
undefined
||
!
Array
.
isArray
(
tags
))
{
return
return
;
}
for
(
const
tag
of
tags
)
{
if
((
tag
.
to
as
TagView
).
path
===
route
.
path
)
{
(
scrollPaneRef
.
value
as
any
).
value
.
moveToTarget
(
tag
)
(
scrollPaneRef
.
value
as
any
).
value
.
moveToTarget
(
tag
)
;
// when query is different then update
if
((
tag
.
to
as
TagView
).
fullPath
!==
route
.
fullPath
)
{
useTagsViewStoreHook
().
updateVisitedView
(
route
)
tagsView
.
updateVisitedView
(
route
);
}
}
}
})
})
;
}
function
isActive
(
tag
:
TagView
)
{
return
tag
.
path
===
route
.
path
return
tag
.
path
===
route
.
path
;
}
function
isAffix
(
tag
:
TagView
)
{
return
tag
.
meta
&&
tag
.
meta
.
affix
return
tag
.
meta
&&
tag
.
meta
.
affix
;
}
function
isFirstView
()
{
try
{
return
(
selectedTag
.
value
as
TagView
).
fullPath
===
visitedViews
.
value
[
1
].
fullPath
||
(
selectedTag
.
value
as
TagView
).
fullPath
===
'
/index
'
return
(
(
selectedTag
.
value
as
TagView
).
fullPath
===
visitedViews
.
value
[
1
].
fullPath
||
(
selectedTag
.
value
as
TagView
).
fullPath
===
"
/index
"
);
}
catch
(
err
)
{
return
false
return
false
;
}
}
function
isLastView
()
{
try
{
return
(
selectedTag
.
value
as
TagView
).
fullPath
===
visitedViews
.
value
[
visitedViews
.
value
.
length
-
1
].
fullPath
return
(
(
selectedTag
.
value
as
TagView
).
fullPath
===
visitedViews
.
value
[
visitedViews
.
value
.
length
-
1
].
fullPath
);
}
catch
(
err
)
{
return
false
return
false
;
}
}
function
refreshSelectedTag
(
view
:
TagView
)
{
useTagsViewStoreHook
().
delCachedView
(
view
)
const
{
fullPath
}
=
view
tagsView
.
delCachedView
(
view
);
const
{
fullPath
}
=
view
;
nextTick
(()
=>
{
router
.
replace
({
path
:
'
/redirect
'
+
fullPath
}).
catch
(
err
=>
{
console
.
warn
(
err
)
})
})
router
.
replace
({
path
:
"
/redirect
"
+
fullPath
}).
catch
((
err
)
=>
{
console
.
warn
(
err
)
;
})
;
})
;
}
function
toLastView
(
visitedViews
:
TagView
[],
view
?:
any
)
{
const
latestView
=
visitedViews
.
slice
(
-
1
)[
0
]
const
latestView
=
visitedViews
.
slice
(
-
1
)[
0
]
;
if
(
latestView
&&
latestView
.
fullPath
)
{
router
.
push
(
latestView
.
fullPath
)
router
.
push
(
latestView
.
fullPath
)
;
}
else
{
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if
(
view
.
name
===
'
Dashboard
'
)
{
if
(
view
.
name
===
"
Dashboard
"
)
{
// to reload home page
router
.
replace
({
path
:
'
/redirect
'
+
view
.
fullPath
})
router
.
replace
({
path
:
"
/redirect
"
+
view
.
fullPath
});
}
else
{
router
.
push
(
'
/
'
)
router
.
push
(
"
/
"
);
}
}
}
function
closeSelectedTag
(
view
:
TagView
)
{
useTagsViewStoreHook
()
.
delView
(
view
).
then
((
res
:
any
)
=>
{
tagsView
.
delView
(
view
).
then
((
res
:
any
)
=>
{
if
(
isActive
(
view
))
{
toLastView
(
res
.
visitedViews
,
view
)
toLastView
(
res
.
visitedViews
,
view
)
;
}
})
})
;
}
function
closeLeftTags
()
{
useTagsViewStoreHook
().
delLeftViews
(
selectedTag
.
value
).
then
((
res
:
any
)
=>
{
if
(
!
res
.
visitedViews
.
find
((
item
:
any
)
=>
item
.
fullPath
===
route
.
fullPath
))
{
toLastView
(
res
.
visitedViews
)
tagsView
.
delLeftViews
(
selectedTag
.
value
).
then
((
res
:
any
)
=>
{
if
(
!
res
.
visitedViews
.
find
((
item
:
any
)
=>
item
.
fullPath
===
route
.
fullPath
)
)
{
toLastView
(
res
.
visitedViews
);
}
})
})
;
}
function
closeRightTags
()
{
useTagsViewStoreHook
().
delRightViews
(
selectedTag
.
value
).
then
((
res
:
any
)
=>
{
if
(
!
res
.
visitedViews
.
find
((
item
:
any
)
=>
item
.
fullPath
===
route
.
fullPath
))
{
toLastView
(
res
.
visitedViews
)
tagsView
.
delRightViews
(
selectedTag
.
value
).
then
((
res
:
any
)
=>
{
if
(
!
res
.
visitedViews
.
find
((
item
:
any
)
=>
item
.
fullPath
===
route
.
fullPath
)
)
{
toLastView
(
res
.
visitedViews
);
}
})
})
;
}
function
closeOtherTags
()
{
useTagsViewStoreHook
()
.
delOtherViews
(
selectedTag
.
value
).
then
(()
=>
{
moveToCurrentTag
()
})
tagsView
.
delOtherViews
(
selectedTag
.
value
).
then
(()
=>
{
moveToCurrentTag
()
;
})
;
}
function
closeAllTags
(
view
:
TagView
)
{
useTagsViewStoreHook
().
delRightViews
(
selectedTag
.
value
).
then
((
res
:
any
)
=>
{
if
(
affixTags
.
value
.
some
((
tag
:
any
)
=>
tag
.
path
===
route
.
path
))
{
return
tagsView
.
delRightViews
(
selectedTag
.
value
).
then
((
res
:
any
)
=>
{
if
(
affixTags
.
value
.
some
((
tag
:
any
)
=>
tag
.
path
===
route
.
path
))
{
return
;
}
toLastView
(
res
.
visitedViews
,
view
)
})
toLastView
(
res
.
visitedViews
,
view
)
;
})
;
}
function
openMenu
(
tag
:
TagView
,
e
:
MouseEvent
)
{
const
menuMinWidth
=
105
const
offsetLeft
=
ctx
.
$el
.
getBoundingClientRect
().
left
// container margin left
const
offsetWidth
=
ctx
.
$el
.
offsetWidth
// container width
const
maxLeft
=
offsetWidth
-
menuMinWidth
// left boundary
const
l
=
e
.
clientX
-
offsetLeft
+
15
// 15: margin right
const
menuMinWidth
=
105
;
const
offsetLeft
=
ctx
.
$el
.
getBoundingClientRect
().
left
;
// container margin left
const
offsetWidth
=
ctx
.
$el
.
offsetWidth
;
// container width
const
maxLeft
=
offsetWidth
-
menuMinWidth
;
// left boundary
const
l
=
e
.
clientX
-
offsetLeft
+
15
;
// 15: margin right
if
(
l
>
maxLeft
)
{
left
.
value
=
maxLeft
left
.
value
=
maxLeft
;
}
else
{
left
.
value
=
l
left
.
value
=
l
;
}
top
.
value
=
e
.
clientY
visible
.
value
=
true
selectedTag
.
value
=
tag
top
.
value
=
e
.
clientY
;
visible
.
value
=
true
;
selectedTag
.
value
=
tag
;
}
function
closeMenu
()
{
visible
.
value
=
false
visible
.
value
=
false
;
}
function
handleScroll
()
{
closeMenu
()
closeMenu
()
;
}
onMounted
(()
=>
{
initTags
()
addTags
()
})
initTags
();
addTags
();
});
</
script
>
<
style
lang=
'scss'
scoped
>
...
...
src/layout/index.vue
浏览文件 @
4ae629ab
<
template
>
<div
:class=
"classObj"
class=
"app-wrapper"
>
<div
v-if=
"device==='mobile' && sidebar.opened"
class=
"drawer-bg"
@
click=
"handleClickOutside"
/>
<sidebar
class=
"sidebar-container"
/>
<div
:class=
"
{hasTagsView:needTagsView}" class="main-container">
<div
:class=
"
{'fixed-header':fixedHeader}">
<navbar/>
<tags-view
v-if=
"needTagsView"
/>
<div
v-if=
"device === 'mobile' && sidebar.opened"
class=
"drawer-bg"
@
click=
"handleClickOutside"
/>
<sidebar
class=
"sidebar-container"
/>
<div
:class=
"
{ hasTagsView: needTagsView }" class="main-container">
<div
:class=
"
{ 'fixed-header': fixedHeader }">
<navbar
/>
<tags-view
v-if=
"needTagsView"
/>
</div>
<app-main/>
<app-main
/>
<right-panel
v-if=
"showSettings"
>
<settings/>
<settings
/>
</right-panel>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
computed
,
watchEffect
}
from
"
vue
"
import
{
useWindowSize
}
from
'
@vueuse/core
'
import
{
AppMain
,
Navbar
,
Settings
,
TagsView
}
from
'
./components/index
'
import
Sidebar
from
'
./components/Sidebar/index.vue
'
import
RightPanel
from
'
@/components/RightPanel/index.vue
'
import
{
computed
,
watchEffect
}
from
"
vue
"
;
import
{
useWindowSize
}
from
"
@vueuse/core
"
;
import
{
AppMain
,
Navbar
,
Settings
,
TagsView
}
from
"
./components/index
"
;
import
Sidebar
from
"
./components/Sidebar/index.vue
"
;
import
RightPanel
from
"
@/components/RightPanel/index.vue
"
;
import
{
useAppStoreHook
}
from
"
@/store/modules/app
"
import
{
useSettingStoreHook
}
from
"
@/store/modules/settings
"
import
useStore
from
"
@/store
"
;
const
{
width
,
height
}
=
useWindowSize
();
const
WIDTH
=
992
const
{
width
,
height
}
=
useWindowSize
();
const
WIDTH
=
992
;
const
sidebar
=
computed
(()
=>
useAppStoreHook
().
sidebar
);
const
device
=
computed
(()
=>
useAppStoreHook
().
device
);
const
needTagsView
=
computed
(()
=>
useSettingStoreHook
().
tagsView
);
const
fixedHeader
=
computed
(()
=>
useSettingStoreHook
().
fixedHeader
);
const
showSettings
=
computed
(()
=>
useSettingStoreHook
().
showSettings
);
const
{
app
,
setting
}
=
useStore
();
const
sidebar
=
computed
(()
=>
app
.
sidebar
);
const
device
=
computed
(()
=>
app
.
device
);
const
needTagsView
=
computed
(()
=>
setting
.
tagsView
);
const
fixedHeader
=
computed
(()
=>
setting
.
fixedHeader
);
const
showSettings
=
computed
(()
=>
setting
.
showSettings
);
const
classObj
=
computed
(()
=>
({
hideSidebar
:
!
sidebar
.
value
.
opened
,
openSidebar
:
sidebar
.
value
.
opened
,
withoutAnimation
:
sidebar
.
value
.
withoutAnimation
,
mobile
:
device
.
value
===
'
mobile
'
}))
mobile
:
device
.
value
===
"
mobile
"
,
}))
;
watchEffect
(()
=>
{
if
(
width
.
value
<
WIDTH
)
{
useAppStoreHook
().
toggleDevice
(
"
mobile
"
)
useAppStoreHook
().
closeSideBar
(
true
)
app
.
toggleDevice
(
"
mobile
"
);
app
.
closeSideBar
(
true
);
}
else
{
useAppStoreHook
().
toggleDevice
(
"
desktop
"
)
app
.
toggleDevice
(
"
desktop
"
);
}
})
})
;
function
handleClickOutside
()
{
useAppStoreHook
().
closeSideBar
(
false
)
app
.
closeSideBar
(
false
);
}
</
script
>
...
...
@@ -91,7 +96,7 @@ function handleClickOutside() {
}
.hideSidebar
.fixed-header
{
width
:
calc
(
100%
-
54px
)
width
:
calc
(
100%
-
54px
)
;
}
.mobile
.fixed-header
{
...
...
src/main.ts
浏览文件 @
4ae629ab
...
...
@@ -2,7 +2,7 @@ import {createApp, Directive} from 'vue'
import
App
from
'
./App.vue
'
import
router
from
"
@/router
"
;
import
{
store
}
from
"
@/store
"
;
import
{
createPinia
}
from
"
pinia
"
import
Pagination
from
'
@/components/Pagination/index.vue
'
import
{
localStorage
}
from
"
@/utils/storage
"
;
...
...
@@ -34,7 +34,7 @@ app.config.globalProperties.$listDictsByCode = listDictsByCode
// 注册全局组件
app
.
component
(
'
Pagination
'
,
Pagination
)
.
use
(
store
)
.
use
(
createPinia
()
)
.
use
(
router
)
.
use
(
ElementPlus
,
{
size
:
localStorage
.
get
(
'
size
'
)
||
'
default
'
})
.
use
(
i18n
)
...
...
src/permission.ts
浏览文件 @
4ae629ab
import
router
from
"
@/router
"
;
import
{
ElMessage
}
from
"
element-plus
"
;
import
{
usePermissionStoreHook
}
from
"
@/store/modules/permission
"
;
import
{
useUserStoreHook
}
from
"
@/store/modules/user
"
;
import
{
ElMessage
}
from
"
element-plus
"
;
import
useStore
from
"
@/store
"
;
import
NProgress
from
'
nprogress
'
;
import
'
nprogress/nprogress.css
'
NProgress
.
configure
({
showSpinner
:
false
})
// 进度环显示/隐藏
NProgress
.
configure
({
showSpinner
:
false
})
// 进度环显示/隐藏
// 白名单
const
whiteList
=
[
'
/login
'
,
'
/auth-redirect
'
]
router
.
beforeEach
(
async
(
to
,
form
,
next
)
=>
{
NProgress
.
start
()
const
hasToken
=
useUserStoreHook
()
.
token
const
{
user
,
permission
}
=
useStore
()
const
hasToken
=
user
.
token
if
(
hasToken
)
{
// 如果登录成功,跳转到首页
if
(
to
.
path
===
'
/login
'
)
{
next
({
path
:
'
/
'
})
next
({
path
:
'
/
'
})
NProgress
.
done
()
}
else
{
const
hasGetUserInfo
=
useUserStoreHook
()
.
roles
.
length
>
0
const
hasGetUserInfo
=
user
.
roles
.
length
>
0
if
(
hasGetUserInfo
)
{
next
()
}
else
{
try
{
await
use
UserStoreHook
()
.
getUserInfo
()
const
roles
=
useUserStoreHook
()
.
roles
const
accessRoutes
:
any
=
await
usePermissionStoreHook
()
.
generateRoutes
(
roles
)
await
use
r
.
getUserInfo
()
const
roles
=
user
.
roles
const
accessRoutes
:
any
=
await
permission
.
generateRoutes
(
roles
)
accessRoutes
.
forEach
((
route
:
any
)
=>
{
router
.
addRoute
(
route
)
})
next
({
...
to
,
replace
:
true
})
next
({
...
to
,
replace
:
true
})
}
catch
(
error
)
{
// remove token and go to login page to re-login
await
use
UserStoreHook
()
.
resetToken
()
await
use
r
.
resetToken
()
ElMessage
.
error
(
error
as
any
||
'
Has Error
'
)
next
(
`/login?redirect=
${
to
.
path
}
`
)
NProgress
.
done
()
...
...
src/store/index.ts
浏览文件 @
4ae629ab
import
{
createPinia
}
from
"
pinia
"
;
const
store
=
createPinia
();
export
{
store
};
// 导入首页模块
import
useUserStore
from
'
./modules/user
'
import
useAppStore
from
'
./modules/app
'
import
usePermissionStore
from
'
./modules/permission
'
import
useSettingStore
from
'
./modules/settings
'
import
useTagsViewStore
from
'
./modules/tagsView
'
const
useStore
=
()
=>
({
user
:
useUserStore
(),
app
:
useAppStore
(),
permission
:
usePermissionStore
(),
setting
:
useSettingStore
(),
tagsView
:
useTagsViewStore
()
})
export
default
useStore
\ No newline at end of file
src/store/modules/app.ts
浏览文件 @
4ae629ab
import
{
AppState
}
from
"
@/store/interface
"
;
import
{
localStorage
}
from
"
@/utils/storage
"
;
import
{
store
}
from
"
@/store
"
;
import
{
defineStore
}
from
"
pinia
"
;
import
{
getLanguage
}
from
'
@/lang/index
'
export
const
useAppStore
=
defineStore
({
const
useAppStore
=
defineStore
({
id
:
"
app
"
,
state
:
():
AppState
=>
({
device
:
'
desktop
'
,
...
...
@@ -44,6 +43,4 @@ export const useAppStore = defineStore({
}
})
export
function
useAppStoreHook
()
{
return
useAppStore
(
store
);
}
export
default
useAppStore
;
\ No newline at end of file
src/store/modules/permission.ts
浏览文件 @
4ae629ab
...
...
@@ -3,7 +3,6 @@ import {RouteRecordRaw} from 'vue-router'
import
{
constantRoutes
}
from
'
@/router
'
import
{
listRoutes
}
from
"
@/api/system/menu
"
;
import
{
defineStore
}
from
"
pinia
"
;
import
{
store
}
from
"
@/store
"
;
const
modules
=
import
.
meta
.
glob
(
"
../../views/**/**.vue
"
);
export
const
Layout
=
()
=>
import
(
'
@/layout/index.vue
'
)
...
...
@@ -48,7 +47,7 @@ export const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) =>
}
export
const
usePermissionStore
=
defineStore
({
const
usePermissionStore
=
defineStore
({
id
:
"
permission
"
,
state
:
():
PermissionState
=>
({
routes
:
[],
...
...
@@ -74,6 +73,6 @@ export const usePermissionStore = defineStore({
}
})
export
function
usePermissionStoreHook
()
{
return
usePermissionStore
(
store
);
}
export
default
usePermissionStore
;
src/store/modules/settings.ts
浏览文件 @
4ae629ab
import
{
defineStore
}
from
"
pinia
"
;
import
{
store
}
from
"
@/store
"
;
import
{
SettingState
}
from
"
@/store/interface
"
;
import
defaultSettings
from
'
../../settings
'
import
{
localStorage
}
from
"
@/utils/storage
"
;
...
...
@@ -43,7 +42,4 @@ export const useSettingStore = defineStore({
}
})
export
function
useSettingStoreHook
()
{
return
useSettingStore
(
store
);
}
export
default
useSettingStore
;
src/store/modules/tagsView.ts
浏览文件 @
4ae629ab
import
{
defineStore
}
from
"
pinia
"
;
import
{
store
}
from
"
@/store
"
;
import
{
TagsViewState
}
from
"
@/store/interface
"
;
import
{
defineStore
}
from
"
pinia
"
;
import
{
TagsViewState
}
from
"
@/store/interface
"
;
const
useTagsViewStore
=
defineStore
({
id
:
"
tagsView
"
,
...
...
@@ -112,7 +111,7 @@ const useTagsViewStore = defineStore({
return
true
}
const
cacheIndex
=
this
.
cachedViews
.
indexOf
(
item
.
name
as
string
)
const
cacheIndex
=
this
.
cachedViews
.
indexOf
(
item
.
name
as
string
)
if
(
cacheIndex
>
-
1
)
{
this
.
cachedViews
.
splice
(
cacheIndex
,
1
)
}
...
...
@@ -173,8 +172,4 @@ const useTagsViewStore = defineStore({
}
})
export
function
useTagsViewStoreHook
()
{
return
useTagsViewStore
(
store
);
}
export
default
useTagsViewStore
;
src/store/modules/user.ts
浏览文件 @
4ae629ab
import
{
defineStore
}
from
"
pinia
"
;
import
{
store
}
from
"
@/store
"
;
import
{
UserState
}
from
"
@/store/interface
"
;
import
{
localStorage
}
from
"
@/utils/storage
"
;
import
{
getUserInfo
,
login
,
logout
}
from
"
@/api/login
"
;
import
{
resetRouter
}
from
"
@/router
"
;
const
getDefaultState
=
()
=>
{
return
{
token
:
localStorage
.
get
(
'
token
'
),
nickname
:
''
,
avatar
:
''
,
roles
:
[],
perms
:
[]
}
}
export
const
useUserStore
=
defineStore
({
const
useUserStore
=
defineStore
({
id
:
"
user
"
,
state
:
():
UserState
=>
({
token
:
localStorage
.
get
(
'
token
'
)
||
''
,
...
...
@@ -113,7 +102,4 @@ export const useUserStore = defineStore({
}
})
export
function
useUserStoreHook
()
{
return
useUserStore
(
store
);
}
export
default
useUserStore
;
\ No newline at end of file
src/utils/request.ts
浏览文件 @
4ae629ab
import
axios
from
"
axios
"
;
import
{
ElMessage
,
ElMessageBox
}
from
"
element-plus
"
;
import
{
localStorage
}
from
"
@/utils/storage
"
;
import
{
useUserStoreHook
}
from
"
@/store/modules/user
"
;
import
{
ElMessage
,
ElMessageBox
}
from
"
element-plus
"
;
import
{
localStorage
}
from
"
@/utils/storage
"
;
import
useStore
from
"
@/store
"
;
// 创建 axios 实例
const
service
=
axios
.
create
({
baseURL
:
import
.
meta
.
env
.
VITE_APP_BASE_API
,
timeout
:
50000
,
headers
:
{
'
Content-Type
'
:
'
application/json;charset=utf-8
'
}
headers
:
{
'
Content-Type
'
:
'
application/json;charset=utf-8
'
}
})
// 请求拦截器
...
...
@@ -16,7 +18,8 @@ service.interceptors.request.use(
if
(
!
config
?.
headers
)
{
throw
new
Error
(
`Expected 'config' and 'config.headers' not to be undefined`
);
}
if
(
useUserStoreHook
().
token
)
{
const
{
user
}
=
useStore
()
if
(
user
.
token
)
{
config
.
headers
.
Authorization
=
`
${
localStorage
.
get
(
'
token
'
)}
`
;
}
return
config
...
...
@@ -27,8 +30,8 @@ service.interceptors.request.use(
// 响应拦截器
service
.
interceptors
.
response
.
use
(
({
data
})
=>
{
const
{
code
,
msg
}
=
data
;
({
data
})
=>
{
const
{
code
,
msg
}
=
data
;
if
(
code
===
'
00000
'
)
{
return
data
;
}
else
{
...
...
@@ -40,7 +43,7 @@ service.interceptors.response.use(
}
},
(
error
)
=>
{
const
{
code
,
msg
}
=
error
.
response
.
data
const
{
code
,
msg
}
=
error
.
response
.
data
if
(
code
===
'
A0230
'
)
{
// token 过期
localStorage
.
clear
();
// 清除浏览器全部缓存
window
.
location
.
href
=
'
/
'
;
// 跳转登录页
...
...
src/views/dashboard/index.vue
浏览文件 @
4ae629ab
<
template
>
<div
class=
"dashboard-container"
>
<github-corner
class=
"github-corner"
/>
<github-corner
class=
"github-corner"
/>
<!-- 数据 -->
<el-row
:gutter=
"40"
class=
"card-panel-col"
>
<!--
<el-col
:xs=
"24"
:lg=
"6"
class=
"card-panel-col"
>
<!--
<el-col
:xs=
"24"
:lg=
"6"
class=
"card-panel-col"
>
<div
class=
"card-panel"
>
<div
class=
"card-panel-icon-wrapper"
style=
"margin-top: -10px"
>
<el-image
style=
"width:200px; height: 100px"
...
...
@@ -22,15 +22,13 @@
</div>
</el-col>
-->
<el-col
:xs=
"24"
:sm=
"12"
:lg=
"6"
class=
"card-panel-col"
>
<el-col
:xs=
"24"
:sm=
"12"
:lg=
"6"
class=
"card-panel-col"
>
<div
class=
"card-panel"
>
<div
class=
"card-panel-icon-wrapper icon-people"
>
<svg-icon
icon-class=
"peoples"
class-name=
"card-panel-icon"
/>
<svg-icon
icon-class=
"peoples"
class-name=
"card-panel-icon"
/>
</div>
<div
class=
"card-panel-description"
>
<div
class=
"card-panel-text"
>
访问数
</div>
<div
class=
"card-panel-text"
>
访问数
</div>
<div
class=
"card-panel-num"
>
1000
</div>
</div>
</div>
...
...
@@ -42,9 +40,7 @@
<svg-icon
icon-class=
"message"
class-name=
"card-panel-icon"
/>
</div>
<div
class=
"card-panel-description"
>
<div
class=
"card-panel-text"
>
消息数
</div>
<div
class=
"card-panel-text"
>
消息数
</div>
<div
class=
"card-panel-num"
>
1000
</div>
</div>
</div>
...
...
@@ -53,25 +49,21 @@
<el-col
:xs=
"24"
:sm=
"12"
:lg=
"6"
class=
"card-panel-col"
>
<div
class=
"card-panel"
>
<div
class=
"card-panel-icon-wrapper icon-money"
>
<svg-icon
icon-class=
"money"
class-name=
"card-panel-icon"
/>
<svg-icon
icon-class=
"money"
class-name=
"card-panel-icon"
/>
</div>
<div
class=
"card-panel-description"
>
<div
class=
"card-panel-text"
>
支付金额
</div>
<div
class=
"card-panel-text"
>
支付金额
</div>
<div
class=
"card-panel-num"
>
1000
</div>
</div>
</div>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:lg=
"6"
class=
"card-panel-col"
>
<el-col
:xs=
"24"
:sm=
"12"
:lg=
"6"
class=
"card-panel-col"
>
<div
class=
"card-panel"
>
<div
class=
"card-panel-icon-wrapper icon-shopping"
>
<svg-icon
icon-class=
"shopping"
class-name=
"card-panel-icon"
/>
<svg-icon
icon-class=
"shopping"
class-name=
"card-panel-icon"
/>
</div>
<div
class=
"card-panel-description"
>
<div
class=
"card-panel-text"
>
订单数
</div>
<div
class=
"card-panel-text"
>
订单数
</div>
<div
class=
"card-panel-num"
>
1000
</div>
</div>
</div>
...
...
@@ -80,47 +72,65 @@
<!-- 项目 + 团队成员介绍 -->
<el-row
:gutter=
"40"
>
<!-- 项目介绍 -->
<el-col
:md=
"12"
:lg=
"12"
class=
"card-panel-col"
>
<Project/>
<Project
/>
</el-col>
<!-- 团队介绍 -->
<el-col
:md=
"12"
:lg=
"12"
class=
"card-panel-col"
>
<Team/>
<Team
/>
</el-col>
</el-row>
<!-- Echarts 图表 -->
<el-row
:gutter=
"40"
style=
"margin-top: 20px"
>
<el-col
:sm=
"24"
:lg=
"8"
class=
"card-panel-col"
>
<BarChart
id=
"barChart"
height=
"400px"
width=
"100%"
class=
"chart-container"
/>
<BarChart
id=
"barChart"
height=
"400px"
width=
"100%"
class=
"chart-container"
/>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:lg=
"8"
class=
"card-panel-col"
>
<PieChart
id=
"pieChart"
height=
"400px"
width=
"100%"
class=
"chart-container"
/>
<PieChart
id=
"pieChart"
height=
"400px"
width=
"100%"
class=
"chart-container"
/>
<!--订单漏斗图-->
<!--
<FunnelChart
id=
"funnelChart"
height=
"400px"
width=
"100%"
class=
"chart-container"
/>
-->
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:lg=
"8"
class=
"card-panel-col"
>
<RadarChart
id=
"radarChart"
height=
"400px"
width=
"100%"
class=
"chart-container"
/>
<RadarChart
id=
"radarChart"
height=
"400px"
width=
"100%"
class=
"chart-container"
/>
</el-col>
</el-row>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
// Vue引用
import
{
computed
,
nextTick
,
onMounted
,
reactive
,
toRefs
,
watchEffect
}
from
"
vue
"
;
import
{
computed
,
nextTick
,
onMounted
,
reactive
,
toRefs
,
watchEffect
,
}
from
"
vue
"
;
// 组件引用
import
GithubCorner
from
'
@/components/GithubCorner/index.vue
'
import
SvgIcon
from
'
@/components/SvgIcon/index.vue
'
import
GithubCorner
from
"
@/components/GithubCorner/index.vue
"
;
import
SvgIcon
from
"
@/components/SvgIcon/index.vue
"
;
import
BarChart
from
"
./components/Chart/BarChart.vue
"
;
import
PieChart
from
"
./components/Chart/PieChart.vue
"
;
import
RadarChart
from
"
./components/Chart/RadarChart.vue
"
;
...
...
@@ -129,20 +139,19 @@ import FunnelChart from "./components/Chart/FunnelChart.vue";
import
Project
from
"
./components/Project/index.vue
"
;
import
Team
from
"
./components/Team/index.vue
"
;
import
BScroll
from
'
better-scroll
'
import
{
useUserStoreHook
}
from
"
@/store/modules/user
"
import
BScroll
from
"
better-scroll
"
;
const
roles
=
computed
(()
=>
useUserStoreHook
().
roles
);
const
avatar
=
computed
(()
=>
useUserStoreHook
().
avatar
);
const
nickname
=
computed
(()
=>
useUserStoreHook
().
nickname
);
import
useStore
from
"
@/store
"
;
const
{
user
}
=
useStore
();
const
roles
=
computed
(()
=>
user
.
roles
);
const
avatar
=
computed
(()
=>
user
.
avatar
);
const
nickname
=
computed
(()
=>
user
.
nickname
);
</
script
>
<
style
lang=
"scss"
scoped
>
.dashboard-container
{
padding
:
24px
;
background-color
:
rgb
(
240
,
242
,
245
);
...
...
@@ -200,8 +209,8 @@ const nickname = computed(() => useUserStoreHook().nickname);
overflow
:
hidden
;
color
:
#666
;
background
:
#fff
;
box-shadow
:
4px
4px
40px
rgba
(
0
,
0
,
0
,
.05
);
border-color
:
rgba
(
0
,
0
,
0
,
.05
);
box-shadow
:
4px
4px
40px
rgba
(
0
,
0
,
0
,
0
.05
);
border-color
:
rgba
(
0
,
0
,
0
,
0
.05
);
&
:hover
{
.card-panel-icon-wrapper
{
...
...
@@ -225,7 +234,7 @@ const nickname = computed(() => useUserStoreHook().nickname);
}
.icon-shopping
{
background
:
#34bfa3
background
:
#34bfa3
;
}
}
...
...
@@ -291,12 +300,8 @@ const nickname = computed(() => useUserStoreHook().nickname);
}
}
.chart-container
{
background
:
#ffffff
;
}
}
</
style
>
src/views/login/index.vue
浏览文件 @
4ae629ab
<
template
>
<div
class=
"login-container"
>
<el-form
ref=
"loginFormRef"
:model=
"loginForm"
:rules=
"loginRules"
class=
"login-form"
auto-complete=
"on"
label-position=
"left"
ref=
"loginFormRef"
:model=
"loginForm"
:rules=
"loginRules"
class=
"login-form"
auto-complete=
"on"
label-position=
"left"
>
<div
class=
"title-container"
>
<h3
class=
"title"
>
{{
$t
(
'
login.title
'
)
}}
</h3>
<lang-select
class=
"set-language"
/>
<h3
class=
"title"
>
{{
$t
(
"
login.title
"
)
}}
</h3>
<lang-select
class=
"set-language"
/>
</div>
<el-form-item
prop=
"username"
>
<span
class=
"svg-container"
>
<svg-icon
icon-class=
"user"
/>
<svg-icon
icon-class=
"user"
/>
</span>
<el-input
ref=
"username"
v-model=
"loginForm.username"
:placeholder=
"$t('login.username')"
name=
"username"
type=
"text"
tabindex=
"1"
auto-complete=
"on"
ref=
"username"
v-model=
"loginForm.username"
:placeholder=
"$t('login.username')"
name=
"username"
type=
"text"
tabindex=
"1"
auto-complete=
"on"
/>
</el-form-item>
<el-tooltip
:disabled=
"capslockTooltipDisabled"
content=
"Caps lock is On"
placement=
"right"
:disabled=
"capslockTooltipDisabled"
content=
"Caps lock is On"
placement=
"right"
>
<el-form-item
prop=
"password"
>
<span
class=
"svg-container"
>
<svg-icon
icon-class=
"password"
/>
</span>
<span
class=
"svg-container"
>
<svg-icon
icon-class=
"password"
/>
</span>
<el-input
:key=
"passwordType"
ref=
"passwordRef"
v-model=
"loginForm.password"
:type=
"passwordType"
placeholder=
"Password"
name=
"password"
tabindex=
"2"
auto-complete=
"on"
@
keyup.native=
"checkCapslock"
@
blur=
"capslockTooltipDisabled = true"
@
keyup.enter.native=
"handleLogin"
:key=
"passwordType"
ref=
"passwordRef"
v-model=
"loginForm.password"
:type=
"passwordType"
placeholder=
"Password"
name=
"password"
tabindex=
"2"
auto-complete=
"on"
@
keyup.native=
"checkCapslock"
@
blur=
"capslockTooltipDisabled = true"
@
keyup.enter.native=
"handleLogin"
/>
<span
class=
"show-pwd"
@
click=
"showPwd"
>
<svg-icon
:icon-class=
"passwordType === 'password' ? 'eye' : 'eye-open'"
/>
</span>
<svg-icon
:icon-class=
"passwordType === 'password' ? 'eye' : 'eye-open'"
/>
</span>
</el-form-item>
</el-tooltip>
<!-- 验证码 -->
<el-form-item
prop=
"code"
>
<span
class=
"svg-container"
>
<svg-icon
icon-class=
"validCode"
/>
</span>
<span
class=
"svg-container"
>
<svg-icon
icon-class=
"validCode"
/>
</span>
<el-input
v-model=
"loginForm.code"
auto-complete=
"off"
:placeholder=
"$t('login.code')"
style=
"width: 65%"
@
keyup.enter.native=
"handleLogin"
v-model=
"loginForm.code"
auto-complete=
"off"
:placeholder=
"$t('login.code')"
style=
"width: 65%"
@
keyup.enter.native=
"handleLogin"
/>
<div
class=
"captcha"
>
<img
:src=
"captchaBase64"
@
click=
"handleCaptchaGenerate"
height=
"38px"
/>
<img
:src=
"captchaBase64"
@
click=
"handleCaptchaGenerate"
height=
"38px"
/>
</div>
</el-form-item>
<el-button
size=
"default"
:loading=
"loading"
type=
"primary"
style=
"width:100%;margin-bottom:30px;"
@
click.native.prevent=
"handleLogin"
>
{{
$t
(
'
login.login
'
)
}}
<el-button
size=
"default"
:loading=
"loading"
type=
"primary"
style=
"width: 100%; margin-bottom: 30px"
@
click.native.prevent=
"handleLogin"
>
{{
$t
(
"
login.login
"
)
}}
</el-button>
<div
class=
"tips"
>
<span
style=
"margin-right:20px;"
>
{{
$t
(
'
login.username
'
)
}}
: admin
</span>
<span>
{{
$t
(
'
login.password
'
)
}}
: 123456
</span>
<span
style=
"margin-right: 20px"
>
{{
$t
(
"
login.username
"
)
}}
: admin
</span
>
<span>
{{
$t
(
"
login.password
"
)
}}
: 123456
</span>
</div>
</el-form>
<div
v-if=
"showCopyright
==
true"
class=
"copyright"
>
<p>
{{
$t
(
'
login.copyright
'
)
}}
</p>
<p>
{{
$t
(
'
login.icp
'
)
}}
</p>
<div
v-if=
"showCopyright
==
true"
class=
"copyright"
>
<p>
{{
$t
(
"
login.copyright
"
)
}}
</p>
<p>
{{
$t
(
"
login.icp
"
)
}}
</p>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
onMounted
,
reactive
,
ref
,
toRefs
,
watch
,
nextTick
}
from
"
vue
"
;
import
{
onMounted
,
reactive
,
ref
,
toRefs
,
watch
,
nextTick
}
from
"
vue
"
;
// 组件依赖
import
{
ElForm
,
ElInput
}
from
"
element-plus
"
;
import
router
from
'
@/router
'
import
LangSelect
from
'
@/components/LangSelect/index.vue
'
;
import
SvgIcon
from
'
@/components/SvgIcon/index.vue
'
;
import
{
ElForm
,
ElInput
}
from
"
element-plus
"
;
import
router
from
"
@/router
"
;
import
LangSelect
from
"
@/components/LangSelect/index.vue
"
;
import
SvgIcon
from
"
@/components/SvgIcon/index.vue
"
;
// 状态管理依赖
import
{
useUserStoreHook
}
from
"
@/store/modules/user
"
;
import
{
useAppStoreHook
}
from
"
@/store/modules/app
"
;
import
useStore
from
"
@/store
"
;
// API依赖
import
{
getCaptcha
}
from
"
@/api/login
"
;
import
{
useRoute
}
from
"
vue-router
"
;
import
{
getCaptcha
}
from
"
@/api/login
"
;
import
{
useRoute
}
from
"
vue-router
"
;
const
{
user
}
=
useStore
();
const
route
=
useRoute
();
const
loginFormRef
=
ref
(
ElForm
)
const
passwordRef
=
ref
(
ElInput
)
const
loginFormRef
=
ref
(
ElForm
)
;
const
passwordRef
=
ref
(
ElInput
)
;
const
state
=
reactive
({
loginForm
:
{
username
:
'
admin
'
,
password
:
'
123456
'
,
code
:
''
,
uuid
:
''
username
:
"
admin
"
,
password
:
"
123456
"
,
code
:
""
,
uuid
:
""
,
},
loginRules
:
{
username
:
[{
required
:
true
,
trigger
:
'
blur
'
}],
password
:
[{
required
:
true
,
trigger
:
'
blur
'
,
validator
:
validatePassword
}]
username
:
[{
required
:
true
,
trigger
:
"
blur
"
}],
password
:
[
{
required
:
true
,
trigger
:
"
blur
"
,
validator
:
validatePassword
},
],
},
loading
:
false
,
passwordType
:
'
password
'
,
redirect
:
''
,
captchaBase64
:
''
,
passwordType
:
"
password
"
,
redirect
:
""
,
captchaBase64
:
""
,
// 大写提示禁用
capslockTooltipDisabled
:
true
,
otherQuery
:
{},
clientHeight
:
document
.
documentElement
.
clientHeight
,
showCopyright
:
true
})
showCopyright
:
true
,
})
;
function
validatePassword
(
rule
:
any
,
value
:
any
,
callback
:
any
)
{
if
(
value
.
length
<
6
)
{
callback
(
new
Error
(
'
The password can not be less than 6 digits
'
))
callback
(
new
Error
(
"
The password can not be less than 6 digits
"
));
}
else
{
callback
()
callback
()
;
}
}
...
...
@@ -151,83 +166,88 @@ const {
redirect
,
captchaBase64
,
capslockTooltipDisabled
,
showCopyright
}
=
toRefs
(
state
)
showCopyright
,
}
=
toRefs
(
state
)
;
function
checkCapslock
(
e
:
any
)
{
const
{
key
}
=
e
state
.
capslockTooltipDisabled
=
key
&&
key
.
length
===
1
&&
(
key
>=
'
A
'
&&
key
<=
'
Z
'
)
const
{
key
}
=
e
;
state
.
capslockTooltipDisabled
=
key
&&
key
.
length
===
1
&&
key
>=
"
A
"
&&
key
<=
"
Z
"
;
}
function
showPwd
()
{
if
(
state
.
passwordType
===
'
password
'
)
{
state
.
passwordType
=
''
if
(
state
.
passwordType
===
"
password
"
)
{
state
.
passwordType
=
""
;
}
else
{
state
.
passwordType
=
'
password
'
state
.
passwordType
=
"
password
"
;
}
nextTick
(()
=>
{
passwordRef
.
value
.
focus
()
})
passwordRef
.
value
.
focus
()
;
})
;
}
function
handleLogin
()
{
loginFormRef
.
value
.
validate
((
valid
:
boolean
)
=>
{
if
(
valid
)
{
state
.
loading
=
true
useUserStoreHook
().
login
(
state
.
loginForm
).
then
(()
=>
{
router
.
push
({
path
:
state
.
redirect
||
'
/
'
,
query
:
state
.
otherQuery
})
state
.
loading
=
false
}).
catch
(()
=>
{
state
.
loading
=
false
handleCaptchaGenerate
()
})
state
.
loading
=
true
;
user
.
login
(
state
.
loginForm
)
.
then
(()
=>
{
router
.
push
({
path
:
state
.
redirect
||
"
/
"
,
query
:
state
.
otherQuery
});
state
.
loading
=
false
;
})
.
catch
(()
=>
{
state
.
loading
=
false
;
handleCaptchaGenerate
();
});
}
else
{
return
false
return
false
;
}
})
})
;
}
// 获取验证码
function
handleCaptchaGenerate
()
{
getCaptcha
().
then
(
response
=>
{
const
{
img
,
uuid
}
=
response
.
data
state
.
captchaBase64
=
"
data:image/gif;base64,
"
+
img
getCaptcha
().
then
(
(
response
)
=>
{
const
{
img
,
uuid
}
=
response
.
data
;
state
.
captchaBase64
=
"
data:image/gif;base64,
"
+
img
;
state
.
loginForm
.
uuid
=
uuid
;
})
})
;
}
watch
(
route
,
()
=>
{
const
query
=
route
.
query
if
(
query
)
{
state
.
redirect
=
query
.
redirect
as
string
state
.
otherQuery
=
getOtherQuery
(
query
)
}
},
{
immediate
:
true
watch
(
route
,
()
=>
{
const
query
=
route
.
query
;
if
(
query
)
{
state
.
redirect
=
query
.
redirect
as
string
;
state
.
otherQuery
=
getOtherQuery
(
query
);
}
)
},
{
immediate
:
true
,
}
);
function
getOtherQuery
(
query
:
any
)
{
return
Object
.
keys
(
query
).
reduce
((
acc
:
any
,
cur
:
any
)
=>
{
if
(
cur
!==
'
redirect
'
)
{
acc
[
cur
]
=
query
[
cur
]
if
(
cur
!==
"
redirect
"
)
{
acc
[
cur
]
=
query
[
cur
]
;
}
return
acc
},
{})
return
acc
;
},
{})
;
}
onMounted
(()
=>
{
handleCaptchaGenerate
()
handleCaptchaGenerate
()
;
window
.
onresize
=
()
=>
{
if
(
state
.
clientHeight
>
document
.
documentElement
.
clientHeight
)
{
state
.
showCopyright
=
false
state
.
showCopyright
=
false
;
}
else
{
state
.
showCopyright
=
true
state
.
showCopyright
=
true
;
}
}
})
}
;
})
;
</
script
>
<
style
lang=
"scss"
>
...
...
@@ -240,7 +260,6 @@ $cursor: #fff;
/* reset element-ui css */
.login-container
{
.title-container
{
position
:
relative
;
...
...
@@ -262,7 +281,6 @@ $cursor: #fff;
}
}
.el-input
{
display
:
inline-block
;
height
:
47px
;
...
...
@@ -285,12 +303,12 @@ $cursor: #fff;
}
}
.el-input__inner
{
&
:hover
{
border-color
:
var
(
--
el-input-hover-border
,
var
(
--
el-border-color-hover
));
box-shadow
:none
;
.el-input__inner
{
&
:hover
{
border-color
:
var
(
--
el-input-hover-border
,
var
(
--
el-border-color-hover
));
box-shadow
:
none
;
}
box-shadow
:none
;
box-shadow
:
none
;
}
.el-form-item
{
...
...
@@ -384,6 +402,5 @@ $light_gray: #eee;
vertical-align
:
middle
;
}
}
}
</
style
>
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录