element根据输入,动态生成表格(版本一)

场景:后台页面根据商品规格和规格值,动态(增删改查)在表格中生成对应的sku.

如图:

在这里插入图片描述

代码如下:

edit.html

<template xmlns="">
  <div class="app-container">
    <el-form ref="form" :model="form" label-width="80px" autocomplete="off" @submit.native.prevent>
      <h3>基础信息</h3>
      <div style="padding-left: 60px">
        <el-row>
          <el-col :span="7">
            <el-form-item label="spu_id">
              <el-input v-model="form.id" autocomplete="off" placeholder="" :disabled="true" />
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="名称" :required="true">
              <el-input v-model="form.goods_name" autocomplete="off" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="商品类型" :required="true">
              <el-select v-model="form.goods_type" placeholder="请选择">
                <el-option
                  v-for="item in goodsTypeOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value">
                </el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="5">
            <el-form-item label="品牌" :required="true">
              <el-select v-model="form.brand_code" style="width: 100%">
                <el-option
                  v-for="item in brandOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="分类" :required="true">
              <el-cascader
                v-model="form.cate_code"
                collapse-tags
                clearable
                :options="cateOptions"
                :props="{ multiple: false, checkStrictly: true }"
                filterable
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="5">
            <el-form-item label="赠品" :required="true">
              <el-radio v-model="form.is_gift" label="1"></el-radio>
              <el-radio v-model="form.is_gift" label="2"></el-radio>
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="列表显示" :required="true">
              <el-radio v-model="form.is_show_video" label="1"></el-radio>
              <el-radio v-model="form.is_show_video" label="2"></el-radio>
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="是否套组" :required="true">
              <el-radio v-model="form.is_suite" label="1"></el-radio>
              <el-radio v-model="form.is_suite" label="2"></el-radio>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="5">
            <el-form-item label="是否预售" :required="true">
              <el-radio v-model="form.is_pre_sales" label="1"></el-radio>
              <el-radio v-model="form.is_pre_sales" label="2"></el-radio>
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="是否临期" :required="true">
              <el-radio v-model="form.is_expire" label="1"></el-radio>
              <el-radio v-model="form.is_expire" label="2"></el-radio>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="5">
            <el-form-item label="包装规格">
              <el-input v-model="form.pack_spec" autocomplete="off" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="包装数量">
              <el-input v-model="form.pack_count" autocomplete="off" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="销售单位">
              <el-input v-model="form.unit_name" autocomplete="off" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="保质期">
              <el-date-picker
                v-model="form.expire_date"
                type="date"
                value-format="yyyy-MM-dd"
                placeholder="选择日期"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="商品图片" :required="true">
              <ul class="el-upload-list el-upload-list--picture-card">
                <vuedraggable v-model="form.images">
                  <li v-for="(item, index) in form.images" :key="index" class="el-upload-list__item is-success animated">
                    <img :src="item" alt="" class="el-upload-list__item-thumbnail ">
                    <i class="el-icon-close" />
                    <span class="el-upload-list__item-actions">
                      <!-- 预览 -->
                      <span class="el-upload-list__item-preview" @click="viewImage(item)">
                        <i class="el-icon-zoom-in" />
                      </span>
                      <!-- 删除 -->
                      <span class="el-upload-list__item-delete" @click="delImage(index)">
                        <i class="el-icon-delete" />
                      </span>
                    </span>
                  </li>
                </vuedraggable>
              </ul>
              <!--            <el-button size="small" type="info" @click="uploadImageDialog = true">点击上传</el-button>-->
              <label for="upload-btn" class="el-button el-button--info el-button--mini">上传文件</label>
              <input
                v-show="false"
                id="upload-btn"
                ref="uploadBtn"
                type="file"
                accept="image/png, image/jpeg, image/gif, image/jpg"
                @change="loadImg($event)"
              >
              <div class="el-upload__tip">第1张为主图,其它为从图.尺寸800*800</div>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="7">
            <el-form-item label="推荐语">
              <el-input v-model="form.recommendation" autocomplete="off" placeholder="" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="描述" :required="true">
              <tinymce v-model="form.description" :height="300" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="保管事项">
              <el-select v-model="form.keep_desc_tpl_id" placeholder="请选择" style="width: 100%">
                <el-option
                  v-for="item in keepOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="温馨提示">
              <el-select v-model="form.notice_desc_tpl_id" placeholder="请选择" style="width: 100%">
                <el-option
                  v-for="item in noticeOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </div>
      <h3>sku信息</h3>
      <div style="padding-left: 60px">
        <sku-edit ref="skuEdit" />
      </div>
      <el-form-item>
        <el-button type="primary" @click="save">保存</el-button>
        <router-link style="padding-left: 10px;" to="/goods/erp-goods-list">
          <el-button>返回列表</el-button>
        </router-link>
      </el-form-item>
    </el-form>
    <el-dialog :visible.sync="imageViewDialog">
      <img width="100%" :src="imageViewUrl" alt="">
    </el-dialog>
    <el-dialog
      width="700px"
      custom-class="image-upload"
      :visible.sync="croperImageDialog"
      :close-on-click-modal="false"
      @close="emptyUpload"
    >
      <div style="display:flex;justify-content: center;">
        <cropper ref="myCropper" @updateImageList="updateImageList" />
      </div>
      <div slot="footer">
        <div class="dialog-footer-btn">
          <div>
            <el-button @click="croperImageDialog = false">返 回</el-button>
            <el-button type="primary" @click="cropImg">确 定</el-button>
          </div>
        </div>
      </div>
    </el-dialog>
  </div>
