前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >猿实战10——动态表单之实现类目属性绑定

猿实战10——动态表单之实现类目属性绑定

作者头像
山旮旯的胖子
发布2020-09-14 10:17:28
9120
发布2020-09-14 10:17:28
举报
文章被收录于专栏:猿人工厂

猿实战是一个原创系列文章,通过实战的方式,采用前后端分离的技术结合SpringMVC Spring Mybatis,手把手教你撸一个完整的电商系统,跟着教程走下来,变身猿人找到工作不是问题。想要一起实战吗?关注公号即可获取基础代码!

上一个章节,猿人君教会了你实现了后台类目,今天开始我们来讲述,类目和属性之间的关系绑定。

为什么需要这种绑定关系

大家都知道,商品是有类别的,而类别之所以能够分门别类,是因为这些类别,本身就具备一些特性,而这些特性就是我们之前提到过的商品属性。

关于这个话题还不太理解的朋友们,建议您回过头去看看之前的文章,搞明白之后,相信可以解决您的疑惑。

功能概览

在之前的文章中,我们其实已经分析过类目和属性关系的设计了,简而言之,属性描述着类目的特性,而类目在将来,会把这些特性赋予具体的商品。

有的朋友可能比较着急,像快速的去知道这是怎样的一个链路,不过胖子认为,要搞清这样一个链路,还是先做一些比较实际的工作为好。量变之所能够质变,是量让你学会思考,产生了质的东西,才是属于你自己的。

我们先看看这一块儿的整体功能概览。

在类目与属性的关系中,在整体功能上,可以分为类目属性列表、类目属性值列表,在列表页面都提供了新增编辑功能。类目的选择,支持多级联动,新增/编辑属性或者属性值,支持输入检索,勾选检索内容,实现快速新增属性/或者属性值的功能。检索支持模糊查询,而且需要同时支持检索属性/属性组,属性值/属性值组。

数据库设计

根据之前的后台类目设计文章,我们很清楚的获知了后台类目的一些特征,我们根据之前的设计猿设计5——真电商之颠覆你的类目认知猿设计6——真电商之属性的套路你了解吗将这些设计落地为数据库的物理设计,大家可以看一下。

就类目属性的整体设计而言,类目属性更加明确了属性的类型,我们之前的属性库,作为基础数据,为类目属性提供数据来源。类目的特性,在绑定类目属性关系时来确定某一类属性的特性。

类目属性整体前端

类目属性的绑定,从功能上讲,依然是一种整体和部分的关系,属性值列表的展示依赖于,类目属性列表的选择,而属性/属性值的新增和编辑功能,则依赖着各自的组件。最后,由一个view来组织和整合它们就好了。

