客户使用的业务服务:PC端,小程序,移动web,移动app
管理员使用的业务服务:PC后台管理端。
PC后台管理端的功能:管理用户账号(登录,退出,用户管理,权限管理),商品管理(商品分类,分类参数,商品信息,订单),数据统计
电商后台管理系统采用前后端分离的开发模式
前端项目是基于Vue的SPA(单页应用程序)项目
前端技术栈:Vue,Vue-Router,Vuex,Element-UI,Axios,Echarts
后端技术栈:Node.js,Express,Jwt(模拟session),MySQL,Sequelize(操作数据库的框架)
A.安装Vue脚手架
B.通过脚手架创建项目 命令:vue ui
C.配置路由、Vuex
D.配置Element-UI:在插件中安装,搜索vue-cli-plugin-element
E.配置Axios:在依赖中安装,搜索axios(运行依赖)
F.初始化git
G.将本地项目托管到github或者gitee(码云)中
B.安装git
在Windows上使用Git,可以从Git官网直接下载安装程序进行安装。
测试命令:git --version
C.点击网站右上角“登录”,登录码云,并进行账号设置
D.在本地创建公钥:在终端运行:ssh-keygen -t rsa -C “[email protected]”
E.找到公钥地址:
Your identification has been saved in /c/Users/My/.ssh/id_rsa.
Your public key has been saved in /c/Users/My/.ssh/id_rsa.pub.
当我们创建公钥完毕之后,请注意打印出来的信息“Your public key has been saved in”
/c/Users/My/.ssh/id_rsa.pub : c盘下面的Users下面的My下面的.ssh下面的id_rsa.pub就是我们创建好的公钥了
E.打开id_rsa.pub文件,复制文件中的所有代码,点击码云中的SSH公钥,将生成的公钥复制到公钥中
G.测试公钥:打开终端,输入命令
ssh -T [email protected]
H.将本地代码托管到码云中
点击码云右上角的+号->新建仓库
I.进行git配置:
打开项目所在位置的终端,进行git仓库关联
A.安装mysql数据库,并导入创建数据库(mydb),执行mydb.sql文件
打开MySQL命令行,执行以下代码:
mysql> create database mydb; # 创建数据库
mysql> use mydb; # 使用已创建的数据库
mysql> source /Vue电商项目相关资料/vue_api_server/db/mydb.sql # 导入备份数据库
B.安装nodeJS,配置后台项目,从终端打开后台项目vue_api_server
然后在终端中输入命令安装项目依赖包:npm install
启动项目命令:node app.js
C.安装postman,使用postman测试api接口
postman下载路径:
https://www.postman.com/downloads/?utm_source=postman-home
A.登录状态保持
如果服务器和客户端同源,建议可以使用cookie或者session来保持登录状态
如果客户端和服务器跨域了,建议使用token进行维持登录状态。
B.登录逻辑:
在登录页面输入账号和密码进行登录,将数据发送给服务器
服务器返回登录的结果,登录成功则返回数据中带有token
客户端得到token并进行保存,后续的请求都需要将此token发送给服务器,服务器会验证token以保证用户身份。
在项目src目录下新建utils文件夹,然后在其中新建 request.js文件。 需要什么就配什么!
// 1.引入axios
// 2.axios.create方法创建实例
// 3.使用实例对象创建请求拦截器
// 4.使用实例创建响应拦截器
// 5.export抛出实例对象
import axios from 'axios'
// import store from '@/store'
// 1. 创建新的axios实例
const request = axios.create({
// 公共接口--这里注意后面会讲
baseURL: 'http://127.0.0.1:8888/api/private/v1/',
// 超时时间 单位是ms,这里设置了3s的超时时间
timeout: 3 * 1000
})
// request拦截器
request.interceptors.request.use(
config => {
// 发请求前做的一些处理,数据转化,配置请求头,设置token,设置loading等,根据需求去添加
config.data = JSON.stringify(config.data) // 数据转化,也可以使用qs转换
config.headers = {
'Content-Type': 'application/json' // 配置请求头
}
// 注意使用token的时候需要用localStorage方法来获取
const token = localStorage.getItem('token') // 这里取token之前,你肯定需要先拿到token,存一下
if (token) {
config.headers.Authorization = token // 如果要求携带在请求头中
}
return config
}, error => {
Promise.reject(error)
})
// response拦截器
request.interceptors.response.use(
response => {
// 接收到响应数据并成功后的一些共有的处理,关闭loading等
return response
}, error => {
/* 接收到异常响应的处理开始 */
if (error && error.response) {
// 1.公共错误处理
// 2.根据响应码具体处理
switch (error.response.status) {
case 400:
error.message = '错误请求'
break
case 401:
error.message = '未授权,请重新登录'
break
case 403:
error.message = '拒绝访问'
break
case 404:
error.message = '请求错误,未找到该资源'
// window.location.href = '/NotFound'
break
case 405:
error.message = '请求方法未允许'
break
case 408:
error.message = '请求超时'
break
case 500:
error.message = '服务器端出错'
break
case 501:
error.message = '网络未实现'
break
case 502:
error.message = '网络错误'
break
case 503:
error.message = '服务不可用'
break
case 504:
error.message = '网络超时'
break
case 505:
error.message = 'http版本不支持该请求'
break
default:
error.message = `连接错误${error.response.status}`
}
} else {
// 超时处理
if (JSON.stringify(error).includes('timeout')) {
this.$message.error('服务器响应超时,请刷新当前页')
}
error.message = '连接服务器失败'
}
this.$message.error(error.message)
/* 处理结束 */
// 如果不需要错误处理,以上的处理过程都可省略
return Promise.resolve(error.response)
})
export default request
在项目src目录下的utils文件夹中新建 http.js文件。
/* http.js */
// 导入封装好的axios实例
import request from './request'
const http = {
/**
* methods: 请求
* @param url 请求地址
* @param params 请求参数
*/
get (url, params) {
const config = {
method: 'get',
url: url
}
if (params) {
config.params = params
}
return request(config)
},
post (url, params) {
const config = {
method: 'post',
url: url
}
if (params) {
config.data = params
}
return request(config)
},
put (url, params) {
const config = {
method: 'put',
url: url
}
if (params) {
config.params = params
}
return request(config)
},
delete (url, params) {
const config = {
method: 'delete',
url: url
}
if (params) {
config.params = params
}
return request(config)
}
}
// 导出/* http.js */
// 导入封装好的axios实例
import request from './request'
const http = {
/**
* methods: 请求
* @param url 请求地址
* @param params 请求参数
*/
get (url, params) {
const config = {
method: 'get',
url: url
}
if (params) {
config.params = params
}
return request(config)
},
post (url, data) {
const config = {
method: 'post',
url: url
}
if (data) {
config.data = data
}
console.log(config)
return request(config)
},
put (url, data) {
const config = {
method: 'put',
url: url
}
if (params) {
config.data = data
}
return request(config)
},
delete (url, params) {
const config = {
method: 'delete',
url: url
}
if (params) {
config.params = params
}
return request(config)
}
}
// 导出
export default http
在项目src目录下新建api文件夹,以用户登录为案例,然后在其中新建 user.js文件
import http from '../utils/http.js'
// post请求
export function login (params) {
return http.post('login', params)
}
添加新分支login,在login分支中开发当前项目vue_shop:
打开vue_shop终端,使用git status确定当前项目状态。
确定当前工作目录是干净的之后,创建一个分支进行开发,开发完毕之后将其合并到master
git checkout -b login
然后查看新创建的分支:git branch
确定我们正在使用login分支进行开发
然后执行vue ui命令打开ui界面,然后运行serve,运行app查看当前项目效果
打开项目的src目录,点击main.js文件(入口文件)进行更改
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './plugins/element.js'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
再打开App.vue(根组件),将根组件的内容进行操作梳理(template中留下根节点,script中留下默认导出,去掉组件,style中去掉所有样式)
<template>
<div id="app">
<router-view>router-view>
div>
template>
<script>
export default {
name: 'app'
}
script>
<style>
style>
再打开router.js(路由),将routes数组中的路由规则清除,然后将views删除,将components中的helloworld.vue删除
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
]
})
在components文件夹中新建Login.vue组件,添加template,script,style标签,style标签中的scoped可以防止组件之间的样式冲突,没有scoped则样式是全局的
<template>
<div class="login_container">
div>
template>
<script>
export default {
}
script>
<style lang="scss" scoped>
.login_container {
background-color: #2b5b6b;
height: 100%;
}
style>
在router.js中导入组件并设置规则
在App.vue中添加路由占位符
const router = new Router({
routes: [
{ path: '/', redirect: '/login' },
{ path: '/login', component: Login }
]
})
当我们给Login.vue中的内容添加样式的时候,会报错“缺少sass-loader”,需要配置sass加载器(开发依赖),安装sass(开发依赖)
cnpm install [email protected]
cnpm i [email protected] // 安装
// 如果node-sass安装失败,也可以直接在package.json中写上版本号,删除node_modules文件夹,重新npm i即可
然后需要添加公共样式,安装normalize.css
npm install normalize.css -S
在main.js中我们引入normalize.css样式:import 'normalize.css/normalize.css'
然后Login.vue中的根元素也需要设置撑满全屏(height:100%)
最终Login.vue文件中的代码如下
<template>
<div>
<ul class="bg-bubbles">
<li v-for="(item, index) in bubbles" :key="index">li>
ul>
<el-card class="box-card">
<h2>登录h2>
<el-form :model="loginForm" :rules="rules" ref="LoginFormRef">
<el-form-item prop="username">
<el-input v-model="loginForm.username" prefix-icon="el-icon-user">el-input>
el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="loginForm.password" prefix-icon="el-icon-lock">el-input>
el-form-item>
<el-form-item class="btns">
<el-button type="primary" @click="submitLogin('LoginFormRef')">登录el-button>
<el-button type="info" @click="resetLogin('LoginFormRef')">重置el-button>
el-form-item>
el-form>
el-card>
div>
template>
<script>
import {
login
} from '@/api/user'
export default {
data() {
return {
// 数据绑定
bubbles: [], // 背景样式
loginForm: {
username: '',
password: ''
},
// 表单验证规则
rules: {
username: [{
required: true,
message: '*请输入用户名',
trigger: 'blur'
},
{
min: 3,
max: 10,
message: '*用户名长度在3到10个字符',
trigger: 'blur'
}
],
password: [{
required: true,
message: '*请输入密码',
trigger: 'blur'
},
{
min: 6,
max: 18,
message: '*密码长度在6到18个字符',
trigger: 'blur'
}
]
}
}
},
created() {
this.bubbles.length = 10
},
methods: {
submitLogin(LoginFormRef) { // 提交表单
// 点击登录的时候先调用validate方法验证表单内容是否有误
this.$refs.LoginFormRef.validate(valid => {
if (valid) {
login(this.loginForm).then(res => {
if (res.data.meta.status !== 200) {
return this.$message.error(res.data.meta.msg)
}
// 保存token,存储到localStorage以及vuex中
this.$store.commit('login', res.data.data.token)
this.$message.success(res.data.meta.msg)
// 导航至/home
this.$router.push('/home').catch(() => {})
}).catch(err => {
console.log(err)
})
} else {
this.$message.error('登录失败')
}
})
},
resetLogin(LoginFormRef) { // 重置表单
// this=>当前组件对象,其中的属性$refs包含了设置的表单ref
this.$refs[LoginFormRef].resetFields()
}
}
}
script>
<style lang="scss" scoped>
.bg-bubbles {
background-image: linear-gradient(to bottom right, rgb(41, 174, 165), rgb(3, 234, 167));
margin: 0px;
position: absolute;
width: 97%;
height: 100%;
overflow: hidden;
li {
position: absolute;
// bottom 的设置是为了营造出气泡从页面底部冒出的效果;
bottom: -100px;
// 默认的气泡大小;
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.3);
list-style: none;
// 使用自定义动画使气泡渐现、上升、下降和翻滚;
animation: square 15s infinite;
transition-timing-function: linear;
// 分别设置每个气泡不同的位置、大小、透明度和速度,以显得有层次感;
&:nth-child(1) {
left: 5%;
}
&:nth-child(2) {
left: 10%;
width: 90px;
height: 90px;
animation-delay: 2s;
animation-duration: 7s;
}
&:nth-child(3) {
left: 15%;
animation-delay: 4s;
}
&:nth-child(4) {
left: 30%;
width: 60px;
height: 60px;
animation-duration: 8s;
background-color: rgba(255, 255, 255, 0.3);
}
&:nth-child(5) {
left: 60%;
}
&:nth-child(6) {
right: 10%;
width: 120px;
height: 120px;
animation-delay: 3s;
background-color: rgba(255, 255, 255, 0.2);
}
&:nth-child(7) {
left: 22%;
width: 160px;
height: 160px;
animation-delay: 2s;
}
&:nth-child(8) {
left: 75%;
width: 20px;
height: 20px;
animation-delay: 4s;
animation-duration: 15s;
}
&:nth-child(9) {
left: 25%;
width: 100px;
height: 100px;
animation-delay: 2s;
animation-duration: 12s;
background-color: rgba(255, 255, 255, 0.3);
}
&:nth-child(10) {
right: 15%;
width: 80px;
height: 80px;
animation-delay: 5s;
}
}
// 自定义 square 动画;
@keyframes square {
0% {
opacity: 0.5;
transform: translateY(0px) rotate(45deg);
}
25% {
opacity: 0.75;
transform: translateY(-400px) rotate(90deg)
}
50% {
opacity: 1;
transform: translateY(-800px) rotate(135deg);
}
100% {
opacity: 0;
transform: translateX(-150px) rotate(180deg);
}
}
}
.box-card {
width: 360px;
position: absolute;
top: 25%;
left: 34%;
padding: 0 20px;
background-color: #fff;
opacity: 0.8;
}
.btns {
display: flex;
justify-content: flex-end;
}
style>
其中我们有用到一下内容,需要进行进一步处理:
A.添加element-ui的表单组件
在plugins文件夹中打开element.js文件,进行elementui的按需导入
import { Form, FormItem } from 'element-ui'
import { Input } from 'element-ui'
import { Button } from 'element-ui'
import { Icon } from 'element-ui'
import { Card } from 'element-ui'
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)
Vue.use(Button)
Vue.use(Icon)
Vue.use(Card)
B.添加表单验证的步骤
1).给添加属性:rules=“rules”,rules是一堆验证规则,定义在script、中
2).在script中添加rules:
export default{
data(){
return{......,
rules: {
username: [
{ required: true, message: '*请输入用户名', trigger: 'blur' },
{ min: 3, max: 10, message: '*用户名长度在3到10个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '*请输入密码', trigger: 'blur' },
{ min: 6, max: 18, message: '*密码长度在6到18个字符', trigger: 'blur' }
]
}......
3).通过的prop属性设置验证规则
5.配置弹窗提示:
在plugins文件夹中打开element.js文件,进行elementui的按需导入
import {Message} from ‘element-ui’
进行全局挂载:Vue.prototype.$message = Message;
在login.vue组件中编写弹窗代码:this.$message.error(‘登录失败’)
A.登录成功之后,需要将后台返回的token保存通过状态管理保存token(登录状态)操作完毕之后,需要跳转到/home
export default {
state: {
token: '' // 定义token
},
mutations: {
login: function(state, token) {
state.token = token
localStorage.setItem('token', token) // 保存token
},
logout: function(state) {
localStorage.removeItem('token') // 删除token
}
}
}
修改stroe目录下的index.js文件,添加user模块
import Vue from 'vue'
import Vuex from 'vuex'
import user from '@/store/modules/user.js'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
user
}
})
submitLogin(LoginFormRef) { // 提交表单
// 点击登录的时候先调用validate方法验证表单内容是否有误
this.$refs.LoginFormRef.validate(valid => {
if (valid) {
login(this.loginForm).then(res => {
if (res.data.meta.status !== 200) {
return this.$message.error(res.data.meta.msg)
}
// 保存token,存储到localStorage以及vuex中
this.$store.commit('login', res.data.data.token)
this.$message.success(res.data.meta.msg)
// 导航至/home
this.$router.push('/home').catch(() => {})
}).catch(err => {
console.log(err)
})
} else {
this.$message.error('登录失败')
}
})
添加一个组件Home.vue,并为之添加规则
<template>
<div>
this is home
<el-button type="info" @click="logout"> 退出 el-button>
div>
template>
<script>
export default {
methods: {
logout() {
sessionStorage.clear()
this.$router.push('/login')
}
}
}
script>
<style lang="scss" scoped>
style>
添加路由规则
const routes = [
{ path: '/', redirect: '/login' },
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/home',
name: 'home',
component: Home
}
]
如果用户没有登录,不能访问/home,如果用户通过url地址直接访问,则强制跳转到登录页面
打开router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '@/views/Login.vue'
import Home from '@/views/Home.vue'
Vue.use(VueRouter)
const routes = [
{ path: '/', redirect: '/login' },
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/home',
name: 'home',
component: Home
}
]
const router = new VueRouter({
routes
})
// 挂载路由导航守卫,to表示将要访问的路径,from表示从哪里来,next是下一个要做的操作
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
return next()
}
// 获取token
const token = localStorage.getItem('token')
console.log(token)
if (!token) {
return next('/login')
}
next()
})
export default router
在Home组件中添加一个退出功能按钮,给退出按钮添加点击事件,添加事件处理代码如下:
export default {
methods:{
logout(){
window.sessionStorage.clear();
this.$router.push('/login');
}
}
}
A.处理ESLint警告
打开脚手架面板,查看警告信息
默认情况下,ESLint和vscode格式化工具有冲突,需要添加配置文件解决冲突。
在项目根目录添加 .prettierrc 文件
{
"semi":false,
"singleQuote":true
}
打开.eslintrc.js文件,禁用对 space-before-function-paren 的检查:
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'space-before-function-paren' : 0
},
B.合并按需导入的element-ui
import Vue from 'vue'
import { Form, FormItem, Input, Button, Icon, Card, Message } from 'element-ui'
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)
Vue.use(Button)
Vue.use(Icon)
Vue.use(Card)
Vue.prototype.$message = Message
新建一个项目终端,输入命令‘git status’查看修改过的与新增的文件内容
将所有文件添加到暂存区:git add .
将所有代码提交到本地仓库:git commit -m “添加登录功能以及/home的基本结构”
查看当前分支: git branch 发现所有代码都被提交到了login分支
将login分支代码合并到master主分支,先切换到master:git checkout master
在master分支进行代码合并:git merge login
将本地的master推送到远端的码云:git push
推送本地的子分支到码云,先切换到子分支:git checkout 分支名
然后推送到码云:git push -u origin 远端分支名