</template>

<script>

import { spuCreate, spuDetail } from '@/api/goods/goods'
import { getOptions, uploadUrl } from '@/api/common/common'
import { empty } from '@/utils/index'
import Tinymce from '@/components/Tinymce/index.vue'
import skuEdit from '@/views/goods/goods/sku_edit.vue'
import { getToken } from '@/utils/auth'
import vuedraggable from 'vuedraggable'
import cropper from '@/views/goods/component/cropper.vue'
export default {
  name: 'GoodsEdit',
  components: { Tinymce, vuedraggable, cropper, skuEdit },
  props: ['id'],
  data() {
    return {
      salesAmountLimit: 1,
      uploadImageDialog: false,
      croperImageDialog: false,
      imageViewDialog: false,
      imageViewUrl: false,
      uploadUrl: uploadUrl,
      cropperDialogVisible: false,
      tagOptions: [],
      keepOptions: [],
      noticeOptions: [],
      uppy: {},
      cateOptions: [],
      brandOptions: [],
      goodsTypeOptions: [],
      uploadHeaders: {
        token: getToken()
      },
      leftTagFileList: [],
      rightTagFileList: [],
      form: {},
      copy: false,
      initForm: {
        id: '',
        goods_name: '',
        barcode: '',
        cate_code: '',
        brand_code: '',
        description: '',
        images: [],
        pack_spec: '',
        pack_count: '',
        unit_name: '',
        expire_date: '',
        video: '',
        is_expire: '1',
        is_gift: '1',
        is_pre_sales: '1',
        video_cover: '',
        is_show_video: '1',
        recommendation: '',
        goods_type: '1',
        is_suite: '1',
        merchant_id: '',
        keep_desc_tpl_id: '',
        notice_desc_tpl_id: '',
        sku_map_list: [],
        spu_attrs: []
      }
    }
  },
  computed: {},
  watch: {
    'form.left_tag_type': {
      deep: true,
      handler(newVal, oldVal) {
        if ((newVal === 7 && oldVal !== 7) || (newVal !== 7 && oldVal === 7)) {
          this.form.left_tag = ''
          this.leftTagFileList = []
        }
      }
    },
    'form.right_tag_type': {
      deep: true,
      handler(newVal, oldVal) {
        if ((newVal === 7 && oldVal !== 7) || (newVal !== 7 && oldVal === 7)) {
          this.form.right_tag = ''
          this.rightTagFileList = []
        }
      }
    }
  },
  mounted() {
    if (this.$route.query.copy) {
      this.copy = JSON.parse(this.$route.query.copy)
    }
  },
  activated() {
    this.form = JSON.parse(JSON.stringify(this.initForm))
    this.detail()
    getOptions({ option_names: [
      'cateOptions', 'brandOptions', 'goodsTypeOptions', 'goodsDescTplOption', 'goodsKeepTplOption'] }).then(response => {
      this.goodsTypeOptions = response.data.goodsTypeOptions
      this.cateOptions = response.data.cateOptions
      this.brandOptions = response.data.brandOptions
      this.keepOptions = response.data.goodsKeepTplOption
      this.noticeOptions = response.data.goodsDescTplOption
    })
  },
  methods: {
    async save() {
      // 获取 skuEdit 子组件 的信息
      this.form.sku_map_list = this.$refs.skuEdit.spuInfo.sku_map_list
      // is_draft =1 为草稿数据 2-为审核通过的数据
      this.form.is_draft = 2
      await spuCreate(this.form)
      this.$message({
        message: '保存成功,待审核!',
        type: 'success'
      })
      this.form = JSON.parse(JSON.stringify(this.initForm))
      this.$router.push({ path: '/goods/goods/draft-list' })
    },
    detail() {
      const id = this.$route.query.id
      const params = {}
      params.id = id
      if (empty(id)) {
        return
      }
      spuDetail(params).then(response => {
        this.form = response.data
        this.$refs.skuEdit.spuInfo.sku_map_list = this.form.sku_map_list
        this.$refs.skuEdit.spuAttrs = this.form.spu_attrs
        this.form.is_expire = this.form.is_expire.toString()
        this.form.is_gift = this.form.is_gift.toString()
        this.form.is_show_video = this.form.is_show_video.toString()
        this.form.is_suite = this.form.is_suite.toString()
        this.form.is_pre_sales = this.form.is_pre_sales.toString()
        this.form.is_expire = this.form.is_expire.toString()
        if (this.copy) {
          this.form.id = ''
        }
      })
    },
    viewImage(item) {
      this.imageViewUrl = item.url
      this.imageViewDialog = true
    },
    delImage(index) {
      this.form.images.splice(index, 1)
    },
    loadImg(e) {
      const file = e.target.files[0]
      if (file.size / 1024 / 1024 > 5) {
        this.$message.error('上传文件不能超过5M')
        return false
      }
      if (!/\.(jpg|png|gif|JPG|PNG|GIF|)$/.test(file.name)) {
        this.$message.info('图片类型必须是jpg,png的一种')
        return false
      }
      const reader = new FileReader()
      reader.onload = (e) => {
        let data
        if (typeof e.target.result === 'object') {
          data = window.URL.createObjectURL(new Blob([e.target.result]))
        } else {
          data = e.target.result
        }
        this.croperImageDialog = true
        this.$nextTick(() => {
          this.$refs.myCropper.imgLoad(file, data)
        })
      }
      reader.readAsArrayBuffer(file)
    },
    emptyUpload() {
      this.$refs.uploadBtn.value = ''
    },
    cropImg() {
      this.$refs.myCropper.crop()
    },
    updateImageList(item) {
      this.form.images.push(item.url)
      this.croperImageDialog = false
    }
  }
}
</script>

