Procházet zdrojové kódy

feat: 完善商品列表与编辑部分

Sun před 3 roky
rodič
revize
d704c15c57
2 změnil soubory, kde provedl 512 přidání a 17 odebrání
  1. 455 0
      src/views/business/goods/add.vue
  2. 57 17
      src/views/business/goods/index.vue

+ 455 - 0
src/views/business/goods/add.vue

@@ -0,0 +1,455 @@
+<template>
+  <div class="app-container goods-add">
+    <el-divider content-position="left">商品信息</el-divider>
+    <el-form ref="addItem" :rules="rules" :model="addData" label-width="120px">
+      <el-row :gutter="40">
+        <el-col :span="23">
+          <el-form-item label="商品名称:" prop="title">
+            <el-input v-model="addData.title" placeholder="请输入商品名称"/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="23">
+          <el-form-item label="商品主图:" prop="picUrl">
+            <!-- <Upload v-model="addData.picUrl" :limit="1" :type="4" :beforeUpload="beforeUpload" /> -->
+            <div class="tip">支持jpg、png格式上传,建议使用尺寸800x800像素以上、大小不超过1M的正方形图片;</div>
+          </el-form-item>
+        </el-col>
+        <el-col :span="23">
+          <el-form-item label="支持盲豆兑换:" prop="exchangeShow">
+            <el-switch
+              v-model="addData.exchangeShow"
+              active-color="#13ce66"
+              inactive-color="#ff4949"
+              :active-value="1"
+              :inactive-value="0"
+              active-text="支持"
+              inactive-text="不支持">
+            </el-switch>
+            <div class="tip">关闭则不再兑换大厅显示,不支持盲豆兑换。</div>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-divider content-position="left">价格库存</el-divider>
+      <el-row :gutter="40">
+        <el-col :span="23">
+          <el-form-item label="启用多SKU:" prop="multiSku">
+            <!-- <el-switch
+              v-model="addData.multiSku"
+              active-color="#13ce66"
+              inactive-color="#ff4949"
+              :active-value="1"
+              :inactive-value="0"
+              active-text="是"
+              inactive-text="否"
+            /> -->
+            <el-radio-group v-model="addData.multiSku">
+              <el-radio-button :label="0">单规格</el-radio-button>
+              <el-radio-button :label="1">多规格</el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row v-if="!multipleSpec" :gutter="40">
+        <el-col :span="11">
+          <el-form-item label="价格:" prop="value">
+            <el-input v-model="addData.value" placeholder="请输入商品价格">
+              <template slot="append">元</template>
+            </el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="兑换价格:" prop="exchangePrice">
+            <el-input v-model="addData.exchangePrice" placeholder="请输入兑换价格" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="11">
+          <el-form-item label="成本:" prop="cost">
+            <el-input v-model="addData.cost" placeholder="请输入商品成本">
+              <template slot="append">元</template>
+            </el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="库存:" prop="quantity">
+            <el-input v-model="addData.quantity" placeholder="请输入商品库存" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="list-group" v-if="multipleSpec">
+      <div style="margin-bottom: 16px">
+        <el-button :plain="true" type="primary" size="small" @click="handleSpecificationShow">添加规格</el-button>
+      </div>
+      <div class="list-group-header">
+        <div class="f2">规格名</div>
+        <div class="f2">规格值</div>
+        <div class="f1 tr">操作</div>
+      </div>
+      <Draggable v-if="products.length > 0" v-model="specifications" :options="dragOptions">
+        <transition-group type="transition" :name="'flip-list'">
+          <div class="list-group-item" v-for="item in specifications" :key="item.specification + item.value">
+            <div class="f2">{{item.specification}}</div>
+            <div class="f2">{{item.value}}</div>
+            <div class="f1 tr"><el-button type="danger" size="mini" @click="handleSpecificationDelete(item)">删除</el-button></div>
+          </div>
+        </transition-group>
+      </Draggable>
+      <div v-else class="list-empty">无内容</div>
+    </div>
+    <el-dialog :visible.sync="specVisiable" title="设置规格" :close-on-click-modal="false" width="400px">
+      <el-form ref="specForm" :model="specForm" label-width="80px">
+        <el-form-item label="规格名:" prop="specification">
+          <el-input v-model="specForm.specification"/>
+        </el-form-item>
+        <el-form-item label="规格值:" prop="value">
+          <el-input v-model="specForm.value"/>
+          <div class="tip">多个规格值请用逗号隔开</div>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="specVisiable = false">取消</el-button>
+        <el-button type="primary" @click="handleSpecificationAdd">确定</el-button>
+      </div>
+    </el-dialog>
+    <br>
+    <div class="list-group" v-if="multipleSpec">
+      <div class="list-group-header">
+        <div class="f2">商品规格</div>
+        <div class="f1">商品售价</div>
+        <div class="f1">折扣价格</div>
+        <div class="f1">商品数量</div>
+        <div class="f2">商品图片</div>
+        <div class="f1 tr">操作</div>
+      </div>
+      <Draggable v-if="products.length > 0" v-model="products" :options="dragOptions">
+        <transition-group type="transition" :name="'flip-list'">
+          <div class="list-group-item" v-for="item in products" :key="item.specificationList.toString()">
+            <div class="f2">{{item.specificationList.toString()}}</div>
+            <div class="f1">{{item.price}}</div>
+            <div class="f1">{{item.discountPrice}}</div>
+            <div class="f1">{{item.number}}</div>
+            <div class="f2"><img v-if="item.imgUrl" :src="IMG_URL+item.imgUrl" width="40"></div>
+            <div class="f1 tr"><el-button type="primary" size="mini" @click="handleProductShow(item)">设置</el-button></div>
+          </div>
+        </transition-group>
+      </Draggable>
+      <div v-else class="list-empty">无内容</div>
+    </div>
+
+    <el-dialog :visible.sync="productVisiable" title="设置商品" :close-on-click-modal="false" width="400px">
+      <el-form ref="productForm" :model="productForm" label-width="100px">
+        <el-form-item label="商品规格:" prop="specificationList">
+          <el-tag v-for="tag in productForm.specificationList" :key="tag">
+            {{ tag }}
+          </el-tag>
+        </el-form-item>
+        <el-form-item label="商品原价:" prop="price">
+          <el-input-number v-model="productForm.price"/>
+        </el-form-item>
+        <el-form-item label="折扣价:" prop="discountPrice">
+          <el-input-number v-model="productForm.discountPrice"/>
+        </el-form-item>
+        <el-form-item label="商品数量:" prop="number">
+          <el-input-number v-model="productForm.number"/>
+        </el-form-item>
+        <el-form-item label="商品图片:" prop="imgUrl">
+          <Upload v-model="productForm.imgUrl" :limit="1" :type="4" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="productVisiable = false">取消</el-button>
+        <el-button type="primary" @click="handleProductEdit">确定</el-button>
+      </div>
+    </el-dialog>
+    <br>
+    <el-row>
+      <el-col :span="24" style="text-align: right">
+        <el-button type="primary" @click="updateItem()">发布</el-button>
+        <el-button type="info" @click="$router.replace('/goods/list')">取消</el-button>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<script>
+import Draggable from 'vuedraggable'
+import { getGoodsDetail, addGoods } from '@/api/business/goods'
+
+export default {
+  components: {
+    Draggable
+  },
+  data() {
+    return {
+      id: this.$route.query.id,
+      addData: {
+        multiSku: 0
+      },
+      rules: {
+        title: [{ required: true, message: '请输入商品名称', trigger: 'blur' }]
+      },
+
+
+      dragOptions: {
+        animation: 100,
+      },
+      specifications: [],
+      products: [],
+      
+      IMG_URL: process.env.IMG_URL,
+      
+      specVisiable: false, // 规格弹窗
+      specForm: { specification: '', value: '', imgUrl: '' }, // 规格暂存
+      productVisiable: false,
+      productForm: { id: 0, specificationList: [], price: 0.00, discountPrice: 0.00, number: 0, imgUrl: '' },
+    }
+  },
+  computed: {
+    multipleSpec() {
+      return this.addData.multiSku === 1
+    }
+  },
+  created() {
+    if (this.id) {
+      getGoodsDetail(this.id).then(res => {
+        this.addData = res.data
+      })
+    }
+  },
+  methods: {
+    beforeUpload(e) {
+      if (e.size > 1024000) {
+        this.$message({
+          message: '图片过大,请调整大小确保不超过1M!',
+          type: 'warning'
+        });
+        return false
+      }
+    },
+    handleSpecificationShow() {
+      this.specForm = { specification: '', value: '', imgUrl: '' }
+      this.specVisiable = true
+    },
+    handleSpecificationDelete(row) {
+      const index = this.specifications.indexOf(row)
+      this.specifications.splice(index, 1)
+      this.specToProduct()
+    },
+    specToProduct() {
+      if (this.specifications.length === 0) {
+        return
+      }
+      // 根据specifications创建临时规格列表
+      var specValues = []
+      var spec = this.specifications[0].specification
+      var values = []
+      values.push(0)
+
+      for (var i = 1; i < this.specifications.length; i++) {
+        const aspec = this.specifications[i].specification
+
+        if (aspec === spec) {
+          values.push(i)
+        } else {
+          specValues.push(values)
+          spec = aspec
+          values = []
+          values.push(i)
+        }
+      }
+      specValues.push(values)
+
+      // 根据临时规格列表生产商品规格
+      // 算法基于 https://blog.csdn.net/tyhj_sf/article/details/53893125
+      var productsIndex = 0
+      var products = []
+      var combination = []
+      var n = specValues.length
+      for (var s = 0; s < n; s++) {
+        combination[s] = 0
+      }
+      var index = 0
+      var isContinue = false
+      do {
+        var specifications = []
+        for (var x = 0; x < n; x++) {
+          var z = specValues[x][combination[x]]
+          specifications.push(this.specifications[z].value)
+        }
+        products[productsIndex] = {
+          id: productsIndex,
+          specificationList: specifications,
+          price: 0.00,
+          discountPrice: 0.00,
+          number: 0,
+          imgUrl: ''
+        }
+        productsIndex++
+
+        index++
+        combination[n - 1] = index
+        for (var j = n - 1; j >= 0; j--) {
+          if (combination[j] >= specValues[j].length) {
+            combination[j] = 0
+            index = 0
+            if (j - 1 >= 0) {
+              combination[j - 1] = combination[j - 1] + 1
+            }
+          }
+        }
+        isContinue = false
+        for (var p = 0; p < n; p++) {
+          if (combination[p] !== 0) {
+            isContinue = true
+          }
+        }
+      } while (isContinue)
+
+      this.products = products
+    },
+    handleSpecificationAdd() {
+      const valueArr = this.specForm.value.replace(/,/ig,',').replace(/\s*/g,"").split(',') // 替换所有的中文逗号为英文逗号并去空格
+      valueArr.forEach(element => {
+        let index = this.specifications.length - 1
+        for (var i = 0; i < this.specifications.length; i++) {
+          const v = this.specifications[i]
+          if (v.specification === this.specForm.specification) {
+            if (v.value !== element) {
+              index = i
+            }
+          }
+        }
+        this.specifications.splice(index + 1, 0, {
+          specification: this.specForm.specification,
+          value: element,
+          imgUrl: ''
+        })
+        
+      });
+      this.specToProduct()
+      this.specVisiable = false
+    },
+    handleProductShow(row) {
+      this.productForm = Object.assign({}, row)
+      this.productVisiable = true
+    },
+    handleProductEdit() {
+      if (this.multipleSpec) {
+        for (var i = 0; i < this.products.length; i++) {
+          const v = this.products[i]
+          if (v.id === this.productForm.id) {
+            this.products.splice(i, 1, this.productForm)
+            break
+          }
+        }
+      } else {
+        for (var i = 0; i < this.productSingle.length; i++) {
+          const v = this.productSingle[i]
+          if (v.id === this.productForm.id) {
+            this.productSingle.splice(i, 1, this.productForm)
+            break
+          }
+        }
+      }
+      
+      this.productVisiable = false
+    },
+
+
+    updateItem() {
+      this.$refs.addItem.validate((valid, items) => {
+        if (valid) {
+          const action = this.addData.id ? editGoods : publishProduct
+          action({
+            product: this.addData,
+            appProductCommodities: this.multipleSpec? this.products:this.productSingle
+          }).then(res => {
+            if (res.code === 0) {
+              this.$message({
+                message: this.addData.id ? '修改成功!' : '添加成功!',
+                type: 'success'
+              })
+              this.$router.replace('/goods/list')
+            }
+          })
+        } else {
+          if (items && Object.keys(items).length>0) {
+            this.$message({
+              message: items[Object.keys(items)[0]][0].message,
+              type: 'warning'
+            })
+          }
+          
+        }
+      })
+    },
+
+    
+  }
+}
+</script>
+<style lang="scss">
+.goods-add {
+  .tip {
+    font-size: 12px;
+    color: #999;
+  }
+  .el-divider {
+    .is-left {
+      color: #409EFF;
+      font-size: 20px;
+      font-weight: bolder;
+      left: 10px;
+    }
+  }
+  .flip-list-move {
+    transition: transform 0.5s;
+  }
+  .no-move {
+    transition: transform 0s;
+  }
+  .ghost {
+    opacity: 0.5;
+    background: #c8ebfb;
+  }
+  .list-group {
+    min-height: 120px;
+    padding: 20px;
+    border: 1px solid #EEE;
+  }
+  .list-empty {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 14px;
+    color: #888;
+    height: 100px;
+  }
+  .list-group-item, .list-group-header {
+    padding: 0 16px;
+    height: 40px;
+    border-bottom: 1px solid #EBEEF5;
+    cursor: move;
+    display: flex;
+    align-items: center;
+    font-size: 14px;
+    color: #606266;
+    justify-content: space-between;
+    .f1 {
+      flex: 1;
+    }
+    .f2 {
+      flex: 2;
+    }
+    .tr {
+      text-align: right;
+    }
+  }
+  .list-group-header {
+    cursor: default;
+    background: #EBEEF5;
+  }
+  .list-group-item:hover {
+    background: #F3F6F9;
+  }
+}
+</style>

