代码下载
商品分类页
新商品分类组件 goods/Cate.vue,在router.js中导入子级路由组件 Cate.vue,并设置路由规则。
绘制商品分类基本结构
在Cate.vue组件中添加面包屑导航以及卡片视图中的添加分类按钮:
<template>
<div>
<!-- 面包屑导航 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>商品管理</el-breadcrumb-item>
<el-breadcrumb-item>商品分类</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片 -->
<el-card>
<!-- 添加 -->
<el-button type="primary" @click="showAddCateDialog">添加分类</el-button>
<!-- 分类列表 -->
<tree-table class="tree-table" :data="catelist" :columns="columns" :selection-type = "false" :expand-type="false" :show-index="true" :index-text="'#'" border :show-row-hover="false">
<template slot="isOk" slot-scope="scope">
<i class="el-icon-error" style="color: red;" v-if="scope.row.cat_deleted"></i>
<i class="el-icon-success" style="color: lightgreen;" v-else></i>
</template>
<template slot="order" slot-scope="scope">
<el-tag v-if="scope.row.cat_level === 0">一级</el-tag>
<el-tag type="success" v-else-if="scope.row.cat_level === 1">二级</el-tag>
<el-tag type="warning" v-else>三级</el-tag>
</template>
<template slot="opt" 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="removeCateById(scope.row.cat_id)">删除</el-button>
</template>
</tree-table>
<!-- 分页 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pagenum"
:page-sizes="[2, 3, 5, 10]"
:page-size="queryInfo.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="父级分类:">
<el-cascader v-model="selectedKeys" :options="parentCateList" :props="cascaderProps" @change="parentCateChange" clearable></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>
export default {
data() {
return {
catelist: [],
queryInfo: {
type: 3,
pagenum: 1,
pagesize: 5
},
total: 0,
columns: [
{
label: '分类名称',
prop: 'cat_name'
},
{
label: '是否有效',
// 列类型,可选值有 'template'(自定义列模板)
type: 'template',
// 列类型为 'template'(自定义列模板) 时,对应的作用域插槽(它可以获取到 row, rowIndex, column, columnIndex)名称
template: 'isOk'
},
{
label: '排序',
type: 'template',
template: 'order'
},
{
label: '操作',
type: 'template',
template: 'opt'
}
],
addCateDialogVisible: false,
addCateForm: {
cat_pid: 0,
cat_name: '',
cat_level: 0
},
// 添加商品分类表单验证
addCateFormRules: {
cat_name: [
{ required: true, message: '请输入分类名称', trigger: 'blur' }
]
},
parentCateList: [],
cascaderProps: {
expandTrigger: 'hover',
checkStrictly: true,
value: 'cat_id',
label: 'cat_name',
children: 'children'
},
selectedKeys: []
}
},
created() {
this.getCateList()
},
methods: {
async getCateList() {
const { data: res } = await this.$http.get('categories', { params: this.queryInfo })
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.catelist = res.data.result
this.total = res.data.total
this.$msg.success('商品分类获取成功')
},
handleSizeChange(size) {
console.log('size: ', size);
this.queryInfo.pagesize = size
const maxN = parseInt(this.total / this.queryInfo.pagesize + '') + (this.total % this.queryInfo.pagesize > 0 ? 1 : 0)
this.queryInfo.pagenum = this.queryInfo.pagenum > maxN ? maxN : this.queryInfo.pagenum
this.getCateList()
},
handleCurrentChange(page) {
console.log('page: ', page);
this.queryInfo.pagenum = page
this.getCateList()
},
// 展示添加商品分类对话框
async showAddCateDialog() {
// 获取父级分类
const { data: res } = await this.$http.get('categories', { params: { type: 2 } })
if (res.meta.status !== 200) return this.$msg.error(res.meta.error)
this.parentCateList = res.data
this.addCateDialogVisible = true
},
// 关闭添加商品分类对话框
addCateDialogClosed() {
this.$refs.addCateFormRef.resetFields()
this.addCateForm.cat_pid = 0
this.addCateForm.cat_level = 0
this.addCateForm.cat_name = ''
this.selectedKeys = []
},
// 添加商品分类
async addCate() {
// 验证表单
this.$refs.addFormRef.validate(async valid => {
if (!valid) return this.$msg.error('请填写正确的分类名称')
const { data: res } = await this.$http.post('categories', this.addCateForm)
if (res.meta.status !== 201) return this.$msg.error(res.meta.msg)
this.$msg.success('添加商品分类成功')
this.getCateList()
// 关闭对话框,重置数据
this.addCateDialogVisible = false
})
},
parentCateChange(v) {
console.log('change: ', v);
// 处理父分类id和分类级别
if (this.selectedKeys.length > 0) {
this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1]
this.addCateForm.cat_level = this.selectedKeys.length
} else {
this.addCateForm.cat_pid = 0
this.addCateForm.cat_level = 0
}
},
async removeCateById(uid) {
const confirm = await this.$confirm('此操作将永久删除该分类, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(e => e);
// 如果用户确认删除,则返回值为字符串 confirm;如果用户取消了删除,则返回值为字符串 cancel
console.log('confirm: ', confirm);
if (confirm !== 'confirm') return
const { data: res } = await this.$http.delete('categories/' + uid)
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success('删除商品分类成功')
if (this.queryInfo.pagenum > 1 && this.catelist.length === 1) this.queryInfo.pagenum -= 1
this.getCateList()
}
}
}
</script>
1、请求分类数据,请求分类数据并将数据保存在data中
2、使用插件展示数据,使用第三方插件 vue-table-with-tree-grid 展示分类数据:
- 在vue 控制台中点击依赖->安装依赖->运行依赖->输入vue-table-with-tree-gird->点击安装
- 打开main.js,导入vue-table-with-tree-grid
import TreeTable from 'vue-table-with-tree-grid'
,并全局注册组件Vue.component('tree-table', TreeTable)
3、自定义数据列,使用vue-table-with-tree-grid定义模板列并添加自定义列
4、完成分页功能
5、完成添加分类,添加级联菜单显示父级分类。先导入Cascader组件,并注册;然后添加使用级联菜单组件
参数管理页
只允许给三级分类内容设置参数,参数分为动态参数和静态参数属性。
添加 goods/Params.vue 子组件,并在router.js中引入该组件并设置路由规则。
绘制参数管理基本结构
完成Params.vue组件的基本布局,其中警告提示信息使用了el-alert,在element.js引入该组件并注册:
<template>
<div>
<!-- 面包屑导航 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>商品管理</el-breadcrumb-item>
<el-breadcrumb-item>参数列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片 -->
<el-card>
<el-alert title="注意:只允许为第三级分类设置相关参数!" type="warning" show-icon :closable="false"></el-alert>
<!-- 商品分类 -->
<div class="cat_opt">
<span>请选择商品分类:</span>
<el-cascader v-model="selectedCateKeys" :options="cateList" :props="cateProps" @change="handleChange" clearable></el-cascader>
</div>
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<el-tab-pane label="动态参数" name="many">
<el-button type="primary" size="mini" :disabled="selectedCateKeys.length!==3" @click="addDialogVisible = true">添加参数</el-button>
<!-- 动态参数列表 -->
<el-table :data="manyTableData" style="width: 100%" border stripe>
<el-table-column type="expand">
<template slot-scope="scope">
<el-tag v-for="(v, i) in scope.row.attr_vals" :key="i" closable @close="handleClose(scope.row, i)">{{v}}</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 prop="attr_name" label="属性名称"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditDialog(scope.row.attr_id)">编辑</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeParams(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" :disabled="selectedCateKeys.length!==3" @click="addDialogVisible = true">添加属性</el-button>
<!-- 静态属性列表 -->
<el-table :data="onlyTableData" style="width: 100%" border stripe>
<el-table-column type="expand">
<template slot-scope="scope">
<el-tag v-for="(v, i) in scope.row.attr_vals" :key="i" closable @close="handleClose(scope.row, i)">{{v}}</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 prop="attr_name" label="属性名称"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditDialog(scope.row.attr_id)">编辑</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeParams(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="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>
<!-- 编辑参数对话框 -->
<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>
</div>
</template>
主要功能实现
<script>
export default {
data() {
return {
cateList: [],
selectedCateKeys: [],
cateProps: {
expandTrigger: 'hover',
value: 'cat_id',
label: 'cat_name',
children: 'children'
},
// 被激活的标签页名
activeName: 'many',
// 动态参数数据
manyTableData: [],
// 静态属性数据
onlyTableData: [],
// 是否展示添加参数对话框
addDialogVisible: false,
// 添加参数对话框数据
addForm: {
attr_name: ''
},
// 添加参数对话框验证规则
addFormRules: {
attr_name: [
{ required: true, message: '请输入参数名称', trigger: 'blur' }
]
},
// 是否展示编辑参数对话框
editDialogVisible: false,
// 编辑参数对话框数据
editForm: {
attr_name: ''
},
// 编辑参数对话框验证规则
editFormRules: {
attr_name: [
{ required: true, message: '请输入参数名称', trigger: 'blur' }
]
}
}
},
created() {
this.getCateList()
},
methods: {
// 获取分类数据
async getCateList() {
const { data: res } = await this.$http.get('categories', { params: { type: 3 } })
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success('获取商品分类成功')
this.cateList = res.data
},
// 获取参数列表数据
async getParamsData() {
const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`, { params: { sel: this.activeName } })
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success('获取参数列表成功')
// 将 attr_vals 转换为数组
res.data.forEach(item => {
item.attr_vals = item.attr_vals ? item.attr_vals.split(' ') : []
item.inputVisible = false
item.inputValue = ''
});
if (this.activeName === 'many') this.manyTableData = res.data
this.onlyTableData = res.data
},
// 分类改变
handleChange() {
if (this.selectedCateKeys.length !== 3) {
this.selectedCateKeys = []
this.manyTableData = []
this.onlyTableData = []
return
}
console.log('change: ', this.selectedCateKeys);
this.getParamsData()
},
// 点击标签页
handleTabClick() {
if (this.selectedCateKeys.length === 3) this.getParamsData()
},
// 关闭添加参数对话框
addDialogClosed() {
this.$refs.addFormRef.resetFields()
},
// 添加商品参数
addParams() {
// 验证表单
this.$refs.addFormRef.validate(async valid => {
if (!valid) return this.$msg.error('请填写正确的参数名称')
const { data: res } = await this.$http.post(`categories/${this.cateId}/attributes`, {
attr_name: this.addForm.attr_name,
attr_sel: this.activeName
})
if (res.meta.status !== 201) return this.$msg.error(res.meta.msg)
this.$msg.success(`添加${this.titleText}成功`)
this.addDialogVisible = false
this.getParamsData()
})
},
// 展示编辑参数对话框
async showEditDialog(attrId) {
const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes/${attrId}`, { params: { attr_sel: this.activeName } })
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success(`获取${this.titleText}成功`)
this.editForm = res.data
this.editDialogVisible = true
},
// 关闭编辑参数对话框
editDialogClosed() {
this.$refs.editFormRef.resetFields()
},
// 编辑商品参数
editParams() {
// 验证表单
this.$refs.editFormRef.validate(async valid => {
if (!valid) return this.$msg.error('请填写正确的参数名称')
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.$msg.error(res.meta.msg)
this.$msg.success(`添加${this.titleText}成功`)
this.editDialogVisible = false
this.getParamsData()
})
},
// 删除商品参数
async removeParams(attrId) {
const confirm = await this.$confirm(`此操作将永久删除该${this.titleText}, 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(e => e);
// 如果用户确认删除,则返回值为字符串 confirm;如果用户取消了删除,则返回值为字符串 cancel
console.log('confirm: ', confirm);
if (confirm !== 'confirm') return
const { data: res } = await this.$http.delete(`categories/${this.cateId}/attributes/${attrId}`)
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success(`删除${this.titleText}成功`)
this.getParamsData()
},
// 保存参数可选项
async saveAttrVals(row, attrVals, isAdd) {
const { data: res } = await this.$http.put(`categories/${this.cateId}/attributes/${row.attr_id}`, {
attr_name: row.attr_name,
attr_sel: this.activeName,
attr_vals: attrVals
})
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success(`${isAdd ? '添加' : '删除'}${this.titleText}可选项成功`)
this.getParamsData()
},
// 删除参数可选项
handleClose(row, i) {
// 删除元素
const attrVals = [...row.attr_vals.slice(0, i), ...row.attr_vals.slice(i + 1)].join(' ')
console.log('attrVals: ', attrVals, '\ni: ', i);
this.saveAttrVals(row, attrVals, false)
},
// 展示添加参数可选项输入框
showInput(row) {
row.inputVisible = true
// 让文本框自动获得焦点
// $nextTick 方法的作用,就是当页面上元素被重新渲染之后,才会指定回调函数中的代码
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus();
});
},
//
handleInputConfirm(row) {
if (row.inputValue.trim().length === 0) {
row.inputVisible = false
row.inputValue = ''
return
}
// 添加元素
const attrVals = row.attr_vals.concat(row.inputValue.trim()).join(' ')
console.log('attrVals: ', attrVals);
this.saveAttrVals(row, attrVals, true)
}
},
// 计算属性
computed: {
// 选中的分类id
cateId() {
if (this.selectedCateKeys.length === 3) return this.selectedCateKeys[2]
return null
},
// 动态计算标题的文本
titleText() {
if (this.activeName === 'many') {
return '动态参数'
}
return '静态属性'
}
}
}
</script>
1、完成级联选择框,完成商品分类级联选择框
2、展示参数,展示动态参数数据以及静态属性数据
3、添加参数,完成添加参数或属性
4、编辑参数,完成编辑参数或属性
5、删除参数,删除参数或属性
6、动态参数和静态属性管理:
- 展示动态参数可选项,动态参数可选项展示及操作在获取动态参数的方法中进行处理。
- 添加/删除动态参数可选项
- 展示静态属性可选项,静态属性可选项展示及操作在获取动态参数的方法中进行处理。
- 添加/删除静态属性可选项
注意:当用户在级联选择框中选中了非三级分类时,需要清空表格中数据
商品列表页
添加 goods/List.vue 子组件,并在router.js中引入该组件并设置路由规则
绘制商品列表页基本结构
略,详情参考如下代码:
<template>
<div>
<!-- 面包屑导航 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>商品管理</el-breadcrumb-item>
<el-breadcrumb-item>商品列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片 -->
<el-card>
<!-- 搜索与添加 -->
<el-row :gutter="20">
<el-col :span="8">
<el-input placeholder="请输入内容" v-model.trim="queryInfo.query" clearable @clear="getGoodsList">
<el-button slot="append" icon="el-icon-search" @click="queryGoodsList" :disabled="queryInfo.query ? false : true"></el-button>
</el-input>
</el-col>
<el-col :span="4">
<el-button type="primary" @click="goAddPage">添加商品</el-button>
</el-col>
</el-row>
<!-- 用户列表 -->
<el-table :data="goodsList" style="width: 100%" border stripe>
<el-table-column type="index" label="#"></el-table-column>
<el-table-column prop="goods_name" label="商品名称"></el-table-column>
<el-table-column prop="goods_price" label="商品价格(元)" width="95"></el-table-column>
<el-table-column prop="goods_weight" label="商品重量" width="70"></el-table-column>
<el-table-column prop="add_time" label="创建时间" width="140">
<template slot-scope="scope">
{{scope.row.add_time | dateFormatter}}
</template>
</el-table-column>
<el-table-column label="操作" width="130">
<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="removeById(scope.row.goods_id)"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pagenum"
:page-sizes="[2, 3, 5, 10]"
:page-size="queryInfo.pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="total" background>
</el-pagination>
<pre>{{goodsList}}</pre>
</el-card>
</div>
</template>
主要功能实现
<script>
export default {
data() {
return {
// 获取商品列表接口参数
queryInfo: {
query: '', // 搜索内容
pagenum: 1, // 页面
pagesize: 10 // 每页显示条数
},
// 商品列表数据
goodsList: [],
// 商品列表总条数
total: 0
}
},
created() {
this.getGoodsList()
},
methods: {
// 获取商品列表数据
async getGoodsList() {
const { data: res } = await this.$http.get('goods', { params: this.queryInfo })
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.goodsList = res.data.goods
this.total = res.data.total
this.$msg.success('获取商品列表成功')
},
// 查询商品
queryGoodsList() {
this.queryInfo.pagenum = 1
this.getGoodsList()
},
// pagesize 改变
handleSizeChange(size) {
console.log('size: ', size);
this.queryInfo.pagesize = size
const maxN = parseInt(this.total / this.queryInfo.pagesize + '') + (this.total % this.queryInfo.pagesize > 0 ? 1 : 0)
this.queryInfo.pagenum = this.queryInfo.pagenum > maxN ? maxN : this.queryInfo.pagenum
this.getGoodsList()
},
// 页码值 改变
handleCurrentChange(num) {
console.log('num: ', num);
this.queryInfo.pagenum = num
this.getGoodsList()
},
// 删除商品
async removeById(gid) {
const confirm = await this.$confirm('此操作将永久删除该商品, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(e => e);
// 如果用户确认删除,则返回值为字符串 confirm;如果用户取消了删除,则返回值为字符串 cancel
console.log('confirm: ', confirm);
if (confirm !== 'confirm') return
const { data: res } = this.$http.delete('goods/' + gid)
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success('删除商品成功')
this.getGoodsList()
},
// 添加商品
goAddPage() {
console.log('goAddPage');
this.$router.push('/home/addGoods')
}
}
}
</script>
1、数据展示,添加数据表格展示数据以及分页功能的实现,搜索功能的实现。在main.js中添加过滤器:
Vue.filter('dateFormatter', (ov) => {
const date = new Date(ov)
const y = date.getFullYear()
const m = (date.getMonth() + 1 + '').padStart(2, '0')
const d = (date.getDate() + '').padStart(2, '0')
const hh = (date.getHours() + '').padStart(2, '0')
const mm = (date.getHours() + '').padStart(2, '0')
const ss = (date.getHours() + '').padStart(2, '0')
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
})
2、实现删除商品
3、添加商品,添加编程式导航,在List.vue中添加编程式导航,并创建添加商品路由组件及规则
添加商品页
添加 goods/Add.vue 子组件,并在router.js中引入该组件并设置路由规则。
绘制添加商品页基本结构
- 布局过程中需要使用Steps组件,在element.js中引入并注册该组件,并在global.css中给组件设置全局样式
- 其他略……
<template>
<div>
<!-- 面包屑导航 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>商品管理</el-breadcrumb-item>
<el-breadcrumb-item>添加商品</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片 -->
<el-card>
<!-- 警告 -->
<el-alert
title="添加商品信息"
type="info"
center
show-icon
:closable="false">
</el-alert>
<!-- 步骤条 -->
<el-steps :space="200" :active="activeIndex * 1" align-center finish-status="success">
<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>
<!-- 标签栏 -->
<el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="70px" label-position="top">
<el-tabs v-model="activeIndex" tab-position="left" :before-leave="beforeTabLeave" @tab-click="tabClicked">
<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="goods_cat">
<el-cascader v-model="addForm.goods_cat" :options="cateList" :props="cateProps" @change="handleChange" clearable></el-cascader>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="商品参数" name="1">
<el-form-item v-for="(v, i) in manyTableData" :key="i" :label="v.attr_name">
<el-checkbox-group v-model="v.attr_vals">
<el-checkbox v-for="(item, index) in v.attr_vals" :key="index" :label="item" border></el-checkbox>
</el-checkbox-group>
</el-form-item>
<pre>{{manyTableData}}</pre>
</el-tab-pane>
<el-tab-pane label="商品属性" name="2">
<el-form-item v-for="(v, i) in onlyTableData" :key="i" :label="v.attr_name">
<el-input v-model="v.attr_vals"></el-input>
</el-form-item>
<pre>{{onlyTableData}}</pre>
</el-tab-pane>
<el-tab-pane label="商品图片" name="3">
<el-upload
action="http://127.0.0.1:8888/api/private/v1/upload"
:on-preview="handlePreview"
:on-remove="handleRemove"
:headers="headerObj"
:on-success="handleSuccess"
list-type="picture">
<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" @blur="onEditorBlur"></quill-editor>
<el-button class="btnAdd" type="primary" @click="add">添加商品</el-button>
</el-tab-pane>
</el-tabs>
</el-form>
<!-- 预览对话框 -->
<el-dialog
title="图片预览"
:visible.sync="previewVisible"
width="50%">
<img class="previewImg" :src="previewPath" alt="">
</el-dialog>
</el-card>
</div>
</template>
主要功能实现
<script>
import _ from 'lodash';
export default {
data() {
return {
activeIndex: '0',
addForm: {
goods_name: '',
goods_price: '',
goods_weight: '',
goods_number: '',
goods_cat: [],
// 商品图片
pics: [],
// 描述
goods_introduce: '',
// 参数
attrs: []
},
addFormRules: {
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: 'change' }]
},
// 商品分类
cateList: [],
cateProps: {
expandTrigger: 'hover',
value: 'cat_id',
label: 'cat_name',
children: 'children'
},
// 动态参数列表
manyTableData: [],
// 静态属性列表
onlyTableData: [],
// 请求头
headerObj: { Authorization: window.sessionStorage.getItem('token') },
// 是否展示预览图
previewVisible: false,
// 预览图片路径
previewPath: ''
}
},
created() {
this.getCateList()
},
methods: {
// 获取分类数据
async getCateList() {
const { data: res } = await this.$http.get('categories', { params: { type: 3 } })
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success('获取商品分类成功')
this.cateList = res.data
},
// 选择分类
handleChange(v) {
if (this.addForm.goods_cat.length !== 3) {
this.addForm.goods_cat = []
return
}
console.log('handleChange value: ', v);
},
// 标签页钩子函数
beforeTabLeave(ai, oai) {
console.log('ai: ', ai, ', oai: ', oai);
if (oai === '0' && this.addForm.goods_cat.length !== 3) {
this.$msg.error('请选择商品分类')
return false
}
},
// 点击标签栏
async tabClicked() {
if (this.activeIndex === '1') {
const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`, { params: { sel: 'many' } })
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success('获取动态参数成功')
res.data.forEach(item => {
item.attr_vals = item.attr_vals.length > 0 ? item.attr_vals.split(' ') : []
});
this.manyTableData = res.data
} else if (this.activeIndex === '2') {
const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`, { params: { sel: 'only' } })
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success('获取动态参数成功')
this.onlyTableData = res.data
}
},
// 点击预览图片
handlePreview(file) {
console.log('handlePreview: ', file);
this.previewPath = file.response.data.url
this.previewVisible = true
},
// 点击删除图片
handleRemove(file, fileList) {
// console.log('handleRemove:\nfile: ', file, '\nfileList: ', fileList);
const index = this.addForm.pics.findIndex(v => v.pic === file.response.data.tmp_path)
if (index !== -1) this.addForm.pics.splice(index, 1)
console.log('addForm: ', this.addForm);
},
// 上传文件成功
handleSuccess(response, file, fileList) {
// console.log('handleSuccess:\nresponse: ', response, '\nfile: ', file, '\nfileList: ', fileList);
this.addForm.pics.push({ pic: response.data.tmp_path })
console.log('addForm: ', this.addForm);
},
// 富文本编辑器失去焦点
onEditorBlur() {
console.log('content: ', this.addForm.goods_introduce);
},
// 添加商品
add() {
this.$refs.addFormRef.validate(async valid => {
if (!valid) return this.$msg.error('请填写必要的表单项')
// lodash 深拷贝
const addForm = _.cloneDeep(this.addForm)
// 处理分类数据
addForm.goods_cat = addForm.goods_cat.join(',')
// 处理动态参数
this.manyTableData.forEach(item => {
const newInfo = {
attr_id: item.attr_id,
attr_value: item.attr_vals.join(' ')
}
addForm.attrs.push(newInfo)
});
// 处理静态属性
this.onlyTableData.forEach(item => {
const newInfo = {
attr_id: item.attr_id,
attr_value: item.attr_vals
}
addForm.attrs.push(newInfo)
});
console.log('addForm: ', addForm);
// 发起请求
const { data: res } = await this.$http.post('goods', addForm)
if (res.meta.status !== 201) this.$msg.error(res.meta.msg)
this.$msg.success('添加商品成功')
this.$router.push('/home/goods')
})
}
},
computed: {
cateId() {
if (this.addForm.goods_cat.length === 3) return this.addForm.goods_cat[2]
return null
}
}
}
</script>
1、添加tab栏切换验证,也就是说不输入某些内容,无法切换到别的tab栏
2、展示信息,展示商品参数信息、商品属性信息,在商品参数信息展示中使用的el-checkbox,el-checkbox-group组件,打开element.js引入组件并注册组件
3、完成图片上传,使用upload组件完成图片上传,在element.js中引入upload组件,并注册。因为upload组件进行图片上传的时候并不是使用axios发送请求,所以需要手动为上传图片的请求添加token,即为upload组件添加headers属性
4、使用富文本插件,想要使用富文本插件vue-quill-editor,就必须先从依赖安装该插件,引入并注册vue-quill-editor
5、添加商品,完成添加商品的操作,在添加商品之前,为了避免goods_cat数组转换字符串之后导致级联选择器报错需要打开vue控制条,点击依赖,安装lodash,把addForm进行深拷贝
订单列表页
创建订单列表路由组件并添加路由规则。
绘制订单列表页基本结构
略,详情参考如下代码:
<template>
<div>
<!-- 面包屑导航 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>订单管理</el-breadcrumb-item>
<el-breadcrumb-item>订单列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片 -->
<el-card>
<!-- 搜索与添加 -->
<el-row :gutter="20">
<el-col :span="8">
<el-input placeholder="请输入内容" v-model.trim="queryInfo.query" clearable @clear="getOrderList">
<el-button slot="append" icon="el-icon-search" @click="queryOrderList" :disabled="queryInfo.query ? false : true"></el-button>
</el-input>
</el-col>
</el-row>
<!-- 用户列表 -->
<el-table :data="orderList" style="width: 100%" border stripe>
<el-table-column type="index" label="#"></el-table-column>
<el-table-column prop="order_number" label="订单编号"></el-table-column>
<el-table-column prop="order_price" label="订单价格"></el-table-column>
<el-table-column prop="order_pay" label="是否付款">
<template slot-scope="scope">
<el-tag type="danger" v-if="scope.row.pay_status === '0'">未付款</el-tag>
<el-tag type="success" v-else>已付款</el-tag>
</template>
</el-table-column>
<el-table-column prop="is_send" label="是否发货"></el-table-column>
<el-table-column prop="create_time" label="下单时间">
<template slot-scope="scope">
{{scope.row.create_time | dateFormatter}}
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="addressVisible = true"></el-button>
<el-button type="success" icon="el-icon-location" size="mini" @click="showProgressBox(scope.row.order_number)"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pagenum"
:page-sizes="[2, 3, 5, 10]"
:page-size="queryInfo.pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="total" background>
</el-pagination>
<pre>{{orderList}}</pre>
</el-card>
<!-- 修改地址对话框 -->
<el-dialog title="添加商品分类" :visible.sync="addressVisible" width="50%" @close="addressDialogClosed">
<el-form :model="addressForm" :rules="addressFormRules" ref="addressFormRef" label-width="120px">
<el-form-item label="省市区/县:" prop="address1">
<el-cascader v-model="addressForm.address1" :options="cityData" clearable></el-cascader>
</el-form-item>
<el-form-item label="详细地址:" prop="address2">
<el-input v-model="addressForm.address2"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addressVisible = false">取 消</el-button>
<el-button type="primary" @click="addressConfirm">确 定</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"
:color="index === 0 ? '#00ff00' : ''"
:timestamp="activity.time">
{{activity.context}}
</el-timeline-item>
</el-timeline>
</el-dialog>
</div>
</template>
主要功能实现
<script>
import cityData from './citydata.js'
export default {
data() {
return {
queryInfo: {
query: '',
pagenum: 1,
pagesize: 10
},
total: 0,
// 订单列表
orderList: [],
// 省市区/县数据
cityData,
// 级联选择器属性
cascaderProps: {
expandTrigger: 'hover'
},
// 修改地址对话框
addressVisible: false,
addressForm: {
address1: [],
address2: ''
},
addressFormRules: {
address1: [{ required: true, message: '请选择省市区县', trigger: 'change' }],
address2: [{ required: true, message: '请填写详细地址', trigger: 'blur' }]
},
// 物流进度对话框
progressVisible: false,
// 物流进度数据
progressInfo: []
}
},
created() {
this.getOrderList()
},
methods: {
// 获取订单列表
async getOrderList() {
const { data: res } = await this.$http.get('orders', { params: this.queryInfo })
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success('获取订单列表成功')
this.orderList = res.data.goods
this.total = res.data.total
},
// 查询订单列表
queryOrderList() {
this.queryInfo.pagenum = 1
this.getOrderList()
},
// pagesize 改变
handleSizeChange(size) {
console.log('size: ', size);
this.queryInfo.pagesize = size
const maxN = parseInt(this.total / this.queryInfo.pagesize + '') + (this.total % this.queryInfo.pagesize > 0 ? 1 : 0)
this.queryInfo.pagenum = this.queryInfo.pagenum > maxN ? maxN : this.queryInfo.pagenum
this.getOrderList()
},
// 页码值 改变
handleCurrentChange(num) {
console.log('num: ', num);
this.queryInfo.pagenum = num
this.getOrderList()
},
// 关闭修改地址对话框
addressDialogClosed() {
// console.log('addressForm: ', this.addressForm);
this.$refs.addressFormRef.resetFields()
},
// 修改地址
addressConfirm() {
// console.log('addressForm: ', this.addressForm);
this.addressVisible = false
},
// 展示物流进度对话框
async showProgressBox(orderNumber) {
// const { data: res } = await this.$http.get('kuaidi/' + orderNumber)
// if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
// this.$msg.success('获取物流进度成功')
// this.progressInfo = res.data
const data = `[
{
"time": "2018-05-10 09:39:00",
"ftime": "2018-05-10 09:39:00",
"context": "已签收,感谢使用顺丰,期待再次为您服务",
"location": ""
},
{
"time": "2018-05-10 08:23:00",
"ftime": "2018-05-10 08:23:00",
"context": "[北京市]北京海淀育新小区营业点派件员 顺丰速运 95338正在为您派件",
"location": ""
},
{
"time": "2018-05-10 07:32:00",
"ftime": "2018-05-10 07:32:00",
"context": "快件到达 [北京海淀育新小区营业点]",
"location": ""
},
{
"time": "2018-05-10 02:03:00",
"ftime": "2018-05-10 02:03:00",
"context": "快件在[北京顺义集散中心]已装车,准备发往 [北京海淀育新小区营业点]",
"location": ""
},
{
"time": "2018-05-09 23:05:00",
"ftime": "2018-05-09 23:05:00",
"context": "快件到达 [北京顺义集散中心]",
"location": ""
},
{
"time": "2018-05-09 21:21:00",
"ftime": "2018-05-09 21:21:00",
"context": "快件在[北京宝胜营业点]已装车,准备发往 [北京顺义集散中心]",
"location": ""
},
{
"time": "2018-05-09 13:07:00",
"ftime": "2018-05-09 13:07:00",
"context": "顺丰速运 已收取快件",
"location": ""
},
{
"time": "2018-05-09 12:25:03",
"ftime": "2018-05-09 12:25:03",
"context": "卖家发货",
"location": ""
},
{
"time": "2018-05-09 12:22:24",
"ftime": "2018-05-09 12:22:24",
"context": "您的订单将由HLA(北京海淀区清河中街店)门店安排发货。",
"location": ""
},
{
"time": "2018-05-08 21:36:04",
"ftime": "2018-05-08 21:36:04",
"context": "商品已经下单",
"location": ""
}
]`
this.progressInfo = JSON.parse(data)
this.progressVisible = true
}
}
}
</script>
1、实现数据展示及分页
2、制作省市区/县联动,制作 citydata.js 数据文件放到到components/order文件夹中,然后导入citydata.js文件
3、制作物流进度对话框,因为使用的是element-ui中提供的Timeline组件,所以需要导入并注册组件
数据统计页
创建数据统计路由组件并添加路由规则。
导入ECharts并使用,具体实现如下:
<template>
<div>
<!-- 面包屑导航 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>数据统计</el-breadcrumb-item>
<el-breadcrumb-item>数据报表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片 -->
<el-card>
<div id="main" style="width: 750px; height:400px;"></div>
</el-card>
</div>
</template>
<script>
// 只会导出(export default mutations)这个默认的对象返回
// import echarts from 'echarts'
// 将 若干export导出的内容组合成一个对象返回
import * as echarts from 'echarts'
import _ from 'lodash'
export default {
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'
}
]
}
}
},
async mounted() {
const { data: res } = await this.$http.get('reports/type/1')
if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
this.$msg.success('获取折线图数据成功')
// 初始化
var myChart = echarts.init(document.getElementById('main'))
// 设置配置项
myChart.setOption(_.merge(this.options, res.data))
}
}
</script>