代码语言:javascript
复制
<template>
  <div id="attributeLibraryDiv">
    <div v-if="backgroundCategory">
      <el-card shadow="never">
        <div>
          <el-form ref="listQuery" :model="listQuery" :inline="true">
            <el-form-item label="所属类目:" style="width:46%;" prop="stair">
              <el-select v-model="listQuery.fristCategoryId" clearable filterable allow-create placeholder="请选择" style="width:30%;" @change="selectCatOne">
                <el-option
                  v-for="item in fristList"
                  :key="item.categoryId + '^-^'"
                  :label="item.categoryName"
                  :value="item.categoryId"
                />
              </el-select>
              <el-select v-model="listQuery.secondCategoryId" clearable filterable allow-create placeholder="请选择" style="width:30%;" @change="selectCatTwo">
                <el-option
                  v-for="item in secondList"
                  :key="item.categoryId + '^-^'"
                  :label="item.categoryName"
                  :value="item.categoryId"
                />
              </el-select>
              <el-select v-model="listQuery.thridCategoryId" clearable filterable allow-create placeholder="请选择" style="width:30%;" @change="selectCatThree">
                <el-option
                  v-for="item in thirdList"
                  :key="item.categoryId + '^-^'"
                  :label="item.categoryName"
                  :value="item.categoryId"
                />
              </el-select>
            </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" @click="addDate()">新增属性</el-button>
              <el-button icon="el-icon-s-tools" @click="resetForm('listQuery')">重置</el-button>
            </el-form-item>
          </el-form>
        </div>
      </el-card>
      <div style="height:20px;" />
      <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.categoryPropertyId }}</template>
          </el-table-column>
          <el-table-column label="属性ID">
            <template slot-scope="scope">{{ scope.row.propertyId }}</template>
          </el-table-column>
          <el-table-column label="属性名">
            <template slot-scope="scope">{{ scope.row.propertyName }}</template>
          </el-table-column>
          <el-table-column label="排序">
            <template slot-scope="scope">{{ scope.row.sortOrder }}</template>
          </el-table-column>
          <el-table-column label="状态">
            <template slot-scope="scope">{{ scope.row.status == 1 ? "启用" : "停用" }}</template>
          </el-table-column>
          <el-table-column label="属性类型">
            <template slot-scope="scope">{{ getPropertyType(scope.row.propertyType) }}</template>
          </el-table-column>
          <el-table-column label="录入方式">
            <template slot-scope="scope">
              {{ getInputType(scope.row.inputType) }}
            </template>
          </el-table-column>
          <el-table-column label="是否导航属性">
            <template slot-scope="scope">{{ scope.row.nav == 1 ? "是" : "否" }}</template>
          </el-table-column>
          <el-table-column label="是否必填">
            <template slot-scope="scope">
              {{ scope.row.required == 1 ? "必填" : "选填" }}
            </template></el-table-column>
          <el-table-column label="操作" width="300">
            <template slot-scope="scope">
              <el-button
                type="primary"
                size="mini"
                @click="handleLook(scope.$index,scope.row)"
              >查看属性值
              </el-button>
              <el-button
                type="primary"
                size="mini"
                @click="handleUpdate(scope.row)"
              >修改
              </el-button>
              <el-button
                size="mini"
                type="danger"
                @click="handleDelete(scope.$index, 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" />
    </div>
    <div v-if="attributeValue">
      <attributeValueListSearch ref="attributeValueListSearch" :showflag.sync="showflag" :cpid="categoryPropertyId" :cid="categoryId" @returnBack="returnBack" />
    </div>
    <!-- 新增/编辑弹框 -->
    <el-dialog v-if="dialogFormVisible" :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
      <attributeCUpdate ref="attributeCUpdate" :attributeflag.sync="attributeFlag" @closeClick="closeClick" @addClick="addClick" />
    </el-dialog>
  </div>
