商品品牌管理基础CRUD及数据校验

This commit is contained in:
Kirk Lin 2021-06-10 22:09:16 +08:00
parent be70a27905
commit c742c181e2
46 changed files with 4212 additions and 30 deletions

View file

@ -79,5 +79,6 @@
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:8.0.23" level="project" />
<orderEntry type="library" name="Maven: com.google.protobuf:protobuf-java:3.11.4" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: javax.servlet:servlet-api:2.5" level="project" />
<orderEntry type="library" name="Maven: javax.validation:validation-api:2.0.1.Final" level="project" />
</component>
</module>

View file

@ -84,5 +84,10 @@
<version>${javax.servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,26 @@
package name.lkk.common.enume;
public enum OrderStatusEnum {
CREATE_NEW(0,"待付款"),
PAYED(1,"已付款"),
SENDED(2,"已发货"),
RECIEVED(3,"已完成"),
CANCLED(4,"已取消"),
SERVICING(5,"售后中"),
SERVICED(6,"售后完成");
private Integer code;
private String msg;
OrderStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}

View file

@ -0,0 +1,50 @@
package name.lkk.common.exception;
/**
* <p>Title: BizCodeEnum</p>
* Description
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景最后三位表示错误码例如10000110:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述将他们定义为枚举形式
* 错误码列表
* 10: 通用
* 001参数格式校验
* 002: 短信验证码频率太高
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
* 15: 用户
* 21: 库存
*/
public enum BizCodeEnum {
UNKNOW_EXCEPTION(10000, "系统未知异常"),
VAILD_EXCEPTION(10001, "参数格式校验失败"),
SMS_CODE_EXCEPTION(10002, "验证码获取频率太高,稍后再试"),
TO_MANY_REQUEST(10003, "请求流量过大"),
SMS_SEND_CODE_EXCEPTION(10403, "短信发送失败"),
USER_EXIST_EXCEPTION(15001, "用户已经存在"),
PHONE_EXIST_EXCEPTION(15002, "手机号已经存在"),
LOGINACTT_PASSWORD_ERROR(15003, "账号或密码错误"),
SOCIALUSER_LOGIN_ERROR(15004, "社交账号登录失败"),
NOT_STOCK_EXCEPTION(21000, "商品库存不足"),
PRODUCT_UP_EXCEPTION(11000,"商品上架异常");
private int code;
private String msg;
BizCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}

View file

@ -0,0 +1,23 @@
package name.lkk.common.exception;
/**
* <p>Title: NotStockException</p>
* Description
* date2020/7/2 11:43
*/
public class NotStockException extends RuntimeException{
private Long skuId;
public NotStockException(String msg) {
super(msg + "号商品没有足够的库存了");
}
public Long getSkuId() {
return skuId;
}
public void setSkuId(Long skuId) {
this.skuId = skuId;
}
}

View file

@ -0,0 +1,7 @@
package name.lkk.common.valid;
/**
* 新增分组
*/
public interface AddGroup {
}

View file

@ -0,0 +1,30 @@
package name.lkk.common.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* <p>Title: ListValue</p>
* DescriptionJSR303自定义注解 必须有前三个方法
* date2020/6/1 23:25
*/
@Documented
// 指定校验器 这里可以指定多个不同的校验器
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.firenay.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}

View file

@ -0,0 +1,40 @@
package name.lkk.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* <p>Title: ListValueConstraintValidator</p>
* Description校验器规定ListValue这个注解 用于校验 Integer 类型的数据
* POSTman :{"name":"aaa","logo":"https://github.com/1046762075","sort":0,"firstLetter":"d","showStatus":0}
* date2020/6/1 23:33
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
/**
* set 里面就是使用注解时规定的值, 例如: @ListValue(vals = {0,1}) set= {0,1}
*/
private Set<Integer> set = new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
/**
* 判断是否校验成功
* @param value 需要校验的值
* 判断这个值再set里面没
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}

View file

@ -0,0 +1,7 @@
package name.lkk.common.valid;
/**
* 修改分组
*/
public interface UpdateGroup {
}

View file

@ -0,0 +1,4 @@
package name.lkk.common.valid;
public interface UpdateStatusGroup {
}

View file

@ -0,0 +1 @@
com.firenay.common.valid.ListValue.message=\u5FC5\u987B\u63D0\u4EA4\u6307\u5B9A\u7684\u503C [0,1]

View file

@ -38,6 +38,14 @@ spring:
- Path=/api/ware/**
filters:
- RewritePath=/api/(?<segment>.*), /$\{segment}
# OSS签名的转发
- id: third-server_route
uri: lb://kkmall-third-server
predicates:
- Path=/api/third/server/**
filters:
- RewritePath=/api/(?<segment>.*), /$\{segment}
#过滤器要放最下面才不会被替换路径/api/*路径
- id: admin_route
uri: lb://kkmall-admin

View file

@ -1,14 +1,15 @@
package name.lkk.kkmall.product.controller;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import name.lkk.common.valid.AddGroup;
import name.lkk.common.valid.UpdateGroup;
import name.lkk.common.valid.UpdateStatusGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import name.lkk.kkmall.product.entity.BrandEntity;
import name.lkk.kkmall.product.service.BrandService;
@ -53,12 +54,19 @@ public class BrandController {
return R.ok().put("brand", brand);
}
@GetMapping("/infos")
public R info(@RequestParam("brandIds") List<Long> brandIds) {
List<BrandEntity> brand = brandService.getBrandByIds(brandIds);
return R.ok().put("data", brand);
}
/**
* 保存
* 保存 开启JSR303校验 规定这是新增分组 实现新增的规则
* POSTman{"name":"aaa","logo":"abc","brandId":1}
* POSTman :{"name":"aaa","logo":"https://github.com/1046762075","sort":0,"firstLetter":"d","showStatus":0}
*/
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@RequestBody BrandEntity brand){
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand) {
brandService.save(brand);
return R.ok();
@ -66,12 +74,20 @@ public class BrandController {
/**
* 修改
* POSTman{"name":"aaa","logo":"abc"}
*/
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@RequestBody BrandEntity brand){
brandService.updateById(brand);
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand) {
brandService.updateDetail(brand);
return R.ok();
}
/**
* 修改状态
*/
@RequestMapping("/update/status")
public R updateStatus(@Validated(UpdateStatusGroup.class) @RequestBody BrandEntity brand) {
brandService.updateById(brand);
return R.ok();
}

View file

@ -2,35 +2,52 @@ package name.lkk.kkmall.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import name.lkk.common.valid.AddGroup;
import name.lkk.common.valid.ListValue;
import name.lkk.common.valid.UpdateGroup;
import name.lkk.common.valid.UpdateStatusGroup;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
/**
* 品牌
*
* @author KirkLin
* @email linkirk@163.com
* @date 2021-06-07 15:14:37
* 自定义JSR303校验
* 根据分组进行校验 Controller里面要进行规定
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
* POSTman{"name":"aaa","logo":"abc","brandId":1}
*/
@NotNull(message = "修改必须定制品牌id", groups = {UpdateGroup.class})
@Null(message = "新增不能指定id", groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})
private String name;
/**
* 品牌logo地址
* 品牌logo地址 修改可以不带上logoURL
*/
@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的URL地址", groups={AddGroup.class, UpdateGroup.class})
private String logo;
/**
* 介绍
@ -39,14 +56,21 @@ public class BrandEntity implements Serializable {
/**
* 显示状态[0-不显示1-显示]
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals = {0,1}, groups = {AddGroup.class, UpdateGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
/**
* 检索首字母
* 检索首字母 修改可以不带, 不管是新增还是修改都必须是一个字母
*/
@NotEmpty(groups = {AddGroup.class})
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母", groups = {AddGroup.class, UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(groups = {AddGroup.class})
@Min(value = 0, message = "排序必须是一个正整数" , groups = {AddGroup.class, UpdateGroup.class})
private Integer sort;
}

View file

@ -0,0 +1,45 @@
package name.lkk.kkmall.product.exception;
import lombok.extern.slf4j.Slf4j;
import name.lkk.common.exception.BizCodeEnum;
import name.lkk.common.utils.R;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.support.WebExchangeBindException;
import java.util.HashMap;
import java.util.Map;
/**
* <p>Title: MallExceptionControllerAdvice</p>
* Description集中处理所有异常
* date2020/6/1 21:19
* @author kirklin
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.firenay.mall.product.controller")
public class MallExceptionControllerAdvice {
@ExceptionHandler(value = {WebExchangeBindException.class})
public R handleVaildException(WebExchangeBindException e) {
log.error("数据校验出现问题{},异常类型:{}", e.getMessage(), e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String, String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError) -> {
// 错误字段 错误提示
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(), BizCodeEnum.VAILD_EXCEPTION.getMsg()).put("data", errorMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable) {
log.error("错误:", throwable);
return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(), BizCodeEnum.UNKNOW_EXCEPTION.getMsg());
}
}

View file

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import name.lkk.common.utils.PageUtils;
import name.lkk.kkmall.product.entity.BrandEntity;
import java.util.List;
import java.util.Map;
/**
@ -16,5 +17,13 @@ import java.util.Map;
public interface BrandService extends IService<BrandEntity> {
PageUtils queryPage(Map<String, Object> params);
/**
* 当品牌进行更新的时候 保证关联表的数据也需要进行更新
*/
void updateDetail(BrandEntity brand);
List<BrandEntity> getBrandByIds(List<Long> brandIds);
}

View file

@ -16,5 +16,8 @@ import java.util.Map;
public interface CategoryBrandRelationService extends IService<CategoryBrandRelationEntity> {
PageUtils queryPage(Map<String, Object> params);
void updateBrand(Long brandId, String name);
}

View file

@ -1,6 +1,10 @@
package name.lkk.kkmall.product.service.impl;
import name.lkk.kkmall.product.service.CategoryBrandRelationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -11,10 +15,14 @@ import name.lkk.common.utils.Query;
import name.lkk.kkmall.product.dao.BrandDao;
import name.lkk.kkmall.product.entity.BrandEntity;
import name.lkk.kkmall.product.service.BrandService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Service("brandService")
public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> implements BrandService {
@Autowired
private CategoryBrandRelationService categoryBrandRelationService;
@Override
public PageUtils queryPage(Map<String, Object> params) {
@ -26,4 +34,24 @@ public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> impleme
return new PageUtils(page);
}
/**
* 当品牌进行更新的时候 保证关联表的数据也需要进行更新
*/
@Transactional
@Override
public void updateDetail(BrandEntity brand) {
// 保证冗余字段的数据一致
this.updateById(brand);
if(!StringUtils.isEmpty(brand.getName())){
// 同步更新其他关联表的数据
categoryBrandRelationService.updateBrand(brand.getBrandId(), brand.getName());
// TODO 更新其它关联
}
}
@Override
public List<BrandEntity> getBrandByIds(List<Long> brandIds) {
return baseMapper.selectList(new QueryWrapper<BrandEntity>().in("brand_id",brandIds));
}
}

View file

@ -1,5 +1,6 @@
package name.lkk.kkmall.product.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.stereotype.Service;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@ -26,4 +27,13 @@ public class CategoryBrandRelationServiceImpl extends ServiceImpl<CategoryBrandR
return new PageUtils(page);
}
@Override
public void updateBrand(Long brandId, String name) {
CategoryBrandRelationEntity entity = new CategoryBrandRelationEntity();
entity.setBrandId(brandId);
entity.setBrandName(name);
// 将所有品牌id为 brandId 的进行更新
this.update(entity, new UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId));
}
}

View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>name.lkk.kkmall</groupId>
<artifactId>kkmall-third-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>kkmall-third-server</name>
<description>第三方服务</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>name.lkk.kkmall</groupId>
<artifactId>kkmall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<!-- 排除 Mybatis-Plus -->
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,15 @@
package name.lkk.kkmall.thirdserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class KkmallThirdServerApplication {
public static void main(String[] args) {
SpringApplication.run(KkmallThirdServerApplication.class, args);
}
}

View file

@ -0,0 +1,71 @@
package name.lkk.kkmall.thirdserver.controller;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import name.lkk.common.utils.R;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 生成后端签名
*/
@RestController
public class OSSController {
@Resource
OSS ossClient;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
private PolicyConditions policyConds;
@RequestMapping("/third/server/oss/policy")
public R policy() {
String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
// callbackUrl为 上传回调服务器的URL请将下面的IP和Port配置为您自己的真实信息
String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String dir = format + "/"; // 用户上传文件时指定的前缀
Map<String, String> respMap = null;
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap = new LinkedHashMap<>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
} catch (Exception e) {
System.out.println(e.getMessage());
}
return R.ok().put("data", respMap);
}
}

View file

@ -0,0 +1 @@
management.endpoints.web.exposure.include=*

View file

@ -0,0 +1,15 @@
spring:
application:
name: kkmall-third-server
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
alicloud:
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
bucket: mall-kk
access-key: LTAI5tCHtotRisgLvCsp5pv5
secret-key: zWiSQOcz5faTSkropwNssB7rqgMYmV
server:
port: 30000

View file

@ -0,0 +1,37 @@
package name.lkk.kkmall.thirdserver;
import com.aliyun.oss.OSSClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@SpringBootTest
class KkmallThirdServerApplicationTests {
@Autowired
private OSSClient ossClient;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucketName;
@Test
void contextLoads() {
}
@Test
public void testFindPath() throws IOException {
// 上传网络流
InputStream inputStream = new FileInputStream("/Users/kirklin/Pictures/ideabg.jpeg");
ossClient.putObject(bucketName, "ideabg.jpeg", inputStream);
// 关闭OSSClient
ossClient.shutdown();
System.out.println("上传成功");
}
}

View file

