diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/satisfactionEvaluate/TheRapyRecuperationSatisfactionEvaluateController.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/satisfactionEvaluate/TheRapyRecuperationSatisfactionEvaluateController.java index b14d399..f7efc84 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/satisfactionEvaluate/TheRapyRecuperationSatisfactionEvaluateController.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/satisfactionEvaluate/TheRapyRecuperationSatisfactionEvaluateController.java @@ -1,28 +1,39 @@ package io.v.nutz.zhgh.therapyRecuperation.controller.satisfactionEvaluate; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import cn.wizzer.framework.base.Result; +import cn.wizzer.framework.page.Pagination; +import com.deepoove.poi.XWPFTemplate; +import com.deepoove.poi.config.Configure; +import com.deepoove.poi.data.PictureRenderData; import io.v.nutz.base.query.PageForm; +import io.v.nutz.base.utils.OfficeTemplateUtil; import io.v.nutz.sys.models.Sys_user; import io.v.nutz.web.commons.utils.ShiroUtil; import io.v.nutz.zhgh.therapyRecuperation.model.*; import io.v.nutz.zhgh.therapyRecuperation.service.TheRapyRecuperationEnrollService; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.nutz.boot.starter.ftp.FtpService; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.json.Json; import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; -import java.util.Date; -import java.util.List; -import java.util.Objects; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.time.ZoneId; +import java.util.*; @IocBean @At("/platform/theRapyRecuperation/satisfactionEvaluate") @@ -33,6 +44,10 @@ public class TheRapyRecuperationSatisfactionEvaluateController { private Dao dao; @Inject private TheRapyRecuperationEnrollService enrollService; + @Inject + private FtpService ftpService; + @Inject + private OfficeTemplateUtil officeTemplateUtil; @At("") @@ -44,14 +59,69 @@ public class TheRapyRecuperationSatisfactionEvaluateController { @At @RequiresPermissions("theRapyRecuperation.satisfactionEvaluate") - public Result pageData(PageForm pageForm) { - - return Result.success(); + public Result pageData(PageForm pageForm, Integer year, String lineId, String playTime, String destinationId, String currentCategory) { + Pagination pagination = null; + if (currentCategory.equals("line")) { + Sql sql = Sqls.create(""" + SELECT + t1.id, + t1.lineId, + concat(DATE_FORMAT(t1.playStartTime, '%Y-%m-%d'), ' 至 ', DATE_FORMAT(t1.playEndTime, '%Y-%m-%d')) AS playTime, + t1.playStartTime, + t1.playEndTime, + t2.lineName, + (SELECT count(1) FROM the_rapy_recuperation_enroll WHERE takePartInLineId = t1.id AND isTakePartIn = 1) totalNum, + (SELECT count(1) FROM the_rapy_recuperation_satisfaction_evaluate WHERE selectLineId = t1.id) evaluatedNum, + ( + SELECT + SUM( + IFNULL(accommodationScore, 0) + IFNULL(cateringScore, 0) + IFNULL(travelScore, 0) + IFNULL(guideScore, 0) + IFNULL(safetyScore, 0)) / (COUNT(*) * 100) * 100 AS averageScore + FROM + the_rapy_recuperation_satisfaction_evaluate + WHERE + selectLineId = t1.id) averageScore + FROM + the_rapy_recuperation_line_union_select t1 + LEFT JOIN the_rapy_recuperation_line t2 ON t2.id = t1.lineId + $condition + """); + Cnd cnd = Cnd.NEW(); + cnd.and("YEAR(t1.selectTime)", "=", year); + cnd.andEX("t1.lineId", "=", lineId); + sql.setCondition(cnd); + pagination = enrollService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + } else { + Sql sql = Sqls.create(""" + SELECT + t1.id, + t1.baseName, + (SELECT count(1) FROM the_rapy_recuperation_enroll WHERE takePartInBaseManagementId = t1.id AND isTakePartIn = 1) totalNum, + (SELECT count(1) FROM the_rapy_recuperation_satisfaction_evaluate WHERE hotelId = t1.id) evaluatedNum, + ( + SELECT + SUM( + IFNULL(accommodationScore, 0) + IFNULL(cateringScore, 0) + IFNULL(travelScore, 0) + IFNULL(guideScore, 0) + IFNULL(safetyScore, 0)) / (COUNT(*) * 100) * 100 AS averageScore + FROM + the_rapy_recuperation_satisfaction_evaluate + WHERE + hotelId = t1.id) averageScore + FROM + `the_rapy_recuperation_base_management` t1 + $condition + """); + Cnd cnd = Cnd.NEW(); + cnd.and("t1.year", "=", year); + cnd.andEX("t1.id", "=", destinationId); + sql.setCondition(cnd); + pagination = enrollService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + } + return Result.success(pagination); } /** * 查询线路 + * * @param year * @return */ @@ -78,6 +148,7 @@ public class TheRapyRecuperationSatisfactionEvaluateController { /** * 查询线路的出行时间会有多条 + * * @param lineId * @param year * @return @@ -96,13 +167,387 @@ public class TheRapyRecuperationSatisfactionEvaluateController { ORDER BY playStartTime ASC """); - sql.setParam("lineId",lineId); + sql.setParam("lineId", lineId); sql.setParam("year", year); List list = enrollService.listMap(sql); return Result.success(list); } + /** + * 查询目的地 + * + * @param year + * @return + */ + @At + @RequiresPermissions("theRapyRecuperation.satisfactionEvaluate") + public Result listDestinations(Integer year) { + Sql sql = Sqls.create(""" + SELECT + t1.id, + t1.baseName + FROM + `the_rapy_recuperation_base_management` t1 + WHERE + year = @year + """); + sql.setParam("year", year); + List list = enrollService.listMap(sql); + return Result.success(list); + } + + /** + * 年度总体满意度详情 + * + * @param year + * @return + */ + @At + @RequiresPermissions("theRapyRecuperation.satisfactionEvaluate") + public Result totalStat(Integer year) { + NutMap result = NutMap.NEW(); + + // 参加总人数 + int totalNum = dao.count(TheRapyRecuperationEnroll.class, Cnd.where("YEAR(signingUptime)", "=", year)); + // 已评价人数 + int evaluatedNum = dao.count(TheRapyRecuperationSatisfactionEvaluate.class, Cnd.where("YEAR(evaluateDate)", "=", year)); + // 完成率 + int evaluateRate = (int) (evaluatedNum / (double) totalNum * 100); + // 总体满意度 + Sql sql = Sqls.create(""" + SELECT + COUNT(*) as totalPeople, + AVG(IFNULL(accommodationScore, 0)) as avgAccommodationScore, + AVG(IFNULL(cateringScore, 0)) as avgCateringScore, + AVG(IFNULL(travelScore, 0)) as avgTravelScore, + AVG(IFNULL(guideScore, 0)) as avgGuideScore, + AVG(IFNULL(safetyScore, 0)) as avgSafetyScore, + SUM( + IFNULL(accommodationScore, 0) + + IFNULL(cateringScore, 0) + + IFNULL(travelScore, 0) + + IFNULL(guideScore, 0) + + IFNULL(safetyScore, 0) + ) as totalScore, + SUM( + IFNULL(accommodationScore, 0) + + IFNULL(cateringScore, 0) + + IFNULL(travelScore, 0) + + IFNULL(guideScore, 0) + + IFNULL(safetyScore, 0) + ) / (COUNT(*) * 100) * 100 as averageScore + FROM + the_rapy_recuperation_satisfaction_evaluate + WHERE + YEAR(evaluateDate) = @year + """); + sql.setParam("year", year); + NutMap map = enrollService.fetch(sql); + + result.put("totalNum", totalNum); + result.put("evaluatedNum", evaluatedNum); + result.put("evaluateRate", evaluateRate); + result.put("averageScore", map.getInt("averageScore")); + + return Result.success(result); + } + + + /** + * 某一条线路的满意度详情 + * + * @param selectLineId + * @return + */ + @At + @RequiresPermissions("theRapyRecuperation.satisfactionEvaluate") + public Result lineStat(String selectLineId) { + Sql sql = Sqls.create(""" + SELECT + t1.id, + t1.lineId, + concat(DATE_FORMAT(t1.playStartTime, '%Y-%m-%d'), ' 至 ', DATE_FORMAT(t1.playEndTime, '%Y-%m-%d')) AS playTime, + t1.playStartTime, + t1.playEndTime, + t2.lineName, + (SELECT count(1) FROM the_rapy_recuperation_enroll WHERE takePartInLineId = t1.id AND isTakePartIn = 1) totalNum, + (SELECT count(1) FROM the_rapy_recuperation_satisfaction_evaluate WHERE selectLineId = t1.id) evaluatedNum, + ( + SELECT + SUM( + IFNULL(accommodationScore, 0) + IFNULL(cateringScore, 0) + IFNULL(travelScore, 0) + IFNULL(guideScore, 0) + IFNULL(safetyScore, 0)) / (COUNT(*) * 100) * 100 AS averageScore + FROM + the_rapy_recuperation_satisfaction_evaluate + WHERE + selectLineId = t1.id) averageScore + FROM + the_rapy_recuperation_line_union_select t1 + LEFT JOIN the_rapy_recuperation_line t2 ON t2.id = t1.lineId + WHERE t1.id = @selectLineId + """); + sql.setParam("selectLineId", selectLineId); + NutMap basicInfo = enrollService.fetch(sql); + + int totalNum = basicInfo.getInt("totalNum", 0); + + List evaluates = dao.query(TheRapyRecuperationSatisfactionEvaluate.class, Cnd.where("selectLineId", "=", selectLineId)); + + List dimensionsList = new ArrayList<>(); + + List list = List.of("住宿条件", "餐饮状况", "行程景点安排", "车辆状况和司机服务", "导游服务", "安全措施"); + for (String s : list) { + NutMap row = NutMap.NEW(); + row.put("dimensionName", s); + if (s.equals("住宿条件")) { + List distribution = new ArrayList<>(); + // 好 + long goodNum = evaluates.stream().filter(e -> e.getAccommodationScore() >= 18 && e.getAccommodationScore() <= 20).count(); + distribution.add(NutMap.NEW().addv("label", "好").addv("count", goodNum).addv("rate", goodNum * 100 / totalNum)); + // 较好 + long betterNum = evaluates.stream().filter(e -> e.getAccommodationScore() >= 16 && e.getAccommodationScore() <= 17).count(); + distribution.add(NutMap.NEW().addv("label", "较好").addv("count", betterNum).addv("rate", betterNum * 100 / totalNum)); + // 一般 + long generalNum = evaluates.stream().filter(e -> e.getAccommodationScore() >= 12 && e.getAccommodationScore() <= 15).count(); + distribution.add(NutMap.NEW().addv("label", "一般").addv("count", generalNum).addv("rate", generalNum * 100 / totalNum)); + // 差 + long badNum = evaluates.stream().filter(e -> e.getAccommodationScore() < 12).count(); + distribution.add(NutMap.NEW().addv("label", "差").addv("count", badNum).addv("rate", badNum * 100 / totalNum)); + row.put("distribution", distribution); + + // 平均分 + int sum = evaluates.stream().filter(e -> e.getAccommodationScore() != null).mapToInt(TheRapyRecuperationSatisfactionEvaluate::getAccommodationScore).sum(); + row.put("averageScore", sum / (goodNum + betterNum + generalNum + badNum)); + + } else if (s.equals("餐饮状况")) { + List distribution = new ArrayList<>(); + // 好 + long goodNum = evaluates.stream().filter(e -> e.getCateringScore() >= 18 && e.getCateringScore() <= 20).count(); + distribution.add(NutMap.NEW().addv("label", "好").addv("count", goodNum).addv("rate", goodNum * 100 / totalNum)); + // 较好 + long betterNum = evaluates.stream().filter(e -> e.getCateringScore() >= 16 && e.getCateringScore() <= 17).count(); + distribution.add(NutMap.NEW().addv("label", "较好").addv("count", betterNum).addv("rate", betterNum * 100 / totalNum)); + // 一般 + long generalNum = evaluates.stream().filter(e -> e.getCateringScore() >= 12 && e.getCateringScore() <= 15).count(); + distribution.add(NutMap.NEW().addv("label", "一般").addv("count", generalNum).addv("rate", generalNum * 100 / totalNum)); + // 差 + long badNum = evaluates.stream().filter(e -> e.getCateringScore() < 12).count(); + distribution.add(NutMap.NEW().addv("label", "差").addv("count", badNum).addv("rate", badNum * 100 / totalNum)); + row.put("distribution", distribution); + + // 平均分 + int sum = evaluates.stream().filter(e -> e.getCateringScore() != null).mapToInt(TheRapyRecuperationSatisfactionEvaluate::getCateringScore).sum(); + row.put("averageScore", sum / (goodNum + betterNum + generalNum + badNum)); + + } else if (s.equals("行程景点安排")) { + List distribution = new ArrayList<>(); + // 好 + long goodNum = evaluates.stream().filter(e -> e.getTravelScore() >= 25 && e.getTravelScore() <= 30).count(); + distribution.add(NutMap.NEW().addv("label", "好").addv("count", goodNum).addv("rate", goodNum * 100 / totalNum)); + // 较好 + long betterNum = evaluates.stream().filter(e -> e.getTravelScore() >= 20 && e.getTravelScore() <= 24).count(); + distribution.add(NutMap.NEW().addv("label", "较好").addv("count", betterNum).addv("rate", betterNum * 100 / totalNum)); + // 一般 + long generalNum = evaluates.stream().filter(e -> e.getTravelScore() >= 15 && e.getTravelScore() <= 19).count(); + distribution.add(NutMap.NEW().addv("label", "一般").addv("count", generalNum).addv("rate", generalNum * 100 / totalNum)); + // 差 + long badNum = evaluates.stream().filter(e -> e.getTravelScore() < 15).count(); + distribution.add(NutMap.NEW().addv("label", "差").addv("count", badNum).addv("rate", badNum * 100 / totalNum)); + row.put("distribution", distribution); + + // 平均分 + int sum = evaluates.stream().filter(e -> e.getTravelScore() != null).mapToInt(TheRapyRecuperationSatisfactionEvaluate::getTravelScore).sum(); + row.put("averageScore", sum / (goodNum + betterNum + generalNum + badNum)); + + } else if (s.equals("车辆状况和司机服务")) { + List distribution = new ArrayList<>(); + // 好 + long goodNum = evaluates.stream().filter(e -> e.getCarDriverScore() >= 9 && e.getCarDriverScore() <= 10).count(); + distribution.add(NutMap.NEW().addv("label", "好").addv("count", goodNum).addv("rate", goodNum * 100 / totalNum)); + // 较好 + long betterNum = evaluates.stream().filter(e -> e.getCarDriverScore() >= 7 && e.getCarDriverScore() <= 8).count(); + distribution.add(NutMap.NEW().addv("label", "较好").addv("count", betterNum).addv("rate", betterNum * 100 / totalNum)); + // 一般 + long generalNum = evaluates.stream().filter(e -> e.getCarDriverScore() >= 5 && e.getCarDriverScore() <= 6).count(); + distribution.add(NutMap.NEW().addv("label", "一般").addv("count", generalNum).addv("rate", generalNum * 100 / totalNum)); + // 差 + long badNum = evaluates.stream().filter(e -> e.getCarDriverScore() < 5).count(); + distribution.add(NutMap.NEW().addv("label", "差").addv("count", badNum).addv("rate", badNum * 100 / totalNum)); + row.put("distribution", distribution); + + // 平均分 + int sum = evaluates.stream().filter(e -> e.getCarDriverScore() != null).mapToInt(TheRapyRecuperationSatisfactionEvaluate::getCarDriverScore).sum(); + row.put("averageScore", sum / (goodNum + betterNum + generalNum + badNum)); + + } else if (s.equals("导游服务")) { + List distribution = new ArrayList<>(); + // 好 + long goodNum = evaluates.stream().filter(e -> e.getGuideScore() >= 9 && e.getGuideScore() <= 10).count(); + distribution.add(NutMap.NEW().addv("label", "好").addv("count", goodNum).addv("rate", goodNum * 100 / totalNum)); + // 较好 + long betterNum = evaluates.stream().filter(e -> e.getGuideScore() >= 7 && e.getGuideScore() <= 8).count(); + distribution.add(NutMap.NEW().addv("label", "较好").addv("count", betterNum).addv("rate", betterNum * 100 / totalNum)); + // 一般 + long generalNum = evaluates.stream().filter(e -> e.getGuideScore() >= 5 && e.getGuideScore() <= 6).count(); + distribution.add(NutMap.NEW().addv("label", "一般").addv("count", generalNum).addv("rate", generalNum * 100 / totalNum)); + // 差 + long badNum = evaluates.stream().filter(e -> e.getGuideScore() < 5).count(); + distribution.add(NutMap.NEW().addv("label", "差").addv("count", badNum).addv("rate", badNum * 100 / totalNum)); + row.put("distribution", distribution); + + // 平均分 + int sum = evaluates.stream().filter(e -> e.getGuideScore() != null).mapToInt(TheRapyRecuperationSatisfactionEvaluate::getGuideScore).sum(); + row.put("averageScore", sum / (goodNum + betterNum + generalNum + badNum)); + + } else if (s.equals("安全措施")) { + List distribution = new ArrayList<>(); + // 好 + long goodNum = evaluates.stream().filter(e -> e.getSafetyScore() >= 9 && e.getSafetyScore() <= 10).count(); + distribution.add(NutMap.NEW().addv("label", "好").addv("count", goodNum).addv("rate", goodNum * 100 / totalNum)); + // 较好 + long betterNum = evaluates.stream().filter(e -> e.getSafetyScore() >= 7 && e.getSafetyScore() <= 8).count(); + distribution.add(NutMap.NEW().addv("label", "较好").addv("count", betterNum).addv("rate", betterNum * 100 / totalNum)); + // 一般 + long generalNum = evaluates.stream().filter(e -> e.getSafetyScore() >= 5 && e.getSafetyScore() <= 6).count(); + distribution.add(NutMap.NEW().addv("label", "一般").addv("count", generalNum).addv("rate", generalNum * 100 / totalNum)); + // 差 + long badNum = evaluates.stream().filter(e -> e.getSafetyScore() < 5).count(); + distribution.add(NutMap.NEW().addv("label", "差").addv("count", badNum).addv("rate", badNum * 100 / totalNum)); + row.put("distribution", distribution); + + // 平均分 + int sum = evaluates.stream().filter(e -> e.getSafetyScore() != null).mapToInt(TheRapyRecuperationSatisfactionEvaluate::getSafetyScore).sum(); + row.put("averageScore", sum / (goodNum + betterNum + generalNum + badNum)); + } + dimensionsList.add(row); + } + + return Result.success(Map.of("basicInfo", basicInfo, "dimensionsList", dimensionsList, "evaluates", evaluates)); + } + + + @At + @RequiresPermissions("theRapyRecuperation.satisfactionEvaluate") + public Result destinationStat(String destinationId) { + Sql sql = Sqls.create(""" + SELECT + t1.id, + t1.baseName, + (SELECT count(1) FROM the_rapy_recuperation_enroll WHERE takePartInBaseManagementId = t1.id AND isTakePartIn = 1) totalNum, + (SELECT count(1) FROM the_rapy_recuperation_satisfaction_evaluate WHERE hotelId = t1.id) evaluatedNum, + ( + SELECT + SUM( + IFNULL(accommodationScore, 0) + IFNULL(cateringScore, 0) + IFNULL(travelScore, 0) + IFNULL(guideScore, 0) + IFNULL(safetyScore, 0)) / (COUNT(*) * 100) * 100 AS averageScore + FROM + the_rapy_recuperation_satisfaction_evaluate + WHERE + hotelId = t1.id) averageScore + FROM + `the_rapy_recuperation_base_management` t1 + WHERE t1.id = @destinationId + """); + sql.setParam("destinationId", destinationId); + NutMap basicInfo = enrollService.fetch(sql); + + + int totalNum = basicInfo.getInt("totalNum", 1); + + List evaluates = dao.query(TheRapyRecuperationSatisfactionEvaluate.class, Cnd.where("hotelId", "=", destinationId)); + + List dimensionsList = new ArrayList<>(); + + List list = List.of("住宿条件", "餐饮状况", "行程景点安排", "安全措施"); + for (String s : list) { + NutMap row = NutMap.NEW(); + row.put("dimensionName", s); + if (s.equals("住宿条件")) { + List distribution = new ArrayList<>(); + // 好 + long goodNum = evaluates.stream().filter(e -> e.getAccommodationScore() >= 25 && e.getAccommodationScore() <= 30).count(); + distribution.add(NutMap.NEW().addv("label", "好").addv("count", goodNum).addv("rate", totalNum == 0 ? 0 : goodNum * 100 / totalNum)); + // 较好 + long betterNum = evaluates.stream().filter(e -> e.getAccommodationScore() >= 20 && e.getAccommodationScore() <= 24).count(); + distribution.add(NutMap.NEW().addv("label", "较好").addv("count", betterNum).addv("rate", totalNum == 0 ? 0 : betterNum * 100 / totalNum)); + // 一般 + long generalNum = evaluates.stream().filter(e -> e.getAccommodationScore() >= 15 && e.getAccommodationScore() <= 19).count(); + distribution.add(NutMap.NEW().addv("label", "一般").addv("count", generalNum).addv("rate", totalNum == 0 ? 0 : generalNum * 100 / totalNum)); + // 差 + long badNum = evaluates.stream().filter(e -> e.getAccommodationScore() < 15).count(); + distribution.add(NutMap.NEW().addv("label", "差").addv("count", badNum).addv("rate", totalNum == 0 ? 0 : badNum * 100 / totalNum)); + row.put("distribution", distribution); + + // 平均分 + int sum = evaluates.stream().filter(e -> e.getAccommodationScore() != null).mapToInt(TheRapyRecuperationSatisfactionEvaluate::getAccommodationScore).sum(); + row.put("averageScore", (goodNum + betterNum + generalNum + badNum) == 0 ? 0 : sum / (goodNum + betterNum + generalNum + badNum)); + + } else if (s.equals("餐饮状况")) { + List distribution = new ArrayList<>(); + // 好 + long goodNum = evaluates.stream().filter(e -> e.getCateringScore() >= 25 && e.getCateringScore() <= 30).count(); + distribution.add(NutMap.NEW().addv("label", "好").addv("count", goodNum).addv("rate", totalNum == 0 ? 0 : goodNum * 100 / totalNum)); + // 较好 + long betterNum = evaluates.stream().filter(e -> e.getCateringScore() >= 20 && e.getCateringScore() <= 24).count(); + distribution.add(NutMap.NEW().addv("label", "较好").addv("count", betterNum).addv("rate", totalNum == 0 ? 0 : betterNum * 100 / totalNum)); + // 一般 + long generalNum = evaluates.stream().filter(e -> e.getCateringScore() >= 15 && e.getCateringScore() <= 19).count(); + distribution.add(NutMap.NEW().addv("label", "一般").addv("count", generalNum).addv("rate", totalNum == 0 ? 0 : generalNum * 100 / totalNum)); + // 差 + long badNum = evaluates.stream().filter(e -> e.getCateringScore() < 15).count(); + distribution.add(NutMap.NEW().addv("label", "差").addv("count", badNum).addv("rate", totalNum == 0 ? 0 : badNum * 100 / totalNum)); + row.put("distribution", distribution); + + // 平均分 + int sum = evaluates.stream().filter(e -> e.getCateringScore() != null).mapToInt(TheRapyRecuperationSatisfactionEvaluate::getCateringScore).sum(); + row.put("averageScore", (goodNum + betterNum + generalNum + badNum) == 0 ? 0 : sum / (goodNum + betterNum + generalNum + badNum)); + + } else if (s.equals("行程景点安排")) { + List distribution = new ArrayList<>(); + // 好 + long goodNum = evaluates.stream().filter(e -> e.getTravelScore() >= 25 && e.getTravelScore() <= 30).count(); + distribution.add(NutMap.NEW().addv("label", "好").addv("count", goodNum).addv("rate", totalNum == 0 ? 0 : goodNum * 100 / totalNum)); + // 较好 + long betterNum = evaluates.stream().filter(e -> e.getTravelScore() >= 20 && e.getTravelScore() <= 24).count(); + distribution.add(NutMap.NEW().addv("label", "较好").addv("count", betterNum).addv("rate", totalNum == 0 ? 0 : betterNum * 100 / totalNum)); + // 一般 + long generalNum = evaluates.stream().filter(e -> e.getTravelScore() >= 15 && e.getTravelScore() <= 19).count(); + distribution.add(NutMap.NEW().addv("label", "一般").addv("count", generalNum).addv("rate", totalNum == 0 ? 0 : generalNum * 100 / totalNum)); + // 差 + long badNum = evaluates.stream().filter(e -> e.getTravelScore() < 15).count(); + distribution.add(NutMap.NEW().addv("label", "差").addv("count", badNum).addv("rate", totalNum == 0 ? 0 : badNum * 100 / totalNum)); + row.put("distribution", distribution); + + // 平均分 + int sum = evaluates.stream().filter(e -> e.getTravelScore() != null).mapToInt(TheRapyRecuperationSatisfactionEvaluate::getTravelScore).sum(); + row.put("averageScore", (goodNum + betterNum + generalNum + badNum) == 0 ? 0 : sum / (goodNum + betterNum + generalNum + badNum)); + + } else if (s.equals("安全措施")) { + List distribution = new ArrayList<>(); + // 好 + long goodNum = evaluates.stream().filter(e -> e.getSafetyScore() >= 9 && e.getSafetyScore() <= 10).count(); + distribution.add(NutMap.NEW().addv("label", "好").addv("count", goodNum).addv("rate", totalNum == 0 ? 0 : goodNum * 100 / totalNum)); + // 较好 + long betterNum = evaluates.stream().filter(e -> e.getSafetyScore() >= 7 && e.getSafetyScore() <= 8).count(); + distribution.add(NutMap.NEW().addv("label", "较好").addv("count", betterNum).addv("rate", totalNum == 0 ? 0 : betterNum * 100 / totalNum)); + // 一般 + long generalNum = evaluates.stream().filter(e -> e.getSafetyScore() >= 5 && e.getSafetyScore() <= 6).count(); + distribution.add(NutMap.NEW().addv("label", "一般").addv("count", generalNum).addv("rate", generalNum * 100 / totalNum)); + // 差 + long badNum = evaluates.stream().filter(e -> e.getSafetyScore() < 5).count(); + distribution.add(NutMap.NEW().addv("label", "差").addv("count", badNum).addv("rate", badNum * 100 / totalNum)); + row.put("distribution", distribution); + + // 平均分 + int sum = evaluates.stream().filter(e -> e.getSafetyScore() != null).mapToInt(TheRapyRecuperationSatisfactionEvaluate::getSafetyScore).sum(); + row.put("averageScore", (goodNum + betterNum + generalNum + badNum) == 0 ? 0 : sum / (goodNum + betterNum + generalNum + badNum)); + } + dimensionsList.add(row); + } + + return Result.success(Map.of("basicInfo", basicInfo, "dimensionsList", dimensionsList, "evaluates", evaluates)); + } + + /** * 提交评价 */ @@ -168,8 +613,16 @@ public class TheRapyRecuperationSatisfactionEvaluateController { result.put("teaNum", teaNum); result.put("hotelId", hotel.getId()); result.put("travelAgencyName", travelAgency.getTravelAgencyName()); - result.put("playStartTime", hotel.getActivityStartTime()); - result.put("playEndTime", hotel.getActivityEndTime()); + + String specificTime = enroll.getSpecificTime(); + String[] times = specificTime.split("-"); + int year = enroll.getSigningUptime().toInstant().atZone(ZoneId.systemDefault()).getYear(); + + String playStartTime = (year + "年" + times[0]).replace("年", "-").replace("月", "-").replace("日", ""); + String playEndTime = (year + "年" + times[1]).replace("年", "-").replace("月", "-").replace("日", ""); + + result.put("playStartTime", playStartTime); + result.put("playEndTime", playEndTime); } TheRapyRecuperationSatisfactionEvaluate evaluate = dao.fetch(TheRapyRecuperationSatisfactionEvaluate.class, Cnd.where("enrollId", "=", enrollId) @@ -183,4 +636,372 @@ public class TheRapyRecuperationSatisfactionEvaluateController { } + @At + @RequiresPermissions("theRapyRecuperation.satisfactionEvaluate") + public void export(Integer year, HttpServletResponse response) throws Exception { + // 创建临时目录 + File tempDir = new File(System.getProperty("java.io.tmpdir") + "/satisfaction_export_" + System.currentTimeMillis()); + File lineDir = new File(tempDir, "线路评价"); + File destinationDir = new File(tempDir, "目的地评价"); + + lineDir.mkdirs(); + destinationDir.mkdirs(); + + try { + // 线路 + Sql sql = Sqls.create(""" + SELECT + t1.id, + t2.playStartTime, + t2.playEndTime, + t3.lineName, + t3.travelAgencyPlace, + t1.accommodationScore, + t1.cateringScore, + t1.travelScore, + t1.guideScore, + t1.safetyScore, + t1.carDriverScore, + t1.mobile, + t1.signature, + t1.evaluateText, + t4.travelAgencyName, + t5.loginName, + t5.userName + FROM + the_rapy_recuperation_satisfaction_evaluate t1 + LEFT JOIN the_rapy_recuperation_line_union_select t2 ON t2.id = t1.selectLineId + LEFT JOIN the_rapy_recuperation_line t3 ON t3.id = t2.lineId + LEFT JOIN the_rapy_recuperation_travel_agency t4 ON t4.id = t3.travelAgencyId + LEFT JOIN the_rapy_recuperation_enroll t5 on t5.id = t1.enrollId + where t1.selectLineId is not null and t1.selectLineId != '' and YEAR(t1.evaluateDate) = @year + """); + sql.setParam("year", year); + List list = enrollService.listMap(sql); + for (NutMap row : list) { + int accommodationScore = row.getInt("accommodationScore", 0); + if (accommodationScore >= 18 && accommodationScore <= 20) { + row.put("a1", accommodationScore); + } else if (accommodationScore >= 16 && accommodationScore <= 17) { + row.put("a2", accommodationScore); + } else if (accommodationScore >= 12 && accommodationScore <= 15) { + row.put("a3", accommodationScore); + } else if (accommodationScore < 12) { + row.put("a4", accommodationScore); + } + + int cateringScore = row.getInt("cateringScore", 0); + if (cateringScore >= 18 && cateringScore <= 20) { + row.put("b1", cateringScore); + } else if (cateringScore >= 16 && cateringScore <= 17) { + row.put("b2", cateringScore); + } else if (cateringScore >= 12 && cateringScore <= 15) { + row.put("b3", cateringScore); + } else if (cateringScore < 12) { + row.put("b4", cateringScore); + } + + int travelScore = row.getInt("travelScore", 0); + if (travelScore >= 25 && travelScore <= 30) { + row.put("c1", travelScore); + } else if (travelScore >= 20 && travelScore <= 24) { + row.put("c2", travelScore); + } else if (travelScore >= 15 && travelScore <= 19) { + row.put("c3", travelScore); + } else if (travelScore < 15) { + row.put("c4", travelScore); + } + + int carDriverScore = row.getInt("carDriverScore", 0); + if (carDriverScore >= 9 && carDriverScore <= 10) { + row.put("d1", carDriverScore); + } else if (carDriverScore >= 7 && carDriverScore <= 8) { + row.put("d2", carDriverScore); + } else if (carDriverScore >= 5 && carDriverScore <= 6) { + row.put("d3", carDriverScore); + } else if (carDriverScore < 5) { + row.put("d4", carDriverScore); + } + + int guideScore = row.getInt("guideScore", 0); + if (guideScore >= 9 && guideScore <= 10) { + row.put("e1", guideScore); + } else if (guideScore >= 7 && guideScore <= 8) { + row.put("e2", guideScore); + } else if (guideScore >= 5 && guideScore <= 6) { + row.put("e3", guideScore); + } else if (guideScore < 5) { + row.put("e4", guideScore); + } + + int safetyScore = row.getInt("safetyScore", 0); + if (safetyScore >= 9 && safetyScore <= 10) { + row.put("f1", safetyScore); + } else if (safetyScore >= 7 && safetyScore <= 8) { + row.put("f2", safetyScore); + } else if (safetyScore >= 5 && safetyScore <= 6) { + row.put("f3", safetyScore); + } else if (safetyScore < 5) { + row.put("f4", safetyScore); + } + + // 总分 + int totalScore = accommodationScore + cateringScore + travelScore + carDriverScore + guideScore + safetyScore; + row.put("totalScore", totalScore); + + Date evaluateDate = row.getTime("evaluateDate"); + String formatDate = DateUtil.format(evaluateDate, "yyyy年MM月dd日"); + row.put("formatDate", formatDate); + + // 教职工人数 + int totalNum = dao.count(TheRapyRecuperationEnroll.class, Cnd.where("takePartInLineId", "=", row.getString("selectLineId")) + .and("isTakePartIn", "=", 1)); + row.put("totalNum", totalNum); + + // 时间 + Date playStartTime = row.getTime("playStartTime"); + String formatPlayStartTime = DateUtil.format(playStartTime, "yyyy年MM月dd日"); + Date playEndTime = row.getTime("playEndTime"); + String formatPlayEndTime = DateUtil.format(playEndTime, "MM月dd日"); + row.put("playDate", formatPlayStartTime + " 至 " + formatPlayEndTime); + + // 签名 + try (ByteArrayOutputStream byteOs = new ByteArrayOutputStream()) { + ftpService.download(row.getString("signature"), byteOs); + byte[] imageBytes = byteOs.toByteArray(); + PictureRenderData pictureRenderData = new PictureRenderData(70, 30, ".png", imageBytes); + row.put("qm", pictureRenderData); + } catch (Exception e) { + // 处理异常 + } + + // 生成文件名 + String fileName = row.getString("loginName") + "-" + row.getString("lineName") + ".docx"; + // 替换文件名中的非法字符 + fileName = fileName.replaceAll("[\\\\/:*?\"<>|]", "_"); + + File outputFile = new File(lineDir, fileName); + try (FileOutputStream os = new FileOutputStream(outputFile)) { + Configure config = Configure.builder().build(); + XWPFTemplate.compile(officeTemplateUtil.getInputStream("lxyjz"), config).render(row).writeAndClose(os); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // 目的地 + Sql sql2 = Sqls.create(""" + SELECT + t1.id, + t1.accommodationScore, + t1.cateringScore, + t1.travelScore, + t1.guideScore, + t1.safetyScore, + t1.carDriverScore, + t1.mobile, + t1.signature, + t1.evaluateText, + t1.enrollId, + t2.baseName, + t3.travelAgencyName, + t4.specificTime, + t4.loginName, + t4.userName, + t4.specificTime + FROM + the_rapy_recuperation_satisfaction_evaluate t1 + LEFT JOIN the_rapy_recuperation_base_management t2 ON t2.id = t1.hotelId + LEFT JOIN the_rapy_recuperation_travel_agency t3 ON t3.id = t2.travelAgencyId + LEFT JOIN the_rapy_recuperation_enroll t4 ON t4.id = t1.enrollId + WHERE + t1.hotelId IS NOT NULL && t1.hotelId != '' and YEAR(t1.evaluateDate) = @year + """); + sql2.setParam("year", year); + + List list2 = enrollService.listMap(sql2); + for (NutMap row : list2) { + int accommodationScore = row.getInt("accommodationScore", 0); + if (accommodationScore >= 25 && accommodationScore <= 30) { + row.put("a1", accommodationScore); + } else if (accommodationScore >= 20 && accommodationScore <= 24) { + row.put("a2", accommodationScore); + } else if (accommodationScore >= 15 && accommodationScore <= 19) { + row.put("a3", accommodationScore); + } else if (accommodationScore < 15) { + row.put("a4", accommodationScore); + } + + int cateringScore = row.getInt("cateringScore", 0); + if (cateringScore >= 25 && cateringScore <= 30) { + row.put("b1", cateringScore); + } else if (cateringScore >= 20 && cateringScore <= 24) { + row.put("b2", cateringScore); + } else if (cateringScore >= 15 && cateringScore <= 19) { + row.put("b3", cateringScore); + } else if (cateringScore < 15) { + row.put("b4", cateringScore); + } + + int travelScore = row.getInt("travelScore", 0); + if (travelScore >= 25 && travelScore <= 30) { + row.put("c1", travelScore); + } else if (travelScore >= 20 && travelScore <= 24) { + row.put("c2", travelScore); + } else if (travelScore >= 15 && travelScore <= 19) { + row.put("c3", travelScore); + } else if (travelScore < 15) { + row.put("c4", travelScore); + } + + int safetyScore = row.getInt("safetyScore", 0); + if (safetyScore >= 9 && safetyScore <= 10) { + row.put("d1", safetyScore); + } else if (safetyScore >= 7 && safetyScore <= 8) { + row.put("d2", safetyScore); + } else if (safetyScore >= 5 && safetyScore <= 6) { + row.put("d3", safetyScore); + } else if (safetyScore < 5) { + row.put("d4", safetyScore); + } + + // 总分 + int totalScore = accommodationScore + cateringScore + travelScore + safetyScore; + row.put("totalScore", totalScore); + + // 评价时间 + Date evaluateDate = row.getTime("evaluateDate"); + String formatDate = DateUtil.format(evaluateDate, "yyyy年MM月dd日"); + row.put("formatDate", formatDate); + + // 教职工人数 + int totalNum = dao.count(TheRapyRecuperationEnroll.class, Cnd.where("takePartInBaseManagementId", "=", row.getString("hotelId")) + .and("isTakePartIn", "=", 1)); + row.put("totalNum", totalNum); + + // 时间 + TheRapyRecuperationEnroll enroll = dao.fetch(TheRapyRecuperationEnroll.class, row.getString("enrollId")); + String specificTime = enroll.getSpecificTime(); + String[] times = specificTime.split("-"); + int year2 = enroll.getSigningUptime().toInstant().atZone(ZoneId.systemDefault()).getYear(); + + row.put("playDate", year2 + "年" + times[0] + "至" + times[1]); + + // 签名 + try (ByteArrayOutputStream byteOs = new ByteArrayOutputStream()) { + ftpService.download(row.getString("signature"), byteOs); + byte[] imageBytes = byteOs.toByteArray(); + PictureRenderData pictureRenderData = new PictureRenderData(70, 30, ".png", imageBytes); + row.put("qm", pictureRenderData); + } catch (Exception e) { + // 处理异常 + } + + // 生成文件名(使用目的地名称和评价ID) + String fileName = row.getString("userName") + "(" + row.getString("specificTime") + ")" + ".docx"; + // 替换文件名中的非法字符 + fileName = fileName.replaceAll("[\\\\/:*?\"<>|]", "_"); + + File outputFile = new File(destinationDir, fileName); + try (FileOutputStream os = new FileOutputStream(outputFile)) { + // 获取模板文件路径 + Configure config = Configure.builder().build(); + XWPFTemplate.compile(officeTemplateUtil.getInputStream("lxyfd"), config).render(row).writeAndClose(os); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // 创建ZIP文件 + String zipFileName = "满意度评价_" + year + "年.zip"; + File zipFile = new File(tempDir.getParentFile(), zipFileName); + + // 压缩文件 + try (java.util.zip.ZipOutputStream zipOut = new java.util.zip.ZipOutputStream(new FileOutputStream(zipFile))) { + // 添加线路文件夹 + addFolderToZip(lineDir, lineDir.getName(), zipOut); + // 添加目的地文件夹 + addFolderToZip(destinationDir, destinationDir.getName(), zipOut); + } + + // 设置响应头 + response.setContentType("application/zip"); + response.setHeader("Content-Disposition", "attachment; filename=" + java.net.URLEncoder.encode(zipFileName, "UTF-8")); + + // 将ZIP文件写入响应 + try (java.io.FileInputStream fis = new java.io.FileInputStream(zipFile); + java.io.OutputStream os = response.getOutputStream()) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = fis.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + os.flush(); + } + } finally { + // 清理临时文件 + deleteDirectory(tempDir); + File zipFile = new File(tempDir.getParentFile(), "满意度评价_" + year + "年.zip"); + if (zipFile.exists()) { + zipFile.delete(); + } + } + } + + /** + * 将文件夹添加到ZIP中 + */ + private void addFolderToZip(File folder, String folderName, java.util.zip.ZipOutputStream zipOut) throws Exception { + if (folder.exists() && folder.isDirectory()) { + File[] files = folder.listFiles(); + if (files != null) { + // 添加空文件夹 + if (files.length == 0) { + java.util.zip.ZipEntry zipEntry = new java.util.zip.ZipEntry(folderName + "/"); + zipOut.putNextEntry(zipEntry); + zipOut.closeEntry(); + return; + } + + // 添加文件 + for (File file : files) { + if (file.isDirectory()) { + addFolderToZip(file, folderName + "/" + file.getName(), zipOut); + } else { + byte[] buffer = new byte[4096]; + try (java.io.FileInputStream fis = new java.io.FileInputStream(file)) { + java.util.zip.ZipEntry zipEntry = new java.util.zip.ZipEntry(folderName + "/" + file.getName()); + zipOut.putNextEntry(zipEntry); + + int length; + while ((length = fis.read(buffer)) > 0) { + zipOut.write(buffer, 0, length); + } + zipOut.closeEntry(); + } + } + } + } + } + } + + /** + * 递归删除目录 + */ + private void deleteDirectory(File directory) { + if (directory.exists()) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + deleteDirectory(file); + } else { + file.delete(); + } + } + } + directory.delete(); + } + } + } diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationSatisfactionEvaluate.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationSatisfactionEvaluate.java index 3e8ee90..b9e1796 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationSatisfactionEvaluate.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationSatisfactionEvaluate.java @@ -65,6 +65,11 @@ public class TheRapyRecuperationSatisfactionEvaluate extends BaseModel { @ColDefine(type = ColType.INT) private Integer travelScore; + @Column + @Comment("车辆司机服务得分") + @ColDefine(type = ColType.INT) + private Integer carDriverScore; + @Column @Comment("导游服务得分") @ColDefine(type = ColType.INT) diff --git a/src/main/resources/views/mobile/therapyRecuperation/SatisfactionEvaluate.js b/src/main/resources/views/mobile/therapyRecuperation/SatisfactionEvaluate.js index f989ba6..abf6316 100644 --- a/src/main/resources/views/mobile/therapyRecuperation/SatisfactionEvaluate.js +++ b/src/main/resources/views/mobile/therapyRecuperation/SatisfactionEvaluate.js @@ -56,7 +56,6 @@ const SatisfactionEvaluate = { :key="idx" class="option-cell" :class="{'selected': getScoreLevel(formData[item.field], item) === idx}" - @click="selectOption(index, idx)" > {{option.range}} @@ -127,7 +126,7 @@ const SatisfactionEvaluate = { - `, + `, data() { return { show: false, @@ -170,7 +169,7 @@ const SatisfactionEvaluate = { { title: "车辆状况和司机服务", maxScore: 10, - field: "guideScore", + field: "carDriverScore", options: [ { label: "好", range: "10-9" }, { label: "较好", range: "8-7" }, @@ -256,9 +255,11 @@ const SatisfactionEvaluate = { }, 0); }, currentEvaluationItems() { - return this.formData.type === 1 - ? this.evaluationItems - : this.evaluationItemsType2; + if (this.formData && this.formData.selectLineId) { + return this.evaluationItems; + } else { + return this.evaluationItemsType2; + } }, }, methods: { @@ -267,17 +268,6 @@ const SatisfactionEvaluate = { this.getBasicInfo(); this.show = true; }, - selectOption(itemIndex, optionIndex) { - const item = this.currentEvaluationItems[itemIndex]; - const range = item.options[optionIndex].range; - if (range.includes("-")) { - const highScore = Number(range.split("-")[0]); - this.formData[item.field] = highScore; - } else if (range.includes("<")) { - const threshold = Number(range.replace("<", "")); - this.formData[item.field] = threshold - 1; - } - }, onSubmit() { this.$dialog .confirm({ diff --git a/src/main/resources/views/platform/theRapyRecuperation/satisfactionEvaluate/chart.js b/src/main/resources/views/platform/theRapyRecuperation/satisfactionEvaluate/chart.js new file mode 100644 index 0000000..c1f9432 --- /dev/null +++ b/src/main/resources/views/platform/theRapyRecuperation/satisfactionEvaluate/chart.js @@ -0,0 +1,249 @@ +const evaluateChart = { + template: /*language=HTML*/ ` + +
+
+ {{currentRoute.lineName}} ({{currentRoute.playTime}}) +
+
+ 参加人数: {{currentRoute.totalNum}}人 + 已评价人数: {{currentRoute.evaluatedNum}}人 + 评价完成率: {{currentRoute.evaluateRate}}% + 平均分: + + {{currentRoute.averageScore}}分 + + +
+
+ + + + + + + + + + + + + +
+
各维度评分对比
+
+
+ +
+
满意度分布
+
+
+
+ `, + data() { + return { + visible: false, + currentRoute: {}, + dimensionsList: [] + }; + }, + methods: { + onOpen(row, tableData) { + this.currentRoute = row; + this.dimensionsList = tableData.filter( + (item) => item.lineName === row.lineName + ); + this.visible = true; + + // 在下一个DOM更新周期渲染图表 + this.$nextTick(() => { + this.renderCharts(); + }); + }, + getSatisfactionClass(score) { + if (score >= 90) return "text-success"; + if (score >= 80) return "text-warning"; + return "text-danger"; + }, + getLevelColor(level) { + const colors = { + 好: "#67C23A", + 较好: "#409EFF", + 一般: "#E6A23C", + 差: "#F56C6C", + }; + return colors[level] || "#909399"; + }, + renderCharts() { + // 需要引入echarts库 + if (window.echarts && this.dimensionsList.length > 0) { + this.renderDimensionsChart(); + this.renderDistributionChart(); + } + }, + renderDimensionsChart() { + const chartDom = this.$refs.dimensionsChart; + const myChart = echarts.init(chartDom); + + const dimensions = this.dimensionsList.map( + (item) => item.dimensionName + ); + const scores = this.dimensionsList.map((item) => item.averageScore); + + const option = { + tooltip: { + trigger: "axis", + axisPointer: { + type: "shadow", + }, + }, + grid: { + left: "3%", + right: "4%", + bottom: "3%", + containLabel: true, + }, + xAxis: { + type: "category", + data: dimensions, + axisLabel: { + interval: 0, + rotate: 30, + }, + }, + yAxis: { + type: "value", + min: 70, + max: 100, + }, + series: [ + { + name: "平均分", + type: "bar", + data: scores, + itemStyle: { + color: function (params) { + const score = params.value; + if (score >= 90) return "#67C23A"; + if (score >= 80) return "#E6A23C"; + return "#F56C6C"; + }, + }, + label: { + show: true, + position: "top", + formatter: "{c}分", + }, + }, + ], + }; + + myChart.setOption(option); + }, + renderDistributionChart() { + const chartDom = this.$refs.distributionChart; + const myChart = echarts.init(chartDom); + + // 统计各个评价等级的总人数 + const distribution = { + 好: 0, + 较好: 0, + 一般: 0, + 差: 0, + }; + + this.dimensionsList.forEach((dimension) => { + dimension.distribution.forEach((item) => { + distribution[item.label] += item.count; + }); + }); + + const option = { + tooltip: { + trigger: "item", + formatter: "{b}: {c}人 ({d}%)", + }, + legend: { + orient: "vertical", + left: "left", + }, + series: [ + { + name: "满意度分布", + type: "pie", + radius: "70%", + center: ["50%", "50%"], + data: [ + { + value: distribution["好"], + name: "好", + itemStyle: { color: "#67C23A" }, + }, + { + value: distribution["较好"], + name: "较好", + itemStyle: { color: "#409EFF" }, + }, + { + value: distribution["一般"], + name: "一般", + itemStyle: { color: "#E6A23C" }, + }, + { + value: distribution["差"], + name: "差", + itemStyle: { color: "#F56C6C" }, + }, + ], + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: "rgba(0, 0, 0, 0.5)", + }, + }, + label: { + formatter: "{b}: {c}人 ({d}%)", + }, + }, + ], + }; + + myChart.setOption(option); + } + } +}; + +export default evaluateChart; diff --git a/src/main/resources/views/platform/theRapyRecuperation/satisfactionEvaluate/evaluateChart.js b/src/main/resources/views/platform/theRapyRecuperation/satisfactionEvaluate/evaluateChart.js new file mode 100644 index 0000000..417c918 --- /dev/null +++ b/src/main/resources/views/platform/theRapyRecuperation/satisfactionEvaluate/evaluateChart.js @@ -0,0 +1,375 @@ +const evaluateChart = { + template: /*language=HTML*/ ` + +
+
+ + +
+
+ 参加人数: {{currentRoute.totalNum}}人 + 已评价人数: {{currentRoute.evaluatedNum}}人 + 评价完成率: {{currentRoute.totalNum === 0 ? 0 : Math.round(currentRoute.evaluatedNum / currentRoute.totalNum * 100)}}% + 平均分: + + {{currentRoute.averageScore || 0}}分 + + +
+
+ + + + + + + + + + + + + +
+
满意度分布
+
+
+ + +
+
用户评价详情
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ `, + data() { + return { + visible: false, + loading: false, + currentRoute: null, + dimensionsList: [], + evaluates: [], + currentCategory: null, + }; + }, + methods: { + onOpen(id, currentCategory) { + this.visible = true; + this.loading = true; + this.currentCategory = currentCategory; + + if (this.currentCategory === "line") { + $.post("/platform/theRapyRecuperation/satisfactionEvaluate/lineStat", { + selectLineId: id, + }) + .then((res) => { + if (res.code === 0) { + this.currentRoute = res.data.basicInfo; + this.dimensionsList = res.data.dimensionsList; + this.evaluates = res.data.evaluates; + this.$nextTick(() => { + this.renderCharts(); + }); + } else { + this.$toast(res.msg); + } + }) + .always(() => { + this.loading = false; + }); + } else { + $.post( + "/platform/theRapyRecuperation/satisfactionEvaluate/destinationStat", + { + destinationId: id, + }, + ) + .then((res) => { + if (res.code === 0) { + this.currentRoute = res.data.basicInfo; + this.dimensionsList = res.data.dimensionsList; + this.evaluates = res.data.evaluates; + this.$nextTick(() => { + this.renderCharts(); + }); + } else { + this.$toast(res.msg); + } + }) + .always(() => { + this.loading = false; + }); + } + }, + getSatisfactionClass(score) { + // 根据评分范围返回不同的样式类 + if (score >= 25) return "text-success"; // 对于 30 分的项目 + if (score >= 15) return "text-success"; // 对于 20 分的项目 + if (score >= 8) return "text-success"; // 对于 10 分的项目 + if (score >= 4) return "text-success"; // 对于 5 分的项目 + + if (score >= 20 && score < 25) return "text-warning"; // 对于 30 分的项目 + if (score >= 12 && score < 15) return "text-warning"; // 对于 20 分的项目 + if (score >= 6 && score < 8) return "text-warning"; // 对于 10 分的项目 + if (score >= 3 && score < 4) return "text-warning"; // 对于 5 分的项目 + + return "text-danger"; + }, + getLevelColor(level) { + const colors = { + 好: "#67C23A", + 较好: "#409EFF", + 一般: "#E6A23C", + 差: "#F56C6C", + }; + return colors[level] || "#909399"; + }, + renderCharts() { + if (window.echarts && this.dimensionsList.length > 0) { + this.renderDistributionChart(); + } + }, + renderDistributionChart() { + const chartDom = this.$refs.distributionChart; + const myChart = echarts.init(chartDom); + + // 统计各个评价等级的总人数 + const distribution = { + 好: 0, + 较好: 0, + 一般: 0, + 差: 0, + }; + + this.dimensionsList.forEach((dimension) => { + dimension.distribution.forEach((item) => { + distribution[item.label] += item.count; + }); + }); + + const option = { + tooltip: { + trigger: "item", + formatter: "{b}: {c}人 ({d}%)", + }, + legend: { + orient: "vertical", + left: "left", + }, + series: [ + { + name: "满意度分布", + type: "pie", + radius: "70%", + center: ["50%", "50%"], + data: [ + { + value: distribution["好"], + name: "好", + itemStyle: { color: "#67C23A" }, + }, + { + value: distribution["较好"], + name: "较好", + itemStyle: { color: "#409EFF" }, + }, + { + value: distribution["一般"], + name: "一般", + itemStyle: { color: "#E6A23C" }, + }, + { + value: distribution["差"], + name: "差", + itemStyle: { color: "#F56C6C" }, + }, + ], + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: "rgba(0, 0, 0, 0.5)", + }, + }, + label: { + formatter: "{b}: {c}人 ({d}%)", + }, + }, + ], + }; + + myChart.setOption(option); + }, + }, +}; + +// 添加组件样式 +document.head.insertAdjacentHTML( + "beforeend", + ` + +`, +); diff --git a/src/main/resources/views/platform/theRapyRecuperation/satisfactionEvaluate/index.html b/src/main/resources/views/platform/theRapyRecuperation/satisfactionEvaluate/index.html new file mode 100644 index 0000000..341abee --- /dev/null +++ b/src/main/resources/views/platform/theRapyRecuperation/satisfactionEvaluate/index.html @@ -0,0 +1,616 @@ + + +
+ + + + + + + + + + +
+ + + +
+
+ +
+
+
{{totalStat.totalNum}}
+
参加总人数
+
+
+
+
+ + +
+
+ +
+
+
{{totalStat.evaluatedNum}}
+
已评价人数
+
+
+
+
+ + +
+
+ +
+
+
+ {{totalStat.totalNum ? totalStat.evaluateRate : 0}}% +
+
评价完成率
+
+
+
+
+ + +
+
+ +
+
+
{{totalStat.averageScore}}
+
总体满意度
+
+
+
+
+
+
+ + + +
+
+
+ + {{pageForm.currentCategory === 'line' ? '线路' : + '目的地'}}评价列表 +
+
+ 导出数据 +
+
+
+ + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + + + + +