ソースを参照

feat[litemall-admin, litemall-wx]: 支持专题商品添加和编辑

Junling Bu 6 年 前
コミット
6fc6cb6c6e

+ 19 - 1
litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminTopicController.java

@@ -7,7 +7,9 @@ import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc;
 import org.linlinjava.litemall.core.util.ResponseUtil;
 import org.linlinjava.litemall.core.validator.Order;
 import org.linlinjava.litemall.core.validator.Sort;
+import org.linlinjava.litemall.db.domain.LitemallGoods;
 import org.linlinjava.litemall.db.domain.LitemallTopic;
+import org.linlinjava.litemall.db.service.LitemallGoodsService;
 import org.linlinjava.litemall.db.service.LitemallTopicService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.StringUtils;
@@ -16,7 +18,10 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.validation.constraints.NotNull;
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 @RestController
 @RequestMapping("/admin/topic")
@@ -26,6 +31,8 @@ public class AdminTopicController {
 
     @Autowired
     private LitemallTopicService topicService;
+    @Autowired
+    private LitemallGoodsService goodsService;
 
     @RequiresPermissions("admin:topic:list")
     @RequiresPermissionsDesc(menu={"推广管理" , "专题管理"}, button="查询")
@@ -72,7 +79,18 @@ public class AdminTopicController {
     @GetMapping("/read")
     public Object read(@NotNull Integer id) {
         LitemallTopic topic = topicService.findById(id);
-        return ResponseUtil.ok(topic);
+        Integer[] goodsIds = topic.getGoods();
+        List<LitemallGoods> goodsList = null;
+        if(goodsIds == null || goodsIds.length == 0){
+            goodsList = new ArrayList<>();
+        }
+        else{
+            goodsList = goodsService.queryByIds(goodsIds);
+        }
+        Map<String, Object> data = new HashMap<>(2);
+        data.put("topic", topic);
+        data.put("goodsList", goodsList);
+        return ResponseUtil.ok(data);
     }
 
     @RequiresPermissions("admin:topic:update")

+ 2 - 2
litemall-admin/src/api/topic.js

@@ -16,11 +16,11 @@ export function createTopic(data) {
   })
 }
 
-export function readTopic(data) {
+export function readTopic(query) {
   return request({
     url: '/topic/read',
     method: 'get',
-    data
+    params: query
   })
 }
 

+ 22 - 0
litemall-admin/src/router/index.js

@@ -332,6 +332,28 @@ export const asyncRouterMap = [
         }
       },
       {
+        path: 'topic-create',
+        component: () => import('@/views/promotion/topicCreate'),
+        name: 'topicCreate',
+        meta: {
+          perms: ['POST /admin/topic/create'],
+          title: '专题创建',
+          noCache: true
+        },
+        hidden: true
+      },
+      {
+        path: 'topic-edit',
+        component: () => import('@/views/promotion/topicEdit'),
+        name: 'topicEdit',
+        meta: {
+          perms: ['GET /admin/topic/read', 'POST /admin/topic/update'],
+          title: '专题编辑',
+          noCache: true
+        },
+        hidden: true
+      },
+      {
         path: 'groupon-rule',
         component: () => import('@/views/promotion/grouponRule'),
         name: 'grouponRule',

+ 5 - 183
litemall-admin/src/views/promotion/topic.vue

@@ -35,7 +35,7 @@
 
       <el-table-column align="center" label="阅读数量" prop="readCount"/>
 
-      <el-table-column align="center" label="操作" min-width="200" class-name="small-padding fixed-width">
+      <el-table-column align="center" label="操作" min-width="100" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button v-permission="['POST /admin/topic/update']" type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
           <el-button v-permission="['POST /admin/topic/delete']" type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
@@ -49,44 +49,6 @@
       <back-to-top :visibility-height="100" />
     </el-tooltip>
 
-    <!-- 添加或修改对话框 -->
-    <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
-      <el-form ref="dataForm" :rules="rules" :model="dataForm" status-icon label-position="left" label-width="100px" style="width: 400px; margin-left:50px;">
-        <el-form-item label="专题标题" prop="title">
-          <el-input v-model="dataForm.title"/>
-        </el-form-item>
-        <el-form-item label="专题子标题" prop="subtitle">
-          <el-input v-model="dataForm.subtitle"/>
-        </el-form-item>
-        <el-form-item label="专题图片" prop="picUrl">
-          <el-upload
-            :headers="headers"
-            :action="uploadPath"
-            :show-file-list="false"
-            :on-success="uploadPicUrl"
-            class="avatar-uploader"
-            accept=".jpg,.jpeg,.png,.gif">
-            <img v-if="dataForm.picUrl" :src="dataForm.picUrl" class="avatar">
-            <i v-else class="el-icon-plus avatar-uploader-icon"/>
-          </el-upload>
-        </el-form-item>
-        <el-form-item style="width: 700px;" label="专题内容">
-          <editor :init="editorInit" v-model="dataForm.content"/>
-        </el-form-item>
-        <el-form-item label="商品低价" prop="price">
-          <el-input v-model="dataForm.price"/>
-        </el-form-item>
-        <el-form-item label="阅读量" prop="readCount">
-          <el-input v-model="dataForm.readCount"/>
-        </el-form-item>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="dialogFormVisible = false">取消</el-button>
-        <el-button v-if="dialogStatus=='create'" type="primary" @click="createData">确定</el-button>
-        <el-button v-else type="primary" @click="updateData">确定</el-button>
-      </div>
-    </el-dialog>
-
   </div>
 </template>
 
@@ -120,19 +82,15 @@
 </style>
 
 <script>
-import { listTopic, createTopic, updateTopic, deleteTopic } from '@/api/topic'
-import { createStorage, uploadPath } from '@/api/storage'
+import { listTopic, deleteTopic } from '@/api/topic'
 import BackToTop from '@/components/BackToTop'
-import Editor from '@tinymce/tinymce-vue'
 import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
-import { getToken } from '@/utils/auth'
 
 export default {
   name: 'Topic',
-  components: { BackToTop, Editor, Pagination },
+  components: { BackToTop, Pagination },
   data() {
     return {
-      uploadPath,
       list: [],
       total: 0,
       listLoading: true,
@@ -143,65 +101,6 @@ export default {
         subtitle: undefined,
         sort: 'add_time',
         order: 'desc'
-      },
-      dataForm: {
-        id: undefined,
-        titile: undefined,
-        subtitle: undefined,
-        picUrl: undefined,
-        content: '',
-        price: undefined,
-        readCount: undefined,
-        goods: []
-      },
-      contentDetail: '',
-      contentDialogVisible: false,
-      dialogFormVisible: false,
-      dialogStatus: '',
-      textMap: {
-        update: '编辑',
-        create: '创建'
-      },
-      rules: {
-        title: [
-          { required: true, message: '专题标题不能为空', trigger: 'blur' }
-        ],
-        subtitle: [
-          { required: true, message: '专题子标题不能为空', trigger: 'blur' }
-        ],
-        content: [
-          { required: true, message: '专题内容不能为空', trigger: 'blur' }
-        ]
-      },
-      downloadLoading: false,
-      editorInit: {
-        language: 'zh_CN',
-        convert_urls: false,
-        plugins: [
-          'advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools importcss insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount'
-        ],
-        toolbar: [
-          'searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent  blockquote undo redo removeformat subscript superscript code codesample',
-          'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen'
-        ],
-        images_upload_handler: function(blobInfo, success, failure) {
-          const formData = new FormData()
-          formData.append('file', blobInfo.blob())
-          createStorage(formData)
-            .then(res => {
-              success(res.data.data.url)
-            })
-            .catch(() => {
-              failure('上传失败,请重新上传')
-            })
-        }
-      }
-    }
-  },
-  computed: {
-    headers() {
-      return {
-        'X-Litemall-Admin-Token': getToken()
       }
     }
   },
@@ -227,88 +126,11 @@ export default {
       this.listQuery.page = 1
       this.getList()
     },
-    resetForm() {
-      this.dataForm = {
-        id: undefined,
-        titile: undefined,
-        subtitle: undefined,
-        picUrl: undefined,
-        content: '',
-        price: undefined,
-        readCount: undefined,
-        goods: []
-      }
-    },
     handleCreate() {
-      this.resetForm()
-      this.dialogStatus = 'create'
-      this.dialogFormVisible = true
-      this.$nextTick(() => {
-        this.$refs['dataForm'].clearValidate()
-      })
-    },
-    uploadPicUrl: function(response) {
-      this.dataForm.picUrl = response.data.url
-    },
-    createData() {
-      this.$refs['dataForm'].validate(valid => {
-        if (valid) {
-          createTopic(this.dataForm)
-            .then(response => {
-              this.list.unshift(response.data.data)
-              this.dialogFormVisible = false
-              this.$notify.success({
-                title: '成功',
-                message: '创建专题成功'
-              })
-            })
-            .catch(response => {
-              this.$notify.error({
-                title: '失败',
-                message: response.data.errmsg
-              })
-            })
-        }
-      })
-    },
-    showContent(content) {
-      this.contentDetail = content
-      this.contentDialogVisible = true
+      this.$router.push({ path: '/promotion/topic-create' })
     },
     handleUpdate(row) {
-      this.dataForm = Object.assign({}, row)
-      this.dialogStatus = 'update'
-      this.dialogFormVisible = true
-      this.$nextTick(() => {
-        this.$refs['dataForm'].clearValidate()
-      })
-    },
-    updateData() {
-      this.$refs['dataForm'].validate(valid => {
-        if (valid) {
-          updateTopic(this.dataForm)
-            .then(() => {
-              for (const v of this.list) {
-                if (v.id === this.dataForm.id) {
-                  const index = this.list.indexOf(v)
-                  this.list.splice(index, 1, this.dataForm)
-                  break
-                }
-              }
-              this.dialogFormVisible = false
-              this.$notify.success({
-                title: '成功',
-                message: '更新专题成功'
-              })
-            })
-            .catch(response => {
-              this.$notify.error({
-                title: '失败',
-                message: response.data.errmsg
-              })
-            })
-        }
-      })
+      this.$router.push({ path: '/promotion/topic-edit', query: { id: row.id }})
     },
     handleDelete(row) {
       deleteTopic(row)

+ 289 - 0
litemall-admin/src/views/promotion/topicCreate.vue

@@ -0,0 +1,289 @@
+<template>
+  <div class="app-container">
+
+    <el-form ref="topic" :rules="rules" :model="topic" status-icon label-position="left" label-width="100px" style="width: 800px; margin-left:50px;">
+      <el-form-item label="专题标题" prop="title">
+        <el-input v-model="topic.title"/>
+      </el-form-item>
+      <el-form-item label="专题子标题" prop="subtitle">
+        <el-input v-model="topic.subtitle"/>
+      </el-form-item>
+      <el-form-item label="专题图片" prop="picUrl">
+        <el-upload
+          :headers="headers"
+          :action="uploadPath"
+          :show-file-list="false"
+          :on-success="uploadPicUrl"
+          class="avatar-uploader"
+          accept=".jpg,.jpeg,.png,.gif">
+          <img v-if="topic.picUrl" :src="topic.picUrl" class="avatar">
+          <i v-else class="el-icon-plus avatar-uploader-icon"/>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="专题内容" prop="content">
+        <editor :init="editorInit" v-model="topic.content"/>
+      </el-form-item>
+      <el-form-item label="商品低价" prop="price">
+        <el-input v-model="topic.price"/>
+      </el-form-item>
+      <el-form-item label="阅读量" prop="readCount">
+        <el-input v-model="topic.readCount"/>
+      </el-form-item>
+      <el-form-item label="专题商品" prop="goods">
+        <el-button style="float:right;" size="mini" type="primary" @click="handleCreate()">创建商品</el-button>
+
+        <!-- 查询结果 -->
+        <el-table :data="goodsList" border fit highlight-current-row>
+
+          <el-table-column align="center" label="商品ID" prop="id"/>
+          <el-table-column align="center" property="picUrl" label="图片">
+            <template slot-scope="scope">
+              <img :src="scope.row.picUrl" width="60">
+            </template>
+          </el-table-column>
+          <el-table-column align="center" label="商品名称" prop="name"/>
+          <el-table-column align="center" label="商品介绍" prop="brief"/>
+          <el-table-column align="center" label="操作" class-name="small-padding fixed-width">
+            <template slot-scope="scope">
+              <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-form-item>
+
+    </el-form>
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="handleCancel">取消</el-button>
+      <el-button type="primary" @click="handleConfirm">确定</el-button>
+    </div>
+
+    <el-dialog :visible.sync="addVisiable" title="添加商品">
+      <div class="search">
+        <el-input v-model="listQuery.goodsSn" clearable class="filter-item" style="width: 200px;" placeholder="请输入商品编号"/>
+        <el-input v-model="listQuery.name" clearable class="filter-item" style="width: 200px;" placeholder="请输入商品名称"/>
+        <el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">查找</el-button>
+        <el-table v-loading="listLoading" :data="list" element-loading-text="正在查询中。。。" border fit highlight-current-row @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55"/>
+          <el-table-column align="center" label="商品ID" prop="id"/>
+          <el-table-column align="center" property="picUrl" label="图片">
+            <template slot-scope="scope">
+              <img :src="scope.row.picUrl" width="40">
+            </template>
+          </el-table-column>
+          <el-table-column align="center" label="商品名称" prop="name"/>
+        </el-table>
+        <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
+
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="addVisiable = false">取消</el-button>
+        <el-button type="primary" @click="confirmAdd">确定</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<style>
+.el-dialog {
+  width: 800px;
+}
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #20a0ff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 120px;
+  height: 120px;
+  line-height: 120px;
+  text-align: center;
+}
+.avatar {
+  width: 145px;
+  height: 145px;
+  display: block;
+}
+</style>
+
+<script>
+import { createTopic } from '@/api/topic'
+import { listGoods } from '@/api/goods'
+import { createStorage, uploadPath } from '@/api/storage'
+import BackToTop from '@/components/BackToTop'
+import Editor from '@tinymce/tinymce-vue'
+import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
+import { getToken } from '@/utils/auth'
+
+export default {
+  name: 'TopicEdit',
+  components: { BackToTop, Editor, Pagination },
+  data() {
+    return {
+      uploadPath,
+      id: 0,
+      topic: {
+        goods: []
+      },
+      goodsList: [],
+      addVisiable: false,
+      list: [],
+      total: 0,
+      listLoading: false,
+      listQuery: {
+        page: 1,
+        limit: 5,
+        id: undefined,
+        name: undefined,
+        sort: 'add_time',
+        order: 'desc'
+      },
+      selectedlist: [],
+      rules: {
+        title: [
+          { required: true, message: '专题标题不能为空', trigger: 'blur' }
+        ],
+        subtitle: [
+          { required: true, message: '专题子标题不能为空', trigger: 'blur' }
+        ],
+        content: [
+          { required: true, message: '专题内容不能为空', trigger: 'blur' }
+        ],
+        price: [
+          { required: true, message: '专题低价不能为空', trigger: 'blur' }
+        ]
+      },
+      editorInit: {
+        language: 'zh_CN',
+        convert_urls: false,
+        height: 500,
+        plugins: [
+          'advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools importcss insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount'
+        ],
+        toolbar: [
+          'searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent  blockquote undo redo removeformat subscript superscript code codesample',
+          'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen'
+        ],
+        images_upload_handler: function(blobInfo, success, failure) {
+          const formData = new FormData()
+          formData.append('file', blobInfo.blob())
+          createStorage(formData)
+            .then(res => {
+              success(res.data.data.url)
+            })
+            .catch(() => {
+              failure('上传失败,请重新上传')
+            })
+        }
+      }
+    }
+  },
+  computed: {
+    headers() {
+      return {
+        'X-Litemall-Admin-Token': getToken()
+      }
+    }
+  },
+  created() {
+  },
+  methods: {
+    getList() {
+      this.listLoading = true
+      listGoods(this.listQuery).then(response => {
+        this.list = response.data.data.list
+        this.total = response.data.data.total
+        this.listLoading = false
+      }).catch(() => {
+        this.list = []
+        this.total = 0
+        this.listLoading = false
+      })
+    },
+    handleFilter() {
+      this.listQuery.page = 1
+      this.getList()
+    },
+    handleSelectionChange(val) {
+      this.selectedlist = val
+    },
+    uploadPicUrl: function(response) {
+      this.topic.picUrl = response.data.url
+    },
+    handleCreate() {
+      this.listQuery = {
+        page: 1,
+        limit: 5,
+        id: undefined,
+        name: undefined,
+        sort: 'add_time',
+        order: 'desc'
+      }
+      this.list = []
+      this.total = 0
+      this.selectedlist = []
+      this.addVisiable = true
+    },
+    confirmAdd() {
+      const newGoodsIds = []
+      const newGoodsList = []
+      this.selectedlist.forEach(item => {
+        const id = item.id
+        let found = false
+        this.topic.goods.forEach(goodsId => {
+          if (id === goodsId) {
+            found = true
+          }
+        })
+        if (!found) {
+          newGoodsIds.push(id)
+          newGoodsList.push(item)
+        }
+      })
+
+      if (newGoodsIds.length > 0) {
+        this.topic.goods = this.topic.goods.concat(newGoodsIds)
+        this.goodsList = this.goodsList.concat(newGoodsList)
+      }
+      this.addVisiable = false
+    },
+    handleDelete(row) {
+      for (var index = 0; index < this.topic.goods.length; index++) {
+        if (row.id === this.topic.goods[index]) {
+          this.topic.goods.splice(index, 1)
+        }
+      }
+      for (var index2 = 0; index2 < this.goodsList.length; index2++) {
+        if (row.id === this.goodsList[index2].id) {
+          this.goodsList.splice(index2, 1)
+        }
+      }
+    },
+    handleCancel() {
+      this.$router.push({ path: '/promotion/topic' })
+    },
+    handleConfirm() {
+      this.$refs['topic'].validate(valid => {
+        if (valid) {
+          createTopic(this.topic).then(response => {
+            this.$router.push({ path: '/promotion/topic' })
+          })
+            .catch(response => {
+              this.$notify.error({
+                title: '失败',
+                message: response.data.errmsg
+              })
+            })
+        }
+      })
+    }
+  }
+}
+</script>

+ 307 - 0
litemall-admin/src/views/promotion/topicEdit.vue

@@ -0,0 +1,307 @@
+<template>
+  <div class="app-container">
+
+    <el-form ref="topic" :rules="rules" :model="topic" status-icon label-position="left" label-width="100px" style="width: 800px; margin-left:50px;">
+      <el-form-item label="专题标题" prop="title">
+        <el-input v-model="topic.title"/>
+      </el-form-item>
+      <el-form-item label="专题子标题" prop="subtitle">
+        <el-input v-model="topic.subtitle"/>
+      </el-form-item>
+      <el-form-item label="专题图片" prop="picUrl">
+        <el-upload
+          :headers="headers"
+          :action="uploadPath"
+          :show-file-list="false"
+          :on-success="uploadPicUrl"
+          class="avatar-uploader"
+          accept=".jpg,.jpeg,.png,.gif">
+          <img v-if="topic.picUrl" :src="topic.picUrl" class="avatar">
+          <i v-else class="el-icon-plus avatar-uploader-icon"/>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="专题内容" prop="content">
+        <editor :init="editorInit" v-model="topic.content"/>
+      </el-form-item>
+      <el-form-item label="商品低价" prop="price">
+        <el-input v-model="topic.price"/>
+      </el-form-item>
+      <el-form-item label="阅读量" prop="readCount">
+        <el-input v-model="topic.readCount"/>
+      </el-form-item>
+      <el-form-item label="专题商品" prop="goods">
+        <el-button style="float:right;" size="mini" type="primary" @click="handleCreate()">创建商品</el-button>
+
+        <!-- 查询结果 -->
+        <el-table :data="goodsList" border fit highlight-current-row>
+
+          <el-table-column align="center" label="商品ID" prop="id"/>
+          <el-table-column align="center" property="picUrl" label="图片">
+            <template slot-scope="scope">
+              <img :src="scope.row.picUrl" width="60">
+            </template>
+          </el-table-column>
+          <el-table-column align="center" label="商品名称" prop="name"/>
+          <el-table-column align="center" label="商品介绍" prop="brief"/>
+          <el-table-column align="center" label="操作" class-name="small-padding fixed-width">
+            <template slot-scope="scope">
+              <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-form-item>
+
+    </el-form>
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="handleCancel">取消</el-button>
+      <el-button type="primary" @click="handleConfirm">确定</el-button>
+    </div>
+
+    <el-dialog :visible.sync="addVisiable" title="添加商品">
+      <div class="search">
+        <el-input v-model="listQuery.goodsSn" clearable class="filter-item" style="width: 200px;" placeholder="请输入商品编号"/>
+        <el-input v-model="listQuery.name" clearable class="filter-item" style="width: 200px;" placeholder="请输入商品名称"/>
+        <el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">查找</el-button>
+        <el-table v-loading="listLoading" :data="list" element-loading-text="正在查询中。。。" border fit highlight-current-row @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55"/>
+          <el-table-column align="center" label="商品ID" prop="id"/>
+          <el-table-column align="center" property="picUrl" label="图片">
+            <template slot-scope="scope">
+              <img :src="scope.row.picUrl" width="40">
+            </template>
+          </el-table-column>
+          <el-table-column align="center" label="商品名称" prop="name"/>
+        </el-table>
+        <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
+
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="addVisiable = false">取消</el-button>
+        <el-button type="primary" @click="confirmAdd">确定</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<style>
+.el-dialog {
+  width: 800px;
+}
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #20a0ff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 120px;
+  height: 120px;
+  line-height: 120px;
+  text-align: center;
+}
+.avatar {
+  width: 145px;
+  height: 145px;
+  display: block;
+}
+</style>
+
+<script>
+import { readTopic, updateTopic } from '@/api/topic'
+import { listGoods } from '@/api/goods'
+import { createStorage, uploadPath } from '@/api/storage'
+import BackToTop from '@/components/BackToTop'
+import Editor from '@tinymce/tinymce-vue'
+import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
+import { getToken } from '@/utils/auth'
+
+export default {
+  name: 'TopicEdit',
+  components: { BackToTop, Editor, Pagination },
+  data() {
+    return {
+      uploadPath,
+      id: 0,
+      topic: {},
+      goodsList: [],
+      addVisiable: false,
+      list: [],
+      total: 0,
+      listLoading: false,
+      listQuery: {
+        page: 1,
+        limit: 5,
+        id: undefined,
+        name: undefined,
+        sort: 'add_time',
+        order: 'desc'
+      },
+      selectedlist: [],
+      rules: {
+        title: [
+          { required: true, message: '专题标题不能为空', trigger: 'blur' }
+        ],
+        subtitle: [
+          { required: true, message: '专题子标题不能为空', trigger: 'blur' }
+        ],
+        content: [
+          { required: true, message: '专题内容不能为空', trigger: 'blur' }
+        ],
+        price: [
+          { required: true, message: '专题低价不能为空', trigger: 'blur' }
+        ]
+      },
+      editorInit: {
+        language: 'zh_CN',
+        convert_urls: false,
+        height: 500,
+        plugins: [
+          'advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools importcss insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount'
+        ],
+        toolbar: [
+          'searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent  blockquote undo redo removeformat subscript superscript code codesample',
+          'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen'
+        ],
+        images_upload_handler: function(blobInfo, success, failure) {
+          const formData = new FormData()
+          formData.append('file', blobInfo.blob())
+          createStorage(formData)
+            .then(res => {
+              success(res.data.data.url)
+            })
+            .catch(() => {
+              failure('上传失败,请重新上传')
+            })
+        }
+      }
+    }
+  },
+  computed: {
+    headers() {
+      return {
+        'X-Litemall-Admin-Token': getToken()
+      }
+    }
+  },
+  created() {
+    if (this.$route.query.id == null) {
+      return
+    }
+
+    this.id = this.$route.query.id
+    this.getTopic()
+  },
+  methods: {
+    getTopic() {
+      this.listLoading = true
+      readTopic({ id: this.id })
+        .then(response => {
+          this.topic = response.data.data.topic
+          this.goodsList = response.data.data.goodsList
+          this.listLoading = false
+        })
+        .catch(() => {
+          this.topic = {}
+          this.goodsList = []
+          this.listLoading = false
+        })
+    },
+    getList() {
+      this.listLoading = true
+      listGoods(this.listQuery).then(response => {
+        this.list = response.data.data.list
+        this.total = response.data.data.total
+        this.listLoading = false
+      }).catch(() => {
+        this.list = []
+        this.total = 0
+        this.listLoading = false
+      })
+    },
+    handleFilter() {
+      this.listQuery.page = 1
+      this.getList()
+    },
+    handleSelectionChange(val) {
+      this.selectedlist = val
+    },
+    uploadPicUrl: function(response) {
+      this.topic.picUrl = response.data.url
+    },
+    handleCreate() {
+      this.listQuery = {
+        page: 1,
+        limit: 5,
+        id: undefined,
+        name: undefined,
+        sort: 'add_time',
+        order: 'desc'
+      }
+      this.list = []
+      this.total = 0
+      this.selectedlist = []
+      this.addVisiable = true
+    },
+    confirmAdd() {
+      const newGoodsIds = []
+      const newGoodsList = []
+      this.selectedlist.forEach(item => {
+        const id = item.id
+        let found = false
+        this.topic.goods.forEach(goodsId => {
+          if (id === goodsId) {
+            found = true
+          }
+        })
+        if (!found) {
+          newGoodsIds.push(id)
+          newGoodsList.push(item)
+        }
+      })
+
+      if (newGoodsIds.length > 0) {
+        this.topic.goods = this.topic.goods.concat(newGoodsIds)
+        this.goodsList = this.goodsList.concat(newGoodsList)
+      }
+      this.addVisiable = false
+    },
+    handleDelete(row) {
+      for (var index = 0; index < this.topic.goods.length; index++) {
+        if (row.id === this.topic.goods[index]) {
+          this.topic.goods.splice(index, 1)
+        }
+      }
+      for (var index2 = 0; index2 < this.goodsList.length; index2++) {
+        if (row.id === this.goodsList[index2].id) {
+          this.goodsList.splice(index2, 1)
+        }
+      }
+    },
+    handleCancel() {
+      this.$router.push({ path: '/promotion/topic' })
+    },
+    handleConfirm() {
+      this.$refs['topic'].validate(valid => {
+        if (valid) {
+          updateTopic(this.topic).then(response => {
+            this.$router.push({ path: '/promotion/topic' })
+          })
+            .catch(response => {
+              this.$notify.error({
+                title: '失败',
+                message: response.data.errmsg
+              })
+            })
+        }
+      })
+    }
+  }
+}
+</script>

+ 7 - 0
litemall-db/src/main/java/org/linlinjava/litemall/db/service/LitemallGoodsService.java

@@ -11,6 +11,7 @@ import org.springframework.util.StringUtils;
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 @Service
@@ -247,4 +248,10 @@ public class LitemallGoodsService {
         example.or().andNameEqualTo(name).andIsOnSaleEqualTo(true).andDeletedEqualTo(false);
         return goodsMapper.countByExample(example) != 0;
     }
+
+    public List<LitemallGoods> queryByIds(Integer[] ids) {
+        LitemallGoodsExample example = new LitemallGoodsExample();
+        example.or().andIdIn(Arrays.asList(ids)).andIsOnSaleEqualTo(true).andDeletedEqualTo(false);
+        return goodsMapper.selectByExampleSelective(example, columns);
+    }
 }

+ 0 - 1
litemall-wx/pages/topicDetail/topicDetail.wxml

@@ -7,7 +7,6 @@
     <view class="topic-goods">
       <view class="h">
         <text class="t">专题商品</text>
-        <image bindtap="postComment" class="i" src="http://nos.netease.com/mailpub/hxm/yanxuan-wap/p/20150730/style/img/icon-normal/comment-add-2aca147c3f.png"></image>
       </view>
       <view class="b">
         <view class="item" wx:for="{{topicGoods}}" wx:for-index="index" wx:for-item="item" wx:key="id">

+ 16 - 0
litemall-wx/pages/topicDetail/topicDetail.wxss

@@ -143,6 +143,22 @@
   margin-top: 20rpx;
 }
 
+.topic-goods .h {
+  height: 93rpx;
+  line-height: 93rpx;
+  width: 720rpx;
+  padding-right: 30rpx;
+  border-bottom: 1px solid #d9d9d9;
+}
+
+.topic-goods .h .t {
+  display: block;
+  float: left;
+  width: 50%;
+  font-size: 29rpx;
+  color: #333;
+}
+
 .topic-goods .b .item {
   border-top: 1px solid #d9d9d9;
   margin: 0 20rpx;