Merge remote-tracking branch 'origin/main'
This commit is contained in:
+830
-9
@@ -1,28 +1,39 @@
|
|||||||
package io.v.nutz.zhgh.therapyRecuperation.controller.satisfactionEvaluate;
|
package io.v.nutz.zhgh.therapyRecuperation.controller.satisfactionEvaluate;
|
||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.wizzer.framework.base.Result;
|
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.query.PageForm;
|
||||||
|
import io.v.nutz.base.utils.OfficeTemplateUtil;
|
||||||
import io.v.nutz.sys.models.Sys_user;
|
import io.v.nutz.sys.models.Sys_user;
|
||||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||||
import io.v.nutz.zhgh.therapyRecuperation.model.*;
|
import io.v.nutz.zhgh.therapyRecuperation.model.*;
|
||||||
import io.v.nutz.zhgh.therapyRecuperation.service.TheRapyRecuperationEnrollService;
|
import io.v.nutz.zhgh.therapyRecuperation.service.TheRapyRecuperationEnrollService;
|
||||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
|
import org.nutz.boot.starter.ftp.FtpService;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
import org.nutz.dao.Sqls;
|
import org.nutz.dao.Sqls;
|
||||||
import org.nutz.dao.sql.Sql;
|
import org.nutz.dao.sql.Sql;
|
||||||
import org.nutz.ioc.loader.annotation.Inject;
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.json.Json;
|
||||||
import org.nutz.lang.util.NutMap;
|
import org.nutz.lang.util.NutMap;
|
||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
import org.nutz.mvc.annotation.Ok;
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
|
||||||
import java.util.Date;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.util.List;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.Objects;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
@IocBean
|
@IocBean
|
||||||
@At("/platform/theRapyRecuperation/satisfactionEvaluate")
|
@At("/platform/theRapyRecuperation/satisfactionEvaluate")
|
||||||
@@ -33,6 +44,10 @@ public class TheRapyRecuperationSatisfactionEvaluateController {
|
|||||||
private Dao dao;
|
private Dao dao;
|
||||||
@Inject
|
@Inject
|
||||||
private TheRapyRecuperationEnrollService enrollService;
|
private TheRapyRecuperationEnrollService enrollService;
|
||||||
|
@Inject
|
||||||
|
private FtpService ftpService;
|
||||||
|
@Inject
|
||||||
|
private OfficeTemplateUtil officeTemplateUtil;
|
||||||
|
|
||||||
|
|
||||||
@At("")
|
@At("")
|
||||||
@@ -44,14 +59,69 @@ public class TheRapyRecuperationSatisfactionEvaluateController {
|
|||||||
|
|
||||||
@At
|
@At
|
||||||
@RequiresPermissions("theRapyRecuperation.satisfactionEvaluate")
|
@RequiresPermissions("theRapyRecuperation.satisfactionEvaluate")
|
||||||
public Result pageData(PageForm pageForm) {
|
public Result pageData(PageForm pageForm, Integer year, String lineId, String playTime, String destinationId, String currentCategory) {
|
||||||
|
Pagination pagination = null;
|
||||||
return Result.success();
|
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
|
* @param year
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@@ -78,6 +148,7 @@ public class TheRapyRecuperationSatisfactionEvaluateController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询线路的出行时间会有多条
|
* 查询线路的出行时间会有多条
|
||||||
|
*
|
||||||
* @param lineId
|
* @param lineId
|
||||||
* @param year
|
* @param year
|
||||||
* @return
|
* @return
|
||||||
@@ -96,13 +167,387 @@ public class TheRapyRecuperationSatisfactionEvaluateController {
|
|||||||
ORDER BY
|
ORDER BY
|
||||||
playStartTime ASC
|
playStartTime ASC
|
||||||
""");
|
""");
|
||||||
sql.setParam("lineId",lineId);
|
sql.setParam("lineId", lineId);
|
||||||
sql.setParam("year", year);
|
sql.setParam("year", year);
|
||||||
List<NutMap> list = enrollService.listMap(sql);
|
List<NutMap> list = enrollService.listMap(sql);
|
||||||
return Result.success(list);
|
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<NutMap> 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<TheRapyRecuperationSatisfactionEvaluate> evaluates = dao.query(TheRapyRecuperationSatisfactionEvaluate.class, Cnd.where("selectLineId", "=", selectLineId));
|
||||||
|
|
||||||
|
List<NutMap> dimensionsList = new ArrayList<>();
|
||||||
|
|
||||||
|
List<String> list = List.of("住宿条件", "餐饮状况", "行程景点安排", "车辆状况和司机服务", "导游服务", "安全措施");
|
||||||
|
for (String s : list) {
|
||||||
|
NutMap row = NutMap.NEW();
|
||||||
|
row.put("dimensionName", s);
|
||||||
|
if (s.equals("住宿条件")) {
|
||||||
|
List<NutMap> 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<NutMap> 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<NutMap> 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<NutMap> 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<NutMap> 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<NutMap> 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<TheRapyRecuperationSatisfactionEvaluate> evaluates = dao.query(TheRapyRecuperationSatisfactionEvaluate.class, Cnd.where("hotelId", "=", destinationId));
|
||||||
|
|
||||||
|
List<NutMap> dimensionsList = new ArrayList<>();
|
||||||
|
|
||||||
|
List<String> list = List.of("住宿条件", "餐饮状况", "行程景点安排", "安全措施");
|
||||||
|
for (String s : list) {
|
||||||
|
NutMap row = NutMap.NEW();
|
||||||
|
row.put("dimensionName", s);
|
||||||
|
if (s.equals("住宿条件")) {
|
||||||
|
List<NutMap> 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<NutMap> 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<NutMap> 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<NutMap> 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("teaNum", teaNum);
|
||||||
result.put("hotelId", hotel.getId());
|
result.put("hotelId", hotel.getId());
|
||||||
result.put("travelAgencyName", travelAgency.getTravelAgencyName());
|
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)
|
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<NutMap> 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<NutMap> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -65,6 +65,11 @@ public class TheRapyRecuperationSatisfactionEvaluate extends BaseModel {
|
|||||||
@ColDefine(type = ColType.INT)
|
@ColDefine(type = ColType.INT)
|
||||||
private Integer travelScore;
|
private Integer travelScore;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("车辆司机服务得分")
|
||||||
|
@ColDefine(type = ColType.INT)
|
||||||
|
private Integer carDriverScore;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
@Comment("导游服务得分")
|
@Comment("导游服务得分")
|
||||||
@ColDefine(type = ColType.INT)
|
@ColDefine(type = ColType.INT)
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ const SatisfactionEvaluate = {
|
|||||||
:key="idx"
|
:key="idx"
|
||||||
class="option-cell"
|
class="option-cell"
|
||||||
:class="{'selected': getScoreLevel(formData[item.field], item) === idx}"
|
:class="{'selected': getScoreLevel(formData[item.field], item) === idx}"
|
||||||
@click="selectOption(index, idx)"
|
|
||||||
>
|
>
|
||||||
{{option.range}}
|
{{option.range}}
|
||||||
</td>
|
</td>
|
||||||
@@ -127,7 +126,7 @@ const SatisfactionEvaluate = {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
show: false,
|
show: false,
|
||||||
@@ -170,7 +169,7 @@ const SatisfactionEvaluate = {
|
|||||||
{
|
{
|
||||||
title: "车辆状况和司机服务",
|
title: "车辆状况和司机服务",
|
||||||
maxScore: 10,
|
maxScore: 10,
|
||||||
field: "guideScore",
|
field: "carDriverScore",
|
||||||
options: [
|
options: [
|
||||||
{ label: "好", range: "10-9" },
|
{ label: "好", range: "10-9" },
|
||||||
{ label: "较好", range: "8-7" },
|
{ label: "较好", range: "8-7" },
|
||||||
@@ -256,9 +255,11 @@ const SatisfactionEvaluate = {
|
|||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
currentEvaluationItems() {
|
currentEvaluationItems() {
|
||||||
return this.formData.type === 1
|
if (this.formData && this.formData.selectLineId) {
|
||||||
? this.evaluationItems
|
return this.evaluationItems;
|
||||||
: this.evaluationItemsType2;
|
} else {
|
||||||
|
return this.evaluationItemsType2;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -267,17 +268,6 @@ const SatisfactionEvaluate = {
|
|||||||
this.getBasicInfo();
|
this.getBasicInfo();
|
||||||
this.show = true;
|
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() {
|
onSubmit() {
|
||||||
this.$dialog
|
this.$dialog
|
||||||
.confirm({
|
.confirm({
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
const evaluateChart = {
|
||||||
|
template: /*language=HTML*/ `
|
||||||
|
<el-dialog
|
||||||
|
title="满意度评价详情"
|
||||||
|
:visible.sync="visible"
|
||||||
|
width="80%"
|
||||||
|
>
|
||||||
|
<div class="details-header">
|
||||||
|
<div class="details-title">
|
||||||
|
{{currentRoute.lineName}} ({{currentRoute.playTime}})
|
||||||
|
</div>
|
||||||
|
<div class="details-stats">
|
||||||
|
<span>参加人数: {{currentRoute.totalNum}}人</span>
|
||||||
|
<span>已评价人数: {{currentRoute.evaluatedNum}}人</span>
|
||||||
|
<span>评价完成率: {{currentRoute.evaluateRate}}%</span>
|
||||||
|
<span>平均分:
|
||||||
|
<span :class="getSatisfactionClass(currentRoute.averageScore)">
|
||||||
|
{{currentRoute.averageScore}}分
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 维度评分表格 -->
|
||||||
|
<el-table :data="dimensionsList" style="width: 100%" border>
|
||||||
|
<el-table-column prop="dimensionName" label="评价维度" width="180">
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="averageScore"
|
||||||
|
label="平均分"
|
||||||
|
width="100"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span :class="getSatisfactionClass(scope.row.averageScore)">
|
||||||
|
{{scope.row.averageScore}}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="评价分布">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-row :gutter="20" class="distribution-row">
|
||||||
|
<el-col
|
||||||
|
:span="6"
|
||||||
|
v-for="(item, index) in scope.row.distribution"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div class="distribution-item">
|
||||||
|
<div class="level-label">{{item.label}}</div>
|
||||||
|
<div class="level-count">{{item.count}}人</div>
|
||||||
|
<div class="level-rate">{{item.rate}}%</div>
|
||||||
|
<el-progress
|
||||||
|
:percentage="item.rate"
|
||||||
|
:color="getLevelColor(item.label)"
|
||||||
|
:show-text="false"
|
||||||
|
></el-progress>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="chart-container" v-if="dimensionsList.length > 0">
|
||||||
|
<div class="chart-title">各维度评分对比</div>
|
||||||
|
<div ref="dimensionsChart" class="dimensions-chart"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-container" v-if="dimensionsList.length > 0">
|
||||||
|
<div class="chart-title">满意度分布</div>
|
||||||
|
<div ref="distributionChart" class="distribution-chart"></div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
`,
|
||||||
|
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;
|
||||||
+375
@@ -0,0 +1,375 @@
|
|||||||
|
const evaluateChart = {
|
||||||
|
template: /*language=HTML*/ `
|
||||||
|
<el-dialog
|
||||||
|
title="满意度评价详情"
|
||||||
|
:visible.sync="visible"
|
||||||
|
width="80%"
|
||||||
|
top="50px"
|
||||||
|
v-loading="loading"
|
||||||
|
>
|
||||||
|
<div class="details-header" v-if="currentRoute">
|
||||||
|
<div class="details-title">
|
||||||
|
<template v-if="currentCategory==='line'">
|
||||||
|
{{currentRoute.lineName}} ({{currentRoute.playTime}})
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{currentRoute.baseName}}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="details-stats">
|
||||||
|
<span>参加人数: {{currentRoute.totalNum}}人</span>
|
||||||
|
<span>已评价人数: {{currentRoute.evaluatedNum}}人</span>
|
||||||
|
<span>评价完成率: {{currentRoute.totalNum === 0 ? 0 : Math.round(currentRoute.evaluatedNum / currentRoute.totalNum * 100)}}%</span>
|
||||||
|
<span>平均分:
|
||||||
|
<span :class="getSatisfactionClass(currentRoute.averageScore)">
|
||||||
|
{{currentRoute.averageScore || 0}}分
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 维度评分表格 -->
|
||||||
|
<el-table :data="dimensionsList" style="width: 100%" border>
|
||||||
|
<el-table-column prop="dimensionName" label="评价维度" width="180">
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="averageScore"
|
||||||
|
label="平均分"
|
||||||
|
width="100"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span :class="getSatisfactionClass(scope.row.averageScore)">
|
||||||
|
{{scope.row.averageScore}}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="评价分布">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-row :gutter="20" class="distribution-row">
|
||||||
|
<el-col
|
||||||
|
:span="6"
|
||||||
|
v-for="(item, index) in scope.row.distribution"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div class="distribution-item">
|
||||||
|
<div class="level-label">{{item.label}}</div>
|
||||||
|
<div class="level-count">{{item.count}}人</div>
|
||||||
|
<div class="level-rate">{{item.rate}}%</div>
|
||||||
|
<el-progress
|
||||||
|
:percentage="item.rate"
|
||||||
|
:color="getLevelColor(item.label)"
|
||||||
|
:show-text="false"
|
||||||
|
></el-progress>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="chart-container" v-if="dimensionsList.length > 0">
|
||||||
|
<div class="chart-title">满意度分布</div>
|
||||||
|
<div ref="distributionChart" class="distribution-chart"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户评价详情表格 -->
|
||||||
|
<div class="user-ratings-container" v-if="evaluates.length > 0">
|
||||||
|
<div class="chart-title">用户评价详情</div>
|
||||||
|
<el-table :data="evaluates" style="width: 100%" border stripe>
|
||||||
|
<el-table-column prop="userName" label="用户姓名" width="120" sortable></el-table-column>
|
||||||
|
<el-table-column prop="mobile" label="手机号" width="120" sortable></el-table-column>
|
||||||
|
<el-table-column label="住宿" width="80" align="center" sortable="custom"
|
||||||
|
:sort-method="(a, b) => a.accommodationScore - b.accommodationScore">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span :class="getSatisfactionClass(scope.row.accommodationScore)">{{scope.row.accommodationScore}}分</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="餐饮" width="80" align="center" sortable="custom"
|
||||||
|
:sort-method="(a, b) => a.cateringScore - b.cateringScore">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span :class="getSatisfactionClass(scope.row.cateringScore)">{{scope.row.cateringScore}}分</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="交通" width="80" align="center" sortable="custom"
|
||||||
|
:sort-method="(a, b) => a.travelScore - b.travelScore">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span :class="getSatisfactionClass(scope.row.travelScore)">{{scope.row.travelScore}}分</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="司机" width="80" align="center" sortable="custom"
|
||||||
|
:sort-method="(a, b) => a.carDriverScore - b.carDriverScore">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span :class="getSatisfactionClass(scope.row.carDriverScore)">{{scope.row.carDriverScore}}分</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="导游" width="80" align="center" sortable="custom"
|
||||||
|
:sort-method="(a, b) => a.guideScore - b.guideScore">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span :class="getSatisfactionClass(scope.row.guideScore)">{{scope.row.guideScore}}分</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="安全" width="80" align="center" sortable="custom"
|
||||||
|
:sort-method="(a, b) => a.safetyScore - b.safetyScore">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span :class="getSatisfactionClass(scope.row.safetyScore)">{{scope.row.safetyScore}}分</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="evaluateText" label="评价意见" min-width="200">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<div v-if="scope.row.evaluateText" class="user-comment">{{scope.row.evaluateText}}</div>
|
||||||
|
<div v-else class="no-comment">暂无评价意见</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="evaluateDate" label="评价时间" width="160" sortable></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
`,
|
||||||
|
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",
|
||||||
|
`
|
||||||
|
<style>
|
||||||
|
.details-header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.details-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.details-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.chart-container {
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.chart-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 4px solid #409EFF;
|
||||||
|
}
|
||||||
|
.dimensions-chart, .distribution-chart {
|
||||||
|
height: 400px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.distribution-row {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
.distribution-item {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
.level-label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.level-count, .level-rate {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.text-success {
|
||||||
|
color: #67C23A;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.text-warning {
|
||||||
|
color: #E6A23C;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.text-danger {
|
||||||
|
color: #F56C6C;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.user-ratings-container {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.rating-item {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.dimension-name {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.user-comment {
|
||||||
|
white-space: pre-line;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.no-comment {
|
||||||
|
color: #909399;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.pagination-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`,
|
||||||
|
);
|
||||||
+616
@@ -0,0 +1,616 @@
|
|||||||
|
<!--#
|
||||||
|
layout("/layouts/platform.html"){
|
||||||
|
#-->
|
||||||
|
|
||||||
|
<div id="app" v-cloak>
|
||||||
|
<guava ref="guava">
|
||||||
|
<!-- 页面标题和分类切换 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title">满意度评价统计</div>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="pageForm.currentCategory"
|
||||||
|
@change="handleCategoryChange"
|
||||||
|
size="medium"
|
||||||
|
>
|
||||||
|
<el-radio-button label="line">线路评价</el-radio-button>
|
||||||
|
<el-radio-button label="destination">目的地评价</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-card">
|
||||||
|
<div class="search">
|
||||||
|
<div class="search-item">
|
||||||
|
<div class="search-item-label">年度:</div>
|
||||||
|
<div class="search-item-option">
|
||||||
|
<el-date-picker
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="选择年度"
|
||||||
|
type="year"
|
||||||
|
:clearable="false"
|
||||||
|
@change="doSearch"
|
||||||
|
v-model="pageForm.year"
|
||||||
|
value-format="yyyy"
|
||||||
|
>
|
||||||
|
</el-date-picker>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 线路评价搜索项 -->
|
||||||
|
<template v-if="pageForm.currentCategory === 'line'">
|
||||||
|
<div class="search-item">
|
||||||
|
<div class="search-item-label">线路:</div>
|
||||||
|
<div class="search-item-option">
|
||||||
|
<el-select
|
||||||
|
v-model="pageForm.lineId"
|
||||||
|
clearable
|
||||||
|
@change="lineIdChange"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请选择线路"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in lineOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.lineName"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-item" v-if="pageForm.lineId">
|
||||||
|
<div class="search-item-label">出行时间:</div>
|
||||||
|
<div class="search-item-option">
|
||||||
|
<el-select
|
||||||
|
v-model="pageForm.playTime"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请选择出行时间"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in lineTimeOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 目的地评价搜索项 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="search-item">
|
||||||
|
<div class="search-item-label">目的地:</div>
|
||||||
|
<div class="search-item-option">
|
||||||
|
<el-select
|
||||||
|
v-model="pageForm.destinationId"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请选择目的地"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in destinationOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.baseName"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="search-query">
|
||||||
|
<el-button
|
||||||
|
@click="doSearch"
|
||||||
|
icon="el-icon-search"
|
||||||
|
type="primary"
|
||||||
|
size="medium"
|
||||||
|
>搜索
|
||||||
|
</el-button>
|
||||||
|
<!-- <el-button @click="resetSearch" icon="el-icon-refresh" plain size="medium"-->
|
||||||
|
<!-- >重置-->
|
||||||
|
<!-- </el-button>-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 整体统计信息卡片 -->
|
||||||
|
<div class="stats-cards" v-if="totalStat.totalNum">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card shadow="hover" class="stat-card">
|
||||||
|
<div class="stat-card-inner">
|
||||||
|
<div class="stat-icon participants">
|
||||||
|
<i class="el-icon-user"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{totalStat.totalNum}}</div>
|
||||||
|
<div class="stat-label">参加总人数</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card shadow="hover" class="stat-card">
|
||||||
|
<div class="stat-card-inner">
|
||||||
|
<div class="stat-icon evaluated">
|
||||||
|
<i class="el-icon-s-check"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{totalStat.evaluatedNum}}</div>
|
||||||
|
<div class="stat-label">已评价人数</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card shadow="hover" class="stat-card">
|
||||||
|
<div class="stat-card-inner">
|
||||||
|
<div class="stat-icon completion">
|
||||||
|
<i class="el-icon-data-line"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">
|
||||||
|
{{totalStat.totalNum ? totalStat.evaluateRate : 0}}%
|
||||||
|
</div>
|
||||||
|
<div class="stat-label">评价完成率</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card shadow="hover" class="stat-card">
|
||||||
|
<div class="stat-card-inner">
|
||||||
|
<div class="stat-icon satisfaction">
|
||||||
|
<i class="el-icon-star-on"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-value">{{totalStat.averageScore}}</div>
|
||||||
|
<div class="stat-label">总体满意度</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 数据列表 -->
|
||||||
|
<el-card class="mt20" shadow="never">
|
||||||
|
<div slot="header" class="clearfix">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title-wrapper">
|
||||||
|
<i class="el-icon-s-data"></i>
|
||||||
|
<span class="card-title"
|
||||||
|
>{{pageForm.currentCategory === 'line' ? '线路' :
|
||||||
|
'目的地'}}评价列表</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="card-header-actions">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
icon="el-icon-download"
|
||||||
|
@click="handleExport"
|
||||||
|
>导出数据</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 线路列表表格 -->
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
style="width: 100%"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
v-loading="loading"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
prop="lineName"
|
||||||
|
label="线路名称"
|
||||||
|
v-if="pageForm.currentCategory === 'line'"
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="baseName"
|
||||||
|
label="目的地名称"
|
||||||
|
v-if="pageForm.currentCategory === 'destination'"
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="playTime"
|
||||||
|
label="出行时间"
|
||||||
|
v-if="pageForm.currentCategory === 'line'"
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="totalNum"
|
||||||
|
label="参加人数"
|
||||||
|
width="100"
|
||||||
|
align="center"
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="evaluatedNum"
|
||||||
|
label="已评价人数"
|
||||||
|
width="100"
|
||||||
|
align="center"
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="evaluateRate"
|
||||||
|
label="评价完成率"
|
||||||
|
width="120"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-progress
|
||||||
|
:percentage="scope.row.totalNum ? Math.round(scope.row.evaluatedNum / scope.row.totalNum * 100) : 0"
|
||||||
|
:color="getProgressColor(scope.row.totalNum ? Math.round(scope.row.evaluatedNum / scope.row.totalNum * 100) : 0)"
|
||||||
|
></el-progress>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="averageScore"
|
||||||
|
label="平均分"
|
||||||
|
width="100"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span class="score-badge"> {{scope.row.averageScore}} </span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="120" align="center">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="mini"
|
||||||
|
@click="showDetails(scope.row)"
|
||||||
|
>查看详情
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!--#include("/layouts/pagination.html"){}#-->
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 使用评价详情组件 -->
|
||||||
|
<evaluate-chart ref="evaluateChartRef"></evaluate-chart>
|
||||||
|
</guava>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
<!--#include('evaluateChart.js'){}#-->
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
mixins: [initTableMixins],
|
||||||
|
components: {
|
||||||
|
"evaluate-chart": evaluateChart,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
pageForm: {
|
||||||
|
year: new Date().getFullYear().toString(),
|
||||||
|
currentCategory: "line",
|
||||||
|
lineId: null,
|
||||||
|
playTime: null,
|
||||||
|
destinationId: null,
|
||||||
|
},
|
||||||
|
lineOptions: [],
|
||||||
|
lineTimeOptions: [],
|
||||||
|
destinationOptions: [],
|
||||||
|
loading: false,
|
||||||
|
totalStat: {
|
||||||
|
totalNum: 0,
|
||||||
|
evaluatedNum: 0,
|
||||||
|
evaluateRate: 0,
|
||||||
|
averageScore: 0,
|
||||||
|
},
|
||||||
|
detailsVisible: false,
|
||||||
|
currentRoute: {},
|
||||||
|
dimensionsList: [],
|
||||||
|
tableData: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
listLines() {
|
||||||
|
$.post(
|
||||||
|
"/platform/theRapyRecuperation/satisfactionEvaluate/listLines",
|
||||||
|
this.pageForm,
|
||||||
|
).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.lineOptions = res.data;
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
lineIdChange(id) {
|
||||||
|
if (id) {
|
||||||
|
$.post(
|
||||||
|
"/platform/theRapyRecuperation/satisfactionEvaluate/listTimeByLineId",
|
||||||
|
this.pageForm,
|
||||||
|
).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.lineTimeOptions = res.data;
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getTotalStat() {
|
||||||
|
$.post(
|
||||||
|
"/platform/theRapyRecuperation/satisfactionEvaluate/totalStat",
|
||||||
|
this.pageForm,
|
||||||
|
).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.totalStat = res.data;
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showDetails(row) {
|
||||||
|
this.$refs.evaluateChartRef.onOpen(
|
||||||
|
row.id,
|
||||||
|
this.pageForm.currentCategory,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
handleCategoryChange() {
|
||||||
|
// 重置表单
|
||||||
|
this.pageForm.lineId = null;
|
||||||
|
this.pageForm.playTime = null;
|
||||||
|
this.pageForm.destinationId = null;
|
||||||
|
// 重新加载数据
|
||||||
|
if (this.pageForm.currentCategory === "line") {
|
||||||
|
this.listLines();
|
||||||
|
} else {
|
||||||
|
this.listDestinations();
|
||||||
|
}
|
||||||
|
this.doSearch();
|
||||||
|
},
|
||||||
|
listDestinations() {
|
||||||
|
$.post(
|
||||||
|
"/platform/theRapyRecuperation/satisfactionEvaluate/listDestinations",
|
||||||
|
this.pageForm,
|
||||||
|
).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.destinationOptions = res.data;
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetSearch() {
|
||||||
|
this.pageForm = {
|
||||||
|
year: new Date().getFullYear().toString(),
|
||||||
|
currentCategory: this.pageForm.currentCategory,
|
||||||
|
lineId: null,
|
||||||
|
playTime: null,
|
||||||
|
destinationId: null,
|
||||||
|
};
|
||||||
|
this.doSearch();
|
||||||
|
},
|
||||||
|
getProgressColor(percentage) {
|
||||||
|
if (percentage < 30) return "#F56C6C";
|
||||||
|
if (percentage < 70) return "#E6A23C";
|
||||||
|
return "#67C23A";
|
||||||
|
},
|
||||||
|
handleExport() {
|
||||||
|
// 构建查询参数
|
||||||
|
const params = {
|
||||||
|
year: this.pageForm.year,
|
||||||
|
currentCategory: this.pageForm.currentCategory,
|
||||||
|
lineId: this.pageForm.lineId,
|
||||||
|
playTime: this.pageForm.playTime,
|
||||||
|
destinationId: this.pageForm.destinationId
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用window.open直接下载文件
|
||||||
|
let queryString = '';
|
||||||
|
const keys = Object.keys(params);
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const key = keys[i];
|
||||||
|
if (params[key] != null) {
|
||||||
|
if (queryString !== '') {
|
||||||
|
queryString += '&';
|
||||||
|
}
|
||||||
|
queryString += key + '=' + encodeURIComponent(params[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.open('/platform/theRapyRecuperation/satisfactionEvaluate/export?' + queryString);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.pageData();
|
||||||
|
this.listLines();
|
||||||
|
this.getTotalStat();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-item {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-item-label {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-query {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-cards {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 100%;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon i {
|
||||||
|
font-size: 30px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon.participants {
|
||||||
|
background-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon.evaluated {
|
||||||
|
background-color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon.completion {
|
||||||
|
background-color: #e6a23c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon.satisfaction {
|
||||||
|
background-color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #303133;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title-wrapper i {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-actions .el-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-actions .el-button [class^="el-icon-"] {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt20 {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-badge {
|
||||||
|
background-color: #409eff;
|
||||||
|
color: white;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-success {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-warning {
|
||||||
|
color: #e6a23c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!--#
|
||||||
|
}
|
||||||
|
#-->
|
||||||
Reference in New Issue
Block a user