Bladeren bron

分类/景区

licailing 5 jaren geleden
bovenliggende
commit
26a7803de5

+ 2 - 0
src/main/java/com/izouma/jiashanxia/domain/Attractions.java

@@ -7,6 +7,7 @@ import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Builder;
 import lombok.Data;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Where;
 
 
 import javax.persistence.Column;
 import javax.persistence.Column;
 import javax.persistence.Convert;
 import javax.persistence.Convert;
@@ -19,6 +20,7 @@ import java.util.List;
 @Builder
 @Builder
 @Entity
 @Entity
 @ApiModel("景区管理")
 @ApiModel("景区管理")
+@Where(clause = " del = 0 ")
 public class Attractions extends BaseEntity {
 public class Attractions extends BaseEntity {
     @ApiModelProperty(value = "名称")
     @ApiModelProperty(value = "名称")
     private String name;
     private String name;

+ 6 - 4
src/main/java/com/izouma/jiashanxia/domain/Category.java

@@ -1,13 +1,13 @@
 package com.izouma.jiashanxia.domain;
 package com.izouma.jiashanxia.domain;
 
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Builder;
 import lombok.Data;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.NoArgsConstructor;
 
 
-import javax.persistence.Entity;
+import javax.persistence.*;
+import java.util.List;
 
 
 @Data
 @Data
 @AllArgsConstructor
 @AllArgsConstructor
@@ -21,6 +21,8 @@ public class Category extends BaseEntity {
 
 
     private Long parent;
     private Long parent;
 
 
-    @ApiModelProperty(value = "多次使用")
-    private boolean repeatedly;
+    @OneToMany
+    @JoinColumn(name = "parent", insertable = false, updatable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
+    List<Category> children;
+
 }
 }

+ 8 - 0
src/main/java/com/izouma/jiashanxia/domain/GoodsInfo.java

@@ -43,5 +43,13 @@ public class GoodsInfo extends BaseEntity {
 //    @Column(nullable = false)
 //    @Column(nullable = false)
 //    private Boolean del = false;
 //    private Boolean del = false;
 
 
+    private Long categoryId;
+
     private String img;
     private String img;
+
+    @Transient
+    private String attractionsName;
+
+    @Transient
+    private String categoryName;
 }
 }

+ 11 - 0
src/main/java/com/izouma/jiashanxia/domain/Package.java

@@ -44,9 +44,20 @@ public class Package extends BaseEntity {
     @ApiModelProperty(value = "套餐类型")
     @ApiModelProperty(value = "套餐类型")
     private PackageType type;
     private PackageType type;
 
 
+    @ApiModelProperty(value = "套餐分类")
+    private Long categoryId;
+
     @ApiModelProperty(value = "图")
     @ApiModelProperty(value = "图")
     @Column(columnDefinition = "TEXT")
     @Column(columnDefinition = "TEXT")
     @Convert(converter = StringArrayConverter.class)
     @Convert(converter = StringArrayConverter.class)
     private List<String> img;
     private List<String> img;
 
 
+    @ApiModelProperty(value = "多次使用")
+    private boolean repeatedly;
+
+    @Transient
+    private String attractionsName;
+
+    @Transient
+    private String categoryName;
 }
 }

+ 26 - 0
src/main/java/com/izouma/jiashanxia/dto/PackageDTO.java

@@ -0,0 +1,26 @@
+package com.izouma.jiashanxia.dto;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+public interface PackageDTO {
+    Long getId();
+
+    String getName();
+
+    String getTitle();
+
+    List<String> getTag();
+
+    BigDecimal getAmount();
+
+    String getDetail();
+
+    List<String> getImg();
+
+    boolean isRepeatedly();
+
+    String getAttractionsName();
+
+    String getCategoryName();
+}

+ 21 - 0
src/main/java/com/izouma/jiashanxia/repo/CategoryRepo.java

@@ -0,0 +1,21 @@
+package com.izouma.jiashanxia.repo;
+
+import com.izouma.jiashanxia.domain.Category;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+import javax.transaction.Transactional;
+import java.util.List;
+
+public interface CategoryRepo extends JpaRepository<Category, Long>, JpaSpecificationExecutor<Category> {
+    @Query("update Category t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+
+    List<Category> findAllByParentIsNull();
+
+    List<Category> findAllByParent(Long parent);
+}

+ 7 - 0
src/main/java/com/izouma/jiashanxia/repo/PackageRepo.java

@@ -1,6 +1,7 @@
 package com.izouma.jiashanxia.repo;
 package com.izouma.jiashanxia.repo;
 
 
 import com.izouma.jiashanxia.domain.Package;
 import com.izouma.jiashanxia.domain.Package;
+import com.izouma.jiashanxia.dto.PackageDTO;
 import com.izouma.jiashanxia.enums.PackageType;
 import com.izouma.jiashanxia.enums.PackageType;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -19,4 +20,10 @@ public interface PackageRepo extends JpaRepository<Package, Long>, JpaSpecificat
     List<Package> findAllByTypeAndDelFalse(PackageType type);
     List<Package> findAllByTypeAndDelFalse(PackageType type);
 
 
     List<Package> findAllByAttractionsId(Long attractionsId);
     List<Package> findAllByAttractionsId(Long attractionsId);
+
+    @Query(nativeQuery = true, value = "select p.*,a.name attractionsName,c.name categoryName " +
+            "from package p " +
+            "left join attractions a on p.attractions_id = a.id " +
+            "left join category c on c.id = p.category_id where p.id = ?1")
+    PackageDTO findInformationById(Long id);
 }
 }

+ 20 - 0
src/main/java/com/izouma/jiashanxia/service/CategoryService.java

