前期配置-后端
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)
后台配置
配置mysql文件
代码运行完后,点左侧右上角 刷新 按钮
启动后台 app.js
postman测试数据
项目配置-前端
登录token
登录状态
如果服务器和客户端同源,建议可以使用cookie或者session来保持登录状态
如果客户端和服务器跨域了,建议使用token进行维持登录状态。
路由router
- 下载插件
yarn add vue-router
- 新建
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
- 在
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接口配置
- 下载
axios
插件
yarn add axios
- 新建
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
- 新建
src/api/Login.js
,配置登录相关的api请求,并导出
// 提供登录 注册 相关的api方法
import axios from '@/utils/request.js'
// 导出login请求
export const login = data =>
axios({
url: 'login',
method: 'post',
data
})
- 新建
src/api/index.js
,汇总所有请求
import { login } from '@/api/Login.js'
import { getMenus } from '@/api/Home.js'
export const loginAPI = login
export const getMenusAPI = getMenus
- 在
src/components/Login.vue
组件中 按需导入组件并使用api
<script>
// 按需导入 api中的方法
import { loginAPI } from '@/api'
export default {}
</script>
Element UI
按需引入
- 下载插件
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
- 在
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 布局容器
- 后台api请求数据
{
"data":
{
"id": 101,
"authName": "商品管理",
"path": null,
"children": [
{
"id": 104,
"authName": "商品列表",
"path": null,
"children": []
}
]
}
"meta": {
"msg": "获取菜单列表成功",
"status": 200
}
}
- 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 表单
<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 tree 树形控件
<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 分页
<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 级联选择器
<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
官网
基本配置
- 下载插件
yarn add vue-table-with-tree-grid
- 全局注册 main.js
//全局注册组件
Vue.component('tree-table', TreeTable)
- 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富文本
官网
- 下载插件
yarn add vue-quill-editor
- 在
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)
- 在
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 深拷贝
- 下载依赖
npm i --save lodash
- 在.vue文件中 导入lodash并使用
<script>
//官方推荐将lodash导入为_
import _ from 'lodash'
...其他代码
const form = _.cloneDeep(this.addForm)
</script>
项目优化
添加进度条--nprogress
官网
- 下载插件
yarn add nprogress
- 在src/utils/request.js路由处理器中配置
- 在请求在到达服务器之前--开启进度条
- 在服务器的响应结束后--结束进度条
// 导入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
- 下载插件(开发依赖)
yarn add babel-plugin-transform-remove-console
- 在
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]
}
生成打包报告
-
命令行形式生成打包报告
vue-cli-service build --report
-
在vue控制台生成打包报告 点击“任务”=>“build”=>“运行” 运行完毕之后点击右侧“分析”,“控制台”面板查看报告
修改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图和笔记
- 修改
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')
})
}
}
- 在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>
- 打开开发入口文件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'
定制首页内容
- 在
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
})
})
}
}
- 然后在
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文件
1.安装开发依赖
yarn add @babel/plugin-syntax-dynamic-import -D
- 在
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")
})
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!