Explorar o código

节目管理样式

licailing %!s(int64=4) %!d(string=hai) anos
pai
achega
17e3e39add

+ 6 - 0
pom.xml

@@ -292,6 +292,12 @@
             <artifactId>itext</artifactId>
             <version>2.1.7</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.bytedeco</groupId>
+            <artifactId>javacv-platform</artifactId>
+            <version>1.5.4</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 45 - 0
src/main/java/com/izouma/wenlvju/annotations/EnumFormat.java

@@ -0,0 +1,45 @@
+package com.izouma.wenlvju.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <pre>
+ * '@EnumFormat 注解 :
+ *  作用 : 用于自定义excel单元格中的内容,转换成对应的枚举值
+ *  属性 :
+ *      value : 要转换的枚举类型
+ *      fromExcel : 指定excel中用户输入的枚举值,可以与toJavaEnum中指定的枚举值一一对应
+ *                  例如 : excel 单元格中输入
+ *                         '待支付' -> OrderStatusEnum.UNPAY
+ *                         '已支付' -> OrderStatusEnum.PAYED
+ *      toJavaEnum : 如上所述
+ *  注意 : toJavaEnum 与 fromExcel 必须搭配使用
+ * </pre>
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnumFormat {
+    /**
+     * 要转换的枚举类型
+     *
+     * @return enum class
+     */
+    Class value();
+
+    /**
+     * 要转换枚举的全部变量名数组集
+     *
+     * @return String[]
+     */
+    String[] toJavaEnum() default {};
+
+    /**
+     * 枚举导出excel时所展示的内容
+     *
+     * @return String[]
+     */
+    String[] fromExcel() default {};
+}

+ 26 - 12
src/main/java/com/izouma/wenlvju/dto/ProgrammeDTO.java

@@ -1,8 +1,12 @@
 package com.izouma.wenlvju.dto;
 
 import cn.hutool.core.bean.BeanUtil;
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.izouma.wenlvju.annotations.EnumFormat;
 import com.izouma.wenlvju.domain.performance.Programme;
 import com.izouma.wenlvju.enums.CompetitionGroup;
+import com.izouma.wenlvju.utils.excel.EnumExcelConverter;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
@@ -20,56 +24,66 @@ import javax.persistence.Transient;
 @Builder
 @ApiModel(value = "节目安排")
 public class ProgrammeDTO {
+    @ExcelIgnore
     private Long id;
 
+    @ExcelIgnore
     private Long performanceId;
 
-    @ApiModelProperty(value = "节目名称")
+    @ExcelProperty(value = "节目名称")
     private String name;
 
-    @ApiModelProperty(value = "参赛专业")
+    @ExcelIgnore
     private Long specialtyId;
 
+    @ExcelProperty(value = "参赛专业")
     private String specialty;
 
+    @EnumFormat(value = CompetitionGroup.class,
+            fromExcel = {"单人", "集体"},
+            toJavaEnum = {"SINGLE", "COLLECTIVE"})
+    @ExcelProperty(value = "参赛组别", converter = EnumExcelConverter.class)
     @Enumerated(EnumType.STRING)
     @ApiModelProperty(value = "参赛组别")
     private CompetitionGroup competitionGroup;
 
-    @ApiModelProperty(value = "参赛级别")
+    @ExcelIgnore
     private Long levelSettingId;
 
+    @ExcelProperty(value = "参赛级别")
     private String level;
 
-    @ApiModelProperty(value = "作品时长")
+    @ExcelProperty(value = "作品时长")
     private int durationOfWork;
 
-    @ApiModelProperty(value = "指导老师")
+    @ExcelProperty(value = "指导老师")
     private String instructor;
 
-    @ApiModelProperty(value = "联系人")
+    @ExcelProperty(value = "联系人")
     private String contact;
 
-    @ApiModelProperty(value = "联系电话")
+    @ExcelProperty(value = "联系电话")
     private String phone;
 
-    @ApiModelProperty(value = "考级机构")
+    @ExcelIgnore
     private Long gradingOrganizationId;
 
+    @ExcelProperty(value = "考级机构")
     private String gradingOrganization;
 
-    @ApiModelProperty(value = "承办单位")
+    @ExcelIgnore
     private Long organizationId;
 
+    @ExcelProperty(value = "承办单位")
     private String organization;
 
-    @ApiModelProperty(value = "考级点")
+    @ExcelProperty(value = "考级点")
     private String examPoint;
 
-    @ApiModelProperty(value = "节目视频")
+    @ExcelProperty(value = "节目视频")
     private String video;
 
-    @ApiModelProperty(value = "参赛人数")
+    @ExcelProperty(value = "参赛人数")
     private long quantity;
 
     public ProgrammeDTO(Programme programme) {

+ 9 - 4
src/main/java/com/izouma/wenlvju/service/ArtTypeService.java

@@ -1,6 +1,5 @@
 package com.izouma.wenlvju.service;
 
-import com.alibaba.fastjson.JSON;
 import com.izouma.wenlvju.domain.ArtType;
 import com.izouma.wenlvju.dto.PageQuery;
 import com.izouma.wenlvju.repo.ArtTypeRepo;
@@ -10,6 +9,7 @@ import org.springframework.data.domain.Page;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
+import java.util.stream.Collectors;
 
 @Service
 @AllArgsConstructor
@@ -21,9 +21,7 @@ public class ArtTypeService {
         return artTypeRepo.findAll(JpaUtils.toSpecification(pageQuery, ArtType.class), JpaUtils.toPageRequest(pageQuery));
     }
 
-    public List<ArtType> getTree(List<?> list1) {
-        String s = JSON.toJSONString(list1);
-        List<ArtType> list = JSON.parseArray(s, ArtType.class);
+    public List<ArtType> getTree(List<ArtType> list) {
         Map<Long, ArtType> dtoMap = new HashMap<>();
         for (ArtType node : list) {
             dtoMap.put(node.getId(), node);
@@ -54,4 +52,11 @@ public class ArtTypeService {
             }
         }
     }
+
+    public List<Long> getIds(List<ArtType> artTypes, String code) {
+        return artTypes.stream()
+                .filter(at -> at.getCode().startsWith(code))
+                .map(ArtType::getId)
+                .collect(Collectors.toList());
+    }
 }

+ 90 - 12
src/main/java/com/izouma/wenlvju/service/performance/ProgrammeService.java

@@ -1,6 +1,9 @@
 package com.izouma.wenlvju.service.performance;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import com.izouma.wenlvju.domain.ArtType;
 import com.izouma.wenlvju.domain.GradingOrganization;
 import com.izouma.wenlvju.domain.Organization;
@@ -15,11 +18,13 @@ import com.izouma.wenlvju.repo.OrganizationRepo;
 import com.izouma.wenlvju.repo.SettingRepo;
 import com.izouma.wenlvju.repo.performance.ParticipantRepo;
 import com.izouma.wenlvju.repo.performance.ProgrammeRepo;
+import com.izouma.wenlvju.service.ArtTypeService;
 import com.izouma.wenlvju.utils.JpaUtils;
 import lombok.AllArgsConstructor;
 import org.springframework.data.domain.Page;
 import org.springframework.stereotype.Service;
 
+import javax.persistence.criteria.Predicate;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -35,20 +40,40 @@ public class ProgrammeService {
     private OrganizationRepo        organizationRepo;
     private ArtTypeRepo             artTypeRepo;
     private SettingRepo             settingRepo;
+    private ArtTypeService          artTypeService;
 
     public Page<Programme> all(PageQuery pageQuery) {
         return programmeRepo.findAll(JpaUtils.toSpecification(pageQuery, Programme.class), JpaUtils.toPageRequest(pageQuery));
     }
 
     public Page<ProgrammeDTO> backAll(PageQuery pageQuery) {
-        Page<Programme> all = programmeRepo.findAll(JpaUtils.toSpecification(pageQuery, Programme.class), JpaUtils.toPageRequest(pageQuery));
-        List<Long> ids = new ArrayList<>();
-        List<Long> settingIds = new ArrayList<>();
+        Map<String, Object> query = pageQuery.getQuery();
+        Object code = query.get("code");
+        String artCode = null;
+        if (ObjectUtil.isNotNull(code)) {
+            artCode = Convert.convert(String.class, code);
+            query.remove("code");
+        }
+
+        List<ArtType> artTypes = artTypeRepo.findAll();
+
+        String finalArtCode = artCode;
+        Page<Programme> all = programmeRepo.findAll(((root, criteriaQuery, criteriaBuilder) -> {
+            List<Predicate> and = JpaUtils.toPredicates(pageQuery, Programme.class, root, criteriaQuery, criteriaBuilder);
+            if (StrUtil.isNotBlank(finalArtCode)) {
+                and.add(root.get("specialtyId").in(artTypeService.getIds(artTypes, finalArtCode)));
+            }
+            return criteriaBuilder.and(and.toArray(new Predicate[0]));
+
+        }), JpaUtils.toPageRequest(pageQuery));
+
         List<Programme> content = all.getContent();
         if (CollUtil.isEmpty(content)) {
             return all.map(ProgrammeDTO::new);
         }
 
+        List<Long> ids = new ArrayList<>();
+        List<Long> settingIds = new ArrayList<>();
         content.forEach(programme -> {
             ids.add(programme.getId());
             settingIds.add(programme.getLevelSettingId());
@@ -60,8 +85,7 @@ public class ProgrammeService {
         Map<Long, String> gradingOrganizationMap = gradingOrganizationRepo.findAll()
                 .stream()
                 .collect(Collectors.toMap(GradingOrganization::getId, GradingOrganization::getName));
-        Map<Long, String> artTypeMap = artTypeRepo.findAll()
-                .stream()
+        Map<Long, String> artTypeMap = artTypes.stream()
                 .collect(Collectors.toMap(ArtType::getId, ArtType::getName));
         Map<Long, String> settingMap = settingRepo.findAllById(settingIds)
                 .stream()
@@ -71,15 +95,69 @@ public class ProgrammeService {
                 .stream()
                 .collect(Collectors.groupingBy(Participant::getProgrammeId, Collectors.counting()));
         return all.map(programme -> {
-            ProgrammeDTO dto = new ProgrammeDTO(programme);
-            dto.setOrganization(organizationMap.get(programme.getOrganizationId()));
-            dto.setGradingOrganization(gradingOrganizationMap.get(programme.getGradingOrganizationId()));
-            dto.setSpecialty(artTypeMap.get(programme.getSpecialtyId()));
-            dto.setLevel(settingMap.get(programme.getLevelSettingId()));
             if (participantMap.containsKey(programme.getId())) {
-                dto.setQuantity(participantMap.get(programme.getId()));
+                return toDTO(programme, organizationMap.get(programme.getOrganizationId()),
+                        gradingOrganizationMap.get(programme.getGradingOrganizationId()),
+                        artTypeMap.get(programme.getSpecialtyId()),
+                        settingMap.get(programme.getLevelSettingId()),
+                        participantMap.get(programme.getId()));
             }
-            return dto;
+            return toDTO(programme, organizationMap.get(programme.getOrganizationId()),
+                    gradingOrganizationMap.get(programme.getGradingOrganizationId()),
+                    artTypeMap.get(programme.getSpecialtyId()),
+                    settingMap.get(programme.getLevelSettingId()), 0);
+        });
+    }
+
+    public ProgrammeDTO toDTO(Programme programme, String organization, String gradingOrganization, String artType,
+                              String setting, long participant) {
+        ProgrammeDTO dto = new ProgrammeDTO(programme);
+        dto.setOrganization(organization);
+        dto.setGradingOrganization(gradingOrganization);
+        dto.setSpecialty(artType);
+        dto.setLevel(setting);
+        dto.setQuantity(participant);
+        return dto;
+    }
+
+    public List<ProgrammeDTO> toDTOList(List<Programme> programmes) {
+        List<Long> ids = new ArrayList<>();
+        List<Long> settingIds = new ArrayList<>();
+
+        programmes.forEach(programme -> {
+            ids.add(programme.getId());
+            settingIds.add(programme.getLevelSettingId());
         });
+
+        Map<Long, String> organizationMap = organizationRepo.findAll()
+                .stream()
+                .collect(Collectors.toMap(Organization::getId, Organization::getName));
+        Map<Long, String> gradingOrganizationMap = gradingOrganizationRepo.findAll()
+                .stream()
+                .collect(Collectors.toMap(GradingOrganization::getId, GradingOrganization::getName));
+        Map<Long, String> artTypeMap = artTypeRepo.findAll()
+                .stream()
+                .collect(Collectors.toMap(ArtType::getId, ArtType::getName));
+        Map<Long, String> settingMap = settingRepo.findAllById(settingIds)
+                .stream()
+                .collect(Collectors.toMap(Setting::getId, Setting::getName));
+
+        Map<Long, Long> participantMap = participantRepo.findAllByProgrammeIdIn(ids)
+                .stream()
+                .collect(Collectors.groupingBy(Participant::getProgrammeId, Collectors.counting()));
+
+        return programmes.stream().map(programme -> {
+            if (participantMap.containsKey(programme.getId())) {
+                return toDTO(programme, organizationMap.get(programme.getOrganizationId()),
+                        gradingOrganizationMap.get(programme.getGradingOrganizationId()),
+                        artTypeMap.get(programme.getSpecialtyId()),
+                        settingMap.get(programme.getLevelSettingId()),
+                        participantMap.get(programme.getId()));
+            }
+            return toDTO(programme, organizationMap.get(programme.getOrganizationId()),
+                    gradingOrganizationMap.get(programme.getGradingOrganizationId()),
+                    artTypeMap.get(programme.getSpecialtyId()),
+                    settingMap.get(programme.getLevelSettingId()), 0);
+        }).collect(Collectors.toList());
     }
 }

+ 75 - 0
src/main/java/com/izouma/wenlvju/utils/excel/EnumExcelConverter.java

@@ -0,0 +1,75 @@
+package com.izouma.wenlvju.utils.excel;
+
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.CellData;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import com.izouma.wenlvju.annotations.EnumFormat;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.EnumUtils;
+import org.springframework.util.Assert;
+
+import java.util.Objects;
+
+/**
+ * @author WuKun
+ * @since 2019/10/10
+ */
+public class EnumExcelConverter implements Converter<Enum> {
+
+    @Override
+    public Class supportJavaTypeKey() {
+        return Enum.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public Enum convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        String cellDataStr = cellData.getStringValue();
+
+        EnumFormat annotation = contentProperty.getField().getAnnotation(EnumFormat.class);
+        Class enumClazz = annotation.value();
+        String[] fromExcel = annotation.fromExcel();
+        String[] toJavaEnum = annotation.toJavaEnum();
+
+        Enum anEnum = null;
+        if (ArrayUtils.isNotEmpty(fromExcel) && ArrayUtils.isNotEmpty(toJavaEnum)) {
+            Assert.isTrue(fromExcel.length == toJavaEnum.length, "fromExcel 与 toJavaEnum 的长度必须相同");
+            for (int i = 0; i < fromExcel.length; i++) {
+                if (Objects.equals(fromExcel[i], cellDataStr)) {
+                    anEnum = EnumUtils.getEnum(enumClazz, toJavaEnum[i]);
+                }
+            }
+        } else {
+            anEnum = EnumUtils.getEnum(enumClazz, cellDataStr);
+        }
+
+        Assert.notNull(anEnum, "枚举值不合法");
+        return anEnum;
+    }
+
+    @Override
+    public CellData convertToExcelData(Enum value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+
+        String enumName = value.name();
+
+        EnumFormat annotation = contentProperty.getField().getAnnotation(EnumFormat.class);
+        String[] fromExcel = annotation.fromExcel();
+        String[] toJavaEnum = annotation.toJavaEnum();
+
+        if (ArrayUtils.isNotEmpty(fromExcel) && ArrayUtils.isNotEmpty(toJavaEnum)) {
+            Assert.isTrue(fromExcel.length == toJavaEnum.length, "fromExcel 与 toJavaEnum 的长度必须相同");
+            for (int i = 0; i < toJavaEnum.length; i++) {
+                if (Objects.equals(toJavaEnum[i], enumName)) {
+                    return new CellData(fromExcel[i]);
+                }
+            }
+        }
+        return new CellData(enumName);
+    }
+}

+ 66 - 0
src/main/java/com/izouma/wenlvju/web/FileUploadController.java

@@ -5,6 +5,10 @@ import com.izouma.wenlvju.service.storage.StorageService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bytedeco.javacv.FFmpegFrameGrabber;
+import org.bytedeco.javacv.Frame;
+import org.bytedeco.javacv.Java2DFrameConverter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -12,13 +16,18 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URLConnection;
 import java.text.SimpleDateFormat;
 import java.util.Base64;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 
 @RestController
@@ -77,4 +86,61 @@ public class FileUploadController {
         }
         return storageService.uploadFromInputStream(is, path);
     }
+
+    @PostMapping("/video")
+    public Map<String, String> uploadVideo(@RequestParam("file") MultipartFile file) {
+        String basePath = "application";
+        try {
+            basePath = file.getContentType().split("/")[0];
+        } catch (Exception ignored) {
+        }
+        String videoPath = basePath + "/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
+                + RandomStringUtils.randomAlphabetic(8)
+                + "." + FilenameUtils.getExtension(file.getOriginalFilename());
+
+        InputStream originInputstream;
+        try {
+            originInputstream = file.getInputStream();
+            if (StringUtils.isEmpty(file.getContentType()) || !file.getContentType().startsWith("video")) {
+                throw new BusinessException(file.getContentType() + " 非视频文件");
+            }
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+            byte[] buffer = new byte[1024];
+            int len;
+            while ((len = originInputstream.read(buffer)) > -1) {
+                baos.write(buffer, 0, len);
+            }
+            baos.flush();
+
+            InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
+            String videoUrl = storageService.uploadFromInputStream(is1, videoPath);
+
+            InputStream is2 = new ByteArrayInputStream(baos.toByteArray());
+
+            FFmpegFrameGrabber g = new FFmpegFrameGrabber(is2);
+            g.start();
+
+            Frame frame = g.grabKeyFrame();
+            Java2DFrameConverter converter = new Java2DFrameConverter();
+            BufferedImage image = converter.convert(frame);
+            ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
+            ImageIO.write(image, "jpeg", baos1);
+            String posterPath = String.format("application/image/%s.jpg",
+                    new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
+                            + RandomStringUtils.randomAlphabetic(8));
+            String posterUrl = storageService
+                    .uploadFromInputStream(new ByteArrayInputStream(baos1.toByteArray()), posterPath);
+            g.stop();
+
+            return new HashMap<String, String>() {{
+                put("src", videoUrl);
+                put("poster", posterUrl);
+            }};
+        } catch (IOException e) {
+            log.error("上传失败", e);
+            throw new BusinessException("上传失败", e.getMessage());
+        }
+    }
 }

+ 1 - 1
src/main/java/com/izouma/wenlvju/web/performance/ProgrammeController.java

@@ -63,7 +63,7 @@ public class ProgrammeController extends BaseController {
     @GetMapping("/excel")
     @ResponseBody
     public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
-        List<Programme> data = all(pageQuery).getContent();
+        List<ProgrammeDTO> data = programmeService.toDTOList(all(pageQuery).getContent());
         ExcelUtils.export(response, data);
     }
 

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

@@ -641,6 +641,15 @@ const router = new Router({
                     name: 'ProgrammeList',
                     component: () =>
                         import(/* webpackChunkName: "programmeList" */ '@/views/performance/ProgrammeList.vue'),
+                    meta: {
+                        title: '节目列表'
+                    }
+                },
+                {
+                    path: '/programmeOrgList',
+                    name: 'ProgrammeOrgList',
+                    component: () =>
+                        import(/* webpackChunkName: "programmeOrgList" */ '@/views/performance/ProgrammeOrgList.vue'),
                     meta: {
                         title: '节目管理'
                     }

+ 206 - 180
src/main/vue/src/views/performance/ProgrammeEdit.vue

@@ -7,183 +7,201 @@
             label-width="80px"
             label-position="right"
             size="small"
-            style="max-width: 600px;"
+            style="max-width: 700px;"
         >
-            <el-form-item prop="name" label="节目名称">
-                <el-input v-model="formData.name"></el-input>
-            </el-form-item>
-            <el-form-item prop="specialtyId" label="参赛专业">
-                <el-cascader
-                    ref="artCascader"
-                    style="width: 100%"
-                    v-model="formData.specialtyId"
-                    :props="optionProps"
-                    :options="artTypes"
-                    :show-all-levels="false"
-                    placeholder="请选择专业"
-                >
-                </el-cascader>
-            </el-form-item>
-            <el-form-item prop="competitionGroup" label="参赛组别">
-                <el-select v-model="formData.competitionGroup" clearable filterable placeholder="请选择">
-                    <el-option
-                        v-for="item in competitionGroupOptions"
-                        :key="item.value"
-                        :label="item.label"
-                        :value="item.value"
-                    >
-                    </el-option>
-                </el-select>
-            </el-form-item>
-            <el-form-item prop="levelSettingId" label="参赛级别" v-if="formData.competitionGroup">
-                <el-select
-                    v-model="formData.levelSettingId"
-                    clearable
-                    filterable
-                    placeholder="请选择"
-                    style="width: 100%"
-                    v-if="formData.competitionGroup == 'SINGLE'"
-                >
-                    <el-option
-                        v-for="item in levelSingleOptions"
-                        :key="item.value"
-                        :label="item.label"
-                        :value="item.value"
-                    >
-                        <span style="float: left">{{ item.label }}</span>
-                        <span style="float: right; color: #8492a6; font-size: 13px">{{ item.desc }}</span>
-                    </el-option>
-                </el-select>
-                <el-select
-                    v-model="formData.levelSettingId"
-                    clearable
-                    filterable
-                    placeholder="请选择"
-                    style="width: 100%"
-                    v-else
-                >
-                    <el-option
-                        v-for="item in levelCollectiveOptions"
-                        :key="item.value"
-                        :label="item.label"
-                        :value="item.value"
-                    >
-                        <span style="float: left">{{ item.label }}</span>
-                        <span style="float: right; color: #8492a6; font-size: 13px">{{ item.desc }}</span>
-                    </el-option>
-                </el-select>
-            </el-form-item>
-            <el-form-item prop="durationOfWork" label="作品时长">
-                <el-input v-model="formData.durationOfWork"></el-input>
-            </el-form-item>
-            <el-form-item prop="instructor" label="指导老师">
-                <el-input v-model="formData.instructor"></el-input>
-            </el-form-item>
-            <el-form-item prop="video" label="作品视频">
-                <video-upload v-model="formData.video"></video-upload>
-            </el-form-item>
-
-            <el-form-item prop="gradingOrganizationId" label="考级机构">
-                <el-select
-                    v-model="formData.gradingOrganizationId"
-                    clearable
-                    filterable
-                    placeholder="请选择"
-                    style="width: 100%"
-                >
-                    <el-option
-                        v-for="item in gradingOrganizationIdOptions"
-                        :key="item.value"
-                        :label="item.label"
-                        :value="item.value"
-                    >
-                    </el-option>
-                </el-select>
-            </el-form-item>
-            <el-form-item prop="organizationId" label="承办单位">
-                <el-select
-                    v-model="formData.organizationId"
-                    clearable
-                    filterable
-                    placeholder="请选择"
-                    style="width: 100%"
-                >
-                    <el-option
-                        v-for="item in organizationIdOptions"
-                        :key="item.value"
-                        :label="item.label"
-                        :value="item.value"
-                    >
-                    </el-option>
-                </el-select>
-            </el-form-item>
-            <el-form-item prop="examPoint" label="考级点">
-                <el-input v-model="formData.examPoint"></el-input>
-            </el-form-item>
-            <el-form-item prop="contact" label="联系人">
-                <el-input v-model="formData.contact"></el-input>
-            </el-form-item>
-            <el-form-item prop="phone" label="联系电话">
-                <el-input v-model="formData.phone"></el-input>
-            </el-form-item>
-
-            <!-- <el-form-item label="参演人员"> -->
-            <el-button type="primary" size="mini" @click="onAddOtherForm()" style="margin: 10px" plain
-                >添加参演人员</el-button
-            >
-            <div class="add-con" v-for="(item, index) in participants" :key="index">
-                <div v-if="!item.del">
-                    <el-row>
-                        <el-col :span="11">
-                            <el-form-item prop="name" label="姓名">
-                                <el-input v-model="item.name"></el-input>
-                            </el-form-item>
-                        </el-col>
-                        <el-col :span="11">
-                            <el-form-item prop="birthday" label="出生年月">
-                                <el-date-picker
-                                    v-model="item.birthday"
-                                    type="date"
-                                    value-format="yyyy-MM-dd"
-                                    placeholder="选择日期"
+            <el-timeline>
+                <el-timeline-item placement="top" size="normal" timestamp="基本信息">
+                    <el-card shadow="hover" :body-style="{ padding: '20px' }">
+                        <el-form-item prop="name" label="节目名称">
+                            <el-input v-model="formData.name" class="width"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="specialtyId" label="参赛专业">
+                            <el-cascader
+                                ref="artCascader"
+                                class="width"
+                                v-model="formData.specialtyId"
+                                :props="optionProps"
+                                :options="artTypes"
+                                :show-all-levels="false"
+                                placeholder="请选择专业"
+                            >
+                            </el-cascader>
+                        </el-form-item>
+                        <el-form-item prop="competitionGroup" label="参赛组别">
+                            <el-select
+                                v-model="formData.competitionGroup"
+                                clearable
+                                filterable
+                                placeholder="请选择"
+                                class="width"
+                            >
+                                <el-option
+                                    v-for="item in competitionGroupOptions"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
                                 >
-                                </el-date-picker>
-                            </el-form-item>
-                        </el-col>
-                    </el-row>
-                    <el-row>
-                        <el-col :span="11">
-                            <el-form-item prop="sex" label="性别">
-                                <el-select v-model="item.sex">
-                                    <el-option label="男" value="男"></el-option>
-                                    <el-option label="女" value="女"></el-option>
-                                </el-select>
-                            </el-form-item>
-                        </el-col>
-                        <el-col :span="11">
-                            <el-form-item prop="phone" label="联系方式">
-                                <el-input v-model="item.phone"></el-input>
-                            </el-form-item>
-                        </el-col>
-                    </el-row>
-                    <el-row>
-                        <el-form-item label="照片">
-                            <single-upload v-model="item.img"></single-upload>
+                                </el-option>
+                            </el-select>
                         </el-form-item>
+                        <el-form-item prop="levelSettingId" label="参赛级别" v-if="formData.competitionGroup">
+                            <el-select
+                                v-model="formData.levelSettingId"
+                                clearable
+                                filterable
+                                placeholder="请选择"
+                                class="width"
+                                v-if="formData.competitionGroup == 'SINGLE'"
+                            >
+                                <el-option
+                                    v-for="item in levelSingleOptions"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                >
+                                    <span style="float: left">{{ item.label }}</span>
+                                    <span style="float: right; color: #8492a6; font-size: 13px">{{ item.desc }}</span>
+                                </el-option>
+                            </el-select>
+                            <el-select
+                                v-model="formData.levelSettingId"
+                                clearable
+                                filterable
+                                placeholder="请选择"
+                                class="width"
+                                v-else
+                            >
+                                <el-option
+                                    v-for="item in levelCollectiveOptions"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                >
+                                    <span style="float: left">{{ item.label }}</span>
+                                    <span style="float: right; color: #8492a6; font-size: 13px">{{ item.desc }}</span>
+                                </el-option>
+                            </el-select>
+                        </el-form-item>
+                        <el-form-item prop="durationOfWork" label="作品时长">
+                            <el-input v-model="formData.durationOfWork" class="width"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="instructor" label="指导老师">
+                            <el-input v-model="formData.instructor" class="width"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="video" label="作品视频">
+                            <video-upload v-model="formData.video" class="width"></video-upload>
+                        </el-form-item>
+                    </el-card>
+                </el-timeline-item>
 
-                        <el-form-item>
-                            <el-button
-                                class="del"
-                                type="danger"
-                                size="small"
-                                icon="el-icon-delete"
-                                @click="onDeleteOtherForm(item, index)"
-                            ></el-button>
+                <el-timeline-item placement="top" size="normal" timestamp="单位信息">
+                    <el-card shadow="hover" :body-style="{ padding: '20px' }">
+                        <el-form-item prop="gradingOrganizationId" label="考级机构">
+                            <el-select
+                                v-model="formData.gradingOrganizationId"
+                                clearable
+                                filterable
+                                placeholder="请选择"
+                                class="width"
+                            >
+                                <el-option
+                                    v-for="item in gradingOrganizationIdOptions"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                >
+                                </el-option>
+                            </el-select>
+                        </el-form-item>
+                        <el-form-item prop="organizationId" label="承办单位">
+                            <el-select
+                                v-model="formData.organizationId"
+                                clearable
+                                filterable
+                                placeholder="请选择"
+                                class="width"
+                            >
+                                <el-option
+                                    v-for="item in organizationIdOptions"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                >
+                                </el-option>
+                            </el-select>
+                        </el-form-item>
+                        <el-form-item prop="examPoint" label="考级点">
+                            <el-input v-model="formData.examPoint" class="width"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="contact" label="联系人">
+                            <el-input v-model="formData.contact" class="width"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="phone" label="联系电话">
+                            <el-input v-model="formData.phone" class="width"></el-input>
                         </el-form-item>
-                    </el-row>
-                </div>
-            </div>
-            <!-- </el-form-item> -->
+                    </el-card>
+                </el-timeline-item>
+
+                <el-timeline-item placement="top" size="normal" timestamp="参演人员">
+                    <el-card shadow="hover" :body-style="{ padding: '20px' }">
+                        <div v-for="(item, index) in participants" :key="index">
+                            <div class="add-con" v-if="!item.del">
+                                <el-row>
+                                    <el-col :span="11">
+                                        <el-form-item prop="name" label="姓名">
+                                            <el-input v-model="item.name"></el-input>
+                                        </el-form-item>
+                                    </el-col>
+                                    <el-col :span="11">
+                                        <el-form-item prop="birthday" label="出生年月">
+                                            <el-date-picker
+                                                v-model="item.birthday"
+                                                type="date"
+                                                value-format="yyyy-MM-dd"
+                                                placeholder="选择日期"
+                                            >
+                                            </el-date-picker>
+                                        </el-form-item>
+                                    </el-col>
+                                </el-row>
+                                <el-row>
+                                    <el-col :span="11">
+                                        <el-form-item prop="sex" label="性别">
+                                            <el-select v-model="item.sex">
+                                                <el-option label="男" value="男"></el-option>
+                                                <el-option label="女" value="女"></el-option>
+                                            </el-select>
+                                        </el-form-item>
+                                    </el-col>
+                                    <el-col :span="11">
+                                        <el-form-item prop="phone" label="联系方式">
+                                            <el-input v-model="item.phone"></el-input>
+                                        </el-form-item>
+                                    </el-col>
+                                </el-row>
+                                <el-row>
+                                    <el-form-item label="照片">
+                                        <single-upload v-model="item.img"></single-upload>
+                                    </el-form-item>
+
+                                    <el-form-item>
+                                        <el-button
+                                            class="del"
+                                            type="danger"
+                                            size="small"
+                                            icon="el-icon-delete"
+                                            @click="onDeleteOtherForm(item, index)"
+                                        ></el-button>
+                                    </el-form-item>
+                                </el-row>
+                            </div>
+                        </div>
+                        <el-button type="primary" size="mini" @click="onAddOtherForm()" style="margin: 10px" plain
+                            >添加参演人员</el-button
+                        >
+                    </el-card>
+                </el-timeline-item>
+            </el-timeline>
 
             <el-form-item>
                 <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
@@ -428,14 +446,14 @@ export default {
         onDeleteOtherForm(info, index) {
             this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'warning' })
                 .then(() => {
-                    const schedules = [...this.schedules];
+                    const participants = [...this.participants];
                     if (info.id) {
                         info.del = true;
-                        schedules[index] = info;
+                        participants[index] = info;
                     } else {
-                        schedules.splice(index, 1);
+                        participants.splice(index, 1);
                     }
-                    this.schedules = schedules;
+                    this.participants = participants;
                     this.$message.success('删除成功');
                 })
                 .catch(e => {});
@@ -444,10 +462,18 @@ export default {
 };
 </script>
 <style lang="less" scoped>
+.edit-view {
+    padding: 0 0;
+    background-color: transparent;
+}
 .add-con {
-    background-color: #f7f7f7;
-    width: 100%;
-    margin: 7px 0;
-    padding-top: 14px;
+    border-style: solid;
+    border-width: 1px;
+    border-color: #dcdfe6;
+    margin-top: 7px;
+    padding-top: 10px;
+}
+.width {
+    width: 260px;
 }
 </style>

+ 158 - 22
src/main/vue/src/views/performance/ProgrammeList.vue

@@ -1,8 +1,27 @@
 <template>
     <div class="list-view">
         <div class="filters-container">
-            <el-form :model="form" inline size="mini">
+            <el-form :model="form" inline label-width="100px">
                 <el-row>
+                    <el-col :span="8">
+                        <el-form-item label="展演活动名称">
+                            <el-select
+                                v-model="performanceId"
+                                clearable
+                                filterable
+                                placeholder="展演活动名称"
+                                style="width: 100%"
+                            >
+                                <el-option
+                                    v-for="item in performances"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                >
+                                </el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
                     <el-col :span="8">
                         <el-form-item label="考级机构名称">
                             <el-select
@@ -41,18 +60,30 @@
                             </el-select>
                         </el-form-item>
                     </el-col>
-                    <el-col :span="8"
-                        ><el-form-item label="考级点名称">
-                            <el-input placeholder="考级点名称" v-model="search" clearable></el-input> </el-form-item
-                    ></el-col>
                 </el-row>
                 <el-row>
                     <el-col :span="8"
-                        ><el-form-item label="节目参赛专业">
-                            <el-input placeholder="参赛专业" v-model="search" clearable></el-input> </el-form-item
+                        ><el-form-item label="考级点名称">
+                            <el-input placeholder="考级点名称" v-model="search" clearable></el-input> </el-form-item
                     ></el-col>
+
+                    <el-col :span="8">
+                        <el-form-item label="参赛专业">
+                            <el-cascader
+                                ref="artCascader"
+                                style="width: 100%"
+                                v-model="specialtyId"
+                                :props="optionProps"
+                                :options="artTypes"
+                                :show-all-levels="false"
+                                placeholder="请选择专业"
+                                clearable
+                            >
+                            </el-cascader>
+                        </el-form-item>
+                    </el-col>
                     <el-col :span="8"
-                        ><el-form-item label="节目参赛组别">
+                        ><el-form-item label="参赛组别">
                             <el-select v-model="competitionGroup" clearable filterable placeholder="参赛组别">
                                 <el-option
                                     v-for="item in competitionGroupOptions"
@@ -108,8 +139,13 @@
                     <el-button @click="getData" type="primary" icon="el-icon-search">搜索 </el-button>
                     <el-button @click="clearSearch" type="primary">清空 </el-button>
                     <!-- <el-button @click="addRow" type="primary" icon="el-icon-plus">添加 </el-button> -->
-                    <el-button @click="download" type="primary" icon="el-icon-download" :loading="downloading"
-                        >导出EXCEL
+                    <el-button
+                        @click="download"
+                        type="primary"
+                        icon="el-icon-download"
+                        :loading="downloading"
+                        :disabled="totalElements < 1"
+                        >导出
                     </el-button>
                 </el-form-item>
             </el-form>
@@ -128,17 +164,22 @@
             <!-- <el-table-column prop="id" label="ID" width="100"> </el-table-column> -->
             <el-table-column prop="name" label="节目名称"> </el-table-column>
             <el-table-column prop="specialty" label="参赛专业"> </el-table-column>
-            <el-table-column prop="competitionGroup" label="参赛组别" :formatter="competitionGroupFormatter">
+            <el-table-column
+                prop="competitionGroup"
+                label="参赛组别"
+                :formatter="competitionGroupFormatter"
+                min-width="70"
+            >
+            </el-table-column>
+            <el-table-column prop="level" label="参赛级别" min-width="70"> </el-table-column>
+            <el-table-column prop="durationOfWork" label="作品时长" min-width="70"> </el-table-column>
+            <el-table-column prop="quantity" label="参赛人数" min-width="70"> </el-table-column>
+            <el-table-column prop="contact" label="联系人" min-width="68"> </el-table-column>
+            <el-table-column prop="phone" label="联系电话" min-width="95"> </el-table-column>
+            <el-table-column prop="gradingOrganization" label="考级机构" show-overflow-tooltip min-width="160">
             </el-table-column>
-            <el-table-column prop="level" label="参赛级别"> </el-table-column>
-            <el-table-column prop="durationOfWork" label="作品时长"> </el-table-column>
-            <el-table-column prop="quantity" label="参赛人数"> </el-table-column>
-            <el-table-column prop="contact" label="联系人"> </el-table-column>
-            <el-table-column prop="phone" label="联系电话"> </el-table-column>
-            <el-table-column prop="gradingOrganization" label="考级机构"> </el-table-column>
-            <el-table-column prop="organization" label="承办单位"> </el-table-column>
+            <el-table-column prop="organization" label="承办单位" min-width="160"> </el-table-column>
             <el-table-column prop="examPoint" label="考级点"> </el-table-column>
-            <el-table-column prop="video" label="节目视频"> </el-table-column>
             <el-table-column label="操作" align="center" fixed="right" min-width="280">
                 <template slot-scope="{ row }">
                     <el-button @click="showRow(row)" size="mini" plain>查看</el-button>
@@ -178,13 +219,14 @@
     </div>
 </template>
 <script>
+import delChild from '@/mixins/delChild';
 import { mapState } from 'vuex';
 import pageableTable from '@/mixins/pageableTable';
 import QrcodeVue from 'qrcode.vue';
 
 export default {
     name: 'ProgrammeList',
-    mixins: [pageableTable],
+    mixins: [pageableTable, delChild],
     data() {
         return {
             multipleMode: false,
@@ -205,7 +247,20 @@ export default {
             gradingOrganizationIdOptions: [],
             organizationIdOptions: [],
             gradingOrganizationId: '',
-            organizationId: ''
+            organizationId: '',
+            performanceId: '',
+            performances: [],
+            artTypes: [],
+            optionProps: {
+                value: 'id',
+                label: 'name',
+                children: 'children',
+                multiple: false,
+                emitPath: false,
+                checkStrictly: true,
+                expandTrigger: 'hover'
+            },
+            specialtyId: ''
         };
     },
     created() {
@@ -226,6 +281,9 @@ export default {
                 console.log(e);
                 this.$message.error(e.error);
             });
+        if (this.$route.query.pid) {
+            this.performanceId = Number(this.$route.query.pid);
+        }
         this.$http
             .post('/setting/byFlag', { flag: 4 })
             .then(res => {
@@ -275,6 +333,30 @@ export default {
                 console.log(e);
                 this.$message.error(e.error);
             });
+        this.$http
+            .post('/performance/all', { size: 1000, query: { del: false } }, { body: 'json' })
+            .then(res => {
+                if (res.content.length > 0) {
+                    res.content.forEach(item => {
+                        this.performances.push({
+                            label: item.name,
+                            value: item.id
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+        this.$http
+            .post('/artType/allList')
+            .then(res => {
+                this.artTypes = this.delChild(res);
+            })
+            .catch(e => {
+                console.log(e);
+            });
     },
     components: {
         QrcodeVue
@@ -309,6 +391,12 @@ export default {
             if (this.organizationId) {
                 data.query.organizationId = this.organizationId;
             }
+            if (this.performanceId) {
+                data.query.performanceId = this.performanceId;
+            }
+            if (this.specialtyId) {
+                data.query.code = this.getCode(this.specialtyId);
+            }
             return data;
         },
         toggleMultipleMode(multipleMode) {
@@ -335,10 +423,31 @@ export default {
         },
         download() {
             this.downloading = true;
+
+            let data = {
+                sort: 'createdAt,desc',
+                query: {}
+            };
+            if (this.competitionGroup) {
+                data.query.competitionGroup = this.competitionGroup;
+            }
+            if (this.levelSettingId) {
+                data.query.levelSettingId = this.levelSettingId;
+            }
+            if (this.gradingOrganizationId) {
+                data.query.gradingOrganizationId = this.gradingOrganizationId;
+            }
+            if (this.organizationId) {
+                data.query.organizationId = this.organizationId;
+            }
+            if (this.specialtyId) {
+                data.query.code = this.getCode(this.specialtyId);
+            }
+
             this.$axios
                 .get('/programme/excel', {
                     responseType: 'blob',
-                    params: { size: 10000 }
+                    params: data
                 })
                 .then(res => {
                     console.log(res);
@@ -394,7 +503,30 @@ export default {
             this.levelSettingId = '';
             this.gradingOrganizationId = '';
             this.organizationId = '';
+            this.specialtyId = '';
             this.getData();
+        },
+        getCode(value) {
+            return this.forTree(this.artTypes, value).code;
+        },
+        forTree(list, value) {
+            var result = null;
+            if (!list) {
+                return;
+            }
+            for (var i in list) {
+                if (result !== null) {
+                    break;
+                }
+                var item = list[i];
+                if (item.id == value) {
+                    result = item;
+                    break;
+                } else if (item.children && item.children.length > 0) {
+                    result = this.forTree(item.children, value);
+                }
+            }
+            return result;
         }
     }
 };
@@ -403,4 +535,8 @@ export default {
 .right {
     float: right;
 }
+/deep/.el-form-item--mini.el-form-item,
+.el-form-item--small.el-form-item {
+    margin-bottom: 10px;
+}
 </style>

+ 510 - 0
src/main/vue/src/views/performance/ProgrammeOrgList.vue

@@ -0,0 +1,510 @@
+<template>
+    <div class="list-view">
+        <div class="filters-container">
+            <el-form :model="form" inline label-width="100px">
+                <el-row>
+                    <el-col :span="8">
+                        <el-form-item label="展演活动名称">
+                            <el-select
+                                v-model="performanceId"
+                                clearable
+                                filterable
+                                placeholder="展演活动名称"
+                                style="width: 100%"
+                            >
+                                <el-option
+                                    v-for="item in performances"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                >
+                                </el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="8">
+                        <el-form-item label="考级机构名称">
+                            <el-select
+                                v-model="gradingOrganizationId"
+                                clearable
+                                filterable
+                                placeholder="考级机构"
+                                style="width: 100%"
+                            >
+                                <el-option
+                                    v-for="item in gradingOrganizationIdOptions"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                >
+                                </el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="8"
+                        ><el-form-item label="考级点名称">
+                            <el-input placeholder="考级点名称" v-model="search" clearable></el-input> </el-form-item
+                    ></el-col>
+                </el-row>
+                <el-row>
+                    <el-col :span="8">
+                        <el-form-item label="参赛专业">
+                            <el-cascader
+                                ref="artCascader"
+                                style="width: 100%"
+                                v-model="specialtyId"
+                                :props="optionProps"
+                                :options="artTypes"
+                                :show-all-levels="false"
+                                placeholder="请选择专业"
+                                clearable
+                            >
+                            </el-cascader>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="8"
+                        ><el-form-item label="参赛组别">
+                            <el-select v-model="competitionGroup" clearable filterable placeholder="参赛组别">
+                                <el-option
+                                    v-for="item in competitionGroupOptions"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                >
+                                </el-option>
+                            </el-select> </el-form-item
+                    ></el-col>
+                    <el-col :span="8"
+                        ><el-form-item label="参赛级别" v-if="competitionGroup">
+                            <el-select
+                                v-model="levelSettingId"
+                                clearable
+                                filterable
+                                placeholder="参赛级别"
+                                style="width: 100%"
+                                v-if="competitionGroup == 'SINGLE'"
+                            >
+                                <el-option
+                                    v-for="item in levelSingleOptions"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                >
+                                    <span style="float: left">{{ item.label }}</span>
+                                    <span style="float: right; color: #8492a6; font-size: 13px">{{ item.desc }}</span>
+                                </el-option>
+                            </el-select>
+                            <el-select
+                                v-model="levelSettingId"
+                                clearable
+                                filterable
+                                placeholder="请选择"
+                                style="width: 100%"
+                                v-else
+                            >
+                                <el-option
+                                    v-for="item in levelCollectiveOptions"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                >
+                                    <span style="float: left">{{ item.label }}</span>
+                                    <span style="float: right; color: #8492a6; font-size: 13px">{{ item.desc }}</span>
+                                </el-option>
+                            </el-select>
+                        </el-form-item></el-col
+                    >
+                </el-row>
+                <el-form-item>
+                    <el-button @click="getData" type="primary" icon="el-icon-search">搜索 </el-button>
+                    <el-button @click="clearSearch" type="primary">清空 </el-button>
+                    <!-- <el-button @click="addRow" type="primary" icon="el-icon-plus">添加 </el-button> -->
+                    <el-button
+                        @click="download"
+                        type="primary"
+                        icon="el-icon-download"
+                        :loading="downloading"
+                        :disabled="totalElements < 1"
+                        >导出
+                    </el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+            :height="tableHeight"
+        >
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+            <!-- <el-table-column prop="id" label="ID" width="100"> </el-table-column> -->
+            <el-table-column prop="name" label="节目名称"> </el-table-column>
+            <el-table-column prop="specialty" label="参赛专业"> </el-table-column>
+            <el-table-column
+                prop="competitionGroup"
+                label="参赛组别"
+                :formatter="competitionGroupFormatter"
+                min-width="70"
+            >
+            </el-table-column>
+            <el-table-column prop="level" label="参赛级别" min-width="70"> </el-table-column>
+            <el-table-column prop="durationOfWork" label="作品时长" min-width="70"> </el-table-column>
+            <el-table-column prop="quantity" label="参赛人数" min-width="70"> </el-table-column>
+            <el-table-column prop="contact" label="联系人" min-width="68"> </el-table-column>
+            <el-table-column prop="phone" label="联系电话" min-width="95"> </el-table-column>
+            <el-table-column prop="gradingOrganization" label="考级机构" show-overflow-tooltip min-width="160">
+            </el-table-column>
+            <el-table-column prop="organization" label="承办单位" min-width="160"> </el-table-column>
+            <el-table-column prop="examPoint" label="考级点"> </el-table-column>
+            <el-table-column label="操作" align="center" fixed="right" min-width="340">
+                <template slot-scope="{ row }">
+                    <el-button @click="showRow(row)" size="mini" plain>查看</el-button>
+                    <el-button type="success" @click="editRow(row)" size="mini" plain>编辑</el-button>
+                    <el-button type="warning" size="mini" plain>浏览视频</el-button>
+                    <el-button @click="showCode(row)" type="primary" size="mini" plain>查看二维码</el-button>
+                    <!-- <el-button @click="deleteRow(row)" type="danger" size="mini" plain>删除</el-button> -->
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <!-- <div class="multiple-mode-wrapper">
+                <el-button v-if="!multipleMode" @click="toggleMultipleMode(true)">批量编辑</el-button>
+                <el-button-group v-else>
+                    <el-button @click="operation1">批量操作1</el-button>
+                    <el-button @click="operation2">批量操作2</el-button>
+                    <el-button @click="toggleMultipleMode(false)">取消</el-button>
+                </el-button-group>
+            </div> -->
+            <el-pagination
+                background
+                @size-change="onSizeChange"
+                @current-change="onCurrentChange"
+                :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]"
+                :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="totalElements"
+            >
+            </el-pagination>
+        </div>
+
+        <el-dialog title="二维码" :visible.sync="dialogCode" width="400px" center>
+            <div style="margin-left: 70px;">
+                <qrcode-vue :value="dialogUrl" :size="200" level="H" />
+            </div>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import delChild from '@/mixins/delChild';
+import { mapState } from 'vuex';
+import pageableTable from '@/mixins/pageableTable';
+import QrcodeVue from 'qrcode.vue';
+
+export default {
+    name: 'ProgrammeOrgList',
+    mixins: [pageableTable, delChild],
+    data() {
+        return {
+            multipleMode: false,
+            search: '',
+            url: '/programme/backAll',
+            downloading: false,
+            competitionGroupOptions: [
+                { label: '个人', value: 'SINGLE' },
+                { label: '集体', value: 'COLLECTIVE' }
+            ],
+            form: {},
+            competitionGroup: '',
+            levelSingleOptions: [],
+            levelCollectiveOptions: [],
+            levelSettingId: '',
+            dialogUrl: '',
+            dialogCode: false,
+            gradingOrganizationIdOptions: [],
+            gradingOrganizationId: '',
+            performanceId: '',
+            performances: [],
+            artTypes: [],
+            optionProps: {
+                value: 'id',
+                label: 'name',
+                children: 'children',
+                multiple: false,
+                emitPath: false,
+                checkStrictly: true,
+                expandTrigger: 'hover'
+            },
+            specialtyId: ''
+        };
+    },
+    created() {
+        this.$http
+            .post('/setting/byFlag', { flag: 3 })
+            .then(res => {
+                if (res.length > 0) {
+                    res.forEach(item => {
+                        this.levelSingleOptions.push({
+                            label: item.name,
+                            value: item.id,
+                            desc: item.code
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+        if (this.$route.query.pid) {
+            this.performanceId = Number(this.$route.query.pid);
+        }
+        this.$http
+            .post('/setting/byFlag', { flag: 4 })
+            .then(res => {
+                if (res.length > 0) {
+                    res.forEach(item => {
+                        this.levelCollectiveOptions.push({
+                            label: item.name,
+                            value: item.id,
+                            desc: item.code
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+        this.$http
+            .post('/gradingOrganization/all', { size: 1000, query: { del: false } }, { body: 'json' })
+            .then(res => {
+                if (res.content.length > 0) {
+                    res.content.forEach(item => {
+                        this.gradingOrganizationIdOptions.push({
+                            label: item.name,
+                            value: item.id
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+        this.$http
+            .post('/performance/all', { size: 1000, query: { del: false } }, { body: 'json' })
+            .then(res => {
+                if (res.content.length > 0) {
+                    res.content.forEach(item => {
+                        this.performances.push({
+                            label: item.name,
+                            value: item.id
+                        });
+                    });
+                }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+        this.$http
+            .post('/artType/allList')
+            .then(res => {
+                this.artTypes = this.delChild(res);
+            })
+            .catch(e => {
+                console.log(e);
+            });
+    },
+    components: {
+        QrcodeVue
+    },
+    computed: {
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        competitionGroupFormatter(row, column, cellValue, index) {
+            let selectedOption = this.competitionGroupOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        beforeGetData() {
+            let data = {
+                sort: 'createdAt,desc',
+                query: {}
+            };
+            if (this.competitionGroup) {
+                data.query.competitionGroup = this.competitionGroup;
+            }
+            if (this.levelSettingId) {
+                data.query.levelSettingId = this.levelSettingId;
+            }
+            if (this.gradingOrganizationId) {
+                data.query.gradingOrganizationId = this.gradingOrganizationId;
+            }
+            if (this.performanceId) {
+                data.query.performanceId = this.performanceId;
+            }
+            if (this.specialtyId) {
+                data.query.code = this.getCode(this.specialtyId);
+            }
+            return data;
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: '/programmeEdit',
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: '/programmeEdit',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        showRow(row) {
+            this.$router.push({
+                path: '/programmeShow',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+
+            let data = {
+                sort: 'createdAt,desc',
+                query: {}
+            };
+            if (this.competitionGroup) {
+                data.query.competitionGroup = this.competitionGroup;
+            }
+            if (this.levelSettingId) {
+                data.query.levelSettingId = this.levelSettingId;
+            }
+            if (this.gradingOrganizationId) {
+                data.query.gradingOrganizationId = this.gradingOrganizationId;
+            }
+            if (this.organizationId) {
+                data.query.organizationId = this.organizationId;
+            }
+            if (this.specialtyId) {
+                data.query.code = this.getCode(this.specialtyId);
+            }
+
+            this.$axios
+                .get('/programme/excel', {
+                    responseType: 'blob',
+                    params: data
+                })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement('a');
+                    link.href = downloadUrl;
+                    link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        deleteRow(row) {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/programme/del/${row.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.getData();
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        this.$message.error(e.error);
+                    }
+                });
+        },
+        showCode(row) {
+            this.dialogCode = true;
+            this.dialogUrl =
+                'http://wljtest.izouma.com/h5/home?performanceId=' +
+                row.performanceId +
+                '&performanceApplyId=' +
+                row.id;
+        },
+        clearSearch() {
+            this.competitionGroup = '';
+            this.levelSettingId = '';
+            this.gradingOrganizationId = '';
+            this.organizationId = '';
+            this.specialtyId = '';
+            this.getData();
+        },
+        getCode(value) {
+            return this.forTree(this.artTypes, value).code;
+        },
+        forTree(list, value) {
+            var result = null;
+            if (!list) {
+                return;
+            }
+            for (var i in list) {
+                if (result !== null) {
+                    break;
+                }
+                var item = list[i];
+                if (item.id == value) {
+                    result = item;
+                    break;
+                } else if (item.children && item.children.length > 0) {
+                    result = this.forTree(item.children, value);
+                }
+            }
+            return result;
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+.right {
+    float: right;
+}
+/deep/.el-form-item--mini.el-form-item,
+.el-form-item--small.el-form-item {
+    margin-bottom: 10px;
+}
+</style>

+ 16 - 12
src/main/vue/src/views/performance/ProgrammeShow.vue

@@ -13,7 +13,7 @@
                 <el-timeline-item placement="top" size="normal" timestamp="节目信息">
                     <el-card shadow="hover" :body-style="{ padding: '20px' }">
                         <el-form-item prop="name" label="节目名称">
-                            <el-input v-model="formData.name" class="width"></el-input>
+                            <el-input v-model="formData.name" class="width" readonly></el-input>
                         </el-form-item>
                         <el-form-item prop="specialtyId" label="参赛专业">
                             <el-cascader
@@ -24,6 +24,7 @@
                                 :options="artTypes"
                                 :show-all-levels="false"
                                 placeholder="请选择专业"
+                                disabled
                             >
                             </el-cascader>
                         </el-form-item>
@@ -34,6 +35,7 @@
                                 clearable
                                 filterable
                                 placeholder="请选择"
+                                disabled
                             >
                                 <el-option
                                     v-for="item in competitionGroupOptions"
@@ -52,6 +54,7 @@
                                 placeholder="请选择"
                                 class="width"
                                 v-if="formData.competitionGroup == 'SINGLE'"
+                                disabled
                             >
                                 <el-option
                                     v-for="item in levelSingleOptions"
@@ -70,6 +73,7 @@
                                 placeholder="请选择"
                                 class="width"
                                 v-else
+                                disabled
                             >
                                 <el-option
                                     v-for="item in levelCollectiveOptions"
@@ -83,10 +87,10 @@
                             </el-select>
                         </el-form-item>
                         <el-form-item prop="durationOfWork" label="作品时长">
-                            <el-input v-model="formData.durationOfWork" class="width"></el-input>
+                            <el-input v-model="formData.durationOfWork" class="width" readonly></el-input>
                         </el-form-item>
                         <el-form-item prop="instructor" label="指导老师">
-                            <el-input v-model="formData.instructor" class="width"></el-input>
+                            <el-input v-model="formData.instructor" class="width" readonly></el-input>
                         </el-form-item>
                         <el-form-item prop="video" label="作品视频">
                             <video-upload v-model="formData.video" class="width"></video-upload>
@@ -103,6 +107,7 @@
                                 filterable
                                 placeholder="请选择"
                                 class="width"
+                                disabled
                             >
                                 <el-option
                                     v-for="item in gradingOrganizationIdOptions"
@@ -120,6 +125,7 @@
                                 filterable
                                 placeholder="请选择"
                                 class="width"
+                                disabled
                             >
                                 <el-option
                                     v-for="item in organizationIdOptions"
@@ -131,13 +137,13 @@
                             </el-select>
                         </el-form-item>
                         <el-form-item prop="examPoint" label="考级点">
-                            <el-input v-model="formData.examPoint" class="width"></el-input>
+                            <el-input v-model="formData.examPoint" class="width" readonly></el-input>
                         </el-form-item>
                         <el-form-item prop="contact" label="联系人">
-                            <el-input v-model="formData.contact" class="width"></el-input>
+                            <el-input v-model="formData.contact" class="width" readonly></el-input>
                         </el-form-item>
                         <el-form-item prop="phone" label="联系电话">
-                            <el-input v-model="formData.phone" class="width"></el-input>
+                            <el-input v-model="formData.phone" class="width" readonly></el-input>
                         </el-form-item>
                     </el-card>
                 </el-timeline-item>
@@ -432,13 +438,11 @@ export default {
     padding: 0 0;
     background-color: transparent;
 }
-.add-con {
-    background-color: #f7f7f7;
-    width: 100%;
-    margin: 7px 0;
-    padding-top: 14px;
-}
 .width {
     width: 260px;
 }
+/deep/.el-input.is-disabled .el-input__inner {
+    background-color: #ffffff;
+    color: #606266;
+}
 </style>

+ 4 - 0
src/main/vue/src/views/record/VideoList.vue

@@ -234,4 +234,8 @@ export default {
     line-height: 15px;
     padding: 0 10px 0 10px;
 }
+/deep/.el-form-item--mini.el-form-item,
+.el-form-item--small.el-form-item {
+    margin-bottom: 10px;
+}
 </style>