@@ -0,0 +1,20 @@
+package com.izouma.jiashanxia.service;
+
+import com.izouma.jiashanxia.domain.Category;
+import com.izouma.jiashanxia.dto.PageQuery;
+import com.izouma.jiashanxia.repo.CategoryRepo;
+import com.izouma.jiashanxia.utils.JpaUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class CategoryService {
+
+    private CategoryRepo categoryRepo;
+
+    public Page<Category> all(PageQuery pageQuery) {
+        return categoryRepo.findAll(JpaUtils.toSpecification(pageQuery, Category.class), JpaUtils.toPageRequest(pageQuery));
+    }
+}

+ 71 - 0
src/main/java/com/izouma/jiashanxia/web/CategoryController.java

@@ -0,0 +1,71 @@
+package com.izouma.jiashanxia.web;
+
+import com.izouma.jiashanxia.domain.Category;
+import com.izouma.jiashanxia.service.CategoryService;
+import com.izouma.jiashanxia.dto.PageQuery;
+import com.izouma.jiashanxia.exception.BusinessException;
+import com.izouma.jiashanxia.repo.CategoryRepo;
+import com.izouma.jiashanxia.utils.ObjUtils;
+import com.izouma.jiashanxia.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/category")
+@AllArgsConstructor
+public class CategoryController extends BaseController {
+    private CategoryService categoryService;
+    private CategoryRepo    categoryRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Category save(@RequestBody Category record) {
+        if (record.getId() != null) {
+            Category orig = categoryRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return categoryRepo.save(orig);
+        }
+        return categoryRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<Category> all(@RequestBody PageQuery pageQuery) {
+        return categoryService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public Category get(@PathVariable Long id) {
+        return categoryRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        categoryRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Category> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+
+    @GetMapping("/allList")
+    public List<Category> allList() {
+        return categoryRepo.findAllByParentIsNull();
+    }
+
+    @PostMapping("/getChildren")
+    public List<Category> getChildren(@RequestParam Long id) {
+        return categoryRepo.findAllByParent(id);
+    }
+}
+

+ 19 - 1
src/main/java/com/izouma/jiashanxia/web/GoodsInfoController.java

@@ -1,6 +1,10 @@
 package com.izouma.jiashanxia.web;
 package com.izouma.jiashanxia.web;
 
 
+import com.izouma.jiashanxia.domain.Attractions;
+import com.izouma.jiashanxia.domain.Category;
 import com.izouma.jiashanxia.domain.GoodsInfo;
 import com.izouma.jiashanxia.domain.GoodsInfo;
+import com.izouma.jiashanxia.repo.AttractionsRepo;
+import com.izouma.jiashanxia.repo.CategoryRepo;
 import com.izouma.jiashanxia.service.GoodsInfoService;
 import com.izouma.jiashanxia.service.GoodsInfoService;
 import com.izouma.jiashanxia.dto.PageQuery;
 import com.izouma.jiashanxia.dto.PageQuery;
 import com.izouma.jiashanxia.exception.BusinessException;
 import com.izouma.jiashanxia.exception.BusinessException;
@@ -15,6 +19,8 @@ import org.springframework.web.bind.annotation.*;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 
 @RestController
 @RestController
 @RequestMapping("/goodsInfo")
 @RequestMapping("/goodsInfo")
@@ -22,6 +28,8 @@ import java.util.List;
 public class GoodsInfoController extends BaseController {
 public class GoodsInfoController extends BaseController {
     private GoodsInfoService goodsInfoService;
     private GoodsInfoService goodsInfoService;
     private GoodsInfoRepo    goodsInfoRepo;
     private GoodsInfoRepo    goodsInfoRepo;
+    private CategoryRepo     categoryRepo;
+    private AttractionsRepo  attractionsRepo;
 
 
     //@PreAuthorize("hasRole('ADMIN')")
     //@PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/save")
     @PostMapping("/save")
@@ -38,7 +46,17 @@ public class GoodsInfoController extends BaseController {
     //@PreAuthorize("hasRole('ADMIN')")
     //@PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/all")
     @PostMapping("/all")
     public Page<GoodsInfo> all(@RequestBody PageQuery pageQuery) {
     public Page<GoodsInfo> all(@RequestBody PageQuery pageQuery) {
-        return goodsInfoService.all(pageQuery);
+        Map<Long, String> categoryMap = categoryRepo.findAll()
+                .stream()
+                .collect(Collectors.toMap(Category::getId, Category::getName));
+        Map<Long, String> attractionsMap = attractionsRepo.findAll()
+                .stream()
+                .collect(Collectors.toMap(Attractions::getId, Attractions::getName));
+        return goodsInfoService.all(pageQuery).map(goodsInfo -> {
+            goodsInfo.setAttractionsName(attractionsMap.get(goodsInfo.getAttractionsId()));
+            goodsInfo.setCategoryName(categoryMap.get(goodsInfo.getCategoryId()));
+            return goodsInfo;
+        });
     }
     }
 
 
     @PostMapping("/allList")
     @PostMapping("/allList")

+ 30 - 3
src/main/java/com/izouma/jiashanxia/web/PackageController.java

@@ -1,7 +1,12 @@
 package com.izouma.jiashanxia.web;
 package com.izouma.jiashanxia.web;
 
 
+import com.izouma.jiashanxia.domain.Attractions;
+import com.izouma.jiashanxia.domain.Category;
 import com.izouma.jiashanxia.domain.Package;
 import com.izouma.jiashanxia.domain.Package;
+import com.izouma.jiashanxia.dto.PackageDTO;
 import com.izouma.jiashanxia.enums.PackageType;
 import com.izouma.jiashanxia.enums.PackageType;
+import com.izouma.jiashanxia.repo.AttractionsRepo;
+import com.izouma.jiashanxia.repo.CategoryRepo;
 import com.izouma.jiashanxia.service.PackageService;
 import com.izouma.jiashanxia.service.PackageService;
 import com.izouma.jiashanxia.dto.PageQuery;
 import com.izouma.jiashanxia.dto.PageQuery;
 import com.izouma.jiashanxia.exception.BusinessException;
 import com.izouma.jiashanxia.exception.BusinessException;
@@ -16,13 +21,17 @@ import org.springframework.web.bind.annotation.*;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 
 @RestController
 @RestController
 @RequestMapping("/package")
 @RequestMapping("/package")
 @AllArgsConstructor
 @AllArgsConstructor
 public class PackageController extends BaseController {
 public class PackageController extends BaseController {
-    private PackageService packageService;
-    private PackageRepo    packageRepo;
+    private PackageService  packageService;
+    private PackageRepo     packageRepo;
+    private CategoryRepo    categoryRepo;
+    private AttractionsRepo attractionsRepo;
 
 
     //@PreAuthorize("hasRole('ADMIN')")
     //@PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/save")
     @PostMapping("/save")
@@ -40,12 +49,29 @@ public class PackageController extends BaseController {
     @PostMapping("/all")
     @PostMapping("/all")
     public Page<Package> all(@RequestBody PageQuery pageQuery) {
     public Page<Package> all(@RequestBody PageQuery pageQuery) {
         pageQuery.setSort("type,asc");
         pageQuery.setSort("type,asc");
-        return packageService.all(pageQuery);
+        Map<Long, String> categoryMap = categoryRepo.findAll()
+                .stream()
+                .collect(Collectors.toMap(Category::getId, Category::getName));
+        Map<Long, String> attractionsMap = attractionsRepo.findAll()
+                .stream()
+                .collect(Collectors.toMap(Attractions::getId, Attractions::getName));
+
+        return packageService.all(pageQuery).map(aPackage -> {
+            aPackage.setAttractionsName(attractionsMap.get(aPackage.getAttractionsId()));
+            aPackage.setCategoryName(categoryMap.get(aPackage.getCategoryId()));
+            return aPackage;
+        });
     }
     }
 
 
     @GetMapping("/get/{id}")
     @GetMapping("/get/{id}")
     public Package get(@PathVariable Long id) {
     public Package get(@PathVariable Long id) {
         return packageRepo.findById(id).orElseThrow(new BusinessException("无记录"));
         return packageRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+//        return packageRepo.findInformationById(id);
+    }
+
+    @GetMapping("/getDTO/{id}")
+    public PackageDTO getDTO(@PathVariable Long id) {
+        return packageRepo.findInformationById(id);
     }
     }
 
 
     @PostMapping("/del/{id}")
     @PostMapping("/del/{id}")
@@ -82,5 +108,6 @@ public class PackageController extends BaseController {
     public List<Package> allList() {
     public List<Package> allList() {
         return packageRepo.findAll();
         return packageRepo.findAll();
     }
     }
+
 }
 }
 
 

+ 1 - 0
src/main/resources/genjson/Category.json

@@ -0,0 +1 @@
+{"tableName":"Category","className":"Category","remark":"分类管理","genTable":true,"genClass":true,"genList":true,"genForm":true,"genRouter":true,"javaPath":"/Users/qiufangchao/Desktop/project/jiashanxia/src/main/java/com/izouma/jiashanxia","viewPath":"/Users/qiufangchao/Desktop/project/jiashanxia/src/main/vue/src/views","routerPath":"/Users/qiufangchao/Desktop/project/jiashanxia/src/main/vue/src","resourcesPath":"/Users/qiufangchao/Desktop/project/jiashanxia/src/main/resources","dataBaseType":"Mysql","fields":[{"name":"name","modelName":"name","remark":"名称","showInList":true,"showInForm":true,"formType":"singleLineText"},{"name":"parent","modelName":"parent","remark":"上级","showInList":true,"showInForm":true,"formType":"number"},{"name":"repeatedly","modelName":"repeatedly","remark":"多次使用","showInList":true,"showInForm":true,"formType":"singleLineText"}],"readTable":false,"dataSourceCode":"dataSource","genJson":"","subtables":[],"update":false,"basePackage":"com.izouma.jiashanxia","tablePackage":"com.izouma.jiashanxia.domain.Category"}

+ 81 - 9
src/main/vue/src/components/PackageEdit.vue

@@ -4,11 +4,28 @@
             :model="formData"
             :model="formData"
             :rules="rules"
             :rules="rules"
             ref="form"
             ref="form"
-            label-width="80px"
+            label-width="90px"
             label-position="right"
             label-position="right"
             size="small"
             size="small"
             style="max-width: 800px;"
             style="max-width: 800px;"
         >
         >
+            <el-form-item prop="attractionsId" label="景区">
+                <el-select
+                    v-model="formData.attractionsId"
+                    clearable
+                    filterable
+                    placeholder="请选择景区"
+                    class="select-width"
+                >
+                    <el-option
+                        v-for="item in attractionsOptions"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                    >
+                    </el-option>
+                </el-select>
+            </el-form-item>
             <el-form-item prop="name" label="套餐标题">
             <el-form-item prop="name" label="套餐标题">
                 <el-input v-model="formData.name" class="input-title"></el-input>
                 <el-input v-model="formData.name" class="input-title"></el-input>
             </el-form-item>
             </el-form-item>
@@ -44,15 +61,26 @@
             <el-form-item prop="amount" label="金额">
             <el-form-item prop="amount" label="金额">
                 <el-input-number type="number" v-model="formData.amount"></el-input-number>
                 <el-input-number type="number" v-model="formData.amount"></el-input-number>
             </el-form-item>
             </el-form-item>
-            <el-form-item prop="detail" label="详情">
-                <rich-text v-model="formData.detail"></rich-text>
+            <el-form-item prop="repeatedly" label="使用次数">
+                <el-radio-group v-model="formData.repeatedly">
+                    <el-radio :label="false">单次使用</el-radio>
+                    <el-radio :label="true">多次使用</el-radio>
+                </el-radio-group>
             </el-form-item>
             </el-form-item>
-            <el-form-item prop="type" label="套餐类型">
-                <el-select v-model="formData.type" clearable filterable placeholder="请选择">
-                    <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value">
+            <el-form-item prop="categoryId" label="套餐类型">
+                <el-select v-model="formData.categoryId" clearable filterable placeholder="请选择" class="select-width">
+                    <el-option
+                        v-for="item in categoryOptions"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                    >
                     </el-option>
                     </el-option>
                 </el-select>
                 </el-select>
             </el-form-item>
             </el-form-item>
+            <el-form-item prop="detail" label="详情">
+                <rich-text v-model="formData.detail"></rich-text>
+            </el-form-item>
             <el-form-item>
             <el-form-item>
                 <el-button @click="onSave" :loading="saving" type="primary" v-if="$route.query.id">保存</el-button>
                 <el-button @click="onSave" :loading="saving" type="primary" v-if="$route.query.id">保存</el-button>
                 <el-button @click="onSave" :loading="saving" type="primary" v-else>下一步</el-button>
                 <el-button @click="onSave" :loading="saving" type="primary" v-else>下一步</el-button>
@@ -77,6 +105,38 @@ export default {
                     this.$message.error(e.error);
                     this.$message.error(e.error);
                 });
                 });
         }
         }
+        this.$http
+            .post('/attractions/all', { size: 1000, query: { del: false } }, { body: 'json' })
+            .then(res => {
+                if (res.content.length > 0) {
+                    res.content.forEach(item => {
+                        this.attractionsOptions.push({
+                            label: item.name,
+                            value: item.id
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+        this.$http
+            .post('/category/getChildren', { id: 731 })
+            .then(res => {
+                if (res.length > 0) {
+                    res.forEach(item => {
+                        this.categoryOptions.push({
+                            label: item.name,
+                            value: item.id
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
     },
     },
     data() {
     data() {
         return {
         return {
@@ -86,6 +146,13 @@ export default {
                 tag: []
                 tag: []
             },
             },
             rules: {
             rules: {
+                attractionsId: [
+                    {
+                        required: true,
+                        message: '请选择景区',
+                        trigger: 'blur'
+                    }
+                ],
                 name: [
                 name: [
                     {
                     {
                         required: true,
                         required: true,
@@ -100,10 +167,10 @@ export default {
                         trigger: 'blur'
                         trigger: 'blur'
                     }
                     }
                 ],
                 ],
-                type: [
+                categoryId: [
                     {
                     {
                         required: true,
                         required: true,
-                        message: '请输入套餐类型',
+                        message: '请选择类型',
                         trigger: 'blur'
                         trigger: 'blur'
                     }
                     }
                 ]
                 ]
@@ -113,7 +180,9 @@ export default {
                 { label: '个人', value: 'PERSONAL' }
                 { label: '个人', value: 'PERSONAL' }
             ],
             ],
             inputVisible: false,
             inputVisible: false,
-            inputValue: ''
+            inputValue: '',
+            attractionsOptions: [],
+            categoryOptions: []
         };
         };
     },
     },
     methods: {
     methods: {
@@ -203,4 +272,7 @@ export default {
 .input-title {
 .input-title {
     width: 350px;
     width: 350px;
 }
 }
+.select-width {
+    width: 350px;
+}
 </style>
 </style>

+ 16 - 0
src/main/vue/src/router.js

@@ -186,6 +186,14 @@ const router = new Router({
                         title: '商品信息'
                         title: '商品信息'
                     }
                     }
                 },
                 },
+                {
+                    path: '/goodsInfoEdit',
+                    name: 'GoodsInfoEdit',
+                    component: () => import(/* webpackChunkName: "goodsInfoEdit" */ '@/views/GoodsInfoEdit.vue'),
+                    meta: {
+                        title: '商品信息编辑'
+                    }
+                },
                 {
                 {
                     path: '/packageGoodsEdit',
                     path: '/packageGoodsEdit',
                     name: 'PackageGoodsEdit',
                     name: 'PackageGoodsEdit',
@@ -365,6 +373,14 @@ const router = new Router({
                     meta: {
                     meta: {
                         title: '用户套餐'
                         title: '用户套餐'
                     }
                     }
+                },
+                {
+                    path: '/categories',
+                    name: 'Categories',
+                    component: () => import(/* webpackChunkName: "categoryList" */ '@/views/manage/Categories.vue'),
+                    meta: {
+                        title: '分类管理'
+                    }
                 }
                 }
                 /**INSERT_LOCATION**/
                 /**INSERT_LOCATION**/
             ]
             ]

+ 6 - 6
src/main/vue/src/views/AttractionsEdit.vue

@@ -12,17 +12,17 @@
             <el-form-item prop="name" label="名称">
             <el-form-item prop="name" label="名称">
                 <el-input v-model="formData.name"></el-input>
                 <el-input v-model="formData.name"></el-input>
             </el-form-item>
             </el-form-item>
-            <el-form-item prop="introduction" label="介绍">
-                <el-input type="textarea" v-model="formData.introduction"></el-input>
+            <el-form-item prop="img" label="图片">
+                <multi-upload v-model="formData.img"></multi-upload>
             </el-form-item>
             </el-form-item>
-            <el-form-item prop="address" label="地址">
-                <el-input v-model="formData.address"></el-input>
+            <el-form-item prop="introduction" label="介绍">
+                <el-input type="textarea" :autosize="{ minRows: 2 }" v-model="formData.introduction"></el-input>
             </el-form-item>
             </el-form-item>
             <el-form-item prop="phone" label="电话">
             <el-form-item prop="phone" label="电话">
                 <el-input v-model="formData.phone"></el-input>
                 <el-input v-model="formData.phone"></el-input>
             </el-form-item>
             </el-form-item>
-            <el-form-item prop="img" label="图片">
-                <multi-upload v-model="formData.img"></multi-upload>
+            <el-form-item prop="address" label="地址">
+                <el-input v-model="formData.address"></el-input>
             </el-form-item>
             </el-form-item>
             <el-form-item>
             <el-form-item>
                 <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
                 <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>

+ 5 - 5
src/main/vue/src/views/AttractionsList.vue

@@ -4,14 +4,14 @@
             <el-input placeholder="输入关键字" v-model="search" clearable class="filter-item"></el-input>
             <el-input placeholder="输入关键字" v-model="search" clearable class="filter-item"></el-input>
             <el-button @click="getData" type="primary" icon="el-icon-search" class="filter-item">搜索 </el-button>
             <el-button @click="getData" type="primary" icon="el-icon-search" class="filter-item">搜索 </el-button>
             <el-button @click="addRow" type="primary" icon="el-icon-plus" class="filter-item">添加 </el-button>
             <el-button @click="addRow" type="primary" icon="el-icon-plus" class="filter-item">添加 </el-button>
-            <el-button
+            <!-- <el-button
                 @click="download"
                 @click="download"
                 type="primary"
                 type="primary"
                 icon="el-icon-download"
                 icon="el-icon-download"
                 :loading="downloading"
                 :loading="downloading"
                 class="filter-item"
                 class="filter-item"
                 >导出EXCEL
                 >导出EXCEL
-            </el-button>
+            </el-button> -->
         </div>
         </div>
         <el-table
         <el-table
             :data="tableData"
             :data="tableData"
@@ -26,9 +26,6 @@
             <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
             <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
             <el-table-column prop="id" label="ID" width="100"> </el-table-column>
             <el-table-column prop="id" label="ID" width="100"> </el-table-column>
             <el-table-column prop="name" label="名称"> </el-table-column>
             <el-table-column prop="name" label="名称"> </el-table-column>
-            <el-table-column prop="introduction" label="介绍"> </el-table-column>
-            <el-table-column prop="address" label="地址"> </el-table-column>
-            <el-table-column prop="phone" label="电话"> </el-table-column>
             <el-table-column prop="img" label="图片">
             <el-table-column prop="img" label="图片">
                 <template slot-scope="{ row }">
                 <template slot-scope="{ row }">
                     <el-image
                     <el-image
@@ -39,6 +36,9 @@
                     ></el-image>
                     ></el-image>
                 </template>
                 </template>
             </el-table-column>
             </el-table-column>
+            <el-table-column prop="introduction" label="介绍"> </el-table-column>
+            <el-table-column prop="phone" label="电话"> </el-table-column>
+            <el-table-column prop="address" label="地址"> </el-table-column>
             <el-table-column label="操作" align="center" fixed="right" min-width="150">
             <el-table-column label="操作" align="center" fixed="right" min-width="150">
                 <template slot-scope="{ row }">
                 <template slot-scope="{ row }">
                     <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
                     <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>

+ 175 - 0
src/main/vue/src/views/GoodsInfoEdit.vue

@@ -0,0 +1,175 @@
+<template>
+    <div class="edit-view">
+        <el-form
+            :model="formData"
+            :rules="rules"
+            ref="form"
+            label-width="90px"
+            label-position="right"
+            size="small"
+            style="max-width: 500px;"
+        >
+            <el-form-item prop="attractionsId" label="景区">
+                <el-select
+                    v-model="formData.attractionsId"
+                    clearable
+                    filterable
+                    placeholder="请选择景区"
+                    class="cl-input"
+                >
+                    <el-option
+                        v-for="item in attractionsOptions"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                    >
+                    </el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item prop="img" label="图片">
+                <single-upload v-model="formData.img"></single-upload>
+            </el-form-item>
+            <el-form-item prop="name" label="名称">
+                <el-input v-model="formData.name" class="cl-input"></el-input>
+            </el-form-item>
+            <el-form-item prop="price" label="价格">
+                <el-input-number v-model="formData.price" class="cl-input"></el-input-number>
+            </el-form-item>
+            <el-form-item prop="unit" label="单位">
+                <el-input v-model="formData.unit" class="cl-input"></el-input>
+            </el-form-item>
+            <el-form-item prop="categoryId" label="类型">
+                <el-select v-model="formData.categoryId" clearable filterable placeholder="请选择" class="cl-input">
+                    <el-option
+                        v-for="item in categoryOptions"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                    >
+                    </el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item>
+                <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
+                <el-button @click="onDelete" :loading="saving" type="danger" v-if="formData.id">删除 </el-button>
+                <el-button @click="$router.go(-1)">取消</el-button>
+            </el-form-item>
+        </el-form>
+    </div>
+</template>
+<script>
+export default {
+    name: 'GoodsInfoEdit',
+    created() {
+        if (this.$route.query.id) {
+            this.$http
+                .get('goodsInfo/get/' + this.$route.query.id)
+                .then(res => {
+                    this.formData = res;
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+        }
+        this.$http
+            .post('/category/getChildren', { id: 730 })
+            .then(res => {
+                if (res.length > 0) {
+                    res.forEach(item => {
+                        this.categoryOptions.push({
+                            label: item.name,
+                            value: item.id
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+        this.$http
+            .post('/attractions/all', { size: 1000, query: { del: false } }, { body: 'json' })
+            .then(res => {
+                if (res.content.length > 0) {
+                    res.content.forEach(item => {
+                        this.attractionsOptions.push({
+                            label: item.name,
+                            value: item.id
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+    },
+    data() {
+        return {
+            saving: false,
+            formData: {},
+            rules: {
+                name: [
+                    {
+                        required: true,
+                        message: '请输入名称',
+                        trigger: 'blur'
+                    }
+                ]
+            },
+            categoryOptions: [],
+            attractionsOptions: []
+        };
+    },
+    methods: {
+        onSave() {
+            this.$refs.form.validate(valid => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+            let data = { ...this.formData };
+
+            this.saving = true;
+            this.$http
+                .post('/goodsInfo/save', data, { body: 'json' })
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.saving = false;
+                    this.$message.error(e.error);
+                });
+        },
+        onDelete() {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/goodsInfo/del/${this.formData.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        console.log(e);
+                        this.$message.error(e.error);
+                    }
+                });
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+.cl-input {
+    width: 350px;
+}
+</style>

+ 85 - 13
src/main/vue/src/views/GoodsInfoList.vue

@@ -25,9 +25,11 @@
         >
         >
             <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
             <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
             <el-table-column prop="id" label="ID" width="100"> </el-table-column>
             <el-table-column prop="id" label="ID" width="100"> </el-table-column>
+            <el-table-column prop="attractionsName" label="景区名称" show-overflow-tooltip> </el-table-column>
             <el-table-column prop="name" label="名称" min-width="120px"> </el-table-column>
             <el-table-column prop="name" label="名称" min-width="120px"> </el-table-column>
             <el-table-column prop="price" label="价格(元)"> </el-table-column>
             <el-table-column prop="price" label="价格(元)"> </el-table-column>
             <el-table-column prop="unit" label="单位"> </el-table-column>
             <el-table-column prop="unit" label="单位"> </el-table-column>
+            <el-table-column prop="categoryName" label="类型"> </el-table-column>
             <el-table-column label="操作" align="center" fixed="right" min-width="150">
             <el-table-column label="操作" align="center" fixed="right" min-width="150">
                 <template slot-scope="{ row }">
                 <template slot-scope="{ row }">
                     <el-button @click="editRow(row, true)" type="primary" size="mini" plain>编辑</el-button>
                     <el-button @click="editRow(row, true)" type="primary" size="mini" plain>编辑</el-button>
@@ -59,6 +61,23 @@
 
 
         <el-dialog :visible.sync="showDialog" width="460px" title="编辑商品" :close-on-click-modal="false">
         <el-dialog :visible.sync="showDialog" width="460px" title="编辑商品" :close-on-click-modal="false">
             <el-form :model="formData" :rules="rules" ref="form" label-width="80px" label-position="right" size="small">
             <el-form :model="formData" :rules="rules" ref="form" label-width="80px" label-position="right" size="small">
+                <el-form-item prop="attractionsId" label="景区">
+                    <el-select
+                        v-model="formData.attractionsId"
+                        clearable
+                        filterable
+                        placeholder="请选择景区"
+                        class="select-width"
+                    >
+                        <el-option
+                            v-for="item in attractionsOptions"
+                            :key="item.value"
+                            :label="item.label"
+                            :value="item.value"
+                        >
+                        </el-option>
+                    </el-select>
+                </el-form-item>
                 <el-form-item prop="img" label="图片">
                 <el-form-item prop="img" label="图片">
                     <single-upload v-model="formData.img"></single-upload>
                     <single-upload v-model="formData.img"></single-upload>
                 </el-form-item>
                 </el-form-item>
@@ -71,6 +90,23 @@
                 <el-form-item prop="unit" label="单位">
                 <el-form-item prop="unit" label="单位">
                     <el-input v-model="formData.unit" class="cl-input"></el-input>
                     <el-input v-model="formData.unit" class="cl-input"></el-input>
                 </el-form-item>
                 </el-form-item>
+                <el-form-item prop="categoryId" label="类型">
+                    <el-select
+                        v-model="formData.categoryId"
+                        clearable
+                        filterable
+                        placeholder="请选择"
+                        class="select-width"
+                    >
+                        <el-option
+                            v-for="item in categoryOptions"
+                            :key="item.value"
+                            :label="item.label"
+                            :value="item.value"
+                        >
+                        </el-option>
+                    </el-select>
+                </el-form-item>
             </el-form>
             </el-form>
             <span slot="footer">
             <span slot="footer">
                 <el-button type="primary" size="mini" @click="save" :loading="saving">保存</el-button>
                 <el-button type="primary" size="mini" @click="save" :loading="saving">保存</el-button>
@@ -85,6 +121,40 @@ import pageableTable from '@/mixins/pageableTable';
 export default {
 export default {
     name: 'GoodsInfoList',
     name: 'GoodsInfoList',
     mixins: [pageableTable],
     mixins: [pageableTable],
+    created() {
+        this.$http
+            .post('/category/getChildren', { id: 730 })
+            .then(res => {
+                if (res.length > 0) {
+                    res.forEach(item => {
+                        this.categoryOptions.push({
+                            label: item.name,
+                            value: item.id
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+        this.$http
+            .post('/attractions/all', { size: 1000, query: { del: false } }, { body: 'json' })
+            .then(res => {
+                if (res.content.length > 0) {
+                    res.content.forEach(item => {
+                        this.attractionsOptions.push({
+                            label: item.name,
+                            value: item.id
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+    },
     data() {
     data() {
         return {
         return {
             multipleMode: false,
             multipleMode: false,
@@ -100,7 +170,9 @@ export default {
                 name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
                 name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
                 unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
                 unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
                 price: [{ required: true, message: '请输入价格', trigger: 'blur' }]
                 price: [{ required: true, message: '请输入价格', trigger: 'blur' }]
-            }
+            },
+            categoryOptions: [],
+            attractionsOptions: []
         };
         };
     },
     },
     computed: {
     computed: {
@@ -126,25 +198,25 @@ export default {
                 }
                 }
             });
             });
         },
         },
-        /*editRow(row) {
+        editRow(row) {
             this.$router.push({
             this.$router.push({
                 path: '/goodsInfoEdit',
                 path: '/goodsInfoEdit',
                 query: {
                 query: {
                     id: row.id
                     id: row.id
                 }
                 }
             });
             });
-        },*/
-        editRow(row, edit) {
-            this.edit = edit;
-            if (!row) {
-                row = {
-                    name: '',
-                    price: ''
-                };
-            }
-            this.formData = { ...row };
-            this.showDialog = true;
         },
         },
+        // editRow(row, edit) {
+        //     this.edit = edit;
+        //     if (!row) {
+        //         row = {
+        //             name: '',
+        //             price: ''
+        //         };
+        //     }
+        //     this.formData = { ...row };
+        //     this.showDialog = true;
+        // },
         download() {
         download() {
             this.downloading = true;
             this.downloading = true;
             this.$axios
             this.$axios

+ 4 - 2
src/main/vue/src/views/PackageList.vue

@@ -25,6 +25,7 @@
         >
         >
             <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
             <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
             <el-table-column prop="id" label="ID" width="60"> </el-table-column>
             <el-table-column prop="id" label="ID" width="60"> </el-table-column>
+            <el-table-column prop="attractionsName" label="景区名称" show-overflow-tooltip> </el-table-column>
             <el-table-column prop="name" label="套餐名称" show-overflow-tooltip> </el-table-column>
             <el-table-column prop="name" label="套餐名称" show-overflow-tooltip> </el-table-column>
             <el-table-column prop="title" label="标题" show-overflow-tooltip> </el-table-column>
             <el-table-column prop="title" label="标题" show-overflow-tooltip> </el-table-column>
             <el-table-column prop="img" label="图">
             <el-table-column prop="img" label="图">
@@ -39,8 +40,9 @@
             </el-table-column>
             </el-table-column>
             <el-table-column prop="tag" label="套餐标签" show-overflow-tooltip> </el-table-column>
             <el-table-column prop="tag" label="套餐标签" show-overflow-tooltip> </el-table-column>
             <el-table-column prop="amount" label="金额(元)"> </el-table-column>
             <el-table-column prop="amount" label="金额(元)"> </el-table-column>
-            <el-table-column prop="detail" label="详情"> </el-table-column>
-            <el-table-column prop="type" label="套餐类型" :formatter="typeFormatter"> </el-table-column>
+            <!-- <el-table-column prop="detail" label="详情"> </el-table-column> -->
+            <!-- <el-table-column prop="type" label="套餐类型" :formatter="typeFormatter"> </el-table-column> -->
+            <el-table-column prop="categoryName" label="套餐类型"> </el-table-column>
             <el-table-column label="操作" align="center" fixed="right" min-width="150">
             <el-table-column label="操作" align="center" fixed="right" min-width="150">
                 <template slot-scope="{ row }">
                 <template slot-scope="{ row }">
                     <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
                     <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>

+ 395 - 0
src/main/vue/src/views/manage/Categories.vue

@@ -0,0 +1,395 @@
+<template>
+    <div>
+        <el-row :gutter="20">
+            <el-col :span="12">
+                <div class="menu-tree">
+                    <el-tree
+                        :data="menus"
+                        :render-content="renderContent"
+                        :highlight-current="true"
+                        :expand-on-click-node="true"
+                        node-key="id"
+                        v-loading="loading"
+                        accordion
+                        @node-click="nodeClick"
+                        :default-expanded-keys="expandKeys"
+                        :default-checked-keys="expandKeys"
+                    >
+                    </el-tree>
+                    <el-button type="text" @click="addRootMenu" style="margin-left: 24px;">添加 </el-button>
+                </div>
+            </el-col>
+            <transition name="el-fade-in">
+                <el-col :span="12" v-if="dialogVisible">
+                    <div class="menu-tree">
+                        <div style="font-weight:bold;padding:10px 0">{{ menu.id ? '编辑菜单' : '新增菜单' }}</div>
+                        <el-form :model="menu" ref="form" label-position="top">
+                            <el-form-item
+                                label="菜单名"
+                                prop="name"
+                                :rules="[{ required: true, message: '请填写菜单名', trigger: 'blur' }]"
+                            >
+                                <el-input v-model="menu.name"></el-input>
+                            </el-form-item>
+                        </el-form>
+                        <div slot="footer">
+                            <el-button @click="dialogVisible = false">取消 </el-button>
+                            <el-button type="primary" @click="addMenu" :loading="loading">保存</el-button>
+                        </div>
+                    </div>
+                </el-col>
+            </transition>
+        </el-row>
+    </div>
+</template>
+<script>
+export default {
+    created() {
+        this.getData();
+    },
+    data() {
+        return {
+            dialogVisible: false,
+            curr: null,
+            loading: false,
+            menus: [],
+            menu: {
+                name: '',
+                root: false,
+                active: true
+            },
+            parent: null,
+            currentRef: null,
+            edit: false,
+            category: null,
+            expandKeys: []
+        };
+    },
+    methods: {
+        addRootMenu() {
+            this.menu = {
+                name: '',
+                active: true,
+                root: true
+            };
+            this.parent = null;
+            this.icon = 'bars';
+            this.dialogVisible = true;
+            setTimeout(() => {
+                this.showIcon('bars');
+            }, 100);
+        },
+        showAddDialog(node, data) {
+            this.edit = false;
+            this.parent = node.data;
+            this.menu = {
+                parent: node.data.id,
+                name: '',
+                active: true,
+                root: false
+            };
+            this.dialogVisible = true;
+            setTimeout(() => {
+                this.showIcon('');
+            }, 100);
+        },
+        showEditDialog(node, data) {
+            this.edit = true;
+            this.currentRef = node.data;
+            const getIconName = icon => {
+                let iconName = '';
+                if (icon) {
+                    iconName = icon
+                        .replace('fas ', '')
+                        .replace('fab ', '')
+                        .replace('fa-', '');
+                }
+                return iconName || null;
+            };
+            let iconName = getIconName(data.icon);
+            this.menu = {
+                ...data
+            };
+            this.icon = iconName;
+            this.dialogVisible = true;
+            setTimeout(() => {
+                this.showIcon(iconName);
+            }, 100);
+        },
+        addMenu() {
+            this.$refs.form.validate(valid => {
+                if (valid) {
+                    this.loading = true;
+                    let menu = { ...this.menu };
+                    delete menu.children;
+                    this.$http
+                        .post('/category/save', menu, { body: 'json' })
+                        .then(res => {
+                            this.loading = false;
+                            this.$message.success('添加成功');
+                            this.dialogVisible = false;
+                            this.getData();
+                        })
+                        .catch(e => {
+                            console.log(e);
+                            this.loading = false;
+                            this.$message.error(e.error);
+                        });
+                }
+            });
+        },
+        remove(node, data) {
+            console.log(node);
+            this.$confirm('确定删除菜单?', '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'error'
+            })
+                .then(() => {
+                    return this.$http.post(
+                        '/category/save',
+                        {
+                            ...data,
+                            active: false,
+                            children: null
+                        },
+                        { body: 'json' }
+                    );
+                })
+                .then(res => {
+                    this.$message.success('删除成功');
+                    this.getData();
+                })
+                .catch(e => {
+                    this.loading = false;
+                    if (e !== 'cancel') {
+                        console.log(e);
+                        this.$message.error(e.error);
+                    }
+                });
+        },
+        moveUp(node, data) {
+            if (node.previousSibling) {
+                this.loading = true;
+                let sort0 = node.previousSibling.data.sort,
+                    sort1 = node.data.sort;
+                Promise.all([
+                    this.$http.post(
+                        '/category/save',
+                        {
+                            ...node.data,
+                            children: null,
+                            sort: sort0
+                        },
+                        { body: 'json' }
+                    ),
+                    this.$http.post(
+                        '/category/save',
+                        {
+                            ...node.previousSibling.data,
+                            children: null,
+                            sort: sort1
+                        },
+                        { body: 'json' }
+                    )
+                ])
+                    .then(_ => {
+                        this.loading = false;
+                        this.getData();
+                    })
+                    .catch(e => {
+                        console.log(e);
+                        this.loading = false;
+                        this.$message.error(e.error);
+                    });
+            }
+        },
+        moveDown(node, data) {
+            if (node.nextSibling) {
+                this.loading = true;
+                let sort0 = node.data.sort,
+                    sort1 = node.nextSibling.data.sort;
+                Promise.all([
+                    this.$http.post(
+                        '/category/save',
+                        {
+                            ...node.data,
+                            children: null,
+                            sort: sort1
+                        },
+                        { body: 'json' }
+                    ),
+                    this.$http.post(
+                        '/category/save',
+                        {
+                            ...node.nextSibling.data,
+                            children: null,
+                            sort: sort0
+                        },
+                        { body: 'json' }
+                    )
+                ])
+                    .then(_ => {
+                        this.loading = false;
+                        this.getData();
+                    })
+                    .catch(e => {
+                        console.log(e);
+                        this.loading = false;
+                        this.$message.error(e.error);
+                    });
+            }
+        },
+        getData() {
+            this.$http
+                .get('/category/allList')
+                .then(res => {
+                    this.menus = res;
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+        },
+        renderContent(h, { node, data, store }) {
+            return (
+                <span
+                    class={
+                        this.menu.id == data.id || (this.menu.parent == data.id && !this.menu.id)
+                            ? 'custom-tree-node selected'
+                            : 'custom-tree-node'
+                    }
+                >
+                    <span>{data.name}</span>
+                    <span class="opt">
+                        <el-button
+                            type="text"
+                            on-click={e => {
+                                this.showEditDialog(node, data), e.stopPropagation();
+                            }}
+                            icon="el-icon-edit"
+                        >
+                            编辑
+                        </el-button>
+                        <el-button
+                            type="text"
+                            on-click={e => {
+                                this.showAddDialog(node, data), e.stopPropagation();
+                            }}
+                            icon="el-icon-plus"
+                        >
+                            添加
+                        </el-button>
+                        <el-button
+                            type="text"
+                            on-click={e => {
+                                this.remove(node, data), e.stopPropagation();
+                            }}
+                            icon="el-icon-delete"
+                        >
+                            删除
+                        </el-button>
+                    </span>
+                </span>
+            );
+        },
+        showIcon(val) {
+            if (!this.$refs.iconContainer) return;
+            if (FontAwesome.icon({ prefix: 'fas', iconName: val })) {
+                this.$refs.iconContainer.innerHTML = '';
+                let i = document.createElement('i');
+                i.className = 'fas fa-' + val;
+                this.$refs.iconContainer.append(i);
+                FontAwesome.dom.i2svg();
+                this.menu.icon = 'fas fa-' + val;
+            } else if (FontAwesome.icon({ prefix: 'fab', iconName: val })) {
+                this.$refs.iconContainer.innerHTML = '';
+                let i = document.createElement('i');
+                i.className = 'fab fa-' + val;
+                this.$refs.iconContainer.append(i);
+                FontAwesome.dom.i2svg();
+                this.menu.icon = 'fab fa-' + val;
+            } else {
+                this.$refs.iconContainer.innerHTML = '';
+                let i = document.createElement('i');
+                i.className = 'fab fa-' + val;
+                this.$refs.iconContainer.append(i);
+                FontAwesome.dom.i2svg();
+                this.menu.icon = '';
+            }
+        },
+        nodeClick(data, node, el) {
+            if (this.expandKeys[0] != data.id) {
+                this.expandKeys = [data.id];
+            }
+        }
+    },
+    watch: {
+        icon(val) {
+            this.showIcon(val);
+        },
+        category() {
+            this.getData();
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+.menu-tree {
+    border-radius: 4px;
+    background: white;
+    margin-top: 20px;
+    padding: 10px;
+}
+</style>
+<style lang="less">
+.menu-tree {
+    .el-tree-node__content {
+        height: 40px;
+        line-height: 40px;
+    }
+}
+.custom-tree-node {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 14px;
+    padding-right: 8px;
+    line-height: 40px;
+    height: 40px;
+    .url {
+        flex-grow: 1;
+        text-align: right;
+        margin-right: 20px;
+        color: #999;
+    }
+    .opt {
+        opacity: 0;
+    }
+    &.selected {
+        border: 2px solid #409eff;
+        border-radius: 4px;
+        padding: 0 10px;
+        box-sizing: border-box;
+        .opt {
+            opacity: 1;
+        }
+    }
+}
+
+.custom-tree-node:hover {
+    .opt {
+        opacity: 1;
+    }
+}
+
+.available-icons {
+    color: #409eff;
+    text-decoration: none;
+    &:hover {
+        color: #409eff;
+        text-decoration: none;
+    }
+}
+</style>