Compare commits

...

6 Commits

Author SHA1 Message Date
林鑫 6a7019ec1a
Update FUNDING.yml 2024-05-25 10:29:18 +08:00
林鑫 7375986a8e
Update README.md 2024-05-25 10:24:33 +08:00
林鑫 251936dcdc
Update README.md 2024-05-25 10:21:48 +08:00
lin-xin 921c38cea2 修改username 2024-04-18 22:51:36 +08:00
lin-xin 718f255817 优化样式和权限 2024-04-18 22:46:26 +08:00
lin-xin 433b60e720 更新文档 2024-04-13 11:07:56 +08:00
15 changed files with 426 additions and 667 deletions

2
.github/FUNDING.yml vendored
View File

@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: https://lin-xin.gitee.io/images/weixin.jpg
custom: https://lin-xin.github.io/images/weixin.jpg

View File

@ -7,10 +7,11 @@
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
</a>
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上地址](https://lin-xin.gitee.io/example/work/)
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上演示](https://lin-xin.github.io/example/vue-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)
## 赞助商
@ -25,7 +26,7 @@
请作者喝杯咖啡吧!(微信号linxin_20)
![微信扫一扫](https://lin-xin.gitee.io/images/weixin.jpg)
![微信扫一扫](https://lin-xin.github.io/images/weixin.jpg)
## 前言

View File

@ -4,14 +4,6 @@
outline: 0 !important;
}
html,
body,
#app,
.wrapper {
width: 100%;
height: 100%;
overflow: hidden;
}
body {
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'microsoft yahei', arial, STHeiTi, sans-serif;

View File

@ -1,61 +1,66 @@
<template>
<div class="header">
<!-- 折叠按钮 -->
<div class="header-left">
<img class="logo" src="../assets/img/logo.svg" alt="">
<div class="web-title">
后台管理系统
</div>
<div class="collapse-btn" @click="collapseChage">
<el-icon v-if="sidebar.collapse">
<Expand />
</el-icon>
<el-icon v-else>
<Fold />
</el-icon>
</div>
</div>
<div class="header-right">
<div class="header-user-con">
<div class="btn-icon" @click="router.push('/theme')">
<el-tooltip effect="dark" content="设置主题" placement="bottom">
<i class="el-icon-lx-skin"></i>
</el-tooltip>
</div>
<div class="btn-icon" @click="router.push('/ucenter')">
<el-tooltip effect="dark" :content="message ? `有${message}条未读消息` : `消息中心`" placement="bottom">
<i class="el-icon-lx-notice"></i>
</el-tooltip>
<span class="btn-bell-badge" v-if="message"></span>
</div>
<div class="btn-icon" @click="setFullScreen">
<el-tooltip effect="dark" content="全屏" placement="bottom">
<i class="el-icon-lx-full"></i>
</el-tooltip>
</div>
<!-- 用户头像 -->
<el-avatar class="user-avator" :size="30" :src="imgurl" />
<!-- 用户名下拉菜单 -->
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
<span class="el-dropdown-link">
{{ username }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
<el-dropdown-item>项目仓库</el-dropdown-item>
</a>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
<div class="header">
<!-- 折叠按钮 -->
<div class="header-left">
<img class="logo" src="../assets/img/logo.svg" alt="" />
<div class="web-title">后台管理系统</div>
<div class="collapse-btn" @click="collapseChage">
<el-icon v-if="sidebar.collapse">
<Expand />
</el-icon>
<el-icon v-else>
<Fold />
</el-icon>
</div>
</div>
<div class="header-right">
<div class="header-user-con">
<div class="btn-icon" @click="router.push('/theme')">
<el-tooltip effect="dark" content="设置主题" placement="bottom">
<i class="el-icon-lx-skin"></i>
</el-tooltip>
</div>
<div class="btn-icon" @click="router.push('/ucenter')">
<el-tooltip
effect="dark"
:content="message ? `有${message}条未读消息` : `消息中心`"
placement="bottom"
>
<i class="el-icon-lx-notice"></i>
</el-tooltip>
<span class="btn-bell-badge" v-if="message"></span>
</div>
<div class="btn-icon" @click="setFullScreen">
<el-tooltip effect="dark" content="全屏" placement="bottom">
<i class="el-icon-lx-full"></i>
</el-tooltip>
</div>
<!-- 用户头像 -->
<el-avatar class="user-avator" :size="30" :src="imgurl" />
<!-- 用户名下拉菜单 -->
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
<span class="el-dropdown-link">
{{ username }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
<el-dropdown-item>项目仓库</el-dropdown-item>
</a>
<a href="https://lin-xin.gitee.io/example/vuems-doc/" target="_blank">
<el-dropdown-item>官方文档</el-dropdown-item>
</a>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
@ -63,138 +68,137 @@ import { useSidebarStore } from '../store/sidebar';
import { useRouter } from 'vue-router';
import imgurl from '../assets/img/img.jpg';
const username: string | null = localStorage.getItem('ms_username');
const username: string | null = localStorage.getItem('vuems_name');
const message: number = 2;
const sidebar = useSidebarStore();
//
const collapseChage = () => {
sidebar.handleCollapse();
sidebar.handleCollapse();
};
onMounted(() => {
if (document.body.clientWidth < 1500) {
collapseChage();
}
if (document.body.clientWidth < 1500) {
collapseChage();
}
});
//
const router = useRouter();
const handleCommand = (command: string) => {
if (command == 'loginout') {
localStorage.removeItem('ms_username');
router.push('/login');
} else if (command == 'user') {
router.push('/ucenter');
}
if (command == 'loginout') {
localStorage.removeItem('vuems_name');
router.push('/login');
} else if (command == 'user') {
router.push('/ucenter');
}
};
const setFullScreen = () => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.body.requestFullscreen.call(document.body);
}
}
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.body.requestFullscreen.call(document.body);
}
};
</script>
<style scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
width: 100%;
height: 70px;
color: var(--header-text-color);
background-color: var(--header-bg-color);
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
width: 100%;
height: 70px;
color: var(--header-text-color);
background-color: var(--header-bg-color);
border-bottom: 1px solid #ddd;
}
.header-left {
display: flex;
align-items: center;
padding-left: 20px;
height: 100%;
display: flex;
align-items: center;
padding-left: 20px;
height: 100%;
}
.logo {
width: 35px;
width: 35px;
}
.web-title {
margin: 0 40px 0 10px;
font-size: 22px;
margin: 0 40px 0 10px;
font-size: 22px;
}
.collapse-btn {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 0 10px;
cursor: pointer;
opacity: 0.8;
font-size: 22px;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 0 10px;
cursor: pointer;
opacity: 0.8;
font-size: 22px;
}
.collapse-btn:hover {
opacity: 1;
opacity: 1;
}
.header-right {
float: right;
padding-right: 50px;
float: right;
padding-right: 50px;
}
.header-user-con {
display: flex;
height: 70px;
align-items: center;
display: flex;
height: 70px;
align-items: center;
}
.btn-fullscreen {
transform: rotate(45deg);
margin-right: 5px;
font-size: 24px;
transform: rotate(45deg);
margin-right: 5px;
font-size: 24px;
}
.btn-icon {
position: relative;
width: 30px;
height: 30px;
text-align: center;
cursor: pointer;
display: flex;
align-items: center;
color: var(--header-text-color);
margin: 0 5px;
font-size: 20px;
position: relative;
width: 30px;
height: 30px;
text-align: center;
cursor: pointer;
display: flex;
align-items: center;
color: var(--header-text-color);
margin: 0 5px;
font-size: 20px;
}
.btn-bell-badge {
position: absolute;
right: 4px;
top: 0px;
width: 8px;
height: 8px;
border-radius: 4px;
background: #f56c6c;
color: var(--header-text-color);
position: absolute;
right: 4px;
top: 0px;
width: 8px;
height: 8px;
border-radius: 4px;
background: #f56c6c;
color: var(--header-text-color);
}
.user-avator {
margin: 0 10px 0 20px;
margin: 0 10px 0 20px;
}
.el-dropdown-link {
color: var(--header-text-color);
cursor: pointer;
display: flex;
align-items: center;
color: var(--header-text-color);
cursor: pointer;
display: flex;
align-items: center;
}
.el-dropdown-menu__item {
text-align: center;
text-align: center;
}
</style>