@ -9,14 +9,15 @@ function resolve (dir) {
}
const createLintingRule = () => ({
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter'),
emitWarning: !config.dev.showEslintErrorsInOverlay
}
//关闭语法检查
// test: /\.(js|vue)$/,
// loader: 'eslint-loader',
// enforce: 'pre',
// include: [resolve('src'), resolve('test')],
// options: {
// formatter: require('eslint-friendly-formatter'),
// emitWarning: !config.dev.showEslintErrorsInOverlay
// }
})
module.exports = {

View file

@ -0,0 +1,128 @@
<template>
<div>
<el-upload
action="http://mall-kk.oss-cn-hangzhou.aliyuncs.com"
:data="dataObj"
:list-type="listType"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview"
:limit="maxCount"
:on-exceed="handleExceed"
:show-file-list="showFile"
>
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt />
</el-dialog>
</div>
</template>
<script>
import { policy } from "./policy";
import { getUUID } from '@/utils'
export default {
name: "multiUpload",
props: {
//
value: Array,
//
maxCount: {
type: Number,
default: 30
},
listType:{
type: String,
default: "picture-card"
},
showFile:{
type: Boolean,
default: true
}
},
data() {
return {
dataObj: {
policy: "",
signature: "",
key: "",
ossaccessKeyId: "",
dir: "",
host: "",
uuid: ""
},
dialogVisible: false,
dialogImageUrl: null
};
},
computed: {
fileList() {
let fileList = [];
for (let i = 0; i < this.value.length; i++) {
fileList.push({ url: this.value[i] });
}
return fileList;
}
},
mounted() {},
methods: {
emitInput(fileList) {
let value = [];
for (let i = 0; i < fileList.length; i++) {
value.push(fileList[i].url);
}
this.$emit("input", value);
},
handleRemove(file, fileList) {
this.emitInput(fileList);
},
handlePreview(file) {
this.dialogVisible = true;
this.dialogImageUrl = file.url;
},
beforeUpload(file) {
let _self = this;
return new Promise((resolve, reject) => {
policy()
.then(response => {
// console.log("${filename}");
_self.dataObj.policy = response.data.policy;
_self.dataObj.signature = response.data.signature;
_self.dataObj.ossaccessKeyId = response.data.accessid;
_self.dataObj.key = response.data.dir +getUUID()+"_${filename}";
_self.dataObj.dir = response.data.dir;
_self.dataObj.host = response.data.host;
resolve(true);
})
.catch(err => {
console.log("出错了...",err)
reject(false);
});
});
},
handleUploadSuccess(res, file) {
this.fileList.push({
name: file.name,
// url: this.dataObj.host + "/" + this.dataObj.dir + "/" + file.name ${filename}
url: this.dataObj.host + "/" + this.dataObj.key.replace("${filename}",file.name)
});
this.emitInput(this.fileList);
},
handleExceed(files, fileList) {
this.$message({
message: "最多只能上传" + this.maxCount + "张图片",
type: "warning",
duration: 1000
});
}
}
};
</script>
<style>
</style>

View file

@ -0,0 +1,12 @@
import http from '@/utils/httpRequest.js'
export function policy() {
return new Promise((resolve,reject)=>{
http({
url: http.adornUrl("/third/server/oss/policy"),
method: "get",
params: http.adornParams({})
}).then(({ data }) => {
resolve(data);
})
});
}

View file

@ -0,0 +1,121 @@
<template>
<div>
<el-upload
action="http://mall-kk.oss-cn-hangzhou.aliyuncs.com"
:data="dataObj"
list-type="picture"
:multiple="false"
:show-file-list="showFileList"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件且不超过5MB</div>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="fileList[0].url" alt />
</el-dialog>
</div>
</template>
<script>
import { policy } from "./policy";
import { getUUID } from "@/utils";
export default {
name: "singleUpload",
props: {
value: String
},
computed: {
imageUrl() {
return this.value;
},
imageName() {
if (this.value != null && this.value !== "") {
return this.value.substr(this.value.lastIndexOf("/") + 1);
} else {
return null;
}
},
fileList() {
return [
{
name: this.imageName,
url: this.imageUrl
}
];
},
showFileList: {
get: function() {
return (
this.value !== null && this.value !== "" && this.value !== undefined
);
},
set: function(newValue) {}
}
},
data() {
return {
dataObj: {
policy: "",
signature: "",
key: "",
ossaccessKeyId: "",
dir: "",
host: ""
// callback:'',
},
dialogVisible: false
};
},
methods: {
emitInput(val) {
this.$emit("input", val);
},
handleRemove(file, fileList) {
this.emitInput("");
},
handlePreview(file) {
this.dialogVisible = true;
},
beforeUpload(file) {
let _self = this;
return new Promise((resolve, reject) => {
policy()
.then(response => {
_self.dataObj.policy = response.data.policy;
_self.dataObj.signature = response.data.signature;
_self.dataObj.ossaccessKeyId = response.data.accessid;
_self.dataObj.key = response.data.dir + getUUID() + "_${filename}";
_self.dataObj.dir = response.data.dir;
_self.dataObj.host = response.data.host;
// console.log("", _self.dataObj);
resolve(true);
})
.catch(err => {
reject(false);
});
});
},
handleUploadSuccess(res, file) {
this.showFileList = true;
this.fileList.pop();
this.fileList.push({
name: file.name,
url:
this.dataObj.host +
"/" +
this.dataObj.key.replace("${filename}", file.name)
});
this.emitInput(this.fileList[0].url);
}
}
};
</script>
<style>
</style>

View file

@ -16,7 +16,8 @@ export function getUUID () {
* @param {*} key
*/
export function isAuth (key) {
return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
//return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
return true;
}
/**

View file

@ -0,0 +1,80 @@
<template>
<div>
<el-select placeholder="请选择" v-model="brandId" filterable clearable>
<el-option
v-for="item in brands"
:key="item.brandId"
:label="item.brandName"
:value="item.brandId"
></el-option>
</el-select>
</div>
</template>
<script>
//jsjsjson
//import  from '';
export default {
//import使
components: {},
props: {},
data() {
//
return {
catId: 0,
brands: [
{
label: "a",
value: 1
}
],
brandId: "",
subscribe: null
};
},
// data
computed: {},
//data
watch: {
brandId(val) {
this.PubSub.publish("brandId", val);
}
},
//
methods: {
getCatBrands() {
this.$http({
url: this.$http.adornUrl("/product/categorybrandrelation/brands/list"),
method: "get",
params: this.$http.adornParams({
catId: this.catId
})
}).then(({ data }) => {
this.brands = data.data;
});
}
},
// - 访this
created() {},
// - 访DOM
mounted() {
//
this.subscribe = PubSub.subscribe("catPath", (msg, val) => {
this.catId = val[val.length - 1];
this.getCatBrands();
});
},
beforeCreate() {}, // - 
beforeMount() {}, // - 
beforeUpdate() {}, // - 
updated() {}, // - 
beforeDestroy() {
PubSub.unsubscribe(this.subscribe); //
}, // - 
destroyed() {}, // - 
activated() {} //keep-alive
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,76 @@
<template>
<!--
使用说明
1引入category-cascader.vue
2语法<category-cascader :catelogPath.sync="catelogPath"></category-cascader>
解释
catelogPath指定的值是cascader初始化需要显示的值应该和父组件的catelogPath绑定;
由于有sync修饰符所以cascader路径变化以后自动会修改父的catelogPath这是结合子组件this.$emit("update:catelogPath",v);做的
-->
<div>
<el-cascader
filterable
clearable
placeholder="试试搜索:手机"
v-model="paths"
:options="categorys"
:props="setting"
></el-cascader>
</div>
</template>
<script>
export default {
//import使
components: {},
//
props: {
catelogPath: {
type: Array,
default(){
return [];
}
}
},
data() {
//
return {
setting: {
value: "catId",
label: "name",
children: "children"
},
categorys: [],
paths: this.catelogPath
};
},
watch:{
catelogPath(v){
this.paths = this.catelogPath;
},
paths(v){
this.$emit("update:catelogPath",v);
//使pubsub-js
this.PubSub.publish("catPath",v);
}
},
//
methods: {
getCategorys() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get"
}).then(({ data }) => {
this.categorys = data.data;
});
}
},
// - 访this
created() {
this.getCategorys();
}
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,80 @@
<template>
<div>
<el-input placeholder="输入关键字进行过滤" v-model="filterText"></el-input>
<el-tree
:data="menus"
:props="defaultProps"
node-key="catId"
ref="menuTree"
@node-click="nodeclick"
:filter-node-method="filterNode"
:highlight-current = "true"
></el-tree>
</div>
</template>
<script>
export default {
//import使
components: {},
props: {},
data() {
//
return {
filterText: "",
menus: [],
expandedKey: [],
defaultProps: {
children: "children",
label: "name"
}
};
},
// data
computed: {},
//data
watch: {
filterText(val) {
this.$refs.menuTree.filter(val);
}
},
//
methods: {
//
filterNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
},
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get"
}).then(({ data }) => {
this.menus = data.data;
});
},
nodeclick(data, node, component) {
// console.log("category", data, node, component);
//
this.$emit("tree-node-click", data, node, component);
}
},
// - 访this
created() {
this.getMenus();
},
// - 访DOM
mounted() {},
beforeCreate() {}, // - 
beforeMount() {}, // - 
beforeUpdate() {}, // - 
updated() {}, // - 
beforeDestroy() {}, // - 
destroyed() {}, // - 
activated() {} //keep-alive
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,283 @@
<template>
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible"
@closed="dialogClose"
>
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" label-width="120px">
<!-- @keyup.enter.native="dataFormSubmit()" -->
<el-form-item label="属性名" prop="attrName">
<el-input v-model="dataForm.attrName" placeholder="属性名"></el-input>
</el-form-item>
<el-form-item label="属性类型" prop="attrType">
<el-select v-model="dataForm.attrType" placeholder="请选择">
<el-option label="规格参数" :value="1"></el-option>
<el-option label="销售属性" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="值类型" prop="valueType">
<el-switch
v-model="dataForm.valueType"
active-text="允许多个值"
inactive-text="只能单个值"
active-color="#13ce66"
inactive-color="#ff4949"
:inactive-value="0"
:active-value="1"
></el-switch>
</el-form-item>
<el-form-item label="可选值" prop="valueSelect">
<!-- <el-input v-model="dataForm.valueSelect"></el-input> -->
<el-select
v-model="dataForm.valueSelect"
multiple
filterable
allow-create
placeholder="请输入内容"
></el-select>
</el-form-item>
<el-form-item label="属性图标" prop="icon">
<el-input v-model="dataForm.icon" placeholder="属性图标"></el-input>
</el-form-item>
<el-form-item label="所属分类" prop="catelogId">
<category-cascader :catelogPath.sync="catelogPath"></category-cascader>
</el-form-item>
<el-form-item label="所属分组" prop="attrGroupId" v-if="type == 1">
<el-select ref="groupSelect" v-model="dataForm.attrGroupId" placeholder="请选择">
<el-option
v-for="item in attrGroups"
:key="item.attrGroupId"
:label="item.attrGroupName"
:value="item.attrGroupId"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="可检索" prop="searchType" v-if="type == 1">
<el-switch
v-model="dataForm.searchType"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
<el-form-item label="快速展示" prop="showDesc" v-if="type == 1">
<el-switch
v-model="dataForm.showDesc"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
<el-form-item label="启用状态" prop="enable">
<el-switch
v-model="dataForm.enable"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
</span>
</el-dialog>
</template>
<script>
import CategoryCascader from "../common/category-cascader";
export default {
data() {
return {
visible: false,
dataForm: {
attrId: 0,
attrName: "",
searchType: 0,
valueType: 1,
icon: "",
valueSelect: "",
attrType: 1,
enable: 1,
catelogId: "",
attrGroupId: "",
showDesc: 0
},
catelogPath: [],
attrGroups: [],
dataRule: {
attrName: [
{ required: true, message: "属性名不能为空", trigger: "blur" }
],
searchType: [
{
required: true,
message: "是否需要检索不能为空",
trigger: "blur"
}
],
valueType: [
{
required: true,
message: "值类型不能为空",
trigger: "blur"
}
],
icon: [
{ required: true, message: "属性图标不能为空", trigger: "blur" }
],
attrType: [
{
required: true,
message: "属性类型不能为空",
trigger: "blur"
}
],
enable: [
{
required: true,
message: "启用状态不能为空",
trigger: "blur"
}
],
catelogId: [
{
required: true,
message: "需要选择正确的三级分类数据",
trigger: "blur"
}
],
showDesc: [
{
required: true,
message: "快速展示不能为空",
trigger: "blur"
}
]
}
};
},
props:{
type:{
type: Number,
default: 1
}
},
watch: {
catelogPath(path) {
//
// console.log("", path);
this.attrGroups = [];
this.dataForm.attrGroupId = "";
this.dataForm.catelogId = path[path.length - 1];
if (path && path.length == 3) {
this.$http({
url: this.$http.adornUrl(
`/product/attrgroup/list/${path[path.length - 1]}`
),
method: "get",
params: this.$http.adornParams({ page: 1, limit: 10000000 })
}).then(({ data }) => {
if (data && data.code === 0) {
this.attrGroups = data.page.list;
} else {
this.$message.error(data.msg);
}
});
} else if (path.length == 0) {
this.dataForm.catelogId = "";
} else {
this.$message.error("请选择正确的分类");
this.dataForm.catelogId = "";
}
}
},
components: { CategoryCascader },
methods: {
init(id) {
this.dataForm.attrId = id || 0;
this.dataForm.attrType = this.type;
this.visible = true;
this.$nextTick(() => {
this.$refs["dataForm"].resetFields();
if (this.dataForm.attrId) {
this.$http({
url: this.$http.adornUrl(
`/product/attr/info/${this.dataForm.attrId}`
),
method: "get",
params: this.$http.adornParams()
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataForm.attrName = data.attr.attrName;
this.dataForm.searchType = data.attr.searchType;
this.dataForm.valueType = data.attr.valueType;
this.dataForm.icon = data.attr.icon;
this.dataForm.valueSelect = data.attr.valueSelect.split(";");
this.dataForm.attrType = data.attr.attrType;
this.dataForm.enable = data.attr.enable;
this.dataForm.catelogId = data.attr.catelogId;
this.dataForm.showDesc = data.attr.showDesc;
//attrGroupId
//catelogPath
this.catelogPath = data.attr.catelogPath;
this.$nextTick(() => {
this.dataForm.attrGroupId = data.attr.attrGroupId;
});
}
});
}
});
},
//
dataFormSubmit() {
this.$refs["dataForm"].validate(valid => {
if (valid) {
this.$http({
url: this.$http.adornUrl(
`/product/attr/${!this.dataForm.attrId ? "save" : "update"}`
),
method: "post",
data: this.$http.adornData({
attrId: this.dataForm.attrId || undefined,
attrName: this.dataForm.attrName,
searchType: this.dataForm.searchType,
valueType: this.dataForm.valueType,
icon: this.dataForm.icon,
valueSelect: this.dataForm.valueSelect.join(";"),
attrType: this.dataForm.attrType,
enable: this.dataForm.enable,
catelogId: this.dataForm.catelogId,
attrGroupId: this.dataForm.attrGroupId,
showDesc: this.dataForm.showDesc
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.visible = false;
this.$emit("refreshDataList");
}
});
} else {
this.$message.error(data.msg);
}
});
}
});
},
//dialogClose
dialogClose() {
this.catelogPath = [];
}
}
};
</script>

View file

@ -0,0 +1,245 @@
<template>
<div>
<el-dialog :close-on-click-modal="false" :visible.sync="visible" @closed="dialogClose">
<el-dialog width="40%" title="选择属性" :visible.sync="innerVisible" append-to-body>
<div>
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form-item>
<el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()">查询</el-button>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="innerSelectionChangeHandle"
style="width: 100%;"
>
<el-table-column type="selection" header-align="center" align="center"></el-table-column>
<el-table-column prop="attrId" header-align="center" align="center" label="属性id"></el-table-column>
<el-table-column prop="attrName" header-align="center" align="center" label="属性名"></el-table-column>
<el-table-column prop="icon" header-align="center" align="center" label="属性图标">
<template slot-scope="scope">
<img :src="scope.row.logo" style="width: 60px; height: 60px" />
</template>
</el-table-column>
<el-table-column prop="valueSelect" header-align="center" align="center" label="可选值列表"></el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
></el-pagination>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="innerVisible = false"> </el-button>
<el-button type="primary" @click="submitAddRealtion">确认新增</el-button>
</div>
</el-dialog>
<el-row>
<el-col :span="24">
<el-button type="primary" @click="addRelation">新建关联</el-button>
<el-button
type="danger"
@click="batchDeleteRelation"
:disabled="dataListSelections.length <= 0"
>批量删除</el-button>
<!-- -->
<el-table
:data="relationAttrs"
style="width: 100%"
@selection-change="selectionChangeHandle"
border
>
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<el-table-column prop="attrId" label="#"></el-table-column>
<el-table-column prop="attrName" label="属性名"></el-table-column>
<el-table-column prop="valueSelect" label="可选值">
<template slot-scope="scope">
<el-tooltip placement="top">
<div slot="content">
<span v-for="(i,index) in scope.row.valueSelect.split(';')" :key="index">
{{i}}
<br />
</span>
</div>
<el-tag>{{scope.row.valueSelect.split(";")[0]+" ..."}}</el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column fixed="right" header-align="center" align="center" label="操作">
<template slot-scope="scope">
<el-button type="text" size="small" @click="relationRemove(scope.row.attrId)">移除</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script>
//jsjsjson
//import  from '';
export default {
//import使
components: {},
props: {},
data() {
//
return {
attrGroupId: 0,
visible: false,
innerVisible: false,
relationAttrs: [],
dataListSelections: [],
dataForm: {
key: ""
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
innerdataListSelections: []
};
},
// data
computed: {},
//data
watch: {},
//
methods: {
selectionChangeHandle(val) {
this.dataListSelections = val;
},
innerSelectionChangeHandle(val) {
this.innerdataListSelections = val;
},
addRelation() {
this.getDataList();
this.innerVisible = true;
},
batchDeleteRelation(val) {
let postData = [];
this.dataListSelections.forEach(item => {
postData.push({ attrId: item.attrId, attrGroupId: this.attrGroupId });
});
this.$http({
url: this.$http.adornUrl("/product/attrgroup/attr/relation/delete"),
method: "post",
data: this.$http.adornData(postData, false)
}).then(({ data }) => {
if (data.code == 0) {
this.$message({ type: "success", message: "删除成功" });
this.init(this.attrGroupId);
} else {
this.$message({ type: "error", message: data.msg });
}
}).catch(() => {});
},
//
relationRemove(attrId) {
let data = [];
data.push({ attrId, attrGroupId: this.attrGroupId });
this.$http({
url: this.$http.adornUrl("/product/attrgroup/attr/relation/delete"),
method: "post",
data: this.$http.adornData(data, false)
}).then(({ data }) => {
if (data.code == 0) {
this.$message({ type: "success", message: "删除成功" });
this.init(this.attrGroupId);
} else {
this.$message({ type: "error", message: data.msg });
}
}).catch(() => {});
},
submitAddRealtion() {
this.innerVisible = false;
//
// console.log("", this.innerdataListSelections);
if (this.innerdataListSelections.length > 0) {
let postData = [];
this.innerdataListSelections.forEach(item => {
postData.push({ attrId: item.attrId, attrGroupId: this.attrGroupId });
});
this.$http({
url: this.$http.adornUrl("/product/attrgroup/attr/relation"),
method: "post",
data: this.$http.adornData(postData, false)
}).then(({ data }) => {
if (data.code == 0) {
this.$message({ type: "success", message: "新增关联成功" });
}
this.$emit("refreshData");
this.init(this.attrGroupId);
}).catch(() => {});
} else {
}
},
init(id) {
this.attrGroupId = id || 0;
this.visible = true;
this.$http({
url: this.$http.adornUrl(
"/product/attrgroup/" + this.attrGroupId + "/attr/relation"
),
method: "get",
params: this.$http.adornParams({})
}).then(({ data }) => {
this.relationAttrs = data.data;
}).catch(() => {});
},
dialogClose() {},
//========
//
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl(
"/product/attrgroup/" + this.attrGroupId + "/noattr/relation"
),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.data.list;
this.totalPage = data.data.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
}).catch(() => {});
},
//
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
//
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
}
}
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,159 @@
<template>
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible"
@closed="dialogClose"
>
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="dataFormSubmit()"
label-width="120px"
>
<el-form-item label="组名" prop="attrGroupName">
<el-input v-model="dataForm.attrGroupName" placeholder="组名"></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="dataForm.sort" placeholder="排序"></el-input>
</el-form-item>
<el-form-item label="描述" prop="descript">
<el-input v-model="dataForm.descript" placeholder="描述"></el-input>
</el-form-item>
<el-form-item label="组图标" prop="icon">
<el-input v-model="dataForm.icon" placeholder="组图标"></el-input>
</el-form-item>
<el-form-item label="所属分类" prop="catelogId">
<!-- filterable:让这个选择器可以搜索 -->
<!-- <el-cascader filterable placeholder="试试搜索:手机" v-model="catelogPath" :options="categorys" :props="props"></el-cascader> -->
<!-- :catelogPath="catelogPath"自定义绑定的属性可以给子组件传值 -->
<category-cascader :catelogPath.sync="catelogPath"></category-cascader>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
</span>
</el-dialog>
</template>
<script>
import CategoryCascader from "../common/category-cascader";
export default {
data() {
return {
props: {
value: "catId",
label: "name",
children: "children"
},
visible: false,
categorys: [],
catelogPath: [],
dataForm: {
attrGroupId: 0,
attrGroupName: "",
sort: "",
descript: "",
icon: "",
catelogId: 0
},
dataRule: {
attrGroupName: [
{ required: true, message: "组名不能为空", trigger: "blur" }
],
sort: [{ required: true, message: "排序不能为空", trigger: "blur" }],
descript: [
{ required: true, message: "描述不能为空", trigger: "blur" }
],
icon: [{ required: true, message: "组图标不能为空", trigger: "blur" }],
catelogId: [
{ required: true, message: "所属分类id不能为空", trigger: "blur" }
]
}
};
},
components: { CategoryCascader },
methods: {
dialogClose() {
this.catelogPath = [];
},
getCategorys() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get"
}).then(({ data }) => {
this.categorys = data.data;
});
},
init(id) {
this.dataForm.attrGroupId = id || 0;
this.visible = true;
this.$nextTick(() => {
this.$refs["dataForm"].resetFields();
if (this.dataForm.attrGroupId) {
this.$http({
url: this.$http.adornUrl(
`/product/attrgroup/info/${this.dataForm.attrGroupId}`
),
method: "get",
params: this.$http.adornParams()
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataForm.attrGroupName = data.attrGroup.attrGroupName;
this.dataForm.sort = data.attrGroup.sort;
this.dataForm.descript = data.attrGroup.descript;
this.dataForm.icon = data.attrGroup.icon;
this.dataForm.catelogId = data.attrGroup.catelogId;
// catelogId
this.catelogPath = data.attrGroup.catelogPath;
}
});
}
});
},
//
dataFormSubmit() {
this.$refs["dataForm"].validate(valid => {
if (valid) {
this.$http({
url: this.$http.adornUrl(
`/product/attrgroup/${
!this.dataForm.attrGroupId ? "save" : "update"
}`
),
method: "post",
data: this.$http.adornData({
attrGroupId: this.dataForm.attrGroupId || undefined,
attrGroupName: this.dataForm.attrGroupName,
sort: this.dataForm.sort,
descript: this.dataForm.descript,
icon: this.dataForm.icon,
catelogId: this.catelogPath[this.catelogPath.length - 1]
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.visible = false;
this.$emit("refreshDataList");
}
});
} else {
this.$message.error(data.msg);
}
});
}
});
}
},
created() {
this.getCategorys();
}
};
</script>

View file

@ -0,0 +1,225 @@
<template>
<el-row :gutter="20">
<el-col :span="6">
<category @tree-node-click="treenodeclick"></category>
</el-col>
<el-col :span="18">
<div class="mod-config">
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form-item>
<el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()">查询</el-button>
<el-button type="success" @click="getAllDataList()">查询全部</el-button>
<el-button
v-if="isAuth('product:attrgroup:save')"
type="primary"
@click="addOrUpdateHandle()"
>新增</el-button>
<el-button
v-if="isAuth('product:attrgroup:delete')"
type="danger"
@click="deleteHandle()"
:disabled="dataListSelections.length <= 0"
>批量删除</el-button>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%;"
>
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<el-table-column prop="attrGroupId" header-align="center" align="center" label="分组id"></el-table-column>
<el-table-column prop="attrGroupName" header-align="center" align="center" label="组名"></el-table-column>
<el-table-column prop="sort" header-align="center" align="center" label="排序"></el-table-column>
<el-table-column prop="descript" header-align="center" align="center" label="描述"></el-table-column>
<el-table-column prop="icon" header-align="center" align="center" label="组图标">
<template slot-scope="scope">
<img :src="scope.row.logo" style="width: 60px; height: 60px" />
</template>
</el-table-column>
<el-table-column prop="catelogId" header-align="center" align="center" label="分类id"></el-table-column>
<el-table-column
fixed="right"
header-align="center"
align="center"
width="150"
label="操作"
>
<template slot-scope="scope">
<el-button type="text" size="small" @click="relationHandle(scope.row.attrGroupId)">关联</el-button>
<el-button
type="text"
size="small"
@click="addOrUpdateHandle(scope.row.attrGroupId)"
>修改</el-button>
<el-button type="text" size="small" @click="deleteHandle(scope.row.attrGroupId)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
></el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
<!-- 修改关联关系 -->
<relation-update v-if="relationVisible" ref="relationUpdate" @refreshData="getDataList"></relation-update>
</div>
</el-col>
</el-row>
</template>
<script>
/**
* 父子组件传递数据
* 1)子组件给父组件传递数据事件机制
* 子组件给父组件发送一个事件携带上数据
* // this.$emit("",...)
*/
//jsjsjson
//import  from '';
import Category from "../common/category";
import AddOrUpdate from "./attrgroup-add-or-update";
import RelationUpdate from "./attr-group-relation";
export default {
//import使
components: { Category, AddOrUpdate, RelationUpdate },
props: {},
data() {
return {
catId: 0,
dataForm: {
key: ""
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false,
relationVisible: false
};
},
activated() {
this.getDataList();
},
methods: {
//
relationHandle(groupId) {
this.relationVisible = true;
this.$nextTick(() => {
this.$refs.relationUpdate.init(groupId);
});
},
//
treenodeclick(data, node, component) {
if (node.level == 3) {
this.catId = data.catId;
this.getDataList(); //
}
},
getAllDataList() {
this.catId = 0;
this.getDataList();
},
//
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl(`/product/attrgroup/list/${this.catId}`),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key
})
})
.then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
})
.catch(() => {});
},
//
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
//
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
//
selectionChangeHandle(val) {
this.dataListSelections = val;
},
// /
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true;
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id);
});
},
//
deleteHandle(id) {
var ids = id
? [id]
: this.dataListSelections.map(item => {
return item.attrGroupId;
});
this.$confirm(
`确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}
)
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/attrgroup/delete"),
method: "post",
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.getDataList();
}
});
} else {
this.$message.error(data.msg);
}
});
})
.catch(() => {});
}
}
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,191 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="16">
<el-card class="box-card">
<el-tabs tab-position="left" style="width:98%">
<el-tab-pane
:label="group.attrGroupName"
v-for="(group,gidx) in dataResp.attrGroups"
:key="group.attrGroupId"
>
<!-- 遍历属性,每个tab-pane对应一个表单每个属性是一个表单项 spu.baseAttrs[0] = [{attrId:xx,val:}]-->
<el-form ref="form" :model="dataResp">
<el-form-item
:label="attr.attrName"
v-for="(attr,aidx) in group.attrs"
:key="attr.attrId"
>
<el-input
v-model="dataResp.baseAttrs[gidx][aidx].attrId"
type="hidden"
v-show="false"
></el-input>
<el-select
v-model="dataResp.baseAttrs[gidx][aidx].attrValues"
:multiple="attr.valueType == 1"
filterable
allow-create
default-first-option
placeholder="请选择或输入值"
>
<el-option
v-for="(val,vidx) in attr.valueSelect.split(';')"
:key="vidx"
:label="val"
:value="val"
></el-option>
</el-select>
<el-checkbox
v-model="dataResp.baseAttrs[gidx][aidx].showDesc"
:true-label="1"
:false-label="0"
>快速展示</el-checkbox>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<div style="margin:auto">
<el-button type="success" style="float:right" @click="submitSpuAttrs">确认修改</el-button>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
components: {},
props: {},
data() {
return {
spuId: "",
catalogId: "",
dataResp: {
//
attrGroups: [],
baseAttrs: []
},
spuAttrsMap: {}
};
},
computed: {},
methods: {
clearData(){
this.dataResp.attrGroups = [];
this.dataResp.baseAttrs = [];
this.spuAttrsMap = {};
},
getSpuBaseAttrs() {
this.$http({
url: this.$http.adornUrl(`/product/attr/base/listforspu/${this.spuId}`),
method: "get"
}).then(({ data }) => {
data.data.forEach(item => {
this.spuAttrsMap["" + item.attrId] = item;
});
// console.log("~~~~", this.spuAttrsMap);
});
},
getQueryParams() {
this.spuId = this.$route.query.spuId;
this.catalogId = this.$route.query.catalogId;
// console.log("----", this.spuId, this.catalogId);
},
showBaseAttrs() {
let _this = this;
this.$http({
url: this.$http.adornUrl(
`/product/attrgroup/${this.catalogId}/withattr`
),
method: "get",
params: this.$http.adornParams({})
}).then(({ data }) => {
//baseAttrs
data.data.forEach(item => {
let attrArray = [];
item.attrs.forEach(attr => {
let v = "";
if (_this.spuAttrsMap["" + attr.attrId]) {
v = _this.spuAttrsMap["" + attr.attrId].attrValue.split(";");
if (v.length == 1) {
v = v[0] + "";
}
}
attrArray.push({
attrId: attr.attrId,
attrName: attr.attrName,
attrValues: v,
showDesc: _this.spuAttrsMap["" + attr.attrId]
? _this.spuAttrsMap["" + attr.attrId].quickShow
: attr.showDesc
});
});
this.dataResp.baseAttrs.push(attrArray);
});
this.dataResp.attrGroups = data.data;
});
},
submitSpuAttrs() {
// console.log("·····", this.dataResp.baseAttrs);
//spu_id attr_id attr_name attr_value attr_sort quick_show
let submitData = [];
this.dataResp.baseAttrs.forEach(item => {
item.forEach(attr => {
let val = "";
if (attr.attrValues instanceof Array) {
val = attr.attrValues.join(";");
} else {
val = attr.attrValues;
}
if (val != "") {
submitData.push({
attrId: attr.attrId,
attrName: attr.attrName,
attrValue: val,
quickShow: attr.showDesc
});
}
});
});
this.$confirm("修改商品规格信息, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.$http({
url: this.$http.adornUrl(`/product/attr/update/${this.spuId}`),
method: "post",
data: this.$http.adornData(submitData, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "属性修改成功!"
});
});
})
.catch((e) => {
this.$message({
type: "info",
message: "已取消修改"+e
});
});
}
},
created() {},
activated() {
this.clearData();
this.getQueryParams();
if (this.spuId && this.catalogId) {
this.showBaseAttrs();
this.getSpuBaseAttrs();
}
}
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,267 @@
<template>
<el-row :gutter="20">
<el-col :span="6">
<category @tree-node-click="treenodeclick"></category>
</el-col>
<el-col :span="18">
<div class="mod-config">
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form-item>
<el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()">查询</el-button>
<el-button type="success" @click="getAllDataList()">查询全部</el-button>
<el-button
v-if="isAuth('product:attr:save')"
type="primary"
@click="addOrUpdateHandle()"
>新增</el-button>
<el-button
v-if="isAuth('product:attr:delete')"
type="danger"
@click="deleteHandle()"
:disabled="dataListSelections.length <= 0"
>批量删除</el-button>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%;"
>
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<el-table-column prop="attrId" header-align="center" align="center" label="id"></el-table-column>
<el-table-column prop="attrName" header-align="center" align="center" label="属性名"></el-table-column>
<el-table-column
v-if="attrtype == 1"
prop="searchType"
header-align="center"
align="center"
label="可检索"
>
<template slot-scope="scope">
<i class="el-icon-success" v-if="scope.row.searchType==1"></i>
<i class="el-icon-error" v-else></i>
</template>
</el-table-column>
<el-table-column prop="valueType" header-align="center" align="center" label="值类型">
<template slot-scope="scope">
<el-tag type="success" v-if="scope.row.valueType==0">单选</el-tag>
<el-tag v-else>多选</el-tag>
</template>
</el-table-column>
<el-table-column prop="icon" header-align="center" align="center" label="图标">
<template slot-scope="scope">
<!-- 自定义表格+自定义图片 -->
<img :src="scope.row.logo" style="width: 60px; height: 60px" />
</template>
</el-table-column>
<el-table-column prop="valueSelect" header-align="center" align="center" label="可选值">
<template slot-scope="scope">
<el-tooltip placement="top">
<div slot="content">
<span v-for="(i,index) in scope.row.valueSelect.split(';')" :key="index">
{{i}}
<br />
</span>
</div>
<el-tag>{{scope.row.valueSelect.split(";")[0]+" ..."}}</el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="enable" header-align="center" align="center" label="启用">
<template slot-scope="scope">
<i class="el-icon-success" v-if="scope.row.enable==1"></i>
<i class="el-icon-error" v-else></i>
</template>
</el-table-column>
<el-table-column prop="catelogName" header-align="center" align="center" label="所属分类"></el-table-column>
<el-table-column
v-if="attrtype == 1"
prop="groupName"
header-align="center"
align="center"
label="所属分组"
></el-table-column>
<el-table-column
v-if="attrtype == 1"
prop="showDesc"
header-align="center"
align="center"
label="快速展示"
>
<template slot-scope="scope">
<i class="el-icon-success" v-if="scope.row.showDesc==1"></i>
<i class="el-icon-error" v-else></i>
</template>
</el-table-column>
<el-table-column
fixed="right"
header-align="center"
align="center"
width="150"
label="操作"
>
<template slot-scope="scope">
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.attrId)">修改</el-button>
<el-button type="text" size="small" @click="deleteHandle(scope.row.attrId)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
></el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
:type="attrtype"
v-if="addOrUpdateVisible"
ref="addOrUpdate"
@refreshDataList="getDataList"
></add-or-update>
</div>
</el-col>
</el-row>
</template>
<script>
import Category from "../common/category";
import AddOrUpdate from "./attr-add-or-update";
export default {
//import使
components: { Category, AddOrUpdate },
props: {
attrtype: {
type: Number,
default: 1
}
},
data() {
return {
catId: 0,
type: 1,
dataForm: {
key: ""
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false
};
},
activated() {
this.getDataList();
},
methods: {
//
treenodeclick(data, node, component) {
if (node.level == 3) {
this.catId = data.catId;
this.getDataList(); //
}
},
getAllDataList() {
this.catId = 0;
this.getDataList();
},
//
getDataList() {
this.dataListLoading = true;
let type = this.attrtype == 0 ? "sale" : "base";
this.$http({
url: this.$http.adornUrl(`/product/attr/${type}/list/${this.catId}`),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key
})
})
.then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
})
.catch(() => {});
},
//
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
//
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
//
selectionChangeHandle(val) {
this.dataListSelections = val;
},
// /
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true;
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id);
});
},
//
deleteHandle(id) {
var ids = id
? [id]
: this.dataListSelections.map(item => {
return item.attrId;
});
this.$confirm(
`确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}
)
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/attr/delete"),
method: "post",
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.getDataList();
}
});
} else {
this.$message.error(data.msg);
}
});
})
.catch(() => {});
}
}
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,174 @@
<template>
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible"
>
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="dataFormSubmit()"
label-width="140px"
>
<el-form-item label="品牌名" prop="name">
<el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
</el-form-item>
<el-form-item label="品牌logo地址" prop="logo">
<!-- <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> -->
<single-upload v-model="dataForm.logo"></single-upload>
</el-form-item>
<el-form-item label="介绍" prop="descript">
<el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
</el-form-item>
<el-form-item label="显示状态" prop="showStatus">
<el-switch
v-model="dataForm.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
<el-form-item label="检索首字母" prop="firstLetter">
<el-input v-model="dataForm.firstLetter" placeholder="检索首字母"></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<!-- 传入的必须是一个数字 -->
<el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
</span>
</el-dialog>
</template>
<script>
import SingleUpload from "@/components/upload/singleUpload";
export default {
components: { SingleUpload },
data() {
return {
visible: false,
dataForm: {
brandId: 0,
name: "",
logo: "",
descript: "",
showStatus: 1,
firstLetter: "",
sort: 0
},
dataRule: {
name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
logo: [
{ required: true, message: "品牌logo地址不能为空", trigger: "blur" }
],
descript: [
{ required: true, message: "介绍不能为空", trigger: "blur" }
],
showStatus: [
{
required: true,
message: "显示状态[0-不显示1-显示]不能为空",
trigger: "blur"
}
],
firstLetter: [
{
validator: (rule, value, callback) => {
if (value == "") {
callback(new Error("首字母必须填写"));
} else if (!/^[a-zA-Z]$/.test(value)) {
callback(new Error("首字母必须a-z或者A-Z之间"));
} else {
callback();
}
},
trigger: "blur"
}
],
sort: [
{
validator: (rule, value, callback) => {
if (value == "" && value != 0) {
callback(new Error("排序字段必须填写"));
} else if (!Number.isInteger(value) || value<0) {
callback(new Error("排序必须是一个正整数"));
} else {
callback();
}
},
trigger: "blur"
}
]
}
};
},
methods: {
init(id) {
this.dataForm.brandId = id || 0;
this.visible = true;
this.$nextTick(() => {
this.$refs["dataForm"].resetFields();
if (this.dataForm.brandId) {
this.$http({
url: this.$http.adornUrl(
`/product/brand/info/${this.dataForm.brandId}`
),
method: "get",
params: this.$http.adornParams()
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataForm.name = data.brand.name;
this.dataForm.logo = data.brand.logo;
this.dataForm.descript = data.brand.descript;
this.dataForm.showStatus = data.brand.showStatus;
this.dataForm.firstLetter = data.brand.firstLetter;
this.dataForm.sort = data.brand.sort;
}
});
}
});
},
//
dataFormSubmit() {
this.$refs["dataForm"].validate(valid => {
if (valid) {
this.$http({
url: this.$http.adornUrl(
`/product/brand/${!this.dataForm.brandId ? "save" : "update"}`
),
method: "post",
data: this.$http.adornData({
brandId: this.dataForm.brandId || undefined,
name: this.dataForm.name,
logo: this.dataForm.logo,
descript: this.dataForm.descript,
showStatus: this.dataForm.showStatus,
firstLetter: this.dataForm.firstLetter,
sort: this.dataForm.sort
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.visible = false;
this.$emit("refreshDataList");
}
});
} else {
this.$message.error(data.msg);
}
});
}
});
}
}
};
</script>

View file

@ -0,0 +1,278 @@
<template>
<div class="mod-config">
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form-item>
<el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()">查询</el-button>
<el-button
v-if="isAuth('product:brand:save')"
type="primary"
@click="addOrUpdateHandle()"
>新增</el-button>
<el-button
v-if="isAuth('product:brand:delete')"
type="danger"
@click="deleteHandle()"
:disabled="dataListSelections.length <= 0"
>批量删除</el-button>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%;"
>
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<el-table-column prop="brandId" header-align="center" align="center" label="品牌id"></el-table-column>
<el-table-column prop="name" header-align="center" align="center" label="品牌名"></el-table-column>
<el-table-column prop="logo" header-align="center" align="center" label="品牌logo地址">
<template slot-scope="scope">
<!-- 自定义表格+自定义图片 -->
<img :src="scope.row.logo" style="width: 100px; height: 80px" />
</template>
</el-table-column>
<el-table-column prop="descript" header-align="center" align="center" label="介绍"></el-table-column>
<el-table-column prop="showStatus" header-align="center" align="center" label="显示状态">
<template slot-scope="scope">
<el-switch
v-model="scope.row.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
@change="updateBrandStatus(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column prop="firstLetter" header-align="center" align="center" label="检索首字母"></el-table-column>
<el-table-column prop="sort" header-align="center" align="center" label="排序"></el-table-column>
<el-table-column fixed="right" header-align="center" align="center" width="250" label="操作">
<template slot-scope="scope">
<el-button type="text" size="small" @click="updateCatelogHandle(scope.row.brandId)">关联分类</el-button>
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.brandId)">修改</el-button>
<el-button type="text" size="small" @click="deleteHandle(scope.row.brandId)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
></el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
<el-dialog title="关联分类" :visible.sync="cateRelationDialogVisible" width="30%">
<el-popover placement="right-end" v-model="popCatelogSelectVisible">
<category-cascader :catelogPath.sync="catelogPath"></category-cascader>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="popCatelogSelectVisible = false">取消</el-button>
<el-button type="primary" size="mini" @click="addCatelogSelect">确定</el-button>
</div>
<el-button slot="reference">新增关联</el-button>
</el-popover>
<el-table :data="cateRelationTableData" style="width: 100%">
<el-table-column prop="id" label="#"></el-table-column>
<el-table-column prop="brandName" label="品牌名"></el-table-column>
<el-table-column prop="catelogName" label="分类名"></el-table-column>
<el-table-column fixed="right" header-align="center" align="center" label="操作">
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="deleteCateRelationHandle(scope.row.id,scope.row.brandId)"
>移除</el-button>
</template>
</el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="cateRelationDialogVisible = false"> </el-button>
<el-button type="primary" @click="cateRelationDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import AddOrUpdate from "./brand-add-or-update";
import CategoryCascader from "../common/category-cascader";
export default {
data() {
return {
dataForm: {
key: ""
},
brandId: 0,
catelogPath: [],
dataList: [],
cateRelationTableData: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false,
cateRelationDialogVisible: false,
popCatelogSelectVisible: false
};
},
components: {
AddOrUpdate,
CategoryCascader
},
activated() {
this.getDataList();
},
methods: {
addCatelogSelect() {
//{"brandId":1,"catelogId":2}
this.popCatelogSelectVisible = false;
this.$http({
url: this.$http.adornUrl("/product/categorybrandrelation/save"),
method: "post",
data: this.$http.adornData(
{
brandId: this.brandId,
catelogId: this.catelogPath[this.catelogPath.length - 1]
},
false
)
}).then(({ data }) => {
this.getCateRelation();
}).catch(() => {});
},
deleteCateRelationHandle(id, brandId) {
this.$http({
url: this.$http.adornUrl("/product/categorybrandrelation/delete"),
method: "post",
data: this.$http.adornData([id], false)
}).then(({ data }) => {
this.getCateRelation();
}).catch(() => {});;
},
updateCatelogHandle(brandId) {
this.cateRelationDialogVisible = true;
this.brandId = brandId;
this.getCateRelation();
},
getCateRelation() {
this.$http({
url: this.$http.adornUrl("/product/categorybrandrelation/catelog/list"),
method: "get",
params: this.$http.adornParams({
brandId: this.brandId
})
})
.then(({ data }) => {
this.cateRelationTableData = data.data;
})
.catch(() => {});
},
//
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl("/product/brand/list"),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
}).catch(() => {});;
},
updateBrandStatus(data) {
// console.log("", data);
let { brandId, showStatus } = data;
//
this.$http({
url: this.$http.adornUrl("/product/brand/update/status"),
method: "post",
data: this.$http.adornData({ brandId, showStatus }, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "状态更新成功"
});
}).catch(() => {});;
},
//
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
//
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
//
selectionChangeHandle(val) {
this.dataListSelections = val;
},
// /
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true;
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id);
});
},
//
deleteHandle(id) {
var ids = id
? [id]
: this.dataListSelections.map(item => {
return item.brandId;
});
this.$confirm(
`确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}
)
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/brand/delete"),
method: "post",
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.getDataList();
}
});
} else {
this.$message.error(data.msg);
}
});
})
.catch(() => {});
}
}
};
</script>

View file

@ -0,0 +1,197 @@
<template>
<div class="mod-config">
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form :inline="true" :model="dataForm">
<el-form-item label="分类">
<category-cascader :catelogPath.sync="catelogPath"></category-cascader>
</el-form-item>
<el-form-item label="品牌">
<brand-select style="width:160px"></brand-select>
</el-form-item>
<el-form-item label="价格">
<el-input-number style="width:160px" v-model="dataForm.price.min" :min="0"></el-input-number>-
<el-input-number style="width:160px" v-model="dataForm.price.max" :min="0"></el-input-number>
</el-form-item>
<el-form-item label="检索">
<el-input style="width:160px" v-model="dataForm.key" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="searchSkuInfo">查询</el-button>
</el-form-item>
</el-form>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%;"
@expand-change="getSkuDetails"
>
<el-table-column type="expand">
<template slot-scope="scope">
商品标题{{scope.row.skuTitle}}
<br />
商品副标题{{scope.row.skuSubtitle}}
<br />
商品描述{{scope.row.skuDesc}}
<br />
分类ID{{scope.row.catalogId}}
<br />
SpuID{{scope.row.spuId}}
<br />
品牌ID{{scope.row.brandId}}
<br />
</template>
</el-table-column>
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<el-table-column prop="skuId" header-align="center" align="center" label="skuId"></el-table-column>
<el-table-column prop="skuName" header-align="center" align="center" label="名称"></el-table-column>
<el-table-column prop="skuDefaultImg" header-align="center" align="center" label="默认图片">
<template slot-scope="scope">
<img :src="scope.row.skuDefaultImg" style="width:80px;height:80px;" />
</template>
</el-table-column>
<el-table-column prop="price" header-align="center" align="center" label="价格(¥)"></el-table-column>
<el-table-column prop="saleCount" header-align="center" align="center" label="销量"></el-table-column>
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
<template slot-scope="scope">
<el-button type="text" size="small" @click="previewHandle(scope.row.skuId)">预览</el-button>
<el-button type="text" size="small" @click="commentHandle(scope.row.skuId)">评论</el-button>
<el-dropdown
@command="handleCommand(scope.row,$event)"
size="small"
split-button
type="text"
>
更多
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="uploadImages">上传图片</el-dropdown-item>
<el-dropdown-item command="seckillSettings">参与秒杀</el-dropdown-item>
<el-dropdown-item command="reductionSettings">满减设置</el-dropdown-item>
<el-dropdown-item command="discountSettings">折扣设置</el-dropdown-item>
<el-dropdown-item command="memberPriceSettings">会员价格</el-dropdown-item>
<el-dropdown-item command="stockSettings">库存管理</el-dropdown-item>
<el-dropdown-item command="couponSettings">优惠劵</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
></el-pagination>
</div>
</template>
<script>
import CategoryCascader from "../common/category-cascader";
import BrandSelect from "../common/brand-select";
export default {
data() {
return {
catPathSub: null,
brandIdSub: null,
dataForm: {
key: "",
brandId: 0,
catelogId: 0,
price: {
min: 0,
max: 0
}
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false,
catelogPath: []
};
},
components: {
CategoryCascader,
BrandSelect
},
activated() {
this.getDataList();
},
methods: {
getSkuDetails(row, expand) {
//sku
// console.log("...", row, expand);
},
//
handleCommand(row, command) {
// console.log("~~~~~", row, command);
if ("stockSettings" == command) {
this.$router.push({ path: "/ware-sku", query: { skuId: row.skuId } });
}
},
searchSkuInfo() {
this.getDataList();
},
//
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl("/product/skuinfo/list"),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key,
catelogId: this.dataForm.catelogId,
brandId: this.dataForm.brandId,
min: this.dataForm.price.min,
max: this.dataForm.price.max
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
});
},
//
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
//
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
//
selectionChangeHandle(val) {
this.dataListSelections = val;
}
},
mounted() {
this.catPathSub = PubSub.subscribe("catPath", (msg, val) => {
this.dataForm.catelogId = val[val.length - 1];
});
this.brandIdSub = PubSub.subscribe("brandId", (msg, val) => {
this.dataForm.brandId = val;
});
},
beforeDestroy() {
PubSub.unsubscribe(this.catPathSub);
PubSub.unsubscribe(this.brandIdSub);
} // - 
};
</script>

View file

@ -0,0 +1,37 @@
<template>
<div>
<base-attr :attrtype="0"></base-attr>
</div>
</template>
<script>
import BaseAttr from './baseattr'
export default {
//import使
components: {BaseAttr},
props: {},
data() {
//
return {};
},
// data
computed: {},
//data
watch: {},
//
methods: {},
// - 访this
created() {},
// - 访DOM
mounted() {},
beforeCreate() {}, // - 
beforeMount() {}, // - 
beforeUpdate() {}, // - 
updated() {}, // - 
beforeDestroy() {}, // - 
destroyed() {}, // - 
activated() {} //keep-alive
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,93 @@
<template>
<div>
<el-row>
<el-col :span="24">
<el-form :inline="true" :model="dataForm">
<el-form-item label="分类">
<category-cascader :catelogPath.sync="catelogPath"></category-cascader>
</el-form-item>
<el-form-item label="品牌">
<brand-select style="width:160px"></brand-select>
</el-form-item>
<el-form-item label="状态">
<el-select style="width:160px" v-model="dataForm.status" clearable>
<el-option label="新建" :value="0"></el-option>
<el-option label="上架" :value="1"></el-option>
<el-option label="下架" :value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="检索">
<el-input style="width:160px" v-model="dataForm.key" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="searchSpuInfo">查询</el-button>
</el-form-item>
</el-form>
</el-col>
<el-col :span="24">
<spuinfo :catId="catId"></spuinfo>
</el-col>
</el-row>
</div>
</template>
<script>
import CategoryCascader from "../common/category-cascader";
import BrandSelect from "../common/brand-select";
import Spuinfo from "./spuinfo";
export default {
//import使
components: { CategoryCascader, Spuinfo, BrandSelect },
props: {},
data() {
//
return {
catId: 0,
catelogPath: [],
dataForm: {
status: "",
key: "",
brandId: 0,
catelogId: 0
},
catPathSub: null,
brandIdSub: null
};
},
// data
computed: {},
//data
watch: {},
//
methods: {
searchSpuInfo() {
// console.log("", this.dataForm);
this.PubSub.publish("dataForm",this.dataForm);
}
},
// - 访this
created() {},
// - 访DOM
mounted() {
this.catPathSub = PubSub.subscribe("catPath", (msg, val) => {
this.dataForm.catelogId = val[val.length-1];
});
this.brandIdSub = PubSub.subscribe("brandId", (msg, val) => {
this.dataForm.brandId = val;
});
},
beforeCreate() {}, // - 
beforeMount() {}, // - 
beforeUpdate() {}, // - 
updated() {}, // - 
beforeDestroy() {
PubSub.unsubscribe(this.catPathSub);
PubSub.unsubscribe(this.brandIdSub);
}, // - 
destroyed() {}, // - 
activated() {} //keep-alive
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,818 @@
<template>
<div>
<el-row>
<el-col :span="24">
<el-steps :active="step" finish-status="success">
<el-step title="基本信息"></el-step>
<el-step title="规格参数"></el-step>
<el-step title="销售属性"></el-step>
<el-step title="SKU信息"></el-step>
<el-step title="保存完成"></el-step>
</el-steps>
</el-col>
<el-col :span="24" v-show="step==0">
<el-card class="box-card" style="width:80%;margin:20px auto">
<el-form ref="spuBaseForm" :model="spu" label-width="120px" :rules="spuBaseInfoRules">
<el-form-item label="商品名称" prop="spuName">
<el-input v-model="spu.spuName"></el-input>
</el-form-item>
<el-form-item label="商品描述" prop="spuDescription">
<el-input v-model="spu.spuDescription"></el-input>
</el-form-item>
<el-form-item label="选择分类" prop="catalogId">
<category-cascader></category-cascader>
</el-form-item>
<el-form-item label="选择品牌" prop="brandId">
<brand-select></brand-select>
</el-form-item>
<el-form-item label="商品重量(Kg)" prop="weight">
<el-input-number v-model.number="spu.weight" :min="0" :precision="3" :step="0.1"></el-input-number>
</el-form-item>
<el-form-item label="设置积分" prop="bounds">
<label>金币</label>
<el-input-number
style="width:130px"
placeholder="金币"
v-model="spu.bounds.buyBounds"
:min="0"
controls-position="right"
></el-input-number>
<label style="margin-left:15px">成长值</label>
<el-input-number
style="width:130px"
placeholder="成长值"
v-model="spu.bounds.growBounds"
:min="0"
controls-position="right"
>
<template slot="prepend">成长值</template>
</el-input-number>
</el-form-item>
<el-form-item label="商品介绍" prop="decript">
<multi-upload v-model="spu.decript"></multi-upload>
</el-form-item>
<el-form-item label="商品图集" prop="images">
<multi-upload v-model="spu.images"></multi-upload>
</el-form-item>
<el-form-item>
<el-button type="success" @click="collectSpuBaseInfo">下一步设置基本参数</el-button>
</el-form-item>
</el-form>
</el-card>
</el-col>
<el-col :span="24" v-show="step==1">
<el-card class="box-card" style="width:80%;margin:20px auto">
<el-tabs tab-position="left" style="width:98%">
<el-tab-pane
:label="group.attrGroupName"
v-for="(group,gidx) in dataResp.attrGroups"
:key="group.attrGroupId"
>
<!-- 遍历属性,每个tab-pane对应一个表单每个属性是一个表单项 spu.baseAttrs[0] = [{attrId:xx,val:}]-->
<el-form ref="form" :model="spu">
<el-form-item
:label="attr.attrName"
v-for="(attr,aidx) in group.attrs"
:key="attr.attrId"
>
<el-input
v-model="dataResp.baseAttrs[gidx][aidx].attrId"
type="hidden"
v-show="false"
></el-input>
<el-select
v-model="dataResp.baseAttrs[gidx][aidx].attrValues"
:multiple="attr.valueType == 1"
filterable
allow-create
default-first-option
placeholder="请选择或输入值"
>
<el-option
v-for="(val,vidx) in attr.valueSelect.split(';')"
:key="vidx"
:label="val"
:value="val"
></el-option>
</el-select>
<el-checkbox
v-model="dataResp.baseAttrs[gidx][aidx].showDesc"
:true-label="1"
:false-label="0"
>快速展示</el-checkbox>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<div style="margin:auto">
<el-button type="primary" @click="step = 0">上一步</el-button>
<el-button type="success" @click="generateSaleAttrs">下一步设置销售属性</el-button>
</div>
</el-card>
</el-col>
<el-col :span="24" v-show="step==2">
<el-card class="box-card" style="width:80%;margin:20px auto">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>选择销售属性</span>
<el-form ref="saleform" :model="spu">
<el-form-item
:label="attr.attrName"
v-for="(attr,aidx) in dataResp.saleAttrs"
:key="attr.attrId"
>
<el-input
v-model="dataResp.tempSaleAttrs[aidx].attrId"
type="hidden"
v-show="false"
></el-input>
<el-checkbox-group v-model="dataResp.tempSaleAttrs[aidx].attrValues">
<el-checkbox
v-if="dataResp.saleAttrs[aidx].valueSelect != ''"
:label="val"
v-for="val in dataResp.saleAttrs[aidx].valueSelect.split(';')"
:key="val"
></el-checkbox>
<div style="margin-left:20px;display:inline">
<el-button
v-show="!inputVisible[aidx].view"
class="button-new-tag"
size="mini"
@click="showInput(aidx)"
>+自定义</el-button>
<el-input
v-show="inputVisible[aidx].view"
v-model="inputValue[aidx].val"
:ref="'saveTagInput'+aidx"
size="mini"
style="width:150px"
@keyup.enter.native="handleInputConfirm(aidx)"
@blur="handleInputConfirm(aidx)"
></el-input>
</div>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
<el-button type="primary" @click="step = 1">上一步</el-button>
<el-button type="success" @click="generateSkus">下一步设置SKU信息</el-button>
</el-card>
</el-card>
</el-col>
<el-col :span="24" v-show="step==3">
<el-card class="box-card" style="width:80%;margin:20px auto">
<el-table :data="spu.skus" style="width: 100%">
<el-table-column label="属性组合">
<el-table-column
:label="item.attrName"
v-for="(item,index) in dataResp.tableAttrColumn"
:key="item.attrId"
>
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.attr[index].attrValue }}</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="商品名称" prop="skuName">
<template slot-scope="scope">
<el-input v-model="scope.row.skuName"></el-input>
</template>
</el-table-column>
<el-table-column label="标题" prop="skuTitle">
<template slot-scope="scope">
<el-input v-model="scope.row.skuTitle"></el-input>
</template>
</el-table-column>
<el-table-column label="副标题" prop="skuSubtitle">
<template slot-scope="scope">
<el-input v-model="scope.row.skuSubtitle"></el-input>
</template>
</el-table-column>
<el-table-column label="价格" prop="price">
<template slot-scope="scope">
<el-input v-model="scope.row.price"></el-input>
</template>
</el-table-column>
<el-table-column type="expand">
<template slot-scope="scope">
<el-row>
<el-col :span="24">
<label style="display:block;float:left">选择图集 </label>
<multi-upload
style="float:left;margin-left:10px;"
:showFile="false"
:listType="'text'"
v-model="uploadImages"
></multi-upload>
</el-col>
<el-col :span="24">
<el-divider></el-divider>
</el-col>
<el-col :span="24">
<el-card
style="width:170px;float:left;margin-left:15px;margin-top:15px;"
:body-style="{ padding: '0px' }"
v-for="(img,index) in spu.images"
:key="index"
>
<img :src="img" style="width:160px;height:120px" />
<div style="padding: 14px;">
<el-row>
<el-col :span="12">
<el-checkbox
v-model="scope.row.images[index].imgUrl"
:true-label="img"
false-label
></el-checkbox>
</el-col>
<el-col :span="12">
<el-tag v-if="scope.row.images[index].defaultImg == 1">
<input
type="radio"
checked
:name="scope.row.skuName"
@change="checkDefaultImg(scope.row,index,img)"
/>
</el-tag>
<el-tag v-else>
<input
type="radio"
:name="scope.row.skuName"
@change="checkDefaultImg(scope.row,index,img)"
/>
</el-tag>
</el-col>
</el-row>
</div>
</el-card>
</el-col>
</el-row>
<!-- 折扣满减会员价 -->
<el-form :model="scope.row">
<el-row>
<el-col :span="24">
<el-form-item label="设置折扣">
<label></label>
<el-input-number
style="width:160px"
:min="0"
controls-position="right"
v-model="scope.row.fullCount"
></el-input-number>
<label></label>
<label style="margin-left:15px;"></label>
<el-input-number
style="width:160px"
v-model="scope.row.discount"
:precision="2"
:max="1"
:min="0"
:step="0.01"
controls-position="right"
></el-input-number>
<label></label>
<el-checkbox
v-model="scope.row.countStatus"
:true-label="1"
:false-label="0"
>可叠加优惠</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="设置满减">
<label></label>
<el-input-number
style="width:160px"
v-model="scope.row.fullPrice"
:step="100"
:min="0"
controls-position="right"
></el-input-number>
<label></label>
<label style="margin-left:15px;"></label>
<el-input-number
style="width:160px"
v-model="scope.row.reducePrice"
:step="10"
:min="0"
controls-position="right"
></el-input-number>
<label></label>
<el-checkbox
v-model="scope.row.priceStatus"
:true-label="1"
:false-label="0"
>可叠加优惠</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="设置会员价" v-if="scope.row.memberPrice.length>0">
<br />
<!-- @change="handlePriceChange(scope,mpidx,$event)" -->
<el-form-item v-for="(mp,mpidx) in scope.row.memberPrice" :key="mp.id">
{{mp.name}}
<el-input-number
style="width:160px"
v-model="scope.row.memberPrice[mpidx].price"
:precision="2"
:min="0"
controls-position="right"
></el-input-number>
</el-form-item>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
</el-table-column>
</el-table>
<el-button type="primary" @click="step = 2">上一步</el-button>
<el-button type="success" @click="submitSkus">下一步保存商品信息</el-button>
</el-card>
</el-col>
<el-col :span="24" v-show="step==4">
<el-card class="box-card" style="width:80%;margin:20px auto">
<h1>保存成功</h1>
<el-button type="primary" @click="addAgian">继续添加</el-button>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import CategoryCascader from "../common/category-cascader";
import BrandSelect from "../common/brand-select";
import MultiUpload from "@/components/upload/multiUpload";
export default {
//import使
components: { CategoryCascader, BrandSelect, MultiUpload },
props: {},
data() {
return {
catPathSub: null,
brandIdSub: null,
uploadDialogVisible: false,
uploadImages: [],
step: 0,
//spu_name spu_description catalog_id brand_id weight publish_status
spu: {
//
spuName: "",
spuDescription: "",
catalogId: 0,
brandId: "",
weight: "",
publishStatus: 0,
decript: [], //
images: [], //sku
bounds: {
//
buyBounds: 0,
growBounds: 0
},
baseAttrs: [], //
skus: [] //sku
},
spuBaseInfoRules: {
spuName: [
{ required: true, message: "请输入商品名字", trigger: "blur" }
],
spuDescription: [
{ required: true, message: "请编写一个简单描述", trigger: "blur" }
],
catalogId: [
{ required: true, message: "请选择一个分类", trigger: "blur" }
],
brandId: [
{ required: true, message: "请选择一个品牌", trigger: "blur" }
],
decript: [
{ required: true, message: "请上传商品详情图集", trigger: "blur" }
],
images: [
{ required: true, message: "请上传商品图片集", trigger: "blur" }
],
weight: [
{
type: "number",
required: true,
message: "请填写正确的重量值",
trigger: "blur"
}
]
},
dataResp: {
//
attrGroups: [],
baseAttrs: [],
saleAttrs: [],
tempSaleAttrs: [],
tableAttrColumn: [],
memberLevels: [],
steped: [false, false, false, false, false]
},
inputVisible: [],
inputValue: []
};
},
// data
computed: {},
//data
watch: {
uploadImages(val) {
//skusimgs
let imgArr = Array.from(new Set(this.spu.images.concat(val)));
//{imgUrl:"",defaultImg:0} concat+
this.spu.skus.forEach((item, index) => {
let len = imgArr.length - this.spu.skus[index].images.length; //
if (len > 0) {
let imgs = new Array(len);
imgs = imgs.fill({ imgUrl: "", defaultImg: 0 });
this.spu.skus[index].images = item.images.concat(imgs);
}
});
this.spu.images = imgArr; //
console.log("this.spu.skus", this.spu.skus);
}
},
//
methods: {
addAgian() {
this.step = 0;
this.resetSpuData();
},
resetSpuData() {
this.spu = {
spuName: "",
spuDescription: "",
catalogId: 0,
brandId: "",
weight: "",
publishStatus: 0,
decript: [],
images: [],
bounds: {
buyBounds: 0,
growBounds: 0
},
baseAttrs: [],
skus: []
};
},
handlePriceChange(scope, mpidx, e) {
this.spu.skus[scope.$index].memberPrice[mpidx].price = e;
},
//
getMemberLevels() {
this.$http({
url: this.$http.adornUrl("/member/memberlevel/list"),
method: "get",
params: this.$http.adornParams({
page: 1,
limit: 500
})
})
.then(({ data }) => {
this.dataResp.memberLevels = data.page.list;
})
.catch(e => {
console.log(e);
});
},
showInput(idx) {
console.log("``````", this.view);
this.inputVisible[idx].view = true;
// this.$refs['saveTagInput'+idx].$refs.input.focus();
},
checkDefaultImg(row, index, img) {
console.log("默认图片", row, index);
//
row.images[index].imgUrl = img; //
row.images[index].defaultImg = 1; //;
//
row.images.forEach((item, idx) => {
if (idx != index) {
row.images[idx].defaultImg = 0;
}
});
},
handleInputConfirm(idx) {
let inputValue = this.inputValue[idx].val;
if (inputValue) {
// this.dynamicTags.push(inputValue);
if (this.dataResp.saleAttrs[idx].valueSelect == "") {
this.dataResp.saleAttrs[idx].valueSelect = inputValue;
} else {
this.dataResp.saleAttrs[idx].valueSelect += ";" + inputValue;
}
}
this.inputVisible[idx].view = false;
this.inputValue[idx].val = "";
},
collectSpuBaseInfo() {
//spuBaseForm
this.$refs.spuBaseForm.validate(valid => {
if (valid) {
this.step = 1;
this.showBaseAttrs();
} else {
return false;
}
});
},
generateSaleAttrs() {
//attrspu,
this.spu.baseAttrs = [];
this.dataResp.baseAttrs.forEach(item => {
item.forEach(attr => {
// console.log("" , attr);
let { attrId, attrValues, showDesc } = attr;
//
if (attrValues != "") {
if (attrValues instanceof Array) {
//;
attrValues = attrValues.join(";");
}
this.spu.baseAttrs.push({ attrId, attrValues, showDesc });
}
});
});
// console.log("baseAttrs", this.spu.baseAttrs);
this.step = 2;
this.getShowSaleAttr();
},
generateSkus() {
this.step = 3;
//sku
let selectValues = [];
this.dataResp.tableAttrColumn = [];
this.dataResp.tempSaleAttrs.forEach(item => {
if (item.attrValues.length > 0) {
selectValues.push(item.attrValues);
this.dataResp.tableAttrColumn.push(item);
}
});
let descartes = this.descartes(selectValues);
//[["","6GB",""],["","6GB",""],["","8GB",""],["","8GB",""],
//["","6GB",""],["","6GB",""],["","8GB",""],["","8GB",""],
//["","6GB",""],["","6GB",""],["","8GB",""],["","8GB",""]]
// console.log("", JSON.stringify(descartes));
//descartessku
let skus = [];
descartes.forEach((descar, descaridx) => {
let attrArray = []; //sku
descar.forEach((de, index) => {
//saleAttr
let saleAttrItem = {
attrId: this.dataResp.tableAttrColumn[index].attrId,
attrName: this.dataResp.tableAttrColumn[index].attrName,
attrValue: de
};
attrArray.push(saleAttrItem);
});
//images
let imgs = [];
this.spu.images.forEach((img, idx) => {
imgs.push({ imgUrl: "", defaultImg: 0 });
});
//
let memberPrices = [];
if (this.dataResp.memberLevels.length > 0) {
for (let i = 0; i < this.dataResp.memberLevels.length; i++) {
if (this.dataResp.memberLevels[i].priviledgeMemberPrice == 1) {
memberPrices.push({
id: this.dataResp.memberLevels[i].id,
name: this.dataResp.memberLevels[i].name,
price: 0
});
}
}
}
//;descaridx;
let res = this.hasAndReturnSku(this.spu.skus, descar);
if (res === null) {
skus.push({
attr: attrArray,
skuName: this.spu.spuName + " " + descar.join(" "),
price: 0,
skuTitle: this.spu.spuName + " " + descar.join(" "),
skuSubtitle: "",
images: imgs,
descar: descar,
fullCount: 0,
discount: 0,
countStatus: 0,
fullPrice: 0.0,
reducePrice: 0.0,
priceStatus: 0,
memberPrice: new Array().concat(memberPrices)
});
} else {
skus.push(res);
}
});
this.spu.skus = skus;
// console.log("!!!", this.spu.skus, this.dataResp.tableAttrColumn);
},
//skudescarsku
hasAndReturnSku(skus, descar) {
let res = null;
if (skus.length > 0) {
for (let i = 0; i < skus.length; i++) {
if (skus[i].descar.join(" ") == descar.join(" ")) {
res = skus[i];
}
}
}
return res;
},
getShowSaleAttr() {
//使
if (!this.dataResp.steped[1]) {
this.$http({
url: this.$http.adornUrl(
`/product/attr/sale/list/${this.spu.catalogId}`
),
method: "get",
params: this.$http.adornParams({
page: 1,
limit: 500
})
}).then(({ data }) => {
this.dataResp.saleAttrs = data.page.list;
data.page.list.forEach(item => {
this.dataResp.tempSaleAttrs.push({
attrId: item.attrId,
attrValues: [],
attrName: item.attrName
});
this.inputVisible.push({ view: false });
this.inputValue.push({ val: "" });
});
this.dataResp.steped[1] = true;
});
}
},
showBaseAttrs() {
if (!this.dataResp.steped[0]) {
this.$http({
url: this.$http.adornUrl(
`/product/attrgroup/${this.spu.catalogId}/withattr`
),
method: "get",
params: this.$http.adornParams({})
})
.then(({ data }) => {
//baseAttrs
data.data.forEach(item => {
//
// console.log(item)
let attrArray = [];
//
if (item.attrs != null) {
item.attrs.forEach(attr => {
attrArray.push({
attrId: attr.attrId,
attrValues: "",
showDesc: attr.showDesc
});
});
}
this.dataResp.baseAttrs.push(attrArray);
});
this.dataResp.steped[0] = 0;
this.dataResp.attrGroups = data.data;
})
.catch(e => {
console.log(e);
});
}
},
submitSkus() {
console.log("~~~~~", JSON.stringify(this.spu));
this.$confirm("将要提交商品数据,需要一小段时间,是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/spuinfo/save"),
method: "post",
data: this.$http.adornData(this.spu, false)
}).then(({ data }) => {
if (data.code == 0) {
this.$message({
type: "success",
message: "新增商品成功!"
});
this.step = 4;
} else {
this.$message({
type: "error",
message: "保存失败,原因【" + data.msg + "】"
});
}
});
})
.catch(e => {
// console.log("");
this.$message({
type: "info",
message: "已取消"
});
});
},
//
descartes(list) {
//parent;count
var point = {};
var result = [];
var pIndex = null;
var tempCount = 0;
var temp = [];
//
for (var index in list) {
if (typeof list[index] == "object") {
point[index] = { parent: pIndex, count: 0 };
pIndex = index;
}
}
//
if (pIndex == null) {
return list;
}
//
while (true) {
for (var index in list) {
tempCount = point[index]["count"];
temp.push(list[index][tempCount]);
}
//
result.push(temp);
temp = [];
//
while (true) {
if (point[index]["count"] + 1 >= list[index].length) {
point[index]["count"] = 0;
pIndex = point[index]["parent"];
if (pIndex == null) {
return result;
}
//parent
index = pIndex;
} else {
point[index]["count"]++;
break;
}
}
}
}
},
// - 访this
created() {},
// - 访DOM
mounted() {
// id
this.catPathSub = PubSub.subscribe("catPath", (msg, val) => {
this.spu.catalogId = val[val.length - 1];
});
this.brandIdSub = PubSub.subscribe("brandId", (msg, val) => {
this.spu.brandId = val;
});
this.getMemberLevels();
},
beforeCreate() {}, // - 
beforeMount() {}, // - 
beforeUpdate() {}, // - 
updated() {}, // - 
beforeDestroy() {
PubSub.unsubscribe(this.catPathSub);
PubSub.unsubscribe(this.brandIdSub);
}, // - 
destroyed() {}, // - 
activated() {} //keep-alive
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,156 @@
<template>
<div class="mod-config">
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%;"
>
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<el-table-column prop="id" header-align="center" align="center" label="id"></el-table-column>
<el-table-column prop="spuName" header-align="center" align="center" label="名称"></el-table-column>
<el-table-column prop="spuDescription" header-align="center" align="center" label="描述"></el-table-column>
<el-table-column prop="catalogId" header-align="center" align="center" label="分类"></el-table-column>
<el-table-column prop="brandId" header-align="center" align="center" label="品牌"></el-table-column>
<el-table-column prop="weight" header-align="center" align="center" label="重量(kg)"></el-table-column>
<el-table-column prop="publishStatus" header-align="center" align="center" label="上架状态">
<template slot-scope="scope">
<el-tag v-if="scope.row.publishStatus == 0">新建</el-tag>
<el-tag v-if="scope.row.publishStatus == 1">已上架</el-tag>
<el-tag v-if="scope.row.publishStatus == 2">已下架</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" header-align="center" align="center" label="创建时间"></el-table-column>
<el-table-column prop="updateTime" header-align="center" align="center" label="修改时间"></el-table-column>
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
<template slot-scope="scope">
<el-button
v-if="scope.row.publishStatus == 0"
type="text"
size="small"
@click="productUp(scope.row.id)"
>上架</el-button>
<el-button type="text" size="small" @click="attrUpdateShow(scope.row)">规格</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
></el-pagination>
</div>
</template>
<script>
export default {
data() {
return {
dataSub: null,
dataForm: {},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false
};
},
props: {
catId: {
type: Number,
default: 0
}
},
components: {},
activated() {
this.getDataList();
},
methods: {
productUp(id) {
this.$http({
url: this.$http.adornUrl("/product/spuinfo/" + id + "/up"),
method: "post"
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.getDataList();
}
});
} else {
this.$message.error(data.msg);
}
});
},
//
attrUpdateShow(row) {
// console.log(row);
this.$router.push({
path: "/product-attrupdate",
query: { spuId: row.id, catalogId: row.catalogId }
});
},
//
getDataList() {
this.dataListLoading = true;
let param = {};
Object.assign(param, this.dataForm, {
page: this.pageIndex,
limit: this.pageSize
});
this.$http({
url: this.$http.adornUrl("/product/spuinfo/list"),
method: "get",
params: this.$http.adornParams(param)
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
});
},
//
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
//
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
//
selectionChangeHandle(val) {
this.dataListSelections = val;
},
// /
addOrUpdateHandle(id) {}
},
mounted() {
this.dataSub = PubSub.subscribe("dataForm", (msg, val) => {
// console.log("", val);
this.dataForm = val;
this.getDataList();
});
},
beforeDestroy() {
PubSub.unsubscribe(this.dataSub);
}
};
</script>