This commit is contained in:
zjl
2021-10-03 10:11:06 +08:00
commit 94d899dfc4
20 changed files with 1932 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
<template>
<el-container class="layout">
<el-header class="layout-header">
<div class="main-title">博客API管理后台</div>
<div class="nav-btns-right">
<el-dropdown @command="dropdownMenuCommand">
<el-button type="text">
<i class="el-icon-user-solid"></i>
{{ realname }}
<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="home">返回首页</el-dropdown-item>
<el-dropdown-item command="changePassword">修改密码</el-dropdown-item>
<el-dropdown-item command="logout" divided>退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-container>
<el-aside width="200px">
<el-menu :default-active="activeMenuItem" :default-openeds="openMenuNames" style="height: 100%;">
<el-sub-menu v-for="menu in menus" :key="menu.name" :index="menu.name">
<template #title>
<i :class="menu.icon"></i>
<span>{{menu.title}}</span>
</template>
<el-menu-item v-for="(subItem,subIndex) in menu.child" :key="subIndex" :index="subItem.path" @click="openMenu(subItem.path)">
{{subItem.title}}
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<el-main class="layout-main">
<div class="layout-breadcrumb">
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="(item,index) in $store.state.breadcrumb" :key="index">{{item}}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="layout-content">
<router-view class="main-view"></router-view>
</div>
<div class="layout-copy">2016-{{currentYear}} &copy; colorfulsweet</div>
</el-main>
</el-container>
</el-container>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import { ElContainer, ElHeader, ElMain, ElAside, ElDropdown, ElDropdownMenu, ElDropdownItem, ElButton, ElMenu, ElSubMenu, ElMenuItem, ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'
import menus from '../config/menu'
@Options({
name: 'Home',
components: { ElContainer, ElHeader, ElMain, ElAside, ElDropdown, ElDropdownMenu, ElDropdownItem, ElButton, ElMenu, ElSubMenu, ElMenuItem, ElBreadcrumb, ElBreadcrumbItem }
})
export default class Home extends Vue{
private currentYear = new Date().getFullYear()
// 菜单项
private menus = menus
private activeMenuItem: string | null = null
private openMenuNames: string[] = []
get realname(): string { // 当前用户的显示名称
return this.$store.state.loginInfo.userInfo
? this.$store.state.loginInfo.userInfo.realname : null
}
async created(): Promise<void> {
this.activeMenuItem = this.$route.path
if(this.activeMenuItem) {
let result = /^\/(.*)\//.exec(this.activeMenuItem)
if(result) {
this.openMenuNames.push(result[1])
}
}
if(!localStorage.getItem('login_token')) {
this.$router.push('/login')
return
}
const { data } = await this.$http.post('/api/common/verifyToken', {token: localStorage.getItem('login_token')})
if(data.status) {
// 如果是已过期的token 服务端会签发新的token
this.$store.commit('login', {token: data.newToken || localStorage.getItem('login_token'), userInfo: data.userInfo})
} else {
this.$router.push('/login')
}
}
dropdownMenuCommand(command: string): void {
switch(command) {
case 'home': // 返回首页
this.$router.push('/')
break
case 'changePassword': // 修改密码
// TODO
break
case 'logout': //注销
this.$store.commit('logout')
this.$router.push('/login')
break
}
}
openMenu(path: string): void {
this.$router.push(path)
}
}
</script>
+94
View File
@@ -0,0 +1,94 @@
<template>
<div class="login-wrapper">
<h2 class="title">博客API管理后台</h2>
<el-form ref="loginForm" :model="userInfo" :rules="ruleValidate" :label-width="80">
<el-form-item label="用户名" prop="username">
<el-input v-model="userInfo.username" @on-enter="login"/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="userInfo.password" type="password" @on-enter="login" />
</el-form-item>
</el-form>
<div class="login-btn">
<el-button type="primary" @click="login">登录</el-button>
<el-tooltip content="无需账号密码,只有查询权限" placement="bottom">
<el-button @click="guestLogin">访客模式</el-button>
</el-tooltip>
</div>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import { ref } from 'vue'
import { VForm } from "../types"
import { ElMessage, ElButton, ElForm, ElFormItem, ElInput, ElTooltip } from 'element-plus'
import { AxiosResponse } from 'axios'
@Options({
name: 'Login',
components: { ElButton, ElForm, ElFormItem, ElInput, ElTooltip }
})
export default class Login extends Vue {
private readonly loginForm: VForm = ref('loginForm')
private userInfo: UserInfo = {
username: null,
password: null
}
private ruleValidate = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' }
],
}
/**
* 登录
*/
async login() {
this.loginForm.validate(async (valid: boolean) => {
if(!valid) return
const { data } = await this.$http.post<UserInfo, AxiosResponse<any>>('/api/common/login', this.userInfo)
if(data.token) {
this.$store.commit('login', data)
this.$router.push('/')
} else {
ElMessage.error(data.message)
}
})
}
/**
* 访客模式
*/
async guestLogin() {
const { data } = await this.$http.post<never, AxiosResponse<any>>('/api/common/guestLogin')
if (data.status && data.data.token) {
this.$store.commit('login', data.data)
this.$router.push('/')
} else {
ElMessage.error(data.message)
}
}
}
type UserInfo = {
username: string | null,
password: string | null
};
</script>
<style lang="less" scoped>
.login-wrapper {
width: 400px;
vertical-align: middle;
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
.title {
text-align: center;
margin-bottom: 30px;
}
.login-btn {
text-align: center;
}
}
</style>
+135
View File
@@ -0,0 +1,135 @@
<template>
<div>
<div class="clock-circle">
<div class="clock-face">
<div class="clock-hour" :style="{transform: `rotate(${clock.hourRotate}deg)`}" ></div>
<div class="clock-minute" :style="{transform: `rotate(${clock.minuteRotate}deg)`}"></div>
<div class="clock-second" :style="{transform: `rotate(${clock.secondRotate}deg)`}"></div>
</div>
</div>
<h2 class="time-text">{{timeText}}</h2>
<div class="nav-list">
<Row v-for="menu in menus" :key="menu.name">
<Col :span="3" class="nav-title">{{ menu.title }}</Col>
<Col :span="21">
<router-link :to="submenu.path" v-for="submenu in menu.child" :key="submenu.path" class="nav-item">
<Icon :type="submenu.icon" /> {{submenu.title}}
</router-link>
</Col>
</Row>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import moment from 'moment'
import menus from '../config/menu'
@Component({})
export default class Welcome extends Vue {
private menus = menus
private timeText!: string
private clock = {
hourRotate: 0,
minuteRotate: 0,
secondRotate: 0
}
private clockTimer!: number | null
created(): void {
const timedUpdate = (): void => {
this.updateClock()
this.clockTimer = setTimeout(timedUpdate, 1000)
}
timedUpdate()
}
beforeDestroy(): void {
if(this.clockTimer) {
clearTimeout(this.clockTimer)
this.clockTimer = null
}
}
updateClock(): void {
const now = moment()
this.timeText = now.format('YYYY年M月D日 HH:mm:ss')
this.clock.secondRotate = now.seconds() * 6
this.clock.minuteRotate = now.minutes() * 6 + this.clock.secondRotate / 60
this.clock.hourRotate = ((now.hours() % 12) / 12) * 360 + 90 + this.clock.minuteRotate / 12
}
}
</script>
<style lang="less" scoped>
.clock-circle {
width: 180px;
height: 180px;
margin: 0 auto;
position: relative;
border: 8px solid #000;
border-radius: 50%;
box-shadow: 0 1px 8px rgba(34,34,34,0.3),inset 0 1px 8px rgba(34,34,34,0.3);
}
.clock-face {
width: 100%;
height: 100%;
&::after {
position: absolute;
top: 50%;
left: 50%;
width: 12px;
height: 12px;
margin: -6px 0 0 -6px;
background: #000;
border-radius: 6px;
content: "";
display: block
}
}
.clock-hour,.clock-minute,.clock-second {
width: 0;
height: 0;
position: absolute;
top: 50%;
left: 50%;
background: #000;
}
.clock-hour {
margin: -4px 0 -4px -25%;
padding: 4px 0 4px 25%;
transform-origin: 100% 50%;
border-radius: 4px 0 0 4px
}
.clock-minute {
margin: -40% -3px 0;
padding: 40% 3px 0;
transform-origin: 50% 100%;
border-radius: 3px 3px 0 0
}
.clock-second {
margin: -40% -1px 0 0;
padding: 40% 1px 0;
transform-origin: 50% 100%
}
.time-text {
text-align: center;
}
.nav-list {
.nav-title {
font-size: 16px;
line-height: 66px;
text-align: right;
padding-right: 20px;
}
.nav-item {
display: inline-block;
width: 200px;
font-size: 16px;
margin: 10px;
border: 1px solid #ccc;
padding: 10px;
}
}
</style>