电商后台管理系统(各模块技术点)
登录:
在登陆页面输入账号密码,讲数据发送给服务器
服务器返回登录的结果,成功返回的数据中带有token
在得到token后,将他进行保存,存储在本地存储中。
登录成功之后,需要讲后台返回的token保存到本地存储中,
操作完毕后,需要跳转到home
login() {this.$refs.ruleForm.validate(async (v) => {if (v) {let res = await loginAPI.login(this.loginForm);console.log(res);if (res.data.meta.status == 200) {this.$message.success(res.data.meta.msg);localStorage.setItem("token", res.data.data.token);this.$router.push("/home");} else {this.$message.error(res.data.meta.msg);}}});},
进行路由鉴权,如果没有登录,不能访问/home,强制跳转到登录页面
router.beforeEach((to, from, next) => {if (to.path === "/") {return next()}let token = localStorage.getItem("token")if (!token) {return next("/")}next()
})
实现退出
在home组件添加一个退出按钮,点击直接清空本地存储中的token即可。
首页布局:
使用element-ui进行快速布局,
请求侧边栏数据
created() {HomeAPI.leftMensList(this.menuList).then((res) => {// console.log(res);if (res.data.meta.status == 200) {this.menuList = res.data.data;} else {this.$message({message: "菜单数据获取失败",});}});},
通过v-for 双重循环渲染左侧菜单
<el-menu:default-active="$route.path"class="el-menu-vertical-demo"background-color="#333744"text-color="#fff"active-text-color="#ffd04b":unique-opened="true"router:collapse="isCollapse":collapse-transition="false"@open="handleOpen"@close="handleClose"><el-submenu:index="item.id + ''"v-for="item in menuList":key="item.id"><template slot="title"><i :class="iconsObj[item.id]"></i><span>{{ item.authName }}</span></template><el-menu-itemv-for="item1 in item.children":key="item1.id":index="'/' + item1.path"><i :class="iconsObj1[item1.id]"></i>{{ item1.authName }}</el-menu-item></el-submenu></el-menu>
侧边菜单栏的伸缩功能
<el-aside :width="isCollapse ? '64px' : '300px'"><div class="toggles" @click="toggleCollapse">|||</div>
用户列表基本结构
使用element-ui面包屑组件,完成顶部导航
使用element-ui卡片组件完成主体表格
然后讲用户数据渲染
<div><!-- 面包屑导航 --><Bread></Bread><!-- 卡片区域 --><el-card class="box-card"><!-- 搜索框 --><el-inputplaceholder="请输入内容"v-model="input"clearableclass="input"><template slot="append"><span class="el-icon-search" @click="userSeach"></span></template></el-input><el-button type="primary" class="user" @click="addUser">添加用户</el-button><!-- 用户列表 --><el-table :data="tableData" border style="width: 100%" class="table"><el-table-column type="index" width="100px" label="#"></el-table-column><el-table-column prop="username" label="姓名"> </el-table-column><el-table-column prop="email" label="邮箱"> </el-table-column><el-table-column prop="mobile" label="电话"> </el-table-column><el-table-column prop="role_name" label="角色"> </el-table-column><el-table-column label="状态"><template slot-scope="scope"><el-switchv-model="scope.row.mg_state"@change="userEdit(scope.row)"></el-switch></template></el-table-column><el-table-column label="操作"><template slot-scope="scope"><!-- 修改按钮 --><el-buttontype="primary"icon="el-icon-edit"size="mini"@click="editState(scope.row.id)"></el-button><!-- 删除按钮 --><el-buttontype="danger"icon="el-icon-delete"size="mini"@click="delUser(scope.row.id)"></el-button><!-- 分配权限按钮 --><el-tooltipeffect="dark"content="分配权限"placement="top":enterable="false"><el-buttontype="warning"icon="el-icon-setting"size="mini"@click="distribution(scope.row)"></el-button></el-tooltip></template></el-table-column></el-table><!-- 分页 --><div class="block"><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>
请求用户列表数据:
//用户列表getTableData() {userAPI.getUserList(this.queryInfo).then((res) => {// console.log(res);if (res.data.meta.status !== 200) {this.$message.error(res.data.meta.msg);} else {this.$message.success(res.data.meta.msg);this.tableData = res.data.data.users;this.total = res.data.data.total;}});},
实现分页功能
handleSizeChange(val) {this.queryInfo.pagesize = val;// console.log(`每页 ${val} 条`);this.getTableData();},handleCurrentChange(val) {this.queryInfo.pagenum = val;// console.log(`当前页: ${val}`);this.getTableData();},
实现更新用户状态
<el-switchv-model="scope.row.mg_state"@change="userEdit(scope.row)"></el-switch>
发送请求完成状态的更改
userEdit(stateChange) {// console.log(stateChange);userAPI.editUserState(stateChange.id, stateChange.mg_state).then((res) => {// console.log(res);if (res.data.meta.status == 200) {this.$message.success("更新用户状态成功");} else {this.$message.error("更新用户状态失败");}});},
实现搜索功能
<el-inputplaceholder="请输入内容"v-model="input"clearableclass="input"><template slot="append"><span class="el-icon-search" @click="userSeach"></span></template></el-input>//搜索userSeach() {this.queryInfo.query = this.input;this.getTableData();},
实现添加用户
<el-dialog :visible.sync="addDialogVisible" width="30%"><el-form:model="addForm":rules="addFormRules"ref="addFormRef"label-width="100px"class="demo-ruleForm"><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"><el-input v-model="addForm.email"></el-input></el-form-item><el-form-item label="电话" prop="mobile"><el-input v-model="addForm.mobile"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button type="primary" @click="submitUser">提交</el-button><el-button @click="addDialogVisible = false">取消</el-button></span></el-dialog>
添加数据绑定和校验规则:
data() {//验证邮箱的正则var checkEmail = (rule, value, callback) => {//alert(value)var reg = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;if (reg.test(value)) {// 格式正确返回truecallback();} else {// 格式不正确return callback(new Error("邮箱格式不对"));}};//验证手机号的正则var checkMobile = (rule, value, callback) => {var mobileReg = /^1[3456789]\d{9}$/;if (mobileReg.test(value)) {// 格式正确返回truecallback();} else {// 格式不正确return callback(new Error("手机号格式不对"));}};return {input: "",tableData: [],//获取用户列表的参数queryInfo: {query: "",pagenum: 1, //当前页数pagesize: 10, //当前每页显示多少数据},total: 0,addDialogVisible: false, //添加弹出框//添加 验证addFormRules: {username: [{ required: true, message: "请输入活动名称", trigger: "blur" },{ min: 3, max: 5, message: "长度在 3 到 5 个字符", 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" },],},addForm: {username: "",password: "",email: "",mobile: "",},dialogVisible: false, //编辑弹出框//验证 修改editFormRules: {email: [{ required: true, message: "请输入邮箱地址", trigger: "blur" },{ validator: checkEmail, trigger: "blur" },],mobile: [{ required: true, message: "请输入电话号码", trigger: "blur" },{ validator: checkMobile, trigger: "blur" },],},editForm: {},//需要被分配的用户信息userInfo: {},selectedRole: "",//角色列表roleList: [],roleDialogVisible: false,};},
当关闭对话框时,重置表单。
修改用户信息
根据id查询需要修改的用户数据
//显示编辑对话框editState(id) {userAPI.editState(id).then((res) => {console.log(res);this.editForm = res.data.data;});this.dialogVisible = true;},
弹出框数据绑定及验证:
//控制修改用户对话框的显示与否
editDialogVisible: false,
//修改用户的表单数据
editForm: {username: '',email: '',mobile: ''
},
//修改表单的验证规则对象
editFormRules: {email: [{ required: true, message: '请输入邮箱', trigger: 'blur' },{validator: checkEmail,message: '邮箱格式不正确,请重新输入',trigger: 'blur'}],mobile: [{ required: true, message: '请输入手机号码', trigger: 'blur' },{validator: checkMobile,message: '手机号码不正确,请重新输入',trigger: 'blur'}]
}
用户点击确定按钮,验证数据成功发送请求完成修改
editUser() {this.$refs.editRuleForm.validate((valid) => {if (valid) {userAPI.editUsers(id, {email: this.editForm.email,mobile: this.editForm.mobile,}).then((res) => {console.log(res);if (res.data.meta.status == 200) {this.$message.success("编辑用户成功");} else {this.$message.error("编辑用户失败");}this.dialogVisible = false;this.getTableData();});}});},
删除用户:
根据id删除相对应的数据
delUser(id) {userAPI.deletUser(id).then((res) => {console.log(res);if (res.data.meta.status == 200) {this.$message.success("用户删除成功");} else {this.$message.error("用户删除失败");}this.getTableData();});},
权限管理
权限列表
使用element-ui组件添加面包屑导航
显示数据
<el-table :data="rightsList" border style="width: 100%"><el-table-column type="index" label="#" width="60"></el-table-column><el-table-columnprop="authName"label="权限名称"width="360"></el-table-column><el-table-column prop="path" label="路径" width="360"></el-table-column><el-table-column prop="level" label="权限等级" width="360"><template slot-scope="scope"><el-tag v-if="scope.row.level === '0'">一级</el-tag><el-tag type="success" v-else-if="scope.row.level === '1'">二级</el-tag><el-tag type="warning" v-else>三级</el-tag></template></el-table-column></el-table>created() {roleAPI.getRightList(this.rightsList).then((res) => {console.log(res);this.rightsList = res.data.data;});},
角色列表
使用element-ui面包屑导航,进行布局
显示数据
<el-table row-key="id" :data="roleList" border><!-- 添加展开列 --><el-table-column type="expand"></el-table-column><el-table-column type="index"></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="warning" icon="el-icon-setting">分配权限</el-button></template></el-table-column>
</el-table>export default {data(){return {roleList:[]}},created(){this.getRoleList();},methods:{async getRoleList(){const {data:res} = await this.$http.get('roles')console.log(res.data)this.roleList = res.data;}}
}
生成权限列表
<el-table-column type="expand"><el-row slot-scope="scope"><el-rowv-for="item in scope.row.children":key="item.id"style="border-bottom: 1px solid #eee"><!-- 一级权限 --><el-col :span="5"><el-tag closable @close="removeById(scope.row, item.id)">{{item.authName}}</el-tag><i class="el-icon-caret-right"></i></el-col><!-- 二级权限和三级权限 --><el-col :span="19"><el-rowv-for="item1 in item.children":key="item1.id"style="border-bottom: 1px solid #eee"><el-col :span="6"><el-tagclosabletype="success"@close="removeById(scope.row, item1.id)">{{ item1.authName }}</el-tag><i class="el-icon-caret-right"></i></el-col><el-col :span="14"><el-tagtype="warning"v-for="item2 in item1.children":key="item2.id"closable@close="removeById(scope.row, item2.id)">{{ item2.authName }}</el-tag></el-col></el-row></el-col></el-row></el-row></el-table-column>
添加权限删除功能
//删除权限removeById(role, rightId) {// console.log(role,rightId);this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {roleAPT.removeRightsById(this.roleId,this.rightId).then((res) => {role.children = res.data.data;});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},
.完成树形结构弹窗
在element.js中引入Tree,注册Tree
<!-- 分配权限对话框 -->
<el-dialog title="分配权限" :visible.sync="setRightDialogVisible" width="50%" @close="setRightDialogClose"><!-- 树形组件show-checkbox:显示复选框node-key:设置选中节点对应的值default-expand-all:是否默认展开所有节点:default-checked-keys 设置默认选中项的数组ref:设置引用 --><el-tree :data="rightsList" :props="treeProps" show-checkbox node-key="id" default-expand-all :default-checked-keys="defKeys" ref="treeRef"></el-tree><span slot="footer" class="dialog-footer"><el-button @click="setRightDialogVisible = false">取 消</el-button><el-button type="primary" @click="allotRights">确 定</el-button></span>
</el-dialog><script>
export default {data() {return {//角色列表数据roleList: [],//控制分配权限对话框的显示setRightDialogVisible: false,//权限树数据rightsList: [],//树形控件的属性绑定对象treeProps: {//通过label设置树形节点文本展示authNamelabel: 'authName',//设置通过children属性展示子节点信息children: 'children'},//设置树形控件中默认选中的内容defKeys: [],//保存正在操作的角色idroleId:''}},created() {this.getRoleList()},methods: {async getRoleList() {const { data: res } = await this.$http.get('roles')//如果返回状态为异常状态则报错并返回if (res.meta.status !== 200)return this.$message.error('获取角色列表失败')//如果返回状态正常,将请求的数据保存在data中// this.roleList = res.dataconsole.log(res.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('已经取消删除')}//用户点击了确定表示真的要删除//当发送delete请求之后,返回的数据就是最新的角色权限信息const { data: res } = await this.$http.delete(`roles/${role.id}/rights/${rightId}`)if (res.meta.status !== 200)return this.$message.error('删除角色权限失败')//无需再重新加载所有权限//只需要对现有的角色权限进行更新即可role.children = res.data// this.getRoleList();},async showSetRightDialog(role) {//将role.id保存起来以供保存权限时使用this.roleId = role.id; //获取所有权限的数据const { data: res } = await this.$http.get('rights/tree')//如果返回状态为异常状态则报错并返回if (res.meta.status !== 200) return this.$message.error('获取权限树失败')//如果返回状态正常,将请求的数据保存在data中this.rightsList = res.data//调用getLeafKeys进行递归,将三级权限添加到数组中this.getLeafKeys(role, this.defKeys)//当点击分配权限按钮时,展示对应的对话框this.setRightDialogVisible = trueconsole.log(this.defKeys)},getLeafKeys(node, arr) {//该函数会获取到当前角色的所有三级权限id并添加到defKeys中//如果当前节点不包含children属性,则表示node为三级权限if (!node.children) {return arr.push(node.id)}//递归调用node.children.forEach(item => this.getLeafKeys(item, arr))},setRightDialogClose() {//当用户关闭树形权限对话框的时候,清除掉所有选中状态this.defKeys = []},async allotRights() {//当用户在树形权限对话框中点击确定,将用户选择的//权限发送请求进行更新//获取所有选中及半选的内容const keys = [...this.$refs.treeRef.getCheckedKeys(),...this.$refs.treeRef.getHalfCheckedKeys()]//将数组转换为 , 拼接的字符串const idStr = keys.join(',')//发送请求完成更新const { data: res } = await this.$http.post(`roles/${this.roleId}/rights`,{ rids:idStr })if (res.meta.status !== 200)return this.$message.error('分配权限失败')this.$message.success("分配权限成功")this.getRoleList();//关闭对话框this.setRightDialogVisible = false;}}
}
</script>
商品分类
请求分类数据
data() {return {queryInfo: {query: "",// 当前的页数pagenum: 1,// 当前每页显示多少条数据pagesize: 10,},goodsList: [],total: 0,};},getGoodsList() {goodAPI.getGoodsList(this.queryInfo).then((res) => {console.log(res);this.goodsList = res.data.data.goods;this.total = res.data.data.total;});},
完成分页功能:
// 监听 pagesize 改变的事件handleSizeChange(newSize) {this.queryInfo.pagesize = newSize;this.getGoodsList();},// 监听 页码值 改变的事件handleCurrentChange(newPage) {this.queryInfo.pagenum = newPage;this.getGoodsList();},
完成添加分类
<template><div><!-- 面包屑导航区域 --><el-breadcrumb separator-class="el-icon-arrow-right"><el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item><el-breadcrumb-item>商品管理</el-breadcrumb-item><el-breadcrumb-item>商品分类</el-breadcrumb-item></el-breadcrumb><!-- 卡片视图区域 --><el-card><el-row><el-col><el-button type="primary" @click="showAddCateDialog">添加分类</el-button></el-col></el-row><!-- 表格 --><tree-table class="treeTable" :data="catelist" :columns="columns" :selection-type="false" :expand-type="false" show-index index-text="#" border :show-row-hover="false"><!-- 是否有效 --><template slot="isok" slot-scope="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 type="success" size="mini" v-else-if="scope.row.cat_level===1">二级</el-tag><el-tag type="warning" size="mini" v-else>三级</el-tag></template><!-- 操作 --><template slot="opt"><el-button type="primary" icon="el-icon-edit" size="mini">编辑</el-button><el-button type="danger" icon="el-icon-delete" size="mini">删除</el-button></template></tree-table><!-- 分页区域 --><el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="querInfo.pagenum" :page-sizes="[3, 5, 10, 15]" :page-size="querInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total"></el-pagination></el-card><!-- 添加分类的对话框 --><el-dialog title="添加分类" :visible.sync="addCateDialogVisible" 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="父级分类:"><!-- options 用来指定数据源 --><!-- props 用来指定配置对象 --><el-cascader expand-trigger="hover" :options="parentCateList" :props="cascaderProps" v-model="selectedKeys" @change="parentCateChanged" clearable change-on-select></el-cascader></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="addCateDialogVisible = false">取 消</el-button><el-button type="primary" @click="addCate">确 定</el-button></span></el-dialog></div>
</template><script>
import goodAPI from "../../http/API/Good"
export default {data() {return {// 查询条件querInfo: {type: 3,pagenum: 1,pagesize: 5},// 商品分类的数据列表,默认为空catelist: [],// 总数据条数total: 0,// 为table指定列的定义columns: [{label: '分类名称',prop: 'cat_name'},{label: '是否有效',// 表示,将当前列定义为模板列type: 'template',// 表示当前这一列使用模板名称template: 'isok'},{label: '排序',// 表示,将当前列定义为模板列type: 'template',// 表示当前这一列使用模板名称template: 'order'},{label: '操作',// 表示,将当前列定义为模板列type: 'template',// 表示当前这一列使用模板名称template: 'opt'}],// 控制添加分类对话框的显示与隐藏addCateDialogVisible: false,// 添加分类的表单数据对象addCateForm: {// 将要添加的分类的名称cat_name: '',// 父级分类的Idcat_pid: 0,// 分类的等级,默认要添加的是1级分类cat_level: 0},// 添加分类表单的验证规则对象addCateFormRules: {cat_name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }]},// 父级分类的列表parentCateList: [],// 指定级联选择器的配置对象cascaderProps: {value: 'cat_id',label: 'cat_name',children: 'children'},// 选中的父级分类的Id数组selectedKeys: []}},created() {this.getCateList()},methods: {// 获取商品分类数据async getCateList() {const { data: res } = await goodAPI.getCateList(this.querInfo)if (res.meta.status !== 200) {return this.$message.error('获取商品分类失败!')}console.log(res.data)// 把数据列表,赋值给 catelistthis.catelist = res.data.result// 为总数据条数赋值this.total = res.data.total},// 监听 pagesize 改变handleSizeChange(newSize) {this.querInfo.pagesize = newSizethis.getCateList()},// 监听 pagenum 改变handleCurrentChange(newPage) {this.querInfo.pagenum = newPagethis.getCateList()},// 点击按钮,展示添加分类的对话框showAddCateDialog() {// 先获取父级分类的数据列表this.getParentCateList()// 再展示出对话框this.addCateDialogVisible = true},// 获取父级分类的数据列表async getParentCateList() {const { data: res } = await goodAPI.getCateList( this.querInfo,{params: { type: 2 }})if (res.meta.status !== 200) {return this.$message.error('获取父级分类数据失败!')}console.log(res.data)this.parentCateList = res.data},// 选择项发生变化触发这个函数parentCateChanged() {console.log(this.selectedKeys)// 如果 selectedKeys 数组中的 length 大于0,证明选中的父级分类// 反之,就说明没有选中任何父级分类if (this.selectedKeys.length > 0) {// 父级分类的Idthis.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1]// 为当前分类的等级赋值this.addCateForm.cat_level = this.selectedKeys.length} else {// 父级分类的Idthis.addCateForm.cat_pid = 0// 为当前分类的等级赋值this.addCateForm.cat_level = 0}},// 点击按钮,添加新的分类addCate() {this.$refs.addCateFormRef.validate(async valid => {if (!valid) returnconst { data: res } = await this.$http.post('categories', this.addCateForm)if (res.meta.status !== 201) {return this.$message.error('添加分类失败!')}this.$message.success('添加分类成功!')this.getCateList()this.addCateDialogVisible = false})},// 监听对话框的关闭事件,重置表单数据addCateDialogClosed() {this.$refs.addCateFormRef.resetFields()this.selectedKeys = []this.addCateForm.cat_level = 0this.addCateForm.cat_pid = 0}}
}
</script>
参数管理
首先完成参数管理的布局
<template><div><!-- 面包屑导航区域 --><Break></Break><!-- 卡片区域 --><el-card class="box-card"><!-- 警告区域 --><el-alerttitle="注意:只允许为第三级分类设置相关参数!"type="warning"effect="dark":closable="false"show-icon></el-alert><div class="selectType"><span>选择商品分类:</span><!-- 级联选择器 --><el-cascaderv-model="selectValue":options="selectOptions":props="cateProps"@change="selectChange"></el-cascader></div><el-tabstype="card"class="tabsCard"v-model="activeName"@tab-click="tabClick"><!-- 动态参数 --><el-tab-pane label="动态参数" name="many"><!-- 添加参数 --><el-buttontype="primary"size="mini"class="btn":disabled="btnDisabled"@click="addDialogVisible = true">添加参数</el-button><!-- 添加参数 表格 --><el-table :data="manyParams" style="width: 100%" border><el-table-column type="expand"><template slot-scope="scope"><el-tag:key="index"v-for="(item, index) in scope.row.attr_vals"closable:disable-transitions="false"@close="attrHandleClose(tag)">{{ item }}</el-tag><el-inputclass="input-new-tag"v-if="scope.row.inputVisible"v-model="scope.row.inputValue"ref="saveTagInput"size="small"@keyup.enter.native="handleInputConfirm(scope.row)"@blur="handleInputConfirm(scope.row)"></el-input><el-buttonv-elseclass="button-new-tag"size="small"@click="showInput(scope.row)">+ New Tag</el-button></template></el-table-column><el-table-column type="index" label="#"> </el-table-column><el-table-column label="参数名称" prop="attr_name"></el-table-column><el-table-column label="操作" prop="id"><template v-slot="scope"><el-buttontype="primary"size="mini"icon="el-icon-edit"@click="editParamsDialog(scope.row)">修改</el-button><el-buttontype="danger"size="mini"icon="el-icon-delete"@click="delParams(scope.row.attr_id)">删除</el-button></template></el-table-column></el-table></el-tab-pane><!-- 静态属性 --><el-tab-pane label="静态属性" name="only"><!-- 添加属性 --><el-buttontype="primary"size="mini"class="btn":disabled="btnDisabled"@click="addDialogVisible = true">添加属性</el-button><!-- 添加属性表格 --><el-table :data="onlyParams" style="width: 100%" border><el-table-column type="expand"><template slot-scope="scope"><el-tag:key="index"v-for="(item, index) in scope.row.attr_vals"closable:disable-transitions="false"@close="attrHandleClose(tag)">{{ item }}</el-tag><el-inputclass="input-new-tag"v-if="scope.row.inputVisible"v-model="scope.row.inputValue"ref="saveTagInput"size="small"@keyup.enter.native="handleInputConfirm(scope.row)"@blur="handleInputConfirm(scope.row)"></el-input><el-buttonv-elseclass="button-new-tag"size="small"@click="showInput(scope.row)">+ New Tag</el-button></template></el-table-column><el-table-column type="index" label="#"> </el-table-column><el-table-column label="参数名称" prop="attr_name"></el-table-column><el-table-column label="操作" prop="id"><template v-slot="scope"><el-buttontype="primary"size="mini"icon="el-icon-edit"@click="editParamsDialog(scope.row)">修改</el-button><el-buttontype="danger"size="mini"icon="el-icon-delete"@click="delParams(scope.row.attr_id)">删除</el-button></template></el-table-column></el-table></el-tab-pane></el-tabs></el-card><!-- 修改动态参数弹出框 --><el-dialog:title="'修改' + titleText":visible.sync="editManyDialogVisible"width="30%"><el-form:model="editManyRuleForm":rules="editManyRules"ref="edifManyRuleFormRef"label-width="100px"class="demo-ruleForm"><el-form-item label="动态参数" prop="attr_name"><el-input v-model="editManyRuleForm.attr_name"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="editManyDialogVisible = false">取 消</el-button><el-button type="primary" @click="editMany">确 定</el-button></span></el-dialog><!-- 添加参数弹出框 --><el-dialog:title="'添加' + titleText":visible.sync="addDialogVisible"width="30%"><el-form:model="addParamsForm":rules="editManyRules"ref="addRuleFormRef"label-width="100px"class="demo-ruleForm"><el-form-item label="动态参数" prop="attr_name"><el-input v-model="addParamsForm.attr_name"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="addDialogVisible = false">取 消</el-button><el-button type="primary" @click="addParams">确 定</el-button></span></el-dialog></div>
</template>
完成级联选择框
<!-- 选择商品分类区域 -->
<el-row class="cat_opt"><el-col><span>选择商品分类:</span><!-- 选择商品分类的级联选择框 --><el-cascader expandTrigger='hover' v-model="selectedCateKeys" :options="cateList" :props="cateProps" @change="handleChange" clearable></el-cascader></el-col><el-col></el-col>
</el-row>
......
<script>
export default {data() {return {//分类列表cateList:[],//用户在级联下拉菜单中选中的分类idselectedCateKeys:[],//配置级联菜单中数据如何展示cateProps: {value: 'cat_id',label: 'cat_name',children: 'children'}}},created() {this.getCateList()},methods: {async getCateList(){//获取所有的商品分类列表const { data: res } = await this.$http.get('categories')if (res.meta.status !== 200) {return this.$message.error('获取分类数据失败')}//将数据列表赋值给cateListthis.cateList = res.data// //保存总数据条数// this.total = res.data.total// console.log(res.data);},handleChange(){//当用户在级联菜单中选择内容改变时触发console.log(this.selectedCateKeys);}}
}
</script>
展示参数
展示动态参数数据以及静态属性数据
<!-- tab页签区域 -->
<el-tabs v-model="activeName" @tab-click="handleTabClick"><!-- 添加动态参数的面板 将标签页改为many --><el-tab-pane label="动态参数" name="many"><el-button size="mini" type="primary" :disabled="isButtonDisabled">添加参数</el-button><!-- 动态参数表格 --><el-table :data="manyTableData" border stripe><!-- 展开行 --><el-table-column type="expand"></el-table-column><!-- 索引列 --><el-table-column type="index"></el-table-column><el-table-column label="参数名称" prop="attr_name"></el-table-column><el-table-column label="操作"><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></template></el-table-column></el-table></el-tab-pane><!-- 添加静态属性的面板 将标签页改为only --><el-tab-pane label="静态属性" name="only"><el-button size="mini" type="primary" :disabled="isButtonDisabled">添加属性</el-button><!-- 静态属性表格 --><el-table :data="onlyTableData" border stripe><!-- 展开行 --><el-table-column type="expand"></el-table-column><!-- 索引列 --><el-table-column type="index"></el-table-column><el-table-column label="属性名称" prop="attr_name"></el-table-column><el-table-column label="操作"><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></template></el-table-column></el-table></el-tab-pane></el-tabs><script>
export default {data() {return {......//tab页签激活显示的页签项activeName: 'many',//用来保存动态参数数据manyTableData: [],//用来保存静态属性数据onlyTableData: [] }methods: {.......async handleChange() {//当用户在级联菜单中选择内容改变时触发console.log(this.selectedCateKeys)//发送请求,根据用户选择的三级分类和面板获取参数数据const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`,{ params: { sel: this.activeName } })if (res.meta.status !== 200) {return this.$message.error('获取参数列表数据失败')}console.log(res.data)if (this.activeName === 'many') {//获取的是动态参数this.manyTableData = res.data} else if (this.activeName === 'only') {//获取的是静态属性this.onlyTableData = res.data}},handleTabClick() {console.log(this.activeName)this.handleChange()}},computed: {//添加计算属性用来获取按钮禁用与否isButtonDisabled() {return this.selectedCateKeys.length !== 3},//获取选中的三级分类idcateId() {if (this.selectedCateKeys.length === 3) {return this.selectedCateKeys[this.selectedCateKeys.length - 1]}return null}}
添加参数
<!-- 添加参数或属性对话框 -->
<el-dialog :title="'添加'+titleText" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed"><!-- 添加表单 --><el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px"><el-form-item :label="titleText" prop="attr_name"><el-input v-model="addForm.attr_name"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="addDialogVisible = false">取 消</el-button><el-button type="primary" @click="addParams">确 定</el-button></span>
</el-dialog>export default {data() {return {.......//控制添加参数.属性对话框的显示或隐藏addDialogVisible: false,//添加参数的表单数据对象addForm: {attr_name: ''},//添加表单验证规则addFormRules: {attr_name: [{ required: true, message: '请输入名称', trigger: 'blur' }]}}},methods: {.......addParams() {//当用户点击对话框中的确定时,校验表单this.$refs.addFormRef.validate(async valid => {//校验不通过,returnif (!valid) return//校验通过,发送请求完成添加参数或者属性const { data: res } = this.$http.post(`categories/${this.cateId}/attributes`,{ attr_name: this.addForm.attr_name, attr_sel: this.activeName,attr_vals: "a,b,c" })console.log(res)if (res.meta.status !== 201) {return this.$message.error('添加' + this.titleText + '数据失败')}this.$message.success('添加' + this.titleText + '数据成功')this.addDialogVisible = falsethis.getCateList()})}}
编辑参数
<!-- 修改参数或属性对话框 -->
<el-dialog :title="'修改'+titleText" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed"><!-- 添加表单 --><el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px"><el-form-item :label="titleText" prop="attr_name"><el-input v-model="editForm.attr_name"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="editDialogVisible = false">取 消</el-button><el-button type="primary" @click="editParams">确 定</el-button></span>
</el-dialog>export default {data() {return {.......//控制修改参数.属性对话框的显示或隐藏editDialogVisible:false,//修改参数.属性对话框中的表单editForm:{attr_name:''},//修改表单的验证规则editFormRules:{attr_name:[{ required: true, message: '请输入名称', trigger: 'blur' }]}}},methods: {.......async showEditDialog(attr_id){//发起请求获取需要修改的那个参数数据const {data:res} = await this.$http.get(`categories/${this.cateId}/attributes/${attr_id}`,{params:{ attr_sel:this.activeName }})if (res.meta.status !== 200) {return this.$message.error('获取参数数据失败')}this.editForm = res.data;//显示修改参数.属性对话框this.editDialogVisible = true;},editDialogClosed(){//当关闭修改参数.属性对话框时this.$refs.editFormRef.resetFields()},editParams(){//验证表单this.$refs.editFormRef.validate(async valid => {if(!valid) return;//发送请求完成修改const {data:res} = await this.$http.put(`categories/${this.cateId}/attributes/${this.editForm.attr_id}`,{attr_name:this.editForm.attr_name,attr_sel:this.activeName})if (res.meta.status !== 200) {return this.$message.error('获取参数数据失败')}this.$message.success('修改' + this.titleText + '数据成功')this.editDialogVisible = falsethis.handleChange();})}}
删除参数
给两个删除按钮添加事件
<el-button size="mini" type="danger" icon="el-icon-delete" @click="removeParams(scope.row.attr_id)">删除</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete" @click="removeParams(scope.row.attr_id)">删除</el-button>添加对应的事件处理函数
async removeParams(attr_id){//根据id删除对应的参数或属性//弹窗提示用户是否要删除const confirmResult = await this.$confirm('请问是否要删除该'+this.titleText,'删除提示',{confirmButtonText: '确认删除',cancelButtonText: '取消',type: 'warning'}).catch(err => err)//如果用户点击确认,则confirmResult 为'confirm'//如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel'if (confirmResult != 'confirm') {return this.$message.info('已经取消删除')}//没有取消就是要删除,发送请求完成删除const {data:res} = await this.$http.delete(`categories/${this.cateId}/attributes/${attr_id}`)if (res.meta.status !== 200) {return this.$message.error('删除参数数据失败')}this.$message.success('删除' + this.titleText + '数据成功')this.handleChange()
}
商品列表
<template><div><!-- 面包屑导航区域 --><Break></Break><!-- 卡片视图区域 --><el-card><!-- 搜索与添加区域 --><el-row :gutter="20"><el-col :span="8"><el-input placeholder="请输入内容" clearable><el-button slot="append" icon="el-icon-search"></el-button></el-input></el-col><el-col :span="4"><el-button type="primary" @click="addGoods">添加商品</el-button></el-col></el-row><!-- 用户列表区域 --><el-table :data="goodsList" border><el-table-column label="#" type="index"></el-table-column><el-table-columnlabel="商品名称"prop="goods_name"width="672px"></el-table-column><el-table-columnlabel="商品价格(元)"prop="goods_price"width="100px"></el-table-column><el-table-columnlabel="商品重量"prop="goods_weight"width="100px"></el-table-column><el-table-columnlabel="创建时间"prop="add_time"width="150px"></el-table-column><el-table-column label="操作" width="150px"><template slot-scope="scope"><!-- 修改按钮 --><el-buttontype="primary"icon="el-icon-edit"size="mini"></el-button><!-- 删除按钮 --><el-buttontype="danger"icon="el-icon-delete"size="mini"@click="removeGoods(scope.row.id)"></el-button></template></el-table-column></el-table><!-- 分页器 --><el-pagination@size-change="handleSizeChange"@current-change="handleCurrentChange":current-page="queryInfo.pagenum":page-sizes="[5, 10, 20, 50]":page-size="queryInfo.pagesize"layout="total, sizes, prev, pager, next, jumper":total="total"></el-pagination></el-card></div>
</template><script>
import goodAPI from "../../http/API/Good"
import Break from "../bread";
export default {components: {Break,},data() {return {queryInfo: {query: "",// 当前的页数pagenum: 1,// 当前每页显示多少条数据pagesize: 10,},goodsList: [],total: 0,};},methods: {// 监听 pagesize 改变的事件handleSizeChange(newSize) {this.queryInfo.pagesize = newSize;this.getGoodsList();},// 监听 页码值 改变的事件handleCurrentChange(newPage) {this.queryInfo.pagenum = newPage;this.getGoodsList();},//获取商品列表getGoodsList() {goodAPI.getGoodsList(this.queryInfo).then((res) => {console.log(res);this.goodsList = res.data.data.goods;this.total = res.data.data.total;});},//添加商品addGoods() {this.$router.push("/goods/addgoods");},},created() {this.getGoodsList();},mounted() {},
};
</script> <style scoped></style>
在mian.js添加过滤器:将时间戳转换为年月日
Vue.filter('dateFormat',function(originVal){const dt = new Date(originVal)const y = dt.getFullYear()const m = (dt.getMonth()+1+'').padStart(2,'0')const d = (dt.getDate()+'').padStart(2,'0')const hh = (dt.getHours()+'').padStart(2,'0')const mm = (dt.getMinutes()+'').padStart(2,'0')const ss = (dt.getSeconds()+'').padStart(2,'0')return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
})
实现删除商品
//删除商品removeGoods(id) {this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {goodAPI.deleteGoods(id).then((res) => {console.log(res);});}).then(() => {this.$message({type: "success",message: "删除成功!",});this.getGoodsList();}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},
添加商品:
点击添加跳转到另一个页面
//添加商品addGoods() {this.$router.push("/goods/addgoods");},
在添加组件中进行布局:
<template><div><!-- 面包屑导航区域 --><Break></Break><!-- 卡片区域 --><el-card><!-- 提示区 --><el-alerttitle="添加商品信息"type="info"centershow-icon:closable="false"></el-alert><!-- 步骤条 --><el-steps:space="200":active="activeIndex - 0"finish-status="success"align-center><el-step title="基本信息"></el-step><el-step title="商品参数"></el-step><el-step title="商品属性"></el-step><el-step title="商品图片"></el-step><el-step title="商品内容"></el-step><el-step title="完成"></el-step></el-steps><!-- tab栏区域 --><el-form:model="addForm":rules="addrules"ref="addGoodsForm"label-width="100px"label-position="top"><el-tabsv-model="activeIndex":tab-position="'left'"@tab-click="tabClick":before-leave="beforeTabLeave"><el-tab-pane label="基本信息" name="0"><el-form-item label="商品名称 " prop="goods_name"><el-input v-model="addForm.goods_name"></el-input></el-form-item><el-form-item label="商品价格" prop="goods_price"><el-input v-model="addForm.goods_price" type="number"></el-input></el-form-item><el-form-item label="商品重量" prop="goods_weight"><el-input v-model="addForm.goods_weight" type="number"></el-input></el-form-item><el-form-item label="商品数量" prop="goods_number"><el-input v-model="addForm.goods_number" type="number"></el-input></el-form-item><el-form-item label="商品分类" prop><el-cascaderexpand-trigger="hover"v-model="addForm.goods_cat":options="cateList":props="cateProps"@change="handleChange"></el-cascader></el-form-item></el-tab-pane><el-tab-pane label="商品参数" name="1"><el-form-item:label="item.attr_name"v-for="item in manyTableData":key="item.attr_id"><!-- 复选框组 --><el-checkbox-group v-model="item.attr_vals"><el-checkbox:label="cb"v-for="(cb, i) in item.attr_vals":key="i"></el-checkbox></el-checkbox-group></el-form-item></el-tab-pane><el-tab-pane label="商品属性" name="2"><el-form-item:label="item.attr_name"v-for="item in onlyTableData":key="item.attr_id"><el-input v-model="item.attr_vals"></el-input></el-form-item></el-tab-pane><el-tab-pane label="商品图片" name="3"><!-- 商品图片上传action:指定图片上传api接口:on-preview : 当点击图片时会触发该事件进行预览操作,处理图片预览:on-remove : 当用户点击图片右上角的X号时触发执行:on-success:当用户点击上传图片并成功上传时触发list-type :设置预览图片的方式:headers :设置上传图片的请求头 --><el-uploadaction="http://127.0.0.1:8888/api/private/v1/upload":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-tab-pane label="商品内容" name="4"><!-- 富文本编辑器组件 --><quill-editor v-model="addForm.goods_introduce"></quill-editor><!-- 添加商品按钮 --><el-button type="primary" class="btnAdd" @click="handAdd">添加商品</el-button></el-tab-pane></el-tabs></el-form></el-card></div>
</template><script>
import goodAPI from "../../http/API/Good"
import Break from "../bread";
export default {components: {Break,},data() {return {activeIndex: "0",addForm: {goods_name: "",goods_price: 0,goods_weight: 0,goods_number: 0,//商品所属分类数组goods_cat: [],pics: [],goods_introduce: "",},addrules: {goods_name: [{ required: true, message: "请输入商品名称", trigger: "blur" },],goods_price: [{ required: true, message: "请输入商品价格", trigger: "blur" },],goods_weight: [{ required: true, message: "请输入商品重量", trigger: "blur" },],goods_number: [{ required: true, message: "请输入商品数量", trigger: "blur" },],goods_cat: [{ required: true, message: "请选择商品分类", trigger: "blur" },],},cateList: [],cateProps: {label: "cat_name",value: "cat_id",children: "children",},//动态参数列表数据manyTableData: [],//静态属性列表数据onlyTableData: [],//图片上传组件的headers请求头对象headerObj: { Authorization: window.sessionStorage.getItem("token") },//保存预览图片的url地址previewPath: "",//控制预览图片对话框的显示和隐藏previewVisible: false,};},methods: {getGoodsList() {this.$http.get("categories").then((res) => {this.cateList = res.data.data;});},//级联选择器选中项变化handleChange() {if (this.addForm.goods_cat.length !== 3) {this.addForm.goods_cat = [];}},beforeTabLeave(activeName, oldActiveName) {if (oldActiveName === "0" && this.addForm.goods_cat.length !== 3) {this.$message.error("请选择商品分类");return false;}},//商品参数tabClick() {if (this.activeIndex === "1") {goodAPI.getParamsList({params: { sel: "many" },}).then((res) => {res.data.data.forEach((item) => {item.attr_vals =item.attr_vals.length === 0 ? [] : item.attr_vals.split(" ");});this.manyTableData = res.data.data;});} else if (this.activeIndex === "2") {goodAPI.getParamsList({params: { sel: "only" },}).then((res) => {console.log(res);this.onlyTableData = res.data.data;});}},handlePreview(file) {this.previewPath = file.response.data.url;this.previewVisible = true;},handleRemove(file) {const filePath = file.response.data.tmp_path;const index = this.addForm.pics.findIndex((item) => item.pic === filePath);this.addForm.pics.splice(index, 1);},handleSuccess(response) {this.addForm.pics.push({ pic: response.data.tmp_path });},handAdd() {this.$refs.addGoodsForm.validate(async (valid) => {if (!valid) {return this.$message.error("请填写必要的选项!");}// console.log(this.addForm);let form = this.deepClone(this.addForm);form.goods_cat = form.goods_cat.join(",");form.attrs = [];this.manyTableData.forEach((item) => {form.attrs.push({attr_id: item.attr_id,attr_value: item.attr_vals.join(","),});});this.onlyTableData.forEach((item) => {form.attrs.push({attr_id: item.attr_id,attr_value: item.attr_vals,});});console.log(form);const { data: res } = await goodAPI.addGoods(form);// console.log(res);if (res.meta.status !== 201) return this.$message.error(res.meta.msg);this.$message.success("添加商品成功!");this.$router.push("/goods");});},deepClone(obj) {if (typeof obj !== "object" || obj == null) {return obj;}let result;if (obj instanceof Array) {result = [];} else {result = {};}for (let key in obj) {if (obj.hasOwnProperty(key)) {result[key] = this.deepClone(obj[key]);}}return result;},},created() {this.getGoodsList();},computed: {cateId() {if (this.addForm.goods_cat.length === 3) {return this.addForm.goods_cat[2];}return null;},},mounted() {},
};
</script> <style scoped>
.el-card {margin-top: 15px;
}
.el-steps {margin: 15px 0;
}
.avatar-uploader .el-upload {border: 1px dashed #d9d9d9;border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;
}
.avatar-uploader .el-upload:hover {border-color: #409eff;
}
.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 178px;height: 178px;line-height: 178px;text-align: center;
}
.avatar {width: 178px;height: 178px;display: block;
}
.ql-editor {min-height: 300px;
}
.btnAdd {margin-top: 15px;
}
</style>
完成图片上传
使用upload组件完成图片上传
在element.js中引入upload组件,并注册
因为upload组件进行图片上传的时候并不是使用axios发送请求
所以,我们需要手动为上传图片的请求添加token,即为upload组件添加headers属性
//在页面中添加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 title="图片预览" :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) {this.addForm.pics.push({ pic: response.data.tmp_path })}
}
使用富文本插件
引入并注册vue-quill-editor
//导入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)
<!-- 富文本编辑器组件 -->
<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;
}
添加商品:
//打开Add.vue,导入lodash
<script>
//官方推荐将lodash导入为_
import _ from 'lodash'//给添加商品按钮绑定点击事件
<!-- 添加商品按钮 -->
<el-button type="primary" class="btnAdd" @click="add">添加商品</el-button>
//编写点击事件完成商品添加
add(){this.$refs.addFormRef.validate(async valid=>{if(!valid) return this.$message.error("请填写必要的表单项!")//将addForm进行深拷贝,避免goods_cat数组转换字符串之后导致级联选择器报错const form = _.cloneDeep(this.addForm)//将goods_cat从数组转换为"1,2,3"字符串形式form.goods_cat = form.goods_cat.join(",")//处理attrs数组,数组中需要包含商品的动态参数和静态属性//将manyTableData(动态参数)处理添加到attrsthis.manyTableData.forEach(item=>{form.attrs.push({ attr_id:item.attr_id, attr_value:item.attr_vals.join(" ") }) })//将onlyTableData(静态属性)处理添加到attrsthis.onlyTableData.forEach(item=>{form.attrs.push({ attr_id:item.attr_id, attr_value:item.attr_vals }) })//发送请求完成商品的添加,商品名称必须是唯一的const {data:res} = await this.$http.post('goods',form)if(res.meta.status !== 201){return this.$message.error('添加商品失败')}this.$message.success('添加商品成功')//编程式导航跳转到商品列表this.$router.push('/goods')})
}
</script>
订单列表
实现数据展示及分页
<template><div><!-- 面包屑导航区域 --><Break></Break><!-- 卡片视图区域 --><el-card class="box-card"><el-row><el-col :span="8"><el-input placeholder="请输入内容"><el-button slot="append" icon="el-icon-search"></el-button></el-input></el-col></el-row><!-- 订单列表数据 --><el-table :data="orderList" border stripe class="tableList"><el-table-column type="index"></el-table-column><el-table-column label="订单编号" prop="order_number"></el-table-column><el-table-column label="订单价格" prop="order_price"></el-table-column><el-table-column label="是否付款" prop="pay_status"><template slot-scope="scope"><el-tag type="success" v-if="scope.row.pay_status === '1'">已付款</el-tag><el-tag type="danger" v-else>未付款</el-tag></template></el-table-column><el-table-column label="是否发货" prop="is_send"><template slot-scope="scope"><template>{{ scope.row.is_send }}</template></template></el-table-column><el-table-column label="下单时间" prop="create_time"><template slot-scope="scope">{{ scope.row.create_time | dataForm }}</template></el-table-column><el-table-column label="操作"><template><el-buttonsize="mini"type="primary"icon="el-icon-edit"@click="showEdit"></el-button><el-buttonsize="mini"type="success"icon="el-icon-location"@click="showProgress"></el-button></template></el-table-column></el-table><!-- 分页区域 --><el-pagination@size-change="handleSizeChange"@current-change="handleCurrentChange":current-page="queryInfo.pagenum":page-sizes="[5, 10, 15]":page-size="queryInfo.pagesize"layout="total, sizes, prev, pager, next, jumper":total="total"></el-pagination></el-card><!-- 修改地址的对话框 --><el-dialogtitle="修改地址":visible.sync="editDialogVisible"width="50%"@close="editDialogClosed"><el-form:model="EditRuleForm":rules="editFormRules"ref="addressFormRef"label-width="100px"><el-form-item label="省市区/县" prop="address1"><el-cascader:options="cityData"v-model="EditRuleForm.address1"></el-cascader></el-form-item><el-form-item label="详细地址" prop="address2"><el-input v-model="EditRuleForm.address2"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="editDialogVisible = false">取 消</el-button><el-button type="primary" @click="editDialogVisible = false">确 定</el-button></span></el-dialog><!-- 展示物流进度的对话框 --><el-dialog title="物流进度" :visible.sync="progressVisible" width="50%"><!-- 时间线 --><el-timeline><el-timeline-itemv-for="(activity, index) in progressInfo":key="index":timestamp="activity.time">{{ activity.context }}</el-timeline-item></el-timeline></el-dialog></div>
</template><script>
import cityData from "./citydata.js";
import Break from "../bread";
import ordersAPI from "../../http/API/Order"
export default {components: {Break,},data() {return {queryInfo: {query: "",pagenum: 1,pagesize: 10,},total: 0,orderList: [],editDialogVisible: false,EditRuleForm: {address1: [],address2: "",},editFormRules: {address1: [{ required: true, message: "请选择省市区县", trigger: "blur" },],address2: [{ required: true, message: "请填写详细地址", trigger: "blur" },],},cityData,progressVisible: false,progressInfo: [],};},created() {this.getOrderList();},methods: {async getOrderList() {let { data: res } = await ordersAPI.getOrder(this.queryInfo);// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取订单列表失败!");}console.log(res);this.total = res.data.total;this.orderList = res.data.goods;},handleSizeChange(newSize) {this.queryInfo.pagesize = newSize;this.getOrderList();},handleCurrentChange(newPage) {this.queryInfo.pagenum = newPage;this.getOrderList();},showEdit() {this.editDialogVisible = true;},editDialogClosed() {this.$refs.addressFormRef.resetFields();},async showProgress() {let { data: res } = await ordersAPI.getOrder();if (res.meta.status !== 200) {return this.$message.error("获取物流进度失败!");}this.progressInfo = res.data;this.progressVisible = true;console.log(this.progressInfo);},},
};
</script><style scoped>
.el-cascader {width: 100%;
}
.box-card{margin-top: 20px;
}
.tableList{margin-top: 20px;
}
</style>
数据统计
导入ECharts并使用
<template><div><!-- 面包屑导航区域 --><Break></Break><el-card><div id="myChart" :style="{ width: '700px', height: '400px' }"></div></el-card></div>
</template><script>
import reoprtsAPI from "../../http/API/reports"
import Break from "../bread";
import _ from "lodash";
export default {components: {Break,},data() {return {options: {title: {text: "用户来源",},tooltip: {trigger: "axis",axisPointer: {type: "cross",label: {backgroundColor: "#E9EEF3",},},},grid: {left: "3%",right: "4%",bottom: "3%",containLabel: true,},xAxis: [{boundaryGap: false,},],yAxis: [{type: "value",},],},};},mounted() {// 基于准备好的dom,初始化echarts实例let myChart = this.$echarts.init(document.getElementById("myChart"));reoprtsAPI.getreports().then((res) => {this.reportsList = res.data.data;//准备数据和配置项const result = _.merge(res.data.data, this.options);// 展示数据myChart.setOption(result);});},methods: {},
};
</script> <style scoped>
.el-card {margin-top: 15px;
}
</style>
完成以后进行项目优化
生产打包报告,根据报告优化项目
第三方库启用cdn
element-ui组件按需引入
路由懒加载
首页内容定制
添加进度条
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import "./assets/style.css";
import "./assets/fonts/iconfont.css";
import axios from 'axios'
import './plugins/element.js'
// import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import * as echarts from 'echarts';
import TreeTable from 'vue-table-with-tree-grid';
import VueQuillEditor from 'vue-quill-editor';
axios.defaults.baseURL = 'https://www.liulongbin.top:8888/api/private/v1/'
axios.interceptors.request.use((config) => {//当进入request拦截器,表示发送了请求,我们就开启进度条NProgress.start()//为请求头对象,添加token验证的Authorization字段config.headers.Authorization = localStorage.getItem('token');//必须返回configreturn config
});//在response拦截器中,隐藏进度条
axios.interceptors.response.use(config => {//当进入response拦截器,表示请求已经结束,我们就结束进度条NProgress.done()return config
})
执行build
打开Babel.config.js
生产打包报告
修改webpack的默认配置
```javascript
// vue.config.js
module.exports = {publicPath: "./", //部署应用包时的基本相对路径地址chainWebpack: confing => {//修改入口文件//when判断环境条件,配置//发布模式confing.when(process.env.NODE_ENV === "production", confing => {//entry找到当前默认的打包入口, 调用clear清空默认打包入口, 使用add添加新的打包入口//add方法后面参数为新的入口文件 地址confing.entry('app').clear().add('./src/main-prod.js');//在打包的时候首先检查window上是否有下列组件(CDN提前挂载),如果有的话就不在重新importconfing.set('externals', {vue: 'Vue','vue-router': 'VueRouter',axios: 'axios',lodash: '_',echarts: 'echarts',nprogress: 'NProgress','vue-quill-editor': 'VueQuillEditor'});//插件confing.plugin('html').tap(args => {args[0].isProd = true;return args})});// 开发模式confing.when(process.env.NODE_ENV === "development", confing => {confing.entry('app').clear().add('./src/main-dev.js');confing.plugin('html').tap(args => {//判断是否为发布模式手动添加属性值为是否是发布模式args[0].isProd = false;return args})});}
}
加载外部cdn
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'})})//开发模式config.when(process.env.NODE_ENV === 'development',config=>{config.entry('app').clear().add('./src/main-dev.js')})}
}
然后打开public/index.html添加外部cdn引入代码
<!DOCTYPE html>
<html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>电商管理系统</title><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"><link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.15.1/theme-chalk/index.css"><script src="https://cdn.staticfile.org/vue/2.6.11/vue.min.js"></script><script src="https://cdn.staticfile.org/vue-router/3.2.0/vue-router.min.js"></script><script src="https://cdn.staticfile.org/axios/0.21.1/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/5.0.2/echarts.min.js"></script><script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script><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><script src="https://cdn.staticfile.org/element-ui/2.15.1/index.js"></script><link href="css/goods.20d11ae6.css" rel="prefetch"><link href="css/login_home_welcome.f172da36.css" rel="prefetch"><link href="css/order.7d547f74.css" rel="prefetch"><link href="css/power.9d8f660a.css" rel="prefetch"><link href="css/report.d5a63329.css" rel="prefetch"><link href="css/user.feb292b3.css" rel="prefetch"><link href="js/goods.e452d00b.js" rel="prefetch"><link href="js/login_home_welcome.bb7e7edd.js" rel="prefetch"><link href="js/order.6115a44b.js" rel="prefetch"><link href="js/power.d3b71a1e.js" rel="prefetch"><link href="js/report.0e8de479.js" rel="prefetch"><link href="js/user.0bb9f204.js" rel="prefetch"><link href="css/app.599ad7ef.css" rel="preload" as="style"><link href="css/chunk-vendors.c470e980.css" rel="preload" as="style"><link href="js/app.4942fcdb.js" rel="preload" as="script"><link href="js/chunk-vendors.24e8034f.js" rel="preload" as="script"><link href="css/chunk-vendors.c470e980.css" rel="stylesheet"><link href="css/app.599ad7ef.css" rel="stylesheet">
</head><body><noscript><strong>We're sorry but vue_shop doesn't work properly without JavaScript enabled. Please enable it tocontinue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.24e8034f.js"></script><script src="js/app.4942fcdb.js"></script>
</body></html>
路由懒加载
const Login = () => import( /* webpackChunkName:"login_home_welcome" */ '../views/Login.vue')
const Home = () => import( /* webpackChunkName:"login_home_welcome" */ '../views/Home.vue')
const Welcome = () => import( /* webpackChunkName:"login_home_welcome" */ '../components/Welcome.vue')
const Users = () => import( /* webpackChunkName:"user" */ '../components/User/User.vue')
const Rights = () => import( /* webpackChunkName:"power" */ '../components/Role/rights.vue')
const Roles = () => import( /* webpackChunkName:"power" */ '../components/Role/roles.vue')
const Cate = () => import( /* webpackChunkName:"goods" */ '../components/Good/cate.vue')
const Params = () => import( /* webpackChunkName:"goods" */ '../components/Good/params.vue')
const GoodList = () => import( /* webpackChunkName:"goods" */ '../components/Good/goods.vue')
const GoodAdd = () => import( /* webpackChunkName:"goods" */ '../components/Good/addgoods.vue')
const Order = () => import( /* webpackChunkName:"order" */ '../components/Orders/orders.vue')
const Report = () => import( /* webpackChunkName:"report" */ '../components/Reports/reports.vue')
电商后台管理系统(各模块技术点)相关推荐
- Vue项目实战之电商后台管理系统(二) 主页模块
前言 目录 前言 一.主页布局 1.1 整体布局 1.2 头部区域布局 1.3 左侧菜单布局 1.3.1 静态布局 1.3.2 通过axios请求拦截器来进行权限验证 1.3.3 通过axios获取左 ...
- Vue项目实战之电商后台管理系统(一) 用户登录模块
目录 一.项目概述 二.项目初始化 2.1 前端项目初始化步骤 2.2 后台项目的环境安装配置 三.用户登录/登出功能实现 3.1 登录功能概述 3.1.1 登录状态保持 3.1.2 登录逻辑: 3. ...
- Vue全家桶 - 电商后台管理系统项目开发实录(详)
目录 1. 项目概述 1.1 电商项目基本业务概述 1.2 电商后台管理系统的功能 1.3 电商后台管理系统的开发模式(前.后端分离) 2. 项目初始化 2.1 前端项目初始化步骤 码云相关操作 2. ...
- 计算机毕业设计SSM电商后台管理系统【附源码数据库】
项目运行 环境配置: Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclis ...
- 计算机毕业设计node.js+vue+Element电商后台管理系统
项目介绍 网络的广泛应用给生活带来了十分的便利.所以把电商后台管理与现在网络相结合,利用node技术建设电商后台管理系统,实现电商后台管理的信息化.则对于进一步提高电商后台管理发展,丰富电商后台管理经 ...
- Vue电商后台管理系统项目开发实战(一)
前言 当下根据不同的应用场景,电商系统一般都提供了PC端,移动APP,移动Web,微信等多种访问方式.如下图. 不同的客户端共用同一个服务器,数据库,API.本次项目着重设计PC后台管理,供电商后台管 ...
- Vue2项目总结-电商后台管理系统
Vue2项目总结-电商后台管理系统 去年做的项目,拖了很久,总算是打起精力去做这个项目的总结,并对Vue2的相关知识进行回顾与复习 各个功能模块如果有过多重复冗杂的部分,将会抽取部分值得记录复习的地方 ...
- 电商后台管理系统简介
项目介绍 黑马后台管理系统是一个电商后台管理系统的前端项目,基于Vue+Element实现. 主要包括商品管理.订单管理.会员管理.促销管理.运营管理.内容管理.统计报表.财务管理.权限管理.设置等功 ...
- Vue+Element-UI 电商后台管理系统详细总结
一.概述 基于 Vue 和 Element-UI 的电商后台管理系统 1.1 实现功能 用户登录/退出 用户管理 用户列表 实现用户的增删改查.分页查询以及分配角色功能 权限管理 角色列表 实现角色的 ...
最新文章
- C#通过WMI的wind32 的API函数实现msinfo32的本地和远程计算机的系统摘要信息查看功能...
- 联机日志损坏时的恢复(非正常关闭数据库)
- 键盘按下某键 停止运行java_实现按下一个键执行操作/松开一个键停止操作
- 如何知道远程电脑某一端口是否打开?
- MyBatis创建SqlSession-怎么拿到一个SqlSessionTemplate?
- yml eureka defaultzone 只生效第一个_SpringCloud基础教程(三)-Eureka进阶
- pythonwhile循环怎么修改数据类型_python基础--数据类型循环
- vc6项目-vc8项目 转换日志
- 设计中最困难的部分是决定设计什么
- linux端口映射命令
- 《普林斯顿微积分读本》笔记-第2章三角学回顾
- 【数字图像处理5.3】SLIC算法 超像素分割(无监督聚类方式)python
- 计算机多媒体基础应用,《计算机应用基础》典型多媒体课件简介
- Python爬取——国家统计局省份加城市 并写入数据库
- Java实现PC端支付宝网页支付
- iPhone 14分辨率,屏幕尺寸,PPI 详细数据对比 iPhone 14 Plus、iPhone 14 Pro、iPhone 14 Pro Max
- MySQL5.7官方下载链接导航
- Linux下套接字详解(六)----基于pthread的多线程的TCP套接字(阻塞/同步/并发)
- 记一次PyQT5 core dump调试过程
- windoes 平台 Qt 的下载与安装-(Qt 5.15.2 LTS,这是一个长期支持版本)