vue实战项目-ego商城
搭建项目
提前cmd查一下vue -V查看vuecli的版本,我本来的版本太高跟这个教程完全对不上,所以重新下载了低版本的vuecli
从5.0.4降到3.11.0,因为elementUI只能支持到vuecli3的版本
低版本vuecli安装教程
在VScode里面直接输入vue create ego_cz即可,后面的ego_cz是项目名
但是一开始报错Error: command failed: npm install --loglevel error --legacy-peer-deps
后来发现需要用管理员身份打开IDEA才可以。
搭建项目过程就不讲了,有router和vuex就可以了。
创建好项目之后,cd ego_cz,进入自己的项目目录。
接下来去安装依赖
//npm install --save axios
2.element //vue add element 选择部份依赖即可
安装完依赖,直接去element官网,快速上手部分找到全部组件,用这些替换plugins目录下的内容
里面全是vue2的写法,所以想用这个必须降低vuecli的版本号。唉,搞这个也搞半天。
之后执行npm run serve,查看能否正常进入vue展示页面,并且在中间有个el-button的按钮。这代表依赖都装好了。
页面搭建
views目录下是我们的页面文件,把里面的aboutview删掉以及component目录下的helloword都删掉。
注意:文件name设置不能使用简单的一个词,不然会报错,类似下图:
所以我把每个view文件后面都添加了View让其变得复杂。
首先在views目录下建立文件,作为整体的布局。
里面包含首页、产品管理、规格参数、广告分类四大子模块
因此我们为这四个模块建立了一个目录交main,首页就是不用动,另外要在目录里创建三个文件
分别是:,,
为了解决报错问题,我们要将与之前删掉的aboutbiew和helloword相关的文件内容都删掉,并且要改变router设置。
//
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
- 1
- 2
- 3
- 4
- 5
- 6
//
<template>
<div>
<!-- 登录界面 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'LayoutView',
}
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
//
<template>
<div>
商品管理
</div>
</template>
<script>
export default {
name:'ProductView',
}
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
其他两个同理,先简单写出来,测试路由是否可行
接下来去解决路由问题,打开router里面的,首先从文件中引入LayoutView,把本来的homeview改成我们的LayoutView,在给这个路径下面添加子模块路由。
// src/router/
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/main/'
import LayoutView from '../views/'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'LayoutView',
component: LayoutView,
children: [
//以下都是layout子元素
{
//path啥也不加进来的就是home这个页面
path: '',
name: 'Home',
component:HomeView
},
{
// 加上product就进入了产品页面
path: 'product',
name: 'ProductView',
component:() => import('../views/main/')
},
{
path: 'params',
name: 'ParamsView',
component:() => import('../views/main/')
},
{
path: 'ad',
name: 'ADCategory',
component:() => import('../views/main/')
},
]
},
]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
路由设置完成,运行路由加上path部分查看页面跳转。
为了实现基本的css
我们在assets下面新建css目录,在里面新建文件
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, legend, input, textarea, button, p, blockquote, th, td
{margin: 0;padding: 0;}
body{text-align: center;}
li{ list-style: none;}
a{text-decoration: none;}
input,button,img{border: none}
.active{
color:#409EFF
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
并且记得在里面导入这个css文件
登录UI实现
路由限制(没有登陆时不得进入主页)
在router目录下建立
import router from "./index"; //引入路由对象 在文件里面export default router
//用户未登录时,无法进入这个首页,所以要设置路由权限
router.beforeEach((to, from, next) => {
if (to.meta.isLogin) {
let token = false;
if (token) {
// 已经登陆直接进入
next();
} else {
next({
// 没有登陆就改变路由进入登录界面
name:'LoginView'
})
}
} else {
next();
}
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
在里面添加引用
import './router/permission'
- 1
登录注册页面(写到同一个页面上)
在最外层写一层div确定登陆注册框大概位置,在里面写一层elementUI里面的card,并确定好其大小。
lang='less’会报错,npm安装依赖即可
//html部分
<template>
<!-- 登录注册界面,与Layout级别一样 -->
<div class="login">
<el-card class="box-card">
<!-- slot插槽,可以往里面填写想要的内容 -->
<div slot="header" class="clearfix">
<span>Ego商城</span>
</div>
内容部分
</el-card>
</div>
</template>
//css样式
<style scoped lang='less'>
// lang=“less” 可以敲层级关系的样式
// scoped 使style内的样式只作用于当前的界面
.login{
width: 1200px;
margin:0 auto;
.box-card{
width: 500px;
margin: 100px auto;
}
}
</style>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
希望能够展现出使用tab键更改登陆注册页面的功能,所以找到tabs标签页组件,将其写入内容部分。
因为tabs标签绑定了currentIndex代表当前页面对应内容,所以我们要在data里添加currentIndex,currentIndex是与name值绑定的,我们设其默认值为login,默认进入登录界面。
<template>
<!-- 登录注册界面,与Layout级别一样 -->
<div class="login">
<el-card class="box-card">
<!-- slot插槽,可以往里面填写想要的内容 -->
<div slot="header" class="clearfix">
<span>Ego商城</span>
</div>
<el-tabs v-model="currentIndex" :stretch='true'>
<!-- 与name相对应,v-model双向绑定 -->
<el-tab-pane label="登录" name="login">
登录
</el-tab-pane>
<el-tab-pane label="注册" name="register">
注册
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
data(){
return{
currentIndex:'login',
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
往登录内容里面写入表单,从elementUI里面找表单即可。
把登陆内容改成如下内容
解释下表单的属性,model双向绑定LoginForm表单对象内容,rules代表对每个输入值的判定规则
status icon是表单后面的小叉号,不满足条件执行回调函数
ref是指的获取这个dom元素,点击提交时绑定的方法参数就是ref设定的值
prop设置的需要被校验的字段名
<el-form :model="LoginForm" :rules="rules" status-icon ref="LoginForm">
<!-- ref与model绑定对象要是相同的 -->
<el-form-item label="用户名" label-width="80px" prop="username">
<!-- prop代表的是验证规则 -->
<el-input type="text" v-model=""></el-input>
</el-form-item>
<el-form-item label="密码" label-width="80px" prop="password">
<el-input type="password" v-model=""></el-input>
</el-form-item>
<el-form-item >
<!-- 此处点击事件传的时ref的对象 -->
<el-button type="primary" @click="submitForm('LoginForm')">提交</el-button>
</el-form-item>
</el-form>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
这其中不仅绑定了数据,也绑定了方法
具体用法如下
首先把LoginForm对象写入data内部
LoginForm:{
password:'',
username:'',
},
- 1
- 2
- 3
- 4
写字段校验的方法:
// 用户名验证规则
var validateUserName=(rule,value,callback)=>{
if(value===''){
callback(new Error('请输入用户名'));
}else if(value.length<6){
callback(new Error('用户名太短'))
}
else{
callback();
}
}
// 密码验证规则
var validatePassWord=(rule,value,callback)=>{
if(value===''){
callback(new Error('请输入密码'));
}else{
callback();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
将rules写入data,使得字段名对应自己的校验方法
rules:{
//验证规则对应表示
username:[
{
validator: validateUserName,trigger: 'blur'
}
],
password:[
{
validator: validatePassWord,trigger: 'blur'
}
]
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
最后在方法里面写入绑定的点击事件submitForm函数
submitForm(formName){
this.$refs[formName].validate((valid)=>{
if(valid){
console.log(this.LoginForm);
}else{
return;
}
})
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
注册UI实现与登陆相似
表单内容添加一个确认密码输入框,并为其添加确认密码验证规则
<el-form-item label="确认密码" label-width="80px" prop="checkPassword">
<el-input type="password" v-model=""></el-input>
</el-form-item>
- 1
- 2
- 3
//确认密码验证规则
var checkPass=(rule,value,callback)=>{
if(value===''){
callback(new Error('请输入密码'));
}else if(value !=this.RegisterForm.password){
callback(new Error('两次输入密码不一致'))
}
else{
callback();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
rules部分添加确认密码的校验规则
rules:{
//验证规则对应表示
username:[
{
validator: validateUserName,trigger: 'blur'
}
],
password:[
{
validator: validatePassWord,trigger: 'blur'
}
],
checkPassword:[
{
validator: checkPass,trigger: 'blur'
}
]
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
此时出现的问题是我们之前提交之后输出的都是登陆的信息,想要根据不同标签输出不同的对象值,需要在tabs标签上添加点击事件
<el-tabs v-model="currentIndex" :stretch='true' @tab-click="handleTabsClick">
- 1
用来获取当前处于哪个标签上
首先在data上添加一个activetabs变量,默认值设置为login,点击标签改变当前的标签值
handleTabsClick(tab){
this.activeTab=tab.name
}
- 1
- 2
- 3
对应的标签输出对应的对象值内容
submitForm(formName){
this.$refs[formName].validate((valid)=>{
if(valid){
if(this.activeTab==='login'){
console.log(this.LoginForm);
}
if(this.activeTab==='register'){
console.log(this.RegisterForm)
}
}else{
return;
}
})
},
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
目前为止,登陆注册的前端页面已经基本实现
登录注册后台接口
小细节很多,注意不要出错
首先在ego目录下建立server目录,用来存放后台信息,与前端的src文件是独立开来的
里面建立三个文件,index(建立express对象,处理跨域及端口)、router(不同路由,调用不同方法)和config(连接数据库以及设置数据库操作方法)
事先需要安装好express、mysql、jsonwebtoken、cors、body-parse
直接使用npm install --save 依赖名
1.连接数据库(数据库里的表是直接导入进mysql的,所以不需要自己手写表格)
//
//调用mysql
const mysql = require('mysql');
//连接数据
const client = mysql.createConnection({
host: 'localhost',
user: 'root', //数据库用户名
password: '1234', //数据库密码
database:'ego' //数据库的名字
})
// 传一个sql语句、arr数组参数以及回调函数
const sqlClient = (sql, arr, callback) => {
client.query(sql, arr, (error, result) => {
if (error) {
console.log(error);
return;
} else {
callback(result);
// 没错就直接返回result值
}
})
}
module.exports = sqlClient```
2.定义注册登录前后端交互方法
```javascript
//
const express = require('express');
const router = express.Router();
//引入数据库方法
const sqlClient = require('./config')
// 前端响应
const jwt=require('jsonwebtoken')
// 注册
router.post('/register', (req, res) => {
const { username, password, email } = req.body;
const sql = 'insert into user values(null,?,?,?)';
const arr = [username, password, email]
sqlClient(sql, arr, result => {
// 影响行数大于0则代表插入成功了
if (result.affectedRows > 0) {
res.send({
status: 200,
msg:'注册成功'
})
} else {
res.send({
status: 401,
msg:'注册失败'
})
}
})
})
// 登录
router.post('/login', (req, res) => {
const { username, password } = req.body;
const sql = 'select * from user where username=? and password=?'
const arr = [username, password];
sqlClient(sql, arr, result => {
if (result.length > 0) {
//登陆成功获得token
let token = jwt.sign({
username,
id:result[0].id
}, 'somekeys')
res.send({
status: 200,
token,
username
})
} else {
res.send({
status: 401,
msg:'登陆失败'
})
}
})
})
module.exports = router
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
3.调用express对象,应用路由、设置端口以及跨域处理
//
// 引入express和app
const express = require('express');
const app = express();
// 引入跨域处理
const cors = require('cors');
// express获取post传参数据
const bodyParser = require('body-parser')
// 路由对象
const router = require('./router')
app.use(cors());
app.use(bodyParser.urlencoded({
extended:true
}))
// 前面添加/api就可以加载router,进行跨域了
app.use('/api', router);
//后端响应端口
app.listen(9090, () => {
console.log(9090);
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
后端基本完成,为了使前后端可以同时运行
打开,再scripts里面添加一句
"dev": "concurrently \"npm run serve\" \"nodemon server/\""
- 1
这样我们在终端运行npm run dev就会同时运行前后端服务
后台接口基本完成
前后端交互
打开src,新建目录utils里面存放工具模块,新建request.js
直接复制来即可,使用axios发送请求
//
// 网络请求
import axios from "axios"
import qs from "qs"
import router from "../router"
// import store from "../store"
const toLogin = () =>{
router.push("/login")
}
const errorHandle = (status,info) =>{
switch(status){
case 400:
console.log("服务器收到客户端通过PUT或者POST请求提交的表示,表示的格式正确,但服务器不懂它什么意思");
toLogin();
break;
case 401:
console.log("客户端试图对一个受保护的资源进行操作,却又没有提供正确的认证证书");
toLogin();
break;
case 403:
console.log("客户端请求的结构正确,但是服务器不想处理它");
toLogin();
break;
case 404:
console.log("资源被围定义(网络请求地址错误)");
break;
case 500:
console.log("执行请求处理代码时遇到了异常,它们就发送此响应代码");
break;
case 503:
console.log("最可能的原因是资源不足:服务器突然收到太多请求,以至于无法全部处理");
break;
default:
console.log(info);
break;
}
}
const instance = axios.create({
timeout:5000
})
instance.all = axios.all;
instance.spread = axios.spread
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
instance.interceptors.request.use(
config =>{
if(config.method === 'post'){
config.data = qs.stringify(config.data);
}
// 判断token是否存在,存在就添加到请求头上
// const token = ;
// if(token){
// = ;
// }
return config;
},
error => Promise.reject(error)
)
instance.interceptors.response.use(
response => response.status === 200 ? Promise.resolve(response) : Promise.reject(response) ,
error =>{
const { response } = error;
if(response){
errorHandle(response.status,response.data);
return Promise.reject(response);
}else{
console.log("请求被中断");
}
}
)
export function get(url,params){
return new Promise((resolve,reject) =>{
instance.get(url,{
params
}).then(res =>{
resolve(res.data);
}).catch(err =>{
reject(err.data);
})
})
}
export function post(url,params){
return new Promise((resolve,reject) =>{
instance.post(url,params).then(res =>{
resolve(res.data)
}).catch(err =>{
reject(err.data)
})
})
}
export default instance
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
打开登录注册页面,LoginView页面,引入api以及vuex
建立了vuex,在store下面建立modules目录,新建
//
// 写vuex结构
export default {
namespaced: true,
state: {
user: {
username: "",
token:"",
}
},
mutations: {
setUser(state, user) {
state.user=user
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
外面的index引入login
import Vue from 'vue'
import Vuex from 'vuex'
import login from './modules/login'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
login
}
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
在LoginView界面引入api和vuex
import api from '../api'
import { mapMutations } from 'vuex'
- 1
- 2
methods: {
...mapMutations('login',['setUser']),
submitForm(formName){
this.$refs[formName].validate((valid)=>{
if(valid){
if(this.activeTab==='login'){
// 登录
api.login(this.LoginForm).then(res => {
if(res.data.status===200){
this.setUser(res.data);
localStorage.setItem('ego',JSON.stringify(res.data));
// 登陆成功后跳转首页
this.$router.push('/')
}else{
const h = this.$createElement;
this.$notify({
title:"登陆失败",
message: h('i','用户名密码错误'),
});
}
})
}
if(this.activeTab==='register'){
// 注册
api.register(this.RegisterForm).then(res =>{
if(res.data.status ===200){
const h = this.$createElement;
this.$notify({
title:'注册成功',
message: h('i','请前往登陆页面登录'),
});
}else{
const h = this.$createElement;
this.$notify({
title:'注册失败',
message: h('i','请重新注册'),
});
}
})
}
}else{
return;
}
})
},
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
解决强制刷新问题,登陆后再进入该界面需要直接跳转到主页,首先再utils里面新建文件
// 解决强制刷新的问题,登陆后再进入页面直接跳转到主页面
import store from "@/store";
if (localStorage.getItem('ego')) {
store.commit('login/setUser',JSON.parse(localStorage.getItem('ego')))
}
- 1
- 2
- 3
- 4
- 5
- 6
在main里面引用这个init
import './utils/init'
- 1
router的permission里面改变token内容
import router from "./index"; //引入路由对象 在文件里面export default router
//用户未登录时,无法进入这个首页,所以要设置路由权限
import store from '../store'
router.beforeEach((to, from, next) => {
if (to.meta.isLogin) {
let token = store.state.login.user.token;
if (token) {
// 已经登陆直接进入
next();
} else {
next({
// 没有登陆就改变路由进入登录界面
name:'LoginView'
})
}
} else {
next();
}
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
到此为止登陆注册页面已经完成