const express = require('express') //index.js后端
const app = express()
const port = 3000
const bodyParser = require('body-parser');
app.use(bodyParser.json())
const cors = require('cors');
app.use(cors())
const mysql = require('mysql');
var db = mysql.createPool({
connectionLimit: 10,
host: 'localhost',
user: 'root',
password: '123456',
database: 'kw31'
});
// 引入bcryptjs加密模块,用于注册密码时加密
var bcrypt = require('bcryptjs');
// token
const mySecret = "Life is short, you need front-end";
const jwt = require('jsonwebtoken');
let responseData = {
code: 0,
message: ""
};
app.get('/', (req, res) => res.send('Hello World!'))
app.get('/home', (req, res) => {
let mytoken = req.headers['authorization'];
if (mytoken) {
try {
let result = jwt.verify(mytoken, mySecret);
console.log(result);
res.json(result)
//{ user_id: 2, user_name: 'huhu', iat: 1602763041, exp: 1602849441 }
// let { exp = 0 } = result, current = Math.floor(Date.now() / 1000);
// //官网表示不需要判断,时间到期自动判断
// if (current <= exp) {
// console.log('没过期');
// res.json(result)
// } else {
// console.log('过期了');
// res.json({ code: '999', msg: 'token过期了,请登录' })
// }
} catch (error) {
console.log(error);
res.json({ code: '999', msg: '权限验证失败,你无权访问用户列表' })
}
} else {
res.json({ code: '999', msg: 'token为空,请登录!' })
}
})
// 获取链接列表接口
app.get('/get-article', (req, res) => {
db.query('SELECT * FROM article_table' , (err,results) => {
if (err) throw err;
console.log(results);
res.json(results)
})
})
// 创建链接的接口
app.post('/create-link', (req, res) => {
let {title, content} = req.body;
db.query('INSERT INTO article_table(title,content) VALUES(?,?)' , [title,content], (err,results) => {
if (err) throw err;
console.log(results);
res.json({code:0,msg:'金主爸爸创建成功!'})
})
})
// 删除链接的接口
app.post('/delete-link', (req, res) => {
let {id} = req.body;
db.query('DELETE FROM article_table WHERE id = ?' , [id], (err,results) => {
if (err) throw err;
console.log(results);
res.json({code:0,msg:'金主爸爸删除成功!'})
})
})
// 获取指定id路径接口
app.get('/get-link-show', (req, res) => {
let {id} = req.query;
console.log(id);
db.query('SELECT * FROM article_table WHERE id = ?' , [id], (err,results) => {
if (err) throw err;
console.log(results)
res.json(results)
})
})
// 编辑链接的接口
app.post('/edit-link', (req, res) => {
let {id,title,content} = req.body;
db.query('UPDATE article_table SET title = ?, content = ? WHERE id = ?' , [title,content,id], (err,results) => {
if (err) throw err;
res.json({code:0,msg:'金主爸爸保存成功!'})
})
})
// 获取接口
app.get('/getLink', (req, res) => {
console.log(req.query); // { curr: '1', limit: '10' }
let page = Number(req.query.curr) - 1 // 第几页
let count = Number(req.query.limit) // 每页多少条
db.query(`SELECT * FROM article_table`, (err, result) => {
if (err) throw err;
let total = result.length;
db.query(`SELECT * FROM article_table limit ${page * count},${count}`, (err, result) => {
if (err) throw err;
let responseData = {
code: 0,
count: total,
listData: result
}
res.send(responseData)
});
});
})
// 注册接口
app.post('/register', function (req, res) {
// console.log(req.body); // 打印注册的数据
if (req.body.regUsername.trim() == '') {
responseData.code = 1;
responseData.message = "用户名不能为空";
res.json(responseData);
return;
}
if (req.body.regPassword.trim() == '') {
responseData.code = 2;
responseData.message = "密码不能为空";
res.json(responseData);
return;
}
if (req.body.regPassword.trim() !== req.body.checkPassword.trim()) {
responseData.code = 3;
responseData.message = "两次密码不一致";
res.json(responseData);
return;
}
// 查询数据
db.query(`SELECT username FROM user_table WHERE username='${req.body.regUsername}'`, function (error, results) {
if (error) throw error;
console.log(results);
if (results.length != 0) {
responseData.code = 4;
responseData.message = "用户名已经存在";
res.json(responseData);
return;
} else {
// 使用bcrypt模块将密码加密,自动生成盐和哈希。使用的是同步方法,官网还有异步方法
var hash = bcrypt.hashSync(req.body.regPassword, 8)
//将哈希存储在数据库密码字段中。
db.query(`INSERT INTO user_table(username,password) VALUES('${req.body.regUsername}', '${hash}')`, (error, result) => {
if (error) throw error;
// console.log(result); // 打印数据库插入数据的结果
responseData.code = 0; // 这个0一定要写,否则会继承上一次注册不成功的code值
responseData.message = "注册成功";
res.json(responseData);
})
}
});
})
// 登录
app.post('/login', function (req, res) {
// console.log(req.body); // 打印登录数据
let { logUsername, logPassword } = req.body;
// 这里只是简单验证一下用户名和密码是否非空,建议做下更复杂的验证如长度,非数字等
if (logUsername.trim() == "" || logPassword.trim() == "") {
responseData.code = 1;
responseData.message = "用户名和密码不能为空";
res.json(responseData);
return;
}
// 根据登录名从数据库中查询数据
db.query(`SELECT id,username,password,pic FROM user_table WHERE username="${logUsername}"`, function (error, results) {
if (error) throw error;
// console.log(results); // 打印数据库查询的结果,是一个数组
if (results.length == 0) {
responseData.code = 2;
responseData.message = "用户名不存在";
res.json(responseData);
} else {
// 比较登录密码和数据库中的哈希密码。使用的是bcrypt模块的compareSync同步方法,官网还有异步方法
let checkpass = bcrypt.compareSync(logPassword, results[0].password);
if (checkpass) {
responseData.code = 0;
responseData.message = "登录成功";
// 根据前端登录的用户名或id生成一个token
let token = jwt.sign({ user_id: results[0].id, user_name: results[0].username ,avater: results[0].pic}, mySecret, { expiresIn: '4h' })
responseData.token = token;
res.json(responseData);
} else {
responseData.code = 3;
responseData.message = "密码错误";
res.json(responseData);
}
}
});
})
//上传
const path = require('path');
const fs = require('fs');
const multer = require('multer');
// 在根目录生成一个文件夹,把我们的上传的文件放到这个文件夹里面
var upload = multer({ dest: 'uploads/' })
app.post('/up', upload.single('avatar'), function (req, res) {
console.log(req.file);
var new_file_path = req.file.path + path.parse(req.file.originalname).ext
// var newname2 = req.file.path + path.extname(req.file.originalname)
console.log(new_file_path);
// 使用fs模块的rename重命名
fs.rename(req.file.path, new_file_path, (err) => {
if (err) {
res.json({code:1,msg:'上传失败了'})
} else {
res.json({code:0,msg:'上传成功了'})
}
})
})
app.listen(port, () => console.log(`服务器开始工作啦`))
前端
<template> //App.vue
<div>
<router-view />
</div>
</template>
//main.js
import Vue from 'vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
import VueAwesomeSwiper from 'vue-awesome-swiper'
import router from './routers/index.js';
import http from './config/axios.js';
Vue.prototype.$axios = http;
Vue.use(ElementUI);
Vue.config.productionTip = false
import 'element-tiptap/lib/index.css';
import { ElementTiptapPlugin } from 'element-tiptap';
Vue.use(ElementTiptapPlugin, {
lang: 'zh',//配置语言为中文
});
new Vue({
router,
render: h => h(App),
}).$mount('#app')
//babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
//index.js(路由)
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
import h from '@/views/Home.vue';
import main from '@/views/articles/main.vue';
import s from '@/views/articles/showLink.vue';
import c from '@/views/articles/createLink.vue';
import e from '@/views/articles/editLink.vue';
import o from '@/views/entro.vue';
import p from '@/views/plant-Leek.vue';
import m from '@/views/materia/index.vue'
import v from '@/views/materia/veg.vue'
import d from '@/views/materia/Operate-echarts.vue'
import Login from '@/views/Login.vue';
import Register from '@/views/Register.vue';
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/', redirect:'/home' },//一级路由
{ path: '/home', component:h,children:[
{ path: '', component:main },
{ path: '/show-link', component:s },
{ path: '/create-link', component:c },
{ path: '/edit-link/:id', component:e},
{ path: '/entrepreneurship', component:o},
{ path: '/plant', component:p},
{ path: '/material', component:m},
{ path: '/vegaterbal', component:v},
{ path: '/datacount', component:d},
]},//二级路由
{ path: '/login', name:'LoginPage', component: Login },
{ path: '/register', component: Register }
]
});
export default router;
//Home.vue
<template>
<el-container style="height: 100vh">
<el-header>
<el-row>
<el-col :span="6">
<span class="font1">稂莠</span>
</el-col>
<el-col :span="12" class="frind"> 金主爸爸的韭菜之旅 </el-col>
<el-col :span="5" class="text-right frinds">
<el-dropdown>
<span class="el-dropdown-link">
<el-avatar class="icon" :src="avater" size="small"></el-avatar>
<span style="color: white; margin-right: 10px">{{
loginName
}}</span>
<i class="el-icon-arrow-down" style="margin-right: 15px"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item><span @click="logout">退出</span></el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
</el-row>
</el-header>
<el-container style="height: 500px; border: 1px solid #999">
<el-aside width="230px" style="background-color: rgb(238, 241, 246)">
<el-menu :default-openeds="['1']" unique-opened router active-text-color="#008c8c" :default-active="$route.path">
<el-menu-item index="/home">
<i class="el-icon-setting"></i>
<span slot="title">首页 </span>
</el-menu-item>
<el-submenu index="1">
<template slot="title"><i class="el-icon-tickets"></i>金主</template>
<el-menu-item index="/show-link">金主信息</el-menu-item>
<el-menu-item index="/create-link">创建金主</el-menu-item>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-menu"></i>兴趣</template>
<el-menu-item-group>
<template slot="title">韭菜</template>
<el-menu-item index="/entrepreneurship">割韭菜</el-menu-item>
<el-menu-item index="/plant">种韭菜</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-menu-item index="/entrepreneurship">
<i class="el-icon-setting"></i>
<span slot="title">种菜实例 </span>
</el-menu-item>
<el-menu-item index="/material">
<i class="el-icon-document-add"></i>
<span slot="title">韭菜种类管理 </span>
</el-menu-item>
<el-menu-item index="/vegaterbal">
<i class="el-icon-potato-strips"></i>
<span slot="title">养菜日记 </span>
</el-menu-item>
<el-menu-item index="/datacount">
<i class="el-icon-s-promotion"></i>
<span slot="title">种菜活动统计 </span>
</el-menu-item>
</el-menu>
</el-aside>
<el-container>
<el-main>
<router-view />
</el-main>
</el-container>
</el-container>
<el-footer style="background-color: #eee; color: gray" class="copyright">版权所有 © 金主爸爸科技有限公司</el-footer>
</el-container>
</template>
<script>
export default {
watch: {
'$route.path': function () {
// this.$message.success('系好安全带,发车啦')
this.$message({
showClose: true,
message: '系好安全带,发车啦',
duration: 1000,
type: 'success'
});
}
},
data() {
return {
loginName: "",
avater: ''
};
},
created() {
if (!localStorage.getItem("mytoken")) {
//方法一1)没有token则去登录
this.$router.push("/login");
}
this.$axios.get("/home").then((res) => {
if (res.data.code == "999") {
//方法二2)后端验证:请求/home,没有\错误则返回999,去登录
this.$router.push("/login"); //避免DOM加载
} else {
//否则有token返回其解码数据
this.loginName = res.data.user_name; //如果有,姓名放于data内
this.avater = res.data.avater
}
});
},
methods: {
logout() {
localStorage.clear();
this.$message.success("再见,爸爸!");
this.$router.push({
name: "LoginPage"
});
},
debounce(fn, delay) { //防抖函数
let timeout;
return function () {
clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
},
jieliu(fn, delay) {
let timeout;
return function () {
if (timeout) {
return
}
timeout = setTimeout(() => {
fn.apply(this, arguments) //函数本身再走一遍
timeout = null
})
}
}
},
mounted() {
document.body.style.backgroundColor = "#fff";
},
};
</script>
<style>
body {
margin: 0;
padding: 0;
}
.icon {
position: relative;
top: 7px;
right: 10px;
}
.frinds {
font-size: 20px;
margin-left: 30px !important;
text-align: right !important;
}
.frind {
font-size: 20px;
text-align: center;
}
.el-header {
background-color: #008c8c;
color: white;
line-height: 60px;
}
.copyright {
display: flex;
justify-content: center;
padding-top: 17px;
}
.el-aside {
color: #333;
}
.el-menu-item.is-active {
color: #333;
}
.font1 {
font-family: font1;
color: white;
font-size: 40px !important;
}
@font-face {
font-family: "font1";
font-display: swap;
src: url("//at.alicdn.com/t/webfont_4pyteix3g3l.eot");
/* IE9*/
src: url("//at.alicdn.com/t/webfont_4pyteix3g3l.eot?#iefix") format("embedded-opentype"),
/* IE6-IE8 */
url("//at.alicdn.com/t/webfont_4pyteix3g3l.woff2") format("woff2"),
url("//at.alicdn.com/t/webfont_4pyteix3g3l.woff") format("woff"),
/* chrome、firefox */
url("//at.alicdn.com/t/webfont_4pyteix3g3l.ttf") format("truetype"),
/* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
url("//at.alicdn.com/t/webfont_4pyteix3g3l.svg#站酷高端黑") format("svg");
/* iOS 4.1- */
}
</style>
//lLogin.vue
<template>
<div>
<h1 class="mylogo">
<router-link to="/"
><span class="font1 font-28">稂莠family</span></router-link
>
</h1>
<div class="box">
<h2>
<router-link to="/login" class="text-secondary">登录</router-link>
<span class="text-dark">·</span>
<router-link to="/register" class="text-secondary">注册</router-link>
</h2>
<el-form
:model="formData"
:rules="rules"
ref="loginForm"
label-width="0px"
>
<el-form-item prop="logUsername">
<el-input v-model="formData.logUsername" placeholder="用户名">
<i slot="prefix" class="el-input__icon el-icon-user-solid"></i>
</el-input>
</el-form-item>
<el-form-item prop="logPassword">
<el-input
type="password"
v-model="formData.logPassword"
placeholder="密码"
>
<i slot="prefix" class="el-input__icon el-icon-lock"></i>
</el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="onSubmitForm('loginForm')"
class="myw"
:loading="isLoading">立即登录</el-button
>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
data() {
return {
formData: {
logUsername: "",
logPassword: "",
},
isLoading:false,
rules: {
// rules里的username和el-form-item的prop值必须一致,而prop值必须和formdata里的username一致
logUsername: [
{ required: true, message: "请输入用户名", trigger: "blur" },
],
logPassword: [
{ required: true, message: "请输入密码", trigger: "blur" },
],
},
};
},
methods: {
onSubmitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.isLoading=true
console.log("submit!");
this.$axios
.post("/login", this.formData)
.then((res) => {
console.log(res.data);
if (res.data.code == 1) {
this.$message({
// showClose: true,
message: res.data.message,
type: "warning",
});
return;
}
if (res.data.code == 2) {
this.$message({
// showClose: true,
message: res.data.message,
type: "warning",
});
return;
}
if (res.data.code == 3) {
this.$message({
// showClose: true,
message: res.data.message,
type: "warning",
});
return;
}
if (res.data.code == 0) {
// 存储token
localStorage.setItem("mytoken", res.data.token);
// elementui消息提示
this.$message({
message: res.data.message,
type: "success",
});
this.isLoading=true
this.$router.push("/home");
}
})
.catch((err) => {
console.log("有错误2:");
console.error(err);
});
} else {
console.log("error submit!!");
return false;
}
});
},
},
mounted() {
document.body.style.backgroundColor = "#eee";
},
};
</script>
//Register.vue
<template>
<div>
<h1 class="mylogo">
<router-link to="/home"><span class="font1 ">稂莠family</span></router-link>
</h1>
<div class="box">
<h2>
<router-link to="/login" class="text-secondary">登录</router-link>
<span class="text-dark">·</span>
<router-link to="/register" class="text-secondary">注册</router-link>
</h2>
<el-form :model="formData" :rules="rules" ref="regForm" label-width="0px">
<el-form-item prop="regUsername">
<el-input v-model="formData.regUsername" placeholder="用户名">
<i slot="prefix" class="el-input__icon el-icon-user-solid"></i>
</el-input>
</el-form-item>
<el-form-item label prop="regPassword">
<el-input
type="password"
v-model="formData.regPassword"
autocomplete="off"
placeholder="密码"
>
<i slot="prefix" class="el-input__icon el-icon-lock"></i>
</el-input>
</el-form-item>
<el-form-item label prop="checkPassword">
<el-input
type="password"
v-model="formData.checkPassword"
autocomplete="off"
placeholder="确认密码"
>
<i slot="prefix" class="el-input__icon el-icon-lock"></i>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onRegForm('regForm')" class="myw"
>立即注册</el-button
>
</el-form-item>
</el-form>
<p class="text-center text-primary">
点击 “注册” 即表示您同意并愿意遵守协议
</p>
</div>
</div>
</template>
<script>
export default {
data() {
var validateUserName = (rule, value, callback) => {
var reg = /^\w{3,15}$/;
if (!reg.test(value)) {
callback(new Error("字母、数字、下划线组成"));
} else {
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === "") {
callback(new Error("请再次输入密码"));
} else if (value !== this.formData.regPassword) {
callback(new Error("两次输入密码不一致!"));
} else {
callback();
}
};
return {
formData: {
regUsername: "",
regPassword: "",
checkPassword: "",
},
rules: {
// rules里的username和el-form-item的prop值必须一致,而prop值必须和formdata里的username一致
regUsername: [
{ required: true, message: "请输入用户名", trigger: "blur" },
{ min: 3, max: 15, message: "长度3-16位之间", trigger: "blur" },
{ validator: validateUserName, trigger: "blur" },
],
regPassword: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, max: 15, message: "长度6-15位之间", trigger: "blur" },
],
checkPassword: [
{ min: 6, max: 15, message: "长度6-15位之间", trigger: "blur" },
{ validator: validatePass2, trigger: "blur" },
],
},
};
},
methods: {
onRegForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
console.log("submit!");
this.$axios
.post("/register", this.formData)
.then((res) => {
console.log(res.data);
if (res.data.code == 4) {
this.$message({
showClose: true,
//默认的 Message 是不可以被人工关闭的,如果需要可手动关闭的 Message,可以使用showClose字段
message: res.data.message,
type: "warning",
});
return;
}
if (res.data.code == 0) {
// elementui消息提示
this.$message({
message: "恭喜您,注册成功",
type: "success",
});
// 编程式导航,跳转到login页
this.$router.push("/login");
}
})
.catch((err) => {
console.error(err);
});
} else {
console.log("error submit!!");
return false;
}
});
},
},
mounted() {
document.body.style.backgroundColor = "#eee";
},
};
</script>
这篇博客主要探讨了前端如何实现用户注册、登录功能,以及后台管理系统的构建,包括子路由的设置和使用Babel进行代码转换。此外,还涉及到文件上传的相关技术。
1509

被折叠的 条评论
为什么被折叠?



