采用vue2+element构建网页主体,node作为本地服务器,mysql作为数据库,echarts做了一丁点可视化.
取消lint以及配置代理服务器解决跨域问题
可以在服务器app.js引入cors模块解决跨域,就不用配置代理了.
const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
devServer: {
proxy: {
'/register': {
target: 'http://localhost:5000/register',
changeOrigin: true,
pathRewrite: {'^/register': ''}
},
'/updatePass':{
target: 'http://localhost:5000/updatePass',
pathRewrite: {'^/updatePass':''}
},
'/deleteSpecie':{
target: 'http://localhost:5000/deleteSpecies',
changeOrigin: true,
pathRewrite: {'^/deleteSpecie':''}
},
'/addSpecie':{
target: 'http://localhost:5000/addSpecies',
changeOrigin: true,
pathRewrite: {'^/addSpecie':''}
},
'/selectSpecie':{
target: 'http://localhost:5000/selectSpecies',
changeOrigin: true,
pathRewrite: {'^/selectSpecie':''}
},
'/updateSpecie':{
target: 'http://localhost:5000/updateSpecies',
changeOrigin: true,
pathRewrite: {'^/updateSpecie':''}
},
'/setAvatar':{
target: 'http://localhost:5000/setAvatar',
changeOrigin: true,
pathRewrite: {'^/setAvatar':''}
}
}
}
})
//1.引入express
const express = require('express')
const mysql = require("mysql");
const multer = require("multer")
const cors = require('cors')
const bodyParser = require("body-parser");
const fs = require("fs");
const dayjs = require("dayjs");
//2.创建应用对象
const app = express()
app.use(bodyParser.urlencoded({extended: false})); //parse application/x-www-form-urlencoded
app.use(bodyParser.json());
app.use(cors())
let objMulter = multer({dest: "./public/upload"});
//实例化multer,传递的参数对象,dest表示上传文件的存储路径
app.use(objMulter.any())//any表示任意类型的文件
// app.use(objMulter.image())//仅允许上传图片类型
//静态资源托管
app.use(express.static("./public/upload"));
//创建mysql连接对象
const connection = mysql.createConnection({
host: 'localhost',
port: 3306,
user: 'root',
password: '123456',
database: 'bigevent',
multipleStatements: true
})
connection.connect()
//修改头像
app.post('/setAvatar', (req, res) => {
let oldName = req.files[0].filename;//获取名字
let originalname = req.files[0].originalname;//originnalname其实就是你上传时候文件起的名字
//给新名字加上原来的后缀
let newName = req.files[0].originalname;
fs.renameSync('public/upload/' + oldName, './public/upload/' + newName);//改图片的名字注意此处一定是一个路径,而不是只有文件名
// res.send({
// err: 0,
// url:"http://localhost:5000/public/upload/" +
// newName
// });
const url = "http://localhost:5000/" + newName
console.log(url)
console.log(typeof url)
const username = req.body.name
console.log(username)
const sql = `update account set avatar = '${url}' where username = '${username}'`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
})
res.send(url)
})
//检查登录信息
app.get('/accounts', (request, response) => {
//设置响应头 允许跨域
response.setHeader('Access-Control-Allow-Origin', "*")
//get方式通过params传递的参数可以直接通过request.query获取到
const name = request.query.username
const password = request.query.password
console.log(request.query.username)
console.log(request.query.password)
const sql = `select * from bigevent.account where username='${name}' and password='${password}'`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//检查注册用户的用户名是否合法 即在数据库中是否存在这个用户名
app.get('/checkName', (request, response) => {
//设置响应头 允许跨域
response.setHeader('Access-Control-Allow-Origin', "*")
const name = request.query.username
console.log(request.query.username)
const sql = `select * from bigevent.account where username='${name}'`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//注册用户 往数据库插入用户信息
app.post('/register', (request, response) => {
//设置响应头 允许跨域
response.setHeader('Access-Control-Allow-Origin', "*")
const username = request.body.username
const password = request.body.password
console.log(username)
console.log(password)
const sql = `insert into account(username,password) values('${username}','${password}')`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(result)
})
console.log('有人请求服务器了')
})
//修改用户信息
app.post('/updateInfo', (request, response) => {
// console.log(1)
// console.log(request.body.pass)
const age = request.body.age
const gender = request.body.gender
const newName = request.body.newName
const oldName = request.body.oldName
const maritalStatus = request.body.maritalStatus
console.log(age)
console.log(gender)
console.log(newName)
console.log(oldName)
const sql = `update account set gender = '${gender}',age=${age},username='${newName}',marital_status='${maritalStatus}' where username = '${oldName}'`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(result)
})
})
//修改用户密码
app.post('/updatePass', (request, response) => {
console.log(request.body.pass)
const newPass = request.body.pass
const username = request.body.username
const sql = `update account set password = '${newPass}' where username = '${username}'`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(result)
})
})
//获取文章分类
app.get('/getSpecies', (request, response) => {
//设置响应头 允许跨域
response.setHeader('Access-Control-Allow-Origin', "*")
const sql = `select * from article_species`
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//删除文章分类
app.post('/deleteSpecies', (request, response) => {
//设置响应头 允许跨域
// response.setHeader('Access-Control-Allow-Origin', "*")
const id = request.body.id
console.log(id)
const sql = `delete from article_species where id = ${id}`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//添加文章分类
app.post('/addSpecies', (request, response) => {
//设置响应头 允许跨域
// response.setHeader('Access-Control-Allow-Origin', "*")
const name = request.body.name
const nickname = request.body.nickname
const sql = `insert into article_species(name,nickname) values('${name}','${nickname}')`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//搜索文章分类
app.post('/selectSpecies', (request, response) => {
//设置响应头 允许跨域
// response.setHeader('Access-Control-Allow-Origin', "*")
const name = request.body.name
const sql = `select * from article_species where name = '${name}'`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//更新分类内容
app.post('/updateSpecies', (request, response) => {
//设置响应头 允许跨域
// response.setHeader('Access-Control-Allow-Origin', "*")
const name = request.body.name
const id = request.body.id
const nickname = request.body.nickname
console.log(id)
console.log(name)
console.log(nickname)
const sql = `update article_species set name = '${name}', nickname='${nickname}' where id = ${id}`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//搜索文章
app.post('/selectArticles', (request, response) => {
//设置响应头 允许跨域
// response.setHeader('Access-Control-Allow-Origin', "*")
const specie = request.body.specie
const sql = `select * from articles where specie = '${specie}'`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//发布文章
app.post('/publishArticle', (request, response) => {
//设置响应头 允许跨域
response.setHeader('Access-Control-Allow-Origin', "*")
const title = request.body.title
const content = request.body.content
const author = request.body.author
const pubtime = dayjs().format('YYYY-MM-DD HH:mm:ss')
const specie = request.body.specie
const sql = `insert into articles(title,specie,pubtime,content,author) values('${title}','${specie}','${pubtime}','${content}','${author}')`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//删除文章
app.post('/deleteArticle', (request, response) => {
//设置响应头 允许跨域
// response.setHeader('Access-Control-Allow-Origin', "*")
const id = request.body.id
console.log(id)
const sql = `delete from articles where id = ${id}`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//修改文章
app.post('/editArticle', (request, response) => {
//设置响应头 允许跨域
response.setHeader('Access-Control-Allow-Origin', "*")
const title = request.body.title
const content = request.body.content
const id = request.body.id
const pubtime = dayjs().format('YYYY-MM-DD HH:mm:ss')
const specie = request.body.specie
const sql = `update articles set title ='${title}', specie='${specie}',pubtime='${pubtime}',content='${content}' where id = ${id}`
console.log(sql)
connection.query(sql, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//根据条件查询用户数目并返回
app.get('/statistics', (request, response) => {
//设置响应头 允许跨域
response.setHeader('Access-Control-Allow-Origin', "*")
// const sql = `select * from article_species where id = 1`
// const sql1 = `select * from article_species where id = 2`
connection.query(
// 'select count(1) from article_species;' +
'select count(1) from account where age <18;'+
'select count(1) from account where age >=18 and age <=30;'+
'select count(1) from account where age >=30 and age <=50;'+
'select count(1) from account where age >=50 and age <=70;'+
'select count(1) from account where age >=70;'
, (error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//根据条件查询文章数目并返回
app.post('/articleStatistics', (request, response) => {
//设置响应头 允许跨域
response.setHeader('Access-Control-Allow-Origin', "*")
const species = request.body.species;
console.log(species)
let sql=``
for (const specie of species) {
console.log(specie)
sql +=`select count(1) from articles where specie = '${specie}';`
}
console.log(sql)
connection.query(sql,
(error, result) => {
if (error) {
console.error(error)
return
}
console.log(result)
response.send(JSON.stringify(result))
})
console.log('有人请求服务器了')
})
//4.监听端口启动服务
app.listen(5000, () => {
console.log('服务已经启动,监听端口号5000')
})
import Vue from "vue";
import Router from "vue-router";
import Register from "@/pages/Register";
import Login from "@/pages/Login";
import BackAdmin from "@/pages/BackAdmin";
import ResetPassword from "@/pages/ResetPassword";
import ArticleSpecies from "@/pages/ArticleSpecies";
import setAvatar from "@/pages/setAvatar";
import Statistics from "@/pages/Statistics";
import updateInfo from "@/pages/updateInfo";
import articleList from "@/pages/articleList";
import publishArticle from "@/pages/publishArticle";
import articleDetail from "@/pages/articleDetail";
import editArticle from "@/pages/editArticle";
Vue.use(Router)
const router = new Router({
routes:[
{
path:'/',
component:Login,
meta:{
title:'后台管理登录',
isAuth:true
}
},
{
path:'/register',
component:Register,
meta:{
title:'用户注册'
}
},
{
path:'/admin',
component:BackAdmin,
meta:{
title:'后台管理主页',
auth:true
},
//设置重定向到统计页面
redirect:{
name:'statistics'
},
children:[
{
path:'resetPass',
name:'resetPass',
component:ResetPassword,
meta:{
title:'修改密码',
auth:true
}
},
{
path:'articleSpecies',
name:'species',
component:ArticleSpecies,
meta:{
title:'文章分类',
isAuth:true,
auth:true
}
},
{
path:'setAvatar',
name:'setAvatar',
component:setAvatar,
meta:{
title:'修改头像',
auth:true
}
},
{
path:'statistics',
name:'statistics',
component:Statistics,
meta:{
title:'统计',
auth:true
}
},
{
path:'updateInfo',
name:'updateInfo',
component:updateInfo,
meta:{
title:'修改个人信息',
auth:true
}
},
{
path:'articleList',
name:'articleList',
component:articleList,
meta:{
title:'文章列表',
auth:true
}
},
{
path:'publishArticle',
name:'publishArticle',
component:publishArticle,
meta:{
title:'发布文章',
auth:true
}
},
{
path:'articleDetail',
name:'articleDetail',
component:articleDetail,
meta:{
title:'文章详情',
auth:true
},
},
{
path:'editArticle',
name:'editArticle',
component:editArticle,
meta:{
title:'修改文章',
auth:true
},
},
]
}
]
})
router.beforeEach((to,from,next)=>{
//未登录前往登录后页面跳转到登录页
if(to.meta.auth &&!localStorage.getItem('username')){
router.push('/')
return
}
if(to.meta.title){
document.title = to.meta.title
}
next()
})
//在登录页禁用后退按钮 指定后退页面为登录页
router.afterEach((to,from)=>{
if(to.meta.isAuth){
history.pushState(null, null, location.protocol + '//' + location.host + '/#' + to.path)
}
})
export default router
<template>
<div>
<el-container>
<el-main>
<el-row>
<el-input placeholder="请输入用户名" v-model="username" prefix-icon="el-icon-user-solid">
</el-input>
</el-row>
<el-row>
<el-input placeholder="请输入密码" v-model="password" show-password prefix-icon="el-icon-lollipop" @keyup.enter="handleLogin"></el-input>
</el-row>
<el-row>
<el-button type="primary" style="width: 100%" @click="handleLogin" >登录</el-button>
</el-row>
<el-row>
<router-link to="/register">去注册</router-link>
</el-row>
</el-main>
</el-container>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "Login",
data(){
return{
username:'',
password:'',
timer:null
}
},
methods:{
//添加按键节流
handleLogin(){
if(this.timer){
return
}
this.timer = setTimeout(()=>{
this.login()
this.timer = null
},1000)
},
login(){
if((!this.username || !this.password)){
this.$message.error('用户名或密码不能为空!');
}else {
axios.get('http://localhost:5000/accounts',{
params:{
username:this.username,
password:this.password
}
}).then(
response=>{
console.log(response.data)
if(!response.data.length){
this.$message.error('用户名或密码不正确!');
this.password=''
this.username=''
}else {
localStorage.setItem('username',this.username)
localStorage.setItem('password',this.password)
console.log(response.data[0].avatar)
localStorage.setItem('url',response.data[0].avatar)
this.$message({
message: '登陆成功~',
type: 'success',
})
//将数据存入state不合理页面刷新数据消失
// this.$store.dispatch('addName',this.username)
// this.$store.dispatch('addPass',this.password)
setTimeout(()=>{
this.$router.push('/admin')
},2000)
}
},
error=>{
console.log(error.message)
}
)
}
}
},
mounted() {
}
}
</script>
<style scoped lang="less">
.el-container {
width: 350px;
margin: 300px auto 0;
//padding: 50px;
background-color: rgba(255,255,255,.4);
border-radius: 20px;
.el-row{
padding: 5px;
position: relative;
.el-input__inner{
padding: 0 28px!important;
}
i{
position: absolute;
left: 5px;
top: 50%;
transform: translateY(-50%);
z-index: 99;
}
a{
float: right;
color: #fff;
text-decoration: none;
}
}
}
</style>
<template>
<div>
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" class="demo-ruleForm">
<el-form-item prop="name">
<el-input type="text" v-model="ruleForm.name" autocomplete="off" prefix-icon="el-icon-user-solid" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item prop="pass">
<el-input type="password" v-model="ruleForm.pass" autocomplete="off" placeholder="请输入密码" prefix-icon="el-icon-sugar"></el-input>
</el-form-item>
<el-form-item prop="checkPass">
<el-input type="password" v-model="ruleForm.checkPass" placeholder="请确认密码" prefix-icon="el-icon-apple" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')" style="width: 100%">注册</el-button>
<!-- <el-button @click="resetForm('ruleForm')">重置</el-button>-->
</el-form-item>
<router-link to="/">去登录</router-link>
</el-form>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "Register",
data() {
const validateName = (rule, value, callback) => {
if (!value) {
return callback(new Error('用户名不能为空'));
}
setTimeout(() => {
axios.get('http://localhost:5000/checkName',{
params:{
username:this.ruleForm.name
}
}).then(
response=>{
//校验用户名是否合法
console.log(response.data)
if(response.data.length){
callback(new Error('该用户已存在'));
}else {
callback();
}
},
error=>{
console.log(error.message)
}
)
}, 1000);
};
const validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
}else if(value.length<6){
callback(new Error('密码不能少于6位!'));
}
else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
const validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== this.ruleForm.pass) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
ruleForm: {
pass: '',
checkPass: '',
name:''
},
rules: {
name:[
{ validator: validateName, trigger: 'blur' }
],
pass: [
{ validator: validatePass, trigger: 'blur' }
],
checkPass: [
{ validator: validatePass2, trigger: 'blur' }
],
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
axios.post('http://localhost:8080/register',{
username:this.ruleForm.name,
password:this.ruleForm.pass
}
).then(
response=>{
console.log(response.data)
this.$message({
message: '恭喜你,注册成功',
type: 'success'
})
//将输入存入本地
localStorage.setItem('username',this.ruleForm.name)
localStorage.setItem('password',this.ruleForm.pass)
localStorage.setItem('url','')
setTimeout(()=>{
this.$router.push('/admin')
},1000)
},
error=>{
console.log(error.message)
}
)
} else {
console.log('error submit!!');
return false;
}
});
}
}
}
</script>
<style scoped>
.el-form{
width: 350px;
margin: 250px auto 0;
padding: 50px;
background-color: rgba(255,255,255,.5);
border-radius: 20px;
}
a{
float: right;
color: #fff;
text-decoration: none;
}
</style>
采用element表单组件设置自定义校验规则,十分方便
<template>
<div>
<el-container style="height: 630px; border: 1px solid #eee;overflow: hidden">
<!-- 左侧导航栏开始-->
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-row style="height: 60px">
<h1>后台管理系统</h1>
</el-row>
<!-- <el-menu :default-openeds="['1']"> 设置默认打开选项栏 通过唯一标识index指定-->
<el-menu>
<el-submenu index="1">
<template slot="title"><i class="el-icon-message"></i>首页</template>
<el-menu-item-group>
<el-menu-item >
<router-link :to="{name:'statistics'}" slot="title">
统计
</router-link>
</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-menu"></i>文章管理</template>
<el-menu-item-group>
<el-menu-item :route="{
name:'species'}"
>
<router-link :to="{
name:'species'
}">文章分类</router-link>
</el-menu-item>
<el-menu-item>
<router-link :to="{
name:'articleList'
}">文章列表</router-link>
</el-menu-item>
<el-menu-item>
<router-link :to="{
name:'publishArticle'
}">发布文章</router-link>
</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="3">
<template slot="title"><i class="el-icon-user-solid"></i>个人信息</template>
<el-menu-item>
<router-link :to="{
name:'updateInfo'
}">修改详细信息
</router-link>
</el-menu-item>
<el-menu-item>
<router-link :to="{
name:'resetPass'
}">修改密码
</router-link>
</el-menu-item>
<el-menu-item>
<router-link :to="{
name:'setAvatar'
}">修改头像
</router-link>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<!-- 下拉列表选项卡-->
<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<router-link :to="{name:'updateInfo'}">修改信息</router-link>
</el-dropdown-item>
<el-dropdown-item>
<router-link :to="{name:'resetPass'}">修改密码</router-link>
</el-dropdown-item>
<el-dropdown-item>
<router-link replace to="/">退出</router-link>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 头像与姓名-->
<el-avatar :src="this.url" style="height: 60px;width: 60px;display: inline-block;margin: 0 10px"></el-avatar>
<span style="display: inline-block;height: 60px;float: right;font-weight: 700;font-size: 14px">{{name}}</span>
</el-header>
<!-- 切换路由显示组件位置-->
<el-main style="background-color: rgba(255,255,255,.5);">
<router-view></router-view>
</el-main>
</el-container>
</el-container>
<el-footer style="height: 65px">©Designed by Pegnet ⌖2022</el-footer>
</div>
</template>
<script>
export default {
name: "BackAdmin",
data() {
return {
name:localStorage.getItem('username'),
url:localStorage.getItem('url')
}
},
mounted() {
this.$bus.$on('getUrl',()=>{
this.name = localStorage.getItem('username')
this.url = localStorage.getItem('url')
})
},
beforeDestroy() {
localStorage.removeItem('username')
// localStorage.removeItem('id')
localStorage.removeItem('password')
localStorage.removeItem('url')
}
}
</script>
将用户的输入存入本地以便通过路由守卫鉴权,未登录状态通过url访问网址重定向到登录页
<template>
<div>
<el-container>
<el-card class="box-card" style="">
<h1>
用户总数
</h1>
<span>{{ userSum }}</span>
</el-card>
<el-card class="box-card">
<h1>
文章总数
</h1>
<span>{{ articleSum }}</span>
</el-card>
</el-container>
<div>
<div id="age" style="width: 50%;height: 500px;display: inline-block;margin-top: 20px;float: left"></div>
<div id="species" style="width: 50%;height: 500px;display: inline-block;float:right"></div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts';
import axios from "axios";
export default {
name: "Statistics",
data() {
return {
ageData: [
{value: 0, name: '18岁以下'},
{value: 0, name: '18岁以上30岁以下'},
{value: 0, name: '30岁以上50岁以下'},
{value: 0, name: '50岁以上70岁以下'},
{value: 0, name: '70岁以上'},
],
species: [],
speciesData: [],
userSum: 0,
articleSum: 0
}
},
mounted() {
//获取年龄统计数据
const age = echarts.init(document.getElementById('age'));
const ageOption = {
textStyle:{
fontSize: 14,
fontWeight: 700,
},
title: {
text: '用户年龄分布',
subtext: '来自数据库',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '用户年龄',
type: 'pie',
radius: '50%',
data: this.ageData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
axios.get('http://localhost:5000/statistics').then(
response => {
console.log(response.data)
response.data.forEach((item, index) => {
this.ageData[index].value = item[0]['count(1)']
this.userSum += this.ageData[index].value
// console.log(this.data[index].value)
})
age.setOption(ageOption);
}
)
//获取文章分类统计数据
const species = echarts.init(document.getElementById('species'));
const specieOption = {
textStyle:{
fontSize: 14,
fontWeight: 700,
color:'black'
},
title: {
text: '文章分类数量',
subtext: '来自数据库',
left: 'center'
},
xAxis: {
type: 'category',
data: this.species
},
yAxis: {
type: 'value'
},
series: [
{
data: this.speciesData,
type: 'bar',
showBackground: true,
backgroundStyle: {
color: 'rgba(180, 180, 180, 0.2)'
}
}
]
};
//获取文章分类
axios.get('http://localhost:5000/getSpecies').then(
response => {
// console.log(response.data)
response.data.forEach((item) => {
// console.log(item)
this.species.push(item.name)
})
//获取文章分类文章数量
axios.post('http://localhost:5000/articleStatistics', {
species: this.species
}).then(
response => {
// console.log(response.data)
response.data.forEach((item, index) => {
this.speciesData[index] = item[0]['count(1)']
// console.log(this.data[index].value)
this.articleSum += this.speciesData[index]
console.log(this.speciesData)
species.setOption(specieOption);
})
},
error => {
console.log(error.message)
}
)
species.setOption(specieOption);
// console.log(this.species)
// this.specieData = response.data
},
error => {
console.log(error.message)
}
)
}
}
</script>
<template>
<div>
<el-row style="float: right;margin-bottom: 10px">
<!-- <el-button type="primary">添加分类</el-button>-->
<!-- 添加分类弹出层-->
<el-popover
placement="left"
width="400"
trigger="click">
<!-- 提交表单-->
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="分类名" prop="name">
<el-input type="text" v-model="ruleForm.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="分类别名" prop="nickname">
<el-input type="text" v-model="ruleForm.nickname" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">添加</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
<!-- <el-button type="primary" plain style="width: 100%" @click="addSpecie">确认添加</el-button>-->
<el-button slot="reference" type="primary">添加分类</el-button>
</el-popover>
</el-row>
<!-- 显示分类表格主体-->
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__bounce"
leave-active-class="animate__bounce"
>
<el-table
:data="tableData"
style="width: 100%"
key="1"
>
<el-table-column
label="序号"
width="180"
key="2"
>
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column
label="分类名称"
width="280">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column
label="分类别名"
width="280">
<template slot-scope="scope">
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.nickname }}</el-tag>
</div>
</template>
</el-table-column>
<!-- 表格操作列-->
<el-table-column label="操作">
<!-- 编辑弹出层-->
<template slot-scope="scope">
<el-popover
placement="left"
width="400"
trigger="click"
>
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px"
class="demo-ruleForm">
<el-form-item label="分类名" prop="name">
<!-- <input type="text" :value="scope.row.name">-->
<el-input type="text" v-model="ruleForm.name" :placeholder="scope.row.name"
autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="分类别名" prop="nickname">
<el-input type="text" v-model="ruleForm.nickname" :placeholder="scope.row.nickname"
autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm',scope.$index, scope.row)">修改</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
<el-button
slot="reference"
size="mini"
type="warning"
style="margin-right: 10px"
>编辑
</el-button>
</el-popover>
<el-popconfirm
title="确定删除这个分类吗?"
icon="el-icon-info"
icon-color="red"
@confirm="handleDelete(scope.$index, scope.row)">
<el-button
size="mini"
slot="reference"
type="danger">删除
</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</transition-group>
<!-- <el-empty description="暂无数据" v-show="isShow"></el-empty>-->
</div>
</template>
<script>
import axios from "axios";
import 'animate.css'
export default {
name: "ArticleSpecies",
data() {
//定义检验规则
const validateName = (rule, value, callback) => {
// console.log(value)
if (value === '') {
callback(new Error('请输入分类名'));
} else {
this.tableData.forEach((val) => {
if (value === val.name) {
callback(new Error('分类名已存在!'));
}
// console.log(val)
// console.log(val.name)
})
callback();
}
};
const validateNickname = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入分类别名'));
} else {
callback();
}
};
return {
tableData: [],
isShow: false,
timer: null,
ruleForm: {
name: '',
nickname: '',
},
rules: {
name: [
{validator: validateName, trigger: 'blur'}
],
nickname: [
{validator: validateNickname, trigger: 'blur'}
],
}
}
},
watch: {
tableData: {
// deep:true,
handler(newValue, oldValue) {
if (!newValue) {
this.isShow = true
} else {
this.isShow = true
}
}
}
},
methods: {
handleEdit(index, row) {
console.log(index, row);
},
//删除分类,形参与实参列表必须一一对应
handleDelete(index, row) {
//实现节流以及鉴权
if (this.timer) {
return
} else {
if ([1, 2, 3].includes(row.id) && localStorage.getItem('username') !== 'admin') {
this.timer = setTimeout(() => {
this.$message.error('非管理员不可删除这个分类!');
this.timer = null
}, 1000)
}else {
axios.post('http://localhost:8080/deleteSpecie', {
id: row.id
}).then(
response => {
console.log(response.data)
//获取当前行的id直接从数据中删除
this.tableData.splice(index, 1)
},
error => {
console.log(error.message)
}
)
}
}
},
//添加分类
addSpecie() {
axios.post('http://localhost:8080/addSpecie', {
name: this.ruleForm.name,
nickname: this.ruleForm.nickname
}).then(
response => {
console.log(response.data)
},
error => {
console.log(error.message)
}
)
},
//查询分类
selectSpecie(status, index) {
axios.post('http://localhost:8080/selectSpecie', {
name: this.ruleForm.name,
}).then(
response => {
if (status === 'add') {
this.tableData.push(response.data[0])
} else {
this.tableData.splice(index, 1, response.data[0])
}
// console.log(response.data)
},
error => {
console.log(error.message)
}
)
},
//更新分类
updateSpecie(id, name, nickname) {
axios.post('http://localhost:8080/updateSpecie', {
id,
name,
nickname
}).then(
response => {
console.log(response.data)
},
error => {
console.log(error.message)
}
)
},
//提交添加分类
submitForm(formName, index = '', row = '') {
this.$refs[formName].validate((valid) => {
if (valid) {
if (!index && !row) {
this.addSpecie()
this.selectSpecie('add')
this.ruleForm.nickname = ''
this.ruleForm.name = ''
this.$message({
message: '添加成功~',
type: 'success'
})
} else {
// console.log(e.target)
console.log(index)
console.log(row)
if ([1, 2, 3].includes(row.id) && localStorage.getItem('username') !== 'admin'){
this.$message.error('非管理员不可修改这个分类!');
this.ruleForm.nickname = ''
this.ruleForm.name = ''
}else{
this.updateSpecie(row.id, this.ruleForm.name, this.ruleForm.nickname)
this.selectSpecie('edit', index)
this.ruleForm.nickname = ''
this.ruleForm.name = ''
this.$message({
message: '修改成功~',
type: 'success'
})
}
}
} else {
console.log('error submit!!');
return false;
}
});
},
//重置表单
resetForm(formName) {
this.$refs[formName].resetFields();
}
},
//当dom元素挂载完毕执行
mounted() {
axios.get('http://localhost:5000/getSpecies').then(
response => {
console.log(response.data)
this.tableData = response.data
},
error => {
console.log(error.message)
}
)
}
}
</script>
<template>
<el-container>
<el-card class="box-card" style="width: 100%">
<div slot="header" class="clearfix">
<span style="font-weight: 700">文章列表</span>
</div>
<el-row>
<el-select v-model="value" filterable placeholder="请选择">
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
<el-button type="primary" @click="searchArticle" style="margin-left: 10px">搜索</el-button>
<el-button type="success" plain style="float: right" @click="publishArticle">发表文章</el-button>
</el-row>
<el-main>
<el-table
:data="tableData"
border
style="width: 100%">
<el-table-column
prop="title"
align="center"
label="文章标题"
class="name-wrapper"
width="250">
</el-table-column>
<el-table-column
prop="specie"
align="center"
label="文章分类"
width="160">
</el-table-column>
<el-table-column
prop="pubtime"
align="center"
:formatter="dateFormatter"
label="发布时间"
width="320">
</el-table-column>
<el-table-column
prop="author"
align="center"
label="作者"
width="160">
</el-table-column>
<el-table-column
label="操作"
align="center"
width="305">
<template slot-scope="scope">
<!-- <el-popover-->
<!-- placement="left"-->
<!-- width="800"-->
<!-- trigger="click">-->
<!-- <p ref="content"></p>-->
<!-- <el-button type="primary" slot="reference" size="small" @click="showContent(scope.$index, scope.row)">查看</el-button>-->
<!-- </el-popover>-->
<el-button type="primary" slot="reference" size="small" @click="showContent(scope.$index, scope.row)">查看</el-button>
<!-- 只有文章的作者才有权限编辑和删除文章-->
<el-button type="warning" size="small" v-show="scope.row.author === username" @click="editArticle(scope.row)">编辑</el-button>
<el-popconfirm
title="确定删除这个文章吗?"
icon="el-icon-info"
icon-color="red"
@confirm="deleteArticle(scope.$index,scope.row.id)"
>
<el-button type="danger" slot="reference" size="small" style="margin-left: 7px" v-show="scope.row.author === username">删除</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-main>
</el-card>
</el-container>
</template>
<script>
import axios from "axios";
import dayjs from "dayjs";
export default {
name: "articleList",
data() {
return {
options: [],
value: '',
tableData: [],
timer: null,
status:null,
username:window.localStorage.getItem('username'),
cacheSpecie:localStorage.getItem('cacheSpecie')
}
},
//dom加载完成从数据库获取所有文章分类并渲染页面
mounted() {
axios.get('http://localhost:5000/getSpecies').then(
response => {
this.options = response.data
},
error => {
console.log(error.message)
}
)
//如果有搜索历史优先展示缓存中搜索的结果
if(this.cacheSpecie){
axios.post('http://localhost:5000/selectArticles', {
specie: this.cacheSpecie
}).then(
response => {
this.tableData = response.data
this.value = this.cacheSpecie
this.timer = null
},
error => {
console.log(error.message)
}
)
}
},
methods: {
//根据分类搜索文章列表
searchArticle() {
if (this.timer) {
return
}
this.timer = setTimeout(() => {
axios.post('http://localhost:5000/selectArticles', {
specie: this.value
}).then(
response => {
console.log(response.data)
localStorage.setItem('cacheSpecie',this.value)
this.tableData = response.data
this.timer = null
},
error => {
console.log(error.message)
}
)
}, 500)
},
//定义格式化函数
dateFormatter(cellvalue) {
// console.log('@',cellvalue)
// cellvalue为当前行对象
return dayjs(cellvalue.pubtime).format('YYYY-MM-DD HH:mm:ss')
},
//前往发布文章页面
publishArticle(){
this.$router.push({
name:'publishArticle'
})
},
//查看文章
showContent(index,row){
// console.log(index)
// console.log(row.content)
this.$router.push({
name:'articleDetail',
params:{
article:row
}
})
// this.$refs.content.innerHTML = row.content
},
//修改文章
editArticle(row){
// console.log(index)
// console.log(row.content)
this.$router.push({
name:'editArticle',
params:{
article:row
}
})
// this.$refs.content.innerHTML = row.content
},
//删除文章
deleteArticle(index,id){
console.log(id)
axios.post('http://localhost:5000/deleteArticle', {
id: id
}).then(
response => {
console.log(response.data)
this.tableData.splice(index, 1)
},
error => {
console.log(error.message)
}
)
}
},
}
</script>
只有文章的作者才能修改或删除文章,其余用户只能查看
<template>
<el-container>
<el-header>
<el-input
v-model="title"
placeholder="请输入标题"
style="width: 300px;margin-right: 20px"
maxlength="10"
show-word-limit
></el-input>
<el-select v-model="specie" filterable placeholder="请选择">
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
<el-button type="primary" style="margin-left: 20px" @click="publishArticle">发布文章</el-button>
</el-header>
<div style="border: 1px solid #ccc;height: 400px">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editor"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
style="height: 500px; overflow-y: hidden;"
v-model="content"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="onCreated"
/>
</div>
</el-container>
</template>
<script>
import {Editor, Toolbar} from '@wangeditor/editor-for-vue'
import axios from "axios";
export default {
name: "publishArticle",
components: {Editor, Toolbar},
data() {
return {
editor: null,
content: '',
toolbarConfig: {},
editorConfig: {placeholder: '请输入内容...'},
mode: 'default', // or 'simple'
options: [],
specie: '',
title:'',
timer:null
}
},
methods: {
onCreated(editor) {
this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
},
//发布文章
publishArticle(){
if(this.timer){
return;
}
this.timer = setTimeout(()=>{
if(!this.title){
this.$message({
showClose: true,
message: '请输入标题',
type: 'warning'
});
return this.timer=null
}
if(!this.specie){
this.$message({
showClose: true,
message: '请选择分类',
type: 'warning'
});
return this.timer=null
}
if(!this.content){
this.$message({
showClose: true,
message: '请输入文章内容',
type: 'warning'
});
return this.timer=null
}
console.log(this.title)
console.log(this.content)
console.log(this.specie)
axios.post('http://localhost:5000/publishArticle',{
title:this.title,
content:this.content,
specie:this.specie,
author:localStorage.getItem('username'),
}).then(
response=>{
console.log(response.data)
this.$notify({
title: '成功',
message: '发布成功~',
type: 'success'
});
this.timer=null
this.title=''
this.content=''
this.specie=''
},
error=>{
this.$notify({
title: '失败',
message: '发布失败!',
type: 'warning'
});
console.log(error.message)
}
)
},1000)
}
},
mounted() {
// // 模拟 ajax 请求,异步渲染编辑器
// setTimeout(() => {
// this.content = '请输入内容'
// }, 1500)
axios.get('http://localhost:5000/getSpecies').then(
response => {
this.options = response.data
},
error => {
console.log(error.message)
}
)
},
beforeDestroy() {
const editor = this.editor
if (editor == null) return
editor.destroy() // 组件销毁时,及时销毁编辑器
}
}
</script>
<style scoped>
@import "@wangeditor/editor/dist/css/style.css";
</style>
<template>
<el-container>
<el-header>
<el-button type="primary" plain icon="el-icon-arrow-left" style="float: left" @click="toList">返回</el-button>
<el-input
v-model="title"
placeholder="请输入标题"
style="width: 300px;margin-right: 20px"
maxlength="10"
show-word-limit
></el-input>
<el-select v-model="specie" filterable placeholder="请选择">
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
<el-button type="success" plain style="margin-left: 20px" @click="publishArticle">确认修改</el-button>
</el-header>
<div style="border: 1px solid #ccc;height: 400px">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editor"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
style="height: 500px; overflow-y: hidden;"
v-model="content"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="onCreated"
/>
</div>
</el-container>
</template>
<script>
import {Editor, Toolbar} from '@wangeditor/editor-for-vue'
import axios from "axios";
export default {
name: "editArticle",
components: {Editor, Toolbar},
data() {
return {
editor: null,
content: this.$route.params.article.content,
toolbarConfig: {},
editorConfig: {placeholder: '请输入内容...'},
mode: 'default', // or 'simple'
options: [],
specie: this.$route.params.article.specie,
title:this.$route.params.article.title,
timer:null
}
},
methods: {
onCreated(editor) {
this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
},
//发布文章
publishArticle(){
if(this.timer){
return;
}
this.timer = setTimeout(()=>{
if(!this.title){
this.$message({
showClose: true,
message: '请输入标题',
type: 'warning'
});
return this.timer=null
}
if(!this.specie){
this.$message({
showClose: true,
message: '请选择分类',
type: 'warning'
});
return this.timer=null
}
if(!this.content){
this.$message({
showClose: true,
message: '请输入文章内容',
type: 'warning'
});
return this.timer=null
}
console.log(this.title)
console.log(this.content)
console.log(this.specie)
axios.post('http://localhost:5000/editArticle',{
title:this.title,
content:this.content,
specie:this.specie,
// author:localStorage.getItem('username'),
id:this.$route.params.article.id,
}).then(
response=>{
console.log(response.data)
this.$notify({
title: '成功',
message: '修改成功~',
type: 'success'
});
this.timer=null
this.title=''
this.content=''
this.specie=''
},
error=>{
console.log(error.message)
}
)
},1000)
},
toList(){
this.$router.back()
}
},
mounted() {
// // 模拟 ajax 请求,异步渲染编辑器
// setTimeout(() => {
// this.content = '请输入内容'
// }, 1500)
axios.get('http://localhost:5000/getSpecies').then(
response => {
this.options = response.data
},
error => {
console.log(error.message)
}
)
},
beforeDestroy() {
const editor = this.editor
if (editor == null) return
editor.destroy() // 组件销毁时,及时销毁编辑器
}
}
</script>
<style scoped>
@import "@wangeditor/editor/dist/css/style.css";
</style>
与发布组件一致,只需要简单赋值修改即可
<template>
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="用户名" prop="username">
<el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="性别" style="text-align: left;font-weight: 700">
<el-select v-model="ruleForm.gender" placeholder="请选择性别" value="">
<el-option label="男" value="male"></el-option>
<el-option label="女" value="female"></el-option>
<el-option label="其他" value="other"></el-option>
</el-select>
</el-form-item>
<el-form-item label="婚况" style="text-align: left">
<el-select v-model="ruleForm.maritalStatus" placeholder="请选择婚况" value="">
<el-option label="已婚" value="unmarried"></el-option>
<el-option label="未婚" value="married"></el-option>
<el-option label="丧偶" value="widow"></el-option>
</el-select>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input v-model.number="ruleForm.age"></el-input>
</el-form-item>
<el-form-item style="text-align: left">
<el-button type="primary" @click="submitForm('ruleForm')">确认修改</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</template>
<script>
import axios from "axios";
export default {
name: "updateInfo",
data() {
//检验年龄是否合法
const checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('年龄不能为空'));
}
setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error('请输入数字值'));
} else {
if (value < 10) {
callback(new Error('必须年满10岁'));
} else {
callback();
}
}
}, 1000);
};
//检验用户名是否合法
const validateName = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入用户名'));
} else {
setTimeout(() => {
axios.get('http://localhost:5000/checkName',{
params:{
username:this.ruleForm.username
}
}).then(
response=>{
console.log(response.data)
if(response.data.length){
callback(new Error('该用户已存在'));
}else {
callback();
}
},
error=>{
console.log(error.message)
}
)
}, 1000);
// callback();
}
};
return {
ruleForm: {
username: '',
age: '',
gender:'',
maritalStatus:'',
oldName :localStorage.getItem('username')
},
rules: {
username: [
{ required:true,validator: validateName, trigger: 'blur' }
],
gender: [
{ message: '请选择性别', trigger: 'change' }
],
age: [
{ required:true,validator: checkAge, trigger: 'blur' }
]
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
axios.post('http://localhost:5000/updateInfo',{
newName:this.ruleForm.username,
oldName:this.ruleForm.oldName,
age:this.ruleForm.age,
maritalStatus:this.ruleForm.maritalStatus,
gender:this.ruleForm.gender,
}).then(
response=>{
console.log(response.data)
localStorage.setItem('username',this.ruleForm.username)
this.ruleForm.oldName = this.ruleForm.username
this.$bus.$emit('getUrl')
this.$message({
message: '修改成功~',
type: 'success',
})
this.ruleForm.username = ''
this.ruleForm.age = ''
this.ruleForm.gender = ''
this.ruleForm.maritalStatus = ''
},
error=>{
console.log(error.message)
}
)
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
<template>
<div>
<el-upload
class="avatar-uploader"
action="http://localhost:5000/setAvatar"
:data="{name:this.username}"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar" alt="丢失">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
<div style="color: #3a3b3d">点击上传头像</div>
</el-upload>
<!-- <img :src="this.url" alt="">-->
</div>
</template>
<script>
export default {
name: "setAvatar",
data() {
return {
imageUrl: '',
username:localStorage.getItem('username'),
url:localStorage.getItem('url'),
};
},
methods: {
handleAvatarSuccess(res, file) {
console.log(this.username)
this.imageUrl = URL.createObjectURL(file.raw);
console.log(res)
localStorage.setItem('url',res)
this.$bus.$emit('getUrl')
console.log(this.imageUrl)
},
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!');
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
return isJPG && isLt2M;
}
}
}
</script>
采用element头像组件上传到服务器本地以及数据库
全部代码https://gitee.com/pegnet/bigevent.git