最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • DayNode(VueShopEleM)

    正文概述 掘金(Jean)   2021-08-22   423

    前期配置-后端

    vue脚手架

    基于ui界面创建Vue项目

    终端输入:

    vue ui
    

    Vue脚手架的自定义配置

    A.通过 package.json 进行配置 [不推荐使用]
        "vue":{
            "devServer":{
                "port":"9990",
                "open":true
            }
        }
    B.通过单独的配置文件进行配置,创建vue.config.js
        module.exports = {
            devServer:{
                port:8888,
                open:true
            }
        }
    

    Element-UI

    官网

    A.安装: --终端
    npm install element-ui -S 
    
    B.导入使用: --main.js
    import ElementUI from "element-ui"; 
    import "element-ui/lib/theme-chalk/index.css";
    
    // 全局注册
    Vue.use(ElementUI)
    

    后台配置

    DayNode(VueShopEleM)

    配置mysql文件

    DayNode(VueShopEleM) 代码运行完后,点左侧右上角 刷新 按钮

    启动后台 app.js

    DayNode(VueShopEleM)

    DayNode(VueShopEleM)

    postman测试数据

    DayNode(VueShopEleM)

    项目配置-前端

    登录token

    DayNode(VueShopEleM)

    登录状态

    如果服务器和客户端同源,建议可以使用cookie或者session来保持登录状态

    如果客户端和服务器跨域了,建议使用token进行维持登录状态。

    路由router

    1. 下载插件
    yarn add vue-router
    
    1. 新建scr/router/index.js 文件,配置路由并导出
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    // 导入组件
    import Login from '@/components/Login.vue'
    import Home from '@/components/Home.vue'
    import Welcome from '@/components/Welcome.vue'
    import Users from '@/components/user/Users.vue'
    import Rights from '@/components/right/Rights.vue'
    import Roles from '@/components/right/Roles.vue'
    
    // 全局 注册路由组件
    Vue.use(VueRouter)
    
    //  创建路由规则
    const routes = [{
            path: '/',
            redirect: '/login'
        },
        {
            path: '/login',
            component: Login
        },
        {
            path: '/home',
            component: Home,
            redirect: '/welcome',
            children: [{
                    path: '/welcome', //根路径
                    component: Welcome
                },
                {
                    path: '/users',
                    component: Users
                },
                {
                    path: '/rights',
                    component: Rights
                },
                {
                    path: '/roles',
                    component: Roles
                }
            ]
        }
    ]
    const router = new VueRouter({
        routes
    })
    
    //挂载路由导航守卫,to表示将要访问的路径,from表示从哪里来,next是下一个要做的操作
    router.beforeEach((to, from, next) => {
        if (to.path == '/login') return next()
    
        // 获取token
        const tokenStr = sessionStorage.getItem('token')
    
        // 如果没有token
        if (!tokenStr) return next('/login')
    
        next()
    })
    
    // 创建 并导出 路由管理器对象
    export default router
    
    1. main.js中引入并挂在路由
    import Vue from 'vue'
    import App from './App.vue'
    
    // 导入路由管理器
    import router from '@/router'
    
    new Vue({
        router, // 注册路由
        render: h => h(App)
    }).$mount('#app')
    

    api接口配置

    1. 下载axios插件
    yarn add axios
    
    1. 新建src/utils/request.js文件,进行请求基本配置
    // 导入axios
    import axios from 'axios'
    // 设置请求的根路径
    axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
    
    //请求在到达服务器之前,先会调用use中的这个回调函数来添加请求头信息
    axios.interceptors.request.use(config => {
        //为请求头对象,添加token验证的Authorization字段
        config.headers.Authorization = window.sessionStorage.getItem('token')
        return config
    })
    
    // 导出
    export default axios
    
    1. 新建src/api/Login.js,配置登录相关的api请求,并导出
    // 提供登录 注册 相关的api方法
    import axios from '@/utils/request.js'
    
    // 导出login请求
    export const login = data =>
        axios({
            url: 'login',
            method: 'post',
            data
        })
    
    1. 新建src/api/index.js,汇总所有请求
    import { login } from '@/api/Login.js'
    import { getMenus } from '@/api/Home.js'
    
    export const loginAPI = login
    export const getMenusAPI = getMenus
    
    1. src/components/Login.vue组件中 按需导入组件并使用api
    <script>
    // 按需导入 api中的方法
    import { loginAPI } from '@/api'
    export default {}
    </script>
    

    Element UI

    按需引入

    1. 下载插件
    npm i element-ui -S
    

    2.新建 src/plugs/element.js 文件

    import Vue from 'vue'
    
    // 按需导入  组件
    import { Button, Form, FormItem, Input, Message } from 'element-ui'
    
    Vue.use(Form)
    Vue.use(FormItem)
    Vue.use(Input)
    
    // 进行全局挂载:
    Vue.prototype.$message = Message
    
    1. main.js中引入 样式
    import Vue from 'vue'
    import App from './App.vue'
    
    // 引入elementUI组件和样式文件
    import 'element-ui/lib/theme-chalk/index.css'
    import '@/plugs/element.js'
    
    // 引入其他样式
    // 引入全局样式
    import '@/assets/css/global.css'
    // 引入字体样式
    import '@/assets/fonts/iconfont.css'
    // 导入 第三方插件vue-table-with-tree-grid 展示插件
    import TreeTable from 'vue-table-with-tree-grid'
    

    Container 布局容器

    DayNode(VueShopEleM)

    1. 后台api请求数据
    {
        "data":
            {
                "id": 101,
                "authName": "商品管理",
                "path": null,
                "children": [
                    {
                        "id": 104,
                        "authName": "商品列表",
                        "path": null,
                        "children": []
                    }
                ]
            }
        "meta": {
            "msg": "获取菜单列表成功",
            "status": 200
        }
    }
    
    1. Home.vue
    <template>
      <div>
        <el-container class="home-container">
          <!-- 头部区域 -->
          <el-header>
            <!-- 左侧logo -->
            <div class="left">
              <div class="logo">J.</div>
              <span>Jeanhome</span>
            </div>
            <!-- 右侧 退出登录button -->
            <el-button type="info" @click="logout" plain round size="mini">退出</el-button>
          </el-header>
          <!-- 页面主体区域 -->
          <el-container>
            <!-- 侧边栏 ------------------------------------------------------- -->
            <el-aside :width="isCollapse?'64px':'200px'">
              <!-- 伸缩侧边栏按钮 -->
              <div class="toggle-button" @click="isCollapse=!isCollapse">
                <i :class="isCollapse?'el-icon-moon-night':'el-icon-cloudy-and-sunny'"></i>
              </div>
              <!-- 侧边栏菜单 -->
              <el-menu
                background-color="#fafafa"
                text-color="#B4B4B4"
                active-text-color="#f7d182"
                unique-opened
                :collapse="isCollapse"
                :collapse-transition="false"
                :router="true"
                :default-active="this.activePath"
              >
                <!-- 一级菜单 -->
                <!-- 注意:绑定的index必须是 字符串 -->
                <el-submenu
                  :index="item.id.toString()"
                  :key="item.id"
                  v-for="item in menuList"
                  class="first-item"
                  active-background-color="#fff"
                >
                  <!-- 一级菜单模板 -->
                  <template slot="title">
                    <!-- 图标 -->
                    <i :class="item.iconclass"></i>
                    <!-- 文本 -->
                    <span>{{item.authName}}</span>
                  </template>
                  <!-- 二级子菜单 -->
                  <el-menu-item
                    :index="'/'+subItem.path"
                    :key="subItem.id"
                    v-for="subItem in item.children"
                    @click="changePath('/'+subItem.path)"
                  >
                    <!-- 二级菜单模板 -->
                    <template slot="title">
                      <!-- 图标 -->
                      <i class="el-icon-set-up"></i>
                      <!-- 文本 -->
                      <span>{{subItem.authName}}</span>
                    </template>
                  </el-menu-item>
                </el-submenu>
              </el-menu>
            </el-aside>
            <!-- 主体结构 -->
            <el-main>
              <!-- 路由占位符 -->
              <router-view></router-view>
            </el-main>
          </el-container>
        </el-container>
      </div>
    </template>
    
    <script>
    import { getMenusAPI } from '@/api'
    export default {
      data() {
        return {
          // 左侧菜单数据
          menuList: [],
          isCollapse: true, // 是否合并左侧菜单栏,false-展开,true-合并
          activePath: null // 当前激活菜单的path
        }
      },
      async created() {
        // 获取 菜单列表的数据
        const { data: res } = await getMenusAPI()
        if (res.meta.status !== 200) return this.$message.error(res.meta.msg)
    
        this.menuList = res.data
    
        // 读取本地缓存的菜单path
        this.activePath = window.sessionStorage.getItem('activePath')
      },
      methods: {
        // 点击退出 button
        logout() {
          window.sessionStorage.clear()
          this.$router.push('/login')
        },
        // 点击菜单,切换并保存 当前点击菜单 的index(path)
        changePath(path) {
          window.sessionStorage.setItem('activePath', path)
          this.activePath = path // 让当前菜单栏高亮
        }
      }
    }
    </script>
    
    <style lang='less' scoped>
    </style>
    

    Dialog+Form 表单

    DayNode(VueShopEleM)

    <template>
      <div>
        <!-- 新增对话框------------------------------------------------ -->
        <el-dialog  :visible.sync="isShowAdd" width="50%" :before-close="handleClose" @close="addDialogClosed">
          <!-- 对话框主体区域 -->
          <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="70px">
            <el-form-item label="用户名" prop="username">
              <el-input v-model="addForm.username"></el-input>
            </el-form-item>
            <el-form-item label="密码" prop="password">
              <el-input v-model="addForm.password"></el-input>
            </el-form-item>
            <el-form-item label="邮箱" prop="email" required>
              <el-input v-model="addForm.email"></el-input>
            </el-form-item>
            <el-form-item label="电话" prop="mobile" required>
              <el-input v-model="addForm.mobile"></el-input>
            </el-form-item>
          </el-form>
          <!-- 对话框底部--------- -->
          <span slot="footer" class="dialog-footer">
            <el-button @click="isShowAdd= false">取 消</el-button>
            <el-button type="primary" @click="addUser">确 定</el-button>
          </span>
        </el-dialog>
      </div>
    </template>
    
    <script>
    import { addUserAPI } from '@/api'
    export default {
      data() {
        //验证邮箱的规则
        var checkEmail = (rule, value, cb) => {
          if (value.trim().length == 0) return cb(new Error('请输入邮箱'))
          const regEmail = /^\w+@\w+(\.\w+)+$/
          if (regEmail.test(value)) {
            return cb()
          }
          //返回一个错误提示
          cb(new Error('请输入合法的邮箱'))
        }
        //验证手机号码的规则
        var checkMobile = (rule, value, cb) => {
          if (!value) return cb(new Error('请输入手机号'))
    
          const regMobile = /^1[34578]\d{9}$/
          if (regMobile.test(value)) {
            return cb()
          }
          //返回一个错误提示
          cb(new Error('请输入合法的手机号码'))
        }
        return {
          isShowAdd: false, // 是否显示 添加用户 对话框
          // 添加用户的表单数据
          addForm: {
            username: '',
            password: '',
            email: '',
            mobile: ''
          },
          // 添加表单的验证规则对象
          addFormRules: {
            username: [
              { required: true, message: '请输入用户名称', trigger: 'blur' },
              {
                min: 3,
                max: 10,
                message: '用户名在3~10个字符之间',
                trigger: 'blur'
              }
            ],
            password: [
              { required: true, message: '请输入密码', trigger: 'blur' },
              {
                min: 6,
                max: 15,
                message: '用户名在6~15个字符之间',
                trigger: 'blur'
              }
            ],
            email: [
              //   { required: true, message: '请输入邮箱', trigger: 'blur' },
              { validator: checkEmail, trigger: 'blur' }
            ],
            mobile: [
              //   { required: true, message: '请输入手机号码', trigger: 'blur' },
              { validator: checkMobile, trigger: 'blur' }
            ]
          }
        }
      },
    
      methods: {
        handleSizeChange(val) {
          //以最新的pagesize来请求数据并展示数据
          this.queryInfo.pagesize = val
          this.getUserList()
        },
        handleCurrentChange(val) {
          //以最新的val页码来请求数据并展示数据
          this.queryInfo.pagenum = val
          this.getUserList()
        },
    
        // 添加用户
        addUser() {
          // 点击 确定按钮时,调用validate进行表单验证
          this.$refs.addFormRef.validate(async valid => {
            if (!valid) return this.$message.error('请填写完整用户信息')
    
            const { data: res } = await addUserAPI(this.addForm)
    
            //判断如果添加失败,就做提示
            if (res.meta.status !== 201) return this.$message.error('添加用户失败')
            //添加成功的提示
            this.$message.success('添加用户成功')
            //关闭对话框
            this.isShowAdd = false
          })
        },
        //对话框关闭之后,重置表单
        addDialogClosed() {
          this.$refs.addFormRef.resetFields()
        }
      }
    }
    </script>
    
    <style lang='less' scoped>
    </style>
    

    Table+Tree

    table DayNode(VueShopEleM) tree 树形控件 DayNode(VueShopEleM)

    <template>
      <div>
        <!-- 卡片容器区域--------------------------------- -->
        <el-card class="box-card">
          <div class="text item">
            <!-- 角色列表区域 ----------------------------------------->
            <el-table :data="roleList" border>
              <!-- !!!添加展开列 ------------------->
              <el-table-column type="expand">
                <template slot-scope="scope">
                  <!-- 渲染一级权限 -->
                  <!-- 只有第一行 需要加上边框 -->
                  <el-row :class="['bottomBorder',i1==0?'topBorder':'']" :key="item1.id" v-for="(item1,i1) in scope.row.children">
                    <el-col :span="5">
                      <el-tag closable @close="removeRightById(scope.row,item1.id)">{{item1.authName}}</el-tag>
                      <i class="el-icon-caret-right"></i>
                    </el-col>
                    <!-- 渲染二,三级权限 -->
                    <el-col :span="19">
                      <!-- 通过for循环嵌套渲染二级权限  -->
                      <!-- 除了第一行 加上边框 -->
                      <el-row :class="[i2!=0?'topBorder':'']" :key="item2.id" v-for="(item2,i2) in item1.children">
                        <el-col :span="6">
                          <el-tag type="success" closable @close="removeRightById(scope.row,item2.id)">{{item2.authName}}</el-tag>
                          <i class="el-icon-caret-right"></i>
                        </el-col>
                        <!-- 三级权限 -->
                        <el-col :span="18">
                          <el-tag
                            closable
                            @close="removeRightById(scope.row,item3.id)"
                            type="warning"
                            :key="item3.id"
                            v-for="item3 in item2.children"
                          >{{item3.authName}}</el-tag>
                        </el-col>
                      </el-row>
                    </el-col>
                  </el-row>
                </template>
              </el-table-column>
              <el-table-column type="index" label="#"></el-table-column>
              <el-table-column label="角色名称" prop="roleName"></el-table-column>
              <el-table-column label="角色描述" prop="roleDesc"></el-table-column>
              <el-table-column label="操作" width="300px">
                <template slot-scope="scope">
                  <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
                  <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
                  <el-button size="mini" type="success" icon="el-icon-setting" @click="showSetRightDialog(scope.row)">分配权限</el-button>
                </template>
              </el-table-column>
            </el-table>
          </div>
        </el-card>
        <!-- 分配权限对话框------------------------------------------ -->
        <el-dialog  @close="setRightDialogClose" :visible.sync="isShowSetRight" width="50%">
          <!-- 树形组件
        show-checkbox:显示复选框
        node-key:设置选中节点对应的值
        default-expand-all:是否默认展开所有节点
        :default-checked-keys 设置默认选中项的数组
          ref:设置引用-->
          <el-tree
            :data="rightsList"
            :default-checked-keys="defKeys"
            show-checkbox
            default-expand-all
            node-key="id"
            ref="treeRef"
            highlight-current
            :props="treeProps"
          ></el-tree>
          <span slot="footer" class="dialog-footer">
            <el-button @click="isShowSetRight = false">取 消</el-button>
            <el-button type="primary" @click="changeRoleRights">确 定</el-button>
          </span>
        </el-dialog>
      </div>
    </template>
    
    <script>
    import { getRolesAPI, delRoleRightAPI, getRightsTreeAPI, changeRoleRightAPI } from '@/api'
    export default {
      data() {
        return {
          roleList: [], //角色列表数据
          isShowSetRight: false, // 是否显示 分配权限 的弹出框
          rightsList: [], //权限树 数据
          //树形控件的属性绑定对象
          treeProps: {
            //通过label设置树形节点文本展示authName
            label: 'authName',
            //设置通过children属性展示子节点信息
            children: 'children'
          },
          defKeys: [], //默认勾选的节点的 key 的数组,三级节点的id
          roleId: null // 当前正在编辑的项 的id
        }
      },
      created() {
        this.getRoleList()
      },
      methods: {
        async getRoleList() {
          const { data: res } = await getRolesAPI()
          //如果返回状态为异常状态则报错并返回
          if (res.meta.status !== 200) return this.$message.error('获取角色列表失败')
          // //如果返回状态正常,将请求的数据保存在data中
          this.roleList = res.data
        },
        // 删除权限
        async removeRightById(role, rightId) {
          //弹窗提示用户是否要删除
          const confirmResult = await this.$confirm('请问是否要删除该权限', '删除提示', {
            confirmButtonText: '确认删除',
            cancelButtonText: '取消',
            type: 'warning'
          }).catch(err => err)
          //如果用户点击确认,则confirmResult 为'confirm'
          //如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel'
          if (confirmResult != 'confirm') return this.$message.info('已经取消删除')
    
          const { data: res } = await delRoleRightAPI(role.id, rightId)
          if (res.meta.status !== 200) return this.$message.error('删除角色权限失败')
    
          //无需再重新加载所有权限
          //只需要对现有的角色权限进行更新即可
          role.children = res.data
        },
        // 点击设置分配权限
        async showSetRightDialog(role) {
          // 弹出 对话框时,就保存当前项的id
          this.roleId = role.id
          //获取所有权限的数据
          const { data: res } = await getRightsTreeAPI()
          //如果返回状态为异常状态则报错并返回
          if (res.meta.status !== 200) return this.$message.error('获取权限树失败')
          //如果返回状态正常,将请求的数据保存在data中
          this.rightsList = res.data
    
          // role是当前用户的信息,children里面就是权限
          //调用getLeafKeys进行递归,将三级权限添加到数组中
          this.getLeafKeys(role, this.defKeys)
          // 显示对话框
          this.isShowSetRight = true
        },
        // 通过递归的形式,获取角色下所有三级权限的id,并保存到defKeys中
        getLeafKeys(node, arr) {
          // 如果当前节点不包含children属性,则表示node为三级权限
          if (!node.children) return arr.push(node.id)
          //递归调用 当前子节点每一个元素
          node.children.forEach(item => this.getLeafKeys(item, arr))
        },
        // 当用户关闭树形权限对话框的时候
        setRightDialogClose() {
          // 需要清除掉所有选中状态
          this.defKeys = []
        },
        //当用户在树形权限对话框中点击确定,将用户选择的 权限发送请求进行更新
        async changeRoleRights() {
          //获取所有选中及半选的 所有id
          const keys = [...this.$refs.treeRef.getCheckedKeys(), ...this.$refs.treeRef.getHalfCheckedKeys()]
          //将数组转换为 , 拼接的字符串
          const rids = keys.join(',')
          console.log(rids)
          //发送请求完成更新
          const { data: res } = await changeRoleRightAPI(this.roleId, rids)
          if (res.meta.status !== 200) return this.$message.error('分配权限失败')
          this.$message.success('分配权限成功')
          // 更新整个角色列表
          this.getRoleList()
          // 关闭对话框
          this.isShowSetRight = false
        }
      }
    }
    </script>
    
    <style lang='less' scoped>
    </style>
    

    Table+Pagination 分页

    DayNode(VueShopEleM)

    <template>
      <div>
        <!-- 卡片容器 -->
        <el-card class="box-card">
          <div class="text item">
            <!-- 数据表格区域-------------------------------------------------- -->
            <!-- 用户列表(表格)区域 -->
            <el-table :data="userList" border stripe>
              <el-table-column label="#" type="index"></el-table-column>
              <el-table-column label="姓名">
                <template slot-scope="scope">{{scope.row.username}}</template>
              </el-table-column>
              <!-- <el-table-column label="姓名" prop="username"></el-table-column> -->
              <el-table-column label="邮箱" prop="email"></el-table-column>
              <el-table-column label="电话" prop="mobile"></el-table-column>
              <el-table-column label="角色" prop="role_name"></el-table-column>
              <el-table-column label="状态">
                <template slot-scope="scope">
                  <!-- scope.row---每条数据,还有 scope.$index--index,scope.column参数 -->
                  <el-switch v-model="scope.row.mg_state" @change="changeStatus(scope.row)" active-color="#f7d182"></el-switch>
                </template>
              </el-table-column>
              <el-table-column label="操作" width="180px">
                <template slot-scope="scope">
                  <!-- 修改 -->
                  <el-button @click="showEditDialog(scope.row.id)" type="primary" icon="el-icon-edit" size="mini"></el-button>
                  <!-- 删除 -->
                  <el-button @click="delUser(scope.row.id)" type="danger" icon="el-icon-delete" size="mini"></el-button>
                  <!-- 分配角色 -->
                  <el-tooltip class="item" effect="light" content="分配角色" placement="top" :enterable="false">
                    <el-button type="success" icon="el-icon-setting" size="mini" @click="setRole(scope.row)"></el-button>
                  </el-tooltip>
                </template>
              </el-table-column>
            </el-table>
            <!--  分页导航区域 -->
            <el-pagination
              @size-change="handleSizeChange"
              @current-change="handleCurrentChange"
              :current-page="queryInfo.pagenum"
              :page-sizes="[1, 2, 5, 10]"
              :page-size="queryInfo.pagesize"
              layout="total, sizes, prev, pager, next, jumper"
              :total="total"
            ></el-pagination>
          </div>
        </el-card>
      </div>
    </template>
    
    <script>
    import { getUsersAPI } from '@/api'
    export default {
      data() {
        return {
          //获取查询用户信息的参数
          queryInfo: {
            query: '',
            pagenum: 1, // 当前页码
            pagesize: 1 // 每页显示行数
          },
          //保存请求回来的用户列表数据
          userList: [],
          total: 0
        }
      },
      created() {
        this.getUserList()
      },
      methods: {
        //发送请求获取用户列表数据
        async getUserList() {
          const { data: res } = await getUsersAPI(this.queryInfo)
          //如果返回状态为异常状态则报错并返回
          if (res.meta.status != 200) return this.$message.error(res.meta.msg)
          //如果返回状态正常,将请求的数据保存在data中
          this.userList = res.data.users
    
          this.total = res.data.total
        },
        // 切换 每页的数量
        handleSizeChange(val) {
          //以最新的pagesize来请求数据并展示数据
          this.queryInfo.pagesize = val
          this.getUserList()
        },
        // 切换 当前页码
        handleCurrentChange(val) {
          //以最新的val页码来请求数据并展示数据
          this.queryInfo.pagenum = val
          this.getUserList()
        }
      }
    }
    </script>
    
    <style lang='less' scoped>
    </style>
    

    Cascader 级联选择器

    DayNode(VueShopEleM)

    <template>
      <div>
        <!-- 添加分类对话框 ----------------------------------------------->
        <el-dialog  :visible.sync="isShowAddCate" width="50%" @close="addCateDialogClosed">
          <!-- 添加分类表单 -->
          <el-form :model="addCateForm" :rules="addCateFormRules" ref="addCateFormRef" label-width="100px">
            <el-form-item label="分类名称" prop="cat_name">
              <el-input v-model="addCateForm.cat_name"></el-input>
            </el-form-item>
            <el-form-item label="父级分类" prop="cat_pid">
              <!-- expandTrigger='hover'(鼠标悬停触发级联) v-model(设置级联菜单绑定数据) 
              :options(指定级联菜单数据源)  :props(用来配置数据显示的规则) 
              clearable(提供“X”号完成删除文本功能) change-on-select(是否可以选中任意一级的菜单)-->
              <el-cascader
                :options="parentCateList"
                :props="cascaderProps"
                v-model="selKeys"
                @change="parentCateChange"
                clearable
                style="width:100%"
              ></el-cascader>
            </el-form-item>
          </el-form>
          <span slot="footer" class="dialog-footer">
            <el-button @click="isShowAddCate = false">取 消</el-button>
            <el-button type="primary" @click="addCate">确 定</el-button>
          </span>
        </el-dialog>
      </div>
    </template>
    
    <script>
    import { getGoodCatesAPI, addGoodCateAPI } from '@/api'
    export default {
      data() {
        return {
          isShowAddCate: false, // 是否显示添加分类对话框
          //添加分类的表单数据对象
          addCateForm: {
            //分类名称
            cat_name: '',
            //添加分类的父级id,0则表示父级为0.添加一级分类
            cat_pid: 0,
            //添加分类的等级,0则表示添加一级分类
            cat_level: 0
          },
          //添加分类校验规则
          addCateFormRules: {
            //验证规则
            cat_name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }]
          },
          parentCateList: [], // 有一二级分类的 分类列表数据
          selKeys: [], // 父级分类下拉框选中的 keys
          cascaderProps: {
            value: 'cat_id',
            label: 'cat_name',
            children: 'children',
            expandTrigger: 'hover', //鼠标悬停触发级联
            checkStrictly: true // 是否可以选中任意一级的菜单
          }
        }
      },
    
      methods: {
        // 点击添加分类按钮
        showAddCateDialog() {
          this.isShowAddCate = true
          this.getParentCateList()
        },
        //获取父级分类数据列表
        async getParentCateList() {
          const { data: res } = await getGoodCatesAPI({ type: 2 })
          if (res.meta.status !== 200) {
            return this.$message.error('获取商品分类列表数据失败')
          }
          this.parentCateList = res.data
        },
        //级联菜单中选择项发生变化时触发
        parentCateChange() {
          console.log(this.selKeys)
          // 如果两级都选了,则this.selKeys有两个id,最后一个id即为我们要添加分类 的父级id
          if (this.selKeys.length >= 0) {
            this.addCateForm.cat_pid = this.selKeys[this.selKeys.length - 1]
          } else {
            // 没有选择,则表示要添加0级分类
            this.addCateForm.cat_pid = 0
          }
          this.addCateForm.cat_level = this.selKeys.length
        },
        // 点击 添加分类对话框 的确定按钮
        addCate() {
          this.$refs.addCateFormRef.validate(async valid => {
            if (!valid) return
    
            // 发送添加分类的请求
            const { data: res } = await addGoodCateAPI(this.addCateForm)
            if (res.meta.status !== 201) return this.$message.error('添加分类失败')
            this.$message.success('添加分类成功')
            this.getCateList()
            // 关闭弹出对话框
            this.isShowAddCate = false
          })
        },
        // 关闭 添加分类对话框
        addCateDialogClosed() {
          //当关闭添加分类对话框时,重置表单
          this.$refs.addCateFormRef.resetFields()
          this.selKeys = []
          this.addCateForm.cat_level = 0
          this.addCateForm.cat_pid = 0
        }
      }
    }
    </script>
    
    <style lang="less" scoped>
    </style>
    

    Upload 上传

    完成图片上传

    //在页面中添加upload组件,并设置对应的事件和属性
    <el-tab-pane label="商品图片" name="3">
      <!-- 商品图片上传
      action:指定图片上传api接口
      :on-preview : 当点击图片时会触发该事件进行预览操作,处理图片预览
      :on-remove : 当用户点击图片右上角的X号时触发执行
      :on-success:当用户点击上传图片并成功上传时触发
      list-type :设置预览图片的方式
      :headers :设置上传图片的请求头 -->
      <el-upload :action="uploadURL" :on-preview="handlePreview" :on-remove="handleRemove" :on-success="handleSuccess" list-type="picture" :headers="headerObj">
        <el-button size="small" type="primary">点击上传</el-button>
      </el-upload>
    </el-tab-pane>
    //在el-card卡片视图下面添加对话框用来预览图片
    <!-- 预览图片对话框 -->
    <el-dialog  :visible.sync="previewVisible" width="50%">
      <img :src="previewPath" class="previewImg" />
    </el-dialog>
    
    //在data中添加数据
    data(){
      return {
        ......
        //添加商品的表单数据对象
        addForm: {
          goods_name: '',
          goods_price: 0,
          goods_weight: 0,
          goods_number: 0,
          goods_cat: [],
          //上传图片数组
          pics: []
        },
        //上传图片的url地址
        uploadURL: 'http://127.0.0.1:8888/api/private/v1/upload',
        //图片上传组件的headers请求头对象
        headerObj: { Authorization: window.sessionStorage.getItem('token') },
        //保存预览图片的url地址
        previewPath: '',
        //控制预览图片对话框的显示和隐藏
        previewVisible:false
      }
    },
    //在methods中添加事件处理函数
    methods:{
      .......
      handlePreview(file) {
        //当用户点击图片进行预览时执行,处理图片预览
        //形参file就是用户预览的那个文件
        this.previewPath = file.response.data.url
        //显示预览图片对话框
        this.previewVisible = true
      },
      handleRemove(file) {
        //当用户点击X号删除时执行
        //形参file就是用户点击删除的文件
        //获取用户点击删除的那个图片的临时路径
        const filePath = file.response.data.tmp_path
        //使用findIndex来查找符合条件的索引
        const index = this.addForm.pics.findIndex(item => item.pic === filePath)
        //移除索引对应的图片
        this.addForm.pics.splice(index, 1)
      },
      handleSuccess(response) {
        //当上传成功时触发执行
        //形参response就是上传成功之后服务器返回的结果
        //将服务器返回的临时路径保存到addForm表单的pics数组中
        this.addForm.pics.push({ pic: response.data.tmp_path })
      }
    }
    

    其他插件

    vue-table-with-tree-grid

    官网

    DayNode(VueShopEleM)

    基本配置

    1. 下载插件
    yarn add vue-table-with-tree-grid
    
    1. 全局注册 main.js
    //全局注册组件
    Vue.component('tree-table', TreeTable)
    
    1. vue组件中使用
    <template>
      <div>
        <!-- 卡片视图区域 -->
        <el-card>
          <!-- 分类表格------------------------------------------------------------  -->
          <!-- :data(设置数据源) :columns(设置表格中列配置信息) :selection-type(是否有复选框) 
          :expand-type(是否展开数据) show-index(是否设置索引列) index-text(设置索引列头)
          border(是否添加纵向边框) :show-row-hover(是否鼠标悬停高亮)-->
          <tree-table
            :data="cateList"
            :columns="columns"
            :selection-type="false"
            :expand-type="false"
            show-index
            index-text="#"
            border
            :show-row-hover="false"
          >
            <!-- 是否有效区域, 设置对应的模板列: slot="isok"(与columns中设置的template一致) -->
            <template #isok="scope">
              <i class="el-icon-success" v-if="scope.row.cat_deleted === false" style="color:lightgreen"></i>
              <i class="el-icon-error" v-else style="color:red"></i>
            </template>
            <!-- 排序 -->
            <template slot="order" slot-scope="scope">
              <el-tag size="mini" v-if="scope.row.cat_level===0">一级</el-tag>
              <el-tag size="mini" type="success" v-else-if="scope.row.cat_level===1">二级</el-tag>
              <el-tag size="mini" type="warning" v-else>三级</el-tag>
            </template>
            <!-- 操作 -->
            <template slot="opt" slot-scope="scope">
              <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
              <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
            </template>
          </tree-table>
    
          <!-- 分页------------------------------------------------------------ -->
          <el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="queryInfo.pagenum"
            :page-sizes="[3, 5, 10, 15]"
            :page-size="queryInfo.pagesize"
            layout="total, sizes, prev, pager, next, jumper"
            :total="total"
          ></el-pagination>
        </el-card>
      </div>
    </template>
    
    <script>
    import { getGoodCatesAPI } from '@/api'
    export default {
      data() {
        return {
          // 商品分类数据列表
          cateList: [],
          //查询分类数据的条件
          queryInfo: {
            type: 3,
            pagenum: 1,
            pagesize: 5
          },
          //保存总数据条数
          total: 0,
          columns: [
            { label: '分类名称', prop: 'cat_name' },
            //type:'template'(将该列设置为模板列),template:'isok'(设置该列模板的名称为isok)
            { label: '是否有效', prop: '', type: 'template', template: 'isok' },
            { label: '排序', prop: '', type: 'template', template: 'order' },
            { label: '操作', prop: '', type: 'template', template: 'opt' }
          ]
        }
      },
      created() {
        this.getCateList()
      },
      methods: {
        async getCateList() {
          //获取商品分类数据
          let { data: res } = await getGoodCatesAPI(this.queryInfo)
          if (res.meta.status !== 200) return this.$message.error('获取商品列表数据失败')
          //将数据列表赋值给cateList
          this.cateList = res.data.result
          //保存总数据条数
          this.total = res.data.total
        },
        handleSizeChange(newSize) {
          //当pagesize发生改变时触发
          this.queryInfo.pagesize = newSize
          this.getCateList()
        },
        handleCurrentChange(newPage) {
          //当pagenum发生改变时触发
          this.queryInfo.pagenum = newPage
          this.getCateList()
        }
      }
    }
    </script>
    
    <style lang="less" scoped>
    </style>
    

    vue-quill-editor富文本

    官网 DayNode(VueShopEleM)

    1. 下载插件
    yarn add vue-quill-editor
    
    1. main.js中引入
    //导入vue-quill-editor(富文本编辑器)
    import VueQuillEditor from 'vue-quill-editor'
    //导入vue-quill-editor的样式
    import 'quill/dist/quill.core.css'
    import 'quill/dist/quill.snow.css'
    import 'quill/dist/quill.bubble.css'
    ......
    //全局注册组件
    Vue.component('tree-table', TreeTable)
    //全局注册富文本组件
    Vue.use(VueQuillEditor)
    
    1. src/components/goods/Add.vue中使用
    <!-- 富文本编辑器组件 -->
    <el-tab-pane label="商品内容" name="4">
      <!-- 富文本编辑器组件 -->
      <quill-editor v-model="addForm.goods_introduce"></quill-editor>
      <!-- 添加商品按钮 -->
      <el-button type="primary" class="btnAdd">添加商品</el-button>
    </el-tab-pane>
    
    //在数据中添加goods_introduce
    //添加商品的表单数据对象
    addForm: {
      goods_name: '',
      goods_price: 0,
      goods_weight: 0,
      goods_number: 0,
      goods_cat: [],
      //上传图片数组
      pics: [],
      //商品的详情介绍
      goods_introduce:''
    }
    //在global.css样式中添加富文本编辑器的最小高度
    .ql-editor{
        min-height: 300px;
    }
    //给添加商品按钮添加间距
    .btnAdd{
      margin-top:15px;
    }
    

    lodash 深拷贝

    1. 下载依赖
    npm i --save lodash
    
    1. 在.vue文件中 导入lodash并使用
    <script>
    //官方推荐将lodash导入为_
    import _ from 'lodash'
    
    ...其他代码
    
    const form = _.cloneDeep(this.addForm)
    </script>
    

    项目优化

    DayNode(VueShopEleM)

    添加进度条--nprogress

    官网

    1. 下载插件
    yarn add nprogress
    
    1. 在src/utils/request.js路由处理器中配置
    • 在请求在到达服务器之前--开启进度条
    • 在服务器的响应结束后--结束进度条

    DayNode(VueShopEleM)

    // 导入axios
    import axios from 'axios'
    // 设置请求的根路径
    axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
    
    //导入进度条插件
    import NProgress from 'nprogress'
    //导入进度条样式
    import 'nprogress/nprogress.css'
    
    //请求在到达服务器之前,先会调用use中的这个回调函数来添加请求头信息
    axios.interceptors.request.use(config => {
        //当进入request拦截器,表示发送了请求,我们就开启进度条
        NProgress.start()
    
        //为请求头对象,添加token验证的Authorization字段
        config.headers.Authorization = window.sessionStorage.getItem('token')
        return config
    })
    
    //在response拦截器中,服务器响应结束后,隐藏进度条
    axios.interceptors.response.use(config => {
        //当进入response拦截器,表示请求已经结束,我们就结束进度条
        NProgress.done()
        return config
    })
    
    // 导出
    export default axios
    

    解决自动换行的问题

    根据ESLint的警告提示更改对应的代码

    .prettierrc文件中更改设置"printWidth":200, 将每行代码的文字数量更改为200

    {
        "semi":false,
        "singleQuote":true,
        "printWidth":200
    }
    

    在build前移除所有的console.log

    DayNode(VueShopEleM)

    1. 下载插件(开发依赖)
    yarn add babel-plugin-transform-remove-console
    
    1. babel.config.js中配置,需要只在项目发布阶段 再移除
    // 开发阶段:程序员 测试 共同完成 项目的过程 , 不要 移除 console 的输出
    // 生成阶段;普通用户可以通过浏览器来使用项目网站 , 要 移除 console的输出
    const proPlugs = []
    if (process.env.NODE_ENV === 'production') {
      proPlugs.push("transform-remove-console")
    }
    module.exports = {
      presets: [
        '@vue/cli-plugin-babel/preset'
      ],
      "plugins": [...proPlugs]
    }
    

    生成打包报告

    1. 命令行形式生成打包报告 vue-cli-service build --report

    2. 在vue控制台生成打包报告 点击“任务”=>“build”=>“运行” 运行完毕之后点击右侧“分析”,“控制台”面板查看报告

    DayNode(VueShopEleM)

    修改webpack的默认配置

    配置不同的 入口文件

    • chainWebpack可以通过链式编程的形式,修改webpack配置

    • configureWebpack可以通过操作对象的形式,修改webpack配置

    在项目根目录中创建vue.config.js文件

    - 设置生产环境production的 入口文件为 main-prod.js
    - 设置开发模式 development的 入口文件为 main-dev.js
    // webpack不能识别高版本语法,所以需要用老的 Common JS语法导出
    module.exports = {
        chainWebpack:config=>{
            //发布模式
            config.when(process.env.NODE_ENV === 'production',config=>{
                //entry找到默认的打包入口,调用clear则是删除默认的打包入口
                //add添加新的打包入口
                config.entry('app').clear().add('./src/main-prod.js')
            })
            //开发模式
            config.when(process.env.NODE_ENV === 'development',config=>{
                config.entry('app').clear().add('./src/main-dev.js')
            })
        }
    }
    

    大文件访问优化

    通过external加载外部CDN资源

    !!待补充cdn图和笔记

    1. 修改vue.config.js文件的配置,在生产环境下使用externals排除包
    module.exports = {
        chainWebpack:config=>{
            //发布模式
            config.when(process.env.NODE_ENV === 'production',config=>{
                //entry找到默认的打包入口,调用clear则是删除默认的打包入口
                //add添加新的打包入口
                config.entry('app').clear().add('./src/main-prod.js')
    
                //!!使用externals设置排除项,编译时,遇到这些模块不要打包
                config.set('externals',{
                    vue:'Vue',
                    'vue-router':'VueRouter',
                    axios:'axios',
                    lodash:'_',
                    echarts:'echarts',
                    nprogress:'NProgress',
                    'vue-quill-editor':'VueQuillEditor',
                    'element-ui': 'ElementUI'
                })
            })
            //开发模式
            config.when(process.env.NODE_ENV === 'development',config=>{
                config.entry('app').clear().add('./src/main-dev.js')
            })
        }
    }
    
    1. 在public/index.js文件下,让外部CDN引入这些排除掉的资源
        <!-- 引入 CDN外部静态资源 -->
        <!-- nprogress 的样式表文件 -->
        <link rel="stylesheet" href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css" />
        <!-- 富文本编辑器 的样式表文件 -->
        <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.core.min.css" />
        <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.snow.min.css" />
        <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.bubble.min.css" />
        <!-- element-ui 的样式表文件 -->
        <link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.8.2/theme-chalk/index.css" />
    
        <script src="https://cdn.staticfile.org/vue/2.5.22/vue.min.js"></script>
        <script src="https://cdn.staticfile.org/vue-router/3.0.1/vue-router.min.js"></script>
        <script src="https://cdn.staticfile.org/axios/0.18.0/axios.min.js"></script>
        <script src="https://cdn.staticfile.org/lodash.js/4.17.11/lodash.min.js"></script>
        <script src="https://cdn.staticfile.org/echarts/4.1.0/echarts.min.js"></script>
        <script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
        <!-- 富文本编辑器的 js 文件 -->
        <script src="https://cdn.staticfile.org/quill/1.3.4/quill.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/vue-quill-editor@3.0.4/dist/vue-quill-editor.js"></script>
    
        <!-- element-ui 的 js 文件 -->
        <script src="https://cdn.staticfile.org/element-ui/2.8.2/index.js"></script>
    
    1. 打开开发入口文件main-prod.js,删除掉默认的引入代码
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    // import './plugins/element.js'
    //导入字体图标
    import './assets/fonts/iconfont.css'
    //导入全局样式
    import './assets/css/global.css'
    //导入第三方组件vue-table-with-tree-grid
    import TreeTable from 'vue-table-with-tree-grid'
    //导入进度条插件
    import NProgress from 'nprogress'
    //导入进度条样式
    // import 'nprogress/nprogress.css'
    // //导入axios
    import axios from 'axios'
    // //导入vue-quill-editor(富文本编辑器)
    import VueQuillEditor from 'vue-quill-editor'
    // //导入vue-quill-editor的样式
    // import 'quill/dist/quill.core.css'
    // import 'quill/dist/quill.snow.css'
    // import 'quill/dist/quill.bubble.css'
    

    定制首页内容

    DayNode(VueShopEleM)

    1. vue.config.js进行配置isProd变量(生产模式-true,开发模式-false)
    module.exports = {
        chainWebpack:config=>{
            config.when(process.env.NODE_ENV === 'production',config=>{
                ......
                
                //使用插件
                config.plugin('html').tap(args=>{
                    //添加参数isProd
                    args[0].isProd = true
                    return args
                })
            })
    
            config.when(process.env.NODE_ENV === 'development',config=>{
                config.entry('app').clear().add('./src/main-dev.js')
    
                //使用插件
                config.plugin('html').tap(args=>{
                    //添加参数isProd
                    args[0].isProd = false
                    return args
                })
            })
        }
    }
    
    1. 然后在public/index.html中使用插件判断是否为发布环境并定制首页内容
    • 根据不同环境,设置不同的title
    • 根据不同环境,判断是否要引入CDN外部静态资源,避免重复引用模块
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        
        <title><%= htmlWebpackPlugin.options.isProd ? '' : 'dev - ' %>电商后台管理系统</title>
    
        <% if(htmlWebpackPlugin.options.isProd){ %>
        <!-- nprogress 的样式表文件 -->
        <link rel="stylesheet" href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css" />
        ........
        <!-- element-ui 的 js 文件 -->
        <script src="https://cdn.staticfile.org/element-ui/2.8.2/index.js"></script>
        <% } %>
      </head>
      .......
    

    路由懒加载

    当路由被访问时才加载对应的路由文件,就是路由懒加载。

    拆分chunks.js文件

    DayNode(VueShopEleM) 1.安装开发依赖

    yarn add @babel/plugin-syntax-dynamic-import -D
    
    1. babel.config.js中声明该插件
    module.exports = {
        presets: ['@vue/cli-plugin-babel/preset'],
        plugins: [
            ...其他代码
            
            //配置路由懒加载插件
            '@babel/plugin-syntax-dynamic-import'
        ]
    }`
    

    项目上线配置

    通过node创建服务器

    在vue_shop同级创建一个文件夹vue_shop_server存放node服务器 使用终端打开vue_shop_server文件夹,

    输入命令 npm init -y 初始化包之后,输入命令 npm i express -S 打开vue_shop目录,复制dist文件夹,粘贴到vue_shop_server中

    在vue_shop_server文件夹中创建app.js文件,编写代码如下:

    const express = require('express')
    
    const app = express()
    
    app.use(express.static('./dist'))
    
    app.listen(8998,()=>{
        console.log("server running at http://127.0.0.1:8998")
    })
    

    然后再次在终端中输入 node app.js

    开启gzip压缩

    打开vue_shop_server文件夹的终端,输入命令:

    npm i compression -D
    

    打开app.js,编写代码:

    const express = require('express')
    // 导入包
    const compression = require('compression')
    
    const app = express()
    // 注册中间件
    app.use(compression())
    app.use(express.static('./dist'))
    
    app.listen(8998,()=>{
        console.log("server running at http://127.0.0.1:8998")
    })
    

    起源地下载网 » DayNode(VueShopEleM)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元