上几个章节,猿人君教会了你实现了属性/属性值和后台类目的绑定关系,今天,猿人君就带你来实现前台类目。
为什么会有前台类目
为了方便运营,在电商系统中,类目分为前台类目和后台类目。如果你经常网购,你会发现一个比较有意思的事情。鼠标移动到一级类目,会展示二级类目,点击一级二级类目,会跳转到对应的频道页面(不过做得大了都是分站,我们先考虑业务),点击三级类目会出发搜索的功能。
这些在之前的设计文章猿设计5——真电商之颠覆你的类目认知中已经讲述过了,这里就不再赘述了。
功能概览
前台类目,从某种程度上说,肩负了部分站点的导航以及内容组织的职责,要完成这一职责,我们可以先来看看需要实现的功能。
在前台类目模块中,系统体现了前台类目的层级关系,每一级类目都有对应的列表、新增/编辑功能。在一级类目中可以维护广告牌、在三级类目中,重点维护前后台类目的关系(以上本章节暂不讨论)。
数据库设计
根据之前的前台类目设计文章,我们很清楚的获知了前台类目的一些特征,我们根据之前的设计,将这些设计落地为数据库的物理设计,大家可以看一下(本章节仅讲述前台类目的实现)。
就类目而言,类目就像是一棵树,每一级类目都有可能有下一级类目,像这种特性,在数据库的设计上,是一种典型的自关联结构。但是类目用于检索时,往往伴随着层级关系的查找,比如提取某一级类目的信息。所以,我们在设计时,给予了一个level字段,用于方便提取固定层级的信息。
由于每一级维护的信息可能不同,所以在数据库设计上,我们做了一个整合,每一条行记录的信息是一二三级所有信息的集合,事实上,不同的层级只涉及部分字段内容,这种设计,也是一种常见的设计。
前台类目整体前端
类目的层级维护,从功能上讲,依然是一种整体和部分的关系,出于业务的考虑,在规模不是特别庞大的情况下,三级已经足够支撑你的运营。我们可以参考下之前的实现思路——将一级类目、二级类目、三级类目分别定义成小的组件。最后,由一个view来组织和整合它们就好了。
<template>
<div id="fnCategoryDiv">
<div v-if="oneCategoryShow">
<frontDeskCategoryOneSearch ref="frontDeskCategoryOneSearch" @lookSubordinate="lookOneSubordinate" />
</div>
<div v-if="twoCategoryShow">
<frontDeskCategoryTwoSearch ref="frontDeskCategoryTwoSearch" :pid="parentId" :pname="parentName" @lookSubordinate="lookTwoSubordinate" @returnBack="returnTwoBack" />
</div>
<div v-if="threeCategoryShow">
<frontDeskCategoryThreeSearch ref="frontDeskCategoryThreeSearch" :pid="parentId" :pname="parentName" @returnBack="returnThreeBack" />
</div>
</div>
</template>
<script>
import frontDeskCategoryOneSearch from '@/components/productManage/frontDeskCategoryOneSearch'
import frontDeskCategoryTwoSearch from '@/components/productManage/frontDeskCategoryTwoSearch'
import frontDeskCategoryThreeSearch from '@/components/productManage/frontDeskCategoryThreeSearch'
export default {
components: {
frontDeskCategoryOneSearch,
frontDeskCategoryTwoSearch,
frontDeskCategoryThreeSearch
},
data() {
return {
// 一级类目
oneCategoryShow: false,
// 二级类目
twoCategoryShow: false,
// 三级类目
threeCategoryShow: false,
parentId: 0,
parentName: '',
catOneId: 0
}
},
created() {
// 显示一级类目
this.oneCategoryShow = true
},
methods: {
// 二级回退
returnTwoBack() {
// 一级二级三级类目显示设置
this.oneCategoryShow = true
this.twoCategoryShow = false
this.threeCategoryShow = false
},
// 三级回退
returnThreeBack(pid, pname) {
// 一级二级三级类目显示设置
this.oneCategoryShow = false
this.twoCategoryShow = true
this.threeCategoryShow = false
this.parentId = this.catOneId
this.parentName = pname
console.log(this.parentId)
},
// 一级查看下级类目
lookOneSubordinate(row) {
// 一级二级三级类目显示设置
this.oneCategoryShow = false
this.twoCategoryShow = true
this.threeCategoryShow = false
this.parentId = row.fnCategoryId
this.parentName = row.fnCategoryName
this.catOneId = row.fnCategoryId
},
// 二级查看下级类目
lookTwoSubordinate(row) {
// 一级二级三级类目显示设置
this.oneCategoryShow = false
this.twoCategoryShow = false
this.threeCategoryShow = true
this.parentId = row.fnCategoryId
this.parentName = row.fnCategoryName
}
}
}
</script>
<style scoped>
</style>
值得注意的是,在查看下级和返回上级种,涉及组件之间的参数传递。您需要将,本级类目的ID作为父ID传入下级,而在下级返回上级的操作种,您需要将上上级的id作为父ID传入上一级列表页面(二级除外)。
关于父子组件之间的通信问题,在之前的文章中已经讲述过很多了,这里就不再赘述了。
前台类目后端实现
其实就前台类目的普通功能而言,目前来说相对简单,最主要的是建立父子关联这样一个自关联的概念。
由于之前已经给出了我们自己定义的代码生成器,属性组的实现也相对简单,考虑到篇幅问题,这一部分我们给出Controller层面的功能实现,service、和dao,还是希望你自行实现,在初学时期,多谢代码,对你熟悉掌握代码编写的技巧,是一个必不可少的环节。
/**
* Copyright(c) 2004-2020 pangzi
* com.pz.basic.mall.controller.product.fncategory.MallFnCategoryController.java
*/
package com.pz.basic.mall.controller.product.fncategory;
import com.pz.basic.mall.domain.base.Result;
import com.pz.basic.mall.domain.product.category.query.QueryMallCategory;
import com.pz.basic.mall.domain.product.category.vo.MallCategoryVo;
import com.pz.basic.mall.domain.product.fncategory.MallFnBgCategoryRel;
import com.pz.basic.mall.domain.product.fncategory.MallFnCategory;
import com.pz.basic.mall.domain.product.fncategory.query.QueryMallFnBgCategoryRel;
import com.pz.basic.mall.domain.product.fncategory.query.QueryMallFnCategory;
import com.pz.basic.mall.service.product.category.MallCategoryService;
import com.pz.basic.mall.service.product.fncategory.MallFnBgCategoryRelService;
import com.pz.basic.mall.service.product.fncategory.MallFnCategoryService;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
*
* @author pangzi
* @date 2020-06-22 20:47:27
*
*
*/
@RestController
@RequestMapping("/fncategory")
public class MallFnCategoryController {
private MallFnCategoryService mallFnCategoryService;
public void setMallFnCategoryService(MallFnCategoryService mallFnCategoryService) {
this.mallFnCategoryService = mallFnCategoryService;
}
/**
* 新增类目
* @param mallFnCategory
* @return
*/
@RequestMapping("/addMallFnCategory")
public Result<MallFnCategory> addMallFnCategory(@RequestBody MallFnCategory mallFnCategory){
try{
return mallFnCategoryService.addMallFnCategory(mallFnCategory);
}catch(Exception e){
e.printStackTrace();
return new Result(false);
}
}
/**
* 根据ID查找类目
* @param id
* @return
*/
@RequestMapping("/findMallFnCategoryById")
public Result<MallFnCategory> findMallFnCategoryById(Long id){
return mallFnCategoryService.getMallFnCategoryById(id.intValue());
}
/**
* 修改类目
* @param mallFnCategory
* @return
*/
@RequestMapping("/updateMallFnCategory")
public Result updateMallFnCategory(@RequestBody MallFnCategory mallFnCategory){
try{
return mallFnCategoryService.updateMallFnCategoryById(mallFnCategory);
}catch(Exception e){
e.printStackTrace();
return new Result(false);
}
}
/**
* 分页返回类目列表
* @param queryMallFnCategory
* @return
*/
@RequestMapping("/findByPage")
public Result<List<MallFnCategory>> findByPage(@RequestBody QueryMallFnCategory queryMallFnCategory){
return mallFnCategoryService.getMallFnCategorysByPage(queryMallFnCategory);
}
}
后台类目前端实现
聊完了后端数据接口的事情,我们一起来看看前端实现的问题,考虑到大部分朋友前端并不是很熟悉,我们再讲讲后台类目前端API组件的封装。
我们先封装访问后端的数据接口:
// 前台类目
export function fetchFnCategoryList(query) {
return request({
url: '/fncategory/findByPage',
method: 'post',
data: query
})
}
export function createMallFnCategory(data) {
return request({
url: '/fncategory/addMallFnCategory',
method: 'post',
data
})
}
export function updateMallFnCategory(data) {
return request({
url: '/fncategory/updateMallFnCategory',
method: 'post',
data
})
}
考虑到之前有朋友说页面编写起来实在困难,在这里, 为了让你更好的掌握前台类目的内容,这次将一二三级的前台页面都给到你。不过猿人君还是希望你尽量自己编码。不然,你真的很难掌握开发技巧。
一级前台类目
<template>
<div id="frontDeskCategoryOneSearchDiv">
<div>
<el-form ref="listQuery" :model="listQuery" :inline="true">
<el-form-item label="类目名称:" prop="fnCategoryNameLike">
<el-input v-model="listQuery.fnCategoryNameLike" placeholder="请输入类目名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button>
<el-button type="primary" icon="el-icon-edit" style="float:right;margin-bottom:20px;" @click="addDate()">新增一级</el-button>
</el-form-item>
</el-form>
</div>
<div>
<el-table
ref="table"
v-loading="listLoading"
:data="list"
style="width: 100%"
border
>
<el-table-column label="类目ID">
<template slot-scope="scope">{{ scope.row.fnCategoryId }}</template>
</el-table-column>
<el-table-column label="类目名称">
<template slot-scope="scope">{{ scope.row.fnCategoryName }}</template>
</el-table-column>
<el-table-column label="父类目ID">
无
</el-table-column>
<el-table-column label="类目级别">
一级类目
</el-table-column>
<el-table-column label="排序">
<template slot-scope="scope">{{ scope.row.sortOrder }}</template>
</el-table-column>status
<el-table-column label="是否上架">
<template slot-scope="scope">{{ scope.row.status ==1?"上架":"下架" }}</template>
</el-table-column>
<el-table-column label="操作" width="260">
<template slot-scope="scope">
<el-button
type="primary"
size="mini"
@click="handleSubordinate(scope.row)"
>查看下级
</el-button>
<el-button
type="primary"
size="mini"
@click="handleUpdate(scope.row)"
>修改
</el-button>
<el-button
type="primary"
size="mini"
@click="handleAdvertisingBoard(scope.row)"
>广告牌
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.pageSize" @pagination="getList" />
<!-- 新增/编辑弹框 -->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="120px" style="width: 320px; margin-left:50px;">
<el-form-item label="类目名称:" prop="fnCategoryName">
<el-input v-model="temp.fnCategoryName" placeholder="请输入类目中文名" />
</el-form-item>
<el-form-item label="类目排序:" prop="sortOrder">
<el-input-number
v-model="temp.sortOrder"
:min="0"
:max="100"
placeholder="请输入类目排序"
/>
</el-form-item>
<el-form-item label="是否上柜:" prop="status">
<el-select v-model="temp.status" placeholder="请选择">
<el-option
v-for="(item,index) in valueList"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="类目ICON:">
<div style="width:300%">
<el-upload
action="http://127.0.0.1:9201//upload/uploadFile?moudle=fncategory"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:on-success="handleSuccess"
:file-list="fnCategoryFileList"
>
<i />
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
</div>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确定
</el-button>
</div>
</el-dialog>
<!-- 广告牌 -->
<el-dialog title="广告牌" :visible.sync="dialogAdvertisingBoardVisible">
<el-form ref="dataFormAdvertisingBoard" :rules="billboardRules" :model="tempBillboard" label-position="right" label-width="120px" style="width: 320px; margin-left:50px;">
<el-form-item label="广告图片:" prop="picture">
<div style="width:300%">
<el-upload
action="http://127.0.0.1:9201//upload/uploadFile?moudle=fncategory/billboard"
list-type="picture-card"
:on-preview="handleBillboardPreview"
:on-remove="handleRemove"
:on-success="handleBillboardSuccess"
:file-list="fnCategoryBillboardFileList"
>
<i />
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="billboardImageUrl" alt="">
</el-dialog>
</div>
</el-form-item>
<el-form-item label="链接类型:" prop="type">
<el-radio v-model="tempBillboard.type" :label="1">单链接</el-radio>
<el-radio v-model="tempBillboard.type" :label="2">多链接</el-radio>
</el-form-item>
<el-form-item label="跳转内容" prop="redirectArea">
<el-input
v-model="tempBillboard.redirectArea"
style="width: 200%;"
type="textarea"
:rows="3"
placeholder="请输入内容"
/>
</el-form-item>
<el-form-item label="广告牌状态:" prop="status">
<el-radio v-model="tempBillboard.status" :label="1">停用</el-radio>
<el-radio v-model="tempBillboard.status" :label="2">启用</el-radio>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialogAdvertisingBoardVisible = false">
取消
</el-button>
<el-button type="primary" @click="advertisingBoardVisibleClick">
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import { fetchFnCategoryList, createMallFnCategory, updateMallFnCategory, fetchFnCategoryBillboardList, createMallFnCategoryBillboard, updateMallFnCategoryBillboard } from '@/api/product-manage'
export default {
components: { Pagination },
data() {
return {
tempBillboard: {
id: undefined,
fnCategoryId: undefined,
// 链接类型
type: 1,
// map_area
redirectArea: '',
// 广告牌状态
status: 1,
imageUrl: ''
},
addBillboard: true,
billboardImageUrl: '',
//
dialogImageUrl: '',
dialogVisible: false,
//
dialogStatus: '',
// 弹框是否显示
dialogFormVisible: false,
// 广告牌弹框是否显示
dialogAdvertisingBoardVisible: false,
// 广告牌弹框校验规则
billboardRules: {
picture: [{ required: true, message: '请上传图片', trigger: 'blur' }],
redirectArea: [{ required: true, message: '请输入跳转内容', trigger: 'blur' }],
status: [{ required: true, message: '请选择是否上架', trigger: 'blur' }],
type: [{ required: true, message: '请选择连接类型', trigger: 'blur' }]
},
// 弹框校验规则
rules: {
fnCategoryName: [{ required: true, message: '请输入类目名称', trigger: 'change' }],
status: [{ required: true, message: '请选择是否上架', trigger: 'change' }],
sortOrder: [{ required: true, message: '请输入排序', trigger: 'blur' }]
},
temp: {
fnCategoryId: undefined,
// 前台类目名:
fnCategoryName: '',
// 是否上架
status: 1,
// 类目排序
sortOrder: 0,
conditions: 'N',
level: 1,
special: 0,
fnCategoryImage: ''
},
fnCategoryFileList: [],
fnCategoryBillboardFileList: [],
// 状态
valueList: [{
value: 1,
label: '是'
}, {
value: 0,
label: '否'
}],
textMap: {
update: '一级类目修改',
create: '一级类目新增'
},
// table集合
list: null,
multipleSelection: [],
// 分页
total: 0,
// loading
listLoading: true,
// 属性集合
categorypointToList: [
{
value: '无',
label: '无'
}
],
listQuery: {
level: 1,
page: 1,
pageSize: 10
},
listBillboardQuery: {
page: 1,
pageSize: 1
}
}
},
created() {
// 列表查询
this.getList()
},
methods: {
// 广告牌弹框保存
advertisingBoardVisibleClick(row) {
if (this.addBillboard) {
createMallFnCategoryBillboard(this.tempBillboard).then(res => {
this.tempBillboard.id = res.model.id
this.dialogAdvertisingBoardVisible = false
this.$notify({
title: 'Success',
message: 'Save Successfully',
type: 'success',
duration: 2000
})
})
} else {
updateMallFnCategoryBillboard(this.tempBillboard).then(res => {
this.dialogAdvertisingBoardVisible = false
this.$notify({
title: 'Success',
message: 'Save Successfully',
type: 'success',
duration: 2000
})
})
}
},
// 广告牌
handleAdvertisingBoard(row) {
this.dialogAdvertisingBoardVisible = true
this.tempBillboard.fnCategoryId = row.fnCategoryId
this.listBillboardQuery.fnCategoryId = row.fnCategoryId
fetchFnCategoryBillboardList(this.listBillboardQuery).then(response => {
this.fnCategoryBillboardFileList = []
if (response.model !== null && response.model.length > 0) {
this.tempBillboard = response.model[0]
this.addBillboard = false
if (this.tempBillboard.imageUrl !== null && this.tempBillboard.imageUrl !== '') {
const obj = new Object()
obj.url = this.tempBillboard.imageUrl
this.fnCategoryBillboardFileList.push(obj)
}
}
// Just to simulate the time of the request
setTimeout(() => {
this.listLoading = false
}, 1.5 * 1000)
})
},
// 查看下级
handleSubordinate(row) {
this.$emit('lookSubordinate', row)
},
// 编辑
handleUpdate(row) {
this.fnCategoryFileList = []
console.log(row)
this.temp = Object.assign({}, row) // copy obj
if (undefined !== row.fnCategoryImage && row.fnCategoryImage !== null && row.fnCategoryImage !== '') {
const obj = new Object()
obj.url = row.fnCategoryImage
console.log(obj)
this.fnCategoryFileList.push(obj)
}
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
// 重置
resetTemp() {
this.temp = {
id: undefined,
// 类目中文名:
fnCategoryName: '',
// 是否上柜
status: 1,
// 类目排序
sortOrder: 0,
conditions: 'N',
level: 1,
special: 0,
fnCategoryImage: ''
}
this.fnCategoryFileList = []
},
resetTempBillboard() {
this.tempBillboard = {
id: undefined,
fnCategoryId: undefined,
// 链接类型
type: 1,
// map_area
redirectArea: '',
// 广告牌状态
status: 1,
imageUrl: ''
}
this.fnCategoryBillboardFileList = []
},
// 新增一级类目
addDate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
// 更新保存方法
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
updateMallFnCategory(tempData).then(() => {
const index = this.list.findIndex(v => v.id === this.temp.id)
this.list.splice(index, 1, this.temp)
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
// 创建保存方法
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createMallFnCategory(this.temp).then(() => {
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
fetchData() {
this.getList()
},
// 列表查询
getList() {
this.listLoading = true
fetchFnCategoryList(this.listQuery).then(response => {
this.list = response.model
this.total = response.totalItem
// Just to simulate the time of the request
setTimeout(() => {
this.listLoading = false
}, 1.5 * 1000)
})
},
handleRemove(file, fileList) {
console.log(file, fileList)
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url
},
handleSuccess(res, file, fileList) {
this.temp.fnCategoryImage = res.model
console.log(this.temp.fnCategoryImage)
},
handleBillboardPreview(file) {
this.billboardImageUrl = file.url
},
handleBillboardSuccess(res, file, fileList) {
this.tempBillboard.imageUrl = res.model
console.log(this.tempBillboard.imageUrl)
}
}
}
</script>
<style scoped>
</style>
二级前台类目
<template>
<div id="backgroundCategoryTwoSearchDiv">
<div style="float:right">
<span @click="returnBack">< <span style="font-size:15px;margin-top:50px;line-height: 30px;">返回上一级</span></span>
<el-button type="primary" icon="el-icon-edit" style="margin-bottom:20px;float: left;margin-right: 40px;" @click="addDate()">新增二级</el-button>
</div>
<div>
<el-table
ref="table"
v-loading="listLoading"
:data="list"
style="width: 100%"
border
>
<el-table-column label="类目ID">
<template slot-scope="scope">{{ scope.row.fnCategoryId }}</template>
</el-table-column>
<el-table-column label="类目名称">
<template slot-scope="scope">{{ scope.row.fnCategoryName }}</template>
</el-table-column>
<el-table-column label="父类目ID">
<template slot-scope="scope">{{ scope.row.parentId }}</template>
</el-table-column>
<el-table-column label="类目级别">
二级类目
</el-table-column>
<el-table-column label="排序">
<template slot-scope="scope">{{ scope.row.sortOrder }}</template>
</el-table-column>status
<el-table-column label="是否上架">
<template slot-scope="scope">{{ scope.row.status ==1?"上架":"下架" }}</template>
</el-table-column>
<el-table-column label="类目指向">
<template slot-scope="scope">{{ scope.row.conditions }}</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button
type="primary"
size="mini"
@click="handleSubordinate(scope.row)"
>查看下级
</el-button>
<el-button
type="primary"
size="mini"
@click="handleUpdate(scope.row)"
>修改
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
<!-- 新增/编辑弹框 -->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="120px" style="width: 320px; margin-left:50px;">
<el-form-item label="类目名称:" prop="fnCategoryName">
<el-input v-model="temp.fnCategoryName" placeholder="请输入类目中文名" />
</el-form-item>
<el-form-item label="类目排序:" prop="sortOrder">
<el-input-number
v-model="temp.sortOrder"
:min="0"
:max="100"
placeholder="请输入类目排序"
/>
</el-form-item>
<el-form-item label="是否上柜:" prop="status">
<el-select v-model="temp.status" placeholder="请选择">
<el-option
v-for="(item,index) in valueList"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="类目指向:" prop="conditions">
<el-input v-model="temp.conditions" placeholder="请输入类目指向" />
</el-form-item>
<el-form-item label="类目ICON:">
<div style="width:300%">
<el-upload
action="http://127.0.0.1:9201//upload/uploadFile?moudle=fncategory"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:file-list="fnCategoryFileList"
>
<i />
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
</div>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import { fetchFnCategoryList, createMallFnCategory, updateMallFnCategory } from '@/api/product-manage'
export default {
components: { Pagination },
props: {
pid: Number,
pname: String
},
data() {
return {
// 二级类目指向弹框是否显示
dialogFormPointToVisible: false,
dialogStatus: '',
// 弹框是否显示
dialogFormVisible: false,
tempPointTo: {
channelPage: ''
},
rulesPointTo: {
channelPage: [{ required: true, message: 'channelPage is required', trigger: 'blur' }]
},
// 弹框校验规则
rules: {
fnCategoryName: [{ required: true, message: '请输入类目名称', trigger: 'change' }],
status: [{ required: true, message: '请选择是否上架', trigger: 'change' }],
sortOrder: [{ required: true, message: '请输入排序', trigger: 'blur' }]
},
temp: {
fnCategoryId: undefined,
// 前台类目名:
fnCategoryName: '',
// 是否上架
status: 1,
// 类目排序
sortOrder: 0,
conditions: 'N',
level: 1,
special: 0,
fnCategoryImage: '',
parentId: this.pid
}, // 状态
valueList: [{
value: 1,
label: '是'
}, {
value: 0,
label: '否'
}],
textMap: {
update: '二级类目修改',
create: '二级类目新增'
},
// table集合
list: null,
multipleSelection: [],
// 分页
total: 0,
// loading
listLoading: true,
//
dialogImageUrl: '',
dialogVisible: false,
//
listQuery: {
level: 2,
page: 1,
pageSize: 10,
parentId: this.pid
},
fnCategoryFileList: []
}
},
created() {
// 列表查询
this.getList()
},
methods: {
/**
* 回退
*/
returnBack() {
this.$emit('returnBack')
},
// 管理
handleManagement(row) {
this.tempPointTo.channelPage = ''
this.dialogFormPointToVisible = true
},
// 查看下级
handleSubordinate(row) {
this.$emit('lookSubordinate', row)
},
// 编辑
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.fnCategoryFileList = []
if (row.imageUrl !== null && row.imageUrl !== '') {
const obj = {}
obj.url = row.imageUrl
this.fnCategoryFileList.push(obj)
}
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
// 重置
resetTemp() {
this.temp = {
id: undefined,
// 类目中文名:
fnCategoryName: '',
// 是否上柜
status: 1,
// 类目排序
sortOrder: 0,
conditions: 'N',
level: 1,
special: 0,
fnCategoryImage: '',
parentId: this.pid
}
this.fnCategoryFileList = []
},
// 新增一级类目
addDate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
// 更新保存方法
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
updateMallFnCategory(tempData).then(() => {
const index = this.list.findIndex(v => v.id === this.temp.id)
this.list.splice(index, 1, this.temp)
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
// 创建保存方法
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createMallFnCategory(this.temp).then(() => {
this.list.unshift(this.temp)
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
// 列表查询
getList() {
this.listLoading = true
fetchFnCategoryList(this.listQuery).then(response => {
this.list = response.model
this.total = response.totalItem
// Just to simulate the time of the request
setTimeout(() => {
this.listLoading = false
}, 1.5 * 1000)
})
},
handleRemove(file, fileList) {
console.log(file, fileList)
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url
this.dialogVisible = true
}
}
}
</script>
<style scoped>
/* .mouseMark:hover {
cursor: pointer;
} */
</style>
三级前台类目
<template>
<div id="backgroundCategoryTwoSearchDiv">
<div style="float:right">
<span @click="returnBack(pid,pname)">< <span style="font-size:15px;margin-top:50px;line-height: 30px;">返回上一级</span></span>
<el-button type="primary" icon="el-icon-edit" style="margin-bottom:20px;float: left;margin-right: 40px;" @click="addDate()">新增三级</el-button>
</div>
<div>
<el-table
ref="table"
v-loading="listLoading"
:data="list"
style="width: 100%"
border
>
<el-table-column label="类目ID">
<template slot-scope="scope">{{ scope.row.fnCategoryId }}</template>
</el-table-column>
<el-table-column label="类目名称">
<template slot-scope="scope">{{ scope.row.fnCategoryName }}</template>
</el-table-column>
<el-table-column label="父类目ID">
<template slot-scope="scope">{{ scope.row.parentId }}</template>
</el-table-column>
<el-table-column label="类目级别">
三级类目
</el-table-column>
<el-table-column label="排序">
<template slot-scope="scope">{{ scope.row.sortOrder }}</template>
</el-table-column>status
<el-table-column label="是否上架">
<template slot-scope="scope">{{ scope.row.status ==1?"上架":"下架" }}</template>
</el-table-column>
<el-table-column label="类目指向">
<template slot-scope="scope">
<a style="color:#4395ff" @click="handleManagement(scope.row)">管理</a>
<!-- </div> -->
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
type="primary"
size="mini"
@click="handleUpdate(scope.row)"
>修改
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.pageSize" @pagination="getList" />
<!-- 新增/编辑弹框 -->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="120px" style="width: 320px; margin-left:50px;">
<el-form-item label="类目名称:" prop="fnCategoryName">
<el-input v-model="temp.fnCategoryName" placeholder="请输入类目中文名" />
</el-form-item>
<el-form-item label="类目排序:" prop="sortOrder">
<el-input-number
v-model="temp.sortOrder"
:min="0"
:max="100"
placeholder="请输入类目排序"
/>
</el-form-item>
<el-form-item label="是否上柜:" prop="status">
<el-select v-model="temp.status" placeholder="请选择">
<el-option
v-for="(item,index) in valueList"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="类目指向:" prop="conditions">
<el-input v-model="temp.conditions" placeholder="请输入类目指向" />
</el-form-item>
<el-form-item label="类目ICON:">
<div style="width:300%">
<el-upload
action="http://127.0.0.1:9201//upload/uploadFile?moudle=fncategory"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:file-list="fnCategoryFileList"
>
<i />
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
</div>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确定
</el-button>
</div>
</el-dialog>
<!-- 三级类目指向弹框 -->
<el-dialog v-if="dialogFormSpecialVisible" title="三级类目指向" :visible.sync="dialogFormSpecialVisible">
<el-row>
<el-col :span="6">
<div style="margin-bottom:10px;font-weight: 600;">第一步:选择后台类目</div>
<el-card shadow="never">
<div>
<el-tree
ref="tree"
:data="data"
show-checkbox
node-key="categoryId"
:default-expanded-keys="[1,2]"
:props="treeProps"
clearable
filterable
allow-create
@check-change="updateKeyChildren"
/>
</div>
</el-card>
</el-col>
<el-col :span="1"> </el-col>
<el-col :span="17">
<el-tooltip placement="top-start" effect="light">
<div slot="content">当某一后台三级类目与前台类目<br>进行初次绑定时,系统会强制将<br>对应前台类目设置成主类目</div>
<div style="margin-bottom:10px;font-weight: 600;">第二步:设置当前类目为前台主类目 <i /></div>
</el-tooltip>
<el-card shadow="never">
<div>
<div>
<el-table
ref="table"
:data="tableList"
style="width: 100%;"
border
>
<el-table-column label="已选后台类目">
<template slot-scope="scope">
{{ scope.row.categoryName }}
</template>
</el-table-column>
<el-table-column label="已选前台主类目">
<template slot-scope="scope">
{{ scope.row.fnCategoryName }}
</template>
</el-table-column>
<el-table-column label="是否设置当前类目为前台主类目" width="140">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.checked">是</el-checkbox>
</template>
</el-table-column>
<el-table-column label="操作" width="300">
<template slot-scope="scope">
<el-button
width="100"
type="danger"
@click="handleDelete(scope.$index, scope.row)"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-card>
</el-col>
<div style="text-align: center;margin-top:20px;">
<el-button @click="dialogFormSpecialVisible = false">
取消
</el-button>
<el-button type="primary" @click="specialClick">
确定
</el-button>
</div>
</el-row>
</el-dialog>
</div>
</template>
<script>
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import { fetchFnCategoryList, createMallFnCategory, updateMallFnCategory, fetchFnCategoryForSelect, createMallFnBgCategoryRel, findForSelectedTable } from '@/api/product-manage'
export default {
components: { Pagination },
props: {
pid: Number,
pname: String
},
data() {
return {
atteibute: {
id: undefined,
mainCategory: 1,
fnCategoryId: 0,
categoryId: 0,
categoryIdName: '',
fnCategoryName: '',
checked: true
},
fnCategoryId: undefined,
// 前台类目名:
fnCategoryName: '',
fnCategoryFileList: [],
treeProps: {
label: 'categoryName',
children: 'children'
},
data: [],
defaultProps: {
children: 'children',
label: 'label'
},
//
dialogImageUrl: '',
dialogVisible: false,
//
dialogStatus: '',
// 特殊属性弹框是否显示
dialogFormSpecialVisible: false,
// 弹框是否显示
dialogFormVisible: false,
// 弹框校验规则
rules: {
fnCategoryName: [{ required: true, message: '请输入类目名称', trigger: 'change' }],
status: [{ required: true, message: '请选择是否上架', trigger: 'change' }],
sortOrder: [{ required: true, message: '请输入排序', trigger: 'blur' }]
},
// 类目指向
categorypointToList: [
{
value: '无',
label: '无'
}, {
value: '个性URL',
label: '个性URL'
}, {
value: '频道页',
label: '频道页'
}, {
value: '后台类目',
label: '后台类目'
}
],
temp: {
fnCategoryId: undefined,
// 前台类目名:
fnCategoryName: '',
// 是否上架
status: 1,
// 类目排序
sortOrder: 0,
conditions: 'N',
level: 3,
special: 0,
fnCategoryImage: '',
parentId: this.pid
},
// 状态
valueList: [
{
value: '是',
label: '是'
}, {
value: '否',
label: '否'
}
],
textMap: {
update: '三级类目修改',
create: '三级类目新增'
},
// table集合
list: null,
tableList: [
],
multipleSelection: [],
// 分页
total: 0,
// loading
listLoading: true,
listQuery: {
level: 3,
page: 1,
pageSize: 10,
parentId: this.pid
},
listDataQuery: {
fnCategoryId: 0
}
}
},
created() {
// 列表查询
this.getList()
},
methods: {
updateKeyChildren(data, key1, key2) {
const checkedNodes = this.$refs.tree.getCheckedNodes()
if (checkedNodes != null && checkedNodes.length > 0) {
for (let i = 0; i < checkedNodes.length; i++) {
this.atteibute = {
id: undefined,
fnCategoryId: this.fnCategoryId,
categoryId: checkedNodes[i].categoryId,
categoryName: checkedNodes[i].categoryName,
fnCategoryName: this.fnCategoryName,
status: 1,
checked: true
}
const checkedNode = checkedNodes[i]
if (checkedNode.categoryId !== null && checkedNode.level === 3) {
let flag = true
for (let j = 0; j < this.tableList.length; j++) {
if (this.tableList[j].categoryId === checkedNode.categoryId) {
console.log(checkedNode)
flag = false
}
}
if (flag) {
if (checkedNode.checked) {
this.atteibute.mainCategory = 1
} else {
this.atteibute.mainCategory = 0
}
this.tableList.push(this.atteibute)
}
}
}
}
},
// 管理保存
specialClick() {
if (this.tableList.length < 1) {
this.dialogFormSpecialVisible = false
return
}
createMallFnBgCategoryRel(this.tableList).then(() => {
this.dialogFormVisible = false
this.dialogFormSpecialVisible = false
this.$notify({
title: 'Success',
message: 'Save Successfully',
type: 'success',
duration: 2000
})
})
},
handleDelete(index, row) {
this.tableList.splice(index, 1)
},
/**
* 回退
*/
returnBack() {
this.$emit('returnBack')
},
// 管理
handleManagement(row) {
this.tableList = []
this.listDataQuery.fnCategoryId = this.fnCategoryId = row.fnCategoryId
this.fnCategoryName = row.fnCategoryName
this.fnCategoryId = row.fnCategoryId
this.fnCategoryName = row.fnCategoryName
fetchFnCategoryForSelect(this.listDataQuery).then(response => {
this.data = response.model
setTimeout(() => {
this.listLoading = false
}, 1.5 * 1000)
findForSelectedTable(this.listDataQuery).then(response => {
this.tableList = response.model
for (let i = 0; i < this.tableList.length; i++) {
this.tableList[i].fnCategoryName = row.fnCategoryName
// console.log(this.data)
}
})
// Just to simulate the time of the request
setTimeout(() => {
this.listLoading = false
}, 1.5 * 1000)
})
this.dialogFormSpecialVisible = true
},
// 编辑
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
// 重置
resetTemp() {
this.temp = {
id: undefined,
// 类目中文名:
fnCategoryName: '',
// 是否上柜
status: 1,
// 类目排序
sortOrder: 0,
conditions: 'N',
level: 3,
special: 0,
fnCategoryImage: '',
parentId: this.pid
}
this.fnCategoryFileList = []
},
// 新增三级类目
addDate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
// 更新保存方法
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
updateMallFnCategory(tempData).then(() => {
const index = this.list.findIndex(v => v.id === this.temp.id)
this.list.splice(index, 1, this.temp)
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
// 创建保存方法
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createMallFnCategory(this.temp).then(() => {
this.list.unshift(this.temp)
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
handleRemove(file, fileList) {
console.log(file, fileList)
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url
this.dialogVisible = true
},
// 列表查询
getList() {
this.listLoading = true
fetchFnCategoryList(this.listQuery).then(response => {
this.list = response.model
this.total = response.totalItem
// Just to simulate the time of the request
setTimeout(() => {
this.listLoading = false
}, 1.5 * 1000)
})
}
}
}
</script>
<style scoped>
/* .mouseMark:hover {
cursor: pointer;
} */
</style>