Compare commits
No commits in common. "master" and "V5.5.0" have entirely different histories.
|
@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
||||||
liberapay: # Replace with a single Liberapay username
|
liberapay: # Replace with a single Liberapay username
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
otechie: # Replace with a single Otechie username
|
otechie: # Replace with a single Otechie username
|
||||||
custom: https://lin-xin.github.io/images/weixin.jpg
|
custom: https://lin-xin.gitee.io/images/weixin.jpg
|
||||||
|
|
|
@ -7,11 +7,10 @@
|
||||||
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
|
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上演示](https://lin-xin.github.io/example/vue-manage-system/)
|
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上地址](https://lin-xin.gitee.io/example/work/)
|
||||||
|
|
||||||
> Vue2 版本请看 [tag-V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0),带后台功能请看 [tsrpc-manage-system](https://github.com/lin-xin/tsrpc-manage-system)
|
> Vue2 版本请看 [tag-V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0),带后台功能请看 [tsrpc-manage-system](https://github.com/lin-xin/tsrpc-manage-system)
|
||||||
|
|
||||||
[文档地址](https://lin-xin.github.io/example/vuems-doc/)
|
|
||||||
[English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md)
|
[English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md)
|
||||||
|
|
||||||
## 赞助商
|
## 赞助商
|
||||||
|
@ -26,7 +25,7 @@
|
||||||
|
|
||||||
请作者喝杯咖啡吧!(微信号:linxin_20)
|
请作者喝杯咖啡吧!(微信号:linxin_20)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 前言
|
## 前言
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,14 @@
|
||||||
outline: 0 !important;
|
outline: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app,
|
||||||
|
.wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'microsoft yahei', arial, STHeiTi, sans-serif;
|
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'microsoft yahei', arial, STHeiTi, sans-serif;
|
||||||
|
|
|
@ -1,66 +1,61 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<!-- 折叠按钮 -->
|
<!-- 折叠按钮 -->
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<img class="logo" src="../assets/img/logo.svg" alt="" />
|
<img class="logo" src="../assets/img/logo.svg" alt="">
|
||||||
<div class="web-title">后台管理系统</div>
|
<div class="web-title">
|
||||||
<div class="collapse-btn" @click="collapseChage">
|
后台管理系统
|
||||||
<el-icon v-if="sidebar.collapse">
|
</div>
|
||||||
<Expand />
|
<div class="collapse-btn" @click="collapseChage">
|
||||||
</el-icon>
|
<el-icon v-if="sidebar.collapse">
|
||||||
<el-icon v-else>
|
<Expand />
|
||||||
<Fold />
|
</el-icon>
|
||||||
</el-icon>
|
<el-icon v-else>
|
||||||
</div>
|
<Fold />
|
||||||
</div>
|
</el-icon>
|
||||||
<div class="header-right">
|
</div>
|
||||||
<div class="header-user-con">
|
</div>
|
||||||
<div class="btn-icon" @click="router.push('/theme')">
|
<div class="header-right">
|
||||||
<el-tooltip effect="dark" content="设置主题" placement="bottom">
|
<div class="header-user-con">
|
||||||
<i class="el-icon-lx-skin"></i>
|
<div class="btn-icon" @click="router.push('/theme')">
|
||||||
</el-tooltip>
|
<el-tooltip effect="dark" content="设置主题" placement="bottom">
|
||||||
</div>
|
<i class="el-icon-lx-skin"></i>
|
||||||
<div class="btn-icon" @click="router.push('/ucenter')">
|
</el-tooltip>
|
||||||
<el-tooltip
|
</div>
|
||||||
effect="dark"
|
<div class="btn-icon" @click="router.push('/ucenter')">
|
||||||
:content="message ? `有${message}条未读消息` : `消息中心`"
|
<el-tooltip effect="dark" :content="message ? `有${message}条未读消息` : `消息中心`" placement="bottom">
|
||||||
placement="bottom"
|
<i class="el-icon-lx-notice"></i>
|
||||||
>
|
</el-tooltip>
|
||||||
<i class="el-icon-lx-notice"></i>
|
<span class="btn-bell-badge" v-if="message"></span>
|
||||||
</el-tooltip>
|
</div>
|
||||||
<span class="btn-bell-badge" v-if="message"></span>
|
<div class="btn-icon" @click="setFullScreen">
|
||||||
</div>
|
<el-tooltip effect="dark" content="全屏" placement="bottom">
|
||||||
<div class="btn-icon" @click="setFullScreen">
|
<i class="el-icon-lx-full"></i>
|
||||||
<el-tooltip effect="dark" content="全屏" placement="bottom">
|
</el-tooltip>
|
||||||
<i class="el-icon-lx-full"></i>
|
</div>
|
||||||
</el-tooltip>
|
<!-- 用户头像 -->
|
||||||
</div>
|
<el-avatar class="user-avator" :size="30" :src="imgurl" />
|
||||||
<!-- 用户头像 -->
|
<!-- 用户名下拉菜单 -->
|
||||||
<el-avatar class="user-avator" :size="30" :src="imgurl" />
|
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
|
||||||
<!-- 用户名下拉菜单 -->
|
<span class="el-dropdown-link">
|
||||||
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
|
{{ username }}
|
||||||
<span class="el-dropdown-link">
|
<el-icon class="el-icon--right">
|
||||||
{{ username }}
|
<arrow-down />
|
||||||
<el-icon class="el-icon--right">
|
</el-icon>
|
||||||
<arrow-down />
|
</span>
|
||||||
</el-icon>
|
<template #dropdown>
|
||||||
</span>
|
<el-dropdown-menu>
|
||||||
<template #dropdown>
|
<a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
|
||||||
<el-dropdown-menu>
|
<el-dropdown-item>项目仓库</el-dropdown-item>
|
||||||
<a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
|
</a>
|
||||||
<el-dropdown-item>项目仓库</el-dropdown-item>
|
<el-dropdown-item command="user">个人中心</el-dropdown-item>
|
||||||
</a>
|
<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
|
||||||
<a href="https://lin-xin.gitee.io/example/vuems-doc/" target="_blank">
|
</el-dropdown-menu>
|
||||||
<el-dropdown-item>官方文档</el-dropdown-item>
|
</template>
|
||||||
</a>
|
</el-dropdown>
|
||||||
<el-dropdown-item command="user">个人中心</el-dropdown-item>
|
</div>
|
||||||
<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
|
</div>
|
||||||
</el-dropdown-menu>
|
</div>
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
|
@ -68,137 +63,138 @@ import { useSidebarStore } from '../store/sidebar';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import imgurl from '../assets/img/img.jpg';
|
import imgurl from '../assets/img/img.jpg';
|
||||||
|
|
||||||
const username: string | null = localStorage.getItem('vuems_name');
|
const username: string | null = localStorage.getItem('ms_username');
|
||||||
const message: number = 2;
|
const message: number = 2;
|
||||||
|
|
||||||
const sidebar = useSidebarStore();
|
const sidebar = useSidebarStore();
|
||||||
// 侧边栏折叠
|
// 侧边栏折叠
|
||||||
const collapseChage = () => {
|
const collapseChage = () => {
|
||||||
sidebar.handleCollapse();
|
sidebar.handleCollapse();
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (document.body.clientWidth < 1500) {
|
if (document.body.clientWidth < 1500) {
|
||||||
collapseChage();
|
collapseChage();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 用户名下拉菜单选择事件
|
// 用户名下拉菜单选择事件
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const handleCommand = (command: string) => {
|
const handleCommand = (command: string) => {
|
||||||
if (command == 'loginout') {
|
if (command == 'loginout') {
|
||||||
localStorage.removeItem('vuems_name');
|
localStorage.removeItem('ms_username');
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
} else if (command == 'user') {
|
} else if (command == 'user') {
|
||||||
router.push('/ucenter');
|
router.push('/ucenter');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setFullScreen = () => {
|
const setFullScreen = () => {
|
||||||
if (document.fullscreenElement) {
|
if (document.fullscreenElement) {
|
||||||
document.exitFullscreen();
|
document.exitFullscreen();
|
||||||
} else {
|
} else {
|
||||||
document.body.requestFullscreen.call(document.body);
|
document.body.requestFullscreen.call(document.body);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
color: var(--header-text-color);
|
color: var(--header-text-color);
|
||||||
background-color: var(--header-bg-color);
|
background-color: var(--header-bg-color);
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 35px;
|
width: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web-title {
|
.web-title {
|
||||||
margin: 0 40px 0 10px;
|
margin: 0 40px 0 10px;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapse-btn {
|
.collapse-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapse-btn:hover {
|
.collapse-btn:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-right {
|
.header-right {
|
||||||
float: right;
|
float: right;
|
||||||
padding-right: 50px;
|
padding-right: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-user-con {
|
.header-user-con {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-fullscreen {
|
.btn-fullscreen {
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--header-text-color);
|
color: var(--header-text-color);
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-bell-badge {
|
.btn-bell-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 4px;
|
right: 4px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: #f56c6c;
|
background: #f56c6c;
|
||||||
color: var(--header-text-color);
|
color: var(--header-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.user-avator {
|
.user-avator {
|
||||||
margin: 0 10px 0 20px;
|
margin: 0 10px 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dropdown-link {
|
.el-dropdown-link {
|
||||||
color: var(--header-text-color);
|
color: var(--header-text-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dropdown-menu__item {
|
.el-dropdown-menu__item {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,32 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tabs-container">
|
<div class="tabs-container">
|
||||||
<el-tabs v-model="activePath" class="tabs" type="card" closable @tab-click="clickTabls" @tab-remove="closeTabs">
|
<el-tabs v-model="activePath" class="tabs" type="card" closable @tab-click="clickTabls" @tab-remove="closeTabs">
|
||||||
<el-tab-pane
|
<el-tab-pane v-for="item in tabs.list" :key="item.path" :label="item.title" :name="item.path"
|
||||||
v-for="item in tabs.list"
|
@click="setTags(item)"></el-tab-pane>
|
||||||
:key="item.path"
|
</el-tabs>
|
||||||
:label="item.title"
|
<div class="Tabs-close-box">
|
||||||
:name="item.path"
|
<el-dropdown @command="handleTags">
|
||||||
@click="setTags(item)"
|
<el-button size="small" type="primary" plain>
|
||||||
></el-tab-pane>
|
标签选项
|
||||||
</el-tabs>
|
<el-icon class="el-icon--right">
|
||||||
<div class="Tabs-close-box">
|
<arrow-down />
|
||||||
<el-dropdown @command="handleTags">
|
</el-icon>
|
||||||
<el-button size="small" type="primary" plain>
|
</el-button>
|
||||||
标签选项
|
<template #dropdown>
|
||||||
<el-icon class="el-icon--right">
|
<el-dropdown-menu size="small">
|
||||||
<arrow-down />
|
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
|
||||||
</el-icon>
|
<el-dropdown-item command="current">关闭当前</el-dropdown-item>
|
||||||
</el-button>
|
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
|
||||||
<template #dropdown>
|
</el-dropdown-menu>
|
||||||
<el-dropdown-menu size="small">
|
</template>
|
||||||
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
|
</el-dropdown>
|
||||||
<el-dropdown-item command="current">关闭当前</el-dropdown-item>
|
</div>
|
||||||
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
|
</div>
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -36,113 +31,119 @@ import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const activePath = ref(route.fullPath);
|
const activePath = ref(route.fullPath)
|
||||||
const tabs = useTabsStore();
|
const tabs = useTabsStore();
|
||||||
// 设置标签
|
// 设置标签
|
||||||
const setTags = (route: any) => {
|
const setTags = (route: any) => {
|
||||||
const isExist = tabs.list.some((item) => {
|
const isExist = tabs.list.some(item => {
|
||||||
return item.path === route.fullPath;
|
return item.path === route.fullPath;
|
||||||
});
|
});
|
||||||
if (!isExist) {
|
if (!isExist) {
|
||||||
tabs.setTabsItem({
|
tabs.setTabsItem({
|
||||||
name: route.name,
|
name: route.name,
|
||||||
title: route.meta.title,
|
title: route.meta.title,
|
||||||
path: route.fullPath,
|
path: route.fullPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
setTags(route);
|
setTags(route);
|
||||||
onBeforeRouteUpdate((to) => {
|
onBeforeRouteUpdate(to => {
|
||||||
setTags(to);
|
setTags(to);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 关闭全部标签
|
// 关闭全部标签
|
||||||
const closeAll = () => {
|
const closeAll = () => {
|
||||||
tabs.clearTabs();
|
tabs.clearTabs();
|
||||||
router.push('/');
|
router.push('/');
|
||||||
};
|
};
|
||||||
// 关闭其他标签
|
// 关闭其他标签
|
||||||
const closeOther = () => {
|
const closeOther = () => {
|
||||||
const curItem = tabs.list.filter((item) => {
|
const curItem = tabs.list.filter(item => {
|
||||||
return item.path === route.fullPath;
|
return item.path === route.fullPath;
|
||||||
});
|
});
|
||||||
tabs.closeTabsOther(curItem);
|
tabs.closeTabsOther(curItem);
|
||||||
};
|
};
|
||||||
const handleTags = (command: string) => {
|
const handleTags = (command: string) => {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'current':
|
case 'current':
|
||||||
// 关闭当前页面的标签页
|
// 关闭当前页面的标签页
|
||||||
tabs.closeCurrentTag({
|
tabs.closeCurrentTag({
|
||||||
$router: router,
|
$router: router,
|
||||||
$route: route,
|
$route: route
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
closeAll();
|
closeAll();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'other':
|
case 'other':
|
||||||
closeOther();
|
closeOther();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clickTabls = (item: any) => {
|
const clickTabls = (item: any) => {
|
||||||
router.push(item.props.name);
|
router.push(item.props.name);
|
||||||
};
|
}
|
||||||
const closeTabs = (path: string) => {
|
const closeTabs = (path: any) => {
|
||||||
const index = tabs.list.findIndex((item) => item.path === path);
|
console.log(path);
|
||||||
tabs.delTabsItem(index);
|
const index = tabs.list.findIndex((item) => item.path === path);
|
||||||
const item = tabs.list[index] || tabs.list[index - 1];
|
tabs.delTabsItem(index);
|
||||||
router.push(item ? item.path : '/');
|
const item = tabs.list[index] ? tabs.list[index] : tabs.list[index - 1];
|
||||||
};
|
if (item) {
|
||||||
|
path === route.fullPath && router.push(item.path);
|
||||||
|
} else {
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => route.fullPath, (newVal, oldVal) => {
|
||||||
|
activePath.value = newVal;
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
|
||||||
() => route.fullPath,
|
|
||||||
(newVal, oldVal) => {
|
|
||||||
activePath.value = newVal;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scss>
|
<style scss>
|
||||||
.tabs-container {
|
.tabs-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 2px 120px 0 0;
|
padding: 2px 120px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
.el-tabs__header {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-tabs__nav {
|
.el-tabs__header {
|
||||||
height: 28px;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-tabs__nav {
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__nav-next,
|
||||||
|
.el-tabs__nav-prev {
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-tabs {
|
||||||
|
--el-tabs-header-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
.el-tabs__nav-next,
|
|
||||||
.el-tabs__nav-prev {
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.el-tabs {
|
|
||||||
--el-tabs-header-height: 28px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Tabs-close-box {
|
.Tabs-close-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-top: 1px;
|
padding-top: 1px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 110px;
|
width: 110px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
|
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -19,7 +19,7 @@ const routes: RouteRecordRaw[] = [
|
||||||
name: 'dashboard',
|
name: 'dashboard',
|
||||||
meta: {
|
meta: {
|
||||||
title: '系统首页',
|
title: '系统首页',
|
||||||
noAuth: true,
|
permiss: '0',
|
||||||
},
|
},
|
||||||
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
|
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
|
||||||
},
|
},
|
||||||
|
@ -273,12 +273,12 @@ const router = createRouter({
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
NProgress.start();
|
NProgress.start();
|
||||||
const role = localStorage.getItem('vuems_name');
|
const role = localStorage.getItem('ms_username');
|
||||||
const permiss = usePermissStore();
|
const permiss = usePermissStore();
|
||||||
|
|
||||||
if (!role && to.meta.noAuth !== true) {
|
if (!role && to.meta.noAuth !== true) {
|
||||||
next('/login');
|
next('/login');
|
||||||
} else if (typeof to.meta.permiss == 'string' && !permiss.key.includes(to.meta.permiss)) {
|
} else if (to.meta.permiss && !permiss.key.includes(to.meta.permiss)) {
|
||||||
// 如果没有权限,则进入403
|
// 如果没有权限,则进入403
|
||||||
next('/403');
|
next('/403');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,50 +6,48 @@ interface ObjectList {
|
||||||
|
|
||||||
export const usePermissStore = defineStore('permiss', {
|
export const usePermissStore = defineStore('permiss', {
|
||||||
state: () => {
|
state: () => {
|
||||||
const defaultList: ObjectList = {
|
const keys = localStorage.getItem('ms_keys');
|
||||||
admin: [
|
|
||||||
'0',
|
|
||||||
'1',
|
|
||||||
'11',
|
|
||||||
'12',
|
|
||||||
'13',
|
|
||||||
'2',
|
|
||||||
'21',
|
|
||||||
'22',
|
|
||||||
'23',
|
|
||||||
'24',
|
|
||||||
'25',
|
|
||||||
'26',
|
|
||||||
'27',
|
|
||||||
'28',
|
|
||||||
'29',
|
|
||||||
'291',
|
|
||||||
'292',
|
|
||||||
'3',
|
|
||||||
'31',
|
|
||||||
'32',
|
|
||||||
'33',
|
|
||||||
'34',
|
|
||||||
'4',
|
|
||||||
'41',
|
|
||||||
'42',
|
|
||||||
'5',
|
|
||||||
'7',
|
|
||||||
'6',
|
|
||||||
'61',
|
|
||||||
'62',
|
|
||||||
'63',
|
|
||||||
'64',
|
|
||||||
'65',
|
|
||||||
'66',
|
|
||||||
],
|
|
||||||
user: ['0', '1', '11', '12', '13'],
|
|
||||||
};
|
|
||||||
const username = localStorage.getItem('vuems_name');
|
|
||||||
console.log(username);
|
|
||||||
return {
|
return {
|
||||||
key: (username == 'admin' ? defaultList.admin : defaultList.user) as string[],
|
key: keys ? JSON.parse(keys) : <string[]>[],
|
||||||
defaultList,
|
defaultList: <ObjectList>{
|
||||||
|
admin: [
|
||||||
|
'0',
|
||||||
|
'1',
|
||||||
|
'11',
|
||||||
|
'12',
|
||||||
|
'13',
|
||||||
|
'2',
|
||||||
|
'21',
|
||||||
|
'22',
|
||||||
|
'23',
|
||||||
|
'24',
|
||||||
|
'25',
|
||||||
|
'26',
|
||||||
|
'27',
|
||||||
|
'28',
|
||||||
|
'29',
|
||||||
|
'291',
|
||||||
|
'292',
|
||||||
|
'3',
|
||||||
|
'31',
|
||||||
|
'32',
|
||||||
|
'33',
|
||||||
|
'34',
|
||||||
|
'4',
|
||||||
|
'41',
|
||||||
|
'42',
|
||||||
|
'5',
|
||||||
|
'7',
|
||||||
|
'6',
|
||||||
|
'61',
|
||||||
|
'62',
|
||||||
|
'63',
|
||||||
|
'64',
|
||||||
|
'65',
|
||||||
|
'66',
|
||||||
|
],
|
||||||
|
user: ['0', '1', '11', '12', '13'],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="wrapper">
|
<v-header />
|
||||||
<v-header />
|
<v-sidebar />
|
||||||
<v-sidebar />
|
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
|
||||||
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
|
<v-tabs></v-tabs>
|
||||||
<v-tabs></v-tabs>
|
<div class="content">
|
||||||
<div class="content">
|
<router-view v-slot="{ Component }">
|
||||||
<router-view v-slot="{ Component }">
|
<transition name="move" mode="out-in">
|
||||||
<transition name="move" mode="out-in">
|
<keep-alive :include="tabs.nameList">
|
||||||
<keep-alive :include="tabs.nameList">
|
<component :is="Component"></component>
|
||||||
<component :is="Component"></component>
|
</keep-alive>
|
||||||
</keep-alive>
|
</transition>
|
||||||
</transition>
|
</router-view>
|
||||||
</router-view>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useSidebarStore } from '@/store/sidebar';
|
import { useSidebarStore } from '@/store/sidebar';
|
||||||
|
@ -28,36 +26,31 @@ const tabs = useTabsStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.wrapper {
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.content-box {
|
.content-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 250px;
|
left: 250px;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 70px;
|
top: 70px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
padding-bottom: 30px;
|
padding-bottom: 30px;
|
||||||
-webkit-transition: left 0.3s ease-in-out;
|
-webkit-transition: left 0.3s ease-in-out;
|
||||||
transition: left 0.3s ease-in-out;
|
transition: left 0.3s ease-in-out;
|
||||||
background: #eef0fc;
|
background: #eef0fc;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content::-webkit-scrollbar {
|
.content::-webkit-scrollbar {
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-collapse {
|
.content-collapse {
|
||||||
left: 65px;
|
left: 65px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,16 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="error-page">
|
<div class="error-page">
|
||||||
<div class="error-box">
|
<div class="error-box">
|
||||||
<div class="error-code">403</div>
|
<div class="error-code">403</div>
|
||||||
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
|
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
|
||||||
<div class="error-handle">
|
<div class="error-handle">
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<el-button type="primary" size="large">返回首页</el-button>
|
<el-button type="primary" size="large">返回首页</el-button>
|
||||||
</router-link>
|
</router-link>
|
||||||
<el-button class="error-btn" size="large" @click="goBack">返回上一页</el-button>
|
<el-button class="error-btn" size="large" @click="goBack">返回上一页</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="403">
|
<script setup lang="ts" name="403">
|
||||||
|
@ -18,50 +18,50 @@ import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
router.go(-2);
|
router.go(-2);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.error-page {
|
.error-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
background: #eef0fc;
|
background: #eef0fc;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-box {
|
.error-box {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 80px 50px;
|
padding: 80px 50px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-code {
|
.error-code {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
font-size: 100px;
|
font-size: 100px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
text-align: center;
|
text-align: center
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-desc {
|
.error-desc {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: #777;
|
color: #777;
|
||||||
text-align: center;
|
text-align: center
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-handle {
|
.error-handle {
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-btn {
|
.error-btn {
|
||||||
margin-left: 100px;
|
margin-left: 100px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="error-page">
|
<div class="error-page">
|
||||||
<div class="error-box">
|
<div class="error-box">
|
||||||
<div class="error-code">404</div>
|
<div class="error-code">404</div>
|
||||||
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
|
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
|
||||||
<div class="error-handle">
|
<div class="error-handle">
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<el-button type="primary" size="large">返回首页</el-button>
|
<el-button type="primary" size="large">返回首页</el-button>
|
||||||
</router-link>
|
</router-link>
|
||||||
<el-button class="error-btn" size="large" @click="goBack">返回上一页</el-button>
|
<el-button class="error-btn" size="large" @click="goBack">返回上一页</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="404">
|
<script setup lang="ts" name="404">
|
||||||
|
@ -18,50 +18,50 @@ import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
router.go(-1);
|
router.go(-1);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.error-page {
|
.error-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
background: #eef0fc;
|
background: #eef0fc;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-box {
|
.error-box {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 80px 50px;
|
padding: 80px 50px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-code {
|
.error-code {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
font-size: 100px;
|
font-size: 100px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
text-align: center;
|
text-align: center
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-desc {
|
.error-desc {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: #777;
|
color: #777;
|
||||||
text-align: center;
|
text-align: center
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-handle {
|
.error-handle {
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-btn {
|
.error-btn {
|
||||||
margin-left: 100px;
|
margin-left: 100px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -83,9 +83,10 @@ const submitForm = (formEl: FormInstance | undefined) => {
|
||||||
formEl.validate((valid: boolean) => {
|
formEl.validate((valid: boolean) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
ElMessage.success('登录成功');
|
ElMessage.success('登录成功');
|
||||||
localStorage.setItem('vuems_name', param.username);
|
localStorage.setItem('ms_username', param.username);
|
||||||
const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
|
const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
|
||||||
permiss.handleSet(keys);
|
permiss.handleSet(keys);
|
||||||
|
localStorage.setItem('ms_keys', JSON.stringify(keys));
|
||||||
router.push('/');
|
router.push('/');
|
||||||
if (checked.value) {
|
if (checked.value) {
|
||||||
localStorage.setItem('login-param', JSON.stringify(param));
|
localStorage.setItem('login-param', JSON.stringify(param));
|
||||||
|
@ -109,7 +110,7 @@ tabs.clearTabs();
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
<div class="login-bg">
|
<div class="login-bg">
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<div class="login-header">
|
<div class="login-header">
|
||||||
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
|
<img class="logo mr10" src="../../assets/img/logo.svg" alt="">
|
||||||
<div class="login-title">后台管理系统</div>
|
<div class="login-title">
|
||||||
|
后台管理系统
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-form :model="param" :rules="rules" ref="register" size="large">
|
<el-form :model="param" :rules="rules" ref="register" size="large">
|
||||||
<el-form-item prop="username">
|
<el-form-item prop="username">
|
||||||
|
@ -25,12 +27,8 @@
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="password">
|
<el-form-item prop="password">
|
||||||
<el-input
|
<el-input type="password" placeholder="密码" v-model="param.password"
|
||||||
type="password"
|
@keyup.enter="submitForm(register)">
|
||||||
placeholder="密码"
|
|
||||||
v-model="param.password"
|
|
||||||
@keyup.enter="submitForm(register)"
|
|
||||||
>
|
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Lock />
|
<Lock />
|
||||||
|
@ -39,9 +37,7 @@
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)">注册</el-button>
|
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)">注册</el-button>
|
||||||
<p class="login-text">
|
<p class="login-text">已有账号,<el-link type="primary" @click="$router.push('/login')">立即登录</el-link></p>
|
||||||
已有账号,<el-link type="primary" @click="$router.push('/login')">立即登录</el-link>
|
|
||||||
</p>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -83,6 +79,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -91,7 +88,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,7 @@
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)"
|
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)">发送邮件</el-button>
|
||||||
>发送邮件</el-button
|
|
||||||
>
|
|
||||||
<p class="login-text"><el-link type="primary" @click="$router.push('/login')">返回登录</el-link></p>
|
<p class="login-text"><el-link type="primary" @click="$router.push('/login')">返回登录</el-link></p>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,11 +31,7 @@ const param = ref({
|
||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
email: [
|
email: [
|
||||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||||
{
|
{ pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, message: '请输入正确的邮箱格式', trigger: 'blur' }
|
||||||
pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
|
||||||
message: '请输入正确的邮箱格式',
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const register = ref<FormInstance>();
|
const register = ref<FormInstance>();
|
||||||
|
@ -51,6 +45,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -59,7 +54,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ import 'vue-cropper/dist/index.css';
|
||||||
import avatar from '@/assets/img/img.jpg';
|
import avatar from '@/assets/img/img.jpg';
|
||||||
import TabsComp from '../element/tabs.vue';
|
import TabsComp from '../element/tabs.vue';
|
||||||
|
|
||||||
const name = localStorage.getItem('vuems_name');
|
const name = localStorage.getItem('ms_username');
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
new1: '',
|
new1: '',
|
||||||
new: '',
|
new: '',
|
||||||
|
|
|
@ -0,0 +1,253 @@
|
||||||
|
<template>
|
||||||
|
<div class="user-container">
|
||||||
|
<el-card class="user-profile mgb20" shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
|
<div class="user-profile-bg"></div>
|
||||||
|
<div class="user-avatar-wrap">
|
||||||
|
<el-avatar class="user-avatar" :size="120" :src="avatarImg" />
|
||||||
|
</div>
|
||||||
|
<div class="user-info">
|
||||||
|
<div class="info-name">{{ name }}</div>
|
||||||
|
<div class="info-desc">
|
||||||
|
<!-- <span>{{ name }}</span>
|
||||||
|
<el-divider direction="vertical" /> -->
|
||||||
|
<span>FE Developer</span>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-link href="https://lin-xin.gitee.io" target="_blank">lin-xin.gitee.io</el-link>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="info-icon">
|
||||||
|
<a href="https://github.com/lin-xin" target="_blank"> <i class="el-icon-lx-github-fill"></i></a>
|
||||||
|
<i class="el-icon-lx-qq-fill"></i>
|
||||||
|
<i class="el-icon-lx-facebook-fill"></i>
|
||||||
|
<i class="el-icon-lx-twitter-fill"></i>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<!-- <div class="user-footer">
|
||||||
|
<div class="user-footer-item">
|
||||||
|
<el-statistic title="Follower" value="18K" />
|
||||||
|
</div>
|
||||||
|
<div class="user-footer-item">
|
||||||
|
<el-statistic title="Following" :value="666" />
|
||||||
|
</div>
|
||||||
|
<div class="user-footer-item">
|
||||||
|
<el-statistic title="Total Post" :value="888" />
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</el-card>
|
||||||
|
<el-card class="user-content" shadow="hover"
|
||||||
|
:body-style="{ padding: '20px 50px', height: '100%', boxSizing: 'border-box' }">
|
||||||
|
<el-tabs v-model="activeName">
|
||||||
|
|
||||||
|
<el-tab-pane name="label1" label="消息通知" class="user-tabpane">
|
||||||
|
<TabsComp />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane name="label2" label="我的头像" class="user-tabpane">
|
||||||
|
<div class="crop-wrap" v-if="activeName === 'label2'">
|
||||||
|
<vueCropper ref="cropper" :img="imgSrc" :autoCrop="true" :centerBox="true" :full="true"
|
||||||
|
mode="contain">
|
||||||
|
</vueCropper>
|
||||||
|
</div>
|
||||||
|
<el-button class="crop-demo-btn" type="primary">选择图片
|
||||||
|
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" @click="saveAvatar">上传并保存</el-button>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane name="label3" label="修改密码" class="user-tabpane">
|
||||||
|
<el-form class="w500" label-position="top">
|
||||||
|
<el-form-item label="旧密码:">
|
||||||
|
<el-input type="password" v-model="form.old"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="新密码:">
|
||||||
|
<el-input type="password" v-model="form.new"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="确认新密码:">
|
||||||
|
<el-input type="password" v-model="form.new1"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="onSubmit">保存</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane name="label4" label="赞赏作者" class="user-tabpane">
|
||||||
|
<div class="plugins-tips">
|
||||||
|
如果该框架 <el-link href="https://github.com/lin-xin/vue-manage-system"
|
||||||
|
target="_blank">vue-manage-system</el-link> 对你有帮助,那就请作者喝杯饮料吧!<el-icon>
|
||||||
|
<ColdDrink />
|
||||||
|
</el-icon> 加微信号 linxin_20 探讨问题。
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img src="https://lin-xin.gitee.io/images/weixin.jpg" />
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="ucenter">
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { VueCropper } from "vue-cropper"
|
||||||
|
import 'vue-cropper/dist/index.css'
|
||||||
|
import avatar from '@/assets/img/img.jpg';
|
||||||
|
import TabsComp from '../element/tabs.vue';
|
||||||
|
|
||||||
|
const name = localStorage.getItem('ms_username');
|
||||||
|
const form = reactive({
|
||||||
|
new1: '',
|
||||||
|
new: '',
|
||||||
|
old: ''
|
||||||
|
});
|
||||||
|
const onSubmit = () => { };
|
||||||
|
|
||||||
|
const activeName = ref('label1');
|
||||||
|
|
||||||
|
const avatarImg = ref(avatar);
|
||||||
|
const imgSrc = ref(avatar);
|
||||||
|
const cropImg = ref('');
|
||||||
|
const cropper: any = ref();
|
||||||
|
|
||||||
|
const setImage = (e: any) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file.type.includes('image/')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event: any) => {
|
||||||
|
imgSrc.value = event.target.result;
|
||||||
|
cropper.value && cropper.value.replace(event.target.result);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cropImage = () => {
|
||||||
|
cropImg.value = cropper.value?.getCroppedCanvas().toDataURL();
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveAvatar = () => {
|
||||||
|
avatarImg.value = cropImg.value;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* .user-container {
|
||||||
|
display: flex;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.user-profile-bg {
|
||||||
|
width: 100%;
|
||||||
|
height: 150px;
|
||||||
|
background-image: url('../../assets/img/bahnhofsidylle.jpg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-profile {
|
||||||
|
position: relative;
|
||||||
|
/* width: 500px; */
|
||||||
|
/* margin-right: 20px; */
|
||||||
|
/* flex: 0 0 auto;
|
||||||
|
align-self: flex-start;
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar-wrap {
|
||||||
|
position: absolute;
|
||||||
|
top: 90px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
border: 5px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 7px 12px 0 rgba(62, 57, 107, .16)
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
text-align: center;
|
||||||
|
padding: 70px 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-name {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #373a3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-desc {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-desc,
|
||||||
|
.info-desc a {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #55595c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon i {
|
||||||
|
font-size: 30px;
|
||||||
|
margin: 0 10px;
|
||||||
|
color: #343434;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-content {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-tabpane {
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-wrap {
|
||||||
|
width: 600px;
|
||||||
|
height: 350px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-demo-btn {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-input {
|
||||||
|
position: absolute;
|
||||||
|
width: 100px;
|
||||||
|
height: 40px;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w500 {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-footer {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1px solid rgba(83, 70, 134, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-footer-item {
|
||||||
|
padding: 20px 0;
|
||||||
|
width: 33.3333333333%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-footer>div+div {
|
||||||
|
border-left: 1px solid rgba(83, 70, 134, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.el-tabs.el-tabs--left {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue