登录:
在登陆页面输入账号密码,讲数据发送给服务器
服务器返回登录的结果,成功返回的数据中带有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-item
v-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-input
placeholder="请输入内容"
v-model="input"
clearable
class="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-switch
v-model="scope.row.mg_state"
@change="userEdit(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<!-- 修改按钮 -->
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="editState(scope.row.id)"
></el-button>
<!-- 删除按钮 -->
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="delUser(scope.row.id)"
></el-button>
<!-- 分配权限按钮 -->
<el-tooltip
effect="dark"
content="分配权限"
placement="top"
:enterable="false"
>
<el-button
type="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-switch
v-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-input
placeholder="请输入内容"
v-model="input"
clearable
class="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)) {
// 格式正确返回true
callback();
} else {
// 格式不正确
return callback(new Error("邮箱格式不对"));
}
};
//验证手机号的正则
var checkMobile = (rule, value, callback) => {
var mobileReg = /^1[3456789]\d{9}$/;
if (mobileReg.test(value)) {
// 格式正确返回true
callback();
} 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-column
prop="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-row
v-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-row
v-for="item1 in item.children"
:key="item1.id"
style="border-bottom: 1px solid #eee"
>
<el-col :span="6">
<el-tag
closable
type="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-tag
type="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设置树形节点文本展示authName
label: 'authName',
//设置通过children属性展示子节点信息
children: 'children'
},
//设置树形控件中默认选中的内容
defKeys: [],
//保存正在操作的角色id
roleId:''
}
},
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.data
console.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 = true
console.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: '',
// 父级分类的Id
cat_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)
// 把数据列表,赋值给 catelist
this.catelist = res.data.result
// 为总数据条数赋值
this.total = res.data.total
},
// 监听 pagesize 改变
handleSizeChange(newSize) {
this.querInfo.pagesize = newSize
this.getCateList()
},
// 监听 pagenum 改变
handleCurrentChange(newPage) {
this.querInfo.pagenum = newPage
this.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) {
// 父级分类的Id
this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1]
// 为当前分类的等级赋值
this.addCateForm.cat_level = this.selectedKeys.length
} else {
// 父级分类的Id
this.addCateForm.cat_pid = 0
// 为当前分类的等级赋值
this.addCateForm.cat_level = 0
}
},
// 点击按钮,添加新的分类
addCate() {
this.$refs.addCateFormRef.validate(async valid => {
if (!valid) return
const { 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 = 0
this.addCateForm.cat_pid = 0
}
}
}
</script>
参数管理
首先完成参数管理的布局
<template>
<div>
<!-- 面包屑导航区域 -->
<Break></Break>
<!-- 卡片区域 -->
<el-card class="box-card">
<!-- 警告区域 -->
<el-alert
title="注意:只允许为第三级分类设置相关参数!"
type="warning"
effect="dark"
:closable="false"
show-icon
>
</el-alert>
<div class="selectType">
<span>选择商品分类:</span>
<!-- 级联选择器 -->
<el-cascader
v-model="selectValue"
:options="selectOptions"
:props="cateProps"
@change="selectChange"
></el-cascader>
</div>
<el-tabs
type="card"
class="tabsCard"
v-model="activeName"
@tab-click="tabClick"
>
<!-- 动态参数 -->
<el-tab-pane label="动态参数" name="many">
<!-- 添加参数 -->
<el-button
type="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-input
class="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-button
v-else
class="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-button
type="primary"
size="mini"
icon="el-icon-edit"
@click="editParamsDialog(scope.row)"
>修改</el-button
>
<el-button
type="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-button
type="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-input
class="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-button
v-else
class="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-button
type="primary"
size="mini"
icon="el-icon-edit"
@click="editParamsDialog(scope.row)"
>修改</el-button
>
<el-button
type="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:[],
//用户在级联下拉菜单中选中的分类id
selectedCateKeys:[],
//配置级联菜单中数据如何展示
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('获取分类数据失败')
}
//将数据列表赋值给cateList
this.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
},
//获取选中的三级分类id
cateId() {
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 => {
//校验不通过,return
if (!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 = false
this.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 = false
this.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-column
label="商品名称"
prop="goods_name"
width="672px"
></el-table-column>
<el-table-column
label="商品价格(元)"
prop="goods_price"
width="100px"
></el-table-column>
<el-table-column
label="商品重量"
prop="goods_weight"
width="100px"
></el-table-column>
<el-table-column
label="创建时间"
prop="add_time"
width="150px"
></el-table-column>
<el-table-column label="操作" width="150px">
<template slot-scope="scope">
<!-- 修改按钮 -->
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
></el-button>
<!-- 删除按钮 -->
<el-button
type="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-alert
title="添加商品信息"
type="info"
center
show-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-tabs
v-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-cascader
expand-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-upload
action="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(动态参数)处理添加到attrs
this.manyTableData.forEach(item=>{
form.attrs.push({ attr_id:item.attr_id, attr_value:item.attr_vals.join(" ") })
})
//将onlyTableData(静态属性)处理添加到attrs
this.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-button
size="mini"
type="primary"
icon="el-icon-edit"
@click="showEdit"
></el-button>
<el-button
size="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-dialog
title="修改地址"
: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-item
v-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');
//必须返回config
return 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提前挂载),如果有的话就不在重新import
confing.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 to
continue.</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')