|
@@ -1,228 +1,288 @@
|
|
|
-<template>
|
|
|
- <view class="page" :style="{'min-height':h+'px', 'padding-top':mt+'px'}">
|
|
|
- <cus-header title='问卷填写' :backAlert="true"></cus-header>
|
|
|
- <div class="top adffcacjc">
|
|
|
- <p>{{title}}</p>
|
|
|
- <p class="tip">共 <span>{{list.length}}</span> 题,已作答 <span style="font-weight: bold;">{{answerNum}}</span> 题,请耐心选择!</p>
|
|
|
- </div>
|
|
|
- <div class="list" :style="{'height':(h-180-mt)+'px'}">
|
|
|
- <div v-if="isLoading" class="loading-container adfacjc">
|
|
|
- <div class="adfac">
|
|
|
- <u-loading-icon size="42"></u-loading-icon>
|
|
|
- <text style="margin-left: 10rpx;font-size: 34rpx;color: #666666;">问卷加载中...</text>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <template v-else>
|
|
|
- <div class="l_item" v-for="(item,index) in list" :key="item.id">
|
|
|
- <QuestionItem :item="item" :index="index" @change="handleAnswerChange"></QuestionItem>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </div>
|
|
|
+<template>
|
|
|
+ <view class="page" :style="{ 'min-height': h + 'px', 'padding-top': mt + 'px' }">
|
|
|
+ <cus-header title="问卷填写" :backAlert="true"></cus-header>
|
|
|
+ <div class="top adffcacjc">
|
|
|
+ <p>{{ title }}</p>
|
|
|
+ <p class="tip">
|
|
|
+ 共
|
|
|
+ <span>{{ list.length }}</span>
|
|
|
+ 题,已作答
|
|
|
+ <span style="font-weight: bold">{{ answerNum }}</span>
|
|
|
+ 题,请耐心选择!
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <scroll-view class="list" scroll-y="true" :scroll-top="scrollTop" :style="{ height: h - 180 - mt + 'px' }">
|
|
|
+ <div v-if="isLoading" class="loading-container adfacjc">
|
|
|
+ <div class="adfac">
|
|
|
+ <u-loading-icon size="42"></u-loading-icon>
|
|
|
+ <text style="margin-left: 10rpx; font-size: 34rpx; color: #666666">问卷加载中...</text>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <template v-else>
|
|
|
+ <view>
|
|
|
+ <div class="l_item" v-for="(item, index) in list" :key="item.id" :id="'question-' + index">
|
|
|
+ <QuestionItem :item="item" :index="index" @change="handleAnswerChange"></QuestionItem>
|
|
|
+ </div>
|
|
|
+ </view>
|
|
|
+ </template>
|
|
|
+ </scroll-view>
|
|
|
<div class="bottom">
|
|
|
- <view class="zt_btn" @tap="submitWj">{{isSubmitting ? '提交中...' : '提交问卷'}}</view>
|
|
|
- </div>
|
|
|
- </view>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script>
|
|
|
- import QuestionItem from '@/components/QuestionItem/index.vue'
|
|
|
- export default {
|
|
|
- components:{QuestionItem},
|
|
|
- data(){
|
|
|
- return {
|
|
|
- title:'',
|
|
|
- teamQuestionnaireId:'',
|
|
|
- list:[],
|
|
|
- questionnaire:null,
|
|
|
- isLoading: true,
|
|
|
- isSubmitting: false
|
|
|
- }
|
|
|
- },
|
|
|
- onLoad(option) {
|
|
|
- this.title = option.title;
|
|
|
- this.teamQuestionnaireId = option.teamQuestionnaireId;
|
|
|
- this.getList();
|
|
|
+ <view class="zt_btn" @tap="submitWj">{{ isSubmitting ? '提交中...' : '提交问卷' }}</view>
|
|
|
+ </div>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import QuestionItem from '@/components/QuestionItem/index.vue';
|
|
|
+export default {
|
|
|
+ components: { QuestionItem },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ title: '',
|
|
|
+ teamQuestionnaireId: '',
|
|
|
+ list: [],
|
|
|
+ questionnaire: null,
|
|
|
+ isLoading: true,
|
|
|
+ isSubmitting: false,
|
|
|
+ h: 0, // 屏幕可用高度
|
|
|
+ mt: 0, // 页面顶部内边距 (用于适配自定义导航栏)
|
|
|
+ scrollTop: 0, // scroll-view 的滚动位置
|
|
|
+ oldScrollTop: 0 // 辅助值,确保即使滚动到相同位置也能触发
|
|
|
+ };
|
|
|
+ },
|
|
|
+ onLoad(option) {
|
|
|
+ const sysInfo = uni.getSystemInfoSync();
|
|
|
+ const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
|
|
|
+ this.h = sysInfo.windowHeight;
|
|
|
+ // 适配自定义导航栏的顶部安全距离 = 状态栏高度 + 胶囊高度 + 上下边距
|
|
|
+ this.mt = menuButtonInfo.top + menuButtonInfo.height + (menuButtonInfo.top - sysInfo.statusBarHeight);
|
|
|
+
|
|
|
+ this.title = option.title;
|
|
|
+ this.teamQuestionnaireId = option.teamQuestionnaireId;
|
|
|
+ this.getList();
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ answerNum() {
|
|
|
+ return this.list.filter((l) => !!l.answer).length || 0;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ handleAnswerChange(e) {
|
|
|
+ const item = this.list[e.index];
|
|
|
+ if (item) {
|
|
|
+ this.$set(item, 'answer', e.value);
|
|
|
+ if (item.warn) {
|
|
|
+ this.$set(item, 'warn', false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.setQuestionnaireCache();
|
|
|
+ },
|
|
|
+ getList() {
|
|
|
+ let questionnaire = null;
|
|
|
+ try {
|
|
|
+ const cacheStr = uni.getStorageSync('questionnaire');
|
|
|
+ if (cacheStr) {
|
|
|
+ questionnaire = JSON.parse(cacheStr);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析问卷缓存失败:', e);
|
|
|
+ uni.removeStorageSync('questionnaire');
|
|
|
+ }
|
|
|
+
|
|
|
+ this.isLoading = true;
|
|
|
+ this.$api
|
|
|
+ .get('/core/team/member/answer/listByUser/' + this.teamQuestionnaireId)
|
|
|
+ .then((res) => {
|
|
|
+ if (res.data.code !== 0) return this.$showToast(res.data.msg);
|
|
|
+ const answerMap = new Map();
|
|
|
+ if (questionnaire && this.teamQuestionnaireId == questionnaire.key) {
|
|
|
+ questionnaire.list.forEach((q) => answerMap.set(q.id, q.answer));
|
|
|
+ }
|
|
|
+
|
|
|
+ this.list = res.data.data.map((item) => ({
|
|
|
+ ...item,
|
|
|
+ warn: false,
|
|
|
+ answer: answerMap.get(item.id) || ''
|
|
|
+ }));
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ this.isLoading = false;
|
|
|
+ this.$showToast('网络异常,请稍后重试');
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ this.isLoading = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ scrollToQuestion(index) {
|
|
|
+ const query = uni.createSelectorQuery().in(this);
|
|
|
+ const targetId = `#question-${index}`;
|
|
|
+ query.select(targetId).boundingClientRect();
|
|
|
+ query.select('.list').scrollOffset(); // 获取 scroll-view 的滚动信息
|
|
|
+ query.select('.list').boundingClientRect(); // 获取 scroll-view 自身的位置信息
|
|
|
+ query.exec((res) => {
|
|
|
+ // 增加安全校验
|
|
|
+ if (!res || !res[0] || !res[1] || !res[2]) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // res[0]: 目标元素 (#question-N) 的位置信息
|
|
|
+ const itemRect = res[0];
|
|
|
+ // res[1]: 滚动容器 (.list) 的滚动信息
|
|
|
+ const scrollViewScroll = res[1];
|
|
|
+ // res[2]: 滚动容器 (.list) 自身的位置信息
|
|
|
+ const scrollViewRect = res[2];
|
|
|
+
|
|
|
+ // 计算目标滚动位置 = 当前滚动距离 + (目标元素顶部 - 滚动容器顶部) - 预留间距
|
|
|
+ const targetPosition = scrollViewScroll.scrollTop + itemRect.top - scrollViewRect.top - 10; // 减10px作为顶部留白
|
|
|
+
|
|
|
+ // 通过先设置为旧值,再在 nextTick 中设置为新值的方式,确保滚动能够触发
|
|
|
+ this.scrollTop = this.oldScrollTop;
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.scrollTop = targetPosition;
|
|
|
+ this.oldScrollTop = targetPosition;
|
|
|
+ });
|
|
|
+ });
|
|
|
},
|
|
|
- computed: {
|
|
|
- answerNum() {
|
|
|
- return this.list.filter(l => !!l.answer).length || 0;
|
|
|
+ submitWj() {
|
|
|
+ if (this.isSubmitting) return;
|
|
|
+
|
|
|
+ let firstUnansweredIndex = -1;
|
|
|
+ this.list.forEach((l, i) => {
|
|
|
+ const isAnswered = !!l.answer;
|
|
|
+ this.$set(l, 'warn', !isAnswered);
|
|
|
+ if (!isAnswered && firstUnansweredIndex === -1) {
|
|
|
+ firstUnansweredIndex = i;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (firstUnansweredIndex > -1) {
|
|
|
+ uni.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: `第 ${firstUnansweredIndex + 1} 项未选择答案,请选择。`,
|
|
|
+ showCancel: false,
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ // 调用新的滚动方法
|
|
|
+ this.scrollToQuestion(firstUnansweredIndex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return; // 终止提交
|
|
|
}
|
|
|
- },
|
|
|
- methods:{
|
|
|
- handleAnswerChange(e) {
|
|
|
- const item = this.list[e.index];
|
|
|
- if (item) {
|
|
|
- this.$set(item, 'answer', e.value);
|
|
|
- if (item.warn) {
|
|
|
- this.$set(item, 'warn', false);
|
|
|
+
|
|
|
+ const submitList = this.list.map((question) => {
|
|
|
+ const formattedUserAnswer = question.userAnswer.map((option) => ({
|
|
|
+ ...option,
|
|
|
+ isSelected: option.questionOption === question.answer
|
|
|
+ }));
|
|
|
+ const { answer, warn, ...restOfQuestion } = question;
|
|
|
+ return {
|
|
|
+ ...restOfQuestion,
|
|
|
+ isAnswer: '1',
|
|
|
+ userAnswer: formattedUserAnswer
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ this.isSubmitting = true;
|
|
|
+ this.$api
|
|
|
+ .post('/core/team/member/answer/submit', submitList)
|
|
|
+ .then((res) => {
|
|
|
+ if (res.data.code !== 0) {
|
|
|
+ this.isSubmitting = false;
|
|
|
+ return this.$showToast(res.data.msg);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- this.setQuestionnaireCache();
|
|
|
- },
|
|
|
- getList(){
|
|
|
- let questionnaire = null;
|
|
|
- try {
|
|
|
- const cacheStr = uni.getStorageSync('questionnaire');
|
|
|
- if (cacheStr) {
|
|
|
- questionnaire = JSON.parse(cacheStr);
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error("解析问卷缓存失败:", e);
|
|
|
- uni.removeStorageSync('questionnaire'); // 缓存有问题,直接清除
|
|
|
- }
|
|
|
-
|
|
|
- this.isLoading = true;
|
|
|
- this.$api.get('/core/team/member/answer/listByUser/' + this.teamQuestionnaireId).then(res => {
|
|
|
- if (res.data.code !== 0) return this.$showToast(res.data.msg);
|
|
|
- const answerMap = new Map();
|
|
|
- if (questionnaire && this.teamQuestionnaireId == questionnaire.key) {
|
|
|
- questionnaire.list.forEach(q => answerMap.set(q.id, q.answer));
|
|
|
- }
|
|
|
-
|
|
|
- this.list = res.data.data.map(item => ({
|
|
|
- ...item,
|
|
|
- warn: false,
|
|
|
- answer: answerMap.get(item.id) || '' // 直接从 Map 中获取答案
|
|
|
- }));
|
|
|
- }).catch(()=>{
|
|
|
- this.isLoading = false;
|
|
|
- this.$showToast('网络异常,请稍后重试');
|
|
|
- }).finally(() => {
|
|
|
- this.isLoading = false;
|
|
|
- });
|
|
|
- },
|
|
|
- submitWj() {
|
|
|
- if (this.isSubmitting) return;
|
|
|
-
|
|
|
- let firstUnansweredIndex = -1;
|
|
|
- this.list.forEach((l, i) => {
|
|
|
- const isAnswered = !!l.answer;
|
|
|
- this.$set(l, 'warn', !isAnswered);
|
|
|
- if (!isAnswered && firstUnansweredIndex === -1) {
|
|
|
- firstUnansweredIndex = i;
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- if (firstUnansweredIndex > -1) {
|
|
|
- return uni.showModal({
|
|
|
- title: '提示',
|
|
|
- content: `第 ${firstUnansweredIndex + 1} 项未选择答案,请选择。`,
|
|
|
- showCancel: false
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- const submitList = this.list.map(question => {
|
|
|
- const formattedUserAnswer = question.userAnswer.map(option => ({
|
|
|
- ...option,
|
|
|
- isSelected: option.questionOption === question.answer
|
|
|
- }));
|
|
|
-
|
|
|
- const { answer, warn, ...restOfQuestion } = question;
|
|
|
-
|
|
|
- return {
|
|
|
- ...restOfQuestion,
|
|
|
- isAnswer: '1',
|
|
|
- userAnswer: formattedUserAnswer
|
|
|
- };
|
|
|
- });
|
|
|
-
|
|
|
- this.isSubmitting = true;
|
|
|
- this.$api.post('/core/team/member/answer/submit', submitList).then(res => {
|
|
|
- if (res.data.code !== 0) {
|
|
|
- this.isSubmitting = false;
|
|
|
- return this.$showToast(res.data.msg);
|
|
|
- }
|
|
|
- uni.removeStorageSync('questionnaire');
|
|
|
- uni.redirectTo({
|
|
|
- url: '/pages/questionnaireResult'
|
|
|
- });
|
|
|
- }).catch(err => {
|
|
|
- this.$showToast('网络异常,请稍后重试');
|
|
|
- })
|
|
|
- .finally(() => {
|
|
|
- this.isSubmitting = false;
|
|
|
- });
|
|
|
- },
|
|
|
- setQuestionnaireCache(){
|
|
|
- const answeredList = this.list
|
|
|
- .filter(l => l.answer)
|
|
|
- .map(l => ({ id: l.id, answer: l.answer }));
|
|
|
-
|
|
|
- if (answeredList.length === 0) {
|
|
|
- uni.removeStorageSync('questionnaire');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const qinfo = {
|
|
|
- key: this.teamQuestionnaireId,
|
|
|
- list: answeredList
|
|
|
- };
|
|
|
- uni.setStorageSync('questionnaire', JSON.stringify(qinfo));
|
|
|
- }
|
|
|
- },
|
|
|
- // onUnload() {
|
|
|
- // this.setQuestionnaireCache();
|
|
|
- // },
|
|
|
- // onBackPress() {
|
|
|
- // this.setQuestionnaireCache();
|
|
|
- // return false;
|
|
|
- // }
|
|
|
- }
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped lang="less">
|
|
|
- .loading-container {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- }
|
|
|
- .page{
|
|
|
- background: #F7F2F6;
|
|
|
- box-sizing: border-box;
|
|
|
-
|
|
|
- .top{
|
|
|
- p{
|
|
|
- font-family: PingFang-SC, PingFang-SC;
|
|
|
- font-weight: bold;
|
|
|
- font-size: 42rpx;
|
|
|
- color: #252525;
|
|
|
- line-height: 51rpx;
|
|
|
- text-align: center;
|
|
|
- margin-top: 48rpx;
|
|
|
- }
|
|
|
- .tip{
|
|
|
- font-family: PingFangSC, PingFang SC;
|
|
|
- font-weight: 400;
|
|
|
- font-size: 26rpx;
|
|
|
- color: #646464;
|
|
|
- line-height: 26rpx;
|
|
|
- text-align: center;
|
|
|
- margin-top: 36rpx;
|
|
|
- span{
|
|
|
- margin: 0 10rpx;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .list{
|
|
|
- width: 100%;
|
|
|
- overflow-y: auto;
|
|
|
- margin-top: 28rpx;
|
|
|
- .l_item{
|
|
|
- margin-top: 20rpx;
|
|
|
- width: 100%;
|
|
|
- background: #FFFFFF;
|
|
|
- padding: 6rpx;
|
|
|
- box-sizing: border-box;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .bottom{
|
|
|
- width: calc(100% - 80rpx);
|
|
|
- height: 88rpx;
|
|
|
- position: fixed;
|
|
|
- left: 40rpx;
|
|
|
- bottom: 40rpx;
|
|
|
- }
|
|
|
- }
|
|
|
-</style>
|
|
|
+ uni.removeStorageSync('questionnaire');
|
|
|
+ uni.redirectTo({
|
|
|
+ url: '/pages/questionnaireResult'
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ this.$showToast('网络异常,请稍后重试');
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ this.isSubmitting = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ setQuestionnaireCache() {
|
|
|
+ const answeredList = this.list.filter((l) => l.answer).map((l) => ({ id: l.id, answer: l.answer }));
|
|
|
+
|
|
|
+ if (answeredList.length === 0) {
|
|
|
+ uni.removeStorageSync('questionnaire');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const qinfo = {
|
|
|
+ key: this.teamQuestionnaireId,
|
|
|
+ list: answeredList
|
|
|
+ };
|
|
|
+ uni.setStorageSync('questionnaire', JSON.stringify(qinfo));
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="less">
|
|
|
+.loading-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+.page {
|
|
|
+ background: #f7f2f6;
|
|
|
+ box-sizing: border-box;
|
|
|
+ /* 【重要】让页面本身不可滚动,所有滚动都交给scroll-view处理 */
|
|
|
+ height: 100vh;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .top {
|
|
|
+ p {
|
|
|
+ font-family: PingFang-SC, PingFang-SC;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 42rpx;
|
|
|
+ color: #252525;
|
|
|
+ line-height: 51rpx;
|
|
|
+ text-align: center;
|
|
|
+ margin-top: 48rpx;
|
|
|
+ }
|
|
|
+ .tip {
|
|
|
+ font-family: PingFangSC, PingFang SC;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #646464;
|
|
|
+ line-height: 26rpx;
|
|
|
+ text-align: center;
|
|
|
+ margin-top: 36rpx;
|
|
|
+ span {
|
|
|
+ margin: 0 10rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .list {
|
|
|
+ width: 100%;
|
|
|
+ /* flex: 1; 如果使用flex布局,可以这样自适应高度 */
|
|
|
+ /* overflow-y: auto; scroll-view自带滚动,无需此属性 */
|
|
|
+ margin-top: 28rpx;
|
|
|
+ .l_item {
|
|
|
+ margin-top: 20rpx;
|
|
|
+ width: 100%;
|
|
|
+ background: #ffffff;
|
|
|
+ padding: 6rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ &:first-child {
|
|
|
+ margin-top: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .bottom {
|
|
|
+ width: calc(100% - 80rpx);
|
|
|
+ height: 88rpx;
|
|
|
+ position: fixed;
|
|
|
+ left: 40rpx;
|
|
|
+ bottom: 40rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|