Vue+ElementUI电商项目(一)

1.电商业务概述

客户使用的业务服务:PC端,小程序,移动web,移动app
管理员使用的业务服务:PC后台管理端。
PC后台管理端的功能:管理用户账号(登录,退出,用户管理,权限管理),商品管理(商品分类,分类参数,商品信息,订单),数据统计
电商后台管理系统采用前后端分离的开发模式
前端项目是基于Vue的SPA(单页应用程序)项目

前端技术栈:Vue,Vue-Router,Vuex,Element-UI,Axios,Echarts
后端技术栈:Node.js,Express,Jwt(模拟session),MySQL,Sequelize(操作数据库的框架)

2.项目初始化

A.安装Vue脚手架
B.通过脚手架创建项目 命令:vue ui
C.配置路由、Vuex
D.配置Element-UI:在插件中安装,搜索vue-cli-plugin-element
E.配置Axios:在依赖中安装,搜索axios(运行依赖)
F.初始化git
G.将本地项目托管到github或者gitee(码云)中

3.码云相关操作

A.注册登录码云账号
Vue+ElementUI电商项目(一)_第1张图片

B.安装git
在Windows上使用Git,可以从Git官网直接下载安装程序进行安装。
测试命令:git --version

C.点击网站右上角“登录”,登录码云,并进行账号设置
Vue+ElementUI电商项目(一)_第2张图片
D.在本地创建公钥:在终端运行:ssh-keygen -t rsa -C “[email protected]
Vue+ElementUI电商项目(一)_第3张图片
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公钥,将生成的公钥复制到公钥中
Vue+ElementUI电商项目(一)_第4张图片
Vue+ElementUI电商项目(一)_第5张图片
G.测试公钥:打开终端,输入命令
ssh -T [email protected]
Vue+ElementUI电商项目(一)_第6张图片
H.将本地代码托管到码云中
点击码云右上角的+号->新建仓库
Vue+ElementUI电商项目(一)_第7张图片
Vue+ElementUI电商项目(一)_第8张图片

I.进行git配置:
Vue+ElementUI电商项目(一)_第9张图片
打开项目所在位置的终端,进行git仓库关联
在这里插入图片描述

4.配置后台项目

A.安装mysql数据库,并导入创建数据库(mydb),执行mydb.sql文件
Vue+ElementUI电商项目(一)_第10张图片
打开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
Vue+ElementUI电商项目(一)_第11张图片

5.实现登录功能

A.登录状态保持
如果服务器和客户端同源,建议可以使用cookie或者session来保持登录状态
如果客户端和服务器跨域了,建议使用token进行维持登录状态。
B.登录逻辑:
在登录页面输入账号和密码进行登录,将数据发送给服务器
服务器返回登录的结果,登录成功则返回数据中带有token
客户端得到token并进行保存,后续的请求都需要将此token发送给服务器,服务器会验证token以保证用户身份。
Vue+ElementUI电商项目(一)_第12张图片

接口管理

封装axios实例 —— request.js

在项目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

封装请求——http.js

在项目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

封装API——用于发送请求

在项目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+ElementUI电商项目(一)_第13张图片
然后执行vue ui命令打开ui界面,然后运行serve,运行app查看当前项目效果
Vue+ElementUI电商项目(一)_第14张图片

登录组件

打开项目的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(‘登录失败’)

通过状态管理保存token(登录状态)

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 远端分支名

你可能感兴趣的:(Vue.js,vue.js,elementui,前端)