</template>
<script>
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import attributeValueListSearch from '@/components/productManage/categoryAttributeValueListSearch'
import attributeCUpdate from '@/components/productManage/attributeCUpdate'
import { fetchCategoryList, fetchCategoryPropertyList, deleteMallCategoryProperty } from '@/api/product-manage'
export default {
  components: {
    attributeValueListSearch, Pagination, attributeCUpdate },
  data() {
    return {
      attributeFlag: true,
      // 弹框是否显示
      dialogFormVisible: false,
      dialogStatus: '',
      textMap: {
        update: '属性修改',
        create: '新增属性'
      },
      categoryPropertyId: 0,
      categoryId: 0,
      // 标识
      showflag: true,
      backgroundCategory: false,
      attributeValue: false,
      // 分页
      total: 0,
      // loading
      listLoading: true,
      // table集合
      list: null,
      listCategoryQuery: {
        parentId: null,
        page: 1,
        pageSize: 500
      },
      listQuery: {
        // 所属类目:
        stair: '',
        //
        fristCategoryId: null,
        //
        secondCategoryId: null,
        //
        thridCategoryId: null,
        categoryId: 0,
        page: 1,
        pageSize: 10
      },
      // 一级
      fristList: [],
      // 二级
      secondList: [],
      // 三级
      thirdList: [],
      propertyTypeList: [
        {
          value: 1,
          label: '普通属性'
        }, {
          value: 2,
          label: '关键属性'
        },
        {
          value: 3,
          label: '文字销售属性'
        }, {
          value: 4,
          label: '图片销售属性'
        }
      ],
      inputTypeList: [
        {
          value: 0,
          label: '单选'
        }, {
          value: 1,
          label: '多选'
        },
        {
          value: 2,
          label: '输入'
        }
      ]
    }
  },
  created() {
    this.backgroundCategory = true
    // 列表查询
    this.getList()
    this.initFristList()
  },
  methods: {
    // 编辑新增
    addClick(data, flag) {
      this.dialogFormVisible = true
    },
    // 关闭
    closeClick() {
      this.dialogFormVisible = false
      this.getList()
    },
    // 返回
    returnBack() {
      this.backgroundCategory = true
      this.attributeValue = false
    },
    // 删除
    handleDelete(index, row) {
      console.log(row)
      deleteMallCategoryProperty(row).then(response => {
        this.$notify({
          title: 'Success',
          message: 'Delete Successfully',
          type: 'success',
          duration: 2000
        })
 
        this.getList()
      })
    },
    // 修改
    handleUpdate(row) {
      console.log(row)
      this.attributeFlag = true
      this.dialogStatus = 'update'
      this.dialogFormVisible = true
      setTimeout(() => {
        this.$refs.attributeCUpdate.updateDate(this.attributeFlag, row)
      }, 50)
    },
    // 查看属性值
    handleLook(index, row) {
      this.backgroundCategory = false
      this.categoryPropertyId = row.categoryPropertyId
      this.categoryId = row.categoryId
      this.attributeValue = true
    },
    // 列表查询
    getList() {
      this.listLoading = true
      this.chooseCategoryId()
      fetchCategoryPropertyList(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)
      })
    },
    // 查询方法
    fetchData() {
      this.getList()
    },
    // 重置表单
    resetForm(formName) {
      this.listQuery.fristCategoryId = null
      //
      this.listQuery.secondCategoryId = null
      //
      this.listQuery.thridCategoryId = null
 
      this.listQuery.categoryId = 0
 
      this.getList()
    },
    // 新增
    addDate() {
      this.chooseCategoryId()
      if (this.listQuery.categoryId === 0) {
        this.$message({
          message: '请先选择需要新增属性的类目!',
          type: 'success'
        })
        return false
      }
      const row = {}
      this.attributeFlag = false
      this.dialogStatus = 'create'
      this.dialogFormVisible = true
      setTimeout(() => {
        this.$refs.attributeCUpdate.updateDate(this.attributeFlag, row, this.listQuery.categoryId, this.listQuery.fristCategoryId, this.listQuery.secondCategoryId, this.listQuery.thridCategoryId)
      }, 50)
    },
    // 列表查询
    initFristList() {
      this.listLoading = true
      fetchCategoryList(this.listCategoryQuery).then(response => {
        this.fristList = response.model
        // Just to simulate the time of the request
        setTimeout(() => {
          this.listLoading = false
        }, 1.5 * 1000)
      })
    },
    selectCatOne(option) {
      this.listLoading = true
      this.listCategoryQuery.parentId = option
      this.listQuery.fristCategoryId = option
      // this.listCategoryQuery.parentId = item.categoryId
      fetchCategoryList(this.listCategoryQuery).then(response => {
        this.secondList = response.model
        // Just to simulate the time of the request
        setTimeout(() => {
          this.listLoading = false
        }, 1.5 * 1000)
        this.getList()
      })
    },
    selectCatTwo(option) {
      this.listLoading = true
      this.listCategoryQuery.parentId = option
      this.listQuery.secondCategoryId = option
      this.secondCategoryId = option
      // this.listCategoryQuery.parentId = item.categoryId
      fetchCategoryList(this.listCategoryQuery).then(response => {
        this.thirdList = response.model
        // Just to simulate the time of the request
        setTimeout(() => {
          this.listLoading = false
        }, 1.5 * 1000)
        this.getList()
      })
    },
    selectCatThree(option) {
      this.listQuery.thridCategoryId = option
      this.thridCategoryId = option
      this.listLoading = true
      this.getList()
    },
    chooseCategoryId() {
      if (this.listQuery.fristCategoryId !== null && this.listQuery.fristCategoryId !== '' && this.listQuery.fristCategoryId > 0) {
        this.listQuery.categoryId = this.listQuery.fristCategoryId
      }
 
      if (this.listQuery.secondCategoryId !== null && this.listQuery.secondCategoryId !== '' && this.listQuery.secondCategoryId > 0) {
        this.listQuery.categoryId = this.listQuery.secondCategoryId
      }
 
      if (this.listQuery.thridCategoryId !== null && this.listQuery.thridCategoryId !== '' && this.listQuery.thridCategoryId > 0) {
        this.listQuery.categoryId = this.listQuery.thridCategoryId
      }
    },
    getPropertyType(propertyType) {
      var rowData = this.propertyTypeList.filter(itmer => {
        if (itmer.value === propertyType) {
          return itmer.label
        }
      })
 
      if (undefined !== rowData[0]) {
        return (rowData[0].label)
      }
    },
 
    getInputType(inputType) {
      var rowData = this.inputTypeList.filter(itmer => {
        if (itmer.value === inputType) {
          return itmer.label
        }
      })
 
      if (undefined !== rowData[0]) {
        return (rowData[0].label)
      }
    }
  }
}
</script>
 
