This commit is contained in:
@jyuhsin
2025-04-28 11:18:47 +08:00
parent 3626f12b29
commit 49acb7e00c
8 changed files with 3500 additions and 23 deletions
@@ -51,7 +51,7 @@ public class QsvSurveyController {
return Result.success(pagination);
}
// 报告
// 报告
@At
@RequiresPermissions("qsv.survey")
public Result report(@Valid String activityId) {
@@ -59,7 +59,16 @@ public class QsvSurveyController {
return Result.success(report);
}
// 选项选择详情
@At
@Ok("void")
@RequiresPermissions("qsv.survey")
public void exportReportXlsx(String activityId, HttpServletResponse response){
qsvSurveyService.exportReportXlsx(activityId, response);
}
// 选项选择详情
@At
@RequiresPermissions("qsv.survey")
public Result selectOptionUsers(@Valid String activityId, @Valid String subjectId, @Valid String optionId) {
@@ -69,16 +78,17 @@ public class QsvSurveyController {
}
// 用户答题
// 用户答题
@At
@RequiresPermissions("qsv.survey")
public Result userAnswer(@Valid String activityId) {
NutMap map = qsvSurveyService.userAnswer(activityId);
public Result userAnswer(PageForm pageForm, @Valid String activityId) { //@Valid String activityId
// NutMap map = qsvSurveyService.userAnswer(activityId);
NutMap map = qsvSurveyService.userAnswer(pageForm, activityId);
return Result.success(map);
}
// 删除用户答题记录
// 删除用户答题记录
@At
@RequiresPermissions("qsv.survey")
public Result deleteUserAnswer(@Valid String id) {
@@ -87,8 +97,9 @@ public class QsvSurveyController {
}
// 导出用户答题记录xlsx
// 导出用户答题记录xlsx
@At
@Ok("void")
@RequiresPermissions("qsv.survey")
public void exportUserAnswerXlsx(@Valid String activityId, HttpServletResponse response) {
qsvSurveyService.exportUserAnswerXlsx(activityId, response);
@@ -1,5 +1,6 @@
package io.v.nutz.zhgh.qsv.service;
import io.v.nutz.base.query.PageForm;
import io.v.nutz.base.service.BaseService;
import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord;
import org.nutz.lang.util.NutMap;
@@ -11,7 +12,10 @@ public interface QsvSurveyService extends BaseService<QsvUserAnswerRecord> {
List<NutMap> report(String activityId);
void exportReportXlsx(String activityId, HttpServletResponse response);
void exportUserAnswerXlsx(String activityId, HttpServletResponse response);
NutMap userAnswer(String activityId);
// NutMap userAnswer(String activityId);
NutMap userAnswer(PageForm pageForm, String activityId);
}
@@ -4,9 +4,12 @@ import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.afterturn.easypoi.excel.export.ExcelExportService;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import io.v.nutz.base.page.Pagination;
import io.v.nutz.base.query.PageForm;
import io.v.nutz.base.service.impl.BaseServiceImpl;
import io.v.nutz.base.utils.CommonDownloadUtil;
import io.v.nutz.zhgh.qsv.models.QsvActivity;
@@ -16,6 +19,7 @@ import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord;
import io.v.nutz.zhgh.qsv.service.QsvSurveyService;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
@@ -79,6 +83,10 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
}
// 处理单选类型题目 处理多选类型题目
else if ("radio".equals(subjectType) || "checkbox".equals(subjectType)) {
long selectTotal = answerExtList.stream()
.filter(ext -> ext.containsKey(subject.getString("id")))
.count();
subjectOptions.forEach(subjectOption -> {
long selectCount = answerExtList.stream()
.filter(ext ->
@@ -89,15 +97,22 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
&& jsonObject.getJSONArray("optionIds").contains(subjectOption.getString("id"));
})
.count();
long round = Math.round((double) selectCount / selectTotal * 100);
subjectOption.put("selectPercent", round + "%");
subjectOption.put("selectCount", selectCount);
});
long selectTotal = answerExtList.stream()
.filter(ext -> ext.containsKey(subject.getString("id")))
.count();
subjectOptions.sort((o1, o2) -> {
int count1 = o1.getInt("selectCount");
int count2 = o2.getInt("selectCount");
if (count1 == count2) {
return Integer.compare(o1.getInt("sortNum"), o2.getInt("sortNum"));
}
return Integer.compare(count2, count1);
});
subject.put("selectTotal", selectTotal);
}
// 添加选项到题目中
subject.addv("options", subjectOptions);
}
@@ -109,6 +124,32 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
}
}
@Override
public void exportReportXlsx(String activityId, HttpServletResponse response) {
List<NutMap> report = report(activityId);
List<ExcelExportEntity> exportEntities = new ArrayList<>();
exportEntities.add(new ExcelExportEntity("选项名称", "text", 80));
exportEntities.add(new ExcelExportEntity("选择人数", "selectCount", 20));
exportEntities.add(new ExcelExportEntity("选择比例", "selectPercent", 20));
Map<String, List<NutMap>> listMap = report.stream().collect(Collectors.groupingBy(v -> v.getString("id")));
Workbook workbook = new XSSFWorkbook();
listMap.forEach((k, v) -> {
NutMap nutMap = v.get(0);
ExcelExportService service = new ExcelExportService();
ExportParams exportParams = new ExportParams();
exportParams.setTitle(nutMap.getString("title"));
exportParams.setSheetName(nutMap.getString("title"));
exportParams.setType(ExcelType.XSSF);
service.createSheetForMap(workbook, exportParams, exportEntities, nutMap.getList("options", NutMap.class));
});
CommonDownloadUtil.download("调研分析.xlsx", workbook, response);
}
@Override
public void exportUserAnswerXlsx(String activityId, HttpServletResponse response) {
// 检查活动ID是否为空
@@ -160,16 +201,24 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
}
@Override
public NutMap userAnswer(String activityId) {
public NutMap userAnswer(PageForm pageForm, String activityId) {
// 检查活动ID是否为空
if (activityId == null || activityId.isEmpty()) {
throw new IllegalArgumentException("活动ID不能为空");
}
try {
Cnd cnd = Cnd.NEW();
cnd.and("activityId", "=", activityId);
cnd.and("isFinish", "=", true);
// Cnd.where("activityId", "=", activityId)
// .and("isFinish", "=", true)
Pagination pagination = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
List<NutMap> answerRecords = pagination.getList();
// 查询用户答题记录
List<QsvUserAnswerRecord> answerRecords = dao().query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
.and("isFinish", "=", true));
// List<QsvUserAnswerRecord> answerRecords = dao().query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
// .and("isFinish", "=", true));
// 将答题记录的扩展JSON转换为JSONObject列表
// List<JSONObject> answerExtList = answerRecords.stream().map(QsvUserAnswerRecord::getExtJson).toList();
@@ -188,7 +237,33 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
// 构建Excel导出实体
List<ExcelExportEntity> excelExportEntities = buildExcelExportEntities(subjects);
// 构建答题记录列表
List<NutMap> list = buildAnswerRecords(answerRecords, subjectMap, options);
List<NutMap> list = answerRecords.stream().map(record -> {
NutMap map = NutMap.NEW()
.addv("id", record.getString("id"))
.addv("loginName", record.getString("loginName"))
.addv("userName", record.getString("userName"))
.addv("unitName", record.getString("unitName"))
.addv("unionName", record.getString("unionName"));
JSONObject extJson = record.getAs("extJson", JSONObject.class);
extJson.forEach((k, v) -> {
JSONObject jsonVal = (JSONObject) v;
String type = subjectMap.get(k).getType();
if (type.equals("text")) {
map.addv(k, jsonVal.getStr("text"));
} else if (type.equals("radio") || type.equals("checkbox")) {
JSONArray optionIds = jsonVal.getJSONArray("optionIds");
String selectOptionTexts = options.stream().filter(option -> optionIds.contains(option.getId()))
.map(QsvOption::getText).collect(Collectors.joining(";"));
map.addv(k, selectOptionTexts);
}
});
return map;
}).toList();
pagination.setList(list);
// List<NutMap> list = buildAnswerRecords(answerRecords, subjectMap, options);
// 构建表格列信息
List<NutMap> tableColumns = excelExportEntities.stream()
@@ -197,7 +272,7 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
return NutMap.NEW()
.addv("tableColumns", tableColumns)
.addv("tableData", list);
.addv("tableData", pagination);
} catch (Exception e) {
throw new RuntimeException("获取用户答题记录失败", e);
}
@@ -252,7 +327,7 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
} else if (type.equals("radio") || type.equals("checkbox")) {
JSONArray optionIds = jsonVal.getJSONArray("optionIds");
String selectOptionTexts = options.stream().filter(option -> optionIds.contains(option.getId()))
.map(QsvOption::getText).collect(Collectors.joining(""));
.map(QsvOption::getText).collect(Collectors.joining(";"));
map.addv(k, selectOptionTexts);
}
});
@@ -1,4 +1,45 @@
const commonUtil = {
//axios配置
axiosService() {
// 创建 axios 实例
const axiosService = axios.create({
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
"x-requested-with": "XMLHttpRequest"
}
})
//axios拦截器
axiosService.interceptors.response.use(
(response) => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera
const isMobile = /iPad|iPhone|iPod|Android/.test(userAgent)
//判断是否为blob 不处理直接返回文件流
if (response.config.responseType === "blob") {
return response
}
if (response.data && response.data.code !== 0) {
if (isMobile) {
vant.Toast(response.data.msg)
} else {
ELEMENT.Message.error(response.data.msg)
}
return Promise.reject(response.data)
}
return response.data
},
(error) => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera
const isMobile = /iPad|iPhone|iPod|Android/.test(userAgent)
if (isMobile) {
vant.Toast(error.message)
} else {
ELEMENT.Message.error(error.message)
}
return Promise.reject(error)
}
)
return axiosService
},
//权限认证
authService() {
function authPermission(permission) {
@@ -65,4 +106,55 @@ const commonUtil = {
}
}
},
//下载文件
downLoadService: function (url, data) {
const loading = ELEMENT.Loading.service({
lock: true,
text: "导出中,请耐心等待",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)"
})
Vue.prototype.$axios
.post(url, data, { responseType: "blob" })
.then((response) => {
if (response.data.type === "application/json") {
try {
const reader = new FileReader()
reader.onload = function () {
// reader.result 包含了 Blob 的内容,转换为字符串
const content = reader.result
// 将字符串解析为 JSON 对象
const jsonObject = JSON.parse(content)
this.$message.error(jsonObject.msg)
}
reader.readAsText(response.data)
} catch (err) {
this.$message.error("下载文件出错")
}
return
}
//获取服务器返回的文件描述信息
const contentDisposition = response.headers["content-disposition"]
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
const matches = filenameRegex.exec(contentDisposition)
let filename = ""
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, "")
filename = decodeURIComponent(filename)
}
//执行下载文件
const url = window.URL.createObjectURL(new Blob([response.data]))
const link = document.createElement("a")
link.href = url
link.setAttribute("download", filename) // 例如 'document.pdf'
document.body.appendChild(link)
link.click()
link.parentNode.removeChild(link)
loading.close()
})
.finally(() => {
loading.close()
})
},
}
File diff suppressed because it is too large Load Diff
@@ -36,6 +36,9 @@
<script type="text/javascript" src="${base!}/assets/platform/plugins/wp-upload-vue/font/iconfont.js"></script>
<script type="text/javascript" src="${base!}/assets/platform/plugins/wp-upload-vue/js/wpupload.js"></script>
<!--axios-->
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
<!-- import JavaScript -->
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
<script src="${base!}/assets/platform/plugins/element-ui/lib/i18n/${lang,escape}.js"></script>
@@ -146,6 +149,8 @@
window.viewImage = viewImage;
Vue.prototype.$viewImage = viewImage
Vue.prototype.$auth = commonUtil.authService()
Vue.prototype.$axios = commonUtil.axiosService()
Vue.prototype.$downLoad = commonUtil.downLoadService
</script>
</head>
<body>
@@ -1,7 +1,7 @@
const answer = {
/*language=HTML*/
template: `
<el-dialog title="查看答卷" :visible.sync="dialogVisible" width="70%" top="2%">
<el-dialog title="查看答卷" :visible.sync="dialogVisible" width="80%">
<el-row type="flex" justify="end" class="mb10">
<el-button type="primary" size="small" icon="el-icon-download" @click="exportUserAnswerXlsx">导出xlsx</el-button>
</el-row>
@@ -14,8 +14,10 @@ const answer = {
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-dialog>
`,
mixins: [initTableMixins],
data() {
return {
dialogVisible: false,
@@ -28,14 +30,16 @@ const answer = {
onOpen(activityId) {
this.activityId = activityId
this.dialogVisible = true
this.list()
this.pageData()
},
list() {
$.post("/platform/qsv/survey/userAnswer", { activityId: this.activityId }).then((res) => {
pageData() {
this.pageForm.activityId = this.activityId
$.post("/platform/qsv/survey/userAnswer", this.pageForm).then((res) => {
if (res.code === 0) {
this.tableColumns = res.data.tableColumns
this.tableData = res.data.tableData
this.tableData = res.data.tableData.list
this.pageForm.totalCount = res.data.tableData.totalCount
}
})
},
@@ -2,6 +2,9 @@ const report = {
/*language=HTML*/
template: `
<el-dialog title="分析报告" :visible.sync="dialogVisible" width="70%" top="2%">
<el-row type="flex" justify="end" class="mb10">
<el-button type="primary" size="small" icon="el-icon-download" @click="exportReportXlsx">导出xlsx</el-button>
</el-row>
<el-collapse v-model="activeName" accordion>
<el-collapse-item :name="subjectIndex + 1" v-for="(subject,subjectIndex) in subjects">
<template slot="title">
@@ -116,6 +119,9 @@ const report = {
this.optionUsers = res.data
}
})
},
exportReportXlsx(){
this.$downLoad("/platform/qsv/survey/exportReportXlsx", { activityId: this.activityId })
}
},
style: /*language=CSS*/ `