猿实战是一个原创系列文章,通过实战的方式,采用前后端分离的技术结合SpringMVC Spring Mybatis,手把手教你撸一个完整的电商系统,跟着教程走下来,变身猿人找到工作不是问题。想要一起实战吗?关注公号,获取基础代码,动手实战吧。
上几个章节,猿人君教会了你实现了属性/属性值和后台类目的绑定关系,今天,猿人君就带你来实现前台类目。
为什么会有前台类目
为了方便运营,在电商系统中,类目分为前台类目和后台类目。如果你经常网购,你会发现一个比较有意思的事情。鼠标移动到一级类目,会展示二级类目,点击一级二级类目,会跳转到对应的频道页面(不过做得大了都是分站,我们先考虑业务),点击三级类目会出发搜索的功能。
这些在之前的设计文章猿设计5——真电商之颠覆你的类目认知中已经讲述过了,这里就不再赘述了。
功能概览
前台类目,从某种程度上说,肩负了部分站点的导航以及内容组织的职责,要完成这一职责,我们可以先来看看需要实现的功能。
在前台类目模块中,系统体现了前台类目的层级关系,每一级类目都有对应的列表、新增/编辑功能。在一级类目中可以维护广告牌、在三级类目中,重点维护前后台类目的关系(以上本章节暂不讨论)。
数据库设计
根据之前的前台类目设计文章,我们很清楚的获知了前台类目的一些特征,我们根据之前的设计,将这些设计落地为数据库的物理设计,大家可以看一下(本章节仅讲述前台类目的实现)。
就类目而言,类目就像是一棵树,每一级类目都有可能有下一级类目,像这种特性,在数据库的设计上,是一种典型的自关联结构。但是类目用于检索时,往往伴随着层级关系的查找,比如提取某一级类目的信息。所以,我们在设计时,给予了一个level字段,用于方便提取固定层级的信息。
由于每一级维护的信息可能不同,所以在数据库设计上,我们做了一个整合,每一条行记录的信息是一二三级所有信息的集合,事实上,不同的层级只涉及部分字段内容,这种设计,也是一种常见的设计。
前台类目整体前端
类目的层级维护,从功能上讲,依然是一种整体和部分的关系,出于业务的考虑,在规模不是特别庞大的情况下,三级已经足够支撑你的运营。我们可以参考下之前的实现思路——将一级类目、二级类目、三级类目分别定义成小的组件。最后,由一个view来组织和整合它们就好了。
"fnCategoryDiv">
if="oneCategoryShow"> "frontDeskCategoryOneSearch" @lookSubordinate="lookOneSubordinate" />
if="twoCategoryShow"> "frontDeskCategoryTwoSearch" :pid="parentId" :pname="parentName" @lookSubordinate="lookTwoSubordinate" @returnBack="returnTwoBack" />
if="threeCategoryShow"> "frontDeskCategoryThreeSearch" :pid="parentId" :pname="parentName" @returnBack="returnThreeBack" />
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 } }}
值得注意的是,在查看下级和返回上级种,涉及组件之间的参数传递。您需要将,本级类目的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 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 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> 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-paginationimport { 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>
二级前台类目
"backgroundCategoryTwoSearchDiv">
"float:right"> @click="returnBack">< "font-size:15px;margin-top:50px;line-height: 30px;">返回上一级
"primary" icon="el-icon-edit" style="margin-bottom:20px;float: left;margin-right: 40px;" @click="addDate()">新增二级
ref="table"
v-loading="listLoading"
:data="list"
style="width: 100%"
border
>
"类目ID">
"scope">{{ scope.row.fnCategoryId }}
"类目名称">
"scope">{{ scope.row.fnCategoryName }}
"父类目ID">
"scope">{{ scope.row.parentId }}
"类目级别">
二级类目
"排序">
"scope">{{ scope.row.sortOrder }}
status
"是否上架">
"scope">{{ scope.row.status ==1?"上架":"下架" }}
"类目指向">
"scope">{{ scope.row.conditions }}
"操作" width="200">
"scope">
type="primary"
size="mini"
@click="handleSubordinate(scope.row)"
>查看下级
type="primary"
size="mini"
@click="handleUpdate(scope.row)"
>修改
"total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
"textMap[dialogStatus]" :visible.sync="dialogFormVisible">
"dataForm" :rules="rules" :model="temp" label-position="right" label-width="120px" style="width: 320px; margin-left:50px;">
"类目名称:" prop="fnCategoryName">
"temp.fnCategoryName" placeholder="请输入类目中文名" />
"类目排序:" prop="sortOrder">
v-model="temp.sortOrder"
:min="0"
:max="100"
placeholder="请输入类目排序"
/>
"是否上柜:" prop="status">
"temp.status" placeholder="请选择">
v-for="(item,index) in valueList"
:key="index"
:label="item.label"
:value="item.value"
/>
"类目指向:" prop="conditions">
"temp.conditions" placeholder="请输入类目指向" />
"类目ICON:">
"width:300%">
action="http://127.0.0.1:9201//upload/uploadFile?moudle=fncategory"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:file-list="fnCategoryFileList"
>
"dialogVisible">
"100%" :src="dialogImageUrl" alt="">
"footer"> @click="dialogFormVisible = false">
取消
"primary" @click="dialogStatus==='create'?createData():updateData()">
确定
import Pagination from '@/components/Pagination' // secondary package based on el-paginationimport { 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 } }} /* .mouseMark:hover { cursor: pointer;} */
三级前台类目
"backgroundCategoryTwoSearchDiv">
"float:right"> @click="returnBack(pid,pname)">< "font-size:15px;margin-top:50px;line-height: 30px;">返回上一级
"primary" icon="el-icon-edit" style="margin-bottom:20px;float: left;margin-right: 40px;" @click="addDate()">新增三级
ref="table"
v-loading="listLoading"
:data="list"
style="width: 100%"
border
>
"类目ID">
"scope">{{ scope.row.fnCategoryId }}
"类目名称">
"scope">{{ scope.row.fnCategoryName }}
"父类目ID">
"scope">{{ scope.row.parentId }}
"类目级别">
三级类目
"排序">
"scope">{{ scope.row.sortOrder }}
status
"是否上架">
"scope">{{ scope.row.status ==1?"上架":"下架" }}
"类目指向">
"scope">
"color:#4395ff" @click="handleManagement(scope.row)">管理
"操作">
"scope">
type="primary"
size="mini"
@click="handleUpdate(scope.row)"
>修改
"total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.pageSize" @pagination="getList" />
"textMap[dialogStatus]" :visible.sync="dialogFormVisible">
"dataForm" :rules="rules" :model="temp" label-position="right" label-width="120px" style="width: 320px; margin-left:50px;">
"类目名称:" prop="fnCategoryName">
"temp.fnCategoryName" placeholder="请输入类目中文名" />
"类目排序:" prop="sortOrder">
v-model="temp.sortOrder"
:min="0"
:max="100"
placeholder="请输入类目排序"
/>
"是否上柜:" prop="status">
"temp.status" placeholder="请选择">
v-for="(item,index) in valueList"
:key="index"
:label="item.label"
:value="item.value"
/>
"类目指向:" prop="conditions">
"temp.conditions" placeholder="请输入类目指向" />
"类目ICON:">
"width:300%">
action="http://127.0.0.1:9201//upload/uploadFile?moudle=fncategory"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:file-list="fnCategoryFileList"
>
"dialogVisible">
"100%" :src="dialogImageUrl" alt="">
"footer"> @click="dialogFormVisible = false">
取消
"primary" @click="dialogStatus==='create'?createData():updateData()">
确定
if="dialogFormSpecialVisible" title="三级类目指向" :visible.sync="dialogFormSpecialVisible">
"6">
"margin-bottom:10px;font-weight: 600;">第一步:选择后台类目
"never">
ref="tree"
:data="data"
show-checkbox
node-key="categoryId"
:default-expanded-keys="[1,2]"
:props="treeProps"
clearable
filterable
allow-create
@check-change="updateKeyChildren"
/>
"1">
"17">
"top-start" effect="light">
"content">当某一后台三级类目与前台类目
进行初次绑定时,系统会强制将
对应前台类目设置成主类目
"margin-bottom:10px;font-weight: 600;">第二步:设置当前类目为前台主类目
"never">
ref="table"
:data="tableList"
style="width: 100%;"
border
>
"已选后台类目">
"scope">
{{ scope.row.categoryName }}
"已选前台主类目">
"scope">
{{ scope.row.fnCategoryName }}
"是否设置当前类目为前台主类目" width="140">
"scope">
"scope.row.checked">是
"操作" width="300">
"scope">
width="100"
type="danger"
@click="handleDelete(scope.$index, scope.row)"
>删除
"text-align: center;margin-top:20px;"> @click="dialogFormSpecialVisible = false">
取消
"primary" @click="specialClick">
确定
import Pagination from '@/components/Pagination' // secondary package based on el-paginationimport { 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) }) } }} /* .mouseMark:hover { cursor: pointer;} */