<style scoped>
#attributeLibraryDiv /deep/ .el-dialog__body {
    padding: 0px 20px;
}
</style>

类目的联动实现

考虑到页面的功能较多,我们依然采用先部分再到整体的实现策略,比如三级类目联动的实现。

在实现之前,我们先想一想,联动是怎样一个过程?首先需要显示一级类目吧?二级类目的展示,自然需要一级类目的数据支持,只有当确定了一级类目,才知道需要一级类目下的二级类目数据,同样的,三级类目,也就依赖于二级类目的选择了。

既然是根据数据做再做选择,那么自然离不开el-select组件了。

既然是三个类目列表,自然少不了存储和记录数据。话说在类似VUE这类前端框架下开发,真的比以前省事儿多了,准备好数据,组件帮你渲染就好了。

查询之间也有依赖关系,自然也需要保存每次选择的结果。

接下来,自然是要考虑数据获取的问题了。一级类目选择之后,自然要加载二级类目的数据了。

嗯,似乎在选择类目数据时,是支持搜索的,这一点el-select组件已经帮我们实现好了。加上clearable filterable 两个属性就可以了。

等等,还忘记了一件事情,API在哪里呢?

代码语言:javascript
复制
// 类目
export function fetchCategoryList(query) {
  return request({
    url: '/category/findByPage',
    method: 'post',
    data: query
  })
}

这个不是之前分页的时候用过吗?嗯,可以考虑改变页码大小的方式继续使用嘛,稍微灵活变通下,满足要求就可以了。当然,你也可以开发一个专用的不分页API进行查询。

代码语言:javascript
复制
listCategoryQuery: {
        parentId: null,
        page: 1,
        pageSize: 500
      }
 

需要注意的是这个“不分页返回类目列表”的实现,因为在后续的很多场景中,往往要求获取整个查询条件下的后台类目,此时封装一个区别于分页的接口,算是一种预先考虑的目的。

类目联动后端实现

后端的实现就简单多了,我们只用返回对应的数据就可以了。用到的api也不多,之前已经实现过了,见MallCategoryController。

代码语言:javascript
复制
 /**
     * 分页返回类目列表
     * @param queryMallCategory
     * @return
     */
    @RequestMapping("/findByPage")
    public  Result<List<MallCategory>> findByPage(@RequestBody QueryMallCategory queryMallCategory){
        return mallCategoryService.getMallCategorysByPage(queryMallCategory);
    }

service以及dao层面的实现,搞定代码生成器猿实战05——手把手教你拥有自己的代码生成器,就可以得到你想要的功能了。想要一起实战吗?关注公号即可获取基础代码!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 猿人工厂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档