View File

@ -1,27 +1,32 @@
<template>
<div class="tabs-container">
<el-tabs v-model="activePath" class="tabs" type="card" closable @tab-click="clickTabls" @tab-remove="closeTabs">
<el-tab-pane v-for="item in tabs.list" :key="item.path" :label="item.title" :name="item.path"
@click="setTags(item)"></el-tab-pane>
</el-tabs>
<div class="Tabs-close-box">
<el-dropdown @command="handleTags">
<el-button size="small" type="primary" plain>
标签选项
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu size="small">
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
<el-dropdown-item command="current">关闭当前</el-dropdown-item>
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<div class="tabs-container">
<el-tabs v-model="activePath" class="tabs" type="card" closable @tab-click="clickTabls" @tab-remove="closeTabs">
<el-tab-pane
v-for="item in tabs.list"
:key="item.path"
:label="item.title"
:name="item.path"
@click="setTags(item)"
></el-tab-pane>
</el-tabs>
<div class="Tabs-close-box">
<el-dropdown @command="handleTags">
<el-button size="small" type="primary" plain>
标签选项
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu size="small">
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
<el-dropdown-item command="current">关闭当前</el-dropdown-item>
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script setup lang="ts">
@ -31,119 +36,113 @@ import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const activePath = ref(route.fullPath)
const activePath = ref(route.fullPath);
const tabs = useTabsStore();
//
const setTags = (route: any) => {
const isExist = tabs.list.some(item => {
return item.path === route.fullPath;
});
if (!isExist) {
tabs.setTabsItem({
name: route.name,
title: route.meta.title,
path: route.fullPath
});
}
const isExist = tabs.list.some((item) => {
return item.path === route.fullPath;
});
if (!isExist) {
tabs.setTabsItem({
name: route.name,
title: route.meta.title,
path: route.fullPath,
});
}
};
setTags(route);
onBeforeRouteUpdate(to => {
setTags(to);
onBeforeRouteUpdate((to) => {
setTags(to);
});
//
const closeAll = () => {
tabs.clearTabs();
router.push('/');
tabs.clearTabs();
router.push('/');
};
//
const closeOther = () => {
const curItem = tabs.list.filter(item => {
return item.path === route.fullPath;
});
tabs.closeTabsOther(curItem);
const curItem = tabs.list.filter((item) => {
return item.path === route.fullPath;
});
tabs.closeTabsOther(curItem);
};
const handleTags = (command: string) => {
switch (command) {
case 'current':
//
tabs.closeCurrentTag({
$router: router,
$route: route
});
break;
case 'all':
closeAll();
break;
switch (command) {
case 'current':
//
tabs.closeCurrentTag({
$router: router,
$route: route,
});
break;
case 'all':
closeAll();
break;
case 'other':
closeOther();
break;
}
case 'other':
closeOther();
break;
}
};
const clickTabls = (item: any) => {
router.push(item.props.name);
}
const closeTabs = (path: any) => {
console.log(path);
const index = tabs.list.findIndex((item) => item.path === path);
tabs.delTabsItem(index);
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;
})
router.push(item.props.name);
};
const closeTabs = (path: string) => {
const index = tabs.list.findIndex((item) => item.path === path);
tabs.delTabsItem(index);
const item = tabs.list[index] || tabs.list[index - 1];
router.push(item ? item.path : '/');
};
watch(
() => route.fullPath,
(newVal, oldVal) => {
activePath.value = newVal;
}
);
</script>
<style scss>
.tabs-container {
position: relative;
overflow: hidden;
background: #fff;
padding: 2px 120px 0 0;
position: relative;
overflow: hidden;
background: #fff;
padding: 2px 120px 0 0;
}
.tabs {
.el-tabs__header {
margin-bottom: 0;
}
.el-tabs__header {
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 {
height: 28px;
}
.el-tabs__nav-next,
.el-tabs__nav-prev {
line-height: 32px;
}
&.el-tabs {
--el-tabs-header-height: 28px;
}
}
.Tabs-close-box {
position: absolute;
right: 0;
top: 0;
box-sizing: border-box;
padding-top: 1px;
text-align: center;
width: 110px;
height: 30px;
background: #fff;
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
z-index: 10;
position: absolute;
right: 0;
top: 0;
box-sizing: border-box;
padding-top: 1px;
text-align: center;
width: 110px;
height: 30px;
background: #fff;
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
z-index: 10;
}
</style>

View File

@ -19,7 +19,7 @@ const routes: RouteRecordRaw[] = [
name: 'dashboard',
meta: {
title: '系统首页',
permiss: '0',
noAuth: true,
},
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
},
@ -273,12 +273,12 @@ const router = createRouter({
router.beforeEach((to, from, next) => {
NProgress.start();
const role = localStorage.getItem('ms_username');
const role = localStorage.getItem('vuems_name');
const permiss = usePermissStore();
if (!role && to.meta.noAuth !== true) {
next('/login');
} else if (to.meta.permiss && !permiss.key.includes(to.meta.permiss)) {
} else if (typeof to.meta.permiss == 'string' && !permiss.key.includes(to.meta.permiss)) {
// 如果没有权限则进入403
next('/403');
} else {

View File

@ -6,48 +6,50 @@ interface ObjectList {
export const usePermissStore = defineStore('permiss', {
state: () => {
const keys = localStorage.getItem('ms_keys');
const 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'],
};
const username = localStorage.getItem('vuems_name');
console.log(username);
return {
key: keys ? JSON.parse(keys) : <string[]>[],
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'],
},
key: (username == 'admin' ? defaultList.admin : defaultList.user) as string[],
defaultList,
};
},
actions: {

View File

@ -1,18 +1,20 @@
<template>
<v-header />
<v-sidebar />
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
<v-tabs></v-tabs>
<div class="content">
<router-view v-slot="{ Component }">
<transition name="move" mode="out-in">
<keep-alive :include="tabs.nameList">
<component :is="Component"></component>
</keep-alive>
</transition>
</router-view>
</div>
</div>
<div class="wrapper">
<v-header />
<v-sidebar />
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
<v-tabs></v-tabs>
<div class="content">
<router-view v-slot="{ Component }">
<transition name="move" mode="out-in">
<keep-alive :include="tabs.nameList">
<component :is="Component"></component>
</keep-alive>
</transition>
</router-view>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useSidebarStore } from '@/store/sidebar';
@ -26,31 +28,36 @@ const tabs = useTabsStore();
</script>
<style>
.wrapper {
height: 100vh;
overflow: hidden;
}
.content-box {
position: absolute;
left: 250px;
right: 0;
top: 70px;
bottom: 0;
padding-bottom: 30px;
-webkit-transition: left 0.3s ease-in-out;
transition: left 0.3s ease-in-out;
background: #eef0fc;
position: absolute;
left: 250px;
right: 0;
top: 70px;
bottom: 0;
padding-bottom: 30px;
-webkit-transition: left 0.3s ease-in-out;
transition: left 0.3s ease-in-out;
background: #eef0fc;
overflow: hidden;
}
.content {
width: auto;
height: 100%;
padding: 20px;
overflow-y: scroll;
box-sizing: border-box;
width: auto;
height: 100%;
padding: 20px;
overflow-y: scroll;
box-sizing: border-box;
}
.content::-webkit-scrollbar {
width: 0;
width: 0;
}
.content-collapse {
left: 65px;
left: 65px;
}
</style>
</style>

View File

@ -1,16 +1,16 @@
<template>
<div class="error-page">
<div class="error-box">
<div class="error-code">403</div>
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack"></el-button>
</div>
</div>
</div>
<div class="error-page">
<div class="error-box">
<div class="error-code">403</div>
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack"></el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="403">
@ -18,50 +18,50 @@ import { useRouter } from 'vue-router';
const router = useRouter();
const goBack = () => {
router.go(-2);
router.go(-2);
};
</script>
<style scoped>
.error-page {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
background: #eef0fc;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
background: #eef0fc;
box-sizing: border-box;
}
.error-box {
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
}
.error-code {
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center;
}
.error-desc {
font-size: 20px;
color: #777;
text-align: center
font-size: 20px;
color: #777;
text-align: center;
}
.error-handle {
margin-top: 50px;
text-align: center;
margin-top: 50px;
text-align: center;
}
.error-btn {
margin-left: 100px;
margin-left: 100px;
}
</style>

View File

@ -1,16 +1,16 @@
<template>
<div class="error-page">
<div class="error-box">
<div class="error-code">404</div>
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack"></el-button>
</div>
</div>
</div>
<div class="error-page">
<div class="error-box">
<div class="error-code">404</div>
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack"></el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="404">
@ -18,50 +18,50 @@ import { useRouter } from 'vue-router';
const router = useRouter();
const goBack = () => {
router.go(-1);
router.go(-1);
};
</script>
<style scoped>
.error-page {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
background: #eef0fc;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
background: #eef0fc;
box-sizing: border-box;
}
.error-box {
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
}
.error-code {
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center;
}
.error-desc {
font-size: 20px;
color: #777;
text-align: center
font-size: 20px;
color: #777;
text-align: center;
}
.error-handle {
margin-top: 50px;
text-align: center;
margin-top: 50px;
text-align: center;
}
.error-btn {
margin-left: 100px;
margin-left: 100px;
}
</style>

View File

@ -83,10 +83,9 @@ const submitForm = (formEl: FormInstance | undefined) => {
formEl.validate((valid: boolean) => {
if (valid) {
ElMessage.success('登录成功');
localStorage.setItem('ms_username', param.username);
localStorage.setItem('vuems_name', param.username);
const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
permiss.handleSet(keys);
localStorage.setItem('ms_keys', JSON.stringify(keys));
router.push('/');
if (checked.value) {
localStorage.setItem('login-param', JSON.stringify(param));
@ -110,7 +109,7 @@ tabs.clearTabs();
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}

View File

@ -2,10 +2,8 @@
<div class="login-bg">
<div class="login-container">
<div class="login-header">
<img class="logo mr10" src="../../assets/img/logo.svg" alt="">
<div class="login-title">
后台管理系统
</div>
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
<div class="login-title">后台管理系统</div>
</div>
<el-form :model="param" :rules="rules" ref="register" size="large">
<el-form-item prop="username">
@ -27,8 +25,12 @@
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" placeholder="密码" v-model="param.password"
@keyup.enter="submitForm(register)">
<el-input
type="password"
placeholder="密码"
v-model="param.password"
@keyup.enter="submitForm(register)"
>
<template #prepend>
<el-icon>
<Lock />
@ -37,7 +39,9 @@
</el-input>
</el-form-item>
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)"></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>
</div>
</div>
@ -79,7 +83,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
}
});
};
</script>
<style scoped>
@ -88,7 +91,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}

View File

@ -13,7 +13,9 @@
</template>
</el-input>
</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"><el-link type="primary" @click="$router.push('/login')"></el-link></p>
</el-form>
</div>
@ -31,7 +33,11 @@ const param = ref({
const rules: FormRules = {
email: [
{ 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>();
@ -45,7 +51,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
}
});
};
</script>
<style scoped>
@ -54,7 +59,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}

View File

@ -104,7 +104,7 @@ 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 name = localStorage.getItem('vuems_name');
const form = reactive({
new1: '',
new: '',

View File

@ -1,253 +0,0 @@
<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>