+ 57 - 17
src/views/business/goods/index.vue

@@ -19,8 +19,8 @@
       </el-form-item>
       <el-form-item label="上架状态" prop="status">
         <el-select v-model="queryParams.status" placeholder="请选择商品状态" clearable>
-          <el-option label="上架" value="1" />
-          <el-option label="下架" value="2" />
+          <el-option label="上架" value="on" />
+          <el-option label="下架" value="off" />
         </el-select>
       </el-form-item>
       <el-form-item>
@@ -31,31 +31,39 @@
     <el-row :gutter="10" class="mb8">
       <el-col :span="10">
         <el-button v-hasPermi="['business:goods:add']" type="primary" icon="el-icon-plus" size="mini" @click="$router.push('/goods/add')">添加商品</el-button>
-        <el-button v-hasPermi="['business:goods:on']" type="primary" plain size="mini">上架</el-button>
-        <el-button v-hasPermi="['business:goods:off']" type="primary" plain size="mini">下架</el-button>
-        <el-button v-hasPermi="['business:goods:remove']" type="danger" plain size="mini">删除</el-button>
+        <!-- <el-button v-hasPermi="['business:goods:on']" type="primary" plain size="mini">上架</el-button> -->
+        <!-- <el-button v-hasPermi="['business:goods:off']" type="primary" plain size="mini">下架</el-button> -->
+        <!-- <el-button v-hasPermi="['business:goods:remove']" type="danger" plain size="mini">删除</el-button> -->
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
     <el-table v-loading="loading" :data="tableData">