<style lang="scss" scoped>
.app-container {
  overflow-y: auto;
  height: 100%;
}

.app-container >>> .cropper-modal {
  opacity: 0.5;
}

el-input {
  width: 300px;
}
</style>

子组件sku_edit.html

<template>
  <div class="">
    <el-form ref="SkuEdit" label-width="110px">
      <el-form-item label="spu规格" required>
        <vuedraggable v-model="spuAttrs" @start="onStart" @end="onEnd">
          <transition-group>
            <template v-for="(item, index) in spuAttrs">
              <div :key="index" :class="(index < (spuAttrs.length - 1)) ? 'attr_select_index' : '' ">
                <div>
                  <el-select
                    v-model="item.attr"
                    filterable
                    allow-create
                    placeholder="选择或创建"
                    size="mini"
                    @blur="addAttr($event,item,index)"
                    @change="changeAttr($event, index)"
                  >
                    <el-option
                      v-for="(cateItem, cateIndex) in cateAttrs"
                      :key="cateIndex"
                      :label="cateItem.label"
                      :value="cateIndex"
                    />
                  </el-select>
                  <i :key="index" class="item el-icon-sort" @mouseover="showTooltipSpec" @mouseleave="hideTooltipSpec" />
                  <span v-if="tooltipVisibleSpec&&index===0">拖动上下排序</span>
                  <i :key="index" class="el-icon-circle-plus attr_options_add" @click="spuAttrItem(1, index + 1)" />
                  <i v-if="spuAttrs.length>1" :key="index" class="el-icon-remove attr_options_del" @click="spuAttrItem(2,index)" />
                </div>
                <div style="margin-left: 20px">
                  <vuedraggable v-model="item.values" @start="onStart" @end="onEnd">
                    <transition-group style="display: flex; flex-wrap: wrap;">
                      <template v-for="(attrVal,vi) in item.values">
                        <div :key="vi">
                          <div style="margin-left: 15px">
                            <el-input
                              v-model="item.values[vi]"
                              placeholder="添加规格值"
                              size="mini"
                              style="width: 100px;"
                              @blur="addAttrValue($event,index, vi)"
                            />
                            <template>
                              <i
                                :key="vi"
                                class="item el-icon-s-operation"
                                @mouseover="showTooltipSpecValue"
                                @mouseleave="hideTooltipSpecValue"
                              />
                              <span v-if="tooltipVisibleSpecValue&&vi===0&&index===0">拖动左右排序</span>
                              <i
                                :key="vi"
                                class="el-icon-circle-plus attr_val_options_add"
                                @click="spuAttrValueItem(1,index)"
                              />
                              <i
                                v-if="item.values.length>1"
                                :key="vi"
                                class="el-icon-remove attr_val_options_del"
                                @click="spuAttrValueItem(2,index,vi)"
                              />
                            </template>
                          </div>

                        </div>
                      </template>
                    </transition-group>
                  </vuedraggable>
                </div>
              </div>
            </template>
          </transition-group>
        </vuedraggable>
      </el-form-item>
      <el-form-item label="映射sku" required>
        <el-table
          :data="spuInfo.sku_map_list"
          border
          :header-row-style="tableHeaderRowStyle"
          :header-cell-style="tableHeaderCellStyle"
          :max-height="tableHeight"
          size="mini"
          style="width: 100%"
          empty-text="暂无数据"
        >
          <el-table-column
            v-for="(item,index) in spuAttrs"
            :key="index"
            :label="item.attr"
          >
            <template slot-scope="scop">
              <div>{{ getAttrValue(scop.row, index) }}</div>
            </template>
          </el-table-column>
          <el-table-column
            prop="barcode"
            label="商品条码"
          >
            <template slot-scope="scope">
              <el-input
                v-model="scope.row.barcode"
                size="mini"
                placeholder="请输入商品条码"
                @blur="addBarcode($event,scope.row,scope.$index)"
              />
            </template>
          </el-table-column>
          <el-table-column
            prop="price"
            label="售价"
          >
            <template slot-scope="scope">
              <el-input
                v-model="scope.row.price"
                size="mini"
                placeholder="请输入商品售价"
                @blur="addPrice($event,scope.row,scope.$index)"
              />
            </template>
          </el-table-column>
          <el-table-column
            prop="market_price"
            label="市场价"
          >
            <template slot-scope="scope">
              <el-input
                v-model="scope.row.price"
                size="mini"
                placeholder="请输入商品市场价"
                @blur="addMarketPrice($event,scope.row,scope.$index)"
              />
            </template>
          </el-table-column>
          <el-table-column
            prop="image"
            label="图片"
            width="100"
          >
            <template slot-scope="scope">
              <el-upload
                class="avatar-uploader"
                :action="uploadUrl+'?type=image'"
                :limit="1"
                :show-file-list="false"
                :on-success="(res,file,fileList) => {handleAvatarSuccess(res,file,fileList,scope.$index)}"
                :headers="uploadHeaders"
              >
                <img v-if="scope.row.image!=''" :src="scope.row.image" class="avatar">
                <i v-else class="el-icon-plus avatar-uploader-icon" />
              </el-upload>
            </template>
          </el-table-column>
        </el-table>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
import vuedraggable from 'vuedraggable'
import { getOptions, uploadUrl } from '@/api/common/common'
import { spuDetail, saveSpu } from '@/api/goods/spu'
import { empty } from '@/utils'
import Common from '@/layout/mixin/Common'
import { getToken } from '@/utils/auth'

export default {
  name: 'SkuEdit',
  components: {
    'vuedraggable': vuedraggable
  },
  mixins: [Common],
  props: ['spu_id'],
  data() {
    return {
      // 1-为规格 2-为规格值
      changeType: 1,
      // 变化内容
      changeContent: '',
      drag: false,
      tooltipVisibleSpec: false,
      tooltipVisibleSpecValue: false,
      uploadUrl: uploadUrl,
      spuInfo: {
        sku_map_list: []
      },
      uploadHeaders: {
        token: getToken()
      },
      cateAttrs: [],
      cateAttrCount: 0,
      spuAttrs: [],
      selectMapSkuVisible: false,
      skuListLoading: false,
      skuList: [],
      barcode: '',
      goodsName: '',
      selectSkuPage: 1,
      selectSkuPageSize: 30,
      selectSkuTotal: 0,
      selectSkuMapIndex: -1
    }
  },
  mounted() {
    getOptions({ option_names: ['spuSpecOptions'] }).then(response => {
      this.cateAttrs = response.data.spuSpecOptions
    })
    if (!empty(this.spu_id)) {
      this.spuInfo.id = this.spu_id
      spuDetail({ id: this.spu_id }).then(res => {
        this.cateAttrCount = Object.keys(this.cateAttrs).length
        this.spuInfo.sku_map_list = res.data.sku_map_list
        this.spuAttrs = res.data.spu_attrs
        this.$refs.imageCropperUpload.setImage([{ path: this.spuInfo.image, url: this.spuInfo.image_url }])
      })
    }
    if (empty(this.spuAttrs)) {
      this.spuAttrs = [
        {
          attr: '',
          values: ['']
        }
      ]
    }
  },
  methods: {
    empty,
    showTooltipSpec() {
      console.log('showTooltipSpec','----------')
      setTimeout(() => {
        this.tooltipVisibleSpec = true
      }, 1000)
    },
    hideTooltipSpec() {
      console.log('hideTooltipSpec','----------')
      this.tooltipVisibleSpec = false
    },
    showTooltipSpecValue() {
      console.log('showTooltipSpecValue','----------')
      setTimeout(() => {
        this.tooltipVisibleSpecValue = true
      }, 1000)
    },
    hideTooltipSpecValue() {
      console.log('hideTooltipSpecValue','----------')
      this.tooltipVisibleSpecValue = false
    },
    onStart() {
      this.drag = true
    },
    handleAvatarSuccess(res, file, fileList, index) {
      this.spuInfo.sku_map_list[index].image = res.data[0].url
    },
    // 添加规格
    addAttr(e, item, index) {
      if (empty(e.target.value)) {
        return
      }
      this.spuAttrs.push({
        attr: e.target.value,
        values: ['']
      })
      // 判断this.spuAttrs.attr 是否有空值,如果有空值则删除
      for (let i = 0; i < this.spuAttrs.length; i++) {
        if (empty(this.spuAttrs[i].attr)) {
          this.spuAttrs.splice(i, 1)
        }
      }
    },
    addBarcode(e, row, index) {
      this.spuInfo.sku_map_list[index].barcode = e.target.value
    },
    addPrice(e, row, index) {
      this.spuInfo.sku_map_list[index].price = e.target.value
    },
    addMarketPrice(e, row, index) {
      this.spuInfo.sku_map_list[index].market_price = e.target.value
    },
    addAttrValue(e, index, vi) {
      if (empty(e.target.value)) {
        return
      }
      this.attrSkuMap(1, index, this.spuAttrs[index].values.length - 1)
    },
    onEnd() {
      this.drag = false
      this.attrSkuMap(3, 0, 0)
    },
    // 选择规格名
    changeAttr(e, index) {
      this.spuAttrs[index].values = ['']
      this.spuAttrs[index].attr = this.cateAttrs[index].label
      this.attrSkuMap(3, index, -1)
    },
    spuAttrItem(type, index) {
      if (type === 1) {
        this.spuAttrs.push({
          attr: '',
          values: ['']
        })
      } else {
        this.changeType = 1
        this.changeContent = this.spuAttrs[index].attr
        this.spuAttrs.splice(index, 1)
      }
      this.attrSkuMap(type, index, type === 1 ? 0 : 1)
    },
    // 添加|删除规格值
    spuAttrValueItem(type, pIndex, subIndex) {
      if (type === 1) {
        this.spuAttrs[pIndex].values.push('')
      } else {
        this.changeType = 2
        this.changeContent = this.spuAttrs[pIndex].values[subIndex]
        this.spuAttrs[pIndex].values.splice(subIndex, 1)
      }
      this.attrSkuMap(type, pIndex, subIndex)
    },
    // 生成sku组合表格,处理每行数据x
    generateCombinations(type, pIndex, subIndex, changeIndex = 0, spliceIndex = -1, currentCombination = [], index = 0) {
      if (index >= this.spuAttrs.length) {
        // 1-新增,2-删除,3-修改
        if (type === 1) {
          // 页面新开 新增的sku情况
          if (empty(this.spuInfo.sku_map_list[changeIndex])) {
            this.spuInfo.sku_map_list.push({
              barcode: '',
              price: '',
              market_price: '',
              attrs: [],
              image: ''
            })
            // attrs  字段与页面显示无关,只为传给后端使用
            for (let j = 0; j < currentCombination.length; j++) {
              this.spuInfo.sku_map_list[changeIndex].attrs.push({
                attr: currentCombination[j].attr,
                value: currentCombination[j].value
              })
            }
            // 页面新开 编辑的sku情况
          } else if (currentCombination.length === this.spuInfo.sku_map_list[changeIndex].attrs.length) {
            for (let j = 0; j < currentCombination.length; j++) {
              this.spuInfo.sku_map_list[changeIndex].attrs[j].attr = currentCombination[j].attr
              this.spuInfo.sku_map_list[changeIndex].attrs[j].value = currentCombination[j].value
            }
          } else if (currentCombination.length > this.spuInfo.sku_map_list[changeIndex].attrs.length) {
            const item = currentCombination.slice(-1)
            this.spuInfo.sku_map_list[changeIndex].attrs.push({
              attr: item.attr,
              value: item.value
            })
          }
        } else if (type === 2) {
          // 遍历currentCombination,把value 拼接组合,后面在循环中删除多余重复的行
          if (this.changeType === 1) {
            let currentCombinationValue = ''
            for (let j = 0; j < currentCombination.length; j++) {
              currentCombinationValue += currentCombination[j].value + '_'
            }
            for (let k = 0; k < this.spuInfo.sku_map_list.length; k++) {
              if (empty(this.spuInfo.sku_map_list[k])) {
                continue
              }
              for (let j = 0; j < this.spuInfo.sku_map_list[k].attrs.length; j++) {
                if (empty(this.spuInfo.sku_map_list[k].attrs[j])) {
                  continue
                }
                if (this.changeContent === this.spuInfo.sku_map_list[k].attrs[j].attr
                ) {
                  // 删除规格
                  this.spuInfo.sku_map_list[k].attrs.splice(j, 1)

                  // 组合规格值
                  let itemValueCombination = ''
                  for (let m = 0; m < this.spuInfo.sku_map_list[k].attrs.length; m++) {
                    itemValueCombination += this.spuInfo.sku_map_list[k].attrs[m].value + '_'
                  }
                  this.spuInfo.sku_map_list[k].combination = itemValueCombination
                }
              }
            }
            for (let v = 0; v < this.spuInfo.sku_map_list.length; v++) {
              let count = 0
              if (empty(this.spuInfo.sku_map_list[v])) {
                continue
              }
              if (currentCombinationValue === this.spuInfo.sku_map_list[v].combination) {
                count++
              }
              // 相同规格的行只保留一条
              if (count > 1) {
                this.spuInfo.sku_map_list.splice(v, 1)
              }
            }
          } else {
            for (let k = 0; k < this.spuInfo.sku_map_list.length; k++) {
              if (empty(this.spuInfo.sku_map_list[k]) || empty(this.spuInfo.sku_map_list[k].attrs)) {
                continue
              }
              const attrs = this.spuInfo.sku_map_list[k]?.attrs || []
              for (let j = 0; j < attrs.length; j++) {
                if (this.empty(attrs[j])) {
                  continue
                }
                if (this.changeContent === attrs[j].value) {
                  // 删除整行
                  this.spuInfo.sku_map_list.splice(k, 1)
                  break // 如果删除了当前行,需要跳出循环
                }
              }
            }
          }
        } else {
          for (let j = 0; j < currentCombination.length; j++) {
            this.spuInfo.sku_map_list[changeIndex].attrs[j].attr = currentCombination[j].attr
            this.spuInfo.sku_map_list[changeIndex].attrs[j].value = currentCombination[j].value
          }
        }
        changeIndex++
        spliceIndex = -1
        // return 之后执行 this.generateCombinations 的逻辑
        return [changeIndex, spliceIndex]
      }
      // 规格和规格值都遍历
      for (var i = 0; i < this.spuAttrs[index].values.length; i++) {
        currentCombination.push({
          attr: this.spuAttrs[index].attr,
          value: this.spuAttrs[index].values[i]
        })
        // 假如有 尺寸 S M ,颜色 黄色 白色
        // 第一次遍历生成 {"attr":"尺寸","value":"S"}
        // 第二次遍历生成 {"attr":"尺寸","value":"S"},{"attr":"颜色","value":"黄色"}组合,然后在渲染到表格的第一行中
        // changeIndex 为行数或者组合数量
        // generateCombinations 自循环,遇到return [changeIndex, spliceIndex]后才往下执行
        const res = this.generateCombinations(type, pIndex, subIndex, changeIndex, spliceIndex, currentCombination, index + 1)
        changeIndex = res[0]
        spliceIndex = res[1]
        currentCombination.pop()
      }
      return [changeIndex, spliceIndex]
    },
    attrSkuMap(type, pIndex, subIndex) {
      this.generateCombinations(type, pIndex, subIndex)
      return
    },
    getAttrValue(item, i) {
      return item.attrs[i].value
    },
    async saveSpu() {
      return await saveSpu(this.spuInfo)
    }
  }
}
</script>
<style scoped lang="scss">
.cate_level1 {
  color: #E6A23C;
}

.attr_select_index {
  margin-bottom: 14px;
}

.attr_options_add, .attr_val_options_add {
  margin-left: -2px;
  font-size: 20px;
  color: #409EFF;
  cursor: pointer;
}

.item {
  padding: 3px;
  //background-color: #fdfdfd;
  //border: solid 1px #eee;
  margin-bottom: 10px;
  cursor: move;
}

.item:hover {
  background-color: #f1f1f1;
  cursor: move;
}

.chosen {
  border: solid 2px #3089dc !important;
}

.attr_options_del, .attr_val_options_del {
  margin-left: -2px;
  font-size: 20px;
  color: #F56C6C;
  cursor: pointer;
}

.tooltip-container {
  position: relative;
  display: inline-block;
}

.tooltip-text {
  visibility: visible;
  width: 120px;
  background-color: black;
  color: #fff;
  text-align: center;
  border-radius: 6px;
  padding: 5px 0;
  position: absolute;
  z-index: 1;
  bottom: 125%; /* Position the tooltip above the icon */
  left: 50%;
  margin-left: -60px;
  opacity: 0;
  transition: opacity 0.3s;
}

.tooltip-container:hover .tooltip-text {
  visibility: visible;
  opacity: 1;
}

.avatar-uploader .el-upload {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}

.avatar-uploader .el-upload:hover {
  border-color: #409EFF;
}

.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 30px;
  height: 30px;
  line-height: 30px;
  text-align: center;
}

.avatar {
  width: 30px;
  height: 30px;
  display: block;
}
</style>

数据结构

{
    "msg": "ok",
    "code": 10000,
    "data": {
        "id": "710432902999261184",
        "goods_name": "spu测试ywz",
        "cate_code": "Lthg98",
        "brand_code": "UpsPhd",
        "supplier_code": "",
        "audit_status": 1,
        "pack_spec": "101",
        "pack_count": "102",
        "unit_name": "103",
        "expire_date": "2024-10-24",
        "images": [
            "https:\/\/img-hyg.hltmsp.com\/member\/image\/20240918\/c7865c97174a4eb92519edd42a98af74851785.png",
            "https:\/\/img-hyg.hltmsp.com\/member\/image\/20240918\/4508beb29e17b950796cc5961a88b520299817.png"
        ],
        "video": "",
        "video_cover": "",
        "is_show_video": 1,
        "description": "<p>spu测试spu测试spu测试<\/p>",
        "sort": 0,
        "is_gift": 1,
        "goods_type": 1,
        "sales_count": 0,
        "is_sales_amount_limit": 1,
        "recommendation": "spu测试spu测试spu测试",
        "is_search": 1,
        "is_pre_sales": 1,
        "is_expire": 1,
        "is_suite": 1,
        "merchant_id": 1,
        "business_type": 1,
        "keep_desc_tpl_id": 2,
        "notice_desc_tpl_id": 0,
        "del_status": 1,
        "create_time": "2024-10-31 10:06:43",
        "update_time": "2024-10-31 10:06:43",
        "sku_map_list": [
            {
                "id": 710432903292862464,
                "spu_id": "710432902999261184",
                "goods_code": "",
                "barcode": "spu_1234561345678ywz1",
                "image": "https:\/\/img-hyg.hltmsp.com\/member\/image\/20240918\/c7865c97174a4eb92519edd42a98af74851785.png",
                "search_weight": 0,
                "sort": 0,
                "sales_count": 0,
                "price": "99.00",
                "market_price": "199.00",
                "cost_price": "0.00",
                "create_time": "2024-10-31 10:06:43",
                "update_time": "2024-10-31 10:06:43",
                "attrs": [
                    {
                        "attr": "尺寸",
                        "value": "尺寸1"
                    },
                    {
                        "attr": "颜色",
                        "value": "颜色1"
                    }
                ]
            },
            {
                "id": 710432903292862465,
                "spu_id": "710432902999261184",
                "goods_code": "",
                "barcode": "spu_12345631345678ywz1",
                "image": "https:\/\/img-hyg.hltmsp.com\/member\/image\/20240918\/c7865c97174a4eb92519edd42a98af74851785.png",
                "search_weight": 0,
                "sort": 0,
                "sales_count": 0,
                "price": "99.00",
                "market_price": "199.00",
                "cost_price": "0.00",
                "create_time": "2024-10-31 10:06:43",
                "update_time": "2024-10-31 10:06:43",
                "attrs": [
                    {
                        "attr": "尺寸",
                        "value": "尺寸1"
                    },
                    {
                        "attr": "颜色",
                        "value": "颜色2"
                    }
                ]
            },
            {
                "id": 710432903292862466,
                "spu_id": "710432902999261184",
                "goods_code": "",
                "barcode": "spu_123456451345678ywz1",
                "image": "https:\/\/img-hyg.hltmsp.com\/member\/image\/20240918\/c7865c97174a4eb92519edd42a98af74851785.png",
                "search_weight": 0,
                "sort": 0,
                "sales_count": 0,
                "price": "99.00",
                "market_price": "199.00",
                "cost_price": "0.00",
                "create_time": "2024-10-31 10:06:43",
                "update_time": "2024-10-31 10:06:43",
                "attrs": [
                    {
                        "attr": "尺寸",
                        "value": "尺寸2"
                    },
                    {
                        "attr": "颜色",
                        "value": "颜色1"
                    }
                ]
            },
            {
                "id": 710432903292862467,
                "spu_id": "710432902999261184",
                "goods_code": "",
                "barcode": "spu_123456561345678ywz1",
                "image": "https:\/\/img-hyg.hltmsp.com\/member\/image\/20240918\/c7865c97174a4eb92519edd42a98af74851785.png",
                "search_weight": 0,
                "sort": 0,
                "sales_count": 0,
                "price": "99.00",
                "market_price": "199.00",
                "cost_price": "0.00",
                "create_time": "2024-10-31 10:06:43",
                "update_time": "2024-10-31 10:06:43",
                "attrs": [
                    {
                        "attr": "尺寸",
                        "value": "尺寸2"
                    },
                    {
                        "attr": "颜色",
                        "value": "颜色2"
                    }
                ]
            }
        ],
        "spu_attrs": [
            {
                "attr": "尺寸",
                "values": [
                    "尺寸1",
                    "尺寸2"
                ]
            },
            {
                "attr": "颜色",
                "values": [
                    "颜色1",
                    "颜色2"
                ]
            }
        ]
    },
    "log_id": "710498316961951744"
}
<think>嗯,用户的问题是关于如何在Element UI的表格中,当表头是动态的情况下,在第列添加输入框。首先,我需要理解用户的具体需求。他们可能有动态生成的表头,也就是说表格的列不是固定的,可能会根据数据或其他条件变化。用户希望在这种动态表格的第列每行都添加输入框。 首先,我应该回顾Element UI的表格组件的基本用法。Element的el-table组件通常是通过配置columns来定义表头的,每个column对应列。如果表头是动态的,那么columns可能是动态生成的数组,比如从后端获取或根据用户选择变化。 用户的问题是在第列添加输入框,不管表头如何变化,第列始终要有输入框。这时候,我需要考虑如何固定第列,或者动态插入列作为第列。可能有两种情况:种是原有的动态表头已经包括第列,另种是用户希望在所有动态生成的其他列之前添加个固定的输入框列。 接下来,我需要想到在Element UI中如何动态添加列。通常,el-table的列是通过循环columns数组生成的,所以如果要在最前面添加列,可以在columns数组的开头插入个新的列配置对象。 然后,这个新增的列需要渲染输入框。在Element表格中,自定义列的内容可以使用scoped slot。也就是在column配置中使用<template slot-scope="scope">来定义内容。对于动态列,可能需要使用作用域插槽的写法,或者在columns数组中定义scopedSlots属性。 另外,输入框需要绑定到数据上,所以需要确保每行的数据对象有个对应的属性来存储输入框的值。例如,数据中的每个对象可能需要个inputValue字段,然后在el-input中使用v-model绑定到这个字段。 可能的步骤是: 1. 在动态生成的columns数组的最前面插入个固定列,用于显示输入框。 2. 在表格的数据结构中确保每行有对应的字段,比如inputValue。 3. 使用scoped slot在动态列中渲染输入框,并正确绑定数据。 还需要考虑的是,当columns是动态的时候,如何确保新增的固定列不会被重复添加,比如在数据更新时。可能需要在生成columns数组时,每次先检查是否已经存在该固定列,如果没有则添加。 另外,用户可能对Vue的响应式原理不太熟悉,所以在数据绑定方面需要确保使用Vue.set或者确保数据属性是响应式的,这样输入框的值变化才能正确反映到数据中。 可能的代码结构示例: 在模板中,使用el-table,循环columns生成el-table-column。在columns数组的最前面添加个类型为'input'的列配置,然后在el-table-column中使用v-if来判断是否是输入框列,如果是,则渲染输入框,否则渲染其他动态列。 数据部分,columns可能是个计算属性,基于原始动态列数组,前面加上固定的输入列。而表格数据中的每个对象需要有对应的inputValue属性。 最后,可能需要注意样式问题,确保新增的列宽度合适,不影响其他动态列的显示。此外,如果表格需要支持排序、过滤等功能,新增的输入列可能需要禁用这些功能,或者做相应处理。 总结下来,解决方案的关键点是在动态columns数组前添加固定列配置,使用scoped slot渲染输入框,并确保数据绑定正确。同时,处理好动态列和固定列的关系,避免重复添加或数据不致的问题。</think>以下是在Element UI中实现动态表头并在第列添加输入框的详细解决方案: 1. **数据结构准备** ```javascript data() { return { // 动态表头数据(示例) dynamicHeaders: [ { prop: 'name', label: '姓名' }, { prop: 'age', label: '年龄' } ], // 表格数据(示例) tableData: [ { name: '张三', age: 25, inputVal: '' }, { name: '李四', age: 30, inputVal: '' } ] } } ``` 2. **组合完整表头** ```javascript computed: { fullHeaders() { // 在第列插入固定输入框列 return [ { type: 'input', // 自定义类型标识 label: '输入列', width: 150 }, ...this.dynamicHeaders ] } } ``` 3. **表格模板实现** ```html <el-table :data="tableData"> <template v-for="(header, index) in fullHeaders"> <!-- 输入框列 --> <el-table-column v-if="header.type === 'input'" :key="index" :label="header.label" :width="header.width"> <template slot-scope="{ row }"> <el-input v-model="row.inputVal" /> </template> </el-table-column> <!-- 动态列 --> <el-table-column v-else :key="header.prop" :prop="header.prop" :label="header.label"> </el-table-column> </template> </el-table> ``` 实现原理分析: 1. **动态表头处理** - 通过`computed`属性组合完整表头,保证输入框列始终位于首位 - 使用扩展运算符`...`保持原有动态列的响应式特性 2. **输入框绑定** - 使用`slot-scope`获取当前行数据 - 通过`v-model`双向绑定到每行的`inputVal`属性 - 需要提前在数据对象中定义`inputVal`字段保证响应式 3. **扩展性考虑** - 支持动态列的增减变化,自动保持输入框列位置不变 - 可通过修改`fullHeaders`计算逻辑调整输入框列的位置和样式 - 支持自定义输入框类型(如数字输入框、带校验的输入框等) 注意事项: 1. 数据致性 - 新增数据项时需要初始化`inputVal`字段 ```javascript this.tableData.push({ ...newItem, inputVal: '' // 必须初始化 }) ``` 2. 性能优化 - 为动态列设置`key`属性时建议使用唯标识(如`prop`) - 当数据量较大时,可考虑虚拟滚动优化 3. 样式调整 - 输入框列宽度建议固定,避免动态列过多导致挤压 - 可通过`header-class-name`属性自定义表头样式 完整示例可参考:https://element.eleme.cn/#/zh-CN/component/table (动态列部分+自定义列模板)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值