init
This commit is contained in:
2025-06-27 17:42:38 +08:00
commit bd6402478b
5317 changed files with 785994 additions and 0 deletions
@@ -0,0 +1,356 @@
<template>
<div class="detail-container">
<div class="detail-con">
<div class="activity-con">
<div :class="useStatus(details.state).class" class="status">{{ useStatus(details.state).name }}</div>
<div class="title">
<span class="title-status" v-if="details.userState === 1">已参加</span>
<span class="title-con" :style="details.userState === 1 ? 'width: 70%' : 'width: 89%'">{{ details.examPlanName }}</span>
</div>
<div class="participate">
<span>{{ details.questionCategory }}</span>
<span v-if="details.userState === 1">已参与{{ details.actualJoinNumber }}</span>
</div>
<div class="question-number"
>{{ details?.examPaperRule?.questionSum === null ? 0 : details?.examPaperRule?.questionSum }}道题 答题时长为{{
details?.examPaperRule?.answerTime
}}分钟</div
>
<div class="answerInfo">
<div class="answer-item">
<span>起止时间</span>
<span>{{ timeSplit(details.startTime) }} ~ {{ timeSplit(details.endTime) }}</span>
</div>
<div class="answer-item">
<span>组织部门</span>
<span>{{ details.organizerDeptName }}</span>
</div>
<div class="answer-item head">
<span>负责人</span>
<span>{{ details.managerName }}</span>
<!-- <a href="tel:18719873644">打电话</a>-->
<span :class="['contact', details.state !== '2' ? 'defaultImg' : 'no-contact']" @click="handlePhone"></span>
</div>
</div>
</div>
<div class="level-con">
<div class="title">奖励等级标准</div>
<div class="encourage" v-html="details.awardLevelStandard"></div>
</div>
<ranke-lists :listsInfo="listsInfo" :detailID="detailID" :allShow="true" />
</div>
<!-- 活动状态 state (状态0-未开始,1进行中,2-已结束)-->
<div class="application-btn" v-if="['0', '1', '2'].includes(details.state)">
<div :class="['btn', appBtnClass()]" @click="handleApplication">报名参加</div>
</div>
<dialog-overlay
:show="dialogCon.show"
:imgUrl="dialogCon.url"
:okText="dialogCon.okText"
@cancel="handleCancel"
:cancelBtn="dialogCon.cancelBtn"
@ok="handleOk"
>
<template #content>
<div class="overlay-con">{{dialogCon.con}}</div>
</template>
</dialog-overlay>
<van-action-sheet v-model:show="actionSheet" :actions="actions" @select="sheetChange" cancel-text="取消" close-on-click-action />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useStatus, timeSplit } from '/@/views/23-physical-questions/questionHooks';
import rankeLists from '/@/views/23-physical-questions/components/rankeLists.vue';
import dialogOverlay from '/@/views/23-physical-questions/components/dialogOverlay.vue';
import tipsIcon from '/@/assets/images/questions/tips.png';
import { useRoute } from 'vue-router';
import { activieDetailApi, rankeListApi } from '/@/views/23-physical-questions/questionApI';
import { newPageParams, openPage } from '/@/hooks/openPage';
const actionSheet = ref(false);
const actions = ref([]);
const dialogCon = ref({
show: false,
url: tipsIcon,
con: '',
type: 1, // 1答题 2提示
cancelBtn: true,
okText: '开始答题',
});
const route = useRoute();
const details = ref<any>({});
const detailID = ref('');
const listsInfo = ref({});
onMounted(() => {
getList();
});
function getList() {
activieDetailApi({
id: route.query.id,
}).then((res: any) => {
if (res.code === 200) {
let awardLevel = res.result.awardLevelStandard;
res.result.awardLevelStandard = awardLevel !== null ? res.result?.awardLevelStandard.replace(/\n/g, '<br/>') : '';
details.value = res.result;
detailID.value = res.result.id;
getRankeList(res.result.id);
}
});
}
// 排名列表
function getRankeList(id: string) {
rankeListApi({
examPlanId: id,
}).then((res: any) => {
if (res.code === 200) {
listsInfo.value = res.result;
}
});
}
/**
* 报名参加
* @parmas 活动状态 state 0 未开始、state 1 进行中、 state 2 已结束
* @parmas 参加状态 userState 1 已参加 、userState 0 未参加
*/
function handleApplication() {
if (details.value.state === '1' && details.value.userState === 0) {
let val = details.value;
dialogCon.value.show = true;
dialogCon.value.con = `${val?.examPaperRule?.questionSum === null ? 0 : val?.examPaperRule?.questionSum}道题,答题时长为${
val?.examPaperRule?.answerTime
}分钟,是否确认开始答题?`;
}
}
function handleCancel() {
dialogCon.value.show = false;
}
function handleOk() {
if (dialogCon.value.type === 1) {
openPage(
'/physical-answer',
newPageParams({
id: details.value.id,
answerTime: details.value?.examPaperRule?.answerTime,
affirmBack: 1,
active: true,
})
);
}
dialogCon.value.show = false;
}
/**
* @parmas 活动状态 state 0 未开始 、活动状态 state 1 进行中、 state 2 已结束
* @parmas 参加状态 userState 1 已参加 、userState 0 未参加
*/
function appBtnClass() {
let text = '';
if (details.value.state === '1') {
if (details.value.userState === 0) {
text = 'active-btn';
} else {
text = 'no-active-btn';
}
} else {
text = 'no-active-btn';
}
return text;
}
// 打电话
function handlePhone() {
if (details.value.state !== '2') {
actionSheet.value = true;
actions.value = [{ name: details.value.managerPhone }];
}
}
function sheetChange() {
// const el = document.createElement('a');
// el.href = `tel://${details.value.managerPhone}`;
// document.body.appendChild(el);
// el.click();
// document.body.removeChild(el);
}
</script>
<style scoped lang="less">
@import '/@/assets/less/question.less';
.detail-container {
width: 100%;
height: 100vh;
padding: 10px 20px;
background-color: #f5f7fb;
position: relative;
.overlay-con{
text-align: center;
}
.detail-con {
width: 100%;
height: calc(100vh - 60px);
overflow-y: auto;
.activity-con {
width: 100%;
background-color: #ffffff;
border-radius: 16px;
padding: 20px;
position: relative;
margin: 14px 0;
.status {
position: absolute;
top: 0;
right: 0;
width: 60px;
height: 26px;
line-height: 26px;
text-align: center;
font-size: 13px;
border-top-right-radius: 16px;
border-bottom-left-radius: 16px;
}
.progress-status {
background-color: #f2742c;
color: #ffffff;
}
.end-status {
background-color: #dddddd;
color: #666666;
}
.title {
display: flex;
align-items: center;
.title-status {
display: inline-block;
width: 50px;
height: 20px;
line-height: 20px;
text-align: center;
background-color: rgba(82, 196, 26, 0.2);
border-radius: 9px;
color: #52c41a;
font-size: 12px;
margin-right: 10px;
}
.title-con {
font-size: 16px;
font-weight: bold;
color: #333333;
}
}
.participate {
display: flex;
flex-direction: row;
justify-content: space-between;
color: #999999;
padding: 12px 0;
font-size: 14px;
span:nth-child(1) {
width: 62%;
}
span:nth-child(2) {
position: relative;
&:after {
content: '';
position: absolute;
top: 4px;
left: -20px;
width: 15px;
height: 15px;
background: url('/@/assets/images/questions/participateIcon.png') no-repeat;
background-size: 100% 100%;
}
}
}
.question-number {
font-weight: 500;
color: #999999;
}
.answerInfo {
margin-top: 10px;
padding: 14px;
border-radius: 16px;
background-color: #eff5ff;
.answer-item {
padding: 3px 0;
> span:nth-child(1) {
display: inline-block;
width: 28%;
color: #666666;
}
> span:nth-child(2) {
color: #333333;
}
> span:nth-child(3) {
padding-left: 14px;
}
}
.head {
position: relative;
}
.contact {
position: absolute;
right: 2px;
bottom: 3px;
width: 85px;
height: 24px;
}
.defaultImg {
background: url('/@/assets/images/questions/contact.png') no-repeat;
background-size: 100% 100%;
}
.no-contact {
color: #666666;
background: url('/@/assets/images/questions/no-contact.png') no-repeat;
background-size: 100% 100%;
}
}
}
.level-con {
padding: 20px;
background-color: #ffffff;
border-radius: 16px;
.title {
font-size: 16px;
font-weight: bold;
color: #333333;
padding-bottom: 10px;
}
.encourage {
padding: 8px 0;
color: #333333;
}
}
}
.application-btn {
width: 100%;
height: 55px;
position: absolute;
left: 0;
bottom: 0;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
.btn {
width: 80%;
height: 42px;
line-height: 42px;
border-radius: 20px;
text-align: center;
}
.active-btn {
background-color: #1a68ee;
color: #ffffff;
}
.no-active-btn {
background-color: #dddddd;
color: #666666;
}
}
}
</style>
<style lang="less">
@import '/@/assets/less/question.less';
</style>
@@ -0,0 +1,244 @@
<template>
<div class="answerActivity-container">
<div class="activity-title">
<div class="active-item" v-for="act in tagLists" :key="act.key">
<span :class="activeTag === act.key ? 'active' : 'default'" @click="handleTags(act.key)">{{ act.name }}</span>
</div>
</div>
<div class="activity-container" ref="activeRefs">
<template v-if="lists.length > 0">
<div class="activity-con" v-for="listItem in lists" @click="handleDetail(listItem)">
<div :class="useStatus(listItem.state).class" class="status">{{ useStatus(listItem.state).name }}</div>
<div class="title">
<span class="title-status" v-if="listItem.userState === 1">已参加</span>
<span class="title-con">{{ listItem.examPlanName }}</span>
</div>
<div class="participate">
<span>{{ listItem.questionCategory }}</span>
<span v-if="listItem.userState === 1">已参与{{ listItem.actualJoinNumber }}</span>
</div>
<div class="question-number"
>{{ listItem.questionSum === null ? 0 : listItem.questionSum }}道题 答题时长为{{listItem.answerTime }}分钟</div
>
<div class="answerInfo">
<div class="answer-item">
<span>起止时间</span>
<span>{{ timeSplit(listItem.startTime) }} ~ {{ timeSplit(listItem.endTime) }}</span>
</div>
<div class="answer-item">
<span>组织部门</span>
<span>{{ listItem.organizerDeptName }}</span>
</div>
<div class="answer-item">
<span>负责人</span>
<span>{{ listItem.managerName }}</span>
</div>
</div>
</div>
</template>
<div class="answer-data" v-else>
<Nodata />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useStatus, timeSplit } from '/@/views/23-physical-questions/questionHooks';
import { activieListApi } from '/@/views/23-physical-questions/questionApI';
import { showFailToast } from 'vant';
import Nodata from '/@/views/23-physical-questions/components/noData.vue';
import { newPageParams, openPage } from '/@/hooks/openPage';
const activeTag = ref(0);
const tagLists = ref([
{
name: '全部活动',
key: 0,
},
{
name: '我参与的',
key: 1,
},
]);
const lists = ref([]);
const activeRefs = ref(null);
onMounted(() => {
getList();
});
function getList() {
activieListApi({
type: activeTag.value,
}).then((res: any) => {
if (res.code === 200) {
lists.value = res.result.records;
} else {
showFailToast({
message: res.message,
forbidClick: true,
});
}
});
}
function handleTags(active: number) {
activeRefs.value.scrollTop = 0;
activeTag.value = active;
getList();
}
function handleDetail(item: any) {
openPage(
'/physical-activityDetail',
newPageParams({
id: item.id,
startNewActivity: -1,
})
);
}
</script>
<style scoped lang="less">
@import '/@/assets/less/question.less';
.answerActivity-container {
width: 100%;
padding: 10px 20px;
background-color: #f5f7fb;
.activity-title {
display: flex;
margin-bottom: 5px;
.active-item {
margin: 0 10px;
}
.active {
color: #333333;
font-weight: bold;
position: relative;
&:after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 9px;
background: linear-gradient(0deg, #1a68ee 0%, rgba(255, 255, 255, 0) 100%);
opacity: 0.5;
}
}
.default {
font-weight: 500;
color: #77849e;
}
}
.activity-container {
width: 100%;
height: calc(100vh - 47px);
overflow-y: auto;
.answer-data {
height: 100%;
display: flex;
align-items: center;
justify-items: center;
}
.activity-con {
width: 100%;
background-color: #ffffff;
border-radius: 16px;
padding: 20px;
position: relative;
margin: 14px 0;
.status {
position: absolute;
top: 0;
right: 0;
width: 60px;
height: 26px;
line-height: 26px;
text-align: center;
font-size: 13px;
border-top-right-radius: 16px;
border-bottom-left-radius: 16px;
}
.progress-status {
background-color: #f2742c;
color: #ffffff;
}
.end-status {
background-color: #dddddd;
color: #666666;
}
.title {
display: flex;
align-items: center;
.title-status {
display: inline-block;
width: 50px;
height: 20px;
line-height: 20px;
text-align: center;
background-color: rgba(82, 196, 26, 0.2);
border-radius: 9px;
color: #52c41a;
font-size: 12px;
margin-right: 10px;
}
.title-con {
font-size: 16px;
font-weight: bold;
color: #333333;
width: 70%;
.text-over();
}
}
.participate {
display: flex;
flex-direction: row;
justify-content: space-between;
color: #999999;
padding: 12px 0;
font-size: 14px;
span:nth-child(1) {
width: 62%;
.text-over();
}
span:nth-child(2) {
position: relative;
&:after {
content: '';
position: absolute;
top: 4px;
left: -20px;
width: 15px;
height: 15px;
background: url('/@/assets/images/questions/participateIcon.png') no-repeat;
background-size: 100% 100%;
}
}
}
.question-number {
font-weight: 500;
color: #333333;
}
.answerInfo {
margin-top: 10px;
padding: 14px;
border-radius: 16px;
background-color: #eff5ff;
.answer-item {
padding: 3px 0;
> span:nth-child(1) {
display: inline-block;
width: 70px;
color: #666666;
}
> span:nth-child(2) {
color: #333333;
}
}
}
}
}
}
</style>
@@ -0,0 +1,64 @@
<template>
<div class="level-container">
<div class="level-bg">
<img :src="rankeTitle" />
</div>
<div class="level-list">
<ranke-lists :listsInfo="listsInfo" />
</div>
</div>
</template>
<script setup lang="ts">
import rankeLists from '/@/views/23-physical-questions/components/rankeLists.vue';
import rankeTitle from '/@/assets/images/questions/rankeTitle.png';
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { rankeListApi } from '/@/views/23-physical-questions/questionApI';
const route = useRoute();
const listsInfo = ref({});
onMounted(() => {
getList();
});
function getList() {
rankeListApi({
examPlanId: route.query.id,
}).then((res: any) => {
if (res.code === 200) {
listsInfo.value = res.result;
}
});
}
</script>
<style scoped lang="less">
.level-container {
width: 100%;
height: 100%;
position: relative;
overflow-y: auto;
background-color: #f5f7fb;
.level-bg {
width: 100%;
height: 234px;
background: url("/@/assets/images/questions/ranke-bg.png") no-repeat;
background-size: contain;
img {
position: absolute;
left: 20px;
top: 50px;
width: 60%;
height: 43px;
}
}
.level-list {
width: 90%;
height: calc(100vh - 120px);
position: absolute;
left: 20px;
top: 110px;
}
}
</style>
@@ -0,0 +1,299 @@
<template>
<div class="answer-home-container">
<van-skeleton title :row="8" :loading="loading">
<div class="times">倒计时:{{ formatTime }}</div>
<div class="interval"></div>
<div class="question">
<question-card :item="questionItem" :ifAnswer="true" @selectOption="selectAnswer" />
</div>
<div class="bottom">
<answer-panel :list="list" :currentNo="currentIndex" :isEdit="true" @last="handleSheet" @next="handleSheet" @submit="handleSubmit" />
</div>
</van-skeleton>
<dialog-overlay
:show="dialogCon.show"
:imgUrl="dialogCon.url"
:okText="dialogCon.okText"
:cancelBtn="dialogCon.canBtn"
@cancel="handleCancel"
@ok="handleOk"
>
<template #content>
<div class="overlay-con">{{dialogCon.con}}</div>
</template>
</dialog-overlay>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import questionCard from '/@/views/23-physical-questions/answer/questionCard.vue';
import answerPanel from '/@/views/23-physical-questions/answer/answerPanel.vue';
import dialogOverlay from '/@/views/23-physical-questions/components/dialogOverlay.vue';
import tipsIcon from '/@/assets/images/questions/tips.png';
import warnIcon from '/@/assets/images/questions/warn.png';
import clockIcon from '/@/assets/images/questions/clock.png';
import { convertTimeToSeconds, usePageAnswer, minuteConverSeconds, useCountdown } from '/@/views/23-physical-questions/questionHooks';
import {
destroyPage,
newPageParams,
openPage,
refreshPage,
setAnswerResult
} from "/@/hooks/openPage";
import {
questionListApi,
activieQuestionListApi,
submitApi,
activieSubmitApi,
firstAidListApi,
} from '/@/views/23-physical-questions/questionApI';
import { showFailToast } from 'vant';
import $bus from '/@/utils/mitt';
import { useInterceptBack } from '/@/hooks/useInterceptAndroid';
import { useLoading } from "/@/utils/compUtils";
const route = useRoute();
const router = useRouter();
const loading = ref(true);
const dialogCon = ref({
show: false,
url: '',
con: '',
canBtn: true,
type: 2, // 2退出 3 倒计时结束 4 提示
okText: '',
});
const currentIndex = ref<number>(0);
const questionItem = ref({});
const list = ref<any>([]);
const platform = sessionStorage.getItem('platform');
const { formatTime, startTimer, stopTimer } = useCountdown(route.query.answerTime);
const submitParams = ref<any>({
paperId: '', // 考卷ID
answerTimeSum: '', // 答题总耗时(单位:分钟)
userSubmitAnswerDetailsDTOS: [], // 用户答案
});
const { loadingSpinner, loadingClose } = useLoading();
onMounted(() => {
useInterceptBack(1, dropOut);
if (platform !== null) {
if (platform === 'C03') {
getAidQuestion();
} else {
route.query.hasOwnProperty('active') ? getActiveQuestion() : getQuestion();
}
}
});
watch(formatTime, (newVal) => {
if (convertTimeToSeconds(newVal) === 0) {
dialogCon.value.show = true;
dialogCon.value.con = '时间到,当前答题已结束。';
dialogCon.value.canBtn = false;
dialogCon.value.url = clockIcon;
dialogCon.value.type = 2;
dialogCon.value.okText = '确认';
stopTimer();
}
});
$bus.on('selectSheet', (index) => {
let quItem = list.value[index];
quItem['index'] = index;
questionItem.value = quItem;
currentIndex.value = index;
});
// 中途退出弹窗
function dropOut() {
dialogCon.value.show = true;
dialogCon.value.con = '中途退出将不会保存您的答案,是否确定退出?';
dialogCon.value.url = clockIcon;
dialogCon.value.type = 3;
dialogCon.value.okText = '退出答题';
}
// 获取随机试卷
function getQuestion() {
questionListApi({ questionCategoryId: route.query.id }).then((res: any) => {
handlePaper(res);
});
}
// 获取活动试卷
function getActiveQuestion() {
activieQuestionListApi({ examPlanId: route.query.id }).then((res: any) => {
handlePaper(res);
});
}
// 获取急救培训试卷
function getAidQuestion() {
firstAidListApi({ activityId: route.query.id }).then((res: any) => {
handlePaper(res);
});
}
// 试卷数据进行处理显示
function handlePaper(res: any) {
if (res.code === 200) {
loading.value = false;
if (res.result.examPaperQuestions.length === 0) {
return false;
}
startTimer();
res.result.examPaperQuestions.map((item: any) => {
item['user_answer'] = '';
});
submitParams.value.paperId = res.result.id;
list.value = res.result.examPaperQuestions;
let quItem = list.value[0];
quItem['index'] = currentIndex.value;
questionItem.value = quItem;
} else {
dialogCon.value.show = true;
dialogCon.value.con = res.message;
dialogCon.value.url = tipsIcon;
dialogCon.value.type = 4;
dialogCon.value.canBtn = false;
dialogCon.value.okText = '我知道了';
}
}
// 上一题、下一题
function handleSheet(type: string) {
const { answer_item, c_index } = usePageAnswer(list.value, type, currentIndex.value);
questionItem.value = answer_item;
currentIndex.value = c_index;
}
// 改变用户选择选项样式
function selectAnswer(answer: string, item: object) {
let findIndex = list.value.findIndex((e) => e.id === item.id);
list.value[findIndex].user_answer = answer;
}
function handleCancel() {
dialogCon.value.show = false;
}
function handleOk() {
dialogCon.value.show = false;
let type = dialogCon.value.type;
if (platform === 'C03') {
if (type === 2) {
// 倒计时时间到,点击确定提交
handleSubmit();
} else if (type === 3) {
// 中途退出点击,退出答案按按钮,返回首页列表
try {
destroyPage();
} catch {
router.go(-1);
}
}
} else {
if (type === 2) {
// 倒计时时间到,点击确定提交
handleSubmit();
} else if (type === 3 || type === 4) {
// 1. type:3 中途退出点击,退出答案按按钮,返回上一个页面
// 2. type:4 错误弹窗,返回上一个页面
try {
destroyPage();
} catch {
router.go(-1);
}
}
}
}
// 提交答案参数
function setParams() {
let answerList: { answer: string; questionId: string }[] = [];
list.value.map((item: any) => {
answerList.push({
answer: item.user_answer,
questionId: item.id,
});
});
const totalTimer = minuteConverSeconds(route.query.answerTime); // 总时间(秒)
const consumeTimer = convertTimeToSeconds(formatTime.value); // 未消耗时间(秒)
const diffTimer = totalTimer - consumeTimer; // 已消耗时间(秒)
const finalTimer = diffTimer =='0'?totalTimer:diffTimer;
submitParams.value.answerTimeSum = finalTimer;
submitParams.value.userSubmitAnswerDetailsDTOS = answerList;
}
async function handleSubmit() {
loadingSpinner();
await setParams();
route.query.hasOwnProperty('active') ? await submitActivty() : await submitRound();
}
// 随机提交答案
function submitRound() {
submitApi(submitParams.value).then((res: any) => {
result(res);
});
}
// 活动提交答案
function submitActivty() {
activieSubmitApi(submitParams.value).then((res: any) => {
result(res);
});
}
// 提交答案提交后逻辑处理
function result(res: any) {
useInterceptBack(0);
if (res.code === 200) {
loadingClose();
refreshPage();
if (platform === 'C03') {
const resParams = { ...res.result, activityId: route.query.id };
setAnswerResult(JSON.stringify(resParams));
stopTimer();
} else {
router.replace({
path: '/physical-result',
query: newPageParams(res.result),
});
}
} else {
loadingClose();
showFailToast({
message: res.result,
forbidClick: true,
});
}
stopTimer();
}
onUnmounted(() => {
stopTimer();
});
</script>
<style scoped lang="less">
.answer-home-container {
height: 100vh;
background-color: #f5f7fb;
position: relative;
.overlay-con {
text-align: center;
}
.times {
color: #333333;
padding: 20px 0 20px 0;
text-align: center;
background-color: #ffffff;
}
.interval {
height: 10px;
background-color: #f5f7fb;
}
.question {
width: 100%;
height: calc(100vh - 140px);
overflow-y: auto;
}
.bottom {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
}
:deep(.van-loading) {
left: 45%;
top: 40%;
}
}
</style>
@@ -0,0 +1,158 @@
<template>
<div class="sheet-container">
<div class="sheet">
<span @click="openSheet"></span>
<span> {{ currentNo + 1 }}/{{ list.length }}</span>
</div>
<div class="btn-group">
<div :class="['btn', useSheetButton(0, currentNo, isEdit, list).class]" @click="getSheet('last')">上一题</div>
<div class="btn next" v-if="useSheetButton(1, currentNo, isEdit, list).ifShow" @click="submit">提交</div>
<div
:class="['btn', useSheetButton(2, currentNo, isEdit, list).class]"
v-if="useSheetButton(2, currentNo, isEdit, list).ifShow"
@click="getSheet('next')"
>下一题</div
>
</div>
<van-popup v-model:show="show" position="bottom" closeable close-icon-position="top-right">
<answer-sheet :list="list" :isEdit="isEdit" />
</van-popup>
<dialog-overlay
:show="dialogCon.show"
:content="dialogCon.con"
:imgUrl="dialogCon.url"
:okText="dialogCon.okText"
@cancel="handleCancel"
@ok="dialogSubmit"
>
<template #content>
<div class="overlay-con">{{dialogCon.con}}</div>
</template>
</dialog-overlay>
</div>
</template>
<script setup name="answer-sheet" lang="ts">
import { ref,onMounted } from 'vue';
import answerSheet from '/@/views/23-physical-questions/answer/answerSheet.vue';
import dialogOverlay from '/@/views/23-physical-questions/components/dialogOverlay.vue';
import submitIcon from '/@/assets/images/questions/submit.png';
import { useSheetButton } from '/@/views/23-physical-questions/questionHooks';
import $bus from '/@/utils/mitt';
const show = ref(false);
const emit = defineEmits(['last', 'next', 'submit']);
const props = defineProps({
list: {
type: Array,
},
currentNo: {
type: Number,
},
// true 答题 false 回顾
isEdit: {
type: Boolean,
},
});
const dialogCon = ref({
show: false,
url: submitIcon,
con: '',
okText: '提交答题',
});
onMounted(()=>{
const platform = sessionStorage.getItem('platform') !== null ? sessionStorage.getItem('platform') : 'C01';
if(platform === 'C03'){
dialogCon.value.con = '请仔细确认答案,是否确认提交?'
}else{
dialogCon.value.con = '答题内容代表了您对该知识的掌握程度,请仔细确认答案,是否确认提交?'
}
})
$bus.on('selectSheet', () => {
show.value = false;
});
function getSheet(type: string) {
if (type === 'last') {
if (props.currentNo > 0) {
emit('last', type);
}
} else {
if (props.currentNo + 1 < props.list.length) {
emit('next', type);
}
}
}
function submit() {
dialogCon.value.show = true;
}
function openSheet() {
show.value = true;
}
function handleCancel() {
dialogCon.value.show = false;
}
// 弹窗提交答题
function dialogSubmit() {
dialogCon.value.show = false;
emit('submit');
}
</script>
<style scoped lang="less">
.sheet-container {
background-color: #ffffff;
padding: 10px 0;
display: flex;
align-items: center;
justify-content: center;
.overlay-con{
text-align: center;
}
.sheet {
width: 30%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
> span:nth-child(1) {
display: inline-block;
width: 20px;
height: 20px;
background: url('/@/assets/images/questions/sheet.png') no-repeat;
background-size: 100% 100%;
}
> span:nth-child(2) {
padding-top: 5px;
color: #333333;
font-size: 12px;
}
}
.btn-group {
width: 70%;
display: flex;
justify-content: space-around;
.btn {
width: 120px;
height: 35px;
line-height: 35px;
text-align: center;
border-radius: 18px;
}
.last {
color: #1a68ee;
background-color: rgba(26, 104, 238, 0.1);
border: 1px solid #1a68ee;
}
.next {
color: #ffffff;
background-color: #1a68ee;
}
.disable {
border: 1px solid #dddddd;
color: #666666;
background-color: #dddddd;
}
}
}
</style>
@@ -0,0 +1,137 @@
<template>
<div class="result-container">
<div class="history-score">
<div class="score-title">
<div>本次得分</div>
<div :style="{ color: useColor(info?.totalScore) }"
><span>{{ info.totalScore }}</span
></div
>
</div>
<div class="score-info">
<div class="info-item">
<span>答题时长</span>
<span>正确率</span>
<span>答题数</span>
</div>
<div class="info-item">
<span>{{ useConvertTime(info?.answerTimeSum) }}</span>
<span :style="{ color: useColor(amastery(info?.trueRate)) }">{{ amastery(info?.trueRate) }}%</span>
<span>{{ info?.answerSum }}/{{ info?.questionSum }}</span>
</div>
</div>
<div class="answerBtn" @click="handleReview">回顾答题</div>
</div>
<div class="history-title"> 答题历史</div>
<div class="history-con">
<record-list :list="lists" />
</div>
</div>
</template>
<script name="answer-result" setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import recordList from '/@/views/23-physical-questions/answer/recordList.vue';
import { useColor, useConvertTime, amastery } from '/@/views/23-physical-questions/questionHooks';
import { newPageParams, openPage } from '/@/hooks/openPage';
import { historyListApi } from '/@/views/23-physical-questions/questionApI';
import { showFailToast } from 'vant';
const route = useRoute();
const lists = ref([]);
const info = ref({});
onMounted(() => {
info.value = route.query;
getLists();
});
function getLists() {
historyListApi({}).then((res: any) => {
if (res.code === 200) {
lists.value = res.result.records;
} else {
showFailToast({
message: res.message,
forbidClick: true,
});
}
});
}
// 回顾答题
function handleReview() {
openPage(
'/physical-review',
newPageParams({
id: info.value.userExamRecordId,
})
);
}
</script>
<style scoped lang="less">
.result-container {
width: 100%;
padding: 10px 20px;
background-color: #f5f7fb;
.history-score {
padding: 10px 20px;
background-color: #ffffff;
border-radius: 16px;
.score-title {
text-align: center;
> div:nth-child(1) {
padding-top: 10px;
}
> div:nth-child(2) {
padding: 10px 0;
color: #52c41a;
span {
font-weight: bold;
font-size: 30px;
}
}
}
.score-info {
padding: 16px 0;
border-radius: 16px;
background-color: #eff5ff;
.info-item:nth-child(1) {
span {
color: #666666;
}
}
.info-item:nth-child(2) {
padding-top: 6px;
span {
color: #333333;
}
}
.info-item {
display: flex;
justify-content: space-between;
span {
flex: 1;
text-align: center;
}
}
}
.answerBtn {
width: 135px;
height: 30px;
line-height: 30px;
margin: 14px auto;
text-align: center;
border: 1px solid #1a68ee;
background-color: rgba(26, 104, 238, 0.1);
color: #1a68ee;
border-radius: 16px;
}
}
.history-title {
padding: 12px 0;
font-size: 16px;
font-weight: bold;
color: #333333;
}
}
</style>
@@ -0,0 +1,289 @@
<template>
<div class="review-container">
<div class="review-info">
<div class="score">得分{{ questionItem.userScore }}</div>
<div class="score">耗时{{ useConvertTime(situation.answerTimeSum) }}</div>
<div class="question">
<span>{{ situation.yesAmount }}</span>
<span>{{ situation.questionSum - situation.yesAmount }}</span>
</div>
</div>
<div class="interval"></div>
<div class="question-container">
<question-card :item="questionItem" :ifAnswer="false" :questionType="questionType" />
<div class="answer-tips" v-if="questionItem.isTrue === 4">
<WarningFilled />
<span>{{ questionItem.remark }}</span>
</div>
<div class="answer" v-else>
<div class="answer-item">
<span>答案</span>
<span :class="standStyle.class">{{ standStyle.text}}</span>
</div>
<div class="answer-item">
<span>您的选择</span>
<span :class="answerStyle.class">{{ answerStyle.text }}</span>
</div>
</div>
<div class="explain">
<div class="title">答疑</div>
<div class="con">{{ questionItem.answer ? questionItem.answer : '暂无' }}</div>
</div>
</div>
<div class="bottom">
<answer-panel :list="list" :currentNo="currentIndex" :isEdit="false" @last="handleSheet" @next="handleSheet" />
</div>
</div>
</template>
<script setup name="answer-review" lang="ts">
import { onMounted, ref } from 'vue';
import $bus from '/@/utils/mitt';
import { useRoute } from 'vue-router';
import questionCard from '/@/views/23-physical-questions/answer/questionCard.vue';
import answerPanel from '/@/views/23-physical-questions/answer/answerPanel.vue';
import { usePageAnswer, useConvertTime } from '/@/views/23-physical-questions/questionHooks';
import { questionDetailApi } from '/@/views/23-physical-questions/questionApI';
import { showFailToast } from 'vant';
import { WarningFilled } from '@ant-design/icons-vue';
const route = useRoute();
const currentIndex = ref(0);
const questionItem = ref({});
const situation = ref({});
const list = ref([]);
const questionType = ref('');
const answerStyle = ref({
class: '',
text: '',
});
const standStyle = ref({
class: '',
text: '',
});
onMounted(() => {
getLists();
});
$bus.on('selectSheet', (index) => {
let quItem = list.value[index];
quItem['index'] = index;
questionItem.value = quItem;
currentIndex.value = index;
answerShow(questionItem.value);
});
function getLists() {
questionDetailApi({
id: route.query.id,
}).then(async (res: any) => {
if (res.code === 200) {
situation.value = res.result;
questionType.value = res.result.questionCategoryId;
list.value = res.result.userExamPaperRecordQuestionVOS;
let quItem = list.value[0];
quItem['index'] = currentIndex.value;
questionItem.value = quItem;
answerShow(questionItem.value);
hanleAnswer();
standardAnswer();
} else {
showFailToast({
message: res.message,
forbidClick: true,
});
}
});
}
/**
* 标准答案样式及内容
*
*/
function standardAnswer() {
let item = questionItem.value;
let paramsClass = '',
paramsText = '暂无';
// 自我健康模块
if (questionType.value === '2') {
if (item.undefinedRealAnswer !== null) {
let find = item.options.find((e: any) => e.id === item.undefinedRealAnswer);
paramsText = find.optionNo;
paramsClass = 'correct';
}
} else {
if (item.real_answer !== '') {
paramsClass = 'correct';
paramsText = item.real_answer;
}
}
standStyle.value.class = paramsClass;
standStyle.value.text = paramsText;
}
/**
* 用户选择答案样式及内容
*
*/
function hanleAnswer() {
let item = questionItem.value;
let paramsClass = '',
paramsText = '未填写';
// 自我健康模块
if (questionType.value === '2') {
if (item.undefinedRealAnswer !== null) {
paramsClass = item.undefinedRealAnswer == item.userAnswer ? 'correct' : 'error';
paramsText = item.user_answer;
}
} else {
if (item.user_answer !== '') {
paramsClass = item.real_answer === item.user_answer ? 'correct' : 'error';
paramsText = item.user_answer;
}
}
answerStyle.value.class = paramsClass;
answerStyle.value.text = paramsText;
}
/**
* real_answer 正确答案
* user_answer 用户答案
* @param currentItem 当前选中的题
*/
function answerShow(currentItem: any) {
let { options, realAnswer, userAnswer,undefinedRealAnswer } = currentItem;
let realList: any[] = [];
let userList: any[] = [];
let answerList: any[] = [];
let userAnswerList: any[] = [];
if (realAnswer) {
realList = realAnswer.split(',');
}
if (userAnswer) {
userList = userAnswer.split(',');
}
options.filter((item: any) => {
realList.indexOf(item.id) > -1 ? answerList.push(item.optionNo) : '';
userList.indexOf(item.id) > -1 ? userAnswerList.push(item.optionNo) : '';
if (undefinedRealAnswer !== null) {
let vis = undefinedRealAnswer.indexOf(item.id);
item.isTrue = vis !== -1;
}
});
currentItem['real_answer'] = answerList.length > 0 ? answerList.join(',') : '';
currentItem['user_answer'] = userAnswerList.length > 0 ? userAnswerList.join(',') : '';
questionItem.value = currentItem;
hanleAnswer();
standardAnswer();
}
// 上一题、下一题
function handleSheet(type: string) {
const { answer_item, c_index } = usePageAnswer(list.value, type, currentIndex.value);
questionItem.value = answer_item;
currentIndex.value = c_index;
answerShow(questionItem.value);
}
</script>
<style scoped lang="less">
.review-container {
height: 100vh;
background-color: #f5f7fb;
position: relative;
.review-info {
padding: 16px 20px;
background-color: #ffffff;
display: flex;
.score {
width: 25%;
color: #333333;
}
.question {
width: 50%;
display: flex;
justify-content: flex-end;
span {
width: 30%;
text-align: right;
}
span:nth-child(1) {
position: relative;
&:after {
content: '';
position: absolute;
right: 21px;
top: 3px;
width: 16px;
height: 16px;
background: url('/@/assets/images/questions/correct.png') no-repeat;
background-size: 100% 100%;
}
}
span:nth-child(2) {
position: relative;
&:after {
content: '';
position: absolute;
right: 21px;
top: 3px;
width: 16px;
height: 16px;
background: url('/@/assets/images/questions/error.png') no-repeat;
background-size: 100% 100%;
}
}
}
}
.question-container {
padding-bottom: 30px;
background-color: #ffffff;
height: calc(100vh - 140px);
overflow-y: auto;
.answer-tips {
margin: -10px 20px 10px 18px;
padding: 10px;
color: #1a68ee;
background-color: #EFF5FF;
border-radius: 8px;
span{
padding-left: 4px;
}
}
.answer {
display: flex;
padding: 0 20px 15px 20px;
.answer-item {
.correct {
color: #52c41a;
}
.error {
color: #ed2a26;
}
}
.answer-item:nth-child(2) {
padding-left: 20px;
}
}
.explain {
margin: 0 20px;
padding: 14px 20px;
background-color: #eff5ff;
border-radius: 16px;
.title {
color: #333333;
}
.con {
color: #666666;
padding-top: 10px;
}
}
}
.interval {
height: 10px;
background-color: #f5f7fb;
}
.bottom {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
}
}
</style>
@@ -0,0 +1,138 @@
<template>
<div class="sheet-con">
<div class="title">
<span>答题卡</span>
<span>已作答</span>
<span>未作答</span>
</div>
<div class="sheet-answer">
<div class="sheet-box" v-for="(item, index) in list" :key="item" @click="handleSheet(index)">
<div :class="['number-item', isEdit && item.user_answer !== '' ? 'selected' : 'default', setStatus(item)]">
<div class="text">{{ index + 1 }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup name="answer-sheet" lang="ts">
import $bus from '/@/utils/mitt';
const props = defineProps({
list: {
type: Array,
default: [],
},
// true 答题 false 回顾
isEdit: {
type: Boolean,
},
});
function handleSheet(index: number) {
$bus.emit('selectSheet', index);
}
// 回顾答题卡样式设置
function setStatus(item: any) {
let statusClass = '';
if (!props.isEdit) {
if (item.userAnswer !== null && item.userAnswer !== '') {
if (item.isTrue === 0) {
statusClass = 'correct';
} else {
statusClass = 'error';
}
} else {
statusClass = 'default';
}
}
return statusClass;
}
</script>
<style scoped lang="less">
.sheet-con {
max-height: 350px;
.title {
padding: 15px 20px;
> span:nth-child(1) {
color: #333333;
font-size: 15px;
}
> span:nth-child(2) {
padding-left: 12%;
color: #333333;
font-size: 12px;
position: relative;
&:after {
content: '';
position: absolute;
right: 51%;
top: 4%;
width: 12px;
height: 12px;
border-radius: 50%;
border: 1px solid #1f91f2;
background-color: rgba(31, 145, 242, 0.2);
}
}
> span:nth-child(3) {
padding-left: 12%;
color: #333333;
font-size: 12px;
position: relative;
&:after {
content: '';
position: absolute;
right: 51%;
top: 4%;
width: 12px;
height: 12px;
border-radius: 50%;
border: 1px solid #999999;
}
}
}
.sheet-answer {
max-height: calc(350px - 55px);
overflow-y: auto;
padding: 0 10px 20px 10px;
width: 100%;
display: flex;
flex-wrap: wrap;
.sheet-box {
width: 16.5%;
display: flex;
justify-content: center;
.number-item {
width: 42px;
height: 42px;
line-height: 42px;
border-radius: 50%;
text-align: center;
margin: 12px 0;
}
}
.default {
border: 1px solid #999999;
color: #333333;
}
.selected {
border: 1px solid rgba(31, 145, 242, 1);
color: #1f91f2;
background-color: rgba(31, 145, 242, 0.2);
}
.correct {
border: 1px solid rgba(82, 196, 26, 1);
color: #52c41a;
background-color: rgba(82, 196, 26, 0.2);
}
.error {
border: 1px solid rgba(237, 42, 38, 1);
color: #ed2a26;
background-color: rgba(237, 42, 38, 0.2);
}
}
}
</style>
@@ -0,0 +1,273 @@
<template>
<div class="card-container">
<div class="title">
<div>{{ handleQuestionType() }}</div>
<div>{{ item.index + 1 }}{{ item.questionDesc }}</div>
</div>
<div class="option">
<div
v-for="(optItem, optIndex) in item.options"
:key="optIndex"
@click="answerItem(optItem, optItem.id)"
:class="['option-item', ifAnswer && item.user_answer.indexOf(optItem.id) > -1 ? 'active' : '', !ifAnswer && showClassOption(optItem)]"
>
<div class="choose-icon correctBtn" v-if="!ifAnswer && correctIcon(optItem)"></div>
<div class="choose-icon errorBtn" v-if="!ifAnswer && wrongIcon(optItem)"></div>
<span class="option-number">{{ optItem.optionNo }}</span>
<span class="option-name">{{ optItem.optionValue }}</span>
</div>
</div>
</div>
</template>
<script setup name="question-card" lang="ts">
import { ref } from 'vue';
const selectClass = ref([]);
const emit = defineEmits(['selectOption']);
const props = defineProps({
item: {
type: Object,
},
// true 答题 false 回顾
ifAnswer: {
type: Boolean,
},
questionType: {
type: String,
},
});
// 答案选项样式
function showClassOption(val: any) {
let text = '';
// questionType 2 自我健康答题
if (props.questionType === '2') {
if (props.item.isTrue === 4) {
// 用户已答题,没有标准答案
if (props.item.userAnswer !== null) {
let listAns = props.item.userAnswer.split(',');
let findVis = listAns.find((e) => e === val.id);
text = findVis !== undefined ? 'active' : '';
}
} else if ([0, 1, 2].includes(props.item.isTrue)) {
// 自我健康答题 已答题,有标准答案
text = optionStatus(val, 'undefinedRealAnswer');
}
} else {
text = optionStatus(val, 'realAnswer');
}
return text;
}
/**
* options背景样式
* @param userAnswer
* @param val
* @param typeFileds 标准答案
*/
function optionStatus(val: object, typeFileds: string) {
let text = '';
const data = props.item;
const ansList = data.userAnswer !== null ? data.userAnswer : [];
const ifcon = ansList.indexOf(val.id);
const same = ansList === props.item[typeFileds];
if (ifcon !== -1) {
if (val.isTrue && same) {
text = 'correct';
} else {
if ((val.isTrue && !same) || !val.isTrue) {
text = 'error';
}
}
} else {
if (val.isTrue) text = 'correct';
}
// if (ifcon !== -1 && val.isTrue && same) {
// text = 'correct';
// } else if (ifcon !== -1 && val.isTrue && !same) {
// text = 'error';
// } else if (ifcon !== -1 && !val.isTrue) {
// text = 'error';
// } else if (ifcon === -1 && val.isTrue) {
// text = 'correct';
// }
return text;
}
/**
* 回答图标
* @param val
*/
function correctIcon(val: any) {
return showIcon(val, 'correct');
}
/**
* 错误图标
* @param val
*/
function wrongIcon(val: any) {
return showIcon(val, 'wrong');
}
/**
* 控制正确、错误图标显示
* @param userAnswer 用户答案
* @param val 选项options值
* @param type correct 正确,wrong 错误
*/
function showIcon(val: object, type: string) {
let vis = false;
let data = props.item;
let typeFileds = '';
// 自我健康图标显示
if (props.questionType === '2') {
if (data.undefinedRealAnswer === null) return false;
typeFileds = data.undefinedRealAnswer;
} else {
if (data.userAnswer === null) return false;
typeFileds = data.realAnswer;
}
let listAns = data.userAnswer.split(',');
let con = listAns.indexOf(val.id);
const same = data.userAnswer === typeFileds;
if (con !== -1) {
if (val.isTrue) {
if ((same && type === 'correct') || (!same && type === 'wrong')) {
vis = true;
}
} else {
if (type === 'wrong') {
vis = true;
}
}
}
// if (con !== -1 && val.isTrue && same && type === 'correct') {
// vis = true;
// } else if (con !== -1 && val.isTrue && !same && type === 'wrong') {
// vis = true;
// } else if (con !== -1 && !val.isTrue && type === 'wrong') {
// vis = true;
// }
return vis;
}
/**
* 答题选中状态
* @param options
* @param index
* DX 多选 DG 单选 PD 判断
*/
function answerItem(options: Object, id: String) {
if (props.ifAnswer) {
selectClass.value = [];
if (props.item.type === 'DX') {
if (props.item.user_answer !== '') {
let val = props.item.user_answer.split(',');
selectClass.value = val;
}
let valueIndex = selectClass.value.indexOf(id);
if (valueIndex === -1) {
selectClass.value.push(id);
} else {
selectClass.value.splice(valueIndex, 1);
}
} else if (props.item.type === 'DG' || props.item.type === 'PD') {
selectClass.value = [];
selectClass.value.push(id);
}
emit('selectOption', selectClass.value.join(','), props.item);
}
}
function handleQuestionType() {
let type = props.item.type;
if (type === 'DG' || type === 'PD') {
return '单选';
} else if (type === 'DX') {
return '多选';
}
}
</script>
<style scoped lang="less">
.card-container {
background-color: #ffffff;
padding: 20px 18px;
.title {
display: flex;
align-items: flex-start;
> div:nth-child(1) {
width: 48px;
height: 20px;
line-height: 20px;
text-align: center;
background-color: rgba(26, 104, 238, 0.1);
border-radius: 4px;
color: #1a68ee;
font-size: 13px;
}
> div:nth-child(2) {
padding-left: 6px;
width: 90%;
color: #333333;
}
}
.option {
padding-top: 20px;
.option-item {
padding: 12px 10px;
background-color: #f4f4f4;
border-radius: 8px;
margin-bottom: 10px;
box-sizing: border-box;
position: relative;
display: flex;
.option-number {
display: inline-block;
width: 26px;
height: 26px;
line-height: 26px;
text-align: center;
color: #333333;
background-color: #ffffff;
border-radius: 50%;
}
.option-name {
display: inline-block;
width: 100%;
padding-left: 10px;
}
}
.active {
background-color: rgba(26, 104, 238, 0.2);
color: #1f91f2;
border: 1px solid #1a68ee;
span:nth-child(1) {
color: #1f91f2;
}
}
.correct {
background-color: rgba(82, 196, 26, 0.2);
color: #52c41a;
border: 1px solid #52c41a;
}
.error {
background-color: rgba(237, 42, 38, 0.2);
color: #ed2a26;
border: 1px solid #ed2a26;
}
.choose-icon {
position: absolute;
left: 10px;
top: 12px;
}
.correctBtn {
width: 26px;
height: 26px;
background: url('/@/assets/images/questions/correct.png') no-repeat;
background-size: 100% 100%;
}
.errorBtn {
width: 26px;
height: 26px;
background: url('/@/assets/images/questions/error.png') no-repeat;
background-size: 100% 100%;
}
}
}
</style>
@@ -0,0 +1,95 @@
<template>
<div class="record-container">
<div class="record-item" v-for="(record, index) in list" :key="index" @click="handleDetail(record)">
<div class="time-con">
<div class="timers">答题时间{{ record.startAnswerTime }}</div>
<div class="type">{{ record.questionCategory }} </div>
</div>
<div class="record-con title">
<div>得分</div>
<div>正确率</div>
<div>耗时</div>
<div>答题数</div>
</div>
<div class="record-con value">
<div :style="{ color: useColor(record.totalScore) }">{{ record?.totalScore }}</div>
<div :style="{ color: useColor(record.trueRate * 100) }">{{ trueRate(record.trueRate) }}%</div>
<div>{{ useConvertTime(record.answerTimeSum) }}</div>
<div>{{ record.answerSum }}/{{ record.questionSum }}</div>
</div>
</div>
</div>
</template>
<script setup name="history-record" lang="ts">
import { useColor, useConvertTime } from '/@/views/23-physical-questions/questionHooks';
import { newPageParams, openPage } from '/@/hooks/openPage';
const props = defineProps({
list: {
type: Array,
default: [],
},
});
function handleDetail(item: any) {
openPage(
'/physical-review',
newPageParams({
id: item.id,
})
);
}
function trueRate(val: Number) {
return Math.round(val * 100);
}
</script>
<style scoped lang="less">
.record-container {
.record-item {
border-radius: 16px;
background-color: #ffffff;
padding: 14px 15px;
margin-bottom: 20px;
.time-con {
border-bottom: 1px solid #e0e0e0;
padding: 0 0 10px 0;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
.timers {
color: #999999;
}
.type {
color: #1a68ee;
background-color: rgba(26, 104, 238, 0.1);
border-radius: 5px;
padding: 3px 6px;
}
}
.title {
padding: 16px 0 5px 0;
> div {
color: #666666;
font-size: 14px;
}
}
.value {
> div {
font-size: 16px;
color: #333333;
}
}
.record-con {
width: 100%;
display: flex;
justify-content: space-between;
>div{
flex: 1;
text-align: center;
}
}
}
}
</style>
@@ -0,0 +1,86 @@
<template>
<div class="dialog-container">
<van-overlay :show="show" :lock-scroll="false">
<div class="wrapper">
<img :src="imgUrl" />
<div class="con">
<slot name="content"></slot>
</div>
<div class="btn-group">
<div v-if="cancelBtn" class="overlay-btn overlay-cancelBtn" @click="handleCancel">{{ cancelText }}</div>
<div class="overlay-btn overlay-okBtn" @click="handleOk">{{ okText }}</div>
</div>
</div>
</van-overlay>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(['cancel', 'ok']);
const props = defineProps({
show: {
type: Boolean,
default: false,
},
imgUrl: {
type: String,
default: '',
},
okText: {
type: String,
default: '确认',
},
cancelText: {
type: String,
default: '取消',
},
cancelBtn: {
type: Boolean,
default: true,
},
});
function handleCancel() {
emit('cancel');
}
function handleOk() {
emit('ok');
}
</script>
<style scoped lang="less">
@import '/@/assets/less/question.less';
.dialog-container {
position: relative;
.wrapper {
position: absolute;
top: 30%;
left: 16%;
width: 68%;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
border-radius: 10px;
padding: 20px;
img {
position: absolute;
top: -35px;
left: 38%;
width: 25%;
}
.con {
padding: 40px 10px 34px 10px;
font-size: 16px;
color: #333333;
//line-height: 26px;
}
.btn-group {
width: 100%;
display: flex;
justify-content: space-around;
}
}
}
</style>
@@ -0,0 +1,53 @@
<template>
<div class="history-container">
<record-list v-if="lists.length > 0" :list="lists" />
<div class="history-data" v-else>
<NoData text="暂无答题记录" />
</div>
</div>
</template>
<script setup lang="ts">
import recordList from '/@/views/23-physical-questions/answer/recordList.vue';
import NoData from '/@/views/23-physical-questions/components/noData.vue';
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { historyListApi } from '/@/views/23-physical-questions/questionApI';
import { showFailToast } from 'vant';
const route = useRoute();
const lists = ref([]);
onMounted(() => {
getList();
});
function getList() {
historyListApi({
questionCategoryId: route.query.id,
}).then((res: any) => {
if (res.code === 200) {
lists.value = res.result.records;
} else {
showFailToast({
message: res.msg,
forbidClick: true,
});
}
});
}
</script>
<style scoped lang="less">
.history-container {
padding: 20px;
width: 100%;
height: 100vh;
background-color: #f5f7fb;
overflow-y: auto;
.history-data {
height: 100%;
display: flex;
align-items: center;
justify-items: center;
}
}
</style>
@@ -0,0 +1,37 @@
<template>
<div class="no-container">
<div class="nodata"></div>
<div v-if="text" class="text">{{ text }}</div>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
const props = defineProps({
text: {
type: String,
default: '暂无数据',
},
});
</script>
<style lang="less" scoped>
.no-container {
width: 100%;
padding: 30px;
display: flex;
flex-direction: column;
.nodata {
width: 75%;
margin: 0 auto;
height: 200px;
background: url('/@/assets/images/mergeQueImages/nodata.png') no-repeat;
background-size: 100% 100%;
}
.text {
text-align: center;
padding: 10px 0;
color: #666666;
}
}
</style>
@@ -0,0 +1,227 @@
<template>
<div class="participate-con">
<div class="participate-title">
<span>参加人员</span>
<span>{{ listsInfo.number }}人参加</span>
</div>
<div class="personnel">
<template v-if="listsInfo?.applyList?.records.length > 0">
<div class="ranke-title">
<div>
<span>排名</span>
<span>姓名</span>
</div>
<div>分数</div>
</div>
<div class="personnel-item my-bottom" v-if="listsInfo?.loginUserInfo !== null">
<div class="icon">{{ listsInfo?.loginUserInfo?.ranking }}</div>
<div class="sculpture">
<img
:src="getFileHttpUrl(listsInfo?.loginUserInfo?.avatar)|| 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
onerror="this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
/>
</div>
<div class="con">
<div class="name">
<span><span class="my">()</span>{{ listsInfo?.loginUserInfo?.userName }}</span>
<span>{{ listsInfo?.loginUserInfo?.userDept }}</span>
</div>
<div class="ranke-time"
>参加时间:{{ listsInfo?.loginUserInfo?.endAnswerTime !== null ? listsInfo?.loginUserInfo?.endAnswerTime : '--' }}</div
>
</div>
<div class="score">
<div>{{ listsInfo?.loginUserInfo?.totalScore ? listsInfo?.loginUserInfo?.totalScore : 0 }}</div>
<div class="ranke-time">耗时:{{ useConvertTime(listsInfo?.loginUserInfo?.answerTimeSum) }}</div>
</div>
</div>
<div class="my-item" v-if="listsInfo?.loginUserInfo !== null"></div>
<div class="personnel-item" v-for="(ranke, rankeIndex) in listsInfo?.applyList?.records" :key="rankeIndex">
<div class="icon" v-if="rankeIndex >= 3">{{ rankeIndex + 1 }}</div>
<div class="icon" v-else :class="useRanke(rankeIndex) ? useRanke(rankeIndex) : ''"></div>
<div class="sculpture">
<img
:src="getFileHttpUrl(ranke.avatar) || 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
onerror="this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
/>
</div>
<div class="con">
<div class="name">
<span>{{ ranke.userName }}</span>
<span>{{ ranke.userDept }}</span>
</div>
<div class="ranke-time">参加时间:{{ ranke.endAnswerTime!==null?ranke.endAnswerTime:'--' }}</div>
</div>
<div class="score">
<div>{{ ranke.totalScore ? ranke.totalScore : 0 }}</div>
<div class="ranke-time">耗时:{{ useConvertTime(ranke.answerTimeSum) }}</div>
</div>
</div>
<div class="personnel-look" v-if="allShow" @click="handleLook">查看全部</div>
</template>
<Nodata v-else />
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
import Nodata from '/@/views/23-physical-questions/components/noData.vue';
import { useRanke } from '/@/views/23-physical-questions/questionHooks';
import { useConvertTime } from '/@/views/23-physical-questions/questionHooks';
import { newPageParams, openPage } from '/@/hooks/openPage';
import { getFileHttpUrl } from '/@/utils/compUtils';
const props = defineProps({
listsInfo: {
type: Object,
// eslint-disable-next-line vue/require-valid-default-prop
default: {},
},
allShow: {
type: Boolean,
default: false,
},
detailID: {
type: String,
default: '',
},
});
function handleLook() {
openPage(
'/physical-level',
newPageParams({
id: props.detailID,
startNewActivity: -1,
})
);
}
</script>
<style scoped lang="less">
.participate-con {
margin-top: 20px;
background-color: #ffffff;
border-radius: 16px;
.participate-title {
padding: 10px 20px;
width: 100%;
height: 42px;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
background: linear-gradient(180deg, rgba(26, 104, 238, 0.2) 0%, rgba(48, 160, 226, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
span:nth-child(1) {
color: #333333;
font-size: 16px;
font-weight: bold;
}
span:nth-child(2) {
padding-left: 10px;
color: #666666;
font-size: 14px;
}
}
.personnel {
.ranke-title {
display: flex;
justify-content: space-between;
padding: 2px 20px;
color: #999999;
div:nth-child(1) {
span:nth-child(1) {
margin-right: 10px;
}
span:nth-child(2) {
padding-left: 10px;
border-left: 1px solid #999999;
}
}
}
.personnel-item {
width: 100%;
padding: 10px 18px;
border-bottom: 1px solid #e0e0e0;
display: flex;
align-items: center;
.icon {
width: 32px;
height: 32px;
line-height: 32px;
text-align: center;
font-weight: bold;
font-size: 16px;
}
.sculpture {
width: 36px;
height: 29px;
margin-left: 8px;
img {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.ranke-one {
background: url('/@/assets/images/questions/reanke1.png') no-repeat;
background-size: contain;
}
.ranke-two {
background: url('/@/assets/images/questions/reanke2.png') no-repeat;
background-size: contain;
}
.ranke-three {
background: url('/@/assets/images/questions/reanke3.png') no-repeat;
background-size: contain;
}
.con {
width: 66%;
padding-left: 8px;
.name {
font-size: 15px;
font-weight: bold;
color: #333333;
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
span:nth-child(2) {
padding-left: 10px;
}
.my {
color: #999999;
}
}
}
.ranke-time {
padding-top: 6px;
font-size: 13px;
color: #999999;
}
.score {
width: 28%;
text-align: right;
div:nth-child(1) {
font-size: 16px;
font-weight: bold;
color: #333333;
}
}
}
.my-bottom {
border-bottom: none;
}
.my-item {
width: 100%;
height: 6px;
background-color: #f5f5f5;
}
.personnel-look {
width: 100%;
color: #1a68ee;
padding: 14px 0;
text-align: center;
}
}
}
</style>
@@ -0,0 +1,242 @@
<template>
<div class="answer-container">
<template v-if="lists.length > 0">
<div class="answer-item" v-for="(answer, indexAns) in lists" :key="indexAns">
<div class="answer-left">
<div class="left-title">
<div>{{ answer.questionCategoryName }}</div>
<div>随机抽选{{ answer.questionSum }}道题答题时间为{{ answer.answerTime }}分钟</div>
</div>
<div class="answer-btn">
<div class="btn start" @click="handleStart(answer)">开始答题</div>
<div class="btn history" @click="handleHistory(answer)">历史记录</div>
</div>
</div>
<div class="answer-progress">
<nut-circle-progress
:progress="amastery(answer.know)"
:stroke-width="progressInfo.width"
:radius="progressInfo.radius"
:color="useColor(amastery(answer.know))"
>
<div class="progress-con">
<div>{{ amastery(answer.know) }}%</div>
<div>掌握率</div>
</div>
</nut-circle-progress>
</div>
</div>
</template>
<Nodata v-else />
<div class="unit-answering" @click="handleLook">
<div class="unit-con">
<div class="answering-con">
<div class="unit-title">
<div>{{ cardInfo.title }}</div>
<div>{{ cardInfo.con }}</div>
</div>
<div class="unit-image"></div>
</div>
<div class="lookBtn">点击查看</div>
</div>
</div>
<dialog-overlay
:show="dialogCon.show"
:content="dialogCon.con"
:imgUrl="dialogCon.url"
:okText="dialogCon.okText"
@cancel="handleCancel"
@ok="handleSubmit"
>
<template #content>
<div class="overlay-con">{{ dialogCon.con }}</div>
</template>
</dialog-overlay>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import DialogOverlay from '/@/views/23-physical-questions/components/dialogOverlay.vue';
import tipsIcon from '/@/assets/images/questions/tips.png';
import { useColor, amastery } from './questionHooks';
import { homeListApi } from '/@/views/23-physical-questions/questionApI';
import { newPageParams, openPage } from '/@/hooks/openPage';
import Nodata from '/@/views/23-physical-questions/components/noData.vue';
const dialogCon = ref({
show: false,
url: tipsIcon,
con: '',
okText: '开始答题',
});
const progressInfo = ref({
width: '5',
radius: '36',
});
const cardInfo = ref({
title: '单位答题活动',
con: '单位组织答题活动,获取一定积分,赢得超大奖励',
});
const lists = ref([]);
const questDetail = ref({});
onMounted(() => {
getList();
});
function getList() {
homeListApi({}).then((res: any) => {
if (res.code === 200) {
lists.value = res.result;
}
});
}
// 打开弹窗
function handleStart(item: any) {
questDetail.value = item;
dialogCon.value.show = true;
dialogCon.value.con = `${item.questionSum}道题,答题时长为${item.answerTime}分钟,是否确认开始答题?`;
}
function handleCancel() {
dialogCon.value.show = false;
}
// 进入答题
function handleSubmit() {
dialogCon.value.show = false;
openPage(
'/physical-answer',
newPageParams({
id: questDetail.value.questionCategoryId,
answerTime: questDetail.value.answerTime,
affirmBack: 1,
})
);
}
// 单位答题活动
function handleLook() {
openPage(
'/physical-activity',
newPageParams({
startNewActivity: 2,
})
);
}
// 历史记录
function handleHistory(item: any) {
openPage(
'/physical-history',
newPageParams({
id: item.questionCategoryId,
})
);
}
</script>
<style scoped lang="less">
.answer-container {
width: 100%;
height: 100vh;
padding: 20px;
overflow-y: auto;
background-color: #f5f7fb;
.overlay-con{
text-align: center;
}
.answer-item {
background-color: #ffffff;
margin: 12px 0;
padding: 20px;
display: flex;
border-radius: 16px;
.answer-left {
width: 80%;
.left-title {
> div:nth-child(1) {
font-weight: bold;
color: #333333;
font-size: 16px;
}
> div:nth-child(2) {
color: #999999;
font-size: 14px;
padding-top: 3px;
}
}
.answer-btn {
display: flex;
padding-top: 10px;
.btn {
width: 73px;
height: 26px;
line-height: 26px;
text-align: center;
border-radius: 5px;
font-size: 13px;
}
.start {
color: #ffffff;
background-color: #1a68ee;
}
.history {
color: #1a68ee;
border: 1px solid #1a68ee;
margin-left: 15px;
}
}
}
.answer-progress {
width: 20%;
.progress-con {
font-size: 12px;
color: #333333;
}
}
}
.unit-answering {
width: 100%;
height: 140px;
border-radius: 16px;
background-color: #1a68ee;
display: flex;
align-items: center;
justify-content: center;
.unit-con {
width: 96%;
height: 98%;
border-radius: 16px;
background-color: #ffffff;
padding: 20px;
.answering-con {
display: flex;
.unit-title {
flex: 1;
> div:nth-child(1) {
font-size: 16px;
font-weight: bold;
color: #333333;
}
> div:nth-child(2) {
font-size: 14px;
color: #999999;
padding-top: 2px;
}
}
.unit-image {
margin-top: 20px;
width: 61px;
height: 61px;
background: url('/@/assets/images/questions/answer.png') no-repeat;
background-size: contain;
}
}
.lookBtn {
font-size: 14px;
font-weight: 500;
color: #1a68ee;
}
}
}
}
</style>
@@ -0,0 +1,41 @@
import { get, post } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
// C01 体检答题 、 C02 癌症答题、 C03 急救培训
const platform = sessionStorage.getItem('platform') !== null ? sessionStorage.getItem('platform') : 'C01';
//答题列表
export const homeListApi = (params: any) => get(`${prefix}/user-exam/home`, params, platform);
// 获取随机答题数据
export const questionListApi = (params: any) => get(`${prefix}/user-exam/answer`, params, platform);
// 获取急救培训答题
export const firstAidListApi = (params: any) => get(`${prefix}/app/angiography/activity/start/answer`, params, platform);
// 答题详情
export const questionDetailApi = (params: any) => get(`${prefix}/user-exam/answer/details`, params, platform);
// 随机提交答题
export const submitApi = (params: any) => post(`${prefix}/user-exam/submit`, params, platform);
// 历史列表
export const historyListApi = (params: any) => get(`${prefix}/user-exam/answer/page`, params, platform);
// 活动分类列表
export const activieListApi = (params: any) => get(`${prefix}/user-plan/app/list`, params, platform);
// 活动分类详情
export const activieDetailApi = (params: any) => get(`${prefix}/user-plan`, params, platform);
// 排行人员列表
export const rankeListApi = (params: any) => get(`${prefix}/user-plan/app/api/join_num`, params, platform);
// 获取活动答题数据
export const activieQuestionListApi = (params: any) => get(`${prefix}/user-plan/answer`, params, platform);
// 活动提交答题
export const activieSubmitApi = (params: any) => post(`${prefix}/user-plan/submit`, params, platform);
// 用户报名
export const registrationApi = (params: any) => get(`${prefix}/user-plan/apply`, params, platform);
@@ -0,0 +1,184 @@
import { computed, onBeforeUnmount, ref, watch } from 'vue';
export function useStatus(type: string) {
const tagType = [
{
class: '',
type: '0',
name: '',
},
{
class: 'progress-status',
type: '1',
name: '进行中',
},
{
class: 'end-status',
type: '2',
name: '已结束',
},
];
const isFind = tagType.find((item) => item.type === type);
return isFind ? isFind : tagType[0];
}
// 排名图标样式
export function useRanke(rankeIndex: number) {
const rankeList = ['ranke-one', 'ranke-two', 'ranke-three'];
// @ts-ignore
const backIndex = rankeList.findIndex((item, index) => index === rankeIndex);
return rankeList[backIndex];
}
export function usePageAnswer(list: never, type: string, currentIndex: number) {
const answer: any = {
answer_item: {},
c_index: 0,
};
// @ts-ignore
const findIndex = list.findIndex((e: any, findex: number) => findex === currentIndex);
if (type === 'last') {
answer.answer_item = list[findIndex - 1];
answer.answer_item['index'] = findIndex - 1;
answer.c_index = currentIndex - 1;
} else {
answer.answer_item = list[findIndex + 1];
answer.answer_item['index'] = findIndex + 1;
answer.c_index = currentIndex + 1;
}
return answer;
}
/**
*
* @param type last 上一题 next 下一题
* @param currentNo 当前选中题下标
* @param isEdit true 答题 false 回顾
* @param list 全部题目
*/
export function useSheetButton(type: number, currentNo: number, isEdit: boolean, list: Array<any>) {
const params = {
class: '',
ifShow: true,
};
switch (type) {
case 0:
if (currentNo === 0) {
params.class = 'disable';
} else if (currentNo + 1 <= list.length) {
params.class = 'last';
}
break;
case 1:
params.ifShow = isEdit && currentNo + 1 === list.length;
break;
case 2:
if (isEdit) {
params.ifShow = currentNo + 1 < list.length;
} else {
params.ifShow = currentNo + 1 <= list.length;
}
params.class = currentNo + 1 < list.length ? 'next' : 'disable';
break;
}
return params;
}
// 正确率字体颜色
export function useColor(know: any) {
const rate = Number(know);
if (rate < 60) {
return '#E60707';
} else if (rate >= 60 && rate < 75) {
return '#FE8700';
} else if (rate >= 75) {
return '#66C706';
}
}
// 秒转为时、分、秒
export function useConvertTime(seconds: any) {
if (seconds === null || seconds === undefined) {
return 0;
}
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
let text = '';
if (hours !== 0) {
isCountDigits(hours) ? (text += hours + ':') : (text += '0' + hours + ':');
}
isCountDigits(minutes) ? (text += minutes + ':') : (text += '0' + minutes + ':');
isCountDigits(secs) ? (text += secs) : (text += '0' + secs);
return text;
}
function isCountDigits(val: number) {
return val.toString().length > 1;
}
// 时分转换为秒
export function convertTimeToSeconds(time: string) {
const [minutes, seconds] = time.split(':');
return parseInt(minutes, 10) * 60 + parseInt(seconds, 10);
}
//分钟为秒
export function minuteConverSeconds(val: number) {
return val * 60;
}
// 截取年月日
export function timeSplit(val: string) {
return !val ? '' : val.length > 10 ? val.substr(0, 10) : val;
}
// 小数转百分率
export function amastery(val: number) {
let rate = 0;
if (val > 0) {
rate = Math.round(val * 100);
}
return rate;
}
/**
* @params duration 时长
*/
export function useCountdown(duration: number) {
// 分钟转为秒
const timer = ref(minuteConverSeconds(duration));
const isTimerRunning = ref(false);
// 格式化时间
const formatTime = computed(() => {
const minutes = Math.floor(timer.value / 60);
const seconds = timer.value % 60;
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
});
// 计时器逻辑
let interval: any = null;
const startTimer = () => {
isTimerRunning.value = true;
interval = setInterval(() => {
timer.value--;
}, 1000);
};
const stopTimer = () => {
isTimerRunning.value = false;
// timer.value = 0;
clearInterval(interval);
};
// 监听计时器状态变化
watch(isTimerRunning, (newValue) => {
if (!newValue) {
clearInterval(interval);
}
});
// 在组件销毁前清除计时器
onBeforeUnmount(() => {
clearInterval(interval);
});
return {
formatTime,
startTimer,
stopTimer,
};
}
@@ -0,0 +1,466 @@
<template>
<div class="traceability">
<div class="header">
<div class="header-font">
<span>{{ type.title }}</span>
<span>{{ type.sub }}</span>
</div>
<img :src="headerIcon" />
</div>
<div class="container">
<div class="question-box" v-for="(quesItem, index) in questionList" :key="index">
<div class="question-header">
<div>{{ quesItem.name }}</div>
<div v-if="qustType === '1'" @click="handleLookRes(quesItem)">查看最新检查结果</div>
</div>
<template v-for="(qus, quIndex) in quesItem.question">
<div class="question-title" v-if="qus.isShow" :key="quIndex">
<div class="title">
<span class="tips">*</span>
<div>
<span>{{ quIndex + 1 }}</span>
<span>{{ qus.title }}</span>
</div>
</div>
<template v-if="qus.inputType === 'radio'">
<a-radio-group v-model:value="qus.answer" @change="(e) => changeRadio(index, e, qus.type)">
<a-radio class="radioStyle" v-for="(optionItem, indexItem) in qus.options" :key="indexItem" :value="optionItem.value">
{{ optionItem.name }}</a-radio
>
</a-radio-group>
</template>
<template v-if="qus.inputType === 'text'">
<div class="error-box">
<div class="error-item" v-for="(optionItem, optionIndex) in qus.options" :key="optionIndex">
<van-field class="field-input" v-model="optionItem.remark" :label="optionItem.sizeMsg" />
<van-field class="field-input" v-model="optionItem.position" :label="optionItem.positionMsg" />
<van-icon
v-if="optionIndex !== 0"
class="deleteIcon"
size="26"
name="clear"
color="#666666"
@click="deleteICon(index, optionIndex)"
/>
</div>
</div>
<div class="error-btn" @click="addInfo(index, qus.options, qus.type)">添加斑块信息</div>
</template>
<template v-if="qus.inputType === 'input'">
<van-field type="number" class="field-answer" v-model="qus.answer" />
</template>
</div>
</template>
</div>
</div>
<div class="bottom-btn">
<van-button class="btn" round type="primary" @click="handleSubmit">提交</van-button>
</div>
<van-overlay :show="popupShow">
<div class="dialog">
<img :src="resultIcon" />
<div class="content-overlay">
<div class="popup-title">{{ detailRes?.name }}</div>
<div v-if="detailRes?.conclusion !== ''" class="popup-con">{{ detailRes?.conclusion }}</div>
<div v-else class="no-date">暂无结果</div>
</div>
<van-icon class="closeIcon" name="close" color="#ffffff" size="36" @click="handleClose" />
</div>
</van-overlay>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import headerIcon from '/@/assets/images/traceability/headerImage.png';
import resultIcon from '/@/assets/images/traceability/resultIcon.png';
import { useTypeHeader } from '/@/views/23intervene/ImageTraceability/traceabilityHooks';
import { useRouter, useRoute } from 'vue-router';
import { showToast, showSuccessToast, showFailToast } from 'vant';
import {
getBehaviorSurveyApi,
getEnvironmentSurveyApi,
getSurveyApi,
submitBehaviorSurveyApi,
submitEnvironmentSurveyApi,
submitSurveyApi,
} from '/@/views/23intervene/ImageTraceability/tranceabilityApi';
import { destroyPage } from '/@/hooks/openPage';
import { useLoading } from '/@/utils/compUtils';
const questionList = ref([]);
const submitParams = ref({});
const popupShow = ref(false);
const router = useRouter();
const route = useRoute();
const detailRes = ref({});
const isEdit = ref(''); // 0 新增 1编辑
const qustType = route.query?.traceabType;
const type = useTypeHeader(qustType); // 1 影像溯源、2 行为溯源、 3环境溯源
const { loadingSpinner, loadingClose } = useLoading();
onMounted(() => {
useTypeApi(qustType).getApi();
});
function useTypeApi(type: number) {
const data = {
1: {
getApi: getSurvey,
subApi: subSurvey,
},
2: {
getApi: getBehavior,
subApi: subBehavior,
},
3: {
getApi: getEnvironment,
subApi: subEnvironment,
},
};
return data[type];
}
/**
*
* @param index
* @param val 0 不存在异常、否, 1 存在异常、是
* @param qustType 1 影像溯源、2 行为溯源、3环境溯源
*/
function changeRadio(index: number, val: number, field: string) {
let data = val.target.value;
let qusData = questionList.value;
submitParams.value[field] = data;
if (qustType === '1') {
qusData[index].question[1].isShow = data === 1;
if (data === 0) {
let optionsList = qusData[index].question[1].options;
if (optionsList.length > 1) {
optionsList.splice(1, optionsList.length - 1);
optionsList[0].size = '';
optionsList[0].position = '';
}
}
} else if (qustType === '2') {
let qusList = qusData[index].question;
if (qusList.length > 1) {
qusList.filter((v: any) => {
if (v.inputType === 'input') {
if (v.answer !== '') {
v.answer = '';
}
v.isShow = data === 1;
}
});
}
}
questionList.value = qusData;
}
function addInfo(index: number, data: Array) {
let dataParams = {
remark: '',
position: '',
sizeMsg: data[0].sizeMsg,
positionMsg: data[0].positionMsg,
};
questionList.value[index].question[1].options.push(dataParams);
}
function deleteICon(index: number, optionIndex: number) {
questionList.value[index].question[1].options.splice(optionIndex, 1);
}
function getParams() {
questionList.value.map((quItem: any) => {
quItem.question.map((item: any) => {
if (item.inputType === 'text' && item.isShow) {
submitParams.value[item.type] = item.options;
}
if (['radio', 'input'].includes(item.inputType) && item.isShow) {
submitParams.value[item.type] = item.answer;
}
});
});
return submitParams.value;
}
// 影像
async function getSurvey() {
const { code, result } = await getSurveyApi({});
getResult(code, result);
}
async function subSurvey() {
const { code, result, message } = await submitSurveyApi(getParams());
subResult(code, result, message);
}
// 行为
async function getBehavior() {
const { code, result } = await getBehaviorSurveyApi({});
getResult(code, result);
}
async function subBehavior() {
const { code, result, message } = await submitBehaviorSurveyApi(getParams());
subResult(code, result, message);
}
// 环境
async function getEnvironment() {
const { code, result } = await getEnvironmentSurveyApi({});
getResult(code, result);
}
async function subEnvironment() {
const { code, result, message } = await submitEnvironmentSurveyApi(getParams());
subResult(code, result, message);
}
function getResult(code: number, result: any) {
if (code === 200) {
isEdit.value = result.status;
questionList.value = result.questionList;
}
}
function subResult(code: number, result: any, message: string) {
if (code === 200) {
loadingClose();
showSuccessToast('提交成功');
try {
destroyPage();
} catch {
router.go(-1);
}
} else {
loadingClose();
showFailToast(message);
}
}
async function handleSubmit() {
let vis = true;
questionList.value.map((v: any) => {
v.question.map((qusItem: any) => {
if (qusItem.isShow && ['radio', 'input'].includes(qusItem.inputType)) {
if (qusItem.answer === '') {
vis = false;
showToast('请填写完问卷');
}
}
if (qusItem.isShow && qusItem.inputType === 'text') {
qusItem.options.map((e: any) => {
if (e.position === '' || e.remark === '') {
vis = false;
showToast('请填写异常信息');
}
});
}
});
});
if (vis) {
loadingSpinner();
useTypeApi(qustType).subApi();
}
}
async function handleLookRes(item: any) {
popupShow.value = true;
detailRes.value = item;
}
function handleClose() {
popupShow.value = false;
}
</script>
<style scoped lang="less">
.traceability {
width: 100%;
height: 100vh;
background-color: #f5f7fb;
position: relative;
.header {
z-index: 4;
width: 100%;
height: 140px;
position: relative;
img {
width: 100%;
height: 100%;
}
.header-font {
position: absolute;
left: 7%;
top: 22%;
display: flex;
flex-direction: column;
font-weight: bold;
font-style: italic;
span:nth-child(1) {
font-size: 30px;
color: #ffffff;
}
span:nth-child(2) {
margin-top: -12px;
color: rgba(255, 255, 255, 0.1);
font-size: 22px;
}
}
}
.container {
width: 100%;
height: calc(100vh - 180px);
overflow-y: auto;
position: absolute;
left: 0;
top: 125px;
z-index: 99;
.question-box {
margin-bottom: 18px;
border-radius: 16px;
background-color: #ffffff;
.question-header {
border-top-left-radius: 16px;
border-top-right-radius: 16px;
display: flex;
justify-content: space-between;
height: 43px;
padding: 20px;
background: linear-gradient(180deg, rgba(26, 104, 238, 0.2) 0%, rgba(48, 160, 226, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
> div:nth-child(1) {
font-size: 16px;
color: #333333;
font-weight: bold;
}
> div:nth-child(2) {
color: #1a68ee;
font-size: 14px;
}
}
.question-title {
padding: 20px;
.title {
color: #333b42;
font-weight: bold;
padding-bottom: 20px;
position: relative;
.tips {
color: #ed2a26;
font-size: 18px;
font-weight: bold;
position: absolute;
top: -2px;
left: -10px;
}
}
.radioStyle {
display: block;
height: 40px;
}
.error-item {
position: relative;
margin-top: 12px;
border-radius: 16px;
& > .field-input:nth-child(1) {
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
& > .field-input:nth-child(2) {
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
}
& > .deleteIcon {
position: absolute;
right: -10px;
top: -10px;
z-index: 9;
}
:deep(.van-field__control:-webkit-autofill) {
background-color: transparent;
padding-left: 10px;
transition: background-color 0s linear 3600s;
}
:deep(.van-field__control) {
padding-left: 10px;
}
:deep(.van-cell) {
background-color: #eff5ff;
line-height: 38px;
}
:deep(.van-field__label) {
font-weight: bold;
}
:deep(.van-cell__value) {
height: 38px;
background-color: #ffffff;
}
}
.error-btn {
text-align: center;
color: #1a68ee;
padding: 20px 0px;
}
.field-answer {
border-bottom: 1px solid #eaecf1;
}
}
}
}
.bottom-btn {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
height: 50px;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
.btn {
width: 80%;
height: 40px;
line-height: 40px;
background-color: #1a68ee;
border-radius: 16px;
color: #ffffff;
text-align: center;
}
}
:deep(.ant-radio-group) {
width: 100%;
}
:deep(.van-overlay){
z-index: 99;
}
.dialog {
position: absolute;
top: 30%;
left: 11%;
width: 80%;
margin: 0 auto;
img {
position: absolute;
top: -35px;
left: 43%;
width: 15%;
}
.content-overlay {
background-color: #ffffff;
border-radius: 16px;
padding: 40px 20px 20px 20px;
.popup-title {
font-size: 18px;
font-weight: bold;
padding-bottom: 10px;
}
.popup-con {
font-size: 15px;
line-height: 25px;
max-height: 120px;
overflow-y: auto;
text-align: left;
}
.no-date {
text-align: center;
color: #999999;
height: 120px;
line-height: 120px;
}
}
.closeIcon {
position: relative;
left: 45%;
padding-top: 10%;
}
}
}
</style>
@@ -0,0 +1,18 @@
export function useTypeHeader(type: number) {
const data = {
1: {
title: '影像溯源',
sub: 'IMAGE TRACEABILITY',
},
2: {
title: '行为溯源',
sub: 'BEHAVIOR TRACEABILITY',
},
3: {
title: '环境溯源',
sub: 'ENVIRONMENTAL TRACEABILITY',
},
};
// @ts-ignore
return data[type];
}
@@ -0,0 +1,21 @@
import { get, post } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
// 获取影像溯源
export const getSurveyApi = (params: any) => get(`${prefix}/cancer/api/getImageSurvey`, params);
// 提交影像溯源
export const submitSurveyApi = (params: any) => post(`${prefix}/cancer/api/postImageSurvey`, params);
// 获取行为溯源
export const getBehaviorSurveyApi = (params: any) => get(`${prefix}/cancer/api/getBehaviorSurvey`, params);
// 提交行为溯源
export const submitBehaviorSurveyApi = (params: any) => post(`${prefix}/cancer/api/postBehaviorSurvey`, params);
// 获取环境溯源
export const getEnvironmentSurveyApi = (params: any) => get(`${prefix}/cancer/api/getEnvironmentSurvey`, params);
// 提交环境溯源
export const submitEnvironmentSurveyApi = (params: any) => post(`${prefix}/cancer/api/postEnvironmentSurvey`, params);
@@ -0,0 +1,94 @@
<template>
<div class="vaccines-container">
<div v-for="(qus, qusIndex) in question" :key="qusIndex">
<template v-if="qus.isShow">
<div class="qus-title">
<span v-if="warnType === 0">{{ qus.indexNo }}</span>
<span>{{ qus.question }}</span>
</div>
<template v-if="qus.type === 0">
<van-radio-group v-model="qus.userAnswer" shape="dot" @change="(e) => handleRadios(qusIndex, e)">
<van-radio class="radioStyle" v-for="(opt, optIndex) in qus.optionList" :name="opt.optionCode" :key="optIndex">{{
opt.option
}}</van-radio>
</van-radio-group>
</template>
<template v-if="qus.type === 1">
<div class="time-box" @click="handleTimes(qus, qusIndex)">
<div class="time-answer" v-if="qus.userAnswer !== '' || qus.userAnswer !== null">{{ qus.userAnswer }}</div>
<div class="time-answer" v-else>{{ rangTime?.join('-') }}</div>
<div class="time-pla" v-if="qus.userAnswer === '' || (qus.userAnswer === null && rangTime)">请选择接种时间 </div>
<van-icon class="right-icon" color="#666666" name="arrow" />
</div>
<van-popup v-model:show="show" position="bottom">
<van-date-picker
v-model="rangTime"
title="选择日期"
:min-date="minDate"
:max-date="new Date()"
@cancel="show = false"
@confirm="onConfirmPicker"
/>
</van-popup>
</template>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits } from 'vue';
import { useRoute } from 'vue-router';
const props = defineProps({
qusIndex: Number,
question: Object,
});
const route = useRoute();
const warnType = parseInt(route.query.warnType); // 0 疫苗+其他题 、 1 更新疫苗、 2 更新其他题
const emit = defineEmits(['radios', 'picker']);
let show = ref(false);
let rangTime = ref([]);
let vanIndex = ref('');
let vanParmas = ref({});
let minDate = new Date(1990, 0, 1);
function handleRadios(index: number, value: string) {
emit('radios', props.qusIndex, index, value);
}
function onConfirmPicker(e: any) {
rangTime.value = e.selectedValues;
emit('picker', vanParmas.value.questionCode, vanIndex.value, rangTime.value);
show.value = false;
}
function handleTimes(vanData: any, val: number) {
vanIndex.value = val;
vanParmas.value = vanData;
show.value = true;
}
</script>
<style scoped lang="less">
.vaccines-container {
width: 100%;
.qus-title {
padding: 10px 0;
}
.radioStyle {
height: 30px;
}
.time-answer {
padding: 10px 0;
font-size: 15px;
color: #333333;
}
.right-icon {
padding-left: 6px;
}
.time-pla {
color: #666666;
}
.time-box {
display: flex;
align-items: center;
}
}
</style>
@@ -0,0 +1,318 @@
<template>
<div class="warning-container">
<div class="warn-con">
<div class="warn-header" v-if="warnType === 0">
<div class="header-font">
<span>免疫预警</span>
<span>IMMUNE WARNING</span>
</div>
<img :src="headerIcon" />
</div>
<div :class="warnType === 0 ? 'warn-box' : 'vancc-box'">
<div class="vancc-title" v-if="warnType === 1">
<div>疫苗信息</div>
<div>请填写疫苗接种信息评估您的细菌·病毒预警情况</div>
</div>
<div class="ques-item" v-for="(qus, qusIndex) in quesList" :key="qusIndex">
<div :class="warnType === 0 ? '' : 'vancc'" v-if="qus.hasChildren && [0, 1].includes(warnType)">
<div class="qus-title">{{ qus.question }}</div>
<Vaccines
v-if="qus.hasChildren"
:question="qus.childrenQuestions"
:qusIndex="qusIndex"
@radios="changeRadios"
@picker="handlePicker"
/>
</div>
<template v-if="qus.isShow && [0, 2].includes(warnType) && !qus.hasChildren">
<div class="qus-title">{{ qus.indexNo }}{{ qus.question }}</div>
<van-radio-group v-model="qus.userAnswer" shape="dot">
<van-radio class="radioStyle" v-for="(opt, optIndex) in qus.optionList" :name="opt.optionCode" :key="optIndex">{{
opt.option
}}</van-radio>
</van-radio-group>
</template>
</div>
</div>
</div>
<div class="bottom-btn">
<van-button class="btn" round type="primary" @click="handleSubmit">提交</van-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getQuestionList, setQuestion } from '/@/views/23intervene/ImmuneWarning/warningApi.ts';
import Vaccines from '/@/views/23intervene/ImmuneWarning/Vaccines.vue';
import { useRouter, useRoute } from 'vue-router';
import headerIcon from '/@/assets/images/traceability/headerImage.png';
import { showSuccessToast, showFailToast } from 'vant';
import { destroyPage } from '/@/hooks/openPage';
import { useLoading } from '/@/utils/compUtils';
const route = useRoute();
const router = useRouter();
let quesList = ref([]);
const warnType = parseInt(route.query.warnType); // 0 疫苗+其他题 、 1 更新疫苗、 2 更新其他题
const { loadingSpinner, loadingClose } = useLoading();
const subParams = ref({
optionList: [],
submitType: '',
surveyCode: '',
});
interface quesItem {
hasChildren: Boolean;
childrenQuestions: Array;
questionCode: String;
optionList: Array;
isShow: Boolean;
meaning: String;
userAnswer: String;
type: Number;
}
onMounted(() => {
getList();
});
function setListIndex(data: any) {
let indexNo = 0;
const list = data.map((item: any) => {
if (item.hasChildren) {
indexNo = 0;
item.childrenQuestions.map((childrenItem: any) => {
if (childrenItem.isShow) {
indexNo++;
childrenItem['indexNo'] = indexNo;
}
});
} else {
if (item.isShow) {
indexNo++;
item['indexNo'] = indexNo;
}
}
return item;
});
return list;
}
function setShowQuestion(vanList: any) {
vanList.map((data: any) => {
if (data.hasChildren) {
const user_answer = data.childrenQuestions[0].userAnswer;
if (['10', '12', '11'].includes(data.questionCode)) {
data.childrenQuestions.map((item: any) => {
if (user_answer !== null || user_answer !== '') {
if (['10.1', '12.1'].includes(item.questionCode)) {
data.childrenQuestions[1].isShow = user_answer === 'A';
}
if (item.questionCode === '11.1') {
const showNo = [1, 2, 3];
showNo.map((itemNo: number) => {
data.childrenQuestions[itemNo].isShow = user_answer === 'A';
});
}
}
});
}
}
});
return vanList;
}
async function getList() {
const { code, result } = await getQuestionList({});
if (code === 200) {
let data_index = await setShowQuestion(result.questionList);
quesList.value = await setListIndex(data_index);
subParams.value.surveyCode = result.surveyCode;
}
}
/**
*
* @param qusIndex
* @param childIndex 子集下标
* @param value 单选题值
*/
function changeRadios(qusIndex: number, childIndex: number, value: string) {
let new_quesList: quesItem[] = quesList.value;
if (new_quesList[qusIndex].hasChildren) {
let childList = new_quesList[qusIndex].childrenQuestions;
if (['10.1', '12.1'].includes(childList[childIndex].questionCode)) {
if (value === 'B') {
childList[1].userAnswer = '';
}
childList[1].isShow = value === 'A';
}
if (childList[childIndex].questionCode === '11.1') {
const showNo = [1, 2, 3];
showNo.map((itemNo: number) => {
childList[itemNo].isShow = value === 'A';
if (value === 'B') {
childList[itemNo].userAnswer = '';
}
});
}
}
quesList.value = setListIndex(new_quesList);
}
/**
*
* @param questionCode 编号
* @param childIndex 子集下标
* @param value 时间
*/
function handlePicker(questionCode: string, childIndex: number, value: string) {
let new_quesList: quesItem[] = quesList.value;
new_quesList.map((item: any) => {
if (item.hasChildren && item.childrenQuestions !== null) {
const chiCode = item.childrenQuestions[childIndex];
if (chiCode && questionCode === chiCode.questionCode) {
item.childrenQuestions[childIndex].userAnswer = value.join('-');
}
}
});
quesList.value = setListIndex(new_quesList);
}
function getParams() {
let list: quesItem[] = [];
quesList.value.map((item: any) => {
if (item.hasChildren) {
item.childrenQuestions.map((childrenItem: any) => {
if ([0, 1].includes(warnType)) {
list.push(answerSame(childrenItem));
}
});
} else {
if ([0, 2].includes(warnType)) {
list.push(answerSame(item));
}
}
});
subParams.value.optionList = list;
subParams.value.submitType = warnType;
return subParams.value;
}
function answerSame(item: quesItem) {
let paramsItem = {
questionCode: item.questionCode,
userOptionCode: item.userAnswer,
};
if (item.isShow) {
if (item.type === 0) {
let meanFind = item.optionList.find((e: any) => e.optionCode === item.userAnswer);
paramsItem.meaning = meanFind.meaning;
} else {
delete paramsItem.meaning;
}
}
return paramsItem;
}
async function handleSubmit() {
loadingSpinner();
const { code, result, message } = await setQuestion(getParams());
if (code === 200) {
loadingClose();
showSuccessToast('提交成功');
try {
destroyPage();
} catch {
router.go(-1);
}
} else {
loadingClose();
showFailToast(message);
}
}
</script>
<style scoped lang="less">
.warning-container {
background-color: #f5f7fb;
.warn-con {
width: 100%;
.warn-header {
z-index: 4;
width: 100%;
height: 140px;
position: relative;
img {
width: 100%;
height: 100%;
}
.header-font {
position: absolute;
left: 7%;
top: 22%;
display: flex;
flex-direction: column;
font-weight: bold;
font-style: italic;
span:nth-child(1) {
font-size: 30px;
color: #ffffff;
}
span:nth-child(2) {
margin-top: -12px;
color: rgba(255, 255, 255, 0.1);
font-size: 22px;
}
}
}
.warn-box {
height: calc(100vh - 190px);
overflow-y: auto;
padding: 10px 20px;
}
.vancc-box {
background-color: #fff;
border-radius: 16px;
padding: 20px;
height: calc(100vh - 60px);
overflow-y: auto;
.vancc-title {
div:nth-child(1) {
font-weight: bold;
font-size: 16px;
}
div:nth-child(2) {
padding: 4px 0;
color: #999999;
}
}
}
.ques-item {
width: 100%;
.vancc {
padding: 10px 12px;
background-color: #f1f1f1;
border-radius: 16px;
margin-top: 10px;
}
.qus-title {
padding: 10px 0;
font-weight: bold;
}
.radioStyle {
height: 30px;
}
}
}
.bottom-btn {
width: 100%;
height: 50px;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
.btn {
width: 80%;
height: 40px;
line-height: 40px;
background-color: #1a68ee;
border-radius: 16px;
color: #ffffff;
text-align: center;
}
}
}
</style>
@@ -0,0 +1,7 @@
import { get, post } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
export const getQuestionList = (params: any) => get(`${prefix}/cancer/immune/survey/info`, params);
export const setQuestion = (params: any) => post(`${prefix}/cancer/immune/survey/submit`, params);
@@ -0,0 +1,3 @@
export function changeRadio(){
}
@@ -0,0 +1,374 @@
<template>
<div class="home-perediction">
<div class="container" v-if="type === 1">
<van-collapse v-model="activeNames" ref="collapse">
<div class="con-box" v-for="(item, index) in tableData" :key="index">
<van-collapse-item>
<template #title>
<div class="header">
<span class="name">{{ item.name }}</span>
<span @click="checkStatus(item.vis, index)">
{{ item.vis ? '收起' : '展开' }}
</span>
</div>
</template>
<a-table
v-if="item.list.length > 0"
:columns="index === 1 ? columns1 : columns"
:data-source="item.list"
:pagination="false"
/>
<Nodata v-else />
</van-collapse-item>
</div>
</van-collapse>
<div class="cancer-box">
<div class="header">
<span class="name">癌症信号预判</span>
<span class="info" @click="goPeredic">修改问卷信息</span>
</div>
<div class="cancer-con" v-if="cancerList.length > 0">
<div
class="cance-width"
v-for="(canItem, canIndex) in cancerList"
:key="canIndex"
:style="{ padding: canIndex % 2 === 0 ? '0 10px 10px 0' : '0 0 10px 0' }"
>
<div :class="['cancer-item', canItem.classBg]">
<div class="icon"></div>
<div class="title">{{ canItem.name }}</div>
<div class="grade" v-if="canItem.grade !== null">{{ canItem.grade }}</div>
<div v-else class="tips">缺少必要指标无法计算</div>
</div>
</div>
<div class="cance-tips">
<span class="con">存在未体检的指标数据如需正常计算相关癌症风险请前往做相关检查 </span>
<span class="tipsLook" @click="handleTips">查看指标</span>
</div>
</div>
<Nodata v-else />
<div v-if="isAbnormal !== ''" class="bottom-btn" @click="handleWarn">干预提示</div>
</div>
</div>
<div class="no-date" v-else>
<img :src="home" />
<div class="tips">请先进行信号预判问卷填写</div>
<div class="bottom-btn" @click="goPeredic">去填写</div>
</div>
<dialog-overlay
:show="dialogCon.show"
:content="dialogCon.con"
:imgUrl="dialogCon.url"
:okText="dialogCon.okText"
:cancelBtn="dialogCon.canBtn"
@ok="handleOk"
>
<template #content>
<div class="overlay-container">
<template v-if="overlayType === '1'">
<div class="title">癌症风险计算所需体检指标</div>
<div class="overlay-con">
<div class="con-item" v-for="(item, index) in overlayList" :key="index"
><span>{{ index + 1 }}</span
><span>{{ item.name }}</span></div
>
</div>
</template>
<template v-if="overlayType === '2'">
<span v-if="isAbnormal === 0" style="color: #52c41a">您的癌症信号正常请继续保持!</span>
<span v-else style="color: #ed2a26">您的癌症信号异常请及时就诊</span>
<!-- {{ // isAbnormal === 0 ? '您的癌症信号正常请继续保持!' : '您的癌症信号异常请及时就诊!' }}-->
</template>
</div>
</template>
</dialog-overlay>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue';
import home from '/@/assets/images/traceability/homePrediction.png';
import signal from '/@/assets/images/traceability/no-signal.png';
import warnIcon from '/@/assets/images/questions/warn.png';
import dialogOverlay from '/@/views/23-physical-questions/components/dialogOverlay.vue';
import Nodata from '/@/views/23-physical-questions/components/noData.vue';
import { getHomeListApi } from '/@/views/23intervene/signalPrediction/predictionApi.ts';
import { columns, columns1 } from '/@/views/23intervene/signalPrediction/signalHooks.ts';
import { newPageParams, openPage } from '/@/hooks/openPage';
const type = ref(0);
const activeNames = ref(['1']);
const collapse = ref(null);
const tableData = ref([
{
name: '表象信号',
list: [],
vis: true,
},
{
name: '检验信号',
list: [],
vis: true,
},
{
name: '检查信号',
list: [],
vis: true,
},
]);
const overlayType = ref('');
const cancerList = ref([]);
const overlayList = ref([]);
const dialogCon = ref({
show: false,
url: warnIcon,
con: '',
canBtn: false,
okText: '我知道了',
});
const isAbnormal = ref(''); // 0 正常、 1 异常
onMounted(() => {
getList();
});
async function getList() {
const { code, result } = await getHomeListApi({});
if (code === 200) {
if (result.appearanceList !== null && result.checkList !== null) {
type.value = 1;
nextTick(() => {
collapse.value.toggleAll(true);
});
}
tableData.value[0].list = result.appearanceList !== null ? result.appearanceList : [];
tableData.value[1].list = result.inspectionList !== null ? result.inspectionList : [];
tableData.value[2].list = result.checkList !== null ? result.checkList : [];
isAbnormal.value = result.isAbnormal;
if (result.cancerList !== null) {
const barColor = {
1: 'lowBg',
2: 'centerBg',
3: 'highBg',
};
result.cancerList.map((v: any) => {
v['classBg'] = v.code !== null ? barColor[v.code] : '';
});
}
cancerList.value = result.cancerList !== null ? result.cancerList : [];
}
}
function handleWarn() {
dialogCon.value.show = true;
// dialogCon.value.con = isAbnormal.value === 0 ? '您的癌症信号正常请继续保持!' : '您的癌症信号异常请及时就诊!';
overlayType.value = '2';
}
function handleTips() {
dialogCon.value.show = true;
// dialogCon.value.con = '';
overlayType.value = '1';
let data = tableData.value[1].list;
overlayList.value = data.filter((v: any) => {
return v.result === null;
});
}
function goPeredic() {
openPage('/signal-prediction', newPageParams({}));
}
function checkStatus(status: Boolean, index: Number) {
tableData.value[index].vis = !status;
}
function handleOk() {
dialogCon.value.show = false;
}
</script>
<style scoped lang="less">
.home-perediction {
height: 100%;
background-color: #f5f7fb;
overflow-y: auto;
:deep(.ant-table-thead > tr > th) {
background: rgba(26, 104, 238, 0.2);
}
:deep(.ant-table-tbody > tr > td) {
border: 1px solid #f0f0f0;
}
.container {
.con-box {
background-color: #ffffff;
margin-top: 20px;
}
.header {
display: flex;
justify-content: space-between;
.name {
font-weight: bold;
font-size: 16px;
}
.info {
color: #1a68ee;
position: relative;
padding-left: 20px;
&::before {
content: '';
position: absolute;
left: 0;
top: 5px;
width: 12px;
height: 12px;
background: url('/@/assets/images/traceability/edit.png') no-repeat;
background-size: 100% 100%;
}
}
}
.min-height {
max-height: 0;
transition: max-height 0.5s ease-out;
overflow: hidden;
}
.max-height {
transition: max-height 0.5s ease-out;
max-height: 600px;
}
.cancer-box {
margin-top: 20px;
background-color: #ffffff;
padding: 20px;
.cancer-con {
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: center;
padding-top: 20px;
margin-bottom: 20px;
.cance-width {
width: 50%;
.cancer-item {
width: 100%;
height: 110px;
border-radius: 10px;
background: url('/@/assets/images/traceability/defaultBg.png') no-repeat;
background-size: 100% 100%;
position: relative;
.title {
padding: 16px;
font-size: 16px;
color: #333333;
font-weight: bold;
}
.grade {
padding-left: 16px;
}
.tips {
padding: 0 16px;
color: #999999;
}
.icon {
position: absolute;
right: 16px;
top: 16px;
width: 22px;
height: 18px;
}
}
.lowBg {
background: url('/@/assets/images/traceability/lowBg.png') no-repeat;
background-size: 100% 100%;
.grade {
color: #52c41a;
}
}
.centerBg {
background: url('/@/assets/images/traceability/centerBg.png') no-repeat;
background-size: 100% 100%;
.grade {
color: #ff7421;
}
.icon {
background: url('/@/assets/images/traceability/centerIcon.png') no-repeat;
background-size: 100% 100%;
}
}
.highBg {
background: url('/@/assets/images/traceability/highBg.png') no-repeat;
background-size: 100% 100%;
.grade {
color: #ed2a26;
}
.icon {
background: url('/@/assets/images/traceability/highBgIcon.png') no-repeat;
background-size: 100% 100%;
}
}
}
.cance-tips {
padding: 20px 0;
.con {
color: #999999;
}
.tipsLook {
color: #1a68ee;
}
}
}
}
}
.no-date {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 55%;
img {
width: 46%;
}
.tips {
font-size: 16px;
padding: 10px 0 20px 0;
color: #666666;
}
}
.bottom-btn {
width: 28%;
height: 38px;
line-height: 38px;
background-color: #1a68ee;
color: #ffffff;
text-align: center;
border-radius: 30px;
margin: 0 auto;
}
.overlay-container {
.title {
color: #333333;
font-size: 18px;
font-weight: bold;
padding-bottom: 10px;
}
.overlay-con {
max-height: 200px;
overflow-y: auto;
.con-item {
padding: 8px 0;
display: flex;
align-items: center;
span:nth-child(1) {
display: inline-block;
width: 22px;
height: 22px;
line-height: 22px;
text-align: center;
border: 1px solid #bdbdbd;
border-radius: 50%;
font-size: 16px;
color: #b6b6b6;
}
span:nth-child(2) {
padding-left: 10px;
width: 90%;
display: inline-block;
text-decoration: underline 4px rgba(26, 104, 238, 0.4);
}
}
}
}
}
</style>
@@ -0,0 +1,277 @@
<template>
<div class="prediction-container">
<div class="prediction-header">
<div class="header-font">
<span>信号预判</span>
<span>SIGNAL PREDICTION</span>
</div>
<img :src="headerIcon" />
</div>
<div class="prediction-con">
<div class="con-title"></div>
<div class="container">
<div class="question-box" v-for="(quesItem, index) in questionList" :key="index">
<template v-for="(qus, quIndex) in quesItem.question">
<div class="question-title" v-if="qus.isShow" :key="quIndex">
<div class="title">
<span class="tips">*</span>
<div>
<span>{{ qus.index_type }}</span>
<span>{{ qus.title }}</span>
</div>
</div>
<template v-if="qus.inputType === 'radio'">
<a-radio-group v-model:value="qus.answer" @change="(e) => changeRadio(index, e, qus.type, qus.options)">
<a-radio
class="radioStyle"
v-for="(optionItem, indexItem) in qus.options"
:key="indexItem"
:value="optionItem.value"
>
{{ optionItem.name }}</a-radio
>
</a-radio-group>
</template>
<template v-if="qus.inputType === 'input'">
<div class="input-box">
<div class="field-answer">
<van-field type="number" v-model="qus.answer" @blur="changeInpput(qus.answer, index, quIndex)" />
</div>
<div class="input-unit">{{ qus.unit }}</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
<div class="bottom-btn">
<van-button class="btn" round type="primary" @click="handleSubmit">提交</van-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import headerIcon from '/@/assets/images/traceability/headerImage.png';
import { getSignalApi, postSignalApi } from '/@/views/23intervene/signalPrediction/predictionApi.ts';
import { showFailToast, showSuccessToast, showToast, showLoadingToast } from 'vant';
import { destroyPage } from '/@/hooks/openPage';
import {useLoading} from '/@/utils/compUtils';
import { useRouter } from 'vue-router';
let questionList = ref({});
let submitParams = ref({});
const router = useRouter();
const { loadingSpinner, loadingClose } = useLoading();
getList();
async function getList() {
const { code, result } = await getSignalApi({});
if (code === 200) {
questionList.value = changeIndex(result.questionList);
}
}
/**
* 添加自定义问卷序号
*/
function changeIndex(data: Array) {
let indexInit = 0;
const list = data.map((v: any) => {
v.question.map((item: any) => {
if (item.isShow) {
indexInit++;
item['index_type'] = indexInit;
}
});
return v;
});
return list;
}
/**
* radio 跳题,给提交对象赋值
* @param index
* @param value radio 选中值
* @param field 提交时所需参数
* @param options 选中题的选项
*/
async function changeRadio(index: number, value: number, field: string, options: object) {
let datas = questionList.value;
let radioVal = value.target.value;
let optionItem = options.find((e: { value: any }) => e.value === 1);
if (optionItem.showCode !== '') {
let codeList = optionItem.showCode.split(',');
codeList.map((code: any) => {
datas.map((item: any) => {
if (code == item.questionCode) {
if (item.question[0].answer !== '') {
item.question[0].answer = '';
}
item.question[0].isShow = radioVal === 1;
}
});
});
}
submitParams.value[field] = radioVal;
questionList.value = changeIndex(datas);
}
// 限制input输入负数
function changeInpput(value: string, index: number, qusIndex: number) {
if (Number(value) < 0) {
showToast('不能输入负数');
questionList.value[index].question[qusIndex].answer = '';
}
}
function getParams() {
questionList.value.map((quItem: any) => {
quItem.question.map((item: any) => {
if (['radio', 'input'].includes(item.inputType) && item.isShow) {
submitParams.value[item.type] = item.answer;
}
});
});
return submitParams.value;
}
async function handleSubmit() {
let vis = true;
questionList.value.map((v: any) => {
v.question.map((qusItem: any) => {
if (qusItem.isShow && ['radio', 'input'].includes(qusItem.inputType)) {
if (qusItem.answer === '') {
vis = false;
showToast('请填写完问卷');
}
}
});
});
if (vis) {
loadingSpinner();
const { code, result, message } = await postSignalApi(getParams());
if (code === 200) {
loadingClose();
showSuccessToast('提交成功');
try {
destroyPage();
} catch {
router.go(-1);
}
} else {
loadingClose();
showFailToast(message);
}
}
}
</script>
<style scoped lang="less">
.prediction-container {
width: 100%;
height: 100vh;
background-color: #f5f7fb;
position: relative;
.prediction-header {
z-index: 4;
width: 100%;
height: 140px;
position: relative;
img {
width: 100%;
height: 100%;
}
.header-font {
position: absolute;
left: 7%;
top: 22%;
display: flex;
flex-direction: column;
font-weight: bold;
font-style: italic;
span:nth-child(1) {
font-size: 30px;
color: #ffffff;
}
span:nth-child(2) {
margin-top: -12px;
color: rgba(255, 255, 255, 0.1);
font-size: 22px;
}
}
}
.prediction-con {
width: 100%;
position: absolute;
left: 0;
top: 125px;
z-index: 99;
border-radius: 16px;
background-color: #ffffff;
.con-title {
width: 100%;
height: 34px;
background: linear-gradient(180deg, rgba(26, 104, 238, 0.2) 0%, rgba(48, 160, 226, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
}
.container {
width: 100%;
height: calc(100vh - 220px);
overflow-y: auto;
.question-box {
.question-title {
padding: 10px 20px;
.title {
color: #333b42;
font-weight: bold;
padding-bottom: 10px;
position: relative;
.tips {
color: #ed2a26;
font-size: 18px;
font-weight: bold;
position: absolute;
top: -2px;
left: -10px;
}
}
.radioStyle {
display: block;
height: 32px;
}
.input-box {
display: flex;
align-items: center;
.field-answer {
width: 40%;
}
:deep(.van-cell) {
border: 1px solid #e6e6ea;
border-radius: 4px;
}
.input-unit {
width: 20%;
padding-left: 10px;
color: #333333;
}
}
}
}
}
}
.bottom-btn {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
height: 50px;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
.btn {
width: 80%;
height: 40px;
line-height: 40px;
background-color: #1a68ee;
border-radius: 16px;
color: #ffffff;
text-align: center;
}
}
}
</style>
@@ -0,0 +1,9 @@
import { get, post } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
export const getHomeListApi = (params: any) => get(`${prefix}/cancer/signal/app/index`, params);
export const getSignalApi = (params: any) => get(`${prefix}/cancer/signal/app/getSurvey`, params);
export const postSignalApi = (params: any) => post(`${prefix}/cancer/signal/app/postSurvey`, params);
@@ -0,0 +1,70 @@
import { h } from 'vue';
function resColor(data: any) {
let color = '';
if (data.resultText !== null) {
color = data.isError === 1 ? 'red' : '';
}
return h('span', { style: { color } }, data.resultText !== null ? data.resultText : '-');
}
// @ts-ignore
export const columns = [
{
title: '信号名称',
dataIndex: 'name',
width: 110,
},
{
title: '结果',
dataIndex: 'result',
width: 60,
customRender: ({ record }) => {
return resColor(record);
},
},
{
title: '单位',
dataIndex: 'unit',
width: 60,
},
{
title: '更新时间',
dataIndex: 'updateTime',
width: 110,
customRender: ({ record }) => {
return record.updateTime !== null ? record.updateTime : '-';
},
},
];
export const columns1 = [
{
title: '指标名称',
dataIndex: 'name',
width: 80,
},
{
title: '结果',
dataIndex: 'result',
width: 70,
customRender: ({ record }) => {
return resColor(record);
},
},
{
title: '单位',
dataIndex: 'unit',
width: 50,
},
{
title: '参考范围',
dataIndex: 'referenceRange',
width: 80,
},
{
title: '更新时间',
dataIndex: 'updateTime',
width: 110,
customRender: ({ record }) => {
return record.updateTime !== null ? record.updateTime : '-';
},
},
];
@@ -0,0 +1,272 @@
<template>
<div class="outer-d">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<div style="height: 100%; overflow: auto">
<template v-if="list.length > 0">
<van-list
v-model:loading="loading"
:finished="finished"
@load="onLoad"
:finished-text="list.length > 0 ? '没有更多了' : ''"
v-model:error="error"
error-text="请求失败点击重新加载"
>
<template v-for="it in vellArray" :key="`collapse${it}`">
<van-collapse v-model="activeNames">
<van-collapse-item :title="it" :name="it">
<template v-for="item in list" :key="item">
<div
class="van-collapse-item-d"
v-if="moment(item.endAnswerTime).format('YYYY').indexOf(it) !== -1"
:title="item"
@click="toReports(item)"
>
<div> 评估时间:{{ moment(item.endAnswerTime).format('YYYY-MM-DD HH:mm:ss') }} </div>
<table>
<tr>
<th class="table-left">主题</th>
<th class="table-right">模块</th>
</tr>
<tr v-for="(t, index) in item.categoryList || []" :key="`category${index}`" class="tbody-tr">
<td class="table-left" v-if="count[index] != 0" :rowspan="count[index]">{{
t.modelName || '-'
}}</td>
<td class="table-right">{{ t.questionCategory || '-' }}</td>
</tr>
</table>
</div>
</template>
</van-collapse-item>
</van-collapse>
</template>
</van-list>
</template>
<div v-if="list.length === 0 && finished" class="empty-d">
<!-- <van-empty description="暂无数据" />-->
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
</div>
</van-pull-refresh>
<van-back-top />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { getAnswerPageApi } from '/@/views/23psychology/psychic/psychologyApi';
import moment from 'moment';
import Empty from '/@/components/Empty.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
import { useRouter } from 'vue-router';
const activeNames = ref<any[]>([]);
const list = ref<any[]>([]); // 列表
const refreshing = ref(false); // 刷新状态
const loading = ref(false); // loading 状态
const finished = ref(false); // 是否完成
const error = ref(false); // 是否报错
const vellArray = ref<any[]>([]);
const pageInfo = ref({
pageNo: 1,
pageSize: 10,
});
const router = useRouter();
const count = ref<any[]>([]);
const dataIndex = ref(0);
function toReports(item: any) {
router.push({
path: '/answer-report',
query: { id: item.id },
});
}
function getRowspan(list: any[]) {
list.map((item, index) => {
if (index === 0) {
dataIndex.value = index;
count.value[index] = 1;
} else {
if (list[index - 1].modelId === item.modelId) {
count.value[dataIndex.value] = parseInt(count.value[dataIndex.value]) + 1;
count.value[index] = 0;
} else {
dataIndex.value = index;
count.value[index] = 1;
}
}
});
console.log(count.value);
}
function onLoad() {
if (refreshing.value) {
list.value = [];
vellArray.value = [];
activeNames.value = [];
refreshing.value = false;
finished.value = false;
error.value = false;
loading.value = true;
pageInfo.value.pageNo = 1;
}
if (finished.value) return;
getAnswerPageApi(pageInfo.value)
.then((res: any) => {
if (res.success) {
error.value = false;
if (res.result.length > 0) {
res.result.map((item: any) => {
if (vellArray.value.length === 0) {
vellArray.value = [new Date(item.endAnswerTime).getFullYear()];
} else {
if (vellArray.value[vellArray.value.length - 1] !== new Date(item.endAnswerTime).getFullYear()) {
vellArray.value.push(new Date(item.endAnswerTime).getFullYear());
}
}
});
}
activeNames.value = vellArray.value;
if (res.result.length < pageInfo.value.pageSize || res.result.length === 0) {
finished.value = true;
} else {
pageInfo.value.pageNo += 1;
}
getRowspan(res.result[0].categoryList);
list.value = list.value.concat(res.result);
} else {
error.value = true;
}
loading.value = false;
})
.catch(() => {
error.value = true;
})
.finally(() => {
loading.value = false;
});
}
const onRefresh = () => {
refreshing.value = true;
onLoad();
};
onLoad();
</script>
<style scoped lang="less">
.empty-d {
height: 90%;
padding-top: 50%;
}
:deep(.van-cell__title) {
background-color: #f5f7fb;
flex: none !important;
}
:deep(.van-cell) {
&:after {
display: none;
}
font-size: 16px;
padding-bottom: 5px !important;
color: rgba(119, 132, 158, 1);
background-color: #f5f7fb !important;
}
:deep(.van-collapse-item__content) {
&:after {
display: none;
}
padding: 0 10px 0 15px;
background-color: #f5f7fb !important;
}
:deep(.van-collapse-item) {
&:after {
display: none;
}
}
:deep(.van-hairline--top-bottom) {
&:after {
display: none;
}
}
.outer-d {
height: 100%;
background-color: #f5f7fb;
overflow: auto;
padding: 10px;
}
.van-collapse-item-d {
padding: 10px;
border-radius: 10px;
font-size: 16px;
background: #ffffff;
margin-bottom: 10px;
}
.middle-d {
padding: 10px 0 0;
> div {
border-radius: 5px;
padding: 10px 17px;
border: 1px solid rgba(230, 230, 234, 1);
margin-bottom: 10px;
> :nth-child(1) {
color: #21bebd;
font-size: 19px;
margin-right: 10px;
font-weight: bold;
}
> :nth-child(2) {
color: #000000;
font-weight: bold;
}
}
}
.bottom-d {
border: 1px solid rgba(224, 224, 224, 1);
border-bottom: none;
.bottom-d-item {
display: flex;
border-bottom: 1px solid rgba(224, 224, 224, 1);
> div {
padding: 10px 17px;
}
> :nth-child(1) {
background-color: #f0f0f0;
width: 40%;
color: #000000;
}
> :nth-child(2) {
text-align: right;
width: 60%;
}
}
}
table {
width: 100%;
table-layout: fixed;
margin-top: 10px;
color: #586275;
//font-size: 16px;
//font-weight: bold;
border-top: 1px solid #E0E0E0;
border-left: 1px solid #E0E0E0;
.tbody-tr{
font-size: 15px;
}
.table-left {
width: 45%;
}
.table-right {
width: 55%;
}
th {
background-color: #c6eeee;
}
th,
td {
padding: 10px 20px;
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
}
}
</style>
@@ -0,0 +1,300 @@
<template>
<div class="outer-d">
<div v-if="state == 2">
<div style="display: flex; justify-content: space-between; align-items: center">
<div style="color: #586275; font-size: 15px"> 评估时间{{ moment(time).format('YYYY-MM-DD HH:mm') }} </div>
<span style="color: #3390ff; font-size: 15px" @click="handleDetails"> 查看答题详情 </span>
</div>
<div v-if="specialReport" class="item-d">
<div style="display: flex; justify-content: space-between; align-items: center">
<span style="color: #333b42; font-size: 16px; font-weight: bold">专题心理知识</span>
<span style="color: #21bebd; font-weight: bold">
总分{{ specialReport?.totalScore || 0 }} <span style="margin-left: 10px">得分{{ specialReport?.userScore || 0 }}</span>
</span>
</div>
<div>
<table-item
:list="specialReport.reportInfo ? specialReport.reportInfo : []"
:count="specialReportCount"
:field-list="['modelName', 'questionCategory', 'score', 'grade']"
/>
</div>
<div v-if="specialReport?.gradeList && specialReport?.gradeList.length > 0" class="grade-d">
<div style="color: #000000; font-size: 16px">等级说明</div>
<div v-for="(item, index) in specialReport?.gradeList" :key="`gradeList-${index}`">
<div>
{{ item?.min }}-{{ item?.max }} <span style="margin-left: 5px">{{ item?.grade }}</span></div
>
<div>{{ item?.prompt }}</div>
</div>
</div>
</div>
<div v-if="allReport" class="item-d">
<div style="display: flex; justify-content: space-between; align-items: center">
<span style="color: #333b42; font-size: 16px; font-weight: bold">综合心理知识</span>
<span style="color: #21bebd; font-weight: bold">
总分{{ allReport?.totalScore || 0 }} <span style="margin-left: 10px">得分{{ allReport?.userScore || 0 }}</span>
</span>
</div>
<div>
<table-item
:list="allReport.reportInfo ? allReport.reportInfo : []"
:count="allReportCount"
:field-list="['modelName', 'questionCategory', 'score', 'grade']"
/>
</div>
<div v-if="allReport?.gradeList && allReport?.gradeList.length > 0" class="grade-d">
<div style="color: #000000; font-size: 16px">等级说明</div>
<div v-for="(item, index) in allReport?.gradeList" :key="`gradeList-${index}`">
<div>
{{ item?.min }}-{{ item?.max }} <span style="margin-left: 5px">{{ item?.grade }}</span></div
>
<div>{{ item?.prompt }}</div>
</div>
</div>
</div>
<div v-if="problemReport" class="item-d">
<div>
<span style="color: #333b42; font-size: 16px; font-weight: bold">心理问题</span>
</div>
<div>
<table-item :list="problemReport ? problemReport : []" class="problem-report">
<template #list-th-slot>
<th>分类</th>
<th>等级</th>
<th>指导语</th>
</template>
<template #list-td-slot="data">
<td>{{ data?.questionCategory || '-' }}</td>
<td>{{ data?.score || '-' }}{{ data?.grade ? `(${data?.grade})` : '' }}</td>
<td>{{ data?.prompt || '-' }}</td>
</template>
</table-item>
</div>
</div>
<div class="knowledge-list-d" v-if="knowledgeList.length > 0">
<span>知识推荐</span>
<div v-for="(item, index) in knowledgeList" :key="`knowledgeList${index}`">
<van-divider v-if="index !== 0" :style="{ backgroundColor: '#EAECF1' }" />
<div class="knowledge-list-item-d" @click="toDetails(item?.id)">
<div> <img :src="getFileHttpUrl(item?.banner as string)" alt="" /> </div>
<div>
<div style="color: #333333; font-weight: bold">{{ item?.knowledgeName }} </div>
<div style="color: #666666; font-size: 14px">{{ item?.description }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="loading-outer" v-else>
<van-loading v-if="state == 0" color="#1989fa" />
<div v-else>
<img :src="answerReportEmpty" alt="" />
<div style="text-align: center; margin-top: 10px">
<span style="font-size: 16px; color: #586275"> 答题结果正在计算中... </span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import answerReportEmpty from '/@/assets/images/psychiology/answerReportEmpty.png';
import { getAnswerReportApi, getKnowledgeApi } from '/@/views/23psychology/psychic/psychologyApi';
import TableItem from '/@/views/23psychology/answer/answerReport/tableItem.vue';
import { ref } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import moment from 'moment/moment';
import { getFileHttpUrl } from '/@/utils/compUtils.ts';
// 1825837438048186370
const route = useRoute();
const router = useRouter();
const state = ref(0);
const time = ref(null);
const specialReport = ref({});
const specialReportCount = ref<any[]>([]);
const allReport = ref({});
const allReportCount = ref<any[]>([]);
const problemReport = ref({});
const knowledgeList = ref<any[]>([]);
const apiInterval = ref<any>(null);
const intervalCount = ref(1);
let idRecord = ref('');
function init() {
getAnswerReportApi({ id: route.query.id })
.then((res: any) => {
if (res.success) {
time.value = res.result.endAnswerTime;
state.value = res.result.state;
specialReport.value = res.result.specialReport;
specialReportCount.value = res.result.specialReport ? preList(res.result.specialReport.reportInfo) : [];
allReport.value = res.result.allReport;
allReportCount.value = res.result.allReport ? preList(res.result.allReport.reportInfo) : [];
problemReport.value = res.result.problemReport;
idRecord.value = res.result.id;
if (res.result.state == '2') {
getKnowledge(res.result.id);
}
if (!apiInterval.value && res.result.state == '1' && intervalCount.value < 10) {
apiInterval.value = setInterval(() => {
intervalCount.value++;
init();
}, 5000);
}
if (res.result.state == '2' || intervalCount.value >= 10) {
apiInterval.value && clearInterval(apiInterval.value);
}
} else {
state.value = 1;
}
})
.catch(() => {
state.value = 1;
});
}
function getKnowledge(id: string) {
getKnowledgeApi({ recordId: id, pageNo: 1, pageSize: 999 })
.then((res: any) => {
console.log(res);
if (res.success) {
knowledgeList.value = res.result;
}
})
.catch(() => {});
}
function toDetails(id: string) {
router.push({
path: '/knowledge-detail',
query: {
id: id,
},
});
}
function preList(list: any[]) {
let dataIndex = 0;
let count: any[] = [];
list.map((item, index) => {
if (index === 0) {
dataIndex = index;
count[index] = 1;
} else {
if (list[index - 1].modelId === item.modelId) {
count[dataIndex] = parseInt(count[dataIndex]) + 1;
count[index] = 0;
} else {
dataIndex = index;
count[index] = 1;
}
}
});
console.log(count);
return [];
}
function handleDetails() {
router.push({
path: '/ps-answerDetails',
query: {
idRecord: idRecord.value,
},
});
}
init();
</script>
<style scoped lang="less">
.outer-d {
padding: 10px;
overflow: auto;
}
.outer-d,
.loading-outer {
width: 100%;
height: 100%;
background-color: #f5f7fb;
}
.loading-outer {
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 30%;
}
}
.item-d {
padding: 10px;
border-radius: 10px;
background-color: #ffffff;
margin-top: 10px;
}
.empty-d {
height: 90%;
padding-top: 50%;
}
.problem-report {
td,
th {
&:nth-child(1) {
width: 32%;
}
&:nth-child(2) {
width: 28%;
}
&:nth-child(3) {
width: 40%;
}
}
th {
background-color: #c6eeee;
}
th,
td {
padding: 10px;
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
}
}
.grade-d {
background-color: #f3f5f9;
color: #77849e;
margin-top: 10px;
padding: 10px;
border-radius: 10px;
div {
padding: 3px 0 0;
}
}
.knowledge-list-d {
background-color: #ffffff;
padding: 10px;
font-size: 16px;
font-weight: bold;
margin-top: 10px;
.knowledge-list-item-d {
padding: 20px 0;
display: flex;
> div {
height: 115px;
overflow: hidden;
&:nth-child(1) {
width: 40%;
img {
width: 100%;
}
}
&:nth-child(2) {
padding: 0 10px;
width: 60%;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
overflow: hidden;
}
}
}
}
</style>
@@ -0,0 +1,145 @@
<template>
<div class="outer-d">
<template v-if="info">
<div class="img-bottom-d">
<div class="img-header">
<div class="header-img">
<img :src="getFileHttpUrl(info?.banner)" alt="" />
</div>
<div class="header-con">
<div class="header-name"> {{ info?.knowledgeName }}</div>
<div class="header-container">
<div :class="['header-des', extendType ? '' : 'expand']"> {{ info?.description }}</div>
<span class="header-expand" @click="changeExpand">
{{ extendType ? '收起' : '展开' }}
<van-icon v-if="!extendType" name="arrow-down" color="#1A68EE" />
<van-icon v-else name="arrow-up" color="#1A68EE" />
</span>
</div>
</div>
</div>
<div class="bottom-item-d">
<div> 主要内容 </div>
<div v-if="info?.content" v-html="info?.content"> </div>
<div class="nodata" v-else>暂无数据</div>
</div>
<div class="bottom-item-d">
<div> 推荐理由 </div>
<div v-if="info?.recommendReason" v-html="info?.recommendReason"> </div>
<div class="nodata" v-else>暂无</div>
</div>
<div class="bottom-item-d">
<div> 具体帮助 </div>
<div v-if="info?.help" v-html="info?.help"> </div>
<div class="nodata" v-else>暂无</div>
</div>
</div>
</template>
<div v-if="empty || !info" class="empty-d">
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { getKnowledgeDetailApi } from '/@/views/23psychology/psychic/psychologyApi';
import { ref } from 'vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
import Empty from '/@/components/Empty.vue';
import { getFileHttpUrl } from '/@/utils/compUtils.ts';
const route = useRoute();
const empty = ref(false);
const info = ref<any>(null);
let extendType = ref(false);
let headerHieght = ref(120);
function init() {
getKnowledgeDetailApi({ id: route.query.id })
.then((res: any) => {
if (res.success) {
info.value = res.result;
} else {
empty.value = true;
}
})
.catch(() => {
empty.value = true;
});
}
init();
function changeExpand() {
extendType.value = !extendType.value;
}
</script>
<style scoped lang="less">
.outer-d {
height: 100%;
overflow: auto;
background-color: #f5f7fb;
position: relative;
.img-bottom-d {
padding: 20px;
.img-header {
display: flex;
padding: 16px;
margin-bottom: 20px;
background-color: #ffffff;
border-radius: 10px;
.expand {
height: 125px;
overflow: hidden;
}
.header-expand {
color: #1a68ee;
}
.header-img {
width: 35%;
height: 170px;
img {
width: 100%;
height: 100%;
}
}
.header-con {
padding-left: 15px;
width: 65%;
.header-name {
font-weight: bold;
font-size: 16px;
}
.header-des {
color: #666666;
padding-top: 10px;
}
}
}
.bottom-item-d {
padding: 16px;
margin-bottom: 20px;
background-color: #ffffff;
border-radius: 10px;
.nodata{
color:#666666;
}
> div {
&:nth-child(1) {
font-size: 16px;
font-weight: bold;
padding-bottom: 10px;
color: #333333;
}
&:nth-child(2) {
font-size: 14px;
color: #666666;
}
}
}
}
}
.empty-d {
height: 90%;
padding-top: 50%;
}
</style>
@@ -0,0 +1,74 @@
<template>
<table>
<tr>
<slot name="list-th-slot">
<th class="table-left">模块</th>
<th class="table-right">分类</th>
<th class="table-right">计分</th>
<th class="table-right">等级</th>
</slot>
</tr>
<tr v-for="(t, index) in props.list" :key="`category${index}`">
<slot name="list-td-slot" v-bind="t || {}">
<td v-if="props.count?.[index] != 0 || !props.count?.[index]" :rowspan="props.count?.[index] ? props.count?.[index] : 1">
{{ t[props.fieldList[0]] || '-' }}
</td>
<td>{{ t[props.fieldList[1]] || '-' }}</td>
<td>{{ t[props.fieldList[2]] || '-' }}</td>
<td>{{ t[props.fieldList[3]] || '-' }}</td>
</slot>
</tr>
</table>
</template>
<script setup lang="ts">
const props = defineProps({
list: {
type: Array,
default: () => [],
},
count: {
type: Array,
default: () => [],
},
fieldList: {
type: Array,
default: () => [],
},
});
</script>
<style scoped lang="less">
table {
width: 100%;
table-layout: fixed;
margin-top: 10px;
color: #586275;
//font-size: 16px;
font-weight: bold;
border-top: 1px solid #e0e0e0;
border-left: 1px solid #e0e0e0;
td,
th {
&:nth-child(1) {
width: 32%;
}
&:nth-child(2) {
width: 28%;
}
&:nth-child(3) {
width: 20%;
}
&:nth-child(4) {
width: 20%;
}
}
th {
background-color: #c6eeee;
}
th,
td {
padding: 10px;
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
}
}
</style>
@@ -0,0 +1,200 @@
<template>
<div class="outer-d">
<div class="top-tip">请选择此次答题类别可一次选择多个类别</div>
<div class="middle-d">
<template v-if="!isEmpty">
<template v-for="(item, index) in list" :key="`topic${index}`">
<template v-if="item?.childrenList && item?.childrenList.length > 0">
<div class="topic-item-d">
<div class="topic-item-d-title-one" style="margin-bottom: 10px">
<img v-if="item.id.indexOf('A01') > -1" :src="chooseTopicItem" alt="" />
<img v-if="item.id.indexOf('B01') > -1" :src="comprehensive" alt="" />
<img v-if="item.id.indexOf('C01') > -1" :src="chooseTopicItemLast" alt="" />
{{ item?.category }}{{ item.chidrenList }}
</div>
<template v-if="item?.id.indexOf('C01') === -1">
<topic-item :list="item?.childrenList" @update-value="changeValue" :count="index + ''" />
</template>
<template v-else>
<van-checkbox-group v-model="checked" shape="square" style="width: 100%">
<div style="display: flex; flex-wrap: wrap">
<div
:key="`topic${index}-${index}-${c}`"
class="check-item-d"
:class="[checked.includes(t.id as string) ? 'checked-item' : '']"
v-for="(t, c) in item.childrenList"
:style="{
width: t.id.indexOf('B01') !== -1 ? '100%' : '46%',
marginLeft: t.id.indexOf('B01') === -1 && c % 2 !== 0 ? '6%' : '0',
}"
>
<van-checkbox :name="t.id"> {{ t?.category }}</van-checkbox>
</div>
</div>
</van-checkbox-group>
</template>
</div>
</template>
</template>
</template>
<template v-else>
<div class="no-data">
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
</template>
</div>
<div class="bottom-button">
<van-button class="btn" round type="primary" @click="toAnswer" :disabled="isEmpty"> 开始答题 </van-button>
</div>
</div>
</template>
<script setup lang="ts">
import { getCategoryApi } from '/@/views/23psychology/psychic/psychologyApi';
import chooseTopicItem from '/@/assets/images/psychiology/chooseTopicItem.png';
import chooseTopicItemLast from '/@/assets/images/psychiology/chooseTopicItemLast.png';
import comprehensive from '/@/assets/images/psychiology/comprehensive.png';
import { showFailToast } from 'vant';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import store from '/@/store';
import TopicItem from '/@/views/23psychology/answer/chooseTopic/topicItem.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
import Empty from '/@/components/Empty.vue';
import { newPageParams, openPage } from '/@/hooks/openPage';
const list = ref<any[]>([]);
const checked = ref<any[]>([]);
const checkedJson = ref<any>({});
const isEmpty = ref(true);
const router = useRouter();
function getCategory() {
getCategoryApi({})
.then((res: any) => {
if (res.success) {
list.value = preTree(res.result || []);
} else {
showFailToast(res.messgae);
}
})
.catch(() => {});
}
function toAnswer() {
let list: any[] = [];
for (const c in checkedJson.value) {
list = list.concat(checkedJson.value[c]);
}
if (checked.value.concat(list).length === 0) return showFailToast('请至少选择一个类别');
store.commit('setQuestionList', checked.value.concat(list));
openPage(
'/ps-selfAnswer',
newPageParams({
affirmBack: 1,
})
);
}
function preTree(d: any[]) {
const dg = (data: any[]) => {
if (data.length === 0) return [];
return data.filter((item) => {
if (item.childrenList && item.childrenList.length > 0) {
item.childrenList = dg(item.childrenList);
return 1 === 1;
} else {
if (item.questionSum && item.id.indexOf('C01') !== -1 ? item.questionSum >= 1 : item.questionSum >= 5 && isEmpty) {
isEmpty.value = false;
}
return item.questionSum && item.id.indexOf('C01') !== -1 ? item.questionSum >= 1 : item.questionSum >= 5;
}
});
};
console.log(dg(d));
return dg(d);
}
function changeValue(v: any) {
checkedJson.value = { ...checkedJson.value, ...v };
console.log(checkedJson.value);
}
getCategory();
</script>
<style scoped lang="less">
.outer-d {
width: 100%;
height: 100%;
}
.no-data {
width: 100%;
padding-top: 50%;
}
.top-tip {
color: #21bebd;
background-color: #eefffe;
padding: 10px;
text-align: center;
font-size: 16px;
}
.middle-d {
background-color: #f5f7fb;
padding: 10px;
overflow: auto;
height: calc(100% - 111px);
.topic-item-d {
background: #ffffff;
border-radius: 10px;
padding: 10px;
margin-bottom: 10px;
.topic-item-d-title-one {
display: flex;
align-items: center;
font-size: 16px;
font-weight: bold;
img {
width: 30px;
height: 30px;
margin-right: 10px;
}
}
.check-item-d {
padding: 10px;
margin-top: 10px;
border: 1px solid #f3f5f9;
}
}
.check-item-d {
background: #f3f5f9;
border-radius: 10px;
}
}
.bottom-button {
width: 100%;
padding: 10px;
background-color: #ffffff;
.btn {
background-color: #21bebd;
border: 1px solid #21bebd;
}
button {
width: 80%;
margin-left: 10%;
}
}
.checked-item {
background-color: #eefffe !important;
border: 1px solid #21bebd !important;
}
:deep(.van-checkbox__icon--checked .van-icon) {
color: #ffffff !important;
background-color: #21bebd !important;
border-color: #21bebd !important;
}
</style>
@@ -0,0 +1,94 @@
<template>
<van-checkbox-group v-model="checked" shape="square" style="width: 100%" @change="changeChecked">
<div style="display: flex; flex-wrap: wrap; justify-content: space-between">
<template v-for="(t, c) in props.list">
<template v-if="t?.childrenList && t?.childrenList.length > 0">
<div style="margin: 10px 0 0; font-size: 16px; width: 100%"> {{ c + 1 }}{{ t?.category }} </div>
<topic-item :list="t?.childrenList" @update-value="updateValueInfo" :count="props.count + '-' + c" />
</template>
<template v-else>
<div
class="check-item-d"
:style="{
width: t?.parentId === 'B01' && !t?.childrenList ? '100%' : '46%',
backgroundColor: t?.childrenList && t?.childrenList.length > 0 ? '#ffffff !important' : '',
borderColor: t?.childrenList && t?.childrenList.length > 0 ? '' : '',
}"
:class="[checked.includes(t.id as string) ? 'checked-item' : '']"
>
<van-checkbox :name="t.id">
{{ `${t.id.indexOf('B01') !== -1 && t?.parentId === 'B01' ? c + 1 + '、' : ''}${t?.category}` }}
</van-checkbox>
</div>
</template>
</template>
</div>
</van-checkbox-group>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { union } from 'lodash-es';
const props = defineProps({
list: {
type: Array,
default: () => [],
},
count: {
type: String,
default: () => '',
},
});
const emit = defineEmits(['updateValue']);
const checked = ref<any[]>([]);
const checkedJson = ref<any>({});
function changeChecked() {
emit('updateValue', { ...{ [props.count]: checked.value }, ...checkedJson.value });
}
function updateValueInfo(v: any) {
checkedJson.value = v;
emit('updateValue', { ...{ [props.count]: checked.value }, ...checkedJson.value });
}
</script>
<style scoped lang="less">
.topic-item-d {
background: #ffffff;
border-radius: 10px;
padding: 10px;
margin-bottom: 10px;
.topic-item-d-title-one {
display: flex;
align-items: center;
font-size: 16px;
font-weight: bold;
img {
width: 30px;
height: 30px;
margin-right: 10px;
}
}
.check-item-d {
background: #f3f5f9;
border-radius: 10px;
padding: 10px;
margin-top: 10px;
width: 46%;
border: 1px solid #f3f5f9;
}
}
.checked-item {
background-color: #eefffe !important;
border: 1px solid #eefffe !important;
}
:deep(.van-checkbox__icon--checked .van-icon) {
color: #ffffff !important;
background-color: #21bebd !important;
border-color: #21bebd !important;
}
</style>
@@ -0,0 +1,339 @@
<template>
<div class="answer-container">
<div class="plan-music" v-if="ifActive">
<div class="time">倒计时{{ formatTime }}</div>
<div class="bg-music">
<img :src="playIcon" v-if="isOpenMusic" alt="" @click="handleMusic('stop')" />
<img :src="stopIcon" alt="" v-else @click="handleMusic('play')" />
<span>背景音乐</span>
<audio loop ref="audioPlayer" :src="audioUrl"></audio>
</div>
</div>
<div
class="q-content"
:style="{ height: ifActive ? 'calc(100vh - 100px)' : 'calc(100vh - 60px)' }"
v-if="quesParams?.paperQuestionList.length > 0"
id="psychlolgy_plan"
>
<AnswerCon :list="quesParams?.paperQuestionList" isEdit @change="handelChange" />
</div>
<div class="no-data" v-else>
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
<div class="bottom-btn">
<van-button class="btn" type="primary" :disabled="quesParams?.paperQuestionList.length === 0" @click="handleSubmit">提交</van-button>
</div>
<Overlay :show="show">
<template #content>
<div class="overlay-block">
<div class="block-top">时间到当前答题已结束</div>
<div class="block-bottom" @click="handleSubmit">我知道了</div>
</div>
</template>
</Overlay>
<Overlay :show="backOver">
<template #content>
<div class="overlay-block">
<div class="block-top">中途退出将不会保存您的答案是否确定退出</div>
<div class="out-btn">
<div class="backBtn canel-out" @click="backOver = false">取消</div>
<div class="backBtn sure-out" @click="backAnswer">退出答题</div>
</div>
</div>
</template>
</Overlay>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { getQuesApi, submitQuesApi, getPlanQuesApi, submitPlanQuesApi } from '/@/views/23psychology/answer/questionAnswer/questionAnswer.api';
import AnswerCon from '/@/views/23psychology/answer/questionAnswer/components/AnswerCon.vue';
import Empty from '/@/components/Empty.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
import store from '/@/store';
import playIcon from '/@/assets/images/psychiology/answer/playIcon.png';
import stopIcon from '/@/assets/images/psychiology/answer/stopIcon.png';
import { showSuccessToast, showFailToast, showToast } from 'vant';
import { useLoading } from '/@/utils/compUtils';
import { getFileUrl } from '/@/hooks/fileUrl';
import { useCountdown } from '/@/views/23psychology/answer/questionAnswer/questionAnswerHook';
import { convertTimeToSeconds, minuteConverSeconds } from '/@/views/23-physical-questions/questionHooks';
import Overlay from '/@/views/components/overlay/Overlay.vue';
import { useInterceptBack } from '/@/hooks/useInterceptAndroid';
import { destroyPage } from '/@/hooks/openPage';
const router = useRouter();
const route = useRoute();
const { loadingSpinner, loadingClose } = useLoading();
const ifActive = route.query.hasOwnProperty('type');
let show = ref(false);
let audioPlayer = ref(null);
let audioUrl = ref('');
let timerOver = ref(false);
let backOver = ref(false);
let quesParams = ref({
paperQuestionList: [],
});
let isOpenMusic = ref(false);
const { formatTime, startTimer, stopTimer } = useCountdown(ifActive ? route.query.answerTime : -1);
watch(formatTime, (newVal) => {
if (ifActive && convertTimeToSeconds(newVal) === 0) {
show.value = true;
stopTimer();
}
});
onMounted(() => {
useInterceptBack(1, dropOut);
document.title = ifActive ? '活动答题' : '自主答题';
ifActive ? getPlanList() : getList();
});
function dropOut() {
backOver.value = true;
}
async function getList() {
loadingSpinner();
try {
let queCode = store.getters.getQuestionList;
const { code, result } = await getQuesApi(queCode);
if (code === 200) {
startTimer();
quesParams.value = result;
}
} catch (e) {
} finally {
loadingClose();
}
}
// 活动问卷
async function getPlanList() {
loadingSpinner();
try {
const { code, result } = await getPlanQuesApi({
planId: route.query.planId,
});
if (code === 200) {
startTimer();
quesParams.value = result;
audioUrl.value = getFileUrl(result.planMusicUrl);
}
} catch (e) {
} finally {
loadingClose();
}
}
function handelChange(data: any) {
quesParams.value.paperQuestionList = data;
}
function subTip() {
let userAnswerList: { answer: any; questionId: any }[] = [];
let data = quesParams.value.paperQuestionList;
for (let i = 0; i < data.length; i++) {
let item = data[i];
if (!item.userAnswer) {
showToast('请完成所有题目');
let errorItem = document.getElementById('psychlolgy_plan');
errorItem.scrollTop = document.getElementById('psychology_' + item.questionId).offsetTop - 35;
break;
} else {
userAnswerList.push({
answer: item.userAnswer,
questionId: item.questionId,
});
}
}
return userAnswerList;
}
function handleSubmit() {
let userAnswerList: any[] = [];
let data = quesParams.value.paperQuestionList;
if (show.value) {
// 时间到,不需要拦截
data.map((item: any) => {
userAnswerList.push({
answer: item.userAnswer,
questionId: item.questionId,
});
});
} else {
// 在规定时间内,拦截填写完
userAnswerList = subTip();
}
if (userAnswerList.length !== data.length) return false;
loadingSpinner();
try {
const params = {
paperId: quesParams.value.paperId,
answerTimeSum: hanleAnswerTime(),
userSubmitAnswerDetailsDTOS: userAnswerList,
};
console.log(params);
timerOver.value = false;
stopTimer();
ifActive ? planSubmit(params) : slefSubmit(params);
} catch (e) {
} finally {
loadingClose();
}
}
function hanleAnswerTime() {
if (ifActive) {
const totalTimer = minuteConverSeconds(route.query.answerTime); // 总时间(秒)
const consumeTimer = convertTimeToSeconds(formatTime.value); // 未消耗时间(秒)
const diffTimer = totalTimer - consumeTimer; // 已消耗时间(秒)
const finalTimer = diffTimer == '0' ? totalTimer : diffTimer;
return finalTimer;
} else {
return convertTimeToSeconds(formatTime.value);
}
}
function backAnswer() {
backOver.value = false;
try {
destroyPage();
} catch {
router.go(-1);
}
}
async function slefSubmit(params: any) {
const res = await submitQuesApi(params);
hanleResult(res);
}
async function planSubmit(params: any) {
const res = await submitPlanQuesApi(params);
hanleResult(res);
}
function hanleResult({ code, result }) {
useInterceptBack(0);
if (code === 200) {
showSuccessToast('答题成功');
router.replace({
path: '/answer-Report',
query: {
id: result,
},
});
} else {
showFailToast('答题失败!');
}
}
// 活动时播放背景音乐
function handleMusic(type: string) {
if(audioUrl.value==='') return showToast('暂无音乐资源!')
if (audioPlayer.value) {
if (type == 'play') {
isOpenMusic.value = true;
audioPlayer.value.play();
} else {
isOpenMusic.value = false;
audioPlayer.value.pause();
}
}
}
onUnmounted(() => {
// 清除用户选择题型
store.commit('setQuestionList', []);
stopTimer();
});
</script>
<style scoped lang="less">
@import url('/@/views/23psychology/components/psychology.less');
.answer-container {
width: 100%;
height: 100vh;
background-color: @con-bg;
.overlay-block {
width: 80%;
height: 170px;
background-color: #fff;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-items: center;
.block-top {
width: 100%;
height: 70%;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
color: #333333;
padding: 0 30px;
text-align: center;
}
.block-bottom {
width: 100%;
padding: 20px;
text-align: center;
border-radius: 0 0 8px 8px;
color: #ffffff;
background-color: @primary-color;
}
.out-btn {
width: 100%;
display: flex;
.backBtn {
width: 50%;
text-align: center;
padding: 20px;
}
.canel-out {
background-color: #e6e6ea;
color: #333333;
border-bottom-left-radius: 8px;
}
.sure-out {
color: #ffffff;
background-color: @primary-color;
border-bottom-right-radius: 8px;
}
}
}
.plan-music {
display: flex;
justify-content: space-between;
padding: 10px 20px;
background-color: #eefffe;
.time {
color: #21bebd;
}
.bg-music {
color: #21bebd;
font-size: 15px;
img {
width: 22px;
height: 22px;
margin-right: 10px;
}
}
}
.q-content {
width: 100%;
overflow-y: auto;
padding: 20px;
scroll-behavior: smooth;
}
.no-data {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 50%;
}
.bottom-btn {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
height: 50px;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
.btn {
.bottom-button();
}
}
}
</style>
@@ -0,0 +1,212 @@
<template>
<div class="question-box">
<div class="question-item" v-for="(item, index) in list" :key="index" :id="'psychology_' + item.questionId">
<div class="question-title" v-if="item.disabledCategory">
<div class="one-title" v-if="showTitle(item.categoryList).one.text">
<img :src="showTitle(item.categoryList).one.icon" />
<span>{{ showTitle(item.categoryList).one.text }}</span>
</div>
<div class="subtitle" v-if="showTitle(item.categoryList).two">{{ showTitle(item.categoryList).two }}</div>
<div class="three-title" v-if="showTitle(item.categoryList).three">{{ showTitle(item.categoryList).three }}</div>
</div>
<div class="question-operate">
<div class="question-top">
<span class="tips">*</span>
<span>{{ index + 1 }}</span>
<span>{{ item.questionDesc }}</span>
<span>{{ questionType(item.type) }}</span>
</div>
<div class="options-content">
<div
v-for="(optionItem, optionIndex) in item.options"
:class="['optionItem', optionClass(isEdit, item, optionItem, item.options)]"
:key="optionIndex"
@click="handleChange(optionItem, item.type, index)"
>
<span>{{ optionItem.optionNo }}.</span>
<span>{{ optionItem.optionValue }}</span>
</div>
</div>
<div v-if="!isEdit && item.realAnswer!==''" class="answer-con">
<div class="standard">
<span class="label">答案</span>
<span :class="answerText(item, item.options, 'realAnswer') === '暂无' ? '' : 'correct-color'">{{
answerText(item, item.options, 'realAnswer')
}}</span>
</div>
<div class="standard">
<span class="label">您的选择</span>
<span :class="userAnswerClass(item, item.options)">{{ answerText(item, item.options, 'userAnswer') }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, ref } from 'vue';
import {
questionType,
showTitle,
answerText,
userAnswerClass,
optionClass,
} from '/@/views/23psychology/answer/questionAnswer/questionAnswerHook';
const emit = defineEmits(['change']);
const props = defineProps({
list: {
type: Array,
// eslint-disable-next-line vue/require-valid-default-prop
default: [],
},
isEdit: {
type: Boolean,
default: true,
},
title: {
type: String,
default: 'title',
},
});
let userList = ref([]);
function handleChange(optionItem: never, type: string, index: number) {
if (!props.isEdit) return false;
const data = props.list as any[];
// 单选,判断
if (type === 'DG' || type === 'PD') {
data[index].userAnswer = optionItem.optionId;
}
// 多选
if (type === 'DX') {
if (userList.value.includes(optionItem.optionId)) {
let index = userList.value.indexOf(optionItem.optionId);
userList.value.splice(index, 1);
} else {
userList.value.push(optionItem.optionId);
}
data[index].userAnswer = userList.value.join(',');
}
emit('change', data);
}
</script>
<style scoped lang="less">
@import url('/@/views/23psychology/components/psychology.less');
.question-box {
width: 100%;
height: 100%;
background-color: #f4f4f4;
.question-item {
width: 100%;
.question-title {
width: 100%;
background-color: #ffffff;
.one-title {
background-color: #c8f5f5;
padding: 16px 10px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
display: flex;
align-items: center;
margin-bottom: 15px;
img {
width: 28px;
}
span {
color: #333333;
font-weight: bold;
font-size: 15px;
padding-left: 10px;
}
}
.subtitle {
padding: 5px 25px 5px 15px;
display: inline-block;
background: url('/@/assets/images/psychiology/answer/titleBg.png') no-repeat;
background-size: 100% 100%;
color: #ffffff;
position: relative;
}
.three-title {
padding: 10px 20px 10px 35px;
position: relative;
&:after {
content: '';
position: absolute;
left: 18px;
top: 14px;
width: 13px;
height: 13px;
border-radius: 50%;
background-color: @primary-color;
}
}
}
.question-operate {
padding: 10px 20px;
background-color: #ffffff;
.question-top {
position: relative;
.tips {
color: #ed2a26;
font-size: 18px;
font-weight: bold;
position: absolute;
top: -2px;
left: -8px;
}
}
.options-content {
padding: 10px 0;
span:nth-child(2) {
padding-left: 4px;
}
.optionItem {
padding: 10px 20px;
margin: 10px 0;
border-radius: 6px;
background-color: #f4f4f4;
color: #77849e;
border: 1px solid #f4f4f4;
}
.select-item {
background-color: #eefffe;
color: @primary-color;
border: 1px solid @primary-color;
}
.error-item {
background-color: rgba(237, 42, 38, 0.2);
color: #ed2a26;
border: 1px solid #ed2a26;
}
.correct-item {
background-color: rgba(82, 196, 26, 0.2);
color: #52c41a;
border: 1px solid #52c41a;
}
}
.answer-con {
display: flex;
background-color: #eff5ff;
padding: 20px;
border-radius: 10px;
.standard {
.label {
color: #333333;
}
.correct-color {
color: #52c41a;
}
.error-color {
color: #ed2a26;
}
}
.standard:nth-child(2) {
padding-left: 10px;
}
}
}
}
}
</style>
@@ -0,0 +1,379 @@
<template>
<div class="outer-d">
<div class="title">
<div>答题时长{{ useConvertTime(quesParams?.answerTimeSum) || '-' }}</div>
<div>答题时间{{ quesParams?.endAnswerTime || '-' }}</div>
</div>
<div class="container" v-if="quesParams.paperQuestionList !== null && quesParams.paperQuestionList.length > 0">
<div class="top-button" id="resultCon">
<div class="ps-two">
<div class="ps-two-title">
<div class="name">心理知识</div>
<div class="score">
<span>总分{{ quesParams.paperScore }}</span>
<span>得分{{ quesParams.totalScore }}</span>
</div>
</div>
<div class="tabs-con">
<div class="tabs-bg">
<div
v-for="(item, index) in tabList"
:class="['tabs-item', index === active ? 'active' : '']"
:key="index"
@click="onClickTab(item.value)"
>
{{ item.label }}
</div>
</div>
</div>
<div class="ps-two-container" v-for="(item, index) in quesParams.paperQuestionList" :key="index">
<template v-if="item.questionCategoryId.indexOf('A01') > -1 || item.questionCategoryId.indexOf('B01') > -1">
<div class="question-title" v-if="item.disabledCategory">
<div class="one-title-no" v-if="showTitle(item.categoryList).one.text">
<img :src="showTitle(item.categoryList).one.icon" />
<span>{{ showTitle(item.categoryList).one.text }}</span>
</div>
<div class="subtitle" v-if="showTitle(item.categoryList).two">{{ showTitle(item.categoryList).two }}</div>
<div class="three-title" v-if="showTitle(item.categoryList).three">{{ showTitle(item.categoryList).three }}</div>
</div>
<div class="question-operate">
<div class="question-top">
<span class="tips">*</span>
<span>{{ index + 1 }}</span>
<span>{{ item.questionDesc }}</span>
<span>{{ questionType(item.type) }}</span>
</div>
<div class="options-content">
<div
v-for="(optionItem, optionIndex) in item.options"
:class="['optionItem', optionClass(false, item, optionItem, item.options)]"
:key="optionIndex"
>
<span>{{ optionItem.optionNo }}.</span>
<span>{{ optionItem.optionValue }}</span>
</div>
</div>
<div v-if="item.realAnswer !== ''" class="answer-con">
<div class="standard">
<span class="label">答案</span>
<span :class="answerText(item, item.options, 'realAnswer') === '暂无' ? '' : 'correct-color'">{{
answerText(item, item.options, 'realAnswer')
}}</span>
</div>
<div class="standard">
<span class="label">您的选择</span>
<span :class="userAnswerClass(item, item.options)">{{ answerText(item, item.options, 'userAnswer') }}</span>
</div>
</div>
</div>
</template>
</div>
</div>
<div class="ps-three">
<div class="ps-two-container" v-for="(item, index) in quesParams.paperQuestionList" :key="index">
<template v-if="item.questionCategoryId.indexOf('C01') > -1">
<div class="question-title" v-if="item.disabledCategory">
<div class="one-title-no one-title-bg" v-if="showTitle(item.categoryList).one.text">
<img :src="showTitle(item.categoryList).one.icon" />
<span>{{ showTitle(item.categoryList).one.text }}</span>
</div>
<div class="subtitle" v-if="showTitle(item.categoryList).two">{{ showTitle(item.categoryList).two }}</div>
<div class="three-title" v-if="showTitle(item.categoryList).three">{{ showTitle(item.categoryList).three }}</div>
</div>
<div class="question-operate">
<div class="question-top">
<span class="tips">*</span>
<span>{{ index + 1 }}</span>
<span>{{ item.questionDesc }}</span>
<span>{{ questionType(item.type) }}</span>
</div>
<div class="options-content">
<div
v-for="(optionItem, optionIndex) in item.options"
:class="['optionItem', optionClass(false, item, optionItem, item.options)]"
:key="optionIndex"
>
<span>{{ optionItem.optionNo }}.</span>
<span>{{ optionItem.optionValue }}</span>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
<div class="no-data" v-else>
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { getAnswerDetailsApi } from '/@/views/23psychology/answer/questionAnswer/questionAnswer.api';
import {
questionType,
showTitle,
answerText,
userAnswerClass,
optionClass,
} from '/@/views/23psychology/answer/questionAnswer/questionAnswerHook';
import { useConvertTime } from '/@/views/23-physical-questions/questionHooks';
import Empty from '/@/components/Empty.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
const route = useRoute();
let active = ref(0);
let tabList = ref([
{
label: '全部',
value: 0,
},
{
label: '我的错题',
value: 1,
},
]);
let quesParams = ref({
paperQuestionList: [],
});
onMounted(() => {
getDetails();
});
async function getDetails() {
const { code, result } = await getAnswerDetailsApi({
id: route.query.idRecord,
type: active.value,
});
if (code === 200) {
quesParams.value = result;
}
}
function onClickTab(val: number) {
active.value = val;
if (quesParams.value.paperQuestionList !== null && quesParams.value.paperQuestionList.length > 0) {
document.getElementById('resultCon').scrollTop = 0;
}
getDetails();
}
</script>
<style scoped lang="less">
@import url('/@/views/23psychology/components/psychology.less');
.outer-d {
width: 100%;
height: 100vh;
background-color: @con-bg;
position: relative;
overflow-y: hidden;
padding: 20px;
.container {
width: 100%;
height: calc(100% - 80px);
margin-top: 20px;
overflow-y: auto;
.ps-two {
background-color: #ffffff;
border-radius: 10px;
}
.ps-three {
background-color: #ffffff;
border-radius: 10px;
margin-top: 20px;
}
.ps-two-title {
padding: 20px;
display: flex;
justify-content: space-between;
background-color: #c8f5f5;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
.name {
font-weight: bold;
font-size: 16px;
color: #333333;
}
.score {
color: #21bebd;
span:nth-child(1) {
padding-right: 10px;
}
}
}
.question-title {
width: 100%;
background-color: #ffffff;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
.one-title-no {
padding: 16px 10px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
display: flex;
align-items: center;
margin-bottom: 15px;
img {
width: 28px;
}
span {
color: #333333;
font-weight: bold;
font-size: 15px;
padding-left: 10px;
}
}
.one-title-bg {
background-color: #c8f5f5;
}
.subtitle {
padding: 5px 25px 5px 15px;
display: inline-block;
background: url('/@/assets/images/psychiology/answer/titleBg.png') no-repeat;
background-size: 100% 100%;
color: #ffffff;
position: relative;
}
.three-title {
padding: 10px 20px 10px 35px;
position: relative;
&:after {
content: '';
position: absolute;
left: 18px;
top: 14px;
width: 13px;
height: 13px;
border-radius: 50%;
background-color: @primary-color;
}
}
}
.question-operate {
padding: 10px 20px;
background-color: #ffffff;
border-radius: 10px;
.question-top {
position: relative;
.tips {
color: #ed2a26;
font-size: 18px;
font-weight: bold;
position: absolute;
top: -2px;
left: -8px;
}
}
.options-content {
padding: 10px 0;
span:nth-child(2) {
padding-left: 4px;
}
.optionItem {
padding: 10px 20px;
margin: 10px 0;
border-radius: 6px;
background-color: #f4f4f4;
color: #77849e;
border: 1px solid #f4f4f4;
}
.select-item {
background-color: #eefffe;
color: @primary-color;
border: 1px solid @primary-color;
}
.error-item {
background-color: rgba(237, 42, 38, 0.2);
color: #ed2a26;
border: 1px solid #ed2a26;
}
.correct-item {
background-color: rgba(82, 196, 26, 0.2);
color: #52c41a;
border: 1px solid #52c41a;
}
}
.answer-con {
display: flex;
background-color: #eff5ff;
padding: 20px;
border-radius: 10px;
.standard {
.label {
color: #333333;
}
.correct-color {
color: #52c41a;
}
.error-color {
color: #ed2a26;
}
}
.standard:nth-child(2) {
padding-left: 10px;
}
}
}
}
.title {
background: linear-gradient(180deg, #21bebd 0%, #87d8d7 100%);
padding: 10px 13px;
border-radius: 10px;
color: #ffffff;
display: flex;
flex-direction: column;
justify-items: center;
position: relative;
div {
padding: 2px 0;
}
&:after {
content: '';
width: 76px;
height: 52px;
position: absolute;
right: 0;
bottom: 0;
background: url('/@/assets/images/psychiology/answer/timer.png') no-repeat;
background-size: 100% 100%;
}
}
.tabs-con {
width: 100%;
height: 64px;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
.tabs-bg {
width: 90%;
height: 45px;
padding: 20px 0;
display: flex;
align-items: center;
justify-content: center;
background-color: #e8ebf2;
border-radius: 5px;
.tabs-item {
width: 48%;
margin: 0 auto;
height: 40px;
line-height: 40px;
text-align: center;
color: #77849e;
border-radius: 5px;
}
.active {
background-color: #ffffff;
color: #252535;
}
}
}
.no-data {
height: 100%;
padding-top: 50%;
}
}
</style>
@@ -0,0 +1,17 @@
import { get, post } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
// 获取自主问卷
export const getQuesApi = (params: any) => post(`${prefix}/api/psychology/answer/paper`, params, 'C01', { isArray: true });
//自主问卷提交
export const submitQuesApi = (params: any) => post(`${prefix}/api/psychology/answer/submit`, params);
// 获取活动问卷
export const getPlanQuesApi = (params: any) => get(`${prefix}/api/psychology/answer/plan/answer`, params);
// 自主问卷提交
export const submitPlanQuesApi = (params: any) => post(`${prefix}/api/psychology/answer/plan/submit`, params);
// 答题详情、
export const getAnswerDetailsApi = (params: any) => get(`${prefix}/api/psychology/answer/details`, params);
@@ -0,0 +1,133 @@
import chooseTopicItem from '/@/assets/images/psychiology/chooseTopicItem.png';
import comprehensive from '/@/assets/images/psychiology/comprehensive.png';
import chooseTopicItemLast from '/@/assets/images/psychiology/chooseTopicItemLast.png';
import { computed, onBeforeUnmount, ref, watch } from 'vue';
import { minuteConverSeconds } from '/@/views/23-physical-questions/questionHooks';
export function questionType(type: string) {
const typeParams = {
DG: '单选题',
DX: '多选题',
PD: '单选题',
};
// @ts-ignore
return typeParams[type];
}
function iconback(text: string) {
let icon = chooseTopicItem;
if (text.indexOf('专题') > -1) {
icon = chooseTopicItem;
} else if (text.indexOf('综合') > -1) {
icon = comprehensive;
} else if (text.indexOf('心理') > -1) {
icon = chooseTopicItemLast;
}
return icon;
}
export function showTitle(list: any) {
let oneText = '',
twoText = '',
threeTwo = '';
if (list.length === 3) {
oneText = list[0];
twoText = list[1];
threeTwo = list[2];
}
if (list.length === 2) {
oneText = list[0];
twoText = list[1];
}
if (list.length === 1) {
twoText = list[0];
}
return {
one: {
icon: iconback(oneText),
text: oneText,
},
two: twoText,
three: threeTwo,
};
}
export function useCountdown(duration: number) {
// 分钟转为秒
const timer = ref(duration === -1 ? 0 : minuteConverSeconds(duration));
const isTimerRunning = ref(false);
// 格式化时间
const formatTime = computed(() => {
const minutes = Math.floor(timer.value / 60);
const seconds = timer.value % 60;
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
});
// 计时器逻辑
let interval: any = null;
const startTimer = () => {
isTimerRunning.value = true;
interval = setInterval(() => {
duration === -1 ? timer.value++ : timer.value--;
}, 1000);
};
const stopTimer = () => {
isTimerRunning.value = false;
if (duration === -1) timer.value = 0;
clearInterval(interval);
};
// 监听计时器状态变化
watch(isTimerRunning, (newValue) => {
if (!newValue) {
clearInterval(interval);
}
});
// 在组件销毁前清除计时器
onBeforeUnmount(() => {
clearInterval(interval);
});
return {
formatTime,
startTimer,
stopTimer,
};
}
// 标准答案内容
export function answerText(item: any, optionList: Array, fields: string) {
if (item[fields] !== '') {
const userAnswer = item[fields].split(',');
const same = optionList.filter((item: any) => userAnswer.includes(item.optionId));
const answer = same.map((item: any) => item.optionNo).join(',');
return answer;
}
return '暂无';
}
// 用户选择样式
export function userAnswerClass(item: any) {
if (item.realAnswer === '' || item.realAnswer === null) return false;
return item.realAnswer === item.userAnswer ? 'correct-color' : 'error-color';
}
//
export function optionClass(isEdit: Boolean, item: any, optionItem: any, optionList: any) {
let optionClass = '';
if (isEdit) {
optionClass = item.userAnswer !== null && item.userAnswer.split(',').includes(optionItem.optionId) ? 'select-item' : '';
} else {
if (item.userAnswer === null || item.userAnswer === '') {
optionClass = '';
}else{
const vis = optionList.some((e: any) => e.isTrue);
if (vis) {
// 有正确答题
const userAnswer = item.userAnswer.split(',');
if (userAnswer.includes(optionItem.optionId)) {
optionItem.isTrue ? (optionClass = 'correct-item') : (optionClass = 'error-item');
}
} else {
// 无正确答案,使用默认样式显示用户选项
if (optionItem.optionId === item.userAnswer) {
optionClass = 'select-item';
}
}
}
}
return optionClass;
}
@@ -0,0 +1,5 @@
import { get, post } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
//通过id 获取列表
export const getPlanRecord = (params: any) => get(`${prefix}/api/psychology/answer/plan/record`, params);
@@ -0,0 +1,327 @@
<template>
<div class="box">
<div class="top-img">
<img :src="RankingListPng" alt="" />
</div>
<div class="ranking-box">
<div class="ranking-top">
<div class="left">
<span class="one">
排行榜
<span class="under"></span>
</span>
<span>({{ list?.joinUserList.length }}人参加)</span>
</div>
<div class="right">
<img :src="RankingPng" alt="" />
</div>
</div>
<div class="ranking-center">
<span style="width: 12%" v-if="list?.showRanking">排名</span>
<span style="flex: 1; text-align: left">用户</span>
<span style="text-align: right">分数</span>
</div>
<div class="ranking-bottom">
<div class="me ranking" v-if="meList?.selfRecord">
<!-- -->
<div class="fraction" v-if="meList?.showRanking">{{ meList?.selfRecord.ranking }}</div>
<div class="picture">
<img
:src="getFileHttpUrl(meList?.selfRecord.avatar) || 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
onerror="this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
alt=""
/>
</div>
<div class="personal">
<div class="top">
<div class="top-left">
<span>{{ meList?.selfRecord.userName }}</span>
<span class="my">()</span>
<span class="line"></span>
<span class="dept">{{ meList?.selfRecord.deptName }}</span>
</div>
<div class="top-right">
<span>{{ meList?.selfRecord.userScore }}</span>
<span></span>
</div>
</div>
<div class="bottom">
<span>{{ meList?.selfRecord.endAnswerTime }}</span>
<span>耗时{{ getTimeStr(meList?.selfRecord.answerTimeSum, 'mm') }}</span>
</div>
</div>
</div>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<div style="height: 100%; overflow: auto">
<van-list
v-model:loading="loading"
:finished="finished"
:finished-text="list?.joinUserList && list?.joinUserList.length > 0 ? '没有更多了' : ''"
@load="onLoad"
v-model:error="error"
error-text="请求失败点击重新加载"
>
<template v-if="list?.joinUserList && list?.joinUserList.length > 0">
<div v-for="(item, index) in list?.joinUserList" :key="index" class="ranking">
<div class="fraction" v-if="list?.showRanking">
<span v-if="index == 0" class="medal gold">
<img :src="GoldPng" alt="" />
</span>
<span v-else-if="index == 1" class="medal silver">
<img :src="SilverPng" alt="" />
</span>
<span v-else-if="index == 2" class="medal bronze">
<img :src="BronzePng" alt="" />
</span>
<span v-else>{{ index + 1 }}</span>
</div>
<div class="picture">
<img
:src="getFileHttpUrl(item.avatar) || 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
onerror="this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
alt=""
/>
</div>
<div class="personal">
<div class="top">
<div class="top-left">
<span>{{ item.userName }}</span>
<span class="line"></span>
<span class="dept">{{ item.deptName }}</span>
</div>
<div class="top-right">
<span>{{ item.userScore }}</span>
<span>分</span>
</div>
</div>
<div class="bottom">
<span>{{ item.endAnswerTime }}</span>
<span>耗时:{{ getTimeStr(item.answerTimeSum, 'mm') }}</span>
</div>
</div></div
>
</template>
</van-list>
</div>
</van-pull-refresh>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { getFileHttpUrl, getTimeStr } from '/@/utils/compUtils.ts';
import RankingListPng from '/@/assets/images/psychiology/answer/ranking_list.png';
import RankingPng from '/@/assets/images/psychiology/answer/ranking.png';
import GoldPng from '/@/assets/images/psychiology/answer/gold.png';
import SilverPng from '/@/assets/images/psychiology/answer/silver.png';
import BronzePng from '/@/assets/images/psychiology/answer/bronze.png';
import { getPlanRecord } from '/@/views/23psychology/answer/rankingList/rankingList.api';
import { showFailToast } from 'vant';
import { useRoute } from 'vue-router';
const route = useRoute();
const list = ref<any[]>([]); // 列表
const refreshing = ref(false); // 刷新状态
const loading = ref(false); // loading 状态
const finished = ref(false); // 是否完成
const error = ref(false); // 是否报错
const meList = ref<any>();
const pageInfo = ref({
pageNo: 1,
pageSize: 10,
});
async function getMeList() {
const data = await getPlanRecord({ planId: route.query.planId });
console.log(data.result.selfRecord, 123);
meList.value = data.result;
}
getMeList();
function onLoad() {
if (refreshing.value) {
list.value = [];
refreshing.value = false;
finished.value = false;
error.value = false;
loading.value = true;
pageInfo.value.pageNo = 1;
}
loading.value = true;
getPlanRecord({ planId: route.query.planId, ...pageInfo.value })
.then((res) => {
const { success, result, message } = res;
if (success) {
list.value = result;
if (result.joinUserList.length === 0 || result.joinUserList.length < pageInfo.value.pageSize) {
finished.value = true;
} else {
pageInfo.value.pageNo += 1;
}
} else {
error.value = true;
showFailToast(message);
}
})
.catch(() => {
error.value = true;
})
.finally(() => {
loading.value = false;
});
}
function onRefresh() {
refreshing.value = true;
onLoad();
}
onLoad();
</script>
<style lang="less" scoped>
.box {
height: 100vh;
position: relative;
background: #eff2f5;
.top-img {
height: 150px;
background: #21bebd;
position: relative;
img {
height: 40px;
position: absolute;
bottom: 40px;
right: 0;
}
}
.ranking-box {
background: #fff;
height: calc(100vh - 140px);
position: absolute;
width: calc(100% - 32px);
top: 120px;
margin: 0 16px;
border-radius: 8px;
padding: 16px 0;
.ranking-top {
position: relative;
padding: 0 16px;
.left {
.one {
display: inline-block;
font-size: 25px;
font-style: oblique;
color: #252535;
font-weight: bold;
transform: translateX(0) skewX(-10deg);
transform-origin: 0 0;
position: relative;
.under {
display: inline-block;
position: absolute;
width: 100%;
height: 15px;
bottom: 5px;
left: 0;
z-index: -1;
background: linear-gradient(180deg, transparent, #21bebd);
}
}
}
.right {
position: absolute;
top: -80px;
right: 0;
height: 120px;
width: 150px;
img {
display: inline-block;
width: 100%;
height: 100%;
}
}
}
.ranking-center {
display: flex;
padding: 0 16px;
span {
width: 15%;
text-align: center;
color: #77849e;
}
}
.ranking-bottom {
height: calc(100% - 60px);
.ranking {
display: flex;
align-items: center;
padding: 16px;
background: #fff;
border-bottom: 1px solid #eaecf1;
.fraction {
width: 12%;
text-align: center;
.medal {
width: 72px;
height: 42px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
margin-left: -16px;
img {
display: inline-block;
width: 20px;
}
}
.gold {
background: linear-gradient(90deg, #fffcf7 0%, #ffedcc 100%);
}
.silver {
background: linear-gradient(90deg, #fdfdfd 0%, #eaeaea 100%);
}
.bronze {
background: linear-gradient(90deg, #fdfdfd 0%, #ffe0c9 100%);
}
}
.picture {
width: 42px;
height: 42px;
border-radius: 50%;
img {
width: 100%;
height: 100%;
display: inline-block;
border-radius: 50%;
}
}
.personal {
flex: 1;
margin-left: 10px;
.top {
display: flex;
justify-content: space-between;
.top-left {
.line {
display: inline-block;
width: 1px;
height: 10px;
margin: 0 8px;
background: #e6e6ea;
}
.dept {
color: #77849e;
}
.my {
padding-left: 5px;
color: #21bebd;
}
}
}
.bottom {
display: flex;
justify-content: space-between;
color: #b6b6b6;
}
}
}
}
}
}
</style>
@@ -0,0 +1,73 @@
<template>
<div class="outer-d">
<div class="top-button">
<van-tabs v-model:active="active" type="card" sticky>
<van-tab title="全部">
<task-list status="0" />
</van-tab>
<van-tab title="已参加">
<task-list status="1" />
</van-tab>
<van-back-top />
</van-tabs>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import TaskList from '/@/views/23psychology/answer/task/taskList.vue';
const active = ref();
</script>
<style scoped lang="less">
.outer-d {
background-color: #f5f7fb;
height: 100%;
overflow: auto;
}
div {
box-sizing: border-box;
}
:deep(.van-tabs__wrap) {
background-color: #fff;
padding: 10px;
height: auto;
}
:deep(.van-tab--card) {
border: none;
}
:deep(.van-tabs__nav--card) {
height: auto;
border-width: 2px;
border-color: #e8ebf2ff !important;
}
:deep(.van-tab--active) {
color: #252535 !important;
background-color: #ffffff !important;
}
:deep(.van-tab--card) {
color: #77849eff;
background-color: #f3f5f8;
padding: 10px 0;
}
:deep(.van-tabs__content) {
padding: 0 20px 20px;
}
.top-button {
height: 100%;
:deep(.van-tabs) {
height: 100%;
}
}
:deep(.van-tabs__content) {
height: calc(100% - 64px) !important;
}
:deep(.van-tab__panel) {
height: 100%;
}
</style>
@@ -0,0 +1,171 @@
<template>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<div style="height: 100%; overflow: auto">
<van-list
v-model:loading="loading"
:finished="finished"
:finished-text="list.length > 0 ? '没有更多了' : ''"
@load="onLoad"
v-model:error="error"
error-text="请求失败点击重新加载"
>
<template v-if="list.length > 0">
<div v-for="it in list" :key="`collapse${it}`">
<div class="top" v-if="it" @click.native="toDetail(it)">
<div class="time">
<div>
<van-icon name="clock-o" size="16" color="#21BEBD" />
<span> {{ moment(it?.startTime).format('YYYY.MM.DD') }} ~ {{ moment(it?.endTime).format('MM.DD') }}</span>
</div>
<div :style="{ color: it?.status == 0 ? '#F36510' : '#52C41A' }">
{{ it?.userState == 1 ? '已参加' : '' }}
</div>
</div>
<div class="content">
<div class="title">
<img :src="it?.state == '1' ? isProgressPng : isOverPng" alt="" />
<span>{{ it?.planName }}</span>
</div>
<div :style="{ marginBottom: it?.questionCategory ? '10px' : '0' }">
<span
style="color: #21bebd; margin-right: 5px"
v-for="item in it?.questionCategory ? it?.questionCategory.split(',') : []"
>
#{{ item }}
</span>
</div>
<div class="info">
已参加{{ it?.actualJoinNumber || 0 }}人
<span style="color: #b6b6b6; margin-left: 30px">
共{{ it?.questionSum }}道题
<span style="margin-left: 10px"> 答题时长为{{ it?.maxLimitTime }}分钟 </span>
</span>
</div>
</div>
</div>
</div>
</template>
<div class="no-data" v-if="list.length === 0 && finished">
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
</van-list>
</div>
</van-pull-refresh>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { getPlanListApi } from '/@/views/23psychology/psychic/psychologyApi';
import { showFailToast } from 'vant';
import { useRouter } from 'vue-router';
import isProgressPng from '/@/assets/images/psychiology/isProgress.png';
import isOverPng from '/@/assets/images/psychiology/isOver.png';
import moment from 'moment';
import Empty from '/@/components/Empty.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
const props = defineProps({
status: {
type: String,
default: () => '0',
},
});
const list = ref<any[]>([]); // 列表
const refreshing = ref(false); // 刷新状态
const loading = ref(false); // loading 状态
const finished = ref(false); // 是否完成
const error = ref(false); // 是否报错
const pageInfo = ref({
pageNo: 1,
pageSize: 10,
});
const router = useRouter();
function toDetail(it: any) {
router.push({
path: '/taskDetails',
query: { id: it?.id },
});
}
function onLoad() {
if (refreshing.value) {
list.value = [];
refreshing.value = false;
finished.value = false;
error.value = false;
loading.value = true;
pageInfo.value.pageNo = 1;
}
loading.value = true;
getPlanListApi({ ...pageInfo.value, type: props.status })
.then((res) => {
const { success, result, message: msg } = res;
if (success) {
list.value = result;
if (result.length === 0 || result.length < pageInfo.value.pageSize) {
finished.value = true;
} else {
pageInfo.value.pageNo += 1;
}
} else {
error.value = true;
showFailToast(msg);
}
})
.catch(() => {
error.value = true;
})
.finally(() => {
loading.value = false;
});
}
function onRefresh() {
refreshing.value = true;
onLoad();
}
onLoad();
</script>
<style scoped lang="less">
.no-data {
width: 100%;
padding-top: 50%;
}
.top {
background: #fff;
border-radius: 8px;
margin-top: 20px;
.time {
font-size: 15px;
border-bottom: 1px solid #eaecf1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
span {
margin-left: 8px;
color: #77849e;
}
}
.content {
padding: 15px;
.title {
font-size: 17px;
color: #252535;
font-weight: bold;
margin-bottom: 10px;
img {
display: inline-block;
width: 50px;
margin-right: 7px;
}
}
.info {
font-size: 14px;
color: #77849e;
}
}
}
</style>
@@ -0,0 +1,244 @@
<template>
<div class="ranking-list">
<div class="title">
<div class="left">
<div class="name">心理知识排名</div>
<div class="number">{{ planRecordList?.joinNumber }}人参加</div>
</div>
<div class="right">总分{{ planRecordList?.totalScore }}</div>
</div>
<div class="title-bottom">
<span style="width: 12%" v-if="planRecordList?.showRanking">排名</span>
<span style="flex: 1; text-align: left">用户</span>
<span style="text-align: right">分数</span>
</div>
<div class="me ranking" v-if="planRecordList?.selfRecord">
<!-- -->
<div class="fraction" v-if="planRecordList?.showRanking">{{ planRecordList?.selfRecord.ranking }}</div>
<div class="picture">
<img
:src="getFileHttpUrl(planRecordList?.selfRecord.avatar) || 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
onerror="this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
alt=""
/>
</div>
<div class="personal">
<div class="top">
<div class="top-left">
<span>{{ planRecordList?.selfRecord.userName }}</span>
<span class="my">()</span>
<span class="line"></span>
<span class="dept">{{ planRecordList?.selfRecord.deptName }}</span>
</div>
<div class="top-right">
<span>{{ planRecordList?.selfRecord.userScore }}</span>
<span></span>
</div>
</div>
<div class="bottom">
<span>{{ planRecordList?.selfRecord.endAnswerTime }}</span>
<span>耗时{{ getTimeStr(planRecordList?.selfRecord.answerTimeSum, 'mm') }}</span>
</div>
</div>
</div>
</div>
<div class="ranking" v-for="(item, index) in planRecordList?.joinUserList" :key="index">
<div class="fraction" v-if="planRecordList?.showRanking">
<span v-if="index == 0" class="medal gold">
<img :src="GoldPng" alt="" />
</span>
<span v-else-if="index == 1" class="medal silver">
<img :src="SilverPng" alt="" />
</span>
<span v-else-if="index == 2" class="medal bronze">
<img :src="BronzePng" alt="" />
</span>
<span v-else>{{ index + 1 }}</span>
</div>
<div class="picture">
<img
:src="getFileHttpUrl(item.avatar) || 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
onerror="this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
alt=""
/>
</div>
<div class="personal">
<div class="top">
<div class="top-left">
<span>{{ item.userName }}</span>
<span class="line"></span>
<span class="dept">{{ item.deptName }}</span>
</div>
<div class="top-right">
<span>{{ item.userScore }}</span>
<span></span>
</div>
</div>
<div class="bottom">
<span>{{ item.endAnswerTime }}</span>
<span>耗时{{ getTimeStr(item.answerTimeSum, 'mm') }}</span>
</div>
</div>
</div>
<div class="openMore" v-if="planRecordList?.joinUserList">
<span type="default" @click="goRanking()">查看全部</span>
</div>
<div v-if="!planRecordList?.joinUserList">
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
</template>
<script setup lang="ts">
import { getFileHttpUrl, getTimeStr } from '/@/utils/compUtils.ts';
import GoldPng from '/@/assets/images/psychiology/answer/gold.png';
import SilverPng from '/@/assets/images/psychiology/answer/silver.png';
import BronzePng from '/@/assets/images/psychiology/answer/bronze.png';
import Empty from '/@/components/Empty.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
import { useRouter } from 'vue-router';
const router = useRouter();
const props = defineProps({
id: {
type: String,
default: '',
},
planRecordList: {
type: Array,
default: () => [],
},
});
function goRanking() {
router.push({
path: '/ranking-list',
query: { planId: props.id },
});
}
</script>
<style lang="less" scoped>
.ranking-list {
padding: 16px;
background: #fff;
border-radius: 8px;
padding-bottom: 0;
.title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
.left {
display: flex;
align-items: center;
.name {
font-weight: bold;
font-size: 17px;
color: #333333;
}
.number {
font-weight: 500;
margin-left: 10px;
font-size: 14px;
color: #77849e;
}
}
.right {
font-weight: bold;
font-size: 14px;
color: #21bebd;
}
}
.title-bottom {
display: flex;
span {
width: 15%;
text-align: center;
color: #77849e;
}
}
.me {
padding: 16px 0px;
margin-bottom: 10px;
}
}
.ranking {
display: flex;
align-items: center;
padding: 16px;
background: #fff;
border-bottom: 1px solid #eaecf1;
.fraction {
width: 12%;
text-align: center;
.medal {
width: 72px;
height: 42px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
margin-left: -16px;
img {
display: inline-block;
width: 20px;
}
}
.gold {
background: linear-gradient(90deg, #fffcf7 0%, #ffedcc 100%);
}
.silver {
background: linear-gradient(90deg, #fdfdfd 0%, #eaeaea 100%);
}
.bronze {
background: linear-gradient(90deg, #fdfdfd 0%, #ffe0c9 100%);
}
}
.picture {
width: 42px;
height: 42px;
border-radius: 50%;
background: pink;
img {
width: 100%;
height: 100%;
display: inline-block;
border-radius: 50%;
}
}
.personal {
flex: 1;
margin-left: 10px;
.top {
display: flex;
justify-content: space-between;
.top-left {
.line {
display: inline-block;
width: 1px;
height: 10px;
margin: 0 8px;
background: #e6e6ea;
}
.dept {
color: #77849e;
}
.my {
padding-left: 5px;
color: #21bebd;
}
}
}
.bottom {
display: flex;
justify-content: space-between;
color: #b6b6b6;
}
}
}
.openMore {
padding: 10px 0;
text-align: center;
background: #fff;
span {
color: #21bebd;
}
}
</style>
@@ -0,0 +1,6 @@
import { get, post } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
//通过id 获取列表
export const getPlan = (params: any) => get(`${prefix}/api/psychology/answer/plan`, params);
export const getPlanRecord = (params: any) => get(`${prefix}/api/psychology/answer/plan/record`, params);
@@ -0,0 +1,285 @@
<template>
<div class="progress" v-if="planList">
<div class="top">
<div class="time">
<div>
<van-icon name="clock-o" size="16" color="#21BEBD" />
<!-- <span> 2024.05.20 ~ 05.28</span>-->
<span> {{ `${planList.startTime} ~ ${dayjs(planList.endTime).format('MM-DD')}` }}</span>
</div>
<div class="state" v-if="planList?.userState == 1">已参加</div>
</div>
<div class="content">
<div class="title">
<img :src="planList?.state == '1' ? isProgressPng : isOverPng" alt="" />
<span>{{ planList?.planName }}</span>
</div>
<div class="info">
<div class="classification">
<span v-for="(item, index) in planList?.questionCategory.split(',')" :key="index">#{{ item }}</span>
</div>
<div class="details">
<span class="number">已参加{{ planList?.actualJoinNumber }}</span>
<span class="information">{{ planList?.questionSum }}道题 答题时长为{{ planList?.maxLimitTime }}分钟</span>
</div>
</div>
</div>
<div class="illustrate">
<span>活动说明</span>
<span class="explain">{{ planList?.description || '--' }}</span>
</div>
</div>
<div class="center" v-if="planList?.userState == 1">
<span>答题时长 {{ getTimeStr(planRecordList?.selfRecord.answerTimeSum, 'mm') }}</span>
<span>答题时间 {{ planRecordList?.selfRecord.endAnswerTime }}</span>
<van-button class="btn" size="small" @click="goReport(planList)">答题报告</van-button>
<img :src="timePng" alt="" />
</div>
<div class="bottom" :style="{ background: planList?.userState == 1 ? '' : '#fff' }">
<RankingList :id="route.query.id" :planRecordList="planRecordList" v-if="planRecordList" />
</div>
</div>
<div class="footer" v-if="planList && planList?.userState == '0'">
<van-button
type="primary"
:class="planList?.state == 1 ? 'footer-true-btn' : 'footer-false-btn'"
:disabled="planList?.state != 1"
@click="show = true"
>
开始答题
</van-button>
</div>
<van-overlay :show="show" @click="show = false">
<div class="wrapper" @click.stop>
<div class="block">
<div class="block-top"> {{ planList?.questionSum }}道题答题时长为{{ planList?.maxLimitTime }}分钟是否确认开始答题 </div>
<div class="block-bottom">
<van-button class="close-btn" @click="show = false">取消</van-button>
<van-button class="go-answer" @click="goAnswer(planList)">开始答题</van-button>
</div>
</div>
</div>
</van-overlay>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getPlan, getPlanRecord } from '/@/views/23psychology/answer/taskDetails/taskDetails.api.ts';
import isProgressPng from '/@/assets/images/psychiology/isProgress.png';
import isOverPng from '/@/assets/images/psychiology/isOver.png';
import timePng from '/@/assets/images/psychiology/answer/time.png';
import RankingList from '/@/views/23psychology/answer/taskDetails/components/rankingList.vue';
import { useRoute, useRouter } from 'vue-router';
import { getTimeStr } from '/@/utils/compUtils.ts';
import { newPageParams, openPage } from '/@/hooks/openPage';
import dayjs from 'dayjs';
const router = useRouter();
let planList = ref();
let planRecordList = ref();
const show = ref(false);
const route = useRoute();
onMounted(async () => {
const data = await getPlan({ id: route.query.id });
const planData = await getPlanRecord({ planId: route.query.id });
planList.value = data.result;
planRecordList.value = planData.result;
});
function goAnswer(value: any) {
show.value = false;
openPage(
'/ps-selfAnswer',
newPageParams({
planId: value?.id,
type: 1,
answerTime: value.maxLimitTime,
affirmBack: 1,
})
);
}
function goReport(value: any) {
router.push({
path: '/answer-Report',
query: { id: value?.paperId },
});
}
</script>
<style lang="less" scoped>
.progress {
padding: 16px;
background: #eaecf1;
min-height: 100vh;
.top {
background: #fff;
border-radius: 8px;
.time {
height: 50px;
font-size: 15px;
border-bottom: 1px solid #eaecf1;
display: flex;
align-items: center;
padding: 16px;
justify-content: space-between;
span {
margin-left: 8px;
color: #77849e;
}
.state {
color: #21bebd;
font-size: 15px;
font-weight: bold;
}
}
.content {
padding: 15px;
border-bottom: 1px solid #eaecf1;
.title {
font-size: 17px;
color: #252535;
font-weight: bold;
margin-bottom: 5px;
img {
display: inline-block;
width: 50px;
margin-right: 7px;
}
}
.info {
font-size: 14px;
color: #252535;
.classification {
color: #21bebd;
margin-bottom: 5px;
span {
margin-right: 10px;
}
}
.details {
.number {
font-weight: 500;
font-size: 14px;
color: #77849e;
}
.information {
margin-left: 20px;
font-weight: 500;
font-size: 14px;
color: #b6b6b6;
}
}
}
}
.illustrate {
padding: 15px;
color: #252535;
line-height: 26px;
font-size: 15px;
.explain {
font-weight: 500;
color: #77849e;
}
}
}
.center {
height: 110px;
background: linear-gradient(180deg, #21bebd 0%, #87d8d7 100%);
border-radius: 10px;
margin: 16px 0;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-around;
color: #fff;
padding: 10px 20px;
position: relative;
font-size: 14px;
.btn {
border-radius: 5px;
font-weight: bold;
font-size: 15px;
color: #21bebd;
line-height: 21px;
}
img {
display: inline-block;
width: 78px;
position: absolute;
right: 5%;
bottom: 0;
}
}
.bottom {
margin-top: 16px;
border-radius: 8px;
.title {
font-weight: bold;
font-size: 17px;
color: #333b42;
line-height: 22px;
}
}
}
.footer {
position: fixed;
bottom: 0;
background: #fff;
width: 100%;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
.footer-true-btn {
width: 70%;
border-radius: 20px;
background: #21bebd;
border-color: #21bebd;
}
.footer-false-btn {
width: 70%;
border-radius: 20px;
color: #fff;
background: #b6b6b6;
border-color: #b6b6b6;
}
}
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.block {
width: 80%;
height: 170px;
background-color: #fff;
/* border: 8px; */
border-radius: 8px;
display: flex;
flex-direction: column;
.block-top {
flex: 1;
height: 40px;
font-size: 15px;
color: #333333;
line-height: 28px;
text-align: center;
padding: 32px 42px;
}
.block-bottom {
.close-btn {
width: 50%;
border: none;
background: #e6e6ea;
border-radius: 0 0 0 8px;
}
.go-answer {
width: 50%;
border: none;
background: #21bebd;
border-radius: 0 0 8px 0;
color: #fff;
}
}
}
</style>
@@ -0,0 +1,258 @@
<template>
<div class="question-box">
<template v-for="(qus, quIndex) in list">
<div class="question-title" :id="surveyCode + 'psychlolgy_' + qus.type" v-if="!hiden.includes(qus?.questionCode)" :key="quIndex">
<div class="title">
<span class="tips" v-if="ifRequire">*</span>
<div class="title-con">
<!-- <span class="qus-title">{{ quIndex + 1 }}</span>-->
<span class="qus-title">{{ qus.title }}</span>
<span class="que-type">{{ questionType(qus.inputType) }}</span>
</div>
</div>
<!-- 单选 -->
<template v-if="qus.inputType === 'radio'">
<div v-for="(item, index) in qus.options" :key="'info' + index">
<div :class="[qus.answer == item.value + '' ? 'blue-b' : 'red-g']" class="item-div" @click="handleRadio(item, quIndex)">
{{ item.name }}
</div>
</div>
</template>
<!-- 多选 -->
<template v-if="qus.inputType === 'check'">
<div v-for="(item, index) in qus.options" :key="'info' + index">
<div
:class="[qus.answer.split(',').includes(item.value + '') ? 'blue-b' : 'red-g']"
class="item-div"
@click="handleCheck(item, quIndex)"
>
{{ item.name }}
</div>
</div>
</template>
<!-- 填空题单个内容 -->
<template v-if="qus.inputType === 'text'">
<div v-if="qus.type == 'actualSleepTime'">
<div class="input-box">
<div class="field-answer">
<van-field
:type="qus.tfNumber ? 'number' : ''"
v-model="qus.answer"
placeholder="请输入"
@input="(e) => handleChange(e, quIndex, qus)"
@blur="(e) => handleBlurText(e, quIndex, qus)"
/>
</div>
<!-- <div class="input-unit">{{ qus.unit }}</div>-->
</div>
</div>
<div class="input-box" v-else>
<div class="field-answer">
<van-field
:type="qus.tfNumber ? 'number' : ''"
v-model="qus.answer"
placeholder="请输入"
@blur="(e) => handleBlurText(e, quIndex, qus)"
/>
</div>
<!-- <div class="input-unit">{{ qus.unit }}</div>-->
</div>
</template>
<!-- 填空题多个内容 -->
<template v-if="qus.inputType === 'inputs'">
<slot name="inputs" v-bind="{ data: qus }"></slot>
</template>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { defineProps, defineExpose, defineEmits } from 'vue';
import { questionType } from '/@/views/23psychology/components/answerTemplateHooks';
import { showToast } from 'vant';
const emit = defineEmits(['radios', 'check', 'inputClear']);
let checkList = ref<any[]>([]);
const props = defineProps({
list: {
type: Array,
},
dataIndex: {
type: Number,
},
ifRequire: {
type: Boolean,
},
hiden: {
type: Object,
},
surveyCode: {
type: String,
default: () => '',
},
});
function handleBlurText(e: any, index: number, chilData: any) {
let data: any[] = props.list as any[];
let value = e.target.value;
if (chilData.hasOwnProperty('tfNumberRange')) {
if (value < 0 || value > 100) {
showToast(`请输入${chilData.tfNumberRange.min}-${chilData.tfNumberRange.max}任意一个数字`);
data[index].answer = '';
emit('inputClear', { dataIndex: props.dataIndex, childData: data });
}
}
}
function handleChange(e: any, index: number, chilData: any) {
let data: any[] = props.list as any[];
let value;
if (e.target.value > 24) {
value = 24;
} else {
value = parseInt(e.target.value);
}
if (chilData.hasOwnProperty('tfNumber')) {
if (value <= 0 || value >= 24) {
showToast(`请输入0到24内任意一个数字`);
data[index].answer = value.toString().substring(0, 2);
emit('inputClear', { dataIndex: props.dataIndex, childData: data });
}
}
}
// 单选
function handleRadio(item: any, index: number) {
let data: any[] = props.list as any[];
data[index].answer = item.value;
emit('radios', { dataIndex: props.dataIndex, childData: data, hidenParams: handleJumpQues(item, index) });
}
// 多选
function handleCheck(item: any, index: number) {
let data: any[] = props.list as any[];
let userAnswer = data[index].answer !== '' ? data[index].answer.split(',').map(Number) : [];
if (userAnswer.includes(item.value)) {
let index = userAnswer.indexOf(item.value);
userAnswer.splice(index, 1);
} else {
userAnswer.push(item.value);
}
data[index].answer = userAnswer.join(',');
emit('check', { dataIndex: props.dataIndex, childData: data });
}
// 跳题
function handleJumpQues(qusItem: any, index: number) {
let data: any[] = props.list as any[];
if (qusItem.hasOwnProperty('hideCode')) {
const numericArray = qusItem.hideCode !== '' ? qusItem.hideCode.split(',').map(Number) : [];
return {
[data[index].type]: numericArray,
};
}
}
// 滚动
function setScrollInto() {
const list: any[] = props.list as [];
let vis = '';
let ind = null;
for (let i = 0; i < list.length; i++) {
if (list[i].answer === '' && props.ifRequire) {
ind = list[i].type;
vis = props.surveyCode + 'psychlolgy_' + ind;
break;
}
}
return vis;
}
defineExpose({
setScrollInto,
});
</script>
<style scoped lang="less">
@import url('/@/views/23psychology/components/psychology.less');
.question-box {
width: 100%;
height: 100%;
.question-title {
padding: 10px 20px;
.title {
color: #333b42;
font-weight: bold;
padding-bottom: 10px;
position: relative;
.tips {
color: #ed2a26;
font-size: 18px;
font-weight: bold;
position: absolute;
top: -2px;
left: -10px;
}
.title-con {
.qus-title {
font-size: 15px;
}
.que-type {
padding-left: 10px;
font-size: 14px;
}
}
}
.item-div {
margin: 10px 0;
padding: 10px 20px;
border-radius: 10px;
}
.red-g {
background-color: #f4f4f4;
}
.blue-b {
background-color: #d2f2f3;
border: 1px solid @primary-color;
color: @primary-color;
}
.radioStyle {
display: block;
height: 32px;
}
.radios {
height: 28px;
}
:deep(.van-checkbox__label) {
height: 70%;
}
.checksInput-box {
width: 100%;
display: flex;
.options-name {
width: 90%;
height: 100%;
}
.checksInput-answer {
width: 100%;
}
:deep(.van-cell) {
border-bottom: 1px solid #e6e6ea;
}
}
.input-box {
width: 100%;
display: flex;
align-items: center;
margin: 6px 0;
.field-answer {
width: 100%;
}
:deep(.van-cell) {
border: 1px solid #e6e6ea;
border-radius: 4px;
}
.input-unit {
width: 20%;
padding-left: 10px;
color: #333333;
}
}
}
}
</style>
@@ -0,0 +1,100 @@
// 判断有几个下划线
function countUnderscores(str: string) {
const matches = str.match(/_/g);
return matches ? matches.length : 0;
}
// inputs类型添加fieldOptions字段
export function addInputFieldOption(quesList: Array<any>) {
const hideParams: any = {};
quesList.map((item) => {
item.childrenQuestions.map((quesItem: any) => {
if (quesItem.inputType === 'inputs') {
quesItem['fieldOptions'] = setFieldOptions(quesItem);
}
if (quesItem.inputType === 'radio') {
const findItem = quesItem.options.find((e: any) => {
if (e?.hideCode && quesItem.answer !== '') {
return [quesItem.answer].includes(e.value);
}
});
if (findItem) {
const findHide = findItem?.hideCode.split(',').map(Number);
hideParams[quesItem.type] = findHide;
}
}
});
});
return { quesList, hideParams };
}
// 设置一个题多个填空,内容回显及添加字段
function setFieldOptions(data: any) {
const number = countUnderscores(data.textInfo);
const options = {};
const answerOption = data.answer !== '' ? data.answer.split(',') : [];
for (let i = 0; i < number; i++) {
const fieldValue = answerOption.length > 0 ? answerOption[i] : '';
// @ts-ignore
options[data.type + i] = fieldValue;
}
return options;
}
/**
* 提交问卷数据
* answerContent 问卷+answer
* queCode 问卷code
* answerDetail type+answer [{"type":"smoke",answer:"444"}]
* @param type
* @param list
* @param startTime 开始时间
* @param endTime 结束时间
*/
export function setSubmitData(type: string, list: Array<any>, startTime: string, endTime: string) {
const questList: Array<any> = [];
let baseQues: Array<any> = [];
list.map((item) => {
const answerDetailList: Array<any> = [];
item.questionList.map((quesItem: any) => {
quesItem.childrenQuestions.map((childItem: any) => {
if (childItem.inputType === 'inputs') {
const fieldList = Object.values(childItem.fieldOptions);
const filterArray = fieldList.filter(Boolean);
childItem.answer = filterArray.length > 0 ? filterArray.join(',') : '';
}
answerDetailList.push({
type: childItem.type,
answer: childItem.answer,
});
});
});
questList.push({
answerContent: JSON.stringify(item),
queCode: item.surveyCode,
answerDetail: JSON.stringify(answerDetailList),
});
baseQues = answerDetailList;
});
let backParams = undefined;
if (type === 'base') {
backParams = {
answerContent: JSON.stringify(baseQues),
};
} else {
backParams = {
questList,
task: '',
startTime,
endTime,
};
}
return backParams;
}
export function questionType(type: string) {
const queType: any = {
radio: '单选题',
check: '多选题',
text: '填空题',
inputs: '填空题',
};
return queType[type];
}
@@ -0,0 +1,14 @@
@primary-color:#21BEBD;
@con-bg:#f5f7fb;
@font-primary-color:#333333;
@font-sub-color:#586275;
.bottom-button(@width:70%,@height:40px,@color:#ffffff,@bg:@primary-color,@borderRadius:20px) {
width: @width;
height: @height;
line-height: @height;
background-color: @bg;
border:1px solid @bg;
border-radius: @borderRadius;
color: @color;
text-align: center;
}
@@ -0,0 +1,8 @@
export function reportsStatus(type: number) {
const params = {
0: 'low-type',
1: 'centre-type',
2: 'tall-type',
};
return params(type);
}
@@ -0,0 +1,184 @@
<template>
<div class="ps-container">
<div class="ps-top-bg"></div>
<div class="ps-con">
<div class="ps-top-text" ref="psText">{{ quesParams.tips }}</div>
<div class="answer-container" :style="psTextHeight" v-if="quesParams.questionList.length > 0">
<div class="question-type" v-for="(item, index) in quesParams.questionList" :key="index">
<div class="type-title">{{ item.name }}</div>
<answer-template
:list="item.childrenQuestions"
:dataIndex="index"
:hiden="getHide()"
:ifRequire="false"
ref="psychology"
@radios="getRadios"
@check="getRadios"
>
<template #inputs="{ data }">
<div class="input-box" v-for="(value, key, index) in data.fieldOptions" :key="index">
<div class="field-answer">
<van-field v-model="data.fieldOptions[key]" :type="data?.tfNumber ? 'number' : ''" placeholder="请输入" />
</div>
</div>
</template>
</answer-template>
</div>
</div>
<div class="no-data" v-else>
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
</div>
<div class="bottom-btn">
<van-button class="btn" type="primary" :disabled="quesParams.questionList.length === 0" @click="handleSubmit">提交</van-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue';
import AnswerTemplate from '/@/views/23psychology/components/AnswerTemplate.vue';
import { setSubmitData, addInputFieldOption } from '/@/views/23psychology/components/answerTemplateHooks';
import { useRouter } from 'vue-router';
import { getBaseQuesApi, submitBaseQuesApi } from '/@/views/23psychology/psychic/psychologyApi';
import { showFailToast, showSuccessToast } from 'vant';
import { destroyPage } from '/@/hooks/openPage';
import { useLoading } from '/@/utils/compUtils';
import Empty from '/@/components/Empty.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
const { loadingSpinner, loadingClose } = useLoading();
const router = useRouter();
let psText = ref(null);
let psTextHeight = ref({});
let psychology = ref(null);
let quesParams = ref({
questionList: [],
result: [],
tips: '',
});
onMounted(() => {
getQuestion();
});
const hide = ref({});
function getHide() {
let result: any[] = [];
if (Object.keys(hide.value).length === 0) return [];
for (let i = 0; i < Object.keys(hide.value).length; i++) {
result = result.concat(hide.value[Object.keys(hide.value)[i]]);
}
return result;
}
async function getQuestion() {
const { code, result } = await getBaseQuesApi({ type: 1 });
if (code === 200) {
quesParams.value.result = result;
const { quesList, hideParams } = addInputFieldOption(result[0].questionList);
hide.value = { ...hideParams };
quesParams.value.questionList = quesList;
quesParams.value.tips = result[0].surveyDesc;
nextTick(() => {
let psHeight = `height:calc(100% - ${psText.value.offsetHeight}px)`;
psTextHeight.value = psHeight;
});
}
}
function handleSubmit() {
loadingSpinner();
let params = setSubmitData('base', quesParams.value.result, '', '');
submitBaseQuesApi(params).then((res: any) => {
if (res.code === 200) {
loadingClose();
destroyPage();
showSuccessToast('提交成功!');
router.go(-1);
} else {
loadingClose();
showFailToast('提交失败!');
}
});
}
function getRadios({ dataIndex, childData, hidenParams }) {
hide.value = { ...hide.value, ...hidenParams };
quesParams.value.questionList[dataIndex].childrenQuestions = childData;
}
</script>
<style scoped lang="less">
@import url('/@/views/23psychology/components/psychology.less');
.ps-container {
width: 100%;
height: 100vh;
background-color: @con-bg;
position: relative;
.ps-top-bg {
width: 100%;
height: 200px;
background: linear-gradient(180deg, @primary-color 20%, rgba(84, 236, 203, 0) 100%);
}
.ps-con {
position: absolute;
top: 20px;
left: 20px;
right: 20px;
width: 90%;
height: calc(100vh - 90px);
background-color: #ffffff;
padding: 20px;
border-radius: 20px;
.ps-top-text {
width: 100%;
color: #586275;
line-height: 26px;
padding-bottom: 10px;
}
.answer-container {
width: 100%;
overflow-y: auto;
.question-type {
margin: 30px 0;
.type-title {
font-weight: bold;
font-size: 16px;
color: @font-primary-color;
}
}
}
.no-data {
height: 100%;
padding-top: 50%;
}
.input-box {
display: flex;
align-items: center;
margin: 6px 0;
.field-answer {
width: 100%;
}
:deep(.van-cell) {
border: 1px solid #e6e6ea;
border-radius: 4px;
}
.input-unit {
width: 20%;
padding-left: 10px;
color: #333333;
}
}
}
.bottom-btn {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
height: 50px;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
.btn {
.bottom-button();
}
}
}
</style>
@@ -0,0 +1,250 @@
<template>
<div class="scale-container">
<div class="scale-con" id="scaleBox" v-if="list.length > 0">
<div class="scale-item" v-for="(item, index) in list" :id="item.surveyCode" :key="index">
<div class="scale-title">
<div>{{ item.surveyName }}</div>
<div>{{ item.surveyDesc }}</div>
</div>
<div class="qus-con">
<div
class="qus-item"
v-for="(quChilds, childsIndex) in item.questionList"
:id="item.surveyCode + '-a-' + index + '-b-'"
:key="childsIndex"
>
<div class="children-title" v-if="quChilds.name">{{ quChilds?.name }}</div>
<answer-template
:hiden="[]"
:survey-code="item.surveyCode + '-a-' + index + '-b-'"
:dataIndex="index"
:list="quChilds?.childrenQuestions"
:ref="
(el) => {
psychologyScale[index + '-' + childsIndex] = el;
}
"
:ifRequire="true"
@inputClear="handleInputClear"
/>
</div>
</div>
</div>
</div>
<div class="no-data" v-else>
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
<div class="bottom-btn">
<van-button class="btn" type="primary" :disabled="list.length === 0" @click="handleSubmit">提交</van-button>
</div>
<Overlay :show="backOver">
<template #content>
<div class="overlay-block">
<div class="block-top">中途退出将不会保存您的答案是否确定退出</div>
<div class="out-btn">
<div class="backBtn canel-out" @click="backOver = false">取消</div>
<div class="backBtn sure-out" @click="backAnswer">退出答题</div>
</div>
</div>
</template>
</Overlay>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import AnswerTemplate from '/@/views/23psychology/components/AnswerTemplate.vue';
import { getBaseQuesApi, submitQuesApi } from '/@/views/23psychology/psychic/psychologyApi';
import { setSubmitData } from '/@/views/23psychology/components/answerTemplateHooks';
import { showFailToast, showSuccessToast, showToast } from 'vant';
import Empty from '/@/components/Empty.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
import Overlay from '/@/views/components/overlay/Overlay.vue';
import moment from 'moment';
import { useLoading } from '/@/utils/compUtils';
import { useInterceptBack } from '/@/hooks/useInterceptAndroid';
import { destroyPage } from '/@/hooks/openPage';
const { loadingSpinner, loadingClose } = useLoading();
let psychologyScale = ref({});
let backOver = ref(false);
const router = useRouter();
const route = useRoute();
const startTime = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
let list = ref([]);
onMounted(() => {
useInterceptBack(1, dropOut);
getQuestion(route.query?.codes);
});
function dropOut() {
backOver.value = true;
}
function backAnswer() {
backOver.value = false;
try {
destroyPage();
} catch {
router.go(-1);
}
}
async function getQuestion(codes: string) {
const { code, result } = await getBaseQuesApi({ type: codes });
if (code === 200) {
list.value = result;
}
}
function handleInputClear({ dataIndex, childData }) {
list.value[dataIndex].childrenQuestions = childData;
}
function handleSubmit() {
let dataParams = Object.entries(psychologyScale.value);
for (const value in dataParams) {
let [key, element] = dataParams[value];
const t: string = element.setScrollInto();
if (t) {
showToast('请完成所有题目');
return (document.getElementById('scaleBox').scrollTop =
document.getElementById(t.substring(0, t.indexOf('-a-'))).offsetTop + document.getElementById(t).offsetTop);
break;
}
}
loadingSpinner();
let endTime = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
let params = setSubmitData('others', list.value, startTime, endTime);
useInterceptBack(0);
submitQuesApi({ ...params, taskId: route.query.taskId }).then((res: any) => {
if (res.code === 200) {
loadingClose();
showSuccessToast('提交成功!');
router.replace({
path: '/ps-result',
query: {
id: res.message,
},
});
} else {
loadingClose();
showFailToast('提交失败!');
}
});
}
</script>
<style scoped lang="less">
@import url('/@/views/23psychology/components/psychology.less');
.scale-container {
width: 100%;
height: 100vh;
background-color: @con-bg;
position: relative;
.overlay-block {
width: 80%;
height: 170px;
background-color: #fff;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-items: center;
.block-top {
width: 100%;
height: 70%;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
color: #333333;
padding: 0 30px;
text-align: center;
}
.block-bottom {
width: 100%;
padding: 20px;
text-align: center;
border-radius: 0 0 8px 8px;
color: #ffffff;
background-color: @primary-color;
}
.out-btn {
width: 100%;
display: flex;
.backBtn {
width: 50%;
text-align: center;
padding: 20px;
}
.canel-out {
background-color: #e6e6ea;
color: #333333;
border-bottom-left-radius: 8px;
}
.sure-out {
color: #ffffff;
background-color: @primary-color;
border-bottom-right-radius: 8px;
}
}
}
.scale-con {
width: 100%;
height: calc(100vh - 60px);
overflow-y: auto;
padding: 20px;
scroll-behavior: smooth;
.scale-item {
margin: 20px 0;
border-radius: 12px;
position: relative;
.scale-title {
padding: 20px;
background-color: #c8f5f5;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
div:nth-child(1) {
font-weight: bold;
color: @font-primary-color;
font-size: 16px;
}
div:nth-child(2) {
color: @font-sub-color;
padding-top: 8px;
}
}
.qus-con {
margin-top: -6px;
background-color: #ffffff;
border-radius: 12px;
.qus-item {
margin: 20px 0;
.children-title {
text-align: center;
font-weight: bold;
padding: 20px 0;
color: @font-primary-color;
}
}
}
}
}
.no-data {
width: 100%;
height: calc(100vh - 60px);
padding-top: 50%;
}
.bottom-btn {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
height: 50px;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
.btn {
.bottom-button();
}
}
}
</style>
@@ -0,0 +1,164 @@
<template>
<div class="evaluation-container">
<div class="top-bg">
<div class="top-con">
<div>自主评估</div>
<div>请选择此次测评的量表可一次选择多个量表</div>
</div>
<div class="top-icon"></div>
</div>
<div class="content">
<template v-if="list.length > 0">
<div class="con-item" v-for="(item, index) in list">
<div class="number">{{ index + 1 }}</div>
<div class="con-left">
<div class="name">{{ item.name }}</div>
<div class="describe">{{ item.describe }}</div>
</div>
<van-checkbox shape="square" checked-color="#21BEBD" v-model="item.checked" />
</div>
</template>
<div class="no-data" v-else>
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
</div>
<div class="bottom-btn">
<van-button class="btn" :disabled="list.length === 0" type="primary" @click="start">开始测评</van-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { getCodeApi } from '/@/views/23psychology/psychic/psychologyApi';
import { showToast } from 'vant';
import Empty from '/@/components/Empty.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
import { newPageParams, openPage } from '/@/hooks/openPage';
const router = useRouter();
let list = ref([]);
onMounted(() => {
getList();
});
async function getList() {
const { code, result: result } = await getCodeApi();
if (code === 200) {
list.value = result;
}
}
function start() {
let filterCheck = list.value.filter((e) => e?.checked);
if (filterCheck.length === 0) {
showToast('请选择测评量表');
return;
}
const codesList = filterCheck.map((item) => item?.code);
openPage(
'/scale-questionnaire',
newPageParams({
codes: codesList.join(','),
affirmBack: 1,
})
);
}
</script>
<style scoped lang="less">
@import url('/@/views/23psychology/components/psychology.less');
.evaluation-container {
width: 100%;
height: 100vh;
background-color: @con-bg;
position: relative;
overflow: hidden;
.top-bg {
width: 100%;
height: 200px;
background: linear-gradient(180deg, @primary-color 20%, rgba(84, 236, 203, 0) 100%);
display: flex;
flex-direction: column;
justify-content: start;
position: relative;
margin-bottom: 20px;
.top-con {
width: 65%;
padding-top: 24px;
padding-left: 40px;
color: #ffffff;
div:nth-child(1) {
font-size: 22px;
font-weight: bold;
font-style: italic;
}
div:nth-child(2) {
padding-top: 6px;
}
}
.top-icon {
position: absolute;
right: 45px;
bottom: 70px;
width: 80px;
height: 76px;
background: url('/@/assets/images/psychiology/scaleIcon.png') no-repeat;
background-size: 100% 100%;
}
}
.content {
width: 90%;
height: calc(100% - 200px);
position: absolute;
left: 20px;
right: 20px;
top: 125px;
overflow-y: auto;
.con-item:nth-child(1) {
margin-top: 15px;
}
.con-item {
padding: 20px;
margin: 15px 0;
background-color: #ffffff;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: space-between;
.number {
font-size: 22px;
font-weight: bold;
color: @primary-color;
}
.con-left {
width: 80%;
.name {
color: @font-primary-color;
font-weight: bold;
font-size: 16px;
}
.describe {
color: @font-sub-color;
}
}
}
}
.no-data {
height: 100%;
padding-top: 50%;
}
.bottom-btn {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
height: 50px;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
.btn {
.bottom-button(70%);
}
}
}
</style>
@@ -0,0 +1,122 @@
<template>
<div v-for="(item, index) in dataValue" :key="index">
<div class="que">
<div class="que-title"> ({{ index + 1 }}) {{ item.queName }}({{ item.queCode }})</div>
<div class="infomation">{{ item.description }}</div>
<van-button
type="primary"
:color="states !== '1' ? '#B6B6B6' : '#21BEBD'"
class="btn"
v-if="!item.result"
@click="goWrite(item)"
:disabled="states !== '1'"
>填写</van-button
>
<div style="color: #21bebd" v-if="item.result">已填写</div>
</div>
<div class="over" v-if="item.result" @click="goViewHistory(item)">
<div>
<div class="time">评估时间{{ item?.date }}</div>
<div class="comment" :style="{ color: item.queColor }">{{ item?.result }}</div>
</div>
<div>
<img :src="BackPng" alt="" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import BackPng from '/@/assets/images/psychiology/back.png';
import { useRoute, useRouter } from 'vue-router';
import { newPageParams, openPage } from '/@/hooks/openPage';
const router = useRouter();
const route = useRoute();
const props = defineProps({
dataValue: {
type: Array,
default: () => [],
},
states: {
type: String,
default: '0',
},
});
function goViewHistory(record: any) {
router.push({
path: '/ps-result',
query: {
id: record.totalId,
},
});
}
function goWrite(record: any) {
openPage(
'/scale-questionnaire',
newPageParams({
codes: record?.queCode,
taskId: route.query.id,
affirmBack: 1,
})
);
// router.push({
// path: '/scale-questionnaire',
// query: {
// codes: record?.queCode,
// taskId: route.query.id,
// affirmBack: 1,
// },
// });
}
</script>
<style lang="less" scoped>
.que {
background: #ffffff;
border-radius: 5px;
padding: 16px;
border: 1px solid #e6e6ea;
margin-top: 10px;
}
.btn {
width: 73px;
height: 29px;
background: #21bebd;
border-radius: 4px;
margin-top: 10px;
}
.que-title {
font-weight: bold;
font-size: 16px;
color: #333333;
}
.infomation {
font-weight: 500;
font-size: 14px;
color: #586275;
line-height: 30px;
}
.over {
background: #f3f5f9;
border-radius: 10px;
padding: 16px;
margin-top: 10px;
display: flex;
align-items: center;
justify-content: space-between;
.time {
font-size: 14px;
color: #586275;
line-height: 21px;
}
.comment {
font-weight: bold;
margin-top: 8px;
font-size: 16px;
color: #ed2a26;
line-height: 26px;
}
img {
display: inline-block;
width: 6px;
}
}
</style>
@@ -0,0 +1,5 @@
import { get } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
//通过id 获取列表
export const getPlanById = (params: any) => get(`${prefix}/app/psychology/psychologyPlan/queryById`, params);
@@ -0,0 +1,91 @@
<template>
<div class="progress" v-if="infoData">
<div class="top">
<div class="time">
<van-icon name="clock-o" size="16" color="#21BEBD" />
<span> {{ infoData?.startTime }} ~ {{ infoData?.endTime }}</span>
</div>
<div class="content">
<div class="title">
<img :src="infoData.state == '1' ? isProgressPng : isOverPng" alt="" />
<span>{{ infoData?.planName }}</span>
</div>
<div class="info">{{ infoData?.description }}</div>
</div>
</div>
<div class="bottom" v-if="dataValue">
<div class="title">测评量表</div>
<Questionnaire :dataValue="dataValue" :states="infoData.state"></Questionnaire>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import isProgressPng from '/@/assets/images/psychiology/isProgress.png';
import isOverPng from '/@/assets/images/psychiology/isOver.png';
import Questionnaire from '/@/views/23psychology/psychic/progress/components/questionnaire.vue';
import { getPlanById } from '/@/views/23psychology/psychic/progress/progress.api';
import { useRoute } from 'vue-router';
const dataValue = ref([]);
const infoData = ref();
const route = useRoute();
onMounted(async () => {
const data = await getPlanById({ id: route.query.id });
infoData.value = data.result;
dataValue.value = infoData.value.queList;
});
</script>
<style lang="less" scoped>
.progress {
padding: 16px;
background: #eaecf1;
min-height: 100vh;
.top {
background: #fff;
border-radius: 8px;
.time {
height: 50px;
font-size: 15px;
border-bottom: 1px solid #eaecf1;
display: flex;
align-items: center;
padding: 16px;
span {
margin-left: 8px;
color: #77849e;
}
}
.content {
padding: 15px;
.title {
font-size: 17px;
color: #252535;
font-weight: bold;
margin-bottom: 10px;
img {
display: inline-block;
width: 50px;
margin-right: 7px;
}
}
.info {
font-size: 14px;
color: #252535;
}
}
}
.bottom {
padding: 16px;
background: #fff;
margin-top: 16px;
border-radius: 8px;
.title {
font-weight: bold;
font-size: 17px;
color: #333b42;
line-height: 22px;
}
}
}
</style>
@@ -0,0 +1,33 @@
import { get, post } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
// 获取问卷
export const getBaseQuesApi = (params: any) => get(`${prefix}/api/psychology/psychologyEvaluateBase/getPsychologyEvaluateQuestion`, params);
// 获取量表
export const getCodeApi = (params: any) => get(`${prefix}/psychology/psychologyPlan/getQueCode`, params);
// 量表提交问卷
export const submitQuesApi = (params: any) => post(`${prefix}/api/psychology/psychologyEvaluateBase/submitPsychologyEvaluate`, params);
// 自主评估历史
export const selectPsychologyEvaluateListApi = (params: any) =>
get(`${prefix}/api/psychology/psychologyEvaluateBase/selectPsychologyEvaluateList`, params);
export const selectPsychologyEvaluateInfoApi = (params: any) =>
get(`${prefix}/api/psychology/psychologyEvaluateBase/selectPsychologyEvaluateInfo`, params);
export const psychologyPlanListApi = (params: any) => get(`${prefix}/app/psychology/psychologyPlan/list`, params);
// 基础问卷提交
export const submitBaseQuesApi = (params: any) => post(`${prefix}/api/psychology/psychologyEvaluateBase/addOrUpdatePsychologyEvaluateBase`, params);
// 心理-知识答题-自主答题-选择题型
export const getCategoryApi = (params: any) => get(`${prefix}/api/psychology/answer/category`, params);
// 心理-知识答题-自主答题-活动列表
export const getPlanListApi = (params: any) => get(`${prefix}/api/psychology/answer/plan/list`, params);
// 心理-知识答题-自主答题-答题历史
export const getAnswerPageApi = (params: any) => get(`${prefix}/api/psychology/answer/page`, params);
// 心理-知识答题-自主答题-答题报告
export const getAnswerReportApi = (params: any) => get(`${prefix}/api/psychology/answer/report`, params);
// 心理-知识答题-自主答题-知识推荐
export const getKnowledgeApi = (params: any) => get(`${prefix}/api/psychology/answer/knowledge/page`, params);
// 心理-知识答题-自主答题-知识推荐详情
export const getKnowledgeDetailApi = (params: any) => get(`${prefix}/api/psychology/answer/knowledge`, params);
@@ -0,0 +1,46 @@
<template>
<div class="report-container">
<SingleReport v-if="info.hasOwnProperty('signleModule')" :list="info?.signleModule" />
<report-more v-else :empty="empty" :info="info" />
</div>
</template>
<script setup lang="ts">
import SingleReport from '/@/views/23psychology/psychic/report/SingleReport.vue';
import { selectPsychologyEvaluateInfoApi } from '/@/views/23psychology/psychic/psychologyApi';
import { showFailToast } from 'vant';
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import ReportMore from '/@/views/23psychology/psychic/report/reportMore.vue';
const info = ref({});
const empty = ref(false);
const route = useRoute();
function init() {
selectPsychologyEvaluateInfoApi({ totalId: route.query.id })
.then((res: any) => {
if (res.success) {
info.value = res.result;
} else {
showFailToast(res.message);
empty.value = true;
}
})
.catch(() => {
empty.value = true;
});
}
init();
</script>
<style scoped lang="less">
@import url('/@/views/23psychology/components/psychology.less');
.report-container {
width: 100%;
height: 100vh;
background-color: @con-bg;
padding: 20px;
overflow-y: auto;
}
</style>
@@ -0,0 +1,98 @@
<template>
<div class="single-container">
<div class="report-item" v-for="(item, index) in props.list || []" :key="index">
<div class="name">{{ item.moduleName }}</div>
<div class="time">评估时间{{ item.createTime ? item.createTime : '-' }}</div>
<div style="display: flex; justify-content: center; margin-top: 10px" v-if="item.moduleName.indexOf('睡眠') === -1">
<div>
<div class="five-pointed-star-bottom" :style="{ backgroundColor: hexToRgb(item.color) }">
<div class="five-pointed-star" style="color: #ffffff" :style="{ backgroundColor: item.color }">
<span> {{ item?.score === '-' ? item?.score : parseFloat(item.score) }}</span>
</div>
</div>
</div>
</div>
<div class="five-pointed-star-text" :style="{ color: item.color }">{{ item.rating }}</div>
<div class="name">指导建议</div>
<div class="tips">{{ item.suggest }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
const props = defineProps({
list: {
type: Array,
default: () => [],
},
});
function hexToRgb(hex: string) {
// 移除十六进制颜色代码中的'#'
let sanitizedHex = hex ? hex.replace('#', '') : '';
// 解析红、绿、蓝值
let r = parseInt(sanitizedHex.substring(0, 2), 16);
let g = parseInt(sanitizedHex.substring(2, 4), 16);
let b = parseInt(sanitizedHex.substring(4, 6), 16);
return `rgb(${r}, ${g}, ${b}, 0.2)`;
}
</script>
<style scoped lang="less">
.five-pointed-star-bottom {
position: relative;
width: 160px;
height: 160px;
clip-path: polygon(50% 0%, 100% 35%, 80% 90%, 20% 90%, 0% 35%);
display: flex;
align-items: center;
justify-content: center;
}
.five-pointed-star-text {
text-align: center;
font-size: 16px;
font-weight: bold;
padding: 10px 0;
}
.five-pointed-star {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 120px;
height: 120px;
clip-path: polygon(50% 0%, 100% 35%, 80% 90%, 20% 90%, 0% 35%);
> span {
font-size: 30px;
font-weight: bold;
margin-bottom: 2%;
}
}
.name {
font-size: 17px;
font-weight: bold;
}
.time {
font-size: 16px;
color: rgba(88, 98, 117, 1);
}
.time,.tips{
padding-top: 4px;
}
.single-container {
width: 100%;
height: 100%;
.report-item {
margin-top: 20px;
padding: 20px;
background-color: #ffffff;
border-radius: 12px;
}
.report-item:nth-child(1) {
margin-top: 0;
}
}
</style>
@@ -0,0 +1,226 @@
<template>
<div class="outer-d">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<div style="height: 100%; overflow: auto">
<template v-if="list.length > 0">
<van-list
v-model:loading="loading"
:finished="finished"
@load="onLoad"
:finished-text="list.length > 0 ? '没有更多了' : ''"
v-model:error="error"
error-text="请求失败点击重新加载"
>
<template v-for="it in vellArray" :key="`collapse${it}`">
<van-collapse v-model="activeNames">
<van-collapse-item :title="it" :name="it">
<template v-for="item in list" :key="item">
<div
class="van-collapse-item-d"
v-if="moment(item.createTime).format('YYYY').indexOf(it) !== -1"
:title="item"
@click="toReports(item)"
>
<div class="timer"> 评估时间:{{ moment(item.createTime).format('YYYY-MM-DD HH:mm:ss') }} </div>
<!-- <div class="middle-d" v-if="item?.queModules && item?.queModules.length > 0">-->
<!-- <div v-for="(t, i) in item?.queModules || []" :key="`queModules${i}`">-->
<!-- <span>{{ i + 1 }}</span>-->
<!-- <span>{{ t || '-' }}</span>-->
<!-- </div>-->
<!-- </div>-->
<div class="bottom-d" v-if="item?.list && item?.list.length > 0">
<div class="bottom-d-item" v-for="(t, i) in item?.list || []" :key="`list${i}`">
<div>{{ t.moduleName || '-' }}</div>
<div>{{ t.rating || '-' }}</div>
</div>
</div>
</div>
</template>
</van-collapse-item>
</van-collapse>
</template>
</van-list>
</template>
<div v-if="list.length === 0 && finished" class="empty-d">
<!-- <van-empty description="暂无数据" />-->
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
</div>
</van-pull-refresh>
<van-back-top />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { selectPsychologyEvaluateListApi } from '/@/views/23psychology/psychic/psychologyApi';
import moment from 'moment';
import Empty from '/@/components/Empty.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
import { useRouter } from 'vue-router';
const activeNames = ref<any[]>([]);
const list = ref<any[]>([]); // 列表
const refreshing = ref(false); // 刷新状态
const loading = ref(false); // loading 状态
const finished = ref(false); // 是否完成
const error = ref(false); // 是否报错
const vellArray = ref<any[]>([]);
const pageInfo = ref({
pageNo: 1,
pageSize: 10,
});
const router = useRouter();
function toReports(item: any) {
router.push({
path: '/ps-result',
query: { id: item.totalId },
});
}
function onLoad() {
if (refreshing.value) {
list.value = [];
vellArray.value = [];
activeNames.value = [];
refreshing.value = false;
finished.value = false;
error.value = false;
loading.value = true;
pageInfo.value.pageNo = 1;
}
if (finished.value) return;
selectPsychologyEvaluateListApi(pageInfo.value)
.then((res: any) => {
if (res.success) {
error.value = false;
if (res.result.length > 0) {
res.result.map((item: any) => {
if (vellArray.value.length === 0) {
vellArray.value = [new Date(item.createTime).getFullYear()];
} else {
if (vellArray.value[vellArray.value.length - 1] !== new Date(item.createTime).getFullYear()) {
vellArray.value.push(new Date(item.createTime).getFullYear());
}
}
});
}
activeNames.value = vellArray.value;
if (res.result.length < pageInfo.value.pageSize || res.result.length === 0) {
finished.value = true;
} else {
pageInfo.value.pageNo += 1;
}
list.value = list.value.concat(res.result);
} else {
error.value = true;
}
loading.value = false;
})
.catch(() => {
error.value = true;
})
.finally(() => {
loading.value = false;
});
}
const onRefresh = () => {
refreshing.value = true;
onLoad();
};
onLoad();
</script>
<style scoped lang="less">
.empty-d {
height: 90%;
padding-top: 50%;
}
:deep(.van-cell__title) {
background-color: #f5f7fb;
flex: none !important;
}
:deep(.van-cell) {
&:after {
display: none;
}
font-size: 16px;
padding-bottom: 5px !important;
color: rgba(119, 132, 158, 1);
background-color: #f5f7fb !important;
}
:deep(.van-collapse-item__content) {
&:after {
display: none;
}
padding: 0 10px 0 15px;
background-color: #f5f7fb !important;
}
:deep(.van-collapse-item) {
&:after {
display: none;
}
}
:deep(.van-hairline--top-bottom) {
&:after {
display: none;
}
}
.outer-d {
height: 100%;
background-color: #f5f7fb;
overflow: auto;
padding: 10px;
}
.van-collapse-item-d {
padding: 20px;
border-radius: 10px;
font-size: 15px;
background: #ffffff;
margin-bottom: 10px;
.timer{
padding-bottom: 10px;
}
}
//.middle-d {
// padding: 10px 0 0;
// > div {
// border-radius: 5px;
// padding: 10px 17px;
// border: 1px solid rgba(230, 230, 234, 1);
// margin-bottom: 10px;
// > :nth-child(1) {
// color: #21bebd;
// font-size: 19px;
// margin-right: 10px;
// font-weight: bold;
// }
// > :nth-child(2) {
// color: #000000;
// font-weight: bold;
// }
// }
//}
.bottom-d {
border: 1px solid rgba(224, 224, 224, 1);
border-bottom: none;
.bottom-d-item {
display: flex;
border-bottom: 1px solid rgba(224, 224, 224, 1);
> div {
padding: 10px 17px;
font-size: 15px;
}
> :nth-child(1) {
background-color: #f0f0f0;
width: 40%;
color: #333333;
}
> :nth-child(2) {
text-align: right;
width: 60%;
}
}
}
</style>
@@ -0,0 +1,163 @@
<template>
<div class="outer-d" v-if="props.info?.questionCodeList">
<div>
<div class="van-collapse-item-d">
<div style="color: rgba(88, 98, 117, 1); font-size: 16px">
评估时间{{ moment(info?.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</div>
<!-- <div style="font-size: 16px; font-weight: bold; color: #000000; padding: 10px 0 12px"> 测评量表 </div>-->
<!-- <div class="middle-d">-->
<!-- <div v-for="(item, i) in props.info?.questionCodeList || []" :key="`questionCodeList${i}`">-->
<!-- <span>{{ i + 1 }}</span>-->
<!-- <span>{{ item }}</span>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
<div v-if="props.info?.moduleList">
<div class="van-collapse-item-d">
<div style="font-size: 16px; font-weight: bold; color: #000000; padding: 10px 0 12px"> 分析模块 </div>
<div class="table-outer-d">
<div class="table-th">
<div>模块名称</div>
<div>计分</div>
<div>等级</div>
</div>
<div v-for="(item, i) in props.info?.moduleList || []" :key="`moduleList${i}`" class="table-td">
<div>{{ item?.moduleName || '-' }}</div>
<div>{{ item?.score === '-' ? item?.score : parseFloat(item.score) }}</div>
<div>{{ item?.rating || '-' }}</div>
</div>
</div>
</div>
</div>
<div v-if="props.info?.moduleSuggestList">
<div class="van-collapse-item-d">
<div style="font-size: 16px; font-weight: bold; color: #000000; padding: 10px 0 0"> 指导意见 </div>
<div class="suggest-d" v-for="(item, i) in props.info?.moduleSuggestList || []" :key="`moduleSuggestList${i}`">
<div :style="{ color: item?.color || '' }">{{ item?.rating || '-' }}</div>
<div>{{ item?.suggest || '-' }}</div>
</div>
</div>
</div>
</div>
<div v-if="props.empty" class="empty-d">
<van-empty description="暂无数据" />
</div>
</template>
<script setup lang="ts">
import moment from 'moment';
const props = defineProps({
info: {
type: Object,
default: () => {},
},
empty: {
type: Boolean,
default: () => false,
},
});
</script>
<style scoped lang="less">
.empty-d {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.outer-d {
height: 100%;
overflow: auto;
background-color: #f5f7fb;
}
.van-collapse-item-d {
padding: 10px;
border-radius: 10px;
background: #ffffff;
margin-bottom: 10px;
//.middle-d {
// > div {
// border-radius: 5px;
// padding: 10px 17px;
// border: 1px solid rgba(230, 230, 234, 1);
// margin-bottom: 10px;
// > :nth-child(1) {
// color: #21bebd;
// font-size: 19px;
// margin-right: 10px;
// font-weight: bold;
// }
// > :nth-child(2) {
// color: #000000;
// font-weight: bold;
// }
// }
//}
}
.table-outer-d {
> div {
display: flex;
width: 100%;
border: 1px solid #e0e0e0ff;
> div {
padding: 5px;
}
}
.table-th {
background-color: rgba(240, 240, 240, 1);
> div {
border-right: 1px solid #e0e0e0ff;
width: calc(100% / 3);
text-align: center;
color: #333333;
&:nth-child(3) {
text-align: left;
}
}
}
.table-td {
border-top: none;
border-right: none;
&:nth-child(1) {
border-top: 1px solid #e0e0e0ff;
}
> div {
display: flex;
align-items: center;
justify-content: center;
border-right: 1px solid #e0e0e0ff;
width: calc(100% / 3);
text-align: center;
&:nth-child(3) {
justify-content: flex-start;
text-align: left;
}
}
}
}
.suggest-d {
background-color: #f3f5f9ff;
border-radius: 20px;
padding: 20px;
margin-top: 10px;
&:nth-child(1) {
margin-top: 0;
}
> div {
&:nth-child(1) {
font-weight: bold;
font-size: 16px;
}
&:nth-child(2) {
font-size: 14px;
color: #77849eff;
}
}
}
</style>
@@ -0,0 +1,73 @@
<template>
<div class="outer-d">
<div class="top-button">
<van-tabs v-model:active="active" type="card" sticky>
<van-tab title="全部">
<task-list />
</van-tab>
<van-tab title="已完成">
<task-list status="1" />
</van-tab>
<van-back-top />
</van-tabs>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import TaskList from '/@/views/23psychology/psychic/task/taskList.vue';
const active = ref();
</script>
<style scoped lang="less">
.outer-d {
background-color: #f5f7fb;
height: 100%;
overflow: auto;
}
div {
box-sizing: border-box;
}
:deep(.van-tabs__wrap) {
background-color: #fff;
padding: 10px;
height: auto;
}
:deep(.van-tab--card) {
border: none;
}
:deep(.van-tabs__nav--card) {
height: auto;
border-width: 2px;
border-color: #e8ebf2ff !important;
}
:deep(.van-tab--active) {
color: #252535 !important;
background-color: #ffffff !important;
}
:deep(.van-tab--card) {
color: #77849eff;
background-color: #f3f5f8;
padding: 10px 0;
}
:deep(.van-tabs__content) {
padding: 0 20px 20px;
}
.top-button {
height: 100%;
:deep(.van-tabs) {
height: 100%;
}
}
:deep(.van-tabs__content) {
height: calc(100% - 64px) !important;
}
:deep(.van-tab__panel) {
height: 100%;
}
</style>
@@ -0,0 +1,171 @@
<template>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<div style="height: 100%; overflow: auto">
<van-list
v-model:loading="loading"
:finished="finished"
:finished-text="list.length > 0 ? '没有更多了' : ''"
@load="onLoad"
v-model:error="error"
error-text="请求失败点击重新加载"
>
<tempalte v-if="list.length > 0">
<div v-for="it in list" :key="`collapse${it}`">
<div class="top" v-if="it" @click.native="toDetail(it)">
<div class="time">
<div>
<van-icon name="clock-o" size="16" color="#21BEBD" />
<span> {{ moment(it?.startTime).format('YYYY.MM.DD') }} ~ {{ moment(it?.endTime).format('MM.DD') }}</span>
</div>
<div :style="{ color: it?.status == 0 ? '#F36510' : '#52C41A' }">
{{ it?.status == 0 ? '待测评' : '已完成' }}
</div>
</div>
<div class="content">
<div class="title">
<img :src="it?.state == '1' ? isProgressPng : isOverPng" alt="" />
<span>{{ it?.planName }}</span>
</div>
<div class="info">已参加{{ it?.actualAttendNumber || 0 }}人</div>
<div style="display: flex; justify-content: space-between; padding-top: 10px">
<span style="color: #252535"> 共包含{{ it?.queCode ? it?.queCode.split(',').length : 0 }}张量表 </span>
<span style="color: #21bebd"> {{ it?.queSum || 0 }}/{{ it?.queCode ? it?.queCode.split(',').length : 0 }} </span>
</div>
<div style="margin-top: 10px">
<van-progress
color="#21BEBD"
stroke-width="8"
pivot-text=""
:percentage="parseInt(((it?.queSum || 0) / (it?.queCode ? it?.queCode.split(',').length : 0)) * 100)"
/>
</div>
</div>
</div>
</div>
</tempalte>
<div class="no-data" v-if="list.length === 0 && finished">
<Empty :url="emptyIcon" :imageSize="[200, 160]" />
</div>
</van-list>
</div>
</van-pull-refresh>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { psychologyPlanListApi } from '/@/views/23psychology/psychic/psychologyApi';
import { showFailToast } from 'vant';
import { useRouter } from 'vue-router';
import isProgressPng from '/@/assets/images/psychiology/isProgress.png';
import isOverPng from '/@/assets/images/psychiology/isOver.png';
import moment from 'moment';
import Empty from '/@/components/Empty.vue';
import emptyIcon from '/@/assets/images/interveneEmpty.png';
const props = defineProps({
status: {
type: String,
default: () => '',
},
});
const list = ref<any[]>([]); // 列表
const refreshing = ref(false); // 刷新状态
const loading = ref(false); // loading 状态
const finished = ref(false); // 是否完成
const error = ref(false); // 是否报错
const pageInfo = ref({
pageNo: 1,
pageSize: 10,
});
const router = useRouter();
function toDetail(it: any) {
router.push({
path: '/psychology-progress',
query: { id: it?.id },
});
}
function onLoad() {
if (refreshing.value) {
list.value = [];
refreshing.value = false;
finished.value = false;
error.value = false;
loading.value = true;
pageInfo.value.pageNo = 1;
}
loading.value = true;
psychologyPlanListApi({ ...pageInfo.value, status: props.status })
.then((res) => {
const { success, result, message: msg } = res;
if (success) {
list.value = result.records;
if (pageInfo.value.pageNo === result.current) {
finished.value = true;
} else {
pageInfo.value.pageNo += 1;
}
} else {
error.value = true;
showFailToast(msg);
}
})
.catch(() => {
error.value = true;
})
.finally(() => {
loading.value = false;
});
}
function onRefresh() {
refreshing.value = true;
onLoad();
}
onLoad();
</script>
<style scoped lang="less">
.no-data {
width: 100%;
padding-top: 50%;
}
.top {
background: #fff;
border-radius: 8px;
margin-top: 20px;
.time {
font-size: 15px;
border-bottom: 1px solid #eaecf1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
span {
margin-left: 8px;
color: #77849e;
}
}
.content {
padding: 15px;
.title {
font-size: 17px;
color: #252535;
font-weight: bold;
margin-bottom: 10px;
img {
display: inline-block;
width: 50px;
margin-right: 7px;
}
}
.info {
font-size: 14px;
color: #77849e;
}
}
}
</style>
+68
View File
@@ -0,0 +1,68 @@
<template>
<div class="down-container">
<div class="ios" @click="down('ios')"></div>
<div class="android" @click="down('android')"></div>
</div>
</template>
<script setup lang="ts">
import { showToast } from 'vant';
function isType() {
let userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (userAgent.match(/iPad/i) || userAgent.match(/iPhone/i) || userAgent.match(/iPod/i)) {
return 'ios';
} else if (userAgent.match(/Android/i)) {
return 'android';
}
}
function androidLoad() {
window.open('https://api.cqygjk.com/health-system/api/anon/sys/toAppDown?pkg=com.sw.healthyclients&type=1');
}
function iosLoad() {
// 响应App store
const appID = '6483208440';
const link = 'itms-apps://itunes.apple.com/app/id' + appID;
window.location.href = 'https://api.cqygjk.com/health-system/api/anon/sys/toAppDown?pkg=com.escortUP.EscortProject&type=2';
}
function down(type: string) {
if (isType() === 'ios') {
if (type === 'ios') {
iosLoad();
} else {
showToast('请选择IOS安装包进行下载!');
}
} else {
if (type === 'ios') {
showToast('请选择Android安装包进行下载!');
} else {
androidLoad();
}
}
}
</script>
<style scoped lang="less">
.down-container {
width: 100%;
height: 100%;
background: url('/@/assets/images/download/bg.png') no-repeat;
background-size: 100% 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.android {
width: 200px;
height: 200px;
background: url('/@/assets/images/download/android.png') no-repeat;
background-size: 100% 100%;
margin-top: 14%;
}
.ios {
width: 200px;
height: 200px;
background: url('/@/assets/images/download/ios.png') no-repeat;
background-size: 100% 100%;
}
}
</style>
@@ -0,0 +1,90 @@
<template>
<div class="drink-box">
<div class="text">
<a-select v-model:value="beerInfo.beerValue" :placeholder="'请选择'" :options="beerType" @change="changeTime"></a-select>
<a-input type="number" v-model:value="beerInfo.valueTime" placeholder="请输入" :min="0" suffix="次/周" @change="changeTime" />
<a-input type="number" v-model:value="beerInfo.valueMach" placeholder="请输入" :min="0" suffix="两/次" @change="changeTime" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const props = defineProps({
beerKeys: String,
beerInfo: {
type: Object,
default: () => {
return {
beerValue: '',
valueTime: '',
valueMach: '',
};
},
},
});
const beerType = ref([
{
label: '红酒',
value: 'wine',
},
{
label: '黄酒',
value: 'yellowWine',
},
{
label: '啤酒',
value: 'beer',
},
{
label: '白酒',
value: 'liquors',
},
{
label: '其他',
value: 'otherWine',
},
]);
const beerInfo = ref({
beerKeys: props.beerKeys,
beerValue: props.beerInfo?.beerValue,
valueTime: props.beerInfo?.valueTime,
valueMach: props.beerInfo?.valueMach,
});
const emit = defineEmits(['change']);
function changeTime() {
emit('change', beerInfo.value);
}
function changeMach() {}
</script>
<style lang="less" scoped>
.drink-box {
.text {
:deep(.ant-select) {
margin: 10px;
height: 54px;
padding: 5px 10px;
display: flex;
align-items: center;
border-radius: 5px;
background: #117474;
border: 1px solid #72bcbc;
color: #83cdcd;
}
:deep(.ant-select-selector) {
background: #117474;
border: none;
}
:deep(.ant-select-arrow) {
color: #83cdcd;
}
:deep(.ant-input-affix-wrapper) {
color: #83cdcd;
border: 1px solid #72bcbc;
}
:deep(.ant-input) {
color: #83cdcd;
}
}
}
</style>
@@ -0,0 +1,82 @@
<template>
<!-- <div>-->
<!-- <div>-->
<!-- <span>{{ topicTitle }}单选</span>-->
<!-- </div>-->
<!-- <a-radio-group v-model:value="checkedList" @change="changeVal">-->
<!-- <template v-for="(item, index) in topicInfo" :key="'info' + index">-->
<!-- <div>-->
<!-- <a-radio :value="item.value">{{ item.title }}{{ item.value }}</a-radio>-->
<!-- </div>-->
<!-- </template>-->
<!-- </a-radio-group>-->
<div>
<span style="font-size: 16px; margin-left: 10px; font-weight: bold; color: #ffffff">{{ topicTitle }}</span>
<template v-for="(item, index) in topicInfo" :key="'info' + index">
<div :class="[checkedListC !== item.code + '' ? 'red-g' : 'blue-b']" class="item-div" @click="clickD(item)"> {{ item.title }}</div>
</template>
</div>
<!-- </div>-->
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
import { propTypes } from '/@/utils/propTypes';
const emit = defineEmits(['changeRVal', 'smoking']);
const props = defineProps({
dataIndex: propTypes.array.def([]), // 下标
pVal: propTypes.string.def(''), // 返回值
pCVal: propTypes.string.def(''), // 返回值
topicTitle: propTypes.string.def(''), // 题目
topicInfo: propTypes.array.def([]), // 选项
orderNo: propTypes.number.def(0), // 选项
});
// 单选的值
const checkedList = ref<string>(props.pVal);
const checkedListC = ref<string>(props.pCVal);
const clickD = (item: object) => {
if (item.code + '' === checkedListC.value) {
checkedList.value = '';
checkedListC.value = '';
} else {
checkedList.value = item.value;
checkedListC.value = item.code + '';
}
emit('changeRVal', { dataIndex: props.dataIndex, answer: checkedList.value, answerCode: checkedListC.value });
if (props.orderNo === 8 && sessionStorage.getItem('type') === '0') {
emit('smoking', { dataIndex: props.dataIndex, answer: checkedList.value, answerCode: checkedListC.value });
}
};
// const checkedList = ref<number>(props.pVal);
//
// const changeVal = (e: any) => {
// checkedList.value = e?.target?.value ?? e;
// emit('changeRVal', { dataIndex: props.dataIndex, checkedList });
// };
</script>
<style scoped lang="less">
.red-g {
border: 1px solid #72bcbc;
color: #83cdcd;
}
.blue-b {
border: 2px solid #bffeff;
color: #bffeff;
}
.item-div {
margin: 10px;
height: 54px;
padding: 5px 10px;
display: flex;
align-items: center;
border-radius: 5px;
background: #117474;
}
.toolbar {
display: none;
}
</style>
@@ -0,0 +1,3 @@
import { get, post } from '/@/views/mobile/api/api';
export const surveySubmit = (params) => post(`health-archives/archives/app/smoking/survey/submit`, params);
@@ -0,0 +1,275 @@
<template>
<div class="box" ref="box">
<div class="title">
<span class="line line-left"></span>
<span class="name">吸烟</span>
<span class="line line-right"></span>
</div>
<div class="content">
<div>
<radio-topic
ref="radioTopic"
topicTitle="1、吸烟状况?"
@changeRVal="changeRVal"
:topicInfo="[
{ title: '吸烟', code: 0 },
{ title: '不吸烟', code: 1 },
{ title: '已戒烟', code: 2 },
]"
/>
</div>
<div class="text">
<span style="font-size: 16px; margin-left: 10px; font-weight: bold; color: #ffffff">2每日根数</span>
<a-input type="number" v-model:value="roots" placeholder="请输入" :min="0" suffix="根/每天" @change="changeSmokeNum" />
</div>
<div class="text">
<span style="font-size: 16px; margin-left: 10px; font-weight: bold; color: #ffffff">3开始吸烟年龄</span>
<a-input type="number" v-model:value="smokingAge" placeholder="请输入" :min="0" suffix="岁" @change="changeAgeNum" />
</div>
<div>
<radio-topic
ref="radioTopic"
topicTitle="4、和您在一起工作的人是否有人吸烟?"
@changeRVal="changePassive"
:topicInfo="[
{ title: '是', code: 1 },
{ title: '否', code: 0 },
]"
/>
</div>
</div>
<div class="title">
<span class="line line-left"></span>
<span class="name">饮酒</span>
<span class="line line-right"></span>
</div>
<div class="content">
<div>
<radio-topic
ref="radioTopic"
topicTitle="1、饮酒状况?"
@changeRVal="changeDrink"
:topicInfo="[
{ title: '饮酒', code: 0 },
{ title: '不饮酒', code: 1 },
{ title: '已戒酒', code: 2 },
]"
/>
</div>
<div class="text">
<span style="font-size: 16px; margin-left: 10px; font-weight: bold; color: #ffffff">2饮酒种类以及频次</span>
<div v-for="(item, index) in Object.keys(beerArr)" :key="index">
<div class="beer">
<span class="beer-one">种类{{ index + 1 }}</span>
<span class="beer-two" @click="handleAddBeer" v-if="index == 0">
<plus-circle-outlined />
添加种类
</span>
<span class="beer-two" v-if="index !== 0" @click="handledeleteBeer(item)"><close-circle-filled /> </span>
</div>
<DrinkType :key="`beer-${item}`" :beerKeys="item" :beerInfo="beerArr[item]" @change="(e) => preData(e, item)"></DrinkType>
</div>
</div>
</div>
<div class="footer">
<a-button class="sub-btn" @click="handleSubmit">提交</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { PlusCircleOutlined, CloseCircleFilled } from '@ant-design/icons-vue';
import RadioTopic from '/@/views/archivesManage/smokeOrDrink/components/radio.vue';
import DrinkType from '/@/views/archivesManage/smokeOrDrink/components/drinkType.vue';
import { ref } from 'vue';
import { surveySubmit } from '/@/views/archivesManage/smokeOrDrink/index.ts';
import { message } from 'ant-design-vue';
import { showToast, showConfirmDialog } from 'vant';
const roots = ref();
const smokingAge = ref();
const radioTopic = ref();
const box = ref();
const beerArr = ref<any>({
'1arr': {
beerValue: '',
valueTime: '',
valueMach: '',
},
});
const beerNum = ref(1);
function changeSmokeNum() {
if (roots.value < 0) {
roots.value = 0;
}
}
function changeAgeNum() {
if (smokingAge.value < 0) {
smokingAge.value = 0;
}
}
const smokingStatus = ref();
function changeRVal(data: object) {
console.log(data);
smokingStatus.value = data.answerCode;
}
const passiveSmoking = ref();
function changePassive(data: object) {
console.log(data);
console.log(data.answerCode);
passiveSmoking.value = data.answerCode;
}
const drinkStatus = ref();
function changeDrink(data: object) {
drinkStatus.value = data.answerCode;
}
function preData(i, v) {
beerArr.value[i?.beerKeys] = i;
}
function handleAddBeer() {
beerNum.value++;
beerArr.value[beerNum.value + 'arr'] = {};
}
async function handleSubmit() {
if (!smokingStatus.value || !roots.value || !smokingAge.value || !passiveSmoking.value || !drinkStatus.value) {
return showToast('问卷未填写完整');
}
const result = {};
for (const key in beerArr.value) {
const item = beerArr.value[key];
const value = item.beerValue;
if (!value) {
return showToast('问卷未填写完整');
}
if (!result[value]) {
result[value] = { frequency: 0, drinkUnit: 0 };
}
result[value].frequency += parseInt(item.valueTime);
result[value].drinkUnit += parseInt(item.valueMach);
}
const params = {
smokingStatus: smokingStatus.value,
roots: roots.value,
smokingAge: smokingAge.value,
passiveSmoking: passiveSmoking.value,
drinkStatus: drinkStatus.value,
...result,
};
showConfirmDialog({
message: '保证问卷填写真实,并确认提交?',
confirmButtonText: '确定',
cancelButtonText: '取消',
getContainer: box.value,
})
.then(async () => {
// on confirm
await surveySubmit(params);
})
.catch(() => {
// on cancel
});
// await surveySubmit(params);
}
function handledeleteBeer(value) {
let b = beerArr.value;
delete b[value];
beerArr.value = b;
}
</script>
<style lang="less" scoped>
.box {
width: 100%;
min-height: 100vh;
background: #117474;
padding: 0 0 15%;
:deep(.van-dialog) {
background: #117474;
}
.title {
display: flex;
align-items: center;
justify-content: center;
.name {
width: 39px;
font-weight: bold;
font-size: 21px;
color: #bffeff;
white-space: nowrap;
margin: 0 10px;
}
.line {
display: inline-block;
width: 125px;
height: 2px;
border-radius: 1px;
}
.line-left {
background: linear-gradient(-90deg, #bffeff 0%, rgba(191, 254, 255, 0) 100%);
}
.line-right {
background: linear-gradient(90deg, #bffeff 0%, rgba(191, 254, 255, 0) 100%);
}
}
.content {
width: 90%;
background: #0c5a5a;
border-radius: 8px;
border: 1px solid #72bcbc;
margin: 10px auto;
min-height: 100px;
padding: 10px;
.text {
:deep(.ant-input-affix-wrapper) {
background: #117474;
height: 54px;
margin: 10px;
height: 54px;
padding: 5px 10px;
width: 95%;
border-radius: 5px;
}
:deep(.ant-input) {
background: #117474;
color: #bffeff;
}
:deep(.ant-input-suffix) {
color: #bffeff;
}
}
.beer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
.beer-one {
color: #ffffff;
}
.beer-two {
color: #bffeff;
}
}
}
.footer {
position: fixed;
bottom: 0;
height: 7%;
background: #0d5a5a;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 10px 0;
.sub-btn {
width: 80%;
height: 90%;
color: #117474;
background: #cfffff;
border-radius: 23px;
}
:deep(.ant-btn:hover, .ant-btn:focus) {
color: #117474;
background: #cfffff;
border-color: #cfffff;
}
}
}
</style>
+8
View File
@@ -0,0 +1,8 @@
import { get, post } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
export const homeApi = (params) => get(`${prefix}/api/health-diabetes/diabetes/glucose/home`, params);
// 历史记录
export const recordApi = (params) => get(`${prefix}/api/health-diabetes/diabetes/glucose/record`, params);
// 录入血糖
export const fillApi = (params) => post(`${prefix}/api/health-diabetes/diabetes/glucose/fill`, params);
+355
View File
@@ -0,0 +1,355 @@
<template>
<div class="bloodGlucose">
<div class="bloodGlucose-1">
<div>
<div>
<div>
<img src="../../assets/images/health-interventions/blood.png" alt="" />
</div>
血糖
</div>
<div :class="descColor">{{ homeInfo.latest.desc !== '' ? homeInfo.latest.desc : '--' }}</div>
</div>
<div>
<div>
<span>{{ homeInfo.latest.value }}</span>
mmol/L
</div>
<div>{{ homeInfo.latest.time !== null ? homeInfo.latest.time : '--' }}</div>
</div>
</div>
<div class="bloodGlucose-2">
<div>指标解析</div>
<div>{{ homeInfo.latest.analyze !== '' ? homeInfo.latest.analyze : '暂无数据' }}</div>
</div>
<div class="bloodGlucose-3">
<div>
<div>近7次记录</div>
<div @click="openMoreRecord">更多记录 &gt;</div>
</div>
<div>
<div ref="chartRef"></div>
</div>
</div>
<div class="vaccine-button">
<div class="button-text" @click="openManualEntry">手动录入</div>
</div>
</div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts';
import { onMounted, ref } from 'vue';
import { homeApi } from './blood';
import { newPageParams, openPage } from '/@/hooks/openPage';
let homeInfo = ref({
latest: {
desc: '',
value: '',
time: '',
analyze: '',
},
lineCharts: {
x: [],
y: [],
},
});
let descColor = ref('');
onMounted(() => {
getHomeInfo();
});
const getHomeInfo = () => {
homeApi({}).then((res: any) => {
if (res.code === 200) {
let { x, y } = res.result.lineCharts;
homeInfo.value = res.result;
setChart(x, y);
setStatus(res.result);
}
});
};
// 血压颜色状态
function setStatus(val: object) {
let desc = val?.latest?.desc;
if (desc !== '') {
switch (desc) {
case '偏高':
descColor.value = 'highColor';
break;
case '正常':
descColor.value = 'normalColor';
break;
case '偏低':
descColor.value = 'lowColor';
break;
}
}
}
// 打开更多记录
const openMoreRecord = () => {
openPage('/bloodGlucoseMoreRecord', newPageParams());
};
// 打开手动录入
const openManualEntry = () => {
openPage('/bloodGlucoseManualEntry', newPageParams());
};
const chartRef = ref<HTMLElement>();
const myChart = ref<any>();
const setChart = (xData, yData) => {
myChart.value = echarts.init(chartRef.value!);
myChart.value.setOption({
graphic: {
type: 'text',
left: 'center',
top: 'center',
silent: true,
invisible: xData.length > 0, // 有数据就隐藏
style: {
fill: '#999',
text: '暂无数据',
fontSize: '15',
},
},
xAxis: {
type: 'category',
data: xData,
axisLine: {
lineStyle: {
color: '#E0E0E0',
},
},
axisTick: {
lineStyle: {
color: '#E0E0E0',
},
},
axisLabel: {
textStyle: {
color: '#333333',
},
},
},
grid: {
top: '5%',
left: '5%',
right: '5%',
bottom: 0,
containLabel: true,
},
yAxis: {
type: 'value',
axisLabel: {
show: true,
textStyle: {
color: '#333333',
},
},
axisLine: {
show: true,
lineStyle: {
color: '#E0E0E0',
},
},
axisTick: {
show: false,
},
},
series: [
{
data: yData,
type: 'line',
symbol: 'circle',
symbolSize: 0,
itemStyle: {
normal: {
lineStyle: {
color: '#13C2C2',
},
},
},
},
],
});
};
</script>
<style scoped lang="less">
@import '../../assets/less/index';
.normalColor {
color: #74b84e;
}
.lowColor {
color: #13c4c4;
}
.highColor {
color: #c93432;
}
.bloodGlucose {
width: 100%;
height: 100vh;
overflow-y: scroll;
padding: 10px 15px 0;
box-sizing: border-box;
background-color: @card-gray;
.bloodGlucose-1 {
width: 100%;
padding: 15px 15px 12px;
box-sizing: border-box;
margin-bottom: 10px;
border-radius: 10px;
background-color: @card-fff;
> div:first-of-type {
width: 100%;
height: 30px;
display: flex;
flex-wrap: nowrap;
margin-bottom: 12px;
justify-content: space-between;
> div:first-of-type {
color: #333333;
height: 30px;
font-size: 13px;
line-height: 30px;
display: flex;
> div {
width: 30px;
height: 30px;
border-radius: 50%;
margin-right: 5px;
img {
width: 100%;
height: 100%;
display: flex;
align-items: center;
}
}
}
> div:last-of-type {
height: 30px;
font-size: 13px;
font-weight: bold;
line-height: 30px;
}
}
> div:last-of-type {
width: 100%;
height: 18px;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
> div:first-of-type {
height: 18px;
font-size: 12px;
line-height: 18px;
font-weight: 500;
color: #333333;
> span {
color: #333333;
font-size: 17px;
font-weight: bold;
margin-right: 3px;
}
}
> div:last-of-type {
color: #999999;
height: 18px;
font-size: 12px;
font-weight: 500;
line-height: 18px;
}
}
}
.bloodGlucose-2 {
width: 100%;
padding: 20px 15px;
box-sizing: border-box;
margin-bottom: 10px;
border-radius: 10px;
background-color: @card-fff;
> div:first-of-type {
width: 100%;
height: 13px;
font-size: 13px;
font-weight: bolder;
color: #333333;
line-height: 26px;
margin-bottom: 18px;
}
> div:last-of-type {
width: 100%;
font-size: 12px;
color: #999999;
line-height: 20px;
}
}
.bloodGlucose-3 {
width: 100%;
border-radius: 10px;
padding: 22px 0 25px;
margin-bottom: 25px;
background-color: @card-fff;
> div:first-of-type {
width: 100%;
height: 14px;
display: flex;
flex-wrap: nowrap;
padding: 0 15px;
box-sizing: border-box;
margin-bottom: 20px;
justify-content: space-between;
> div:first-of-type {
color: #333333;
height: 14px;
font-size: 14px;
line-height: 14px;
}
> div:last-of-type {
color: #999999;
height: 14px;
font-size: 12px;
line-height: 14px;
}
}
> div:last-of-type {
width: 100%;
height: 240px;
> div {
width: 100%;
height: 100%;
}
}
}
.vaccine-button {
height: 40px;
.button-text {
height: 40px;
flex: 1;
.flex-center();
margin: 0 10%;
.footerButton();
}
}
}
</style>
@@ -0,0 +1,241 @@
<template>
<div class="manualEntry">
<div class="manua-container">
<div class="manualEntry-1">
<div>
<div>测量时间</div>
<div>
<div>
<div class="year" @click="openTime(1)">{{ picker.time === '' ? '请选择年月日' : picker.time }}</div>
<div class="time" @click="openTime(2)">{{ picker.divisionTime === '' ? '请选择时分' : picker.divisionTime }}</div>
</div>
</div>
</div>
<div>
<div>血糖</div>
<div>
<div>
<van-field type="number" placeholder="请输入血糖" v-model="manuaNum" @update:model-value="handleInput" />
</div>
<div>mmol/L</div>
</div>
</div>
</div>
<div class="manualEntry-2">
<p>空腹血糖标准值3.9-6.1mmol/L</p>
<p>餐后2小时血糖标准值3.9-7.8mmol/L</p>
</div>
</div>
<div class="vaccine-button">
<div class="button-text" @click="handleSave">保存</div>
</div>
<van-popup :show="picker.show" round position="bottom">
<van-date-picker
v-if="timeType === 1"
title="选择日期"
:maxDate="picker.maxDate"
@cancel="picker.show = false"
@confirm="onConfirmPicker"
/>
<van-time-picker v-else title="选择时间" @cancel="picker.show = false" @confirm="onDivision" />
</van-popup>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { fillApi } from './blood';
import { showSuccessToast, showFailToast } from 'vant';
import moment from 'moment';
import { useLoading } from '/@/utils/compUtils';
import { destroyPage } from '/@/hooks/openPage';
const picker = ref({
show: false,
time: '',
divisionTime: '',
maxDate: new Date(),
});
const { loadingSpinner, loadingClose } = useLoading();
const router = useRouter();
const manuaNum = ref('');
const timeType = ref();
onMounted(() => {
let today = new Date();
let year = moment(today).format('YYYY');
let month = moment(today).format('MM');
let days = moment(today).format('DD');
console.log(year, month, days);
picker.value.maxDate = new Date(year, month, days);
});
function onConfirmPicker(e: { selectedValues: any }) {
let value = e.selectedValues;
picker.value.time = `${value[0]}-${value[1]}-${value[2]}`;
picker.value.show = false;
}
function onDivision(e: { selectedValues: any }) {
let value = e.selectedValues;
picker.value.divisionTime = `${value[0]}:${value[1]}`;
picker.value.show = false;
}
function handleInput(value: number) {
manuaNum.value = value;
}
function handleSave() {
let { time, divisionTime } = picker.value;
if (time === '') {
showFailToast({
message: '请选择年月日',
forbidClick: true,
});
return false;
}
if (divisionTime === '') {
showFailToast({
message: '请选择时分',
forbidClick: true,
});
return false;
}
if (manuaNum.value === '') {
showFailToast({
message: '请输入血糖',
forbidClick: true,
});
return false;
}
loadingSpinner();
fillApi({
fillValue: manuaNum.value,
createTime: `${time} ${divisionTime}`,
}).then((res: any) => {
if (res.code === 200) {
showSuccessToast('保存成功');
loadingClose();
try {
destroyPage();
} catch {
router.go(-1);
}
} else {
showFailToast({
message: res.data.msg,
forbidClick: true,
});
loadingClose();
}
});
}
function openTime(type: number) {
timeType.value = type;
picker.value.show = true;
}
</script>
<style scoped lang="less">
@import '../../assets/less/index';
.manualEntry {
width: 100%;
height: 100vh;
padding-top: 10px;
overflow-y: scroll;
box-sizing: border-box;
background-color: @card-gray;
.manua-container {
height: calc(100vh - 60px);
.manualEntry-1 {
width: 100%;
padding: 0 15px;
margin-bottom: 12px;
box-sizing: border-box;
background-color: @card-fff;
> div {
width: 100%;
height: 50px;
display: flex;
flex-wrap: nowrap;
> div:first-of-type {
width: 100px;
color: #333333;
height: 50px;
font-size: 14px;
line-height: 50px;
}
> div:last-of-type {
flex: 1;
width: 0;
height: 50px;
display: flex;
flex-wrap: nowrap;
> div {
color: #333333;
height: 50px;
display: flex;
line-height: 50px;
font-size: 14px;
&:first-of-type {
flex: 1;
}
.time {
padding-left: 10px;
}
}
input {
width: 100%;
border: none;
outline: none;
font-size: 14px;
background-color: rgba(0, 0, 0, 0);
}
//.arrow:after {
// position: absolute;
// right: 0;
// top: 0;
// bottom: 0;
// width: 10px;
// height: 10px;
// border-left: 2px solid #b6b6b6;
// border-top: 2px solid #b6b6b6;
// transform: rotate(135deg);
//}
}
}
}
.manualEntry-2 {
width: 100%;
height: 40px;
padding: 0 15px;
box-sizing: border-box;
> p {
width: 100%;
height: 20px;
color: #999999;
font-size: 12px;
line-height: 20px;
margin-bottom: 0;
}
}
}
.vaccine-button {
height: 40px;
.button-text {
height: 40px;
flex: 1;
.flex-center();
margin: 0 10%;
.footerButton();
}
}
}
</style>
@@ -0,0 +1,80 @@
<template>
<div class="MoreRecord">
<template v-if="list.length > 0">
<div class="list" v-for="(item, index) in list" :key="index">
<div class="times">{{ item.createTime }}</div>
<div class="item">
<div>
<div>血糖</div>
<div>{{ item.fillValue }}<span>mmo/L</span></div>
</div>
</div>
</div>
</template>
<van-empty v-else description="暂无数据" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { recordApi } from './blood';
const list = ref([]);
getHistory();
function getHistory() {
recordApi({}).then((res: any) => {
list.value = res.result.records;
});
}
</script>
<style scoped lang="less">
@import '../../assets/less/index';
.MoreRecord {
width: 100%;
height: 100vh;
overflow-y: scroll;
background-color: @card-gray;
.list {
width: 100%;
.times {
color: #999999;
width: 100%;
height: 32px;
font-size: 12px;
line-height: 32px;
padding: 0 15px;
box-sizing: border-box;
}
.item {
width: 100%;
background-color: @card-fff;
> div {
width: 100%;
height: 50px;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
padding: 0 15px;
box-sizing: border-box;
> div {
color: #333333;
height: 50px;
font-size: 14px;
line-height: 50px;
> span {
color: #999999;
font-size: 12px;
}
}
}
}
}
}
</style>
@@ -0,0 +1,26 @@
<template>
<van-overlay :show="show">
<div class="wrapper" @click.stop>
<slot name="content"></slot>
</div>
</van-overlay>
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
const props = defineProps({
show: {
type: Boolean,
default: false,
},
});
</script>
<style scoped lang="less">
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
</style>
@@ -0,0 +1,100 @@
<template>
<div class="common-res" :style="content[status].style">
<div class="doctor-img">
<img class="img" :src="doctorImg" alt="" />
</div>
<div class="res-card">
<div class="res-text">您的测评结果</div>
<div class="status" :style="content[status].statusStyle">{{ content[status].text }} </div>
<div class="">测试时间2023-09-11</div>
</div>
</div>
</template>
<script setup lang="ts">
const doctorImg = new URL('/@/assets/images/disease-warning/doctor.png', import.meta.url).href;
const props = defineProps({
status: Number,
});
const content = {
0: {
style: `background: rgba(82, 196, 26, 0.2)`,
statusStyle: 'color: #52c41a',
text: '正常',
},
1: {
style: `background:rgba(250, 219, 20, 0.2)`,
statusStyle: 'color:#FADB14',
text: '低危',
},
2: {
style: `background: rgba(212, 136, 6, 0.2)`,
statusStyle: 'color: #D48806',
text: '中危',
},
3: {
style: `background: rgba(237, 42, 38, 0.2)`,
statusStyle: 'color: #ED2A26',
text: '中高危',
},
4: {
style: `background-color: rgba(237, 42, 38, 0.2);`,
statusStyle: 'color: #ED2A26',
text: '高危',
},
};
</script>
<style scoped lang="less">
.common-res {
height: 210px;
position: relative;
background: rgba(82, 196, 26, 0.2);
.doctor-img {
position: absolute;
top: 20%;
left: 5%;
bottom: 0;
width: 35%;
.img {
width: 100%;
height: 100%;
}
}
.res-card {
position: absolute;
top: 35px;
right: 20px;
width: 195px;
height: 130px;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
color: #999999;
background: #ffffff;
border-radius: 16px;
.res-text {
font-size: 16px;
font-weight: 400;
color: #333333;
}
.status {
font-size: 22px;
font-weight: bold;
color: #52c41a;
margin: 10px 0;
}
.time {
white-space: nowrap;
font-size: 14px;
font-weight: 500;
color: #999999;
}
}
}
</style>
@@ -0,0 +1,31 @@
<template>
<div class="common-title" v-text="text"></div>
</template>
<script setup lang="ts">
const props = defineProps({
text: String,
});
</script>
<style scoped lang="less">
.common-title {
font-size: 16px;
font-weight: bold;
color: #333333;
height: 30px;
display: flex;
align-items: center;
padding-left: 10px;
position: relative;
}
.common-title:before {
content: '';
left: 0;
top: 25%;
position: absolute;
width: 3px;
height: 50%;
background: #21bebe;
border-radius: 2px;
}
</style>
@@ -0,0 +1,197 @@
<template>
<div class="disease-assessment-result">
<div class="result-container">
<CommonRes :status="resStatus" />
<div class="result-con">
<div>
<CommonTit text="评估结果" />
<div class="text-area">{{ resultObj.conclusionInfo.resultDesc }}</div>
</div>
<div>
<CommonTit text="改善建议" />
<div class="text-area" v-for="(item, index) in resultObj.conclusionInfo.tips" :key="index">{{ item }} </div>
</div>
<div>
<CommonTit text="主要危险因素" class="text-area" />
<div>
<div
class="danger-val"
:style="{ display: item.name != null && item.field != 'assessDate' && item.name != '' ? 'flex' : 'none' }"
v-for="(item, index) in resultObj.tableInfo.fields"
:key="index"
>
<div class="img-box">
<div class="img"></div>
<div class="font-333">{{ item.name }}</div>
</div>
<div class="font-666"> {{ item.value }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="bottom-btn" v-if="!route.query.history">
<div class="common-btn history-btn" @click="historicalEvaluation">历史测评</div>
<div class="common-btn again-btn font-fff" @click="aginSelfClick">再测一次</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import CommonTit from './components/commonTit.vue';
import CommonRes from './components/commonRes.vue';
import { get } from '/@/views/mobile/api/api';
import subjectUrl from '/@/views/health/subjectType';
import { showFailToast } from 'vant';
const router = useRouter();
const route = useRoute();
const templateNo = ref('');
const resultObj = ref({
conclusionInfo: {},
tableInfo: {},
});
const resStatus = ref(0); // 评估样式及文字
handleGetRestule();
function handleGetRestule() {
templateNo.value = route.query.templateNo;
let params = { resultId: route.query.resultId };
let findItem = subjectUrl.getSubject.find((e) => e.templateNo === templateNo.value);
if (findItem !== undefined) {
get(findItem.url, params).then((res) => {
handleResult(res);
});
}
}
function handleResult(res: any) {
if (res.code === 200) {
if (res.data.conclusionInfo.resources != null) {
// res.data.conclusionInfo.resources.map((item)=>{
// item.path = urlProcessor(item.path)
// })
}
resultObj.value = res.data;
resStatus.value = res.data.graphInfo.resultLevel;
} else {
showFailToast({
message: res.msg,
forbidClick: true,
});
}
}
// 历史记录
function historicalEvaluation() {
let temNo = templateNo.value;
let url = '';
let params = {
path: '',
query: { templateNo: temNo, templateName: route.query.templateName+'记录' },
};
if (temNo === 500 || temNo === 400) {
url = '/anxiety-history';
} else {
url = '/health-history';
}
params.path = url;
router.push(params);
}
// 再测一次
function aginSelfClick() {
router.push({
path: '/health-subject',
query: {
templateNo: templateNo.value,
templateName: route.query.templateName,
},
});
}
</script>
<style scoped lang="less">
@import '/@/assets/less/index';
.disease-assessment-result {
height: 100vh;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
margin: 0;
padding: 0;
position: relative;
overflow: hidden;
.result-container {
overflow: auto;
}
.result-con {
padding: 16px;
.text-area {
margin-bottom: 16px;
}
.danger-val {
display: flex;
justify-content: space-between;
padding: 14px 0;
border-bottom: #e0e0e0 1px solid;
font-size: 14px;
.dangerShow {
display: none;
}
.img-box {
display: flex;
align-items: center;
}
.img {
width: 20px;
height: 20px;
vertical-align: middle;
margin-right: 10px;
background: url('/@/assets/images/disease-warning/heart.png') no-repeat;
background-size: 100%;
}
}
.danger-val:last-child {
border: none;
}
}
.bottom-btn {
margin: 12px 30px 12px 30px;
display: flex;
gap: 20px;
box-sizing: border-box;
justify-content: space-between;
font-size: 16px;
.common-btn {
flex: 1;
height: 40px;
white-space: nowrap;
.flex-center();
border-radius: 35px;
}
.history-btn {
background: #ffffff;
border: 1px solid #999999;
}
.again-btn {
.qs-btn();
}
}
}
</style>
@@ -0,0 +1,117 @@
<template>
<div id="adjustmentSuggestion">
<div>
<div>
<div>食物种类</div>
<div>
<p v-for="item in adjustmentSuggestion.foodType">{{ item }}</p>
</div>
</div>
<div>
<div>碳水</div>
<div>
<p>{{ adjustmentSuggestion.cho }}</p>
</div>
</div>
<div>
<div>脂肪</div>
<div>
<p>{{ adjustmentSuggestion.fat }}</p>
</div>
</div>
<div>
<div>蛋白质</div>
<div>
<p>{{ adjustmentSuggestion.pro }}</p>
</div>
</div>
<div>
<div>维生素矿物质和膳食纤维</div>
<div>
<p>{{ adjustmentSuggestion.other }}</p>
</div>
</div>
</div>
</div>
</template>
<script>
import { getSuggestionsDietaryAdjustment } from '/@/api/index';
import { defineComponent, onMounted, reactive, toRefs } from 'vue';
import { showFailToast } from 'vant';
export default defineComponent({
setup() {
const state = reactive({
adjustmentSuggestion: {},
});
const getGetSuggestionsDietaryAdjustment = () => {
let params = {};
getSuggestionsDietaryAdjustment(params)
.then((res) => {
state.adjustmentSuggestion = res.data;
})
.catch((err) => {
showFailToast({
message: err.msg,
forbidClick: true,
});
});
};
onMounted(() => {
getGetSuggestionsDietaryAdjustment();
});
return {
...toRefs(state),
};
},
});
</script>
<style lang="less" scoped>
#adjustmentSuggestion {
width: 100%;
background-color: #ffffff;
> div {
width: 100%;
padding: 20px 15px 0;
box-sizing: border-box;
border-radius: 15px 15px 0 0;
> div {
width: 100%;
margin-bottom: 20px;
> div:first-of-type {
color: #252535;
font-size: 16px;
width: 100%;
height: 40px;
line-height: 40px;
padding: 0 10px;
box-sizing: border-box;
margin-bottom: 12px;
background-color: #eef0f4;
}
> div:last-of-type {
width: 100%;
padding: 0 12px;
box-sizing: border-box;
p {
color: #77849e;
width: 100%;
line-height: 20px;
font-size: 14px;
}
}
}
}
}
</style>
@@ -0,0 +1,412 @@
<template>
<div id="dailyPaper">
<div>
<div>评估日期{{ dailyPaper.date }}</div>
<div>
<div class="header-info">
<div class="header">
<span>姓名{{ dailyPaper.name }}</span>
</div>
</div>
<div>
<div style="margin-bottom: 10px">性别:{{ dailyPaper.sex === '1' ? '女' : '男' }}</div>
<div style="margin-bottom: 10px">年龄:{{ dailyPaper.age }}</div>
<div style="margin-bottom: 10px">身高:{{ dailyPaper.height }}m</div>
<div>体重:{{ dailyPaper.weight }}kg</div>
<div>腰围:{{ dailyPaper.waistline }}cm</div>
<div></div>
</div>
</div>
</div>
<div>
<div>
<div class="title">测验目的与意义</div>
<div class="meaning">
该问卷包括监测数据和随访数据针对膳食数据监测和运动数据监测有效的对体重进行管理体重管理不仅是减重还包括调整饮食运动和心理行为重塑生活方式以达到改善健康状况的目的
</div>
</div>
<div>
<div class="title">测验结果</div>
<div class="card-item">
<div>指标信息</div>
<div>
<div>
<div
>BMI<span>{{ dailyPaper.bmi }}kg/</span></div
>
<div>{{ dailyPaper.habitusType }}</div>
</div>
<template v-if="dailyPaper.indexVo">
<div>
<div
>血压<span>收缩压{{ dailyPaper.indexVo.systolicPressure }}mmHg</span></div
>
<div>{{ dailyPaper.indexVo.pressureStatus }}</div>
</div>
<div>
<div
>&#12288;&#12288;&#12288;<span>舒张压{{ dailyPaper.indexVo.diastolicPressure }}mmHg</span></div
>
<div></div>
</div>
</template>
</div>
</div>
<div class="card-item" v-if="dailyPaper.dietVo">
<div>膳食数据</div>
<div>
<div>
<div
>当日总能量摄入量<span>{{ dailyPaper.dietVo.foodTotalEnergy }}kcal</span></div
>
<div v-if="dailyPaper.dietVo.totalEnergyStatus">{{ dailyPaper.dietVo.totalEnergyStatus }}</div>
<div v-else>--</div>
</div>
<div>
<div
>碳水化合物供能占比<span>{{ dailyPaper.dietVo.cho }}%</span></div
>
<div>{{ dailyPaper.dietVo.choStatus }}</div>
</div>
<div>
<div
>蛋白质供能占比<span>{{ dailyPaper.dietVo.pro }}%</span></div
>
<div>{{ dailyPaper.dietVo.proStatus }}</div>
</div>
<div>
<div
>脂肪供能占比<span>{{ dailyPaper.dietVo.fat }}%</span></div
>
<div>{{ dailyPaper.dietVo.fatStatus }}</div>
</div>
</div>
</div>
<div class="card-item" v-if="dailyPaper.sportVo">
<div>运动数据</div>
<div>
<div>
<div>身体活动水平分级</div>
<div v-if="dailyPaper.sportVo.physicalActivityLevel === '1'"></div>
<div v-if="dailyPaper.sportVo.physicalActivityLevel === '2'"></div>
<div v-if="dailyPaper.sportVo.physicalActivityLevel === '3'"></div>
</div>
<div>
<div
>当日运动消耗<span>{{ dailyPaper.sportVo.totalSportConsume }}kcal</span></div
>
<div v-if="dailyPaper.sportVo.totalSportConsumeStatus">{{ dailyPaper.sportVo.totalSportConsumeStatus }}</div>
<div v-else>--</div>
</div>
<div>
<div class="sport_name">运动情况</div>
<div v-if="dailyPaper.sportVo.detailVoList && dailyPaper.sportVo.detailVoList.length === 0">--</div>
</div>
<div class="sport_container" v-if="dailyPaper.sportVo.detailVoList && dailyPaper.sportVo.detailVoList.length > 0">
<div class="item" v-for="item in dailyPaper.sportVo.detailVoList">
<span>{{ item.exerciseModeTrans }}</span>
<span>{{ item.exerciseTime }}分钟</span>
</div>
</div>
</div>
</div>
<div class="card-item" v-if="dailyPaper.sportVo">
<div>能量消耗</div>
<div>
<div>
<div
>基础代谢率BMR:<span>{{ dailyPaper.sportVo.bmr }}kcal</span></div
>
<div>--</div>
</div>
<div>
<div
>当日工作消耗<span>{{ dailyPaper.sportVo.totalWorkConsume }}kcal</span></div
>
<div>--</div>
</div>
<div>
<div
>当日总消耗热量<span>{{ dailyPaper.sportVo.totalEnergyConsume }}kcal</span></div
>
<div>--</div>
</div>
</div>
</div>
<div class="card-item" v-if="dailyPaper.desValue">
<div>吃动平衡评估结果</div>
<div>
<div>
<div
>每日能量剩余量DES:<span>{{ dailyPaper.desValue }}kcal</span></div
>
<div class="result-warp">{{ dailyPaper.desDesc }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { monitoringReportsOneDay } from '/@/api/index';
import { defineComponent, onMounted, reactive, toRefs } from 'vue';
import { useRoute } from 'vue-router';
import { showFailToast } from 'vant';
export default defineComponent({
setup() {
const route = useRoute();
const state = reactive({
dailyPaper: {},
circleWidth: '0',
});
const getMonitoringReportsOneDay = () => {
let params = {
date: route.query.times,
};
monitoringReportsOneDay(params)
.then((res) => {
state.dailyPaper = res.data;
})
.catch((err) => {
showFailToast({
message: err.msg,
forbidClick: true,
});
});
};
onMounted(() => {
getMonitoringReportsOneDay();
});
return {
...toRefs(state),
};
},
});
</script>
<style lang="less" scoped>
#dailyPaper {
width: 100%;
height: 100%;
background-color: #f5f7fb;
> div:nth-of-type(1) {
width: 100%;
padding: 15px;
box-sizing: border-box;
> div:first-of-type {
height: 14px;
font-size: 14px;
color: #77849e;
line-height: 14px;
margin-bottom: 10px;
}
> div:last-of-type {
width: 100%;
border-radius: 15px;
box-sizing: border-box;
background-color: #ffffff;
box-shadow: 0 3px 8px 0 rgba(37, 37, 53, 0.1);
.status-bg1 {
background-color: rgba(255, 79, 68, 0.14);
.status-font {
color: #ff4f44;
}
.circle-border {
border: 1px solid #ff4f44;
}
}
.status-bg2 {
background-color: rgba(255, 214, 51, 0.14);
.status-font {
color: #ffc833;
}
.circle-border {
border: 1px solid #ffc833;
}
}
.status-bg3 {
background-color: rgba(48, 205, 155, 0.14);
.status-font {
color: #2ac79f;
}
.circle-border {
border: 1px solid #2ac79f;
}
}
.header-info {
color: #252535;
width: 100%;
font-size: 14px;
font-weight: bold;
border-radius: 15px;
.header {
display: flex;
justify-content: space-between;
padding-left: 20px;
padding-top: 20px;
}
.header-status {
width: 90%;
margin: 0 auto;
height: 10px;
border-radius: 10px;
background-image: linear-gradient(to right, rgba(255, 79, 68, 1), rgba(255, 214, 51, 1), rgba(42, 199, 159, 1));
position: relative;
.circle-seat {
position: absolute;
top: -5px;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: #ffffff;
}
}
.status-name {
padding-top: 14px;
width: 90%;
margin: 0 auto;
display: flex;
justify-content: space-around;
color: #333333;
font-size: 12px;
}
}
> div:last-of-type {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 10px 20px 20px 20px;
> div {
flex: 0 0 33.333333%;
color: #77849e;
height: 14px;
font-size: 14px;
line-height: 14px;
}
}
}
}
> div:nth-of-type(2) {
width: 100%;
padding: 20px 15px 0;
box-sizing: border-box;
background-color: #ffffff;
border-radius: 15px 15px 0 0;
> div {
width: 100%;
//margin-bottom: 24px;
padding: 20px 0 30px 0;
> .title {
color: #252535;
width: 100%;
height: 16px;
font-size: 16px;
line-height: 16px;
font-weight: bold;
margin-bottom: 12px;
}
> .meaning {
width: 100%;
color: #77849e;
font-size: 13px;
line-height: 22px;
text-indent: 2em;
}
.sport_name {
color: #252535 !important;
}
> .card-item {
width: 100%;
margin-bottom: 20px;
> div:first-of-type {
width: 100%;
height: 40px;
color: #252535;
font-size: 16px;
padding: 0 10px;
line-height: 40px;
box-sizing: border-box;
background-color: #eef0f4;
}
> div:last-of-type {
width: 100%;
padding: 25px 12px;
box-sizing: border-box;
background-color: #f5f7fb;
border-radius: 0 0 8px 8px;
.sport_container {
width: 100%;
text-align: center;
display: flex;
height: auto;
flex-direction: column;
.item {
padding: 8px 0;
font-size: 13px;
span {
display: inline-block;
color: #77849e;
width: 50%;
text-align: right;
&:first-of-type {
color: #252535;
}
&:last-of-type {
}
}
}
}
> div {
height: 14px;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
margin-bottom: 15px;
> div:first-of-type {
color: #252535;
height: 14px;
font-size: 13px;
line-height: 14px;
> span {
color: #77849e;
}
}
> div:last-of-type {
color: #77849e;
height: 14px;
font-size: 13px;
line-height: 14px;
}
.result-warp {
width: 22%;
}
&:last-of-type {
margin-bottom: 0;
}
}
}
}
}
}
}
</style>
@@ -0,0 +1,26 @@
<template>
<div id="dietarySciencePopularization">
<img src="../../../assets/images/living/dietarySciencePopularization.jpg" alt="">
</div>
</template>
<script>
import {defineComponent} from "vue";
export default defineComponent({
setup() {
}
})
</script>
<style lang="less" scoped>
#dietarySciencePopularization {
width: 100%;
background-color: #F5F7FB;
img {
width: 100%;
}
}
</style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,607 @@
<template>
<div id="healthIndependent">
<div>
<div>
<div class="icon"></div>
<div class="userInfor">
<div>
<div>{{ healthManage.name }}</div>
<div>{{ healthManage.typeOfPopulation }}</div>
</div>
<div>
<div
><span>{{ healthManage.sex === '1' ? '女' : '男' }}</span
>{{ healthManage.age }}
</div>
<div @click="openFileEntry()">健康档案 <img src="../../../assets/images/living/more.png" alt="" /></div>
</div>
</div>
<div class="home-right"></div>
</div>
</div>
<div>
<div>
<div>健康监测</div>
<div>
<div>今日监测</div>
<div>
<img v-if="healthManage.inToday === 1" src="../../../assets/images/living/healthIndependentC.png" alt="" />
</div>
</div>
</div>
<div>
<div>
<div>
<template v-if="healthManage.lastDate"> 上次监测时间{{ healthManage.lastDate }} </template>
</div>
<div class="entry" @click="openPsychologicalEvaluationList">
历史数据
<van-icon name="arrow" />
</div>
</div>
<div>
<div @click="getMeals(1)">
<div>
<img src="../../../assets/images/living/healthIndependentC_1.png" alt="" />
</div>
<div>膳食监测</div>
</div>
<div @click="getMeals(2)">
<div>
<img src="../../../assets/images/living/healthIndependentC_2.png" alt="" />
</div>
<div>运动监测</div>
</div>
<div @click="getMeals(3)">
<div>
<img src="../../../assets/images/living/healthIndependentC_3.png" alt="" />
</div>
<div>指标监测</div>
</div>
</div>
</div>
<div>
<div :class="healthManage.dayType ? 'active' : ''" @click="openDailyPaper(healthManage.dayType)">健康日报 </div>
<div :class="healthManage.weekType ? 'active' : ''" @click="openWeekly(healthManage.weekType)">健康周报 </div>
</div>
</div>
<div>
<div>健康建议</div>
<div>
<div v-if="healthManage.status" @click="openAdjustmentSuggestion">
<div>膳食结构调整建议</div>
<div>查看</div>
</div>
<div v-if="healthManage.eatingBalanceStatus" @click="openSuggestionsOnEatingDynamicBalance">
<div>吃动平衡建议</div>
<div>查看</div>
</div>
<div @click="openDietarySciencePopularization">
<div>膳食科普</div>
<div>查看</div>
</div>
</div>
</div>
<div v-if="healthManage.medicineNameList !== null && healthManage.medicineNameList.length > 0">
<div>用药情况</div>
<div>
<div>{{ healthManage.medicineNameList ? healthManage.medicineNameList.join(',') : '' }} </div>
</div>
</div>
</div>
</template>
<script>
import { healthManageHome } from '/@/api/index';
import { defineComponent, onMounted, reactive, toRefs } from 'vue';
import { useRouter } from 'vue-router';
import { showFailToast } from 'vant';
import { newPageParams, openPage } from '/@/hooks/openPage';
export default defineComponent({
setup() {
const router = useRouter();
const state = reactive({
healthManage: {
medicineNameList: [],
},
});
const openFileEntry = () => {
// router.push({
// path: '/fileEntryC',
// query: {
// startNewActivity: 1,
// },
// });
openPage('/fileEntryC', newPageParams());
};
const getHealthManageHome = () => {
let params = {};
healthManageHome(params)
.then((res) => {
state.healthManage = res.data;
})
.catch((err) => {
showFailToast({
message: err.msg,
forbidClick: true,
});
});
};
const getMeals = (type) => {
let url = '';
if (type === 1) {
// 膳食
url = '/meals';
} else if (type === 2) {
// 运动
url = '/sport';
} else if (type === 3) {
// 指标
url = '/indicator';
}
// router.push({
// path: url,
// query: {
// startNewActivity: 1,
// },
// });
openPage(url, newPageParams());
};
const openPsychologicalEvaluationList = () => {
// router.push({
// path: '/psychologicalEvaluationListC',
// query: {
// startNewActivity: 1,
// },
// });
openPage('/psychologicalEvaluationListC', newPageParams());
};
const openDailyPaper = (bol) => {
if (bol) {
// router.push({
// path: '/dailyPaperC',
// query: {
// times: state.healthManage.lastDate,
// startNewActivity: 1,
// },
// });
openPage(
'/dailyPaperC',
newPageParams({
times: state.healthManage.lastDate,
})
);
}
};
// 周报
const openWeekly = (bol) => {
if (bol) {
// router.push({
// path: '/weeklyC',
// query: {
// startNewActivity: 1,
// },
// });
openPage('/weeklyC', newPageParams());
}
};
// 膳食
const openAdjustmentSuggestion = () => {
// router.push({
// path: '/adjustmentSuggestionC',
// query: {
// startNewActivity: 1,
// },
// });
openPage('/adjustmentSuggestionC', newPageParams());
};
// 吃动平衡
const openSuggestionsOnEatingDynamicBalance = () => {
// router.push({
// path: '/suggestionsOnEatingDynamicBalanceC',
// query: {
// startNewActivity: 1,
// },
// });
openPage('/suggestionsOnEatingDynamicBalanceC', newPageParams());
};
// 膳食科普
const openDietarySciencePopularization = () => {
// router.push({
// path: '/dietarySciencePopularizationC',
// query: {
// startNewActivity: 1,
// },
// });
openPage('/dietarySciencePopularizationC', newPageParams());
};
onMounted(() => {
getHealthManageHome();
});
return {
...toRefs(state),
openFileEntry,
openPsychologicalEvaluationList,
openDailyPaper,
openWeekly,
openAdjustmentSuggestion,
openSuggestionsOnEatingDynamicBalance,
openDietarySciencePopularization,
getMeals,
};
},
});
</script>
<style lang="less" scoped>
#healthIndependent {
width: 100%;
height: 100%;
background-color: #f5f7fb;
> div:nth-of-type(1) {
width: 100%;
padding: 15px;
box-sizing: border-box;
> div {
width: 100%;
padding: 20px;
border-radius: 15px;
box-sizing: border-box;
background-color: #ffffff;
box-shadow: 0 3px 8px 0 rgba(37, 37, 53, 0.1);
display: flex;
position: relative;
.icon {
width: 46px;
height: 46px;
background: url('/@/assets/images/living/reportlogo.png') no-repeat;
background-size: 100% 100%;
}
.userInfor {
width: 80%;
padding-left: 3%;
> div:first-of-type {
width: 100%;
height: 17px;
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
> div:first-of-type {
color: #252535;
font-size: 17px;
height: 17px;
font-weight: bold;
line-height: 17px;
}
> div:last-of-type {
//width: 76px;
padding-right: 5px;
color: #BD130B;
font-size: 16px;
height: 16px;
font-weight: bold;
line-height: 16px;
text-align: center;
}
}
> div:last-of-type {
width: 100%;
height: 18px;
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between;
> div:first-of-type {
color: #77849e;
font-size: 14px;
height: 14px;
line-height: 14px;
span {
margin-right: 12px;
}
}
> div:last-of-type {
display: flex;
align-items: center;
height: 18px;
color: #252535;
padding: 0 10px;
font-size: 12px;
line-height: 18px;
border-radius: 10px;
background-color: #ffffff;
border: 1px solid #e6e6ea;
img {
width: 5px;
height: 6px;
margin-left: 5px;
}
}
}
}
.home-right {
position: absolute;
right: 2px;
bottom: 0;
width: 55px;
height: 55px;
background: url('/@/assets/images/living/homeRight.png') no-repeat;
background-size: 100% 100%;
}
}
}
> div:nth-of-type(2) {
width: 100%;
border-radius: 15px;
margin-bottom: 24px;
box-sizing: border-box;
padding: 20px 15px 24px;
background-color: #ffffff;
> div:nth-of-type(1) {
width: 100%;
height: 16px;
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between;
padding-left: 5px;
margin-bottom: 15px;
box-sizing: border-box;
> div:first-of-type {
color: #252535;
height: 16px;
font-size: 16px;
line-height: 16px;
font-weight: bold;
}
> div:last-of-type {
height: 14px;
display: flex;
flex-wrap: nowrap;
> div:first-of-type {
color: #77849e;
height: 14px;
font-size: 13px;
line-height: 14px;
margin-right: 8px;
}
> div:last-of-type {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #f5f7fb;
border: 1px solid #e6e6ea;
img {
width: 100%;
height: 100%;
display: flex;
align-items: center;
}
}
}
}
> div:nth-of-type(2) {
width: 100%;
border-radius: 8px;
margin-bottom: 24px;
background-color: #f1f6ff;
> div:first-of-type {
width: 100%;
height: 40px;
display: flex;
align-items: center;
flex-wrap: nowrap;
justify-content: space-between;
padding: 0 10px 0 15px;
box-sizing: border-box;
border-bottom: 1px solid #eaecf1;
> div {
color: #77849e;
height: 40px;
font-size: 12px;
line-height: 40px;
}
.entry {
padding: 0 10px;
height: 26px;
line-height: 27px;
//border: 1px solid #21BEBE ;
//background-color: #ffffff;
border-radius: 12px;
.van-icon {
font-weight: bold;
}
}
}
> div:last-of-type {
width: 100%;
height: 110px;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
padding: 10px 24px 12px;
box-sizing: border-box;
> div {
width: 65px;
> div:first-of-type {
width: 65px;
height: 65px;
margin-bottom: 8px;
img {
width: 100%;
height: 100%;
}
}
> div:last-of-type {
width: 100%;
height: 14px;
text-align: center;
line-height: 14px;
font-size: 14px;
color: #77849e;
}
}
}
}
> div:nth-of-type(3) {
width: 100%;
height: 48px;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
> div {
color: #ffffff;
width: 45%;
font-size: 18px;
height: 48px;
line-height: 48px;
text-align: center;
border-radius: 8px;
background-color: #b6b6b6;
}
.active {
background-color: #21bebe;
}
}
}
> div:nth-of-type(3) {
width: 100%;
padding: 0 15px;
box-sizing: border-box;
margin-bottom: 24px;
> div:first-of-type {
color: #252535;
width: 100%;
height: 16px;
font-size: 16px;
font-weight: bold;
line-height: 16px;
padding-left: 5px;
box-sizing: border-box;
margin-bottom: 15px;
}
> div:last-of-type {
width: 100%;
> div {
width: 100%;
height: 60px;
padding: 0 15px;
box-sizing: border-box;
border-radius: 8px;
margin-bottom: 15px;
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between;
background-color: #ffffff;
box-shadow: 0 2px 4px 0 rgba(51, 59, 66, 0.12);
> div:first-of-type {
color: #333b42;
height: 16px;
font-size: 16px;
line-height: 16px;
}
> div:last-of-type {
color: #77849e;
height: 13px;
font-size: 13px;
line-height: 13px;
}
&:last-of-type {
margin-bottom: 0;
}
}
}
}
> div:nth-of-type(4) {
width: 100%;
padding: 0 15px;
box-sizing: border-box;
> div:first-of-type {
color: #252535;
width: 100%;
height: 16px;
font-size: 16px;
font-weight: bold;
line-height: 16px;
padding-left: 5px;
box-sizing: border-box;
margin-bottom: 15px;
}
> div:last-of-type {
width: 100%;
> div {
width: 100%;
color: #77849e;
font-size: 14px;
height: 44px;
line-height: 44px;
padding: 0 15px;
border-radius: 8px;
box-sizing: border-box;
background-color: #ffffff;
margin-bottom: 15px;
&:last-of-type {
margin-bottom: 0;
}
}
}
}
}
</style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
<template>
<div id="healthMonitoringHelp">
<img src="../../../assets/images/living/help.png" alt="">
</div>
</template>
<script>
import {defineComponent} from "vue";
export default defineComponent({
setup() {
}
})
</script>
<style lang="less" scoped>
#healthMonitoringHelp {
width: 100%;
background-color: #F5F7FB;
img {
width: 100%;
}
}
</style>
@@ -0,0 +1,625 @@
<template>
<div id="healthMonitoring">
<div>
<div class="help" @click="openHealthMonitoringHelp">
录入帮助
<van-icon name="question-o" />
</div>
<template v-for="item in questionnaire">
<div class="container">
<div class="title">{{ item.title }}</div>
<div class="content">
<template v-for="(item_, index_) in item.children">
<template v-if="item_.type === 'radio'">
<div :class="id ? 'mask' : ''">
<div class="tips">{{ index_ + 1 }}{{ item_.title }}单选</div>
<div class="radio">
<template v-for="item__ in item_.options">
<div
:class="[healthMonitoring[item_.key].includes(item__.id) ? 'active' : '']"
@click="radioChange(item_.key, item__.id)"
>{{ item__.label }}
</div>
</template>
</div>
</div>
</template>
<template v-if="item_.type === 'checkbox'">
<div :class="id ? 'mask' : ''">
<div>{{ index_ + 1 }}{{ item_.title }}多选</div>
<div class="checkbox">
<template v-for="item__ in item_.options">
<div
:class="[healthMonitoring[item_.key].includes(item__.id) ? 'active' : '']"
@click="checkboxChange(item_.key, item__.id)"
>{{ item__.label }}
</div>
</template>
</div>
</div>
</template>
<template v-if="item_.type === 'input'">
<div :class="id ? 'mask' : ''">
<div class="tips">{{ index_ + 1 }}{{ item_.title }}</div>
<div class="inputAfter">
<div>
<div>
<van-field type="number" :label-width="0" v-model="healthMonitoring[item_.key]" clearable />
</div>
<div>{{ item_.unit }}</div>
</div>
</div>
</div>
</template>
</template>
</div>
</div>
</template>
</div>
<div>
<div v-if="id" @click="openBack">返回</div>
<div v-else @click="submit">提交</div>
</div>
</div>
</template>
<script>
import { addOrEdit, checkInfo, eatingInfo } from '/@/api/index';
import moment from 'moment';
import { defineComponent, onMounted, reactive, toRefs } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { showFailToast, showConfirmDialog, showSuccessToast } from 'vant';
import { destroyPage } from '/@/hooks/openPage';
import {useLoading} from '../../../utils/compUtils'
const { loadingSpinner, loadingClose } = useLoading();
export default defineComponent({
setup() {
const route = useRoute();
const router = useRouter();
const state = reactive({
id: '',
healthMonitoring: {
weight: '',
waistline: '',
bloodOxygenSbp: '',
bloodOxygenDbp: '',
sleepQuality: '',
sleepTime: '',
},
questionnaire: [
{
title: '指标监测',
children: [
{
title: '体重(手动记录/每日监测)?',
type: 'input',
key: 'weight',
options: [],
unit: 'kg',
},
{
title: '指标监测-腰围(手动记录/每日监测)?',
type: 'input',
key: 'waistline',
options: [],
unit: 'cm',
},
{
title: '当日血压平均收缩压?',
type: 'input',
key: 'bloodOxygenSbp',
options: [],
unit: 'mmHg',
},
{
title: '当日血压平均舒张压?',
type: 'input',
key: 'bloodOxygenDbp',
options: [],
unit: 'mmHg',
},
{
title: '您昨天的总体睡眠质量如何?',
type: 'radio',
key: 'sleepQuality',
options: [
{
id: '1',
label: '非常好',
},
{
id: '2',
label: '尚好',
},
{
id: '3',
label: '不好',
},
{
id: '4',
label: '非常差',
},
],
unit: '',
},
{
title: '您昨天的实际睡眠时间有几个小时?',
type: 'radio',
key: 'sleepTime',
options: [
{
id: '1',
label: '4-6小时',
},
{
id: '2',
label: '6-8小时',
},
{
id: '3',
label: '8-10小时',
},
{
id: '4',
label: '10小时以上',
},
],
unit: '',
},
],
},
],
});
const openBack = () => {
// router.go(-1);
destroyPage();
};
const openHealthMonitoringHelp = () => {
router.push({
path: 'healthMonitoringHelpC',
});
};
const getInfo = (time) => {
let params = {
date: time,
};
eatingInfo(params).then((res) => {
getRestult(res);
});
};
const getCheckInfo = (id) => {
let params = {
id: id,
};
checkInfo(params).then((res) => {
getRestult(res);
});
};
// 返回结果回填数据
const getRestult = (res) => {
state.healthMonitoring.weight = res.data.weight;
state.healthMonitoring.waistline = res.data.waistline;
state.healthMonitoring.bloodOxygenSbp = res.data.bloodOxygenSbp;
state.healthMonitoring.bloodOxygenDbp = res.data.bloodOxygenDbp;
state.healthMonitoring.sleepQuality = res.data.sleepQuality;
state.healthMonitoring.sleepTime = res.data.sleepTime;
};
const submit = () => {
let params = JSON.parse(JSON.stringify(state.healthMonitoring));
let paramsList = [
params.weight,
params.waistline,
params.bloodOxygenSbp,
params.bloodOxygenDbp,
params.sleepQuality,
params.sleepTime,
];
//every 一个条件不满足返回false
let otherParams = paramsList.every((item) => item !== '');
if (!otherParams) {
showFailToast({
message: '必填项不能为空',
forbidClick: true,
});
return false;
}
loadingSpinner();
showConfirmDialog({
title: '提示',
message: '信息是否填写无误?',
}).then(() => {
// 标识1指标已填写
params.indexFlag = 1;
addOrEdit(params)
.then((res) => {
loadingClose();
showSuccessToast({
message: res.msg,
forbidClick: true,
});
setTimeout(() => {
openBack();
}, 1500);
})
.catch((err) => {
loadingClose();
showFailToast({
message: err.msg,
forbidClick: true,
});
});
});
};
const checkboxChange = (name, value) => {
let nameValue = state.healthMonitoring[name] ? state.healthMonitoring[name].split(',') : [];
let nameValueIndexOf = nameValue.indexOf(value);
if (nameValueIndexOf === -1) {
nameValue.push(value);
} else {
nameValue.splice(nameValueIndexOf, 1);
}
state.healthMonitoring[name] = nameValue.join(',');
};
// 点击选择问题 单选
const radioChange = (name, value) => {
state.healthMonitoring[name] = value;
};
onMounted(() => {
state.id = route.query.id;
if (route.query.id) {
getCheckInfo(route.query.id);
} else {
let currentTime = moment(new Date()).format('YYYY-MM-DD');
getInfo(currentTime);
}
});
return {
...toRefs(state),
openBack,
checkboxChange,
openHealthMonitoringHelp,
radioChange,
submit,
};
},
});
</script>
<style lang="less" scoped>
#healthMonitoring {
width: 100%;
height: 100%;
background-color: #f5f7fb;
.add-icon {
padding: 3px 10px;
color: #21bebe;
font-size: 12px;
border: 1px solid #21bebe;
border-radius: 20px;
display: flex;
align-items: center;
.van-icon {
font-size: 15px;
font-weight: bold;
padding-left: 5px;
}
}
.tips {
position: relative;
display: flex;
justify-content: space-between;
&:after {
content: '*';
position: absolute;
left: -7px;
top: -2px;
color: red;
}
}
.tips-info {
font-size: 13px;
color: #666;
line-height: 28px;
padding: 0 20px !important;
background-color: #f5f7fb !important;
span {
font-weight: bold;
}
}
.range {
display: flex;
line-height: 40px;
color: #999999;
.input_van {
width: 50%;
}
}
> div:nth-of-type(1) {
width: 100%;
position: relative;
padding: 0 0 25px 0;
overflow-y: scroll;
box-sizing: border-box;
height: calc(100% - 55px);
.help {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
width: 100%;
height: 38px;
color: #ff6028;
box-sizing: border-box;
line-height: 28px;
margin: 0 auto 20px;
border: 1px solid #e6e6ea;
background-color: rgba(255, 96, 40, 0.2);
.van-icon {
padding-left: 12px;
}
}
.container {
padding: 0 15px 0 15px;
> .title {
color: #252535;
height: 16px;
font-size: 16px;
font-weight: bold;
line-height: 16px;
text-align: center;
margin-bottom: 20px;
}
> .content {
width: 100%;
margin-bottom: 30px;
> .mask {
position: relative;
&:before {
content: '';
position: absolute;
z-index: 3;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0);
}
}
> div {
width: 100%;
border-radius: 8px;
margin-bottom: 15px;
padding: 20px 15px 25px;
box-sizing: border-box;
background-color: #ffffff;
> div:first-of-type {
color: #252535;
width: 100%;
font-size: 14px;
font-weight: bold;
line-height: 20px;
margin-bottom: 10px;
}
> .radio,
> .checkbox {
width: 100%;
padding: 0 8px;
box-sizing: border-box;
.rang-options {
margin: 0;
height: 120px;
padding: 0 10px 0 10px;
border-top: none;
border-left: none;
border-right: none;
border-bottom: 1px solid #e6e6ea;
border-radius: 0;
.name {
margin: 0 0 25px -10px;
font-size: 14px;
color: #333333;
}
}
> div {
color: #77849e;
font-size: 15px;
width: 100%;
height: 50px;
line-height: 50px;
padding: 0 20px;
border-radius: 5px;
margin-bottom: 20px;
box-sizing: border-box;
border: 1px solid #e6e6ea;
&:last-of-type {
margin-bottom: 0;
}
}
.active {
color: #21bebe;
background-color: rgba(33, 190, 190, 0.2);
border: 1px solid #21bebe;
}
}
> .detailVoList {
width: 100%;
height: 100%;
.detailVoList-item {
width: 100%;
height: 190px;
display: flex;
flex-direction: column;
justify-content: center;
border-bottom: 1px solid #e6e6ea;
position: relative;
.sport-input {
display: flex;
flex-wrap: nowrap;
border-radius: 5px;
position: relative;
.sport-icon {
position: absolute;
right: 5px;
top: 30%;
color: #e6e6ea;
font-size: 20px;
}
.van-cell {
border: 1px solid #e6e6ea;
border-radius: 5px;
}
}
.sport-delete {
position: absolute;
right: 5px;
top: 25%;
color: #77849e;
display: flex;
flex-direction: column;
justify-content: center;
.van-icon {
font-size: 18px;
}
span {
font-size: 11px;
margin-top: 2px;
}
}
.sport-times {
font-size: 12px;
margin-top: 10px;
padding: 0 18px;
span {
margin-left: -12px;
display: inline-block;
margin-bottom: 36px;
}
}
}
}
> .inputAfter {
width: 100%;
height: 100%;
padding: 0 8px;
box-sizing: border-box;
> div {
width: 100%;
height: 50px;
display: flex;
flex-wrap: nowrap;
line-height: 50px;
padding-right: 20px;
border-radius: 5px;
margin-bottom: 20px;
box-sizing: border-box;
border: 1px solid #e6e6ea;
> div:first-of-type {
flex: 1;
height: 50px;
padding: 2px 0;
box-sizing: border-box;
position: relative;
input {
width: 100%;
height: 30px;
}
.sport-arrow {
position: absolute;
right: -5%;
top: 30%;
color: #e6e6ea;
font-size: 20px;
}
}
> div:last-of-type {
color: #77849e;
font-size: 15px;
height: 50px;
line-height: 50px;
}
&:last-of-type {
margin-bottom: 0;
}
}
.range-container {
padding: 34px 10px 20px 10px;
border: none;
}
}
}
}
}
}
> div:nth-of-type(2) {
width: 100%;
height: 54px;
padding: 7px 28px;
box-sizing: border-box;
background-color: #ffffff;
> div {
color: #ffffff;
font-size: 15px;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
border-radius: 8px;
background-color: #21bebe;
}
}
}
</style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,179 @@
<template>
<div id="psychologicalEvaluationList">
<div>
<template v-if="list && list.length">
<template v-for="item in list">
<div class="item" @click="openHealthMonitoring(item.id)">
<div>{{ item.createTime }}</div>
<div>查看</div>
</div>
</template>
</template>
<template v-else>
<div class="img">
<img src="../../../assets/images/living/psychologicalEvaluationListNull.png" alt="" />
</div>
</template>
</div>
<div v-if="!btn">
<div @click="openHealthMonitoring('')">{{ btn ? '重新评估' : '重新自测' }}</div>
</div>
</div>
</template>
<script>
import { list, listDiabetes } from '/@/api/index';
import { defineComponent, onMounted, reactive, toRefs } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { showFailToast } from 'vant';
import { destroyPage } from '/@/hooks/openPage';
export default defineComponent({
setup() {
const route = useRoute();
const router = useRouter();
const state = reactive({
list: [],
btn: true,
});
const getList = () => {
let params = {};
list(params)
.then((res) => {
state.list = res.data;
})
.catch((err) => {
showFailToast({
message: err.msg,
forbidClick: true,
});
});
};
const getDiabetes = () => {
let params = {};
listDiabetes(params)
.then((res) => {
state.list = res.data;
})
.catch((err) => {
showFailToast({
message: err.msg,
forbidClick: true,
});
});
};
const openHealthMonitoring = (id) => {
if (route.query.btn) {
router.push({
path: '/disbetHealthMonitoring',
query: {
id: id,
},
});
} else {
router.push({
path: '/healthMonitoringC',
query: {
id: id,
},
});
}
};
onMounted(() => {
if (route.query.btn) {
state.btn = false;
getDiabetes();
} else {
getList();
}
});
return {
...toRefs(state),
openHealthMonitoring,
};
},
});
</script>
<style lang="less" scoped>
#psychologicalEvaluationList {
width: 100%;
height: 100%;
background-color: #f5f7fb;
> div:nth-of-type(1) {
width: 100%;
padding: 15px 15px 0;
overflow-y: scroll;
box-sizing: border-box;
height: calc(100% - 55px);
.item {
width: 100%;
height: 55px;
padding: 0 15px;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
box-sizing: border-box;
margin-bottom: 15px;
border-radius: 8px;
background-color: #ffffff;
box-shadow: 0 2px 4px 0 rgba(51, 59, 66, 0.12);
> div:first-of-type {
height: 55px;
font-size: 16px;
color: #333b42;
line-height: 55px;
}
> div:last-of-type {
height: 55px;
font-size: 13px;
color: #77849e;
line-height: 55px;
}
&:last-of-type {
margin-bottom: 0;
}
}
.img {
width: 60%;
padding-top: 100px;
margin: 0 auto;
img {
width: 100%;
}
}
}
> div:nth-of-type(2) {
width: 100%;
height: 54px;
padding: 7px 28px;
box-sizing: border-box;
background-color: #ffffff;
> div {
color: #ffffff;
font-size: 15px;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
border-radius: 8px;
background-color: #21bebe;
}
}
}
</style>
@@ -0,0 +1,787 @@
<template>
<div id="healthMonitoring">
<div>
<div class="help" @click="openHealthMonitoringHelp">
录入帮助
<van-icon name="question-o" />
</div>
<template v-for="item in questionnaire">
<div class="container">
<div class="title">{{ item.title }}</div>
<div class="content">
<template v-for="(item_, index_) in item.children">
<template v-if="item_.type === 'radio'">
<div :class="id ? 'mask' : ''">
<div class="tips">{{ index_ + 1 }}{{ item_.title }}单选</div>
<div class="radio">
<template v-for="item__ in item_.options">
<div
:class="[healthMonitoring[item_.key].includes(item__.id) ? 'active' : '']"
@click="radioChange(item_.key, item__.id)"
>{{ item__.label }}
</div>
</template>
</div>
</div>
</template>
<template v-if="item_.type === 'checkbox'">
<div :class="id ? 'mask' : ''">
<div>{{ index_ + 1 }}{{ item_.title }}多选</div>
<div class="checkbox">
<template v-for="item__ in item_.options">
<div
:class="[healthMonitoring[item_.key].includes(item__.id) ? 'active' : '']"
@click="checkboxChange(item_.key, item__.id)"
>{{ item__.label }}
</div>
</template>
</div>
</div>
</template>
<template v-if="item_.type === 'input'">
<div :class="id ? 'mask' : ''">
<div class="tips">{{ index_ + 1 }}{{ item_.title }}</div>
<div class="inputAfter">
<div>
<div>
<van-field type="number" :label-width="0" v-model="healthMonitoring[item_.key]" clearable />
</div>
<div>{{ item_.unit }}</div>
</div>
</div>
</div>
</template>
<template v-if="item_.type === 'picker'">
<div :class="id ? 'mask' : ''">
<div class="tips">
<div>{{ index_ + 1 }}{{ item_.title }}</div>
<div class="add-icon" v-if="item_.key === 'detailVoList'" @click="addSport">
添加运动
<van-icon name="add-o" />
</div>
</div>
<div class="inputAfter" v-if="item_.key === 'physicalActivityLevel'">
<div>
<div>
<van-field
:label-width="0"
:placeholder="item_.placeholder"
v-model="healthMonitoring[item_.key]"
readonly
@click="openPicker(item_.key, '')"
/>
<van-icon class="sport-arrow" name="arrow" />
</div>
<div>{{ item_.unit }}</div>
</div>
</div>
<div class="detailVoList" v-if="item_.key === 'detailVoList'">
<div class="detailVoList-item" v-for="(sportItem, sportIndex) in healthMonitoring.detailVoList">
<div
class="sport-input"
:style="{ width: sportIndex === 0 ? '100%' : '86%' }"
@click="openPicker(item_.key, sportIndex)"
>
<van-field
:label-width="0"
:placeholder="item_.placeholder"
v-model="sportItem.exerciseMode"
readonly
/>
<van-icon class="sport-icon" name="arrow" />
</div>
<div class="sport-delete" v-if="sportIndex !== 0" @click="deletePicker(sportIndex)">
<van-icon name="clear" />
<span>删除</span>
</div>
<div class="sport-times">
<span>时间()</span>
<nut-range
v-model="sportItem.exerciseTime"
:min="0"
:max="120"
:marks="marksSport"
:hiddenRange="true"
@change="(data) => onChangeSport(data, sportIndex)"
/>
</div>
</div>
</div>
</div>
<div class="tips-info" v-if="item_.key === 'physicalActivityLevel'">
<p
><span></span
>75%的时间坐或站立25%的时间站着活动例如办公室工作修理电器钟表售货员酒店服务员化学实验操作讲课等
</p>
<p
><span></span
>40%的时间坐或站立60%的时间特殊职业活动例如学生日常活动机动车驾驶电工安装车床操作金属切削等
</p>
<p
><span></span
>25%的时间站着活动75%的时间特殊职业活动例如非机械化农业劳动炼钢舞蹈体育运动装卸采矿等
</p>
</div>
</template>
</template>
</div>
</div>
</template>
</div>
<div>
<div v-if="id" @click="openBack">返回</div>
<div v-else @click="submit">提交</div>
</div>
</div>
<van-popup :show="picker.show" round position="bottom">
<van-picker :columns="picker.columns" v-model="picker.value" @cancel="picker.show = false" @confirm="onConfirmPicker" />
</van-popup>
</template>
<script>
import { addOrEdit, checkInfo, eatingInfo } from '/@/api/index';
import moment from 'moment';
import { defineComponent, onMounted, reactive, toRefs } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { showFailToast, showConfirmDialog, showSuccessToast } from "vant";
import { destroyPage } from '/@/hooks/openPage';
import {useLoading} from '../../../utils/compUtils'
const { loadingSpinner, loadingClose } = useLoading();
export default defineComponent({
setup() {
const route = useRoute();
const router = useRouter();
const state = reactive({
id: '',
healthMonitoring: {
physicalActivityLevel: '',
weight: '',
detailVoList: [
{
exerciseMode: '',
exerciseTime: 0,
},
],
},
pickerVal: 0,
sportIndex: 0,
picker: {
show: false,
columns: [],
key: '',
},
// 身体活动水平
singleRow: [
{ text: '低', value: '1' },
{ text: '中', value: '2' },
{ text: '高', value: '3' },
],
marksSport: {
0: 0,
30: 30,
60: 60,
90: 90,
120: 120,
},
// 运动情况
sportList: [
{ text: '走路(慢)', value: '1' },
{ text: '跑步(快)', value: '2' },
{ text: '跑步(慢)', value: '3' },
{ text: '跳绳', value: '4' },
{ text: '游泳', value: '5' },
{ text: '自行车', value: '6' },
{ text: '踏板车', value: '7' },
{ text: '瑜伽,普拉提', value: '8' },
{ text: '篮球', value: '9' },
{ text: '排球', value: '10' },
{ text: '乒乓球', value: '11' },
{ text: '台球', value: '12' },
{ text: '网球', value: '13' },
{ text: '羽毛球', value: '14' },
{ text: '足球', value: '15' },
{ text: '舞蹈', value: '16' },
{ text: '太极拳', value: '17' },
{ text: '单杠', value: '18' },
{ text: '俯卧撑', value: '19' },
{ text: '健身操', value: '20' },
{ text: '上下楼(跑)', value: '21' },
{ text: '上下楼(走)', value: '22' },
],
questionnaire: [
{
title: '运动监测数据',
children: [
{
title: '您当日身体活动水平?',
type: 'picker',
key: 'physicalActivityLevel',
options: [],
placeholder: '请选择身体活动水平',
unit: '',
},
{
title: '体重(手动记录/每日监测)?',
type: 'input',
key: 'weight',
options: [],
unit: 'kg',
},
{
title: '当日运动情况?',
type: 'picker',
key: 'detailVoList',
placeholder: '请选择运动方式',
options: [],
unit: '',
},
],
},
],
});
// 打开弹窗
const openPicker = (key, index) => {
state.picker.show = true;
state.picker.key = key;
state.sportIndex = index;
state.picker.columns = key === 'physicalActivityLevel' ? state.singleRow : state.sportList;
};
// 运动方式
const onConfirmPicker = (val) => {
state.picker.show = false;
let key = state.picker.key;
if (key === 'physicalActivityLevel') {
state.healthMonitoring[`${key}`] = returnValue(state.singleRow, val.selectedValues[0]).text;
state.pickerVal = val.selectedValues[0];
}
if (key === 'detailVoList') {
let repeatVal = state.healthMonitoring.detailVoList.find((item) => item.exerciseMode === val.selectedOptions[0].text);
if (repeatVal) {
showFailToast({
message: '不能选择重复的运动方式',
forbidClick: true,
});
return false;
}
state.healthMonitoring.detailVoList[`${state.sportIndex}`].exerciseMode = returnValue(
state.sportList,
val.selectedValues[0]
).text;
}
};
// 运动时间
const onChangeSport = (val, index) => {
state.healthMonitoring.detailVoList[index].exerciseTime = val;
};
// 添加运动方式
const addSport = () => {
if (state.healthMonitoring.detailVoList.length >= 22) {
showFailToast({
message: '只能添加22种运动方式',
forbidClick: true,
});
return false;
}
state.healthMonitoring.detailVoList.push({
exerciseMode: '',
exerciseTime: 0,
});
};
// 删除运动方式
const deletePicker = (index) => {
state.healthMonitoring.detailVoList.splice(index, 1);
};
const returnValue = (list, val) => {
let find = list.find((item) => item.value === val);
if (find) return find;
};
const returnName = (list, val) => {
let find = list.find((item) => item.text === val);
if (find) return find;
};
const openBack = () => {
// router.go(-1);
destroyPage();
};
// 滑块
const onChangeRange = (key, value) => {
state.healthMonitoring[key] = value;
};
const openHealthMonitoringHelp = () => {
router.push({
path: 'healthMonitoringHelpC',
});
};
const getInfo = (time) => {
let params = {
date: time,
};
eatingInfo(params).then((res) => {
getRestult(res);
});
};
const getCheckInfo = (id) => {
let params = {
id: id,
};
checkInfo(params).then((res) => {
getRestult(res);
});
};
// 返回结果回填数据
const getRestult = (res) => {
state.healthMonitoring.physicalActivityLevel = res.data.physicalActivityLevel;
state.healthMonitoring.weight = res.data.weight;
let physicalLevel = res.data.physicalActivityLevel;
let sportList = res.data.detailVoList;
// 身体活动水平
if (physicalLevel) {
let physicalVal = returnValue(state.singleRow, physicalLevel);
if (physicalVal) {
state.healthMonitoring.physicalActivityLevel = physicalVal.text;
}
}
// 运动形式和时间回显
if (sportList.length > 0) {
sportList.map((item) => {
let sortVal = returnValue(state.sportList, item.exerciseMode);
if (sortVal) {
item.exerciseMode = sortVal.text;
}
});
state.healthMonitoring.detailVoList = sportList;
} else {
state.healthMonitoring.detailVoList = [
{
exerciseMode: '',
exerciseTime: 0,
},
];
}
};
const submit = () => {
let params = JSON.parse(JSON.stringify(state.healthMonitoring));
//every 一个条件不满足返回false
let detailVis = params.detailVoList.every((item) => item.exerciseMode !== '' && item.exerciseTime !== 0);
if (!detailVis || params.physicalActivityLevel === '' || params.weight === '') {
showFailToast({
message: '必填项不能为空',
forbidClick: true,
});
return false;
}
let physical = returnName(state.singleRow, params.physicalActivityLevel);
if (physical) {
params.physicalActivityLevel = state.id ? state.pickerVal : physical.value;
}
params.detailVoList.map((item) => {
let sportVal = returnName(state.sportList, item.exerciseMode);
if (sportVal) {
item.exerciseMode = sportVal.value;
}
});
loadingSpinner();
showConfirmDialog({
title: '提示',
message: '信息是否填写无误?',
}).then(() => {
// 标识1运动已填写
params.sportFlag = 1;
addOrEdit(params)
.then((res) => {
loadingClose();
showSuccessToast({
message: res.msg,
forbidClick: true,
});
setTimeout(() => {
openBack();
}, 1500);
})
.catch((err) => {
loadingClose();
showFailToast({
message: err.msg,
forbidClick: true,
});
});
});
};
const checkboxChange = (name, value) => {
let nameValue = state.healthMonitoring[name] ? state.healthMonitoring[name].split(',') : [];
let nameValueIndexOf = nameValue.indexOf(value);
if (nameValueIndexOf === -1) {
nameValue.push(value);
} else {
nameValue.splice(nameValueIndexOf, 1);
}
state.healthMonitoring[name] = nameValue.join(',');
};
// 点击选择问题 单选
const radioChange = (name, value) => {
state.healthMonitoring[name] = value;
};
onMounted(() => {
state.id = route.query.id;
if (route.query.id) {
getCheckInfo(route.query.id);
} else {
let currentTime = moment(new Date()).format('YYYY-MM-DD');
getInfo(currentTime);
}
});
return {
...toRefs(state),
openBack,
checkboxChange,
openHealthMonitoringHelp,
radioChange,
submit,
onChangeRange,
openPicker,
onConfirmPicker,
addSport,
onChangeSport,
deletePicker,
};
},
});
</script>
<style lang="less" scoped>
#healthMonitoring {
width: 100%;
height: 100%;
background-color: #f5f7fb;
.add-icon {
padding: 3px 10px;
color: #21bebe;
font-size: 12px;
border: 1px solid #21bebe;
border-radius: 20px;
display: flex;
align-items: center;
.van-icon {
font-size: 15px;
font-weight: bold;
padding-left: 5px;
}
}
.tips {
position: relative;
display: flex;
justify-content: space-between;
&:after {
content: '*';
position: absolute;
left: -7px;
top: -2px;
color: red;
}
}
.tips-info {
font-size: 13px;
color: #666;
line-height: 28px;
padding: 0 20px !important;
background-color: #f5f7fb !important;
span {
font-weight: bold;
}
}
.range {
display: flex;
line-height: 40px;
color: #999999;
.input_van {
width: 50%;
}
}
> div:nth-of-type(1) {
width: 100%;
position: relative;
padding: 0 0 25px 0;
overflow-y: scroll;
box-sizing: border-box;
height: calc(100% - 55px);
.help {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
width: 100%;
height: 38px;
color: #ff6028;
box-sizing: border-box;
line-height: 28px;
margin: 0 auto 20px;
border: 1px solid #e6e6ea;
background-color: rgba(255, 96, 40, 0.2);
.van-icon {
padding-left: 12px;
}
}
.container {
padding: 0 15px 0 15px;
> .title {
color: #252535;
height: 16px;
font-size: 16px;
font-weight: bold;
line-height: 16px;
text-align: center;
margin-bottom: 20px;
}
> .content {
width: 100%;
margin-bottom: 30px;
> .mask {
position: relative;
&:before {
content: '';
position: absolute;
z-index: 3;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0);
}
}
> div {
width: 100%;
border-radius: 8px;
margin-bottom: 15px;
padding: 20px 15px 25px;
box-sizing: border-box;
background-color: #ffffff;
> div:first-of-type {
color: #252535;
width: 100%;
font-size: 14px;
font-weight: bold;
line-height: 20px;
margin-bottom: 10px;
}
> .radio,
> .checkbox {
width: 100%;
padding: 0 8px;
box-sizing: border-box;
.rang-options {
margin: 0;
height: 120px;
padding: 0 10px 0 10px;
border-top: none;
border-left: none;
border-right: none;
border-bottom: 1px solid #e6e6ea;
border-radius: 0;
.name {
margin: 0 0 25px -10px;
font-size: 14px;
color: #333333;
}
}
> div {
color: #77849e;
font-size: 15px;
width: 100%;
height: 50px;
line-height: 50px;
padding: 0 20px;
border-radius: 5px;
margin-bottom: 20px;
box-sizing: border-box;
border: 1px solid #e6e6ea;
&:last-of-type {
margin-bottom: 0;
}
}
.active {
color: #21bebe;
background-color: rgba(33, 190, 190, 0.2);
border: 1px solid #21bebe;
}
}
> .detailVoList {
width: 100%;
height: 100%;
.detailVoList-item {
width: 100%;
height: 190px;
display: flex;
flex-direction: column;
justify-content: center;
border-bottom: 1px solid #e6e6ea;
position: relative;
.sport-input {
display: flex;
flex-wrap: nowrap;
border-radius: 5px;
position: relative;
.sport-icon {
position: absolute;
right: 5px;
top: 30%;
color: #e6e6ea;
font-size: 20px;
}
.van-cell {
border: 1px solid #e6e6ea;
border-radius: 5px;
}
}
.sport-delete {
position: absolute;
right: 5px;
top: 25%;
color: #77849e;
display: flex;
flex-direction: column;
justify-content: center;
.van-icon {
font-size: 18px;
}
span {
font-size: 11px;
margin-top: 2px;
}
}
.sport-times {
font-size: 12px;
margin-top: 10px;
padding: 0 18px;
span {
margin-left: -12px;
display: inline-block;
margin-bottom: 36px;
}
}
}
}
> .inputAfter {
width: 100%;
height: 100%;
padding: 0 8px;
box-sizing: border-box;
> div {
width: 100%;
height: 50px;
display: flex;
flex-wrap: nowrap;
line-height: 50px;
padding-right: 20px;
border-radius: 5px;
margin-bottom: 20px;
box-sizing: border-box;
border: 1px solid #e6e6ea;
> div:first-of-type {
flex: 1;
height: 50px;
padding: 2px 0;
box-sizing: border-box;
position: relative;
input {
width: 100%;
height: 30px;
}
.sport-arrow {
position: absolute;
right: -5%;
top: 30%;
color: #e6e6ea;
font-size: 20px;
}
}
> div:last-of-type {
color: #77849e;
font-size: 15px;
height: 50px;
line-height: 50px;
}
&:last-of-type {
margin-bottom: 0;
}
}
.range-container {
padding: 34px 10px 20px 10px;
border: none;
}
}
}
}
}
}
> div:nth-of-type(2) {
width: 100%;
height: 54px;
padding: 7px 28px;
box-sizing: border-box;
background-color: #ffffff;
> div {
color: #ffffff;
font-size: 15px;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
border-radius: 8px;
background-color: #21bebe;
}
}
}
</style>
@@ -0,0 +1,255 @@
<template>
<div id="suggestionsOnEatingDynamicBalance">
<div>
<div class="header-bg">
<div :class="statusHeader" class="header-info">
<div class="header">
<span>姓名{{ suggestionsOnEatingDynamicBalance.name }}</span>
<span class="status-font">{{ suggestionsOnEatingDynamicBalance.desDesc }}</span>
</div>
<div class="header-status">
<div class="circle-seat circle-border" :style="{ left: circleWidth }"></div>
</div>
<div class="status-name">
<span>负平衡</span>
<span>基本平衡</span>
<span>正平衡</span>
</div>
</div>
<div class="balance">您当日能量剩余量DES{{ suggestionsOnEatingDynamicBalance.desValue }} kcal</div>
</div>
</div>
<div>
<div>
<div>行为指导建议</div>
<div>
<p v-for="item in suggestionsOnEatingDynamicBalance.getSomeAction">{{ item }}</p>
</div>
</div>
<div>
<div>膳食指导建议</div>
<div>
<p v-for="item in suggestionsOnEatingDynamicBalance.diet">{{ item }}</p>
</div>
</div>
<div>
<div>运动指导建议</div>
<div>
<p v-for="item in suggestionsOnEatingDynamicBalance.motion">{{ item }}</p>
</div>
</div>
</div>
</div>
</template>
<script>
import { getSuggestionsOnEating } from '/@/api/index';
import { defineComponent, onMounted, reactive, toRefs } from 'vue';
import { showFailToast } from 'vant';
export default defineComponent({
setup() {
const state = reactive({
suggestionsOnEatingDynamicBalance: {},
statusHeader: '',
circleWidth: '',
});
const getGetSuggestionsOnEating = () => {
let params = {};
getSuggestionsOnEating(params)
.then((res) => {
state.suggestionsOnEatingDynamicBalance = res.data;
switch (res.data.desLevel) {
case 1:
//基本平衡
state.statusHeader = 'status-bg2';
state.circleWidth = '45%';
break;
case 2:
// 正平衡-能量过量
state.statusHeader = 'status-bg3';
state.circleWidth = '80%';
break;
case 3:
// 正平衡-能量严重过量
state.statusHeader = 'status-bg3';
state.circleWidth = '95%';
break;
case 4:
// 负平衡-能量不足
state.statusHeader = 'status-bg1';
state.circleWidth = '13%';
break;
case 5:
// 负平衡-能量严重不足
state.statusHeader = 'status-bg1';
state.circleWidth = '0';
break;
default:
state.statusHeader = 'status-bg2';
state.circleWidth = '45%';
break;
}
})
.catch((err) => {
showFailToast({
message: err.msg,
forbidClick: true,
});
});
};
onMounted(() => {
getGetSuggestionsOnEating();
});
return {
...toRefs(state),
};
},
});
</script>
<style lang="less" scoped>
#suggestionsOnEatingDynamicBalance {
width: 100%;
background-color: #f5f7fb;
> div:nth-of-type(1) {
width: 100%;
padding: 15px;
margin: 0 auto;
box-sizing: border-box;
.header-bg {
background-color: #ffffff;
border-radius: 15px;
}
.header-info {
color: #252535;
width: 100%;
height: 115px;
font-size: 14px;
line-height: 14px;
font-weight: bold;
border-radius: 15px;
box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.14);
.header {
display: flex;
justify-content: space-between;
padding: 20px;
}
.header-status {
width: 90%;
margin: 0 auto;
height: 10px;
border-radius: 10px;
background-image: linear-gradient(to right, rgba(255, 79, 68, 1), rgba(255, 214, 51, 1), rgba(42, 199, 159, 1));
position: relative;
.circle-seat {
position: absolute;
top: -5px;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: #ffffff;
}
}
.status-name {
padding-top: 14px;
width: 90%;
margin: 0 auto;
display: flex;
justify-content: space-around;
color: #333333;
font-size: 12px;
}
}
.status-bg1 {
background-color: rgba(255, 79, 68, 0.14);
.status-font {
color: #ff4f44;
}
.circle-border {
border: 1px solid #ff4f44;
}
}
.status-bg2 {
background-color: rgba(255, 214, 51, 0.14);
.status-font {
color: #ffc833;
}
.circle-border {
border: 1px solid #ffc833;
}
}
.status-bg3 {
background-color: rgba(48, 205, 155, 0.14);
.status-font {
color: #2ac79f;
}
.circle-border {
border: 1px solid #2ac79f;
}
}
.balance {
padding: 20px;
color: #77849e;
font-size: 14px;
}
}
> div:nth-of-type(2) {
width: 100%;
padding: 20px 15px 0;
box-sizing: border-box;
background-color: #ffffff;
border-radius: 15px 15px 0 0;
> div {
width: 100%;
margin-bottom: 20px;
> div:first-of-type {
color: #252535;
font-size: 16px;
width: 100%;
height: 40px;
line-height: 40px;
padding: 0 10px;
box-sizing: border-box;
margin-bottom: 12px;
background-color: #eef0f4;
}
> div:last-of-type {
width: 100%;
padding: 0 12px;
box-sizing: border-box;
p {
color: #77849e;
width: 100%;
line-height: 20px;
font-size: 14px;
}
}
}
}
}
</style>
@@ -0,0 +1,413 @@
<template>
<div id="weekly">
<div>
<div>评估日期{{ weekly.date }}</div>
<div>
<div class="header-info">
<div class="header">
<span>姓名{{ weekly.name }}</span>
</div>
</div>
<div>
<div style="margin-bottom: 10px">性别{{ weekly.sex === '1' ? '女' : '男' }}</div>
<div style="margin-bottom: 10px">年龄{{ weekly.age }}</div>
<div style="margin-bottom: 10px">身高{{ weekly.height }}m</div>
<div>体重{{ weekly.weight }}kg</div>
<div>腰围{{ weekly.waistline }}cm</div>
<div></div>
</div>
</div>
</div>
<div>
<div>
<div class="title">测验目的与意义</div>
<div class="meaning">
该问卷包括监测数据和随访数据针对膳食数据监测和运动数据监测有效的对体重进行管理体重管理不仅是减重还包括调整饮食运动和心理行为重塑生活方式以达到改善健康状况的目的
</div>
</div>
<div>
<div class="title">测验结果</div>
<div class="card">
<div>指标信息</div>
<div>
<div>
<div
>BMI<span>{{ weekly.bmi }}kg/</span></div
>
<div>{{ weekly.habitusType }}</div>
</div>
<template v-if="weekly.indexVo">
<div>
<div
>血压<span>收缩压{{ weekly.indexVo.systolicPressure }}mmHg</span></div
>
<div>{{ weekly.indexVo.pressureStatus }}</div>
</div>
<div>
<div
>&#12288;&#12288;&#12288;<span>舒张压{{ weekly.diastolicPressure }}mmHg</span></div
>
<div></div>
</div>
</template>
</div>
</div>
<div class="card" v-if="weekly.dietVo">
<div>膳食数据</div>
<div>
<div>
<div
>本周总能量摄入量<span>{{ weekly.dietVo.foodTotalEnergy }}kcal</span></div
>
<div>{{ weekly.dietVo.totalEnergyStatus }}</div>
</div>
<div>
<div
>碳水化合物供能占比<span>{{ weekly.dietVo.cho }}%</span></div
>
<div>{{ weekly.dietVo.choStatus }}</div>
</div>
<div>
<div
>蛋白质供能占比<span>{{ weekly.dietVo.pro }}%</span></div
>
<div>{{ weekly.dietVo.proStatus }}</div>
</div>
<div>
<div
>脂肪供能占比<span>{{ weekly.dietVo.fat }}%</span></div
>
<div>{{ weekly.dietVo.fatStatus }}</div>
</div>
</div>
</div>
<div class="card" v-if="weekly.sportVo">
<div>运动数据</div>
<div>
<div>
<div>身体活动水平分级</div>
<div v-if="weekly.sportVo.physicalActivityLevel === '1'"></div>
<div v-if="weekly.sportVo.physicalActivityLevel === '2'"></div>
<div v-if="weekly.sportVo.physicalActivityLevel === '3'"></div>
</div>
<div>
<div
>本周运动消耗<span>{{ weekly.sportVo.totalSportConsume }}kcal</span></div
>
<div v-if="weekly.sportVo.totalSportConsumeStatus">{{ weekly.sportVo.totalSportConsumeStatus }}</div>
<div v-else>--</div>
</div>
<div>
<div class="sport_name">运动情况</div>
<div v-if="weekly.sportVo.detailVoList && weekly.sportVo.detailVoList.length === 0">--</div>
</div>
<div class="sport_container" v-if="weekly.sportVo.detailVoList && weekly.sportVo.detailVoList.length > 0">
<div class="item" v-for="item in weekly.sportVo.detailVoList">
<span>{{ item.exerciseModeTrans }}</span>
<span>{{ item.exerciseTime }}分钟</span>
</div>
</div>
</div>
</div>
<div class="card" v-if="weekly.sportVo">
<div>能量消耗</div>
<div>
<div>
<div
>基础代谢率BMR:<span>{{ weekly.sportVo.bmr }}kcal</span></div
>
<div>--</div>
</div>
<div>
<div
>本周工作消耗<span>{{ weekly.sportVo.totalWorkConsume }}kcal</span></div
>
<div>--</div>
</div>
<div>
<div
>本周总消耗热量<span>{{ weekly.sportVo.totalEnergyConsume }}kcal</span></div
>
<div>--</div>
</div>
</div>
</div>
<div class="card" v-if="weekly.desValue">
<div>吃动平衡评估结果</div>
<div>
<div>
<div
>本周能量剩余量DES:<span>{{ weekly.desValue }}kcal</span></div
>
<div class="result-warp">{{ weekly.desDesc }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { monitoringReportsOneWeek } from '/@/api/index';
import { defineComponent, onMounted, reactive, toRefs } from 'vue';
import { showFailToast } from 'vant';
export default defineComponent({
setup() {
const state = reactive({
weekly: {},
statusHeader: '',
});
const getMonitoringReportsOneWeek = () => {
let params = {};
monitoringReportsOneWeek(params)
.then((res) => {
state.weekly = res.data;
})
.catch((err) => {
showFailToast({
message: err.msg,
forbidClick: true,
});
});
};
onMounted(() => {
getMonitoringReportsOneWeek();
});
return {
...toRefs(state),
};
},
});
</script>
<style lang="less" scoped>
#weekly {
width: 100%;
background-color: #f5f7fb;
> div:nth-of-type(1) {
width: 100%;
padding: 15px;
box-sizing: border-box;
> div:first-of-type {
height: 14px;
font-size: 14px;
color: #77849e;
line-height: 14px;
margin-bottom: 10px;
}
.status-bg1 {
background-color: rgba(255, 79, 68, 0.14);
.status-font {
color: #ff4f44;
}
.circle-border {
border: 1px solid #ff4f44;
}
}
.status-bg2 {
background-color: rgba(255, 214, 51, 0.14);
.status-font {
color: #ffc833;
}
.circle-border {
border: 1px solid #ffc833;
}
}
.status-bg3 {
background-color: rgba(48, 205, 155, 0.14);
.status-font {
color: #2ac79f;
}
.circle-border {
border: 1px solid #2ac79f;
}
}
> div:last-of-type {
width: 100%;
border-radius: 15px;
box-sizing: border-box;
background-color: #ffffff;
box-shadow: 0 3px 8px 0 rgba(37, 37, 53, 0.1);
> div:first-of-type {
color: #252535;
width: 100%;
font-size: 14px;
line-height: 14px;
font-weight: bold;
}
.header-info {
color: #252535;
width: 100%;
font-size: 14px;
font-weight: bold;
border-radius: 15px;
.header {
display: flex;
justify-content: space-between;
padding-left: 20px;
padding-top: 20px;
}
.header-status {
width: 90%;
margin: 0 auto;
height: 10px;
border-radius: 10px;
background-image: linear-gradient(to right, rgba(255, 79, 68, 1), rgba(255, 214, 51, 1), rgba(42, 199, 159, 1));
position: relative;
.circle-seat {
position: absolute;
top: -5px;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: #ffffff;
}
}
.status-name {
padding-top: 14px;
width: 90%;
margin: 0 auto;
display: flex;
justify-content: space-around;
color: #333333;
font-size: 12px;
}
}
> div:last-of-type {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 10px 20px 20px 20px;
> div {
flex: 0 0 33.333333%;
color: #77849e;
height: 14px;
font-size: 14px;
line-height: 14px;
}
}
}
}
> div:nth-of-type(2) {
width: 100%;
padding: 20px 15px 0;
box-sizing: border-box;
background-color: #ffffff;
border-radius: 15px 15px 0 0;
> div {
width: 100%;
margin-bottom: 24px;
> .title {
color: #252535;
width: 100%;
height: 16px;
font-size: 16px;
line-height: 16px;
font-weight: bold;
margin-bottom: 12px;
}
> .meaning {
width: 100%;
color: #77849e;
font-size: 13px;
line-height: 22px;
text-indent: 2em;
}
.sport_name {
color: #252535 !important;
}
> .card {
width: 100%;
margin-bottom: 20px;
> div:first-of-type {
width: 100%;
height: 40px;
color: #252535;
font-size: 16px;
padding: 0 10px;
line-height: 40px;
box-sizing: border-box;
background-color: #eef0f4;
}
> div:last-of-type {
width: 100%;
padding: 15px 12px;
box-sizing: border-box;
background-color: #f5f7fb;
border-radius: 0 0 8px 8px;
.sport_container {
width: 100%;
text-align: center;
display: flex;
height: auto;
flex-direction: column;
.item {
padding: 8px 0;
font-size: 13px;
span {
display: inline-block;
color: #77849e;
width: 50%;
text-align: right;
&:first-of-type {
color: #252535;
}
&:last-of-type {
}
}
}
}
> div {
height: 14px;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
margin-bottom: 15px;
> div:first-of-type {
color: #252535;
height: 14px;
font-size: 13px;
line-height: 14px;
> span {
color: #77849e;
}
}
> div:last-of-type {
color: #77849e;
height: 14px;
font-size: 13px;
line-height: 14px;
}
.result-warp {
width: 22%;
}
&:last-of-type {
margin-bottom: 0;
}
}
}
}
}
}
}
</style>
@@ -0,0 +1,197 @@
<template>
<div class="etiological">
<HeaderCard :column="listColumn" :titleList="titleList" :checkTitle="checkTitle" @change="handleChange" />
<div class="etioloical-subject" id="etioloicalView">
<div class="subject-item" v-for="(subject, index) in subjectList" :key="index">
<div class="subject-title">{{ subject.className }}</div>
<div class="question-con" v-for="(qsItem, qsIndex) in subject.qsList" :key="qsIndex">
<div class="subtitle">{{ qsIndex + 1 }}{{ qsItem.qsDes }}</div>
<div class="subject-options" v-if="qsItem.qsType === 1">
<van-radio-group v-model="qsItem.answer" v-for="(option, optionIndex) in qsItem.answerList" :key="optionIndex" shape="dot">
<van-radio class="subject-radio" :name="option.answerNo">{{ option.answerDesc }}</van-radio>
</van-radio-group>
</div>
<template v-if="qsItem.qsType === 4">
<div class="subject-input">
<van-field v-model="qsItem.answer" />
</div>
</template>
</div>
</div>
</div>
<div class="etioloical-button">
<div class="button-text" @click="submit">提交</div>
</div>
</div>
</template>
<script setup lang="ts">
import HeaderCard from '../health/components/HeaderCard.vue';
import { questionListApi, submitQuestionApi } from './etiologicalApi';
import { ref } from 'vue';
import { showSuccessToast, showFailToast } from 'vant';
import { useRouter } from 'vue-router';
import { destroyPage } from '/@/hooks/openPage';
import { useLoading } from '../../utils/compUtils';
const { loadingSpinner, loadingClose } = useLoading();
const router = useRouter();
const checkTitle = ref('');
const listColumn = {
name: 'name',
value: 'id',
};
const subjectList = ref([]);
const titleList = ref([]);
const subject = ref(null);
getQuestion();
function getQuestion() {
questionListApi({
templateNo: checkTitle.value,
}).then((res: any) => {
let { answer, dict, questions } = res.data;
titleList.value = dict;
checkTitle.value = checkTitle.value === '' ? dict[0].id : checkTitle.value;
subjectList.value = questions.qsClassList;
if (answer) getAnswer();
});
}
/*
* 回填将选中的答案改为数字类型
* */
function getAnswer() {
let queList = subjectList.value;
for (let i = 0; i < queList.length; i++) {
for (let y = 0; y < queList[i].qsList.length; y++) {
queList[i].qsList[y].answer = parseInt(queList[i].qsList[y].answer);
}
}
}
function handleChange(templateNo: number) {
checkTitle.value = templateNo;
scrollPosition();
getQuestion();
}
// 设置滚动条的初始位置
function scrollPosition() {
let elementView = document.getElementById('etioloicalView');
let { x, y } = elementView.getBoundingClientRect();
elementView.scrollTo(x, y);
}
function submit() {
let list: object[] = [];
let status = true;
let queList = subjectList.value;
for (let i = 0; i < queList.length; i++) {
for (let y = 0; y < queList[i].qsList.length; y++) {
if (queList[i].qsList[y].answer == null) {
status = false;
break;
}
}
}
if (!status) {
showFailToast({
message: '请填写完再提交',
forbidClick: true,
});
return false;
}
loadingSpinner();
let newsQueList = JSON.parse(JSON.stringify(queList));
newsQueList.map((item: object) => {
item.qsList.map((qsitem: object) => {
delete qsitem.answerList;
delete qsitem.factor;
delete qsitem.qsDes;
delete qsitem.qsType;
delete qsitem.count;
delete qsitem.applicableType;
list.push(qsitem);
});
});
submitQuestionApi({
templateNo: checkTitle.value,
qsList: list,
}).then((res: any) => {
if (res.code === 200) {
loadingClose();
// destroyPage();
showSuccessToast({
message: '提交成功',
forbidClick: true,
});
} else {
loadingClose();
showFailToast({
message: res.data.msg,
forbidClick: true,
});
}
});
}
</script>
<style scoped lang="less">
@import '../../assets/less/index.less';
.etiological {
background-color: @card-gray;
.etioloical-subject {
background-color: @card-fff;
height: calc(100vh - 120px);
padding: 0 20px;
overflow-y: auto;
margin-top: 10px;
:deep(.van-cell) {
padding: 0;
}
:deep(.van-radio__icon--checked) {
border-color: #21bebe;
}
:deep(.van-radio__icon--checked.van-radio__icon--dot .van-radio__icon--dot__icon) {
background-color: #21bebe;
}
.subject-input {
border-bottom: 1px solid #e0e0e0;
padding: 0 0 5px 5px;
}
.subject-item {
margin: 10px 0;
.subject-title {
color: #333333;
font-weight: bold;
padding: 10px 0;
}
.question-con {
padding: 0 5px;
.subtitle {
font-size: 15px;
padding: 10px 0 10px 5px;
position: relative;
&:after {
content: '*';
position: absolute;
left: -2px;
top: 8px;
color: #fd1805;
}
}
}
.subject-radio {
margin: 14px 0;
}
}
}
.etioloical-button {
background-color: @card-fff;
height: 50px;
padding-top: 20px;
.button-text {
height: 40px;
flex: 1;
.flex-center();
margin: 0 10%;
.footerButton();
}
}
}
</style>
@@ -0,0 +1,7 @@
import { get, post } from '/@/views/mobile/api/api';
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX || '';
export const questionListApi = (params) => get(`${prefix}/rest/health-survey/trace/questionnaire`, params);
export const submitQuestionApi = (params) => post(`${prefix}/rest/health-survey/trace/submit`, params);
@@ -0,0 +1,258 @@
<template>
<div class="answer-page">
<div class="answer-progress">
<div class="progress-text">
<div class="progress-left"
>答题进度 {{ schedule }}/{{ questionList.length }}
<span>{{ toInt(progressScore) }}%</span>
</div>
<div class="progress-right"
>作答时间 <span>{{ formatTime }}</span></div
>
</div>
<div class="progress-outer">
<a-progress :percent="(100 / questionList.length) * schedule" :show-info="false" strokeLinecap="square" strokeColor="#21BEBE" />
</div>
</div>
<div class="answer-content">
<!--答题区域-->
<van-skeleton title :row="8" :loading="loading">
<QuestionList
class="page"
:class="pageActive ? '' : 'active'"
:key="Date.now()"
:isEdit="isEdit"
:list="questionList"
:current="questionCurrent"
@change-option="changeOption"
/>
</van-skeleton>
<AnswerFooter class="bottom-module" @popup-toggle="popupToggle" @next-question="nextQuestion" :next-questionText="nextQuestionText" />
</div>
<van-popup v-model:show="showPopup" position="bottom" :style="{ height: '100%' }">
<AnswerPanel :isEdit="isEdit" :list="questionList" v-if="showPopup" @back="back" @go-to-question="goToQuestion" />
</van-popup>
<QuitAnswering :questionnaireIndex="questionCurrent" ref="quitDialog" @exit="exit" />
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, unref, watch } from 'vue';
import QuestionList from '/@/components/questionList/questionList.vue';
import AnswerPanel from '/@/views/health-questions/answerPanel.vue';
import AnswerFooter from '/@/views/health-questions/components/answerFooter.vue';
import QuitAnswering from '/@/components/quitAnswering.vue';
import { useRouter, useRoute, onBeforeRouteLeave } from 'vue-router';
import { list, useNextQuestionText, useTime } from '/@/views/health-questions/useQuestion';
import { showConfirmDialog, showToast } from 'vant';
import { getAnswerList, submitAnswer } from '/@/views/health-questions/api';
import { timeFormat, toInt } from '/@/hooks/utils';
import { newPageParams } from '/@/hooks/openPage';
import { useInterceptBack } from '/@/hooks/useInterceptAndroid';
import { useLoading } from '/@/utils/compUtils';
const progressScore = computed(() => {
if (!questionList.value?.length) return 0;
return (100 / questionList.value.length) * schedule.value;
});
const loading = ref(true); //加载骨架屏
const router = useRouter();
const route = useRoute();
const questionList = ref([]); // 题目数据
const questionCurrent = ref(0); // 当前题号
const time = ref({
startDate: timeFormat(new Date()),
endDate: '',
});
const schedule = computed(() => {
let arr = questionList.value.map((item) => item.qsOptionCode) || [];
return arr.filter((item) => item != null && item != '').length || 0;
});
// 底部下一题按钮文本
const nextQuestionText = computed(() => useNextQuestionText(questionList.value.length, questionCurrent, true));
const isEdit = true;
const { formatTime, startTimer, pauseTimer } = useTime(); //定时器
onMounted(() => {
getList();
});
const pageActive = ref(true);
watch(questionCurrent, () => {
if (questionCurrent.value !== 0) {
pageActive.value = false;
}
});
async function getList() {
let { code, data } = await getAnswerList({ quTotal: '10', module: route.query.module });
loading.value = false;
if (code !== 200) {
return showToast('题库加载失败,请稍后再试');
}
if (!data.length) {
showToast('暂无答题内容');
let timerId = setTimeout(() => {
clearTimeout(timerId);
return router.go(-1);
}, 2000);
} else {
startTimer();
questionList.value = data || [];
}
}
//选择答题选项
function changeOption(answer: any) {
let current: string = questionCurrent.value + '';
questionList.value[current] = unref(answer);
}
// 退出弹窗
const quitDialog: any = ref(null);
const { loadingSpinner, loadingClose } = useLoading();
function nextQuestion() {
// 1.如果没有选择禁止进入下一题
let len = questionList.value.length;
let current = questionCurrent.value;
let { qsOptionCode } = questionList.value[current] || '';
if (!qsOptionCode) {
return showToast('请先填写此问题');
}
if (len - 1 > current) {
questionCurrent.value += 1;
quitDialog.value.editBackStatus(current !== len);
//判断是否有没做的题
const list = unref(questionList.value);
let index = list.findIndex((item) => !item?.qsOptionCode);
if (index == -1) {
questionCurrent.value = questionList.value.length - 1;
}
} else {
//判断是否有没做的题
const list = unref(questionList.value);
let index = list.findIndex((item) => !item?.qsOptionCode);
if (index !== -1) {
return showToast(`${index + 1}题没做,请答完再提交`);
}
quitDialog.value.editBackStatus(false);
showConfirmDialog({
title: '',
message: '是否确认提交',
closeOnPopstate: false,
})
.then(() => {
useInterceptBack(0);
time.value.endDate = timeFormat(new Date());
const params = {
...time.value,
list: questionList.value,
module: route.query.module,
};
loadingSpinner();
submitAnswer(params)
.then(({ code, data, message }) => {
loadingClose();
if (code !== 200 || !data) {
return showToast(message);
}
router.replace({
path: '/complete-answer',
query: newPageParams({
id: data + '',
module: route.query.module,
}),
});
})
.catch((msg) => {
console.log('msg', msg);
loadingClose();
showToast('请稍后再试!');
});
pauseTimer();
})
.catch(() => {
// on cancel
});
}
}
//
const showPopup = ref(false);
function popupToggle() {
showPopup.value = !showPopup.value;
}
function back() {
popupToggle();
}
// 答题卡切题
function goToQuestion(index: number) {
questionCurrent.value = index;
}
function exit() {
goToQuestion(1);
}
</script>
<style scoped lang="less">
@import '/@/assets/less/index';
.page {
position: relative;
left: 0;
}
/* 设置页面的目标位置 */
.page.active {
animation: move 0.4s forwards;
position: relative;
}
@keyframes move {
0% {
left: 0;
}
49% {
left: -100%;
}
50% {
left: 100%;
}
100% {
left: 0;
}
}
.answer-page {
height: 100vh;
font-size: 16px;
.flex-column();
justify-content: space-between;
.answer-progress {
height: 60px;
.progress-text {
margin-top: 10px;
padding: 0 16px;
display: flex;
justify-content: space-between;
}
.progress-left {
}
.progress-right {
}
}
.answer-content {
min-height: 400px;
flex: 1;
.flex-column();
justify-content: space-between;
overflow: hidden;
.bottom-module {
padding-bottom: 20px;
}
}
}
</style>
@@ -0,0 +1,117 @@
<!--答题卡页-->
<template>
<div class="question-panel">
<div class="question-container">
<AnswerStatusDesc :isEdit="isEdit" />
<div class="question">
<div class="question-list">
<div v-for="(question, index) in list" :key="index" @click="goToQuestion(index)" class="question-item" :class="status(index)">
{{ index + 1 }}
</div>
</div>
</div>
</div>
<div class="back" @click="backPage">返回</div>
</div>
</template>
<script setup lang="ts">
import AnswerStatusDesc from '/@/views/health-questions/components/answerStatusDesc.vue';
const emit = defineEmits(['back', 'goToQuestion']);
const props = defineProps({
isEdit: {
type: Boolean,
default: () => true,
},
list: {
type: Array,
},
// 答题卡选择状态
answerStatus: {
type: Array,
},
});
const status = (i) => {
if (props.isEdit) {
return props.list[i].qsOptionCode ? 'already' : '';
} else {
return props.answerStatus[i] ? 'already' : 'wrong';
}
};
function goToQuestion(index: number) {
emit('goToQuestion', index);
emit('back');
}
function backPage() {
emit('back');
}
</script>
<style scoped lang="less">
@import '../../assets/less/index';
.question-panel {
display: flex;
height: 100vh;
flex-direction: column;
justify-content: space-between;
.question-container {
flex: 1;
overflow: auto;
}
.question {
margin: 10px 10px;
.question-topic {
font-size: 16px;
}
.question-subText {
font-size: 14px;
color: #999999;
}
}
.question-list {
display: flex;
flex-wrap: wrap;
margin: 10px 0;
.question-item {
width: 40px;
height: 40px;
margin: 10px;
margin-bottom: 0;
border-radius: 50%;
.flex-center();
border: 1px solid #999999;
&.already {
background: rgba(33, 190, 190, 0.2);
//border: 1px solid #21bebe;
border: none;
color: #21bebe;
}
&.wrong {
background: rgba(237, 42, 38, 0.2);
//border: 1px solid #ed2a26;
border: none;
color: #ed2a26;
}
}
}
.back {
margin: 20px 30%;
height: 35px;
background: #ffffff;
border: 1px solid #999999;
border-radius: 35px;
.flex-center();
}
}
</style>
@@ -0,0 +1,64 @@
<template>
<div class="answer-records-list" :class="[empty ? 'em-bg' : '']">
<template v-for="(item, k) in recordList" :key="k">
<AnswerRecordsCard :cardInfo="item" cardType="card1" @view-record="viewRecord" />
</template>
<van-empty class="em-position" v-if="empty" image-size="200" description="暂无数据" />
</div>
</template>
<script setup lang="ts">
import AnswerRecordsCard from '/@/views/health-questions/components/answerRecordsCard.vue';
import { onMounted, ref } from 'vue';
import { answerRecordList } from '/@/views/health-questions/api';
import { newPageParams, openPage } from '/@/hooks/openPage';
import { useRoute } from 'vue-router';
const route = useRoute();
const recordList = ref([]);
const empty = ref(false);
onMounted(() => {
empty.value = false;
getRecordList();
});
function getRecordList() {
answerRecordList({ module: route.query.module })
.then(({ code, data }) => {
if (code == 200) {
if (!data.length) return (empty.value = true);
recordList.value = data;
empty.value = false;
}
})
.catch(() => {
empty.value = true;
});
}
// 查看记录
function viewRecord(id: number) {
openPage('/complete-answer', newPageParams({ id: id + '', module: route.query.module }));
}
</script>
<style scoped lang="less">
@import '../../assets/less/index.less';
.answer-records-list {
height: 100%;
overflow: auto;
.flex-column();
padding: 10px 20px;
background-color: #efefef;
&.em-bg {
background-color: #fff;
}
.em-position {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
}
</style>
+34
View File
@@ -0,0 +1,34 @@
import { get, post } from '/@/views/mobile/api/api';
import { apiPrefix } from '/@/api/apiPrefix';
export enum QsType {
testType = '1', //1推荐试卷 0 固定试卷
}
const prefix: string = import.meta.env.VITE_GLOB_APP_PREFIX;
const { healthAnswer } = apiPrefix;
export const Api = {
answerList: prefix + `/rest/${healthAnswer}/answer/answerList`,
submitAnswer: prefix + `/rest/${healthAnswer}/answer/submitAnswer`,
answerInfoByRecordId: prefix + `/rest/${healthAnswer}/answer/answerInfoByRecordId`,
answerRecordList: prefix + `/rest/${healthAnswer}/answer/answerUserRecordList`,
questionHome: prefix + `/rest/${healthAnswer}/answer/home`,
};
// 获取题目
export const getAnswerList = (params: any) =>
post(Api.answerList, {
qsAnsType: QsType.testType,
...params,
});
// 提交试卷
export const submitAnswer = (params: any) => post(Api.submitAnswer, params);
// 查询考卷记录
export const answerInfoByRecordId = (params: any) => post(Api.answerInfoByRecordId, params);
// 查询答题记录列表
export const answerRecordList = (params: any) => get(Api.answerRecordList, params);
//题目列表接口
export const questionHome = (params: any) => get(Api.questionHome, params);
@@ -0,0 +1,286 @@
<!--完成答题页-->
<template>
<div class="complete-answer-page">
<div class="bg-top">
<div class="top-content">
<div>
<p class="complete-title">完成答题</p>
<p class="complete-sub-title">快来看看你的得分情况吧</p>
</div>
<div class="item-group">
<div class="item-row">
<div class="item-col">
<p>{{ recordInfo.questionNum }}</p>
<p>总计()</p>
</div>
<div class="item-col">
<p>{{ recordInfo.trueAnswer }}</p>
<p>答对()</p>
</div>
<div class="item-col">
<p>{{ timeFormat(recordInfo.startTime, 'HH:mm') }}</p>
<p>{{ timeFormat(recordInfo.startTime, 'yyyy-MM-DD') }}</p>
</div>
</div>
<div class="item-row">
<div class="item-col">
<p>{{ recordInfo.totalScore }}</p>
<p>总分()</p>
</div>
<div class="item-col">
<p>{{ recordInfo.sumScore }}</p>
<p>得分()</p>
</div>
<div class="item-col">
<p>{{ recordInfo.answerUseTime }}</p>
<p>作答用时()</p>
</div>
</div>
</div>
</div>
</div>
<div class="answer-content">
<div class="answer-con-top">
<van-skeleton title :row="8" :loading="loading">
<ResultQuestion :isEdit="false" :list="questionList" :current="questionCurrent" />
</van-skeleton>
</div>
<div>
<AnswerFooter @popup-toggle="popupToggle" @next-question="nextQuestion" :nextQuestionText="nextQuestionText" />
</div>
</div>
<van-popup v-model:show="showPopup" :style="{ height: '100%' }" position="bottom">
<AnswerPanel
v-if="showPopup"
:isEdit="false"
:list="questionList"
:answer-status="answerStatus"
@back="back"
@go-to-question="goToQuestion"
/>
</van-popup>
</div>
</template>
<script setup lang="ts">
import ResultQuestion from '/@/components/result-question/resultQuestion.vue';
import AnswerFooter from '/@/views/health-questions/components/answerFooter.vue';
import { computed, onMounted, ref } from 'vue';
import { useNextQuestionText } from '/@/views/health-questions/useQuestion';
import AnswerPanel from '/@/views/health-questions/answerPanel.vue';
import { useRoute, useRouter } from 'vue-router';
import { answerInfoByRecordId } from '/@/views/health-questions/api';
import { showToast } from 'vant';
import { timeFormat } from '/@/hooks/utils';
import { newPageParams } from '/@/hooks/openPage.ts';
const router = useRouter();
const route = useRoute();
const questionList = ref([]);
// 答题卡状态-颜色
const answerStatus = computed(() => {
let arr: any[] = [];
questionList.value.map((item: any) => {
// 单选
if (item.qsAnsType == 0) {
let status = item.optionVoList.find((val: any) => val.qsScore !== 0).qsOptionCode == item?.chooseOption;
arr.push(status);
// 多选
} else if (item.qsAnsType == 1) {
let correct = item.optionVoList.filter((val: any) => val.qsScore !== 0);
let status = correct.every((val: any) => item.chooseOption.includes(val.qsOptionCode));
arr.push(status);
}
});
return arr;
});
const questionCurrent = ref(0);
onMounted(() => {
getAnswerInfo();
});
const loading = ref(true); //加载骨架屏
// 答题信息
const recordInfo = ref({
questionNum: '',
startTime: '',
sumScore: '',
totalScore: '',
trueAnswer: '',
answerUseTime: '',
});
function getAnswerInfo() {
if (!route.query.id) return showToast('请求参数信息有误');
const params = {
recordId: route.query.id + '',
};
answerInfoByRecordId(params)
.then(({ code, data }) => {
loading.value = false;
if (code == 200) {
const { answerQsOptionVoList, questionNum, startTime, sumScore, totalScore, trueAnswer, answerUseTime } = data;
recordInfo.value = {
questionNum,
startTime,
sumScore,
totalScore,
trueAnswer,
answerUseTime,
};
if (!answerQsOptionVoList?.length) {
loading.value = false;
}
questionList.value = answerQsOptionVoList?.map((item: any) => ({
...item,
correctOption: item.optionVoList.find((val: any) => val.qsScore !== 0).qsOptionCode,
}));
}
})
.catch(() => {
loading.value = false;
});
}
// 下一题
function nextQuestion() {
let len = questionList.value.length;
let current = questionCurrent.value;
if (current < len - 1) {
questionCurrent.value += 1;
} else {
router.replace({ path: '/answer-recordsList', query: newPageParams({ module: route.query.module }) });
// openPage('/answer-recordsList', newPageParams());
}
}
const showPopup = ref(false);
function popupToggle() {
showPopup.value = !showPopup.value;
}
function back() {
popupToggle();
}
// 答题卡切题
function goToQuestion(index: number) {
questionCurrent.value = index;
}
const nextQuestionText = computed(() => useNextQuestionText(questionList.value.length, questionCurrent, false));
</script>
<style scoped lang="less">
p {
margin: 0;
}
.complete-answer-page {
height: 100vh;
display: flex;
flex-direction: column;
.bg-top {
height: 300px;
min-height: 300px;
background: url('/@/assets/images/health-interventions/complete-bg.png') no-repeat;
background-size: 100%;
.top-content {
padding: 20px;
.complete-title {
font-size: 20px;
font-weight: bold;
color: #ffffff;
}
.complete-sub-title {
font-size: 16px;
font-weight: 500;
color: #fff;
margin: 6px 0 10px 0;
}
}
.item-group {
height: 140px;
min-height: 140px;
background: rgba(16, 158, 158, 0.3);
border-radius: 10px;
display: flex;
flex-direction: column;
padding: 0 12px;
.item-row {
flex: 1;
display: flex;
.item-col {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.item-col:nth-child(2):before {
position: absolute;
content: '';
top: 30%;
left: 0;
width: 1px;
height: 30%;
background-color: rgba(222, 222, 222, 0.7);
}
.item-col:nth-child(2):after {
position: absolute;
content: '';
top: 30%;
right: 0;
width: 1px;
height: 30%;
background-color: rgba(222, 222, 222, 0.7);
}
.item-col > p {
font-size: 14px;
font-weight: 500;
color: #ffffff;
}
.item-col > p:first-child {
font-size: 16px;
font-weight: bold;
color: #ffffff;
margin-top: 10px;
}
}
.item-row:first-child {
border-bottom: 1px solid rgba(222, 222, 222, 0.4);
}
}
}
.answer-content {
flex: 1;
min-height: 450px;
margin: -58px 20px 0;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 281px;
background: #ffffff;
box-shadow: 0 8px 18px 2px rgba(51, 51, 51, 0.1);
border-radius: 10px 10px 0 0;
.answer-con-top {
display: flex;
flex-direction: column;
overflow: auto;
}
}
}
</style>
@@ -0,0 +1,63 @@
<template>
<div class="answer-footer">
<div class="answer-sheet" @click="popupToggle">
<van-icon class="vant-icon" name="records" />
<div class="sheet-text">答题卡</div>
</div>
<div class="next-question" @click="nextQuestion">
{{ nextQuestionText }}
</div>
</div>
</template>
<script setup lang="ts">
import { propTypes } from '/@/utils/propTypes';
const emit = defineEmits(['popupToggle', 'nextQuestion']);
const props = defineProps({
nextQuestionText: propTypes.string.def(''),
});
function popupToggle() {
emit('popupToggle');
}
function nextQuestion() {
emit('nextQuestion');
}
</script>
<style scoped lang="less">
@import '/@/assets/less/index';
.answer-footer {
width: 100%;
height: 60px;
display: flex;
align-items: center;
.answer-sheet {
width: 120px;
.flex-center();
flex-direction: column;
.vant-icon {
font-size: 20px;
height: 23px;
font-weight: bold;
}
.sheet-text {
font-size: 12px;
}
}
.next-question {
.flex-center();
flex: 1;
color: #fff;
height: 36px;
margin-right: 20px;
border-radius: 35px;
.qs-btn();
}
}
</style>
@@ -0,0 +1,154 @@
<template>
<div class="answer-records-card" :style="card.style.bgc">
<div class="tag-title" :style="card.style.fontColor">{{ questionTit(cardInfo?.testType) }}</div>
<div class="tag-score">{{ cardInfo?.sumScore }}</div>
<div class="tab-content">
<div class="tab-content-text">
<h3 class="tab-title">{{ cardInfo?.title }}</h3>
<p class="tab-p">{{ cardInfo?.desc }}</p>
<div class="tab-button-box">
<div class="tab-button" :style="card.style.fontColor" @click="viewRecord">查看</div>
<p class="answer-time"> 答题用时{{ cardInfo?.useTime <= 1 ? 1 : cardInfo?.useTime }}分钟</p>
</div>
</div>
<div class="tab-content-img" v-if="false">
<img :src="card.imgUrl" alt="" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { questionTit } from '/@/views/health-questions/useQuestion.ts';
const props = defineProps({
cardType: String,
cardInfo: Object,
});
const emit = defineEmits(['viewRecord']);
const cardList = {
card1: {
imgUrl: new URL('/@/assets/images/health-interventions/ques-icon1.png', import.meta.url).href,
style: {
fontColor: 'color: #1F91F2',
bgc: 'background: linear-gradient(147deg, #40A1F4 0%, #4D89DC 100%);',
},
},
card2: {
imgUrl: new URL('/@/assets/images/health-interventions/ques-icon2.png', import.meta.url).href,
style: {
fontColor: 'color: #52C41A',
bgc: 'background: linear-gradient(147deg, #75C34D 0%, #5AA238 100%);',
},
},
card3: {
imgUrl: new URL('/@/assets/images/health-interventions/ques-icon3.png', import.meta.url).href,
style: {
fontColor: 'color: #13C2C2',
bgc: 'background: linear-gradient(147deg, #3EBDBD 0%, #369898 100%);',
},
},
};
const card = computed(() => cardList[props.cardType]);
//查看记录
function viewRecord() {
emit('viewRecord', props.cardInfo.recordId + '');
}
</script>
<style scoped lang="less">
@import '../../../assets/less/index.less';
.answer-records-card {
width: 100%;
//height: 160px;
padding-bottom: 10px;
position: relative;
background: linear-gradient(147deg, #40a1f4 0%, #4d89dc 100%);
border-radius: 20px;
margin-bottom: 12px;
.tag-title {
.flex-center();
margin-left: 20px;
font-size: 14px;
font-weight: 500;
color: #1f91f2;
width: 80px;
height: 25px;
background: #ffffff;
border-radius: 0 0 6px 6px;
}
.tag-score {
position: absolute;
right: 16px;
top: 10px;
font-size: 17px;
font-weight: bold;
color: #ffffff;
}
.tab-content {
margin-left: 20px;
display: flex;
position: relative;
.tab-content-text {
width: 85%;
margin-top: 10px;
}
.tab-title {
font-size: 17px;
font-weight: bold;
color: #ffffff;
}
.tab-p {
font-size: 14px;
font-weight: 300;
margin: 0;
color: #ffffff;
}
.tab-button-box {
display: flex;
.tab-button {
.flex-center();
width: 100px;
height: 30px;
color: #5596f0;
background: #ffffff;
margin-top: 10px;
font-size: 16px;
font-weight: 500;
box-shadow: 0 4px 8px 0 rgba(46, 85, 138, 0.2);
border-radius: 25px;
}
.answer-time {
margin: 14px 0 0 6px;
font-size: 14px;
font-weight: 500;
color: #ffffff;
white-space: nowrap;
}
}
.tab-content-img {
position: absolute;
top: 28px;
right: 19px;
width: 80px;
height: 66px;
img {
width: 100%;
height: 100%;
}
}
}
}
</style>
@@ -0,0 +1,56 @@
<template>
<div v-if="isEdit" class="status-desc">
<span class="desc-text">状态说明:</span>
<span class="status-text already">已作答</span>
<span class="status-text">未作答</span>
</div>
<div v-else class="status-desc">
<span class="desc-text">状态说明:</span>
<span class="status-text already">正确</span>
<span class="status-text wrong">错误</span>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
isEdit: {
type: Boolean,
default: () => true,
},
});
</script>
<style scoped lang="less">
.status-desc {
height: 40px;
line-height: 40px;
font-size: 16px;
background-color: #eeeeee;
.desc-text {
margin: 0 20px;
}
.status-text:before {
display: inline-block;
content: '';
width: 10px;
height: 10px;
margin: 0 4px;
border-radius: 50%;
border: 1px solid #999999;
}
.already {
margin-right: 20px;
}
.already:before {
background: rgba(33, 190, 190, 0.2);
border: 1px solid #21bebe;
}
.wrong:before {
background: rgba(237, 42, 38, 0.2);
border: 1px solid #ed2a26;
}
}
</style>
@@ -0,0 +1,138 @@
<template>
<div class="tab-card" :style="cardInfo?.style.bgc">
<div class="tag-title" :style="cardInfo?.style.fontColor">{{ card?.module_dictText }}</div>
<div class="tag-time">{{ cardInfo?.timeText }}</div>
<div class="tab-content">
<div class="tab-content-text">
<h3 class="tab-title">{{ card?.title }}</h3>
<p class="tab-p">{{ card?.desc }}</p>
</div>
<div class="tab-content-img" v-if="false">
<img :src="cardInfo?.imgUrl" alt="" />
</div>
</div>
<div class="btn-list">
<div class="tab-button record-btn" @click="recordClick">
{{ cardInfo?.recordText }}
</div>
<div class="tab-button" :style="cardInfo?.style.fontColor" @click="btnClick">
{{ cardInfo?.tabBtnText }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { cardList } from '/@/views/health-questions/data';
const props = defineProps({
cardType: String,
card: Object,
});
const emits = defineEmits(['btnClick', 'recordClick']);
const cardInfo = computed(() => cardList[props.cardType]);
function btnClick() {
emits('btnClick');
}
function recordClick() {
emits('recordClick');
}
</script>
<style scoped lang="less">
@import '/@/assets/less/index.less';
.tab-card {
width: 100%;
padding-bottom: 10px;
position: relative;
background: linear-gradient(147deg, #40a1f4 0%, #4d89dc 100%);
border-radius: 20px;
margin-bottom: 12px;
.tag-title {
.flex-center();
margin-left: 20px;
font-size: 14px;
font-weight: 500;
color: #1f91f2;
width: 80px;
height: 25px;
background: #ffffff;
border-radius: 0 0 6px 6px;
}
.tag-time {
position: absolute;
right: 5px;
top: 10px;
font-size: 14px;
font-weight: 500;
color: #ffffff;
}
.tab-content {
margin-left: 20px;
display: flex;
position: relative;
.tab-content-text {
width: 85%;
margin-top: 10px;
}
.tab-title {
font-size: 17px;
font-weight: bold;
color: #ffffff;
}
.tab-p {
font-size: 14px;
font-weight: 300;
margin: 0;
color: #ffffff;
}
.tab-content-img {
position: absolute;
top: 28px;
right: 19px;
width: 80px;
height: 66px;
img {
width: 100%;
height: 100%;
}
}
}
.btn-list {
display: flex;
justify-content: space-around;
}
.tab-button {
.flex-center();
width: 100px;
height: 30px;
color: #5596f0;
background: #ffffff;
margin-top: 10px;
font-size: 16px;
font-weight: 500;
box-shadow: 0 4px 8px 0 rgba(46, 85, 138, 0.2);
border-radius: 25px;
}
.record-btn {
background: transparent;
border: 1px solid #ffffff;
color: #fff;
margin-left: 10px;
box-shadow: 0 4px 8px 0 rgba(46, 85, 138, 0.2);
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More