-      <el-table-column label="商品ID" prop="id" min-width="70" />
-      <el-table-column label="商品图片" prop="title" />
+      <el-table-column label="商品ID" prop="goodsId" width="60" />
+      <el-table-column label="商品图片" prop="picUrl" align="center" width="80">
+        <template slot-scope="{row}">
+          <a target="_black" v-if="row.picUrl" :href="`https://mp-public-test-1307117429.cos.ap-shanghai.myqcloud.com/${row.picUrl}`"><img :src="`https://mp-public-test-1307117429.cos.ap-shanghai.myqcloud.com/${row.picUrl}`" style="max-height: 37px;max-width: 54px"></a>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
       <el-table-column label="商品名称" prop="title" />
-      <el-table-column label="价格" prop="amount" />
-      <el-table-column label="兑换盲豆" prop="quiety" />
-      <el-table-column label="销量" prop="lastMessageTime" />
-      <el-table-column label="库存" prop="lastMessageTime" />
+      <el-table-column label="价格" prop="value" />
+      <el-table-column label="兑换盲豆" prop="exchangePrice" />
+      <el-table-column label="销量" prop="exchangedQty" />
+      <el-table-column label="库存" prop="quantity" />
       <el-table-column label="状态" prop="status">
         <template slot-scope="{row}">
-          {{ JSON.parse(row.status).desc }}
+          <el-tag v-if="row.status === 'on'" type="success">上架</el-tag>
+          <el-tag v-else-if="row.status === 'off'" type="info">下架</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="排序" prop="lastMessageTime" />
+      <el-table-column label="排序" prop="sort" />
       <el-table-column fixed="right" align="right" label="操作" width="140">
         <template slot-scope="{row}">
-          <el-button v-hasPermi="['business:goods:query']" type="text">查看</el-button>
-          <el-button v-hasPermi="['business:goods:edit']" type="text">编辑</el-button>
-          <el-button v-hasPermi="['business:goods:off']" type="text">下架</el-button>
+          <!-- <el-button v-hasPermi="['business:goods:query']" type="text">查看</el-button> -->
+          <el-button v-hasPermi="['business:goods:edit']" type="text" @click="$router.push('/goods/edit?id=' + row.goodsId)">编辑</el-button>
+          <el-button v-if="row.status === 'off'" v-hasPermi="['business:goods:on']" type="text" @click="setStatus(row, 'on')">上架</el-button>
+          <el-button v-if="row.status === 'on'" v-hasPermi="['business:goods:off']" type="text" @click="setStatus(row, 'off')">下架</el-button>
+          <el-button v-if="row.status === 'off'" v-hasPermi="['business:coupon:remove']" class="del" type="text" @click="del(row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -63,7 +71,7 @@
   </div>
 </template>
 <script>
-import { getGoodsList } from '@/api/business/goods'
+import { getGoodsList, delGoods, setGoodsStatus } from '@/api/business/goods'
 export default {
   name: 'GoodsList',
   data() {
@@ -71,6 +79,7 @@ export default {
       loading: false,
       showSearch: true,
       queryParams: {},
+      tableData: [],
       total: 0
     }
   },
@@ -95,6 +104,37 @@ export default {
       }).catch(() => {
         this.loading = false
       })
+    },
+    setStatus(item, status) {
+      this.$confirm(`确认${status === 'on' ? '上架' : '下架'}商品 “${item.title}” 吗?`, `${status === 'on' ? '上架' : '下架'}商品`, {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        setGoodsStatus({
+          goodsId: item.goodsId,
+          status
+        }).then(res => {
+          if (res.code === 0) {
+            this.$message.success('操作已完成!')
+            this.getList()
+          }
+        })
+      })
+    },
+    del(item) {
+      this.$confirm(`确认删除商品 “${item.title}” 吗?`, '删除商品', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        delGoods(item.goodsId).then(res => {
+          if (res.code === 0) {
+            this.$message.success('操作已完成!')
+            this.getList()
+          }
+        })
+      })
     }
   }
 }