This commit is contained in:
那些花儿
2025-10-22 11:36:11 +08:00
parent 9427e8057e
commit d0f80f57b5
10 changed files with 264 additions and 40 deletions
@@ -7,12 +7,14 @@ import cn.hutool.json.JSONObject;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.qsv.models.QsvActivity;
import com.budwk.app.zhgh.activity.qsv.models.QsvOption;
import com.budwk.app.zhgh.activity.qsv.models.QsvSubject;
import com.budwk.app.zhgh.activity.qsv.models.QsvUserAnswerRecord;
import com.budwk.app.zhgh.activity.qsv.param.QsvAnswerParam;
import com.budwk.app.zhgh.activity.qsv.service.QsvUserAnswerRecordService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
@@ -24,6 +26,7 @@ import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@IocBean
@At("/platform/h5/qsv/survey")
@@ -50,7 +53,7 @@ public class H5QsvSurveyController {
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, Cnd.where(QsvUserAnswerRecord::getActivityId, "=", activityId).and(QsvUserAnswerRecord::getUserId, "=", SecurityUtil.getUserId())
.and(QsvUserAnswerRecord::getUserId, "=", SecurityUtil.getUserId()));
if (answerRecord == null) {
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where(QsvSubject::getActivityId, "=", activityId));
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where(QsvSubject::getActivityId, "=", activityId).asc(QsvSubject::getSortNum));
qsvUserAnswerRecordService.insertRecord(activityId, subjects.stream().map(QsvSubject::getId).toList());
}
@@ -60,8 +63,12 @@ public class H5QsvSurveyController {
answerRecordId = answerRecord2.getId();
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where(QsvSubject::getId, "in", answerRecord2.getSubjectIds()));
dao.fetchLinks(subjects, "options");
Cnd subjectCnd = Cnd.where(QsvSubject::getId, "in", answerRecord2.getSubjectIds());
String dynamic = answerRecord2.getSubjectIds().stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
subjectCnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
List<QsvSubject> subjects = dao.query(QsvSubject.class, subjectCnd);
dao.fetchLinks(subjects, "options",Cnd.NEW().asc(QsvOption::getSortNum));
for (QsvSubject subject : subjects) {
if (subject.getUserSelectOptionIds() == null) {
@@ -91,6 +98,7 @@ public class H5QsvSurveyController {
if (ObjectUtil.isNotEmpty(entries)) {
entries.set("optionIds", subject.getUserSelectOptionIds());
entries.set("text", subject.getUserFillContent());
entries.set("optionInputTexts",subject.getUserInputOptionTexts());
}
extJson.set(subject.getId(), entries);
}
@@ -39,6 +39,22 @@ public class QsvOption extends BaseModel {
@ColDefine(type = ColType.VARCHAR, width = 255)
private String imgUrl;
@Column
@Comment("链接地址")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String link;
@Column
@Comment("描述")
@ColDefine(type = ColType.TEXT)
private String description;
@Column
@Comment("是否允许填写内容")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean enableInput;
@Column
@Comment("排序")
@ColDefine(type = ColType.INT)
@@ -57,6 +57,17 @@ public class QsvSubject extends BaseModel {
@ColDefine(type = ColType.INT, width = 1)
private Integer sortNum;
@Column
@Comment("最小选择数")
@ColDefine(type = ColType.INT, width = 1)
private Integer minSelectNum;
@Column
@Comment("最大选择数")
@ColDefine(type = ColType.INT, width = 1)
private Integer maxSelectNum;
/**
* 选项
*/
@@ -3,6 +3,7 @@ package com.budwk.app.zhgh.activity.qsv.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.nutz.lang.util.NutMap;
import java.util.List;
@@ -26,7 +27,16 @@ public class QsvAnswerParam {
public static class Subject{
private String id;
private List<String> userSelectOptionIds;
private List<InputOption> userInputOptionTexts;
private String userFillContent;
}
@Data
public static class InputOption{
private String optionId;
private String inputText;
}
}
@@ -14,6 +14,7 @@ import com.budwk.app.zhgh.activity.qsv.models.QsvActivity;
import com.budwk.app.zhgh.activity.qsv.models.QsvOption;
import com.budwk.app.zhgh.activity.qsv.models.QsvSubject;
import com.budwk.app.zhgh.activity.qsv.models.QsvUserAnswerRecord;
import com.budwk.app.zhgh.activity.qsv.param.QsvAnswerParam;
import com.budwk.app.zhgh.activity.qsv.service.QsvSurveyService;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
@@ -29,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@@ -249,9 +251,24 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
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);
JSONArray inputTexts = jsonVal.getJSONArray("optionInputTexts");
List<QsvAnswerParam.InputOption> optionInputTexts = jsonVal.getBeanList("optionInputTexts", QsvAnswerParam.InputOption.class);
// String selectOptionTexts = options.stream().filter(option -> optionIds.contains(option.getId()))
// .map(QsvOption::getText).collect(Collectors.joining(";"));
String fullText = options.stream().filter(option -> optionIds.contains(option.getId())).map(option -> {
String text = option.getText();
if (option.getEnableInput() && ObjectUtil.isNotEmpty(inputTexts)) {
// inputTexts.stream().filter(inputText -> inputText.getOptionId().equals(option.getId()))
Optional<QsvAnswerParam.InputOption> optionOptional = optionInputTexts.stream().filter(inputText -> inputText.getOptionId().equals(option.getId())).findFirst();
if (optionOptional.isPresent()) {
text += "(" + optionOptional.get().getInputText() + ")";
}
}
return text;
}).collect(Collectors.joining(";"));
map.addv(k, fullText);
}
});
return map;
@@ -0,0 +1,40 @@
const setting = {
template: /*language=HTML*/ `
<el-dialog :title="title" :visible.sync="visible" append-to-body width="50%">
<el-form :model="formData" label-width="80px">
<el-form-item label="链接" prop="link">
<el-input maxlength="255" placeholder="" v-model="formData.link"></el-input>
</el-form-item>
<el-form-item label="详情" prop="description">
<text-editor v-model="formData.description"></text-editor>
</el-form-item>
<el-form-item label="意见填写" prop="enableInput">
<el-switch v-model="formData.enableInput"></el-switch>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="visible = false">取 消</el-button>
<el-button type="primary" @click="onConfirm">确 定</el-button>
</div>
</el-dialog>
`,
data() {
return {
title: "设置",
visible: false,
formData: {},
ext: null
}
},
methods: {
onOpen(option, ext) {
this.formData = { ...option }
this.ext = ext
this.visible = true
},
onConfirm() {
this.visible = false
this.$emit("confirm", { option: this.formData, ext: this.ext })
}
}
}
@@ -1,4 +1,5 @@
<!--#include('optionImg.js'){}#-->
<!--#include('setting.js'){}#-->
const subjectForm = {
template: /*language=HTML*/ `
@@ -42,6 +43,27 @@ const subjectForm = {
</el-select>
</div>
<!--最小选择数-->
<div v-if="subject.type === 'checkbox'">
最小选择数:
<el-input-number
v-model="subject.minSelectNum"
:min="1"
placeholder="最小选择数">
</el-input-number>
</div>
<!--最大选择数-->
<div v-if="subject.type === 'checkbox'">
最大选择数:
<el-input-number
v-model="subject.maxSelectNum"
:min="1"
:max="subject.options.length"
placeholder="最大选择数">
</el-input-number>
</div>
<!-- <el-input-->
<!-- v-model="subject.hint"-->
@@ -115,6 +137,14 @@ const subjectForm = {
v-model="option.isCorrect"
active-text="正确答案">
</el-switch>
<el-button
type="primary"
icon="el-icon-setting"
size="mini"
@click="openSetting(subjectIndex,optionIndex,option)">
</el-button>
<el-button
type="danger"
icon="el-icon-delete"
@@ -189,10 +219,12 @@ const subjectForm = {
</div>
<option-img ref="optionImgRef" @confirm="onOptionImgConfirm"></option-img>
<setting ref="settingRef" @confirm="onSettingConfirm"></setting>
</div>
`,
components: {
'option-img': optionImg,
'setting': setting
},
data() {
return {
@@ -277,7 +309,7 @@ const subjectForm = {
.catch(() => {
})
},
//添加选项
addOption(subject) {
subject.options.push({
@@ -288,7 +320,7 @@ const subjectForm = {
isCorrect: false
})
},
//删除选项
removeOption(subject, optionIndex) {
subject.options.splice(optionIndex, 1)
@@ -301,18 +333,31 @@ const subjectForm = {
optionIndex
})
},
//选项图片上传成功回调
onOptionImgConfirm({img, ext}) {
this.$set(this.subjects[ext.subjectIndex].options[ext.optionIndex], "imgUrl", img)
},
//删除选项图片
removeOptionImg(subjectIndex, optionIndex){
this.$set(this.subjects[subjectIndex].options[optionIndex], "imgUrl", null)
console.log(this.subjects[subjectIndex])
},
//打开选项设置
openSetting(subjectIndex, optionIndex, option){
this.$refs.settingRef.onOpen(option, {
subjectIndex,
optionIndex
})
},
//选项设置确认
onSettingConfirm({option, ext}){
this.subjects[ext.subjectIndex].options[ext.optionIndex] = option
},
//保存
saveQuestionnaire() {
this.$axios
@@ -1,7 +1,7 @@
const answer = {
/*language=HTML*/
template: `
<el-dialog title="查看答卷" :visible.sync="dialogVisible" width="50%">
<el-dialog title="查看答卷" :visible.sync="dialogVisible" width="70%">
<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>
@@ -62,7 +62,8 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
:key="item.code"
:type="item.name"
@click="tagClick('sexTypes',item.name)"
style="margin-right: 10px;cursor: pointer"
size="small"
style="cursor: pointer;margin: 0;"
v-for="item in sexTypeOptions">
{{ item.name }}
</el-tag>
@@ -73,7 +74,8 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
<el-col class="query-row-title">人员类型:</el-col>
<el-col class="query-row-content query-row-content-tag">
<el-tag
style="margin-right: 10px;cursor: pointer"
size="small"
style="cursor: pointer;margin: 0;"
v-for="item in personTypeOptions"
:key="item.code"
:type="item.name"
@@ -98,7 +100,8 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
<el-col class="query-row-title">人事编制:</el-col>
<el-col class="query-row-content query-row-content-tag">
<el-tag
style="margin-right: 10px;cursor: pointer"
size="small"
style="cursor: pointer;margin: 0;"
v-for="item in preparedByOptions"
:key="item.code"
:type="item.name"
@@ -123,7 +126,8 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
<el-col class="query-row-title">在职状态:</el-col>
<el-col class="query-row-content query-row-content-tag">
<el-tag
style="margin-right: 10px;cursor: pointer"
size="small"
style="cursor: pointer;margin: 0;"
v-for="item in userStateOptions"
:key="item.code"
:type="item.name"
@@ -415,7 +419,7 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
// 出生年月日期范围
birthdayRange: [],
joinMemberRange: [],
leaveMemberRange: [],
leaveMemberRange: []
},
personTypeOptions: [],
preparedByOptions: [],
@@ -441,9 +445,9 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
return {
disabledDate(time) {
// 只能选择与 `year` 相同的年份
return time.getFullYear() !== parseInt(this.pageForm.year);
return time.getFullYear() !== parseInt(this.pageForm.year)
}
};
}
}
},
methods: {
@@ -574,7 +578,7 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
this.pageData()
},
doSearch() {
this.$emit('search', this.pageForm)
this.$emit("search", this.pageForm)
}
},
created() {
@@ -80,6 +80,21 @@ layout("/layouts/platform_h5.html"){
resize: none;
width: 100%;
}
.ui-input-box textarea {
background-color: #fff;
border: none !important;
padding: 10px;
font-size: 14px;
line-height: 18px;
display: inline-block;
margin: 0;
-webkit-appearance: none;
resize: vertical;
width: 100%;
min-height: 40px;
font-family: inherit;
}
</style>
<div id="app" v-cloak>
@@ -91,6 +106,16 @@ layout("/layouts/platform_h5.html"){
<p>{{index+1}}、{{ subject.title }}</p>
<div class="tag">
<van-tag v-if="subject.type === 'checkbox'" type="primary" size="large">多选</van-tag>
<van-tag v-if="subject.type === 'checkbox' && subject.maxSelectNum && subject.minSelectNum && subject.maxSelectNum === subject.minSelectNum" type="success" size="large">
只能选择{{subject.maxSelectNum}}项
</van-tag>
<van-tag v-if="subject.type === 'checkbox' && subject.maxSelectNum && (!subject.minSelectNum || subject.maxSelectNum !== subject.minSelectNum)" type="primary" size="large">
最多可选{{subject.maxSelectNum}}
</van-tag>
<van-tag v-if="subject.type === 'checkbox' && subject.minSelectNum && subject.minSelectNum > 1 && (!subject.maxSelectNum || subject.maxSelectNum !== subject.minSelectNum)" type="warning" size="large">
最少选择{{subject.minSelectNum}}
</van-tag>
<van-tag v-if="subject.type === 'radio'" type="primary" size="large">单选</van-tag>
<van-tag v-if="subject.score" type="primary" size="large">{{subject.score}}分</van-tag>
</div>
@@ -100,33 +125,45 @@ layout("/layouts/platform_h5.html"){
v-model="subject.userSelectOptionIds"
:ref="'subject'+subject.id"
v-if="['radio','checkbox'].includes(subject.type)"
:max="subject.maxSelectNum ? subject.maxSelectNum : subject.options.length"
>
<van-cell-group>
<van-cell
v-for="(option,index) in subject.options"
clickable
:key="option.id"
:title="option.text"
@click="cellToggle(subject,option.id)"
>
<template #icon>
<van-checkbox :name="option.id" :ref="'option'+option.id" style="margin-right: 10px"></van-checkbox>
</template>
<img
slot="right-icon"
v-if="option.imgUrl"
:src="option.imgUrl"
alt=""
style="width: 40px; height: 40px"
@click.stop="previewOptionImg(option.imgUrl)"
/>
</van-cell>
<div v-for="(option,index) in subject.options" :key="option.id">
<van-cell clickable :title="option.text" @click="cellToggle(subject,option.id)">
<template #icon>
<van-checkbox
:name="option.id"
:ref="'option'+option.id"
style="margin-right: 10px"
:disabled="answerRecord.isFinish"
></van-checkbox>
</template>
<img
slot="right-icon"
v-if="option.imgUrl"
:src="option.imgUrl"
alt=""
style="width: 40px; height: 40px"
@click.stop="previewOptionImg(option.imgUrl)"
/>
</van-cell>
<!--意见输入独占一行-->
<div v-if="option.enableInput && subject.userSelectOptionIds.includes(option.id)" class="ui-input-box">
<textarea
v-model="option.inputText"
placeholder="请输入"
:readonly="answerRecord.isFinish"
rows="2"
@input="autoResize($event)"
></textarea>
</div>
</div>
</van-cell-group>
</van-checkbox-group>
<!--填空题-->
<div class="ui-input-box" v-if="subject.type==='text'">
<input type="text" v-model="subject.userFillContent" :readonly="answerRecord.isFinish" />
<textarea v-model="subject.userFillContent" :readonly="answerRecord.isFinish" rows="2" @input="autoResize($event)"></textarea>
</div>
</div>
<div class="button-control" v-if="!answerRecord.isFinish">
@@ -162,6 +199,13 @@ layout("/layouts/platform_h5.html"){
},
methods: {
//自动调整textarea高度
autoResize(event) {
const textarea = event.target
textarea.style.height = "auto"
textarea.style.height = textarea.scrollHeight + "px"
},
//获取活动、题目
listSubjects() {
this.$axios.post("/platform/h5/qsv/survey/subjects", { activityId: this.id }).then((res) => {
@@ -197,6 +241,12 @@ layout("/layouts/platform_h5.html"){
answer?.optionIds.forEach((optionId) => {
this.$nextTick(() => {
this.$refs["option" + optionId][0].toggle()
if (answer.optionInputTexts && answer.optionInputTexts.length > 0) {
const optionInputText = answer.optionInputTexts.find((optionInputText) => optionInputText.optionId === optionId)
if (optionInputText) {
subject.options.find((option) => option.id === optionId).inputText = optionInputText.inputText
}
}
})
})
} else if (subject.type === "text") {
@@ -238,6 +288,14 @@ layout("/layouts/platform_h5.html"){
this.$toast.fail("第" + (i + 1) + "题未做答")
return
}
// 检查多选题的最小选择数
if (subject.type === "checkbox" && subject.minSelectNum) {
if (subject.userSelectOptionIds.length < subject.minSelectNum) {
this.$toast.fail("第" + (i + 1) + "题至少需要选择" + subject.minSelectNum + "个选项")
return
}
}
} else if ("text" === subject.type) {
if (!subject.userFillContent) {
this.$toast.fail("第" + (i + 1) + "题未作答")
@@ -256,9 +314,24 @@ layout("/layouts/platform_h5.html"){
activityId: this.id,
answerRecordId: this.answerRecordId,
subjects: this.subjects.map((subject) => {
// 选项输入
const userInputOptionTexts = []
if (["checkbox", "radio"].includes(subject.type)) {
subject.userSelectOptionIds.forEach((optionId) => {
const option = subject.options.find((option) => option.id === optionId)
if (option.enableInput) {
userInputOptionTexts.push({
optionId: optionId,
inputText: option.inputText
})
}
})
}
return {
id: subject.id,
userSelectOptionIds: ["checkbox", "radio"].includes(subject.type) ? subject.userSelectOptionIds : [],
userInputOptionTexts: userInputOptionTexts,
userFillContent: subject.type === "text" ? subject.userFillContent : null
}
})