前端页面的搭建
使用vue create XXX创建vue项目
安装npm i element-ui -S
目录如下
所使用的资源文件如下
gloable.css
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.ml-5 {
margin-left: 5px;
}
.mr-5 {
margin-right: 5px;
}
.pd-10 {
padding: 10px 0;
}
.m-10 {
margin: 10px 0;
}
a {
text-decoration: none;
color: #666;
}
Aside.vue
<template>
<el-menu :default-openeds="opens" style="min-height: 100%; overflow-x: hidden"
background-color="rgb(48, 65, 86)"
text-color="#fff"
active-text-color="#ffd04b"
:collapse-transition="false"
:collapse="isCollapse"
router
>
<div style="height: 60px; line-height: 60px; text-align: center">
<img src="../assets/logo.png" alt="" style="width: 20px; position: relative; top: 5px;">
<b style="color: white; margin-left: 5px" v-show="logoTextShow">后台管理系统</b>
</div>
<div v-for="item in menus" :key="item.id">
<div v-if="item.path">
<el-menu-item :index="item.path">
<i :class="item.icon"></i>
<span slot="title">{{ item.name }}</span>
</el-menu-item>
</div>
<div v-else>
<el-submenu :index="item.id + ''">
<template slot="title">
<i :class="item.icon"></i>
<span slot="title">{{ item.name }}</span>
</template>
<div v-for="subItem in item.children" :key="subItem.id">
<el-menu-item :index="subItem.path">
<i :class="subItem.icon"></i>
<span slot="title">{{ subItem.name }}</span>
</el-menu-item>
</div>
</el-submenu>
</div>
</div>
</el-menu>
</template>
<script>
export default {
name: "Aside",
props: {
isCollapse: Boolean,
logoTextShow: Boolean
},
data() {
return {
menus: localStorage.getItem("menus") ? JSON.parse(localStorage.getItem("menus")) : [],
opens: localStorage.getItem("menus") ? JSON.parse(localStorage.getItem("menus")).map(v => v.id + '') : []
}
},
}
</script>
<style>
.el-menu-item.is-active {
background-color: rgb(38, 52, 69) !important;
}
.el-menu-item:hover {
background-color: rgb(38, 52, 69) !important;
}
.el-submenu__title:hover {
background-color: rgb(38, 52, 69) !important;
}
/*解决收缩菜单文字不消失问题*/
.el-menu--collapse span {
visibility: hidden;
}
</style>
Header.vue
<template>
<div style="line-height: 60px; display: flex">
<div style="flex: 1;">
<span :class="collapseBtnClass" style="cursor: pointer; font-size: 18px" @click="collapse"></span>
<el-breadcrumb separator="/" style="display: inline-block; margin-left: 10px">
<el-breadcrumb-item :to="'/'">首页</el-breadcrumb-item>
<el-breadcrumb-item>{{ currentPathName }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<el-dropdown style="width: 150px; cursor: pointer; text-align: right">
<div style="display: inline-block">
<img :src="user.avatarUrl" alt=""
style="width: 30px; border-radius: 50%; position: relative; top: 10px; right: 5px">
<span>{{ user.nickname }}</span><i class="el-icon-arrow-down" style="margin-left: 5px"></i>
</div>
<el-dropdown-menu slot="dropdown" style="width: 100px; text-align: center">
<el-dropdown-item style="font-size: 14px; padding: 5px 0">
<router-link to="/password">修改密码</router-link>
</el-dropdown-item>
<el-dropdown-item style="font-size: 14px; padding: 5px 0">
<router-link to="/person">个人信息</router-link>
</el-dropdown-item>
<el-dropdown-item style="font-size: 14px; padding: 5px 0">
<span style="text-decoration: none" @click="logout">退出</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default {
name: "Header",
props: {
collapseBtnClass: String,
user: Object
},
computed: {
currentPathName () {
return this.$store.state.currentPathName; //需要监听的数据
}
},
data() {
return {
}
},
methods: {
collapse() {
// this.$parent.$parent.$parent.$parent.collapse() // 通过4个 $parent 找到父组件,从而调用其折叠方法
this.$emit("asideCollapse")
},
logout() {
this.$store.commit("logout")
this.$message.success("退出成功")
}
}
}
</script>
<style scoped>
</style>
ValidCode.vue
<template>
<div class="ValidCode disabled-select" style="width: 100%; height: 100%" @click="refreshCode">
<span v-for="(item, index) in codeList" :key="index" :style="getStyle(item)">{{item.code}}</span>
</div>
</template>
<script>
export default {
name: 'validCode',
data () {
return {
length: 4,
codeList: []
}
},
mounted () {
this.createdCode()
},
methods: {
refreshCode () {
this.createdCode()
},
createdCode () {
let len = this.length,
codeList = [],
chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789',
charsLen = chars.length
// 生成
for (let i = 0; i < len; i++) {
let rgb = [Math.round(Math.random() * 220), Math.round(Math.random() * 240), Math.round(Math.random() * 200)]
codeList.push({
code: chars.charAt(Math.floor(Math.random() * charsLen)),
color: `rgb(${rgb})`,
padding: `${[Math.floor(Math.random() * 10)]}px`,
transform: `rotate(${Math.floor(Math.random() * 90) - Math.floor(Math.random() * 90)}deg)`
})
}
// 指向
this.codeList = codeList
// 将当前数据派发出去
this.$emit('update:value', codeList.map(item => item.code).join(''))
},
getStyle (data) {
return `color: ${data.color}; font-size: ${data.fontSize}; padding: ${data.padding}; transform: ${data.transform}`
}
}
}
</script>
<style>
.ValidCode{
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.ValidCode span {
display: inline-block;
font-size: 18px;
}
</style>
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from "@/store";
Vue.use(VueRouter)
const routes = [
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue')
},
{
path: '/register',
name: 'Register',
component: () => import('../views/Register.vue')
},
{
path: '/404',
name: '404',
component: () => import('../views/404.vue')
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
// 提供一个重置路由的方法
export const resetRouter = () => {
router.matcher = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
}
// 注意:刷新页面会导致页面路由重置
export const setRoutes = () => {
const storeMenus = localStorage.getItem("menus");
if (storeMenus) {
// 获取当前的路由对象名称数组
const currentRouteNames = router.getRoutes().map(v => v.name)
if (!currentRouteNames.includes('Manage')) {
// 拼装动态路由
const manageRoute = { path: '/', name: 'Manage', component: () => import('../views/Manage.vue'), redirect: "/home", children: [
{ path: 'person', name: '个人信息', component: () => import('../views/Person.vue')},
{ path: 'password', name: '修改密码', component: () => import('../views/Password.vue')}
] }
const menus = JSON.parse(storeMenus)
menus.forEach(item => {
if (item.path) { // 当且仅当path不为空的时候才去设置路由
let itemMenu = { path: item.path.replace("/", ""), name: item.name, component: () => import('../views/' + item.pagePath + '.vue')}
manageRoute.children.push(itemMenu)
} else if(item.children.length) {
item.children.forEach(item => {
if (item.path) {
let itemMenu = { path: item.path.replace("/", ""), name: item.name, component: () => import('../views/' + item.pagePath + '.vue')}
manageRoute.children.push(itemMenu)
}
})
}
})
// 动态添加到现在的路由对象中去
router.addRoute(manageRoute)
}
}
}
// 重置我就再set一次路由
setRoutes()
router.beforeEach((to, from, next) => {
localStorage.setItem("currentPathName", to.name) // 设置当前的路由名称
store.commit("setPath")
// 未找到路由的情况
if (!to.matched.length) {
const storeMenus = localStorage.getItem("menus")
if (storeMenus) {
next("/404")
} else {
// 跳回登录页面
next("/login")
}
}
// 其他的情况都放行
next()
})
export default router
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import router, {resetRouter} from "@/router";
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
currentPathName: ''
},
mutations: {
setPath (state) {
state.currentPathName = localStorage.getItem("currentPathName")
},
logout() {
// 清空缓存
localStorage.removeItem("user")
localStorage.removeItem("menus")
router.push("/login")
// 重置路由
resetRouter()
}
}
})
export default store
request.js
import axios from 'axios'
import router from "@/router";
import {serverIp} from "../../public/config";
const request = axios.create({
baseURL: `http://${serverIp}:9090`,
timeout: 30000
})
// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null
if (user) {
config.headers['token'] = user.token; // 设置请求头
}
return config
}, error => {
return Promise.reject(error)
});
// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
response => {
let res = response.data;
// 如果是返回的文件
if (response.config.responseType === 'blob') {
return res
}
// 兼容服务端返回的字符串数据
if (typeof res === 'string') {
res = res ? JSON.parse(res) : res
}
// 当权限验证不通过的时候给出提示
if (res.code === '401') {
// ElementUI.Message({
// message: res.msg,
// type: 'error'
// });
router.push("/login")
}
return res;
},
error => {
console.log('err' + error) // for debug
return Promise.reject(error)
}
)
export default request
404.vue
<template>
<div style="overflow: hidden; height: 100vh">
<img src="../assets/404.png" alt="" style="width: 100%; height: 100%">
</div>
</template>
<script>
export default {
name: "NotFound"
}
</script>
<style>
.bgImg {
background: url("../assets/404.png") no-repeat;
background-size: 100% 100vh;
}
</style>
Home.vue
<template>
<div style="color: #666;font-size: 14px;">
<div style="padding-bottom: 20px">
<b>欢迎你!{{ user.nickname }}</b>
</div>
<el-card>
主页
</el-card>
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}
}
}
}
</script>
Login.vue
<template>
<div style="height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #0f9876">
<div style="display: flex; background-color: white; width: 50%; border-radius: 5px; overflow: hidden">
<div style="flex: 1">
<img src="@/assets/login.png" alt="" style="width: 100%">
</div>
<div style="flex: 1; display: flex; align-items: center; justify-content: center">
<el-form :model="user" style="width: 80%" :rules="rules" ref="loginRef">
<div style="font-size: 20px; font-weight: bold; text-align: center; margin-bottom: 20px">欢迎登录后台管理系统</div>
<el-form-item prop="username">
<el-input prefix-icon="el-icon-user" size="medium" placeholder="请输入账号" v-model="user.username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input prefix-icon="el-icon-lock" size="medium" show-password placeholder="请输入密码" v-model="user.password"></el-input>
</el-form-item>
<el-form-item prop="code">
<div style="display: flex">
<el-input placeholder="请输入验证码" prefix-icon="el-icon-circle-check" size="medium" style="flex: 1" v-model="user.code"></el-input>
<div style="flex: 1; height: 36px">
<valid-code @update:value="getCode" />
</div>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width: 100%" @click="login">登 录</el-button>
</el-form-item>
<div style="display: flex">
<div style="flex: 1">还没有账号?请 <span style="color: #0f9876; cursor: pointer" @click="$router.push('/register')">注册</span></div>
<div style="flex: 1; text-align: right"><span style="color: #0f9876; cursor: pointer">忘记密码</span></div>
</div>
</el-form>
</div>
</div>
</div>
</template>
<script>
import ValidCode from "@/components/ValidCode.vue";
import user from "@/views/User";
import {setRoutes} from "@/router";
export default {
name: "Login",
components: {
ValidCode
},
data() {
// 验证码校验
const validateCode = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入验证码'))
} else if (value.toLowerCase() !== this.code) {
callback(new Error('验证码错误'))
} else {
callback()
}
}
return {
code: '', // 验证码组件传递过来的code
user: {
code: '', // 表单里用户输入的code 验证码
username: '',
password: ''
},
rules: {
username: [
{ required: true, message: '请输入账号', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
],
code: [
{ validator: validateCode, trigger: 'blur' }
],
}
}
},
created() {
},
methods: {
getCode(code) {
console.log(code)
this.code = code.toLowerCase()
},
login() {
this.$refs['loginRef'].validate((valid) => {
if (valid) {
// 验证通过
this.request.post('/user/login', this.user).then(res => {
if (res.code === '200') {
localStorage.setItem("user", JSON.stringify(res.data)) // 存储用户数据
localStorage.setItem("menus", JSON.stringify(res.data.menus)) // 存储用户数据
setRoutes()
this.$message.success('登录成功')
this.$router.push('/')
} else {
this.$message.error(res.msg)
}
})
}
})
}
}
}
</script>
<style scoped>
</style>
Manage.vue
<template>
<el-container style="min-height: 100vh">
<el-aside :width="sideWidth + 'px'" style="box-shadow: 2px 0 6px rgb(0 21 41 / 35%);">
<Aside :isCollapse="isCollapse" :logoTextShow="logoTextShow" style="padding-bottom: 20px" />
</el-aside>
<el-container>
<el-header style="border-bottom: 1px solid #ccc;">
<Header :collapseBtnClass="collapseBtnClass" @asideCollapse="collapse" :user="user" />
</el-header>
<el-main>
<!-- 表示当前页面的子路由会在 <router-view /> 里面展示-->
<router-view @refreshUser="getUser" />
</el-main>
</el-container>
</el-container>
</template>
<script>
import Aside from "@/components/Aside";
import Header from "@/components/Header";
export default {
name: 'Home',
data() {
return {
collapseBtnClass: 'el-icon-s-fold',
isCollapse: false,
sideWidth: 200,
logoTextShow: true,
user: {}
}
},
components: {
Aside,
Header
},
created() {
// 从后台获取最新的User数据
this.getUser()
},
methods: {
collapse() { // 点击收缩按钮触发
this.isCollapse = !this.isCollapse
if (this.isCollapse) { // 收缩
this.sideWidth = 64
this.collapseBtnClass = 'el-icon-s-unfold'
this.logoTextShow = false
} else { // 展开
this.sideWidth = 200
this.collapseBtnClass = 'el-icon-s-fold'
this.logoTextShow = true
}
},
getUser() {
let username = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")).username : ""
if (username) {
// 从后台获取User数据
this.request.get("/user/username/" + username).then(res => {
// 重新赋值后台的最新User数据
this.user = res.data
})
}
}
}
}
</script>
Menu.vue
<template>
<div>
<div style="margin: 10px 0">
<el-input style="width: 200px" placeholder="请输入名称" suffix-icon="el-icon-search" v-model="name"></el-input>
<!-- <el-input style="width: 200px" placeholder="请输入邮箱" suffix-icon="el-icon-message" class="ml-5" v-model="email"></el-input>-->
<!-- <el-input style="width: 200px" placeholder="请输入地址" suffix-icon="el-icon-position" class="ml-5" v-model="address"></el-input>-->
<el-button class="ml-5" type="primary" @click="load">搜索</el-button>
<el-button type="warning" @click="reset">重置</el-button>
</div>
<div style="margin: 10px 0">
<el-button type="primary" @click="handleAdd(null)">新增 <i class="el-icon-circle-plus-outline"></i></el-button>
<el-popconfirm
class="ml-5"
confirm-button-text='确定'
cancel-button-text='我再想想'
icon="el-icon-info"
icon-color="red"
title="您确定批量删除这些数据吗?"
@confirm="delBatch"
>
<el-button type="danger" slot="reference">批量删除 <i class="el-icon-remove-outline"></i></el-button>
</el-popconfirm>
<!-- <el-upload action="http://localhost:9090/user/import" :show-file-list="false" accept="xlsx" :on-success="handleExcelImportSuccess" style="display: inline-block">-->
<!-- <el-button type="primary" class="ml-5">导入 <i class="el-icon-bottom"></i></el-button>-->
<!-- </el-upload>-->
<!-- <el-button type="primary" @click="exp" class="ml-5">导出 <i class="el-icon-top"></i></el-button>-->
</div>
<el-table :data="tableData" border stripe :header-cell-class-name="'headerBg'"
row-key="id" default-expand-all @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="name" label="名称"></el-table-column>
<el-table-column prop="path" label="路径"></el-table-column>
<el-table-column prop="pagePath" label="页面路径"></el-table-column>
<el-table-column label="图标" class-name="fontSize18" align="center" label-class-name="fontSize12">
<template slot-scope="scope">
<span :class="scope.row.icon" />
</template>
</el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column prop="sortnum" label="顺序"></el-table-column>
<el-table-column label="操作" width="300" align="center">
<template slot-scope="scope">
<el-button type="primary" @click="handleAdd(scope.row.id)" v-if="!scope.row.pid && !scope.row.path">新增子菜单 <i class="el-icon-plus"></i></el-button>
<el-button type="success" @click="handleEdit(scope.row)">编辑 <i class="el-icon-edit"></i></el-button>
<el-popconfirm
class="ml-5"
confirm-button-text='确定'
cancel-button-text='我再想想'
icon="el-icon-info"
icon-color="red"
title="您确定删除吗?"
@confirm="del(scope.row.id)"
>
<el-button type="danger" slot="reference">删除 <i class="el-icon-remove-outline"></i></el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-dialog title="菜单信息" :visible.sync="dialogFormVisible" width="30%" >
<el-form label-width="80px" size="small">
<el-form-item label="名称">
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="路径">
<el-input v-model="form.path" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="页面路径">
<el-input v-model="form.pagePath" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="图标">
<el-select clearable v-model="form.icon" placeholder="请选择" style="width: 100%">
<el-option v-for="item in options" :key="item.name" :label="item.name" :value="item.value">
<i :class="item.value" /> {{ item.name }}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="顺序">
<el-input v-model="form.sortnum" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {serverIp} from "../../public/config";
export default {
name: "Menu",
data() {
return {
tableData: [],
total: 0,
pageNum: 1,
pageSize: 10,
name: "",
form: {},
dialogFormVisible: false,
multipleSelection: [],
options: []
}
},
created() {
this.load()
},
methods: {
load() {
this.request.get("/menu", {
params: {
name: this.name,
}
}).then(res => {
console.log(res)
this.tableData = res.data
})
// 请求图标的数据
this.request.get("/menu/icons").then(res => {
this.options = res.data
})
},
save() {
this.request.post("/menu", this.form).then(res => {
if (res.code === '200') {
this.$message.success("保存成功")
this.dialogFormVisible = false
this.load()
} else {
this.$message.error("保存失败")
}
})
},
handleAdd(pid) {
this.dialogFormVisible = true
this.form = {}
if (pid) {
this.form.pid = pid
}
},
handleEdit(row) {
this.form = JSON.parse(JSON.stringify(row))
this.dialogFormVisible = true
},
del(id) {
this.request.delete("/menu/" + id).then(res => {
if (res.code === '200') {
this.$message.success("删除成功")
this.load()
} else {
this.$message.error("删除失败")
}
})
},
handleSelectionChange(val) {
console.log(val)
this.multipleSelection = val
},
delBatch() {
let ids = this.multipleSelection.map(v => v.id) // [{}, {}, {}] => [1,2,3]
this.request.post("/menu/del/batch", ids).then(res => {
if (res.code === '200') {
this.$message.success("批量删除成功")
this.load()
} else {
this.$message.error("批量删除失败")
}
})
},
reset() {
this.name = ""
this.load()
},
handleSizeChange(pageSize) {
console.log(pageSize)
this.pageSize = pageSize
this.load()
},
handleCurrentChange(pageNum) {
console.log(pageNum)
this.pageNum = pageNum
this.load()
},
exp() {
window.open(`http://${serverIp}:9090/role/export`)
},
handleExcelImportSuccess() {
this.$message.success("导入成功")
this.load()
}
}
}
</script>
<style>
.headerBg {
background: #eee!important;
}
.fontSize18{
font-size: 18px;
}
.fontSize12{
font-size: 12px;
}
</style>
Password.vue
<template>
<el-card style="width: 500px;">
<el-form label-width="120px" size="small" :model="form" :rules="rules" ref="pass">
<el-form-item label="原密码" prop="password">
<el-input v-model="form.password" autocomplete="off" show-password></el-input>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="form.newPassword" autocomplete="off" show-password></el-input>
</el-form-item>
<el-form-item label="确认新密码" prop="confirmPassword">
<el-input v-model="form.confirmPassword" autocomplete="off" show-password></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save">确 定</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
export default {
name: "Password",
data() {
return {
form: {},
user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {},
rules: {
password: [
{ required: true, message: '请输入原密码', trigger: 'blur' },
{ min: 3, message: '长度不少于3位', trigger: 'blur' }
],
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 3, message: '长度不少于3位', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 3, message: '长度不少于3位', trigger: 'blur' }
],
}
}
},
created() {
this.form.username = this.user.username
},
methods: {
save() {
this.$refs.pass.validate((valid) => {
if (valid) {
if (this.form.newPassword !== this.form.confirmPassword) {
this.$message.error("2次输入的新密码不相同")
return false
}
this.request.post("/user/password", this.form).then(res => {
if (res.code === '200') {
this.$message.success("修改成功")
this.$store.commit("logout")
} else {
this.$message.error(res.msg)
}
})
}
})
},
}
}
</script>
<style>
.avatar-uploader {
text-align: center;
padding-bottom: 10px;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 138px;
height: 138px;
line-height: 138px;
text-align: center;
}
.avatar {
width: 138px;
height: 138px;
display: block;
}
</style>
Person.vue
<template>
<el-card style="width: 500px;">
<el-form label-width="80px" size="small">
<el-upload
class="avatar-uploader"
:action="'http://' + serverIp +':9090/file/upload'"
:show-file-list="false"
:on-success="handleAvatarSuccess"
>
<img v-if="form.avatarUrl" :src="form.avatarUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<el-form-item label="用户名">
<el-input v-model="form.username" disabled autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="昵称">
<el-input v-model="form.nickname" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="form.email" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="电话">
<el-input v-model="form.phone" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="地址">
<el-input type="textarea" v-model="form.address" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save">确 定</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
import {serverIp} from "../../public/config";
export default {
name: "Person",
data() {
return {
serverIp: serverIp,
form: {},
user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}
}
},
created() {
this.getUser().then(res => {
console.log(res)
this.form = res
})
},
methods: {
async getUser() {
return (await this.request.get("/user/username/" + this.user.username)).data
},
save() {
this.request.post("/user", this.form).then(res => {
if (res.code === '200') {
this.$message.success("保存成功")
// 触发父级更新User的方法
this.$emit("refreshUser")
// 更新浏览器存储的用户信息
this.getUser().then(res => {
res.token = JSON.parse(localStorage.getItem("user")).token
localStorage.setItem("user", JSON.stringify(res))
})
} else {
this.$message.error("保存失败")
}
})
},
handleAvatarSuccess(res) {
this.form.avatarUrl = res
}
}
}
</script>
<style>
.avatar-uploader {
text-align: center;
padding-bottom: 10px;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 138px;
height: 138px;
line-height: 138px;
text-align: center;
}
.avatar {
width: 138px;
height: 138px;
display: block;
}
</style>
Register.vue
<template>
<div style="height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #669fef">
<div style="display: flex; background-color: white; width: 50%; border-radius: 5px; overflow: hidden">
<div style="flex: 1">
<img src="@/assets/register.png" alt="" style="width: 100%">
</div>
<div style="flex: 1; display: flex; align-items: center; justify-content: center">
<el-form :model="user" style="width: 80%" :rules="rules" ref="registerRef">
<div style="font-size: 20px; font-weight: bold; text-align: center; margin-bottom: 20px">欢迎注册后台管理系统</div>
<el-form-item prop="username">
<el-input prefix-icon="el-icon-user" size="medium" placeholder="请输入账号" v-model="user.username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input prefix-icon="el-icon-lock" size="medium" show-password placeholder="请输入密码" v-model="user.password"></el-input>
</el-form-item>
<el-form-item prop="confirmPass">
<el-input prefix-icon="el-icon-lock" size="medium" show-password placeholder="请确认密码" v-model="user.confirmPass"></el-input>
</el-form-item>
<el-form-item>
<el-button type="info" style="width: 100%" @click="register">注 册</el-button>
</el-form-item>
<div style="display: flex">
<div style="flex: 1">已经有账号了?请 <span style="color: #6e77f2; cursor: pointer" @click="$router.push('/login')">登录</span></div>
</div>
</el-form>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Register",
data() {
// 验证码校验
const validatePassword = (rule, confirmPass, callback) => {
if (confirmPass === '') {
callback(new Error('请确认密码'))
} else if (confirmPass !== this.user.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
}
return {
user: {
username: '',
password: '',
confirmPass: ''
},
rules: {
username: [
{ required: true, message: '请输入账号', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
],
confirmPass: [
{ validator: validatePassword, trigger: 'blur' }
],
}
}
},
created() {
},
methods: {
register() {
this.$refs['registerRef'].validate((valid) => {
if (valid) {
// 验证通过
this.$request.post('/register', this.user).then(res => {
if (res.code === '200') {
this.$router.push('/login')
this.$message.success('注册成功')
} else {
this.$message.error(res.msg)
}
})
}
})
}
}
}
</script>
<style scoped>
</style>
Role.vue
<template>
<div>
<div style="margin: 10px 0">
<el-input style="width: 200px" placeholder="请输入名称" suffix-icon="el-icon-search" v-model="name"></el-input>
<el-button class="ml-5" type="primary" @click="load">搜索</el-button>
<el-button type="warning" @click="reset">重置</el-button>
</div>
<div style="margin: 10px 0">
<el-button type="primary" @click="handleAdd">新增 <i class="el-icon-circle-plus-outline"></i></el-button>
<el-popconfirm
class="ml-5"
confirm-button-text='确定'
cancel-button-text='我再想想'
icon="el-icon-info"
icon-color="red"
title="您确定批量删除这些数据吗?"
@confirm="delBatch"
>
</el-popconfirm>
</div>
<el-table :data="tableData" border stripe :header-cell-class-name="'headerBg'" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="name" label="名称"></el-table-column>
<el-table-column prop="flag" label="唯一标识"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column label="操作" width="280" align="center">
<template slot-scope="scope">
<el-button type="info" @click="selectMenu(scope.row)">分配菜单 <i class="el-icon-menu"></i></el-button>
<el-button type="success" @click="handleEdit(scope.row)">编辑 <i class="el-icon-edit"></i></el-button>
<el-popconfirm
class="ml-5"
confirm-button-text='确定'
cancel-button-text='我再想想'
icon="el-icon-info"
icon-color="red"
title="您确定删除吗?"
@confirm="del(scope.row.id)"
>
<el-button type="danger" slot="reference">删除 <i class="el-icon-remove-outline"></i></el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div style="padding: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[2, 5, 10, 20]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<el-dialog title="角色信息" :visible.sync="dialogFormVisible" width="30%" >
<el-form label-width="80px" size="small">
<el-form-item label="名称">
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="唯一标识">
<el-input v-model="form.flag" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
<el-dialog title="菜单分配" :visible.sync="menuDialogVis" width="30%">
<el-tree
:props="props"
:data="menuData"
show-checkbox
node-key="id"
ref="tree"
:default-expanded-keys="expends"
:default-checked-keys="checks">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span><i :class="data.icon"></i> {{ data.name }}</span>
</span>
</el-tree>
<div slot="footer" class="dialog-footer">
<el-button @click="menuDialogVis = false">取 消</el-button>
<el-button type="primary" @click="saveRoleMenu">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: "Role",
data() {
return {
tableData: [],
total: 0,
pageNum: 1,
pageSize: 10,
name: "",
form: {},
dialogFormVisible: false,
menuDialogVis: false,
multipleSelection: [],
menuData: [],
props: {
label: 'name',
},
expends: [],
checks: [],
roleId: 0,
roleFlag: '',
ids: []
}
},
created() {
this.load()
},
methods: {
load() {
this.request.get("/role/page", {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
name: this.name,
}
}).then(res => {
this.tableData = res.data.data
this.total = res.data.total
})
this.request.get("/menu/ids").then(r => {
this.ids = r.data
})
},
save() {
this.request.post("/role", this.form).then(res => {
if (res.code === '200') {
this.$message.success("保存成功")
this.dialogFormVisible = false
this.load()
} else {
this.$message.error("保存失败")
}
})
},
saveRoleMenu() {
this.request.post("/role/roleMenu/" + this.roleId, this.$refs.tree.getCheckedKeys()).then(res => {
if (res.code === '200') {
this.$message.success("绑定成功")
this.menuDialogVis = false
// 操作管理员角色后需要重新登录
if (this.roleFlag === 'ROLE_ADMIN') {
this.$store.commit("logout")
}
} else {
this.$message.error(res.msg)
}
})
},
handleAdd() {
this.dialogFormVisible = true
this.form = {}
},
handleEdit(row) {
this.form = JSON.parse(JSON.stringify(row))
this.dialogFormVisible = true
},
del(id) {
this.request.delete("/role/" + id).then(res => {
if (res.code === '200') {
this.$message.success("删除成功")
this.load()
} else {
this.$message.error("删除失败")
}
})
},
handleSelectionChange(val) {
console.log(val)
this.multipleSelection = val
},
delBatch() {
let ids = this.multipleSelection.map(v => v.id) // [{}, {}, {}] => [1,2,3]
this.request.post("/role/del/batch", ids).then(res => {
if (res.code === '200') {
this.$message.success("批量删除成功")
this.load()
} else {
this.$message.error("批量删除失败")
}
})
},
reset() {
this.name = ""
this.load()
},
handleSizeChange(pageSize) {
console.log(pageSize)
this.pageSize = pageSize
this.load()
},
handleCurrentChange(pageNum) {
console.log(pageNum)
this.pageNum = pageNum
this.load()
},
async selectMenu(role) {
this.roleId = role.id
this.roleFlag = role.flag
this.load2()
},
load2(){
// 请求菜单数据
this.request.get("/menu").then(res => {
this.menuData = res.data
// 把后台返回的菜单数据处理成 id数组
this.expends = this.menuData.map(v => v.id)
})
this.request.get("/role/roleMenu/" + this.roleId).then(res => {
this.checks = res.data
this.ids.forEach(id => {
if (!this.checks.includes(id)) {
// 可能会报错:Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'setChecked')
this.$nextTick(() => {
this.$refs.tree.setChecked(id, false)
})
}
})
this.menuDialogVis = true
})
},
}
}
</script>
<style>
.headerBg {
background: #eee!important;
}
</style>
User.vue
<template>
<div>
<div style="margin: 10px 0">
<el-input style="width: 200px" placeholder="请输入用户名" suffix-icon="el-icon-search" v-model="username"></el-input>
<el-input style="width: 200px" placeholder="请输入邮箱" suffix-icon="el-icon-message" class="ml-5" v-model="email"></el-input>
<el-input style="width: 200px" placeholder="请输入地址" suffix-icon="el-icon-position" class="ml-5" v-model="address"></el-input>
<el-button class="ml-5" type="primary" @click="load">搜索</el-button>
<el-button type="warning" @click="reset">重置</el-button>
</div>
<div style="margin: 10px 0">
<el-button type="primary" @click="handleAdd">新增 <i class="el-icon-circle-plus-outline"></i></el-button>
<el-popconfirm
class="ml-5"
confirm-button-text='确定'
cancel-button-text='我再想想'
icon="el-icon-info"
icon-color="red"
title="您确定批量删除这些数据吗?"
@confirm="delBatch"
>
<el-button type="danger" slot="reference">批量删除 <i class="el-icon-remove-outline"></i></el-button>
</el-popconfirm>
<el-upload :action="'http://' + serverIp + ':9090/user/import'" :show-file-list="false" accept="xlsx" :on-success="handleExcelImportSuccess" style="display: inline-block">
<el-button type="primary" class="ml-5">导入 <i class="el-icon-bottom"></i></el-button>
</el-upload>
<el-button type="primary" @click="exp" class="ml-5">导出 <i class="el-icon-top"></i></el-button>
</div>
<el-table :data="tableData" border stripe :header-cell-class-name="'headerBg'" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="username" label="用户名" width="140"></el-table-column>
<el-table-column prop="role" label="角色">
<template slot-scope="scope">
<el-tag type="primary" v-if="scope.row.role === 'ROLE_ADMIN'">管理员</el-tag>
<el-tag type="warning" v-if="scope.row.role === 'ROLE_TEACHER'">老师</el-tag>
<el-tag type="success" v-if="scope.row.role === 'ROLE_STUDENT'">学生</el-tag>
</template>
</el-table-column>
<el-table-column prop="nickname" label="昵称" width="120"></el-table-column>
<el-table-column prop="email" label="邮箱"></el-table-column>
<el-table-column prop="phone" label="电话"></el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
<el-table-column label="操作" width="500" align="center">
<template slot-scope="scope">
<el-button type="primary" @click="lookCourse(scope.row.courses)" v-if="scope.row.role === 'ROLE_TEACHER'">查看教授课程 <i class="el-icon-document"></i></el-button>
<el-button type="warning" @click="lookStuCourse(scope.row.stuCourses)" v-if="scope.row.role === 'ROLE_STUDENT'">查看已选课程 <i class="el-icon-document"></i></el-button>
<el-button type="success" @click="handleEdit(scope.row)">编辑 <i class="el-icon-edit"></i></el-button>
<el-popconfirm
class="ml-5"
confirm-button-text='确定'
cancel-button-text='我再想想'
icon="el-icon-info"
icon-color="red"
title="您确定删除吗?"
@confirm="del(scope.row.id)"
>
<el-button type="danger" slot="reference">删除 <i class="el-icon-remove-outline"></i></el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div style="padding: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[2, 5, 10, 20]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<el-dialog title="用户信息" :visible.sync="dialogFormVisible" width="30%" >
<el-form label-width="80px" size="small">
<el-form-item label="用户名">
<el-input v-model="form.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="角色">
<el-select clearable v-model="form.role" placeholder="请选择角色" style="width: 100%">
<el-option v-for="item in roles" :key="item.name" :label="item.name" :value="item.flag"></el-option>
</el-select>
</el-form-item>
<el-form-item label="昵称">
<el-input v-model="form.nickname" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="form.email" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="电话">
<el-input v-model="form.phone" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="地址">
<el-input v-model="form.address" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
<el-dialog title="课程信息" :visible.sync="vis" width="30%" >
<el-table :data="courses" border stripe>
<el-table-column prop="name" label="课程名称"></el-table-column>
<el-table-column prop="score" label="学分"></el-table-column>
</el-table>
</el-dialog>
<el-dialog title="课程信息" :visible.sync="stuVis" width="30%" >
<el-table :data="stuCourses" border stripe>
<el-table-column prop="name" label="课程名称"></el-table-column>
<el-table-column prop="score" label="学分"></el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
import {serverIp} from "../../public/config";
export default {
name: "User",
data() {
return {
serverIp: serverIp,
tableData: [],
total: 0,
pageNum: 1,
pageSize: 10,
username: "",
email: "",
address: "",
form: {},
dialogFormVisible: false,
multipleSelection: [],
roles: [],
courses: [],
vis: false,
stuCourses: [],
stuVis: false
}
},
created() {
this.load()
},
methods: {
lookCourse(courses) {
this.courses = courses
this.vis = true
},
lookStuCourse(stuCourses) {
this.stuCourses = stuCourses
this.stuVis = true
},
load() {
this.request.get("/user/page", {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
username: this.username,
email: this.email,
address: this.address,
}
}).then(res => {
this.tableData = res.data.data
this.total = res.data.total
})
this.request.get("/role").then(res => {
this.roles = res.data
})
},
save() {
this.request.post("/user", this.form).then(res => {
if (res.code === '200') {
this.$message.success("保存成功")
this.dialogFormVisible = false
this.load()
} else {
this.$message.error("保存失败")
}
})
},
handleAdd() {
this.dialogFormVisible = true
this.form = {}
},
handleEdit(row) {
this.form = JSON.parse(JSON.stringify(row))
this.dialogFormVisible = true
},
del(id) {
this.request.delete("/user/" + id).then(res => {
if (res.code === '200') {
this.$message.success("删除成功")
this.load()
} else {
this.$message.error("删除失败")
}
})
},
handleSelectionChange(val) {
console.log(val)
this.multipleSelection = val
},
delBatch() {
let ids = this.multipleSelection.map(v => v.id) // [{}, {}, {}] => [1,2,3]
this.request.post("/user/del/batch", ids).then(res => {
if (res.code === '200') {
this.$message.success("批量删除成功")
this.load()
} else {
this.$message.error("批量删除失败")
}
})
},
reset() {
this.username = ""
this.email = ""
this.address = ""
this.load()
},
handleSizeChange(pageSize) {
console.log(pageSize)
this.pageSize = pageSize
this.load()
},
handleCurrentChange(pageNum) {
console.log(pageNum)
this.pageNum = pageNum
this.load()
},
exp() {
window.open(`http://${serverIp}:9090/user/export`)
},
handleExcelImportSuccess() {
this.$message.success("导入成功")
this.load()
}
}
}
</script>
<style>
.headerBg {
background: #eee!important;
}
</style>
package.json
{
"name": "vue",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^0.25.0",
"core-js": "^3.6.5",
"echarts": "^5.3.0",
"element-ui": "^2.15.6",
"mavon-editor": "^2.10.4",
"video.js": "^7.18.1",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vue-video-player": "^5.0.2",
"vuex": "^3.6.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"vue-template-compiler": "^2.6.11"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}