猿实战13——实现你没听说过的前台类目

时间:2022-07-25
本文章向大家介绍猿实战13——实现你没听说过的前台类目,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

上几个章节,猿人君教会了你实现了属性/属性值和后台类目的绑定关系,今天,猿人君就带你来实现前台类目。

为什么会有前台类目

为了方便运营,在电商系统中,类目分为前台类目和后台类目。如果你经常网购,你会发现一个比较有意思的事情。鼠标移动到一级类目,会展示二级类目,点击一级二级类目,会跳转到对应的频道页面(不过做得大了都是分站,我们先考虑业务),点击三级类目会出发搜索的功能。

这些在之前的设计文章猿设计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>