This commit is contained in:
田岩
2025-08-08 14:22:50 +08:00
parent 401c2cfa1a
commit bb91e8a88b
41 changed files with 2003 additions and 95 deletions
+2 -2
View File
@@ -16,5 +16,5 @@ VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# h5专用地址
# 王洋洋
VITE_APP_BASE_H5_API_Url = 'http://25.64.16.140:9604'
#VITE_APP_BASE_H5_API_Url = 'https://aitstest.jlbank.com.cn:7001'
#VITE_APP_BASE_H5_API_Url = 'http://25.64.16.140:9604'
VITE_APP_BASE_H5_API_Url = 'https://aitstest.jlbank.com.cn:7001'
Binary file not shown.
+3
View File
@@ -141,4 +141,7 @@ view {
.no-overflow{
overflow: hidden;
}
.bold{
font-weight: bold;
}
</style>
+16
View File
@@ -0,0 +1,16 @@
import request from '@/api/request'
import { getToken } from '@/common/common.js'
import { get_base_url } from '@/api/request'
const base_url = '/traexam'
// 开始考试(试卷信息)
export const startTraExamPapersInfo = (data) => {
return request({
url: base_url + '/traExamPapers/startTraExamPapersInfo',
method: 'post',
toastErrors: true,
data
});
};
+20
View File
@@ -0,0 +1,20 @@
export const optionSuccessIcon = (color : string) : string => {
const svgXml = `
<svg t="1754618739937" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6322" width="64" height="64"><path d="M843.693959 293.609061 425.255869 712.056362 186.145026 472.947566 66.579883 592.504522 425.255869 951.165158 963.260126 413.174204Z" fill="${color}" p-id="6323"></path></svg>
`.trim();
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
};
export const optionErrorIcon = () : string => {
const svgXml = `
<svg t="1754619171929" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6505" width="64" height="64"><path d="M910.336 186.368l-72.704-72.704L512 439.808 186.368 113.664 113.664 186.368 439.808 512l-326.144 325.632 72.704 72.704L512 584.192l325.632 326.144 72.704-72.704L584.192 512l326.144-325.632z" fill="#F5212D" p-id="6506"></path></svg>
`.trim();
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
};
export const examinationSheetIcon = () : string => {
const svgXml = `
<svg t="1754630871725" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6324" width="64" height="64"><path d="M802.107077 51.2a145.289846 145.289846 0 0 1 145.092923 145.526154v630.547692a145.289846 145.289846 0 0 1-145.092923 145.526154H221.892923a145.329231 145.329231 0 0 1-145.092923-145.526154V196.726154A145.289846 145.289846 0 0 1 221.892923 51.2h580.214154z m-3.387077 76.8H225.28a71.837538 71.837538 0 0 0-71.561846 67.741538l-0.118154 4.253539v624.009846a71.837538 71.837538 0 0 0 67.465846 71.876923l4.214154 0.118154h573.44c38.203077 0 69.356308-29.932308 71.561846-67.741538l0.118154-4.253539V200.034462a71.876923 71.876923 0 0 0-67.465846-71.876924l-4.214154-0.157538zM729.6 512a38.4 38.4 0 1 1 0 76.8h-230.4a38.4 38.4 0 1 1 0-76.8h230.4z m-384 0a38.4 38.4 0 1 1 0 76.8h-51.2a38.4 38.4 0 1 1 0-76.8h51.2z m384-204.8a38.4 38.4 0 1 1 0 76.8h-230.4a38.4 38.4 0 1 1 0-76.8h230.4z m-384 0a38.4 38.4 0 1 1 0 76.8h-51.2a38.4 38.4 0 0 1 0-76.8h51.2z" p-id="6325"></path></svg>
`.trim();
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
};
@@ -0,0 +1,135 @@
<template>
<view class="item">
<view class="top">
<view class="type">
{{subject_type_text}}
</view>
<view class="score">
5
</view>
<view class="fl1">
</view>
<view class="right">
<!-- <view class="right_text">展开</view>
<uv-icon class="arrow-icon" name="arrow-right"></uv-icon> -->
</view>
</view>
<view class="question_text">
{{subject_data.qnsContent}}
</view>
<view class="option_div">
</view>
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted,
nextTick,
computed,
toRaw
} from 'vue'
import radioSubjectItem from './radio-subject-item.vue'
import {
SUBJECT_TYPE
} from '@/enum.js'
const customStyle = {
borderRadius: '16rpx',
width: '292rpx',
backgroundColor: '#0066FF',
border: '1rpx solid rgb(12, 123, 245)',
}
const emit = defineEmits(['click_option'])
const props = defineProps({
item: {
type: Object,
required: true,
},
mode: {
type: String,
required: true,
}
});
const subject_data = reactive({
qnsId: '',
qnsContent: '',
qnsTyp: '',
})
Object.assign(subject_data, {
qnsId: props.item.qnsId,
qnsContent: props.item.qnsContent,
qnsTyp: props.item.qnsTyp,
})
// 题类型
const subject_type_text = computed(() => SUBJECT_TYPE.find(res => subject_data.qnsTyp === res.value)?.label || '')
</script>
<style scoped lang="scss">
.item {
// height: 170rpx;
overflow: hidden;
display: flex;
flex-direction: column;
padding-bottom: 40rpx;
.top {
display: flex;
align-items: center;
.type {
width: 108rpx;
height: 46rpx;
background: #267EFF;
border-radius: 8rpx;
font-family: PingFangSC;
font-weight: 500;
font-size: 25rpx;
color: #FFFFFF;
text-align: center;
line-height: 46rpx;
}
.score {
margin-left: 24rpx;
font-family: ArialMT;
font-size: 30rpx;
color: #333333;
text-align: left;
}
.right {
display: flex;
align-items: center;
.right_text {
font-family: PingFangSC;
font-weight: 600;
font-size: 28rpx;
color: #333;
line-height: 40rpx;
text-align: left;
font-style: normal;
}
}
}
.question_text {
font-family: PingFangSC;
font-weight: 500;
font-size: 30rpx;
color: #333333;
line-height: 48rpx;
text-align: left;
font-style: normal;
margin-top: 20rpx;
}
}
</style>
@@ -0,0 +1,134 @@
<template>
<view class="option_item" @click.stop="click_option()" :class="select_status">
<view class="option_text">
{{props.analysisVo.itemContent}}
</view>
<!-- <image v-show="select_status==='primary'" class="img" src="@/static/images/common/primary.png" mode="heightFix">
</image>
<image v-show="select_status==='success'" class="img" src="@/static/images/common/success.png" mode="heightFix">
</image>
<image v-show="select_status==='error'" class="img" src="@/static/images/common/error.png" mode="heightFix">
</image> -->
<image :src="select_status_icon" class="img"></image>
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted,
nextTick,
computed,
toRaw
} from 'vue'
import {
optionErrorIcon,
optionSuccessIcon
} from '@/common/imgSvg'
const emit = defineEmits(['click_option'])
const props = defineProps({
analysisVo: {
type: Object,
required: true,
},
mode: {
type: String,
required: false,
},
my_answer: {
type: Array,
required: true,
},
backend_answer: {
type: Array,
required: true,
},
});
const select_status = computed(() => {
if (props.backend_answer.length === 0) {
return props.my_answer.includes(props.analysisVo.itemId) ? 'primary' : 'info';
} else {
return props.my_answer.includes(props.analysisVo.itemId) ? 'success' :
'error';
}
})
const select_status_icon = computed(() => {
if (select_status.value === 'primary') {
return optionSuccessIcon('#0066FF')
} else if (select_status.value === 'success') {
return optionSuccessIcon('#15B859')
} else if (select_status.value === 'error') {
return optionErrorIcon()
}
})
const click_option = () => {
console.log('props.analysisVo.itemId', props.analysisVo.itemId);
emit('click_option', props.analysisVo)
}
</script>
<style scoped lang="scss">
.success {
color: #15B859;
background: #E7F8ED;
}
.primary {
color: #333333;
background: #ECF3FE;
}
.info {
color: #333333;
background: #F9F9F9;
}
.error {
color: #F5212D;
background: #FCE8E9;
}
.option_item {
width: 100%;
height: 108rpx;
border-radius: 15rpx;
margin-top: 32rpx;
font-family: PingFangSC;
font-weight: 500;
font-size: 30rpx;
text-align: left;
font-style: normal;
padding-left: 52rpx;
display: flex;
align-items: center;
// display: -webkit-box;
// -webkit-box-orient: vertical;
// -webkit-line-clamp: 2;
/* 显示的行数 */
overflow: hidden;
.option_text {
flex: 1;
white-space: nowrap;
/* 强制不换行 */
overflow: hidden;
/* 超出部分隐藏 */
text-overflow: ellipsis;
}
.img {
width: 30rpx;
// height: 30rpx;
padding-right: 30rpx;
}
}
</style>
@@ -0,0 +1,174 @@
<template>
<view class="item">
<view class="top">
<view class="type">
{{subject_type_text}}
</view>
<view class="score">
5
</view>
<view class="fl1">
</view>
<view class="right">
<!-- <view class="right_text">展开</view>
<uv-icon class="arrow-icon" name="arrow-right"></uv-icon> -->
</view>
</view>
<view class="question_text">
{{subject_data.qnsContent}}
</view>
<view class="option_div">
<radioSubjectItem v-for="(analysisVo,index) in subject_data.itemVos" @click_option="click_option"
:key="index" :analysisVo="analysisVo" :my_answer="props.item.my_answer"
:backend_answer="props.item.backend_answer"></radioSubjectItem>
</view>
<view class="option_div_button" v-if="subject_data.qnsTyp==='MU' && props.mode=='practice'" >
<uv-button type="primary" class="send_evaluate" :custom-style="customStyle" :throttleTime="300"
:hairline="false" >确定</uv-button>
</view>
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted,
nextTick,
computed,
toRaw
} from 'vue'
import radioSubjectItem from './radio-subject-item.vue'
import {
SUBJECT_TYPE
} from '@/enum.js'
const customStyle = {
borderRadius: '16rpx',
width: '292rpx',
backgroundColor: '#0066FF',
border: '1rpx solid rgb(12, 123, 245)',
}
const emit = defineEmits(['click_option'])
const props = defineProps({
item: {
type: Object,
required: true,
},
mode: {
type: String,
required: true,
}
});
const subject_data = reactive({
qnsId: '',
qnsContent: '',
qnsTyp: '',
itemVos: props.item.itemVos.map(res => ({
...res,
// 选择状态, 'info表示未选' primary 表示蓝色, sunccess 表示绿色, error 表示红色
select_status: 'info'
})),
})
Object.assign(subject_data, {
qnsId: props.item.qnsId,
qnsContent: props.item.qnsContent,
qnsTyp: props.item.qnsTyp,
})
// 题类型
const subject_type_text = computed(() => SUBJECT_TYPE.find(res => subject_data.qnsTyp === res.value)?.label || '')
// 点击答题
const click_option = (analysisVo) => {
// emit('click_option', props.item.qnsId, analysisVo)
// // 无论什么类型如果表里已经有了,再次点击就是弹出
if (props.item.my_answer.includes(analysisVo.itemId)) {
// console.log('无论什么类型如果表里已经有了,再次点击就是弹出', analysisVo.itemId);
props.item.my_answer = props.item.my_answer.filter(id => id !== analysisVo.itemId);
}
else if (props.item.qnsTyp === 'SN') { // 单选
// 单选插入,并且移除其他
props.item.my_answer[0] = analysisVo.itemId
}
else if (props.item.qnsTyp === 'MU') { // 多选题
// 多选直接插入
props.item.my_answer.push(analysisVo.itemId)
} else if (props.item.qnsTyp === 'JD') { // 判断题
// 判断插入,并且移除其他
props.item.my_answer = [analysisVo.itemId]
}
}
</script>
<style scoped lang="scss">
.option_div_button{
width: 100%;
height: 84rpx;
padding-top: 30rpx;
// background-color: red;
display: flex;
flex-direction: column;
align-items: center;
}
.item {
// height: 170rpx;
overflow: hidden;
display: flex;
flex-direction: column;
padding-bottom: 40rpx;
.top {
display: flex;
align-items: center;
.type {
width: 108rpx;
height: 46rpx;
background: #267EFF;
border-radius: 8rpx;
font-family: PingFangSC;
font-weight: 500;
font-size: 25rpx;
color: #FFFFFF;
text-align: center;
line-height: 46rpx;
}
.score {
margin-left: 24rpx;
font-family: ArialMT;
font-size: 30rpx;
color: #333333;
text-align: left;
}
.right {
display: flex;
align-items: center;
.right_text {
font-family: PingFangSC;
font-weight: 600;
font-size: 28rpx;
color: #333;
line-height: 40rpx;
text-align: left;
font-style: normal;
}
}
}
.question_text {
font-family: PingFangSC;
font-weight: 500;
font-size: 30rpx;
color: #333333;
line-height: 48rpx;
text-align: left;
font-style: normal;
margin-top: 20rpx;
}
}
</style>
+21 -2
View File
@@ -3,7 +3,8 @@ export const Max_Upload_Document_Size = 102400 // 最大文档上传限制
export const TextareaInputLimitCharacter = 160 // 全局多行文本输入框限制字数
// 公共tab页的图片
export const LineBg =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAABL0lEQVQ4T6XSv0uCURTG8e9jgpRNDS1BU1sELUFLQVM1tgQFtcR7A8m1LQqaLGhOkQKH/o5aKiiHlgiE0K1oCYLAMk9ov/RF8e313u2ew4dzzzmi2+NsA9gH3hCr6srzbAJxDkS/nbvwYNJilMkDow1FXYUHneWAlV/MqN3ZcKCzHWDb164MGa3/H/Qshdj0YbdUmORIL8HBNRsgShZjoQkznvioY/e19wCgCccSsAcM+bBnjHmyuvx5bw8mrJ93FoEkYrzFej0g5kjrpjH2BTrro8oIYhgYQ0xhTCPibfb0ggjLHKrojwvPEogDIBZgycsYKQrscqpKq3zh7BXo7YBVgRMibLWqqvnLzh6BwTZgESNHD8edoL+hOJupT9CII0oYJcQ14oy0CgHa0JTyCeBLVNeWygE5AAAAAElFTkSuQmCC";
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAABL0lEQVQ4T6XSv0uCURTG8e9jgpRNDS1BU1sELUFLQVM1tgQFtcR7A8m1LQqaLGhOkQKH/o5aKiiHlgiE0K1oCYLAMk9ov/RF8e313u2ew4dzzzmi2+NsA9gH3hCr6srzbAJxDkS/nbvwYNJilMkDow1FXYUHneWAlV/MqN3ZcKCzHWDb164MGa3/H/Qshdj0YbdUmORIL8HBNRsgShZjoQkznvioY/e19wCgCccSsAcM+bBnjHmyuvx5bw8mrJ93FoEkYrzFej0g5kjrpjH2BTrro8oIYhgYQ0xhTCPibfb0ggjLHKrojwvPEogDIBZgycsYKQrscqpKq3zh7BXo7YBVgRMibLWqqvnLzh6BwTZgESNHD8edoL+hOJupT9CII0oYJcQ14oy0CgHa0JTyCeBLVNeWygE5AAAAAElFTkSuQmCC";
// 性别
export const GENDER = [{
value: "m",
label: "男性",
@@ -12,4 +13,22 @@ export const GENDER = [{
value: "f",
label: "女性",
}
] // 性别
]
// 试题题型
export const SUBJECT_TYPE = [{
value: "SN",
label: "单选题",
},
{
value: "MU",
label: "多选题",
},
{
value: "JD",
label: "判断题",
},
{
value: "ES",
label: "问答题",
},
]
+18
View File
@@ -190,6 +190,24 @@
}
}
},
{
"path": "pages/examination/index",
"style": {
"navigationBarTitleText": "考试",
"app-plus": {
"titleNView": false
}
}
},
{
"path": "pages/practice/index",
"style": {
"navigationBarTitleText": "练习",
"app-plus": {
"titleNView": false
}
}
},
{
"path": "pages/course/index",
"style": {
+11 -3
View File
@@ -73,14 +73,13 @@
<uv-button type="primary"
@click="common.navigateTo('/pages/course/study?crsNum='+crsNum+'&crsId='+form.crsId + '&imgId=' + form.imgId)">去学习</uv-button>
<uv-button type="primary">去练习</uv-button>
<uv-button type="primary">去考试</uv-button>
<uv-button type="primary" @click="goto_examination()">去考试</uv-button>
</template>
<template v-if="tabAct == 2">
<uv-button type="primary" class="send_evaluate"
@click="common.navigateTo('/pages/courseDetail/submit?crsNum='+crsNum+'&imgId='+form.imgId)">发布评价</uv-button>
</template>
</view>
</view>
</template>
@@ -161,7 +160,15 @@
const swiper_change = (res) => {
tabAct.value = res.detail.current
}
// 去考试
const goto_examination = () => {
// papersId.value = e.papersId
// paperData.value = setPageCache('examination_'+papersId.value)
// if (!papersId.value || !paperData.value) {
// common.navigateBack()
// return
// }
}
// const getElementPosition = () => {
// return new Promise((resolve) => {
// uni.createSelectorQuery()
@@ -181,6 +188,7 @@
tabAct.value = 2
})
})
onLoad((params) => {
crsNum.value = params.crsNum;
});
+201
View File
@@ -0,0 +1,201 @@
<template>
<view class="main_div max_page">
<nav-bar :is_seat="true"></nav-bar>
<view class="nav-div">
<view class="title ellipsis-text">
这里是练习名称
</view>
<view class="back_div"></view>
</view>
<view class="countdown-and-question-number-div">
<view class="top">
<view class="countdown">
<text class="grey">答题进度</text> <text class="bold">2</text><text class="grey">/10</text>
</view>
<view class="question">
<view class="grey">剩余时间</view>
<uv-count-down :time="30 * 60 * 60 * 1000" format="HH:mm:ss"></uv-count-down>
</view>
</view>
<view class="progress">
<uv-line-progress :percentage="30" :showText="false" activeColor="#FFFFFF" inactiveColor="#89B8FF"
:height="8"></uv-line-progress>
</view>
</view>
<view class="scroll_div">
<swiper class="swiper" :current="tabAct" :indicator-dots="false" :disable-touch="true">
<swiper-item>
<view class="content">
<view class="expose_shadow"></view>
<scroll-view :scroll-y="true" class="scroll-Y" :show-scrollbar="false" :refresher-threshold="0">
<RadioSubject></RadioSubject>
</scroll-view>
</view>
</swiper-item>
</swiper>
</view>
</view>
</template>
<script setup>
import RadioSubject from '@/components/examination/radio-subject.vue'
import {
ref,
computed,
onMounted,
nextTick,
getCurrentInstance
} from 'vue';
const {
proxy
} = getCurrentInstance() // 获取当前组件实例
// 用于存储尺寸信息的响应式变量
const dimensions = ref({
containerHeight: 0,
contentHeight: 0
});
// 检查尺寸并更新
const checkDimensions = async () => {
await nextTick(); // 确保DOM已更新
// 创建查询
const query = uni.createSelectorQuery().in(proxy);
// 添加内容高度查询
query.select('.content').boundingClientRect(data => {
dimensions.value.contentHeight = data?.height || 0;
});
// 添加容器高度查询(注意这里没有调用.exec())
query.select('.scroll_div').boundingClientRect(data => {
dimensions.value.containerHeight = data?.height || 0;
});
// 执行所有查询(只调用一次exec())
query.exec();
};
// 计算属性:判断是否需要开启滚动
const isScrollable = computed(() => {
return dimensions.value.contentHeight > dimensions.value.containerHeight - 20;
});
// 初始化时检查
onMounted(() => {
checkDimensions();
// 监听窗口尺寸变化(可选)
uni.onWindowResize(() => {
checkDimensions();
});
});
</script>
<style scoped lang="scss">
$bg-margin-left: 30rpx;
.scroll-Y {
max-height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx - 68rpx - 60rpx);
min-height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx - 68rpx - 60rpx - 380rpx);
}
.content {
position: relative;
margin: 32rpx $bg-margin-left;
border-radius: 16rpx;
padding: 34rpx 30rpx 34rpx 36rpx;
background-color: #fff;
.expose_shadow {
margin-left: 36rpx;
position: absolute;
bottom: -26rpx;
left: 0%;
width: calc(100% - 66rpx);
height: 28rpx;
background-color: #FFFFFF;
border-radius: 0 0 14rpx 14rpx;
opacity: 0.4;
z-index: 0;
}
}
.countdown-and-question-number-div {
margin: 20rpx $bg-margin-left;
height: 70rpx;
// background-color: red;
display: flex;
flex-direction: column;
justify-content: space-between;
z-index: 10;
.top {
display: flex;
align-items: center;
justify-content: space-between;
font-family: PingFangSC;
font-size: 26rpx;
color: #FFFFFF;
text-align: left;
font-style: normal;
}
.question {
display: flex;
align-items: center;
// background-color: red;
width: 242rpx;
color: red;
.grey {
margin-right: 8rpx;
}
:deep(.uv-count-down__text) {
color: #FFFFFF !important;
font-weight: 600;
font-size: 26rpx !important;
}
}
.grey {
color: #B6E2FF;
}
.bold {
font-weight: 600;
}
}
.nav-div {
position: relative;
.title {
// padding-top:var(--status-bar-height);
// padding: 0 30rpx;
margin: 0 calc($bg-margin-left + 70rpx);
height: 70rpx;
font-family: PingFangSC;
font-weight: 500;
font-size: 34rpx;
color: #FFFFFF;
text-align: center;
font-style: normal;
line-height: 70rpx;
}
.back_div {
position: absolute;
right: $bg-margin-left;
top: 0;
width: 70rpx;
height: 70rpx;
// background-color: red;
}
}
.main_div {
display: flex;
flex-direction: column;
background: linear-gradient(to top, #3480FF, #0066FF);
/* 确保背景占满整个视口高度 */
min-height: 100vh;
margin: 0;
}
</style>
+304
View File
@@ -0,0 +1,304 @@
<template>
<view class="main_div max_page">
<nav-bar :is_seat="true"></nav-bar>
<view class="nav-div">
<view class="title ellipsis-text" @click="hand_in_paper()">
{{paperData.papersName}}
</view>
<view class="back_div"></view>
</view>
<view class="countdown-and-question-number-div">
<view class="top">
<view class="countdown">
<text class="grey">答题进度</text> <text class="bold">{{questionNumber + 1}}</text><text
class="grey">/{{topicList.length}}</text>
</view>
<view class="question">
<view class="grey">剩余时间</view>
<uv-count-down :time="paperData.limitTm * 1000" format="HH:mm:ss"></uv-count-down>
</view>
</view>
<view class="progress">
<uv-line-progress :percentage="progress" :showText="false" activeColor="#FFFFFF" inactiveColor="#89B8FF"
:height="8"></uv-line-progress>
</view>
</view>
<view class="scroll_div">
<swiper class="swiper" :indicator-dots="false" @change="swiperChange" :current="questionNumber" easing-function="linear">
<swiper-item v-for="(item,index) in topicList">
<view class="content">
<view class="expose_shadow"></view>
<scroll-view :scroll-y="true" class="scroll-Y" :show-scrollbar="false" :refresher-threshold="0">
<RadioSubject :item="item" v-if="item.qnsTyp !== 'ES'" :mode="mode">
</RadioSubject>
<CharactersSubject :item="item" v-if="item.qnsTyp === 'ES'" :mode="mode">
</CharactersSubject>
</scroll-view>
</view>
</swiper-item>
</swiper>
</view>
<view class="fixed-btn-box font_pf">
<view class="sheet">
<image :src="examinationSheetIcon()" class="img"></image>
<view class="sheet_text">答题卡</view>
</view>
<view class="button_div">
<uv-button class="button" type="primary" :throttleTime="100" text="上一题" color="#E2EDFF"
custom-style="color:#0066FF;borderRadius:16rpx;height:92rpx;" @click="button_tab(-1)"></uv-button>
<view style="width:46rpx"></view>
<uv-button class="button" type="primary" :throttleTime="100" text="下一题" color="#0066FF"
custom-style="color:#FFFFFF;borderRadius:16rpx;height:92rpx;" @click="button_tab(1)"></uv-button>
</view>
</view>
</view>
</template>
<script setup>
import RadioSubject from '@/components/examination/radio-subject.vue'
import CharactersSubject from '@/components/examination/characters-subject.vue'
import {
ref,
reactive,
computed,
onMounted,
nextTick,
getCurrentInstance
} from 'vue';
import {
examinationSheetIcon
} from '@/common/imgSvg'
import {
onLoad
} from '@dcloudio/uni-app'
import common from '@/common/common';
import {
getPageCache
} from '@/common/common';
const examId = ref('')
const mode = ref('examination') // 考试 examination 练习practice
const paperData = reactive({
papersId: '',
limitTm: 0,
papersName: '',
}) // 试卷信息
const progress = computed(() => { // 计算进度百分比(保留两位小数)
const total = topicList.length;
if (total === 0) return 0;
const completed = questionNumber.value + 1;
return Math.min(Math.round((completed / total) * 10000) / 100, 100);
})
const topicList = reactive([]) // 问题列表
const questionNumber = ref(0) // 题号
const swiperChange = (item) => {
questionNumber.value = item.detail.current
}
// 按上一题下一题
const button_tab = (num) => {
let newNumber = questionNumber.value + num;
if (newNumber < 0) {
newNumber = 0;
} else if (newNumber >= topicList.length) {
newNumber = topicList.length - 1;
}
questionNumber.value = newNumber;
}
// 初始化时检查
onLoad(() => {
const examination = getPageCache('examination')
if (!examination) {
common.navigateBack()
return
}
paperData.papersId = examination.papersId
paperData.limitTm = examination.limitTm
paperData.papersName = examination.papersName
topicList.push(...examination.qnsInfoVos.map(res => ({
...res,
my_answer: [],
backend_answer: [],
})))
});
const hand_in_paper = () => {
console.log('点击交卷', topicList);
}
</script>
<style scoped lang="scss">
$bg-margin-left: 30rpx;
.fixed-btn-box {
position: fixed;
width: 100%;
left: 0;
bottom: 0;
height: 154rpx;
background: #FFFFFF;
border-radius: 16rpx 16rpx 0px 0px;
border: 2rpx solid #E5E5E5;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx 20rpx 20rpx;
.sheet {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 70rpx;
.img {
width: 38rpx;
height: 40rpx;
}
.sheet_text {
margin-top: 8rpx;
font-family: PingFangSC;
font-weight: 500;
font-size: 24rpx;
color: #000000;
}
}
.button_div {
flex: 1;
display: flex;
justify-content: space-between;
.button {
flex: 1;
}
}
}
.scroll-Y {
max-height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx - 68rpx - 60rpx);
min-height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx - 68rpx - 60rpx - 380rpx);
// background-color: yellow;
}
.swiper {
// 状态栏 var(--status-bar-height)
// 标题栏 70rpx
// 进度时间栏 70rpx
// 进度时间栏上下间距 40rpx = 20rpx + 20rpx
// 正文上下间距 64rpx
height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx);
// background-color: red;
}
.content {
position: relative;
margin: 32rpx $bg-margin-left;
border-radius: 16rpx;
padding: 34rpx 30rpx 34rpx 36rpx;
background-color: #fff;
.expose_shadow {
margin-left: 36rpx;
position: absolute;
bottom: -26rpx;
left: 0%;
width: calc(100% - 66rpx);
height: 28rpx;
background-color: #FFFFFF;
border-radius: 0 0 14rpx 14rpx;
opacity: 0.4;
z-index: 0;
// background-color: red;
}
}
.countdown-and-question-number-div {
margin: 20rpx $bg-margin-left;
height: 70rpx;
// background-color: red;
display: flex;
flex-direction: column;
justify-content: space-between;
z-index: 10;
.top {
display: flex;
align-items: center;
justify-content: space-between;
font-family: PingFangSC;
font-size: 26rpx;
color: #FFFFFF;
text-align: left;
font-style: normal;
}
.question {
display: flex;
align-items: center;
// background-color: red;
width: 242rpx;
color: red;
.grey {
margin-right: 8rpx;
}
:deep(.uv-count-down__text) {
color: #FFFFFF !important;
font-weight: 600;
font-size: 26rpx !important;
}
}
.grey {
color: #B6E2FF;
}
.bold {
font-weight: 600;
}
}
.nav-div {
position: relative;
.title {
// padding-top:var(--status-bar-height);
// padding: 0 30rpx;
margin: 0 calc($bg-margin-left + 70rpx);
height: 70rpx;
font-family: PingFangSC;
font-weight: 500;
font-size: 34rpx;
color: #FFFFFF;
text-align: center;
font-style: normal;
line-height: 70rpx;
}
.back_div {
position: absolute;
right: $bg-margin-left;
top: 0;
width: 70rpx;
height: 70rpx;
// background-color: red;
}
}
.main_div {
display: flex;
flex-direction: column;
background: linear-gradient(to top, #3480FF, #0066FF);
/* 确保背景占满整个视口高度 */
min-height: 100vh;
margin: 0;
}
</style>
+2 -1
View File
@@ -88,7 +88,8 @@
<image class="icon" src="@/static/images/index/navigation_question_answering.png"></image>
<view class="title">AI问答</view>
</view>
<view class="item">
<view class="item" @click="common.navigateTo('/pages/examination/index')">
<image class="icon" src="@/static/images/index/navigation_ai.png"></image>
<view class="title">AI陪练</view>
</view>
+1 -1
View File
@@ -6,7 +6,7 @@
</view>
<view>消息</view>
<view class="delete_div" @click="common.navigateBack()">
<image class="img" src="@/static/images/message_notification/delete_icon.png" mode="aspectFit">
<image class="img" src="@/static/images/messageNotification/delete_icon.png" mode="aspectFit">
</image>
</view>
</view>
+8
View File
@@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>
@@ -76,6 +76,7 @@
}
const getData = (from = '') => {
console.log('getDatagetDatagetDatagetDatagetDatagetDatagetData');
if (from == 'first') {
if (total.value !== -1) return;
status.value = 'loading'
@@ -100,8 +101,11 @@
params.examStat = 'N'
}
queryTraExamPapersPage(params).then(res => {
console.log('resresresresres', res);
total.value = res.total
data_list.push(...res.body)
}).catch(res => {
console.log('谢谢谢谢谢谢', res);
}).finally(() => {
if (data_list.length >= total.value) {
status.value = 'nomore'
+265 -64
View File
@@ -7,10 +7,10 @@
<view>考试详情</view>
</view>
<view class="detail_div completed incomplete">
<view class="subscript">已完成</view>
<view class="title_div">
<view class="title ellipsis-text">{{detailData.papersName}}萨达萨达萨达萨达萨达阿斯顿萨达萨达萨达</view>
<view class="title ellipsis-text">{{detailData.papersName}}</view>
<!-- <view class="subscript" >未完成</view> -->
<view class="subscript" >已完成</view>
</view>
<view class="time">
<text class="color_3">起止时间</text><text
@@ -24,30 +24,58 @@
<view class="paper_div">
<view class="paper">
<!-- 试卷信息 -->
<view class="title">
试卷信息
<view class="paper_title_div">
<image src="@/static/images/testingHall/testing.png" mode="heightFix" class="img"></image>
<view class="title">
试卷信息
</view>
</view>
<view class="title-note">
试卷总分{{detailData.totalGrade}}
考试时长<text class="bold">{{detailData.totalGrade}}</text>
</view>
<view class="title-note">
通过分数{{detailData.passGrade}}
</view>
<view class="title-note">
考试次数不限制
</view>
<view class="title-note">
试卷类型随机试卷
</view>
<!-- 试卷结构 -->
<view class="title">
试卷结构
</view>
<view class="table">
<view class="table_tr">
<view class="table_td">
类型
<view class="title-table">
<view class="title-table-item">
<view class="top">
<text class="text_2">{{detailData.passGrade}}</text><text class="text_1"></text>
</view>
<view class="bottom text_1">
试卷总分
</view>
</view>
<view class="title-table-item">
<view class="top">
<text class="text_2">{{detailData.passGrade}}</text><text class="text_1"></text>
</view>
<view class="bottom text_1">
通过分数
</view>
</view>
<view class="title-table-item">
<view class="top text_2">
不限制
</view>
<view class="bottom text_1">
通过次数
</view>
</view>
<view class="title-table-item">
<view class="top text_2">
随机
</view>
<view class="bottom text_1">
试卷类型
</view>
</view>
</view>
<view class="table">
<view class="table_th">
<view class="table_td">
题型
</view>
<view class="fl1"></view>
<view class="table_td">
数量
</view>
@@ -59,6 +87,7 @@
<view class="table_td">
判断题
</view>
<view class="fl1"></view>
<view class="table_td">
{{detailData.judgeNumber}}
</view>
@@ -66,10 +95,11 @@
{{detailData.judgeGrade}}
</view>
</view>
<view class="table_tr">
<view class="table_tr table_tr_dark">
<view class="table_td">
单选题
</view>
<view class="fl1"></view>
<view class="table_td">
{{detailData.singleNumber}}
</view>
@@ -81,6 +111,7 @@
<view class="table_td">
多选题
</view>
<view class="fl1"></view>
<view class="table_td">
{{detailData.multiNumber}}
</view>
@@ -88,10 +119,11 @@
{{detailData.multiGrade}}
</view>
</view>
<view class="table_tr">
<view class="table_tr table_tr_dark">
<view class="table_td">
问答题
</view>
<view class="fl1"></view>
<view class="table_td">
{{detailData.essayNumber}}
</view>
@@ -103,6 +135,7 @@
<view class="table_td">
总计
</view>
<view class="fl1"></view>
<view class="table_td">
20
</view>
@@ -116,8 +149,7 @@
</view>
<view class="fixed_button_div">
<uv-button type="primary" class="send_evaluate" :custom-style="customStyle" :throttleTime="300"
:hairline="false"
@click="common.navigateTo('/pages/courseDetail/submit?crsNum='+crsNum+'&imgId='+form.imgId)">发布评价</uv-button>
:hairline="false" @click="goto_examination()">开始考试</uv-button>
</view>
</view>
@@ -131,6 +163,12 @@
import {
reactive
} from 'vue'
import {
startTraExamPapersInfo
} from '@/api/examination.js'
import common from '@/common/common';
import {setPageCache, getPageCache} from '@/common/common';
const detailData = reactive({
papersId: '',
papersName: '',
@@ -140,14 +178,30 @@
planPersionNumber: '',
})
const customStyle = {
borderRadius: '40rpx', //圆角
backgroundColor: 'rgb(12, 123, 245)',
backgroundColor: '#0066FF',
border: '1px solid rgb(12, 123, 245)',
}
const goto_examination = async () => {
common.loading()
startTraExamPapersInfo({
examId : "1"
}).then(res => {
setPageCache('examination', res.body)
common.navigateTo('/pages/examination/index')
}).finally(() => {
common.hideLoading()
})
// papersId.value = e.papersId
// paperData.value = setPageCache('examination_'+papersId.value)
// if (!papersId.value || !paperData.value) {
// common.navigateBack()
// return
// }
}
onLoad((e) => {
console.log(e);
const testing_hall_details = getPageCache('testing_hall_details')
Object.assign(detailData, {
...e
...testing_hall_details
})
})
</script>
@@ -156,30 +210,89 @@
.fixed_button_div {
position: fixed;
bottom: 30rpx;
width: 80vw;
width: calc(100vw - 60rpx);
}
.title-table {
display: flex;
align-items: center;
justify-content: space-around;
background-color: #F4F6F9;
border-radius: 16rpx;
padding: 28rpx 28rpx 26rpx 28rpx;
margin: 32rpx 0 48rpx 0;
.title-table-item {
// background-color: red;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
height: 96rpx;
}
.text_1 {
font-family: PingFangTC;
font-weight: 400;
font-size: 26rpx;
color: #666666;
}
.text_2 {
font-family: Arial, Arial;
font-weight: normal;
font-size: 34rpx;
color: #333333;
font-weight: 600;
}
.text_3 {}
}
.paper_div {
width: 100%;
min-height: 100%;
background-color: rgb(130, 142, 244);
flex: 1;
.paper {
// width: 100%;
background-color: red;
height: 100%;
margin: 24rpx 24rpx 0 24rpx;
background-color: #fff;
margin: 24rpx 30rpx 0 30rpx;
border-radius: 20rpx;
padding: 16rpx;
background-color: #FFFFFF;
// /* 顶部100px区域设置渐变 */
background-image: linear-gradient(to bottom, #EBF2FE, #FFFFFF);
// /* 只显示顶部100px的渐变 */
background-size: 100% 100px;
background-repeat: no-repeat;
box-shadow: 0 23rpx 38rpx 0px rgba(0, 0, 0, 0.06);
border: 2rpx solid #fff;
}
.title {
font-size: 30rpx;
color: rgb(0, 0, 0);
font-weight: 600;
height: 70rpx;
line-height: 70rpx;
.paper_title_div {
display: flex;
align-items: center;
.img {
height: 32rpx;
}
.title {
font-weight: 600;
height: 70rpx;
line-height: 70rpx;
font-family: PingFangTC;
font-weight: 600;
font-size: 30rpx;
color: #333333;
font-style: normal;
margin-left: 12rpx;
}
}
.title-note {
@@ -197,18 +310,42 @@
justify-content: space-between;
padding: 20rpx 0 80rpx 0;
.table_th {
flex: 1;
display: flex;
background-color: #F4F6F9;
padding: 0 20rpx;
}
.table_tr_dark {
background-color: #FAFAFA;
}
.table_th>.table_td {
color: #333333;
font-weight: 600;
}
.table_tr {
flex: 1;
display: flex;
padding: 0 20rpx;
}
.table_td {
padding: 14rpx 0;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-family: PingFangSC;
font-size: 30rpx;
line-height: 30rpx;
text-align: center;
}
.table_tr>.table_td {
color: #666666;
font-weight: 400;
}
}
}
@@ -218,17 +355,55 @@
align-items: center;
padding-top: var(--status-bar-height);
overflow: hidden;
}
.body-title {
background-color: #337FFF;
width: 100%;
position: relative;
text-align: center;
height: 140rpx;
line-height: 140rpx;
font-family: PingFangSC;
font-weight: 500;
font-size: 38rpx;
color: #FFFFFF;
font-style: normal;
}
.detail_div {
margin-top: -20rpx;
z-index: 2;
width: 100%;
padding: 48rpx 40rpx 20rpx 40rpx;
.title_div{
background-color: #FFFFFF;
border-radius: 16rpx 16rpx 0 0;
position: relative;
overflow: hidden;
.subscript {
position: absolute;
top: 0;
right: 0;
width: 102rpx;
height: 42rpx;
font-family: PingFangSC;
font-weight: 400;
font-size: 24rpx;
line-height: 42rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 0 0 28rpx;
}
.title_div {
display: flex;
justify-content: space-between;
flex: 1;
.title {
font-family: PingFangSC;
font-weight: 600;
@@ -238,21 +413,11 @@
text-align: left;
font-style: normal;
}
.subscript {
width: 102rpx;
height: 42rpx;
border-radius: 10rpx;
font-family: PingFangSC;
font-weight: 400;
font-size: 24rpx;
line-height: 42rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.time {
font-size: 24rpx;
color: #999999;
@@ -283,29 +448,65 @@
color: #666666;
}
}
.incomplete {
// 进行中
.subscript {
color: #FFFFFF;
background-color: #EF5705;
}
.color_class {
color: #D70C18;
}
}
.completed {
// 已完成
.subscript {
color: #FFFFFF;
background-color: #33C583;
}
.color_class {
color: #0066FF;
}
}
.back_div {
position: absolute;
left: 26rpx;
height: 100%;
top: 0;
display: flex;
align-items: center;
.back-icon {
position: relative;
width: 50rpx;
height: 50rpx;
cursor: pointer;
display: flex;
align-items: center;
}
.back-icon::before,
.back-icon::after {
content: '';
position: absolute;
transition: all 0.3s ease;
}
.back-icon::after {
top: 25%;
left: 25%;
width: 40%;
height: 40%;
border-top: 4rpx solid #fff;
border-left: 4rpx solid #fff;
transform: rotate(-45deg);
}
}
</style>
+7 -11
View File
@@ -47,6 +47,7 @@
import {
onLoad
} from '@dcloudio/uni-app'
import {setPageCache} from '@/common/common';
import common from '@/common/common';
const customStyle = {
backgroundColor: "#F1F5FA",
@@ -96,21 +97,16 @@
console.log('搜索', searchText.value);
}
const gotoDetails = (item) => {
const detailData = {
...item
};
const params = new URLSearchParams();
for (const key in detailData) {
params.append(key, detailData[key]);
}
const urlParams = params.toString();
common.navigateTo(`/pages/testingHall/details?${urlParams}`);
setPageCache('testing_hall_details', item)
common.navigateTo('/pages/testingHall/details');
}
onMounted(() => {
triggered.value = true
watch(tabAct, (newValue, oldValue) => {
console.log('resresresresresxxxxxxxxxx', contentRefs.value);
console.log('resresresresresxxxxxxxxxx', newValue);
console.log('resresresresresxxxxxxxxxx', contentRefs.value[newValue]);
typeof contentRefs.value[newValue]?.getData === 'function' && contentRefs.value[newValue]
?.getData('first')
}, {
+15 -11
View File
@@ -39,7 +39,7 @@
computed,
nextTick,
onMounted,
watch
watch
} from 'vue';
import {
LineBg
@@ -51,7 +51,7 @@
} from '@/api/courseDetail.js';
import common from '@/common/common';
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
const courseRef = ref(null);
const correctRef = ref(null);
const tabAct = ref(0);
@@ -60,7 +60,7 @@
}, {
name: '已订正'
}]);
const tabChange = (item) => {
tabAct.value = item.index;
};
@@ -68,13 +68,15 @@
tabAct.value = item.detail.current;
};
onMounted(() => {
watch(tabAct, (newValue, oldValue) => {
if(newValue === 0) {
courseRef.value?.getData('first')
} else if(newValue === 1) {
correctRef.value?.getData('first')
}
}, { immediate: true }) // 这里的immediate会在onMounted之后执行
watch(tabAct, (newValue, oldValue) => {
if (newValue === 0) {
courseRef.value?.getData('first')
} else if (newValue === 1) {
correctRef.value?.getData('first')
}
}, {
immediate: true
}) // 这里的immediate会在onMounted之后执行
})
</script>
@@ -82,6 +84,7 @@
$bg-margin-left: 50rpx;
.my_tabs {
// 解决这个圆角的图片,开穿透
:deep(.uv-tabs__wrapper__nav__line) {
height: 26rpx !important;
@@ -90,7 +93,7 @@
background-repeat: no-repeat !important;
}
}
.swiper {
height: calc(100vh - var(--status-bar-height) - 54rpx - 88rpx - 20rpx - 10rpx);
}
@@ -134,6 +137,7 @@
align-items: center;
justify-content: center;
background-color: #fff;
.back_div {
position: absolute;
left: 20rpx;
+19
View File
@@ -125,6 +125,25 @@
"titleNView": false
}
}
},
{
"path": "pages/examination/index",
"style": {
"navigationBarTitleText": "考试",
"app-plus": {
"titleNView": false
}
}
},
{
"path": "pages/practice/index",
"style": {
"navigationBarTitleText": "练习",
"app-plus": {
"titleNView": false
}
}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

Before

Width:  |  Height:  |  Size: 867 B

After

Width:  |  Height:  |  Size: 867 B

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

@@ -0,0 +1,9 @@
## 1.0.32023-10-13
1. unmounted兼容vue3
## 1.0.22023-06-20
1. 增加外部样式customStyle参数
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
uv-count-down 倒计时
@@ -0,0 +1,25 @@
export default {
props: {
// 倒计时时长,单位ms
time: {
type: [String, Number],
default: 0
},
// 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒
format: {
type: String,
default: 'HH:mm:ss'
},
// 是否自动开始倒计时
autoStart: {
type: Boolean,
default: true
},
// 是否展示毫秒倒计时
millisecond: {
type: Boolean,
default: false
},
...uni.$uv?.props?.countDown
}
}
@@ -0,0 +1,62 @@
// 补0,如1 -> 01
function padZero(num, targetLength = 2) {
let str = `${num}`
while (str.length < targetLength) {
str = `0${str}`
}
return str
}
const SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
export function parseTimeData(time) {
const days = Math.floor(time / DAY)
const hours = Math.floor((time % DAY) / HOUR)
const minutes = Math.floor((time % HOUR) / MINUTE)
const seconds = Math.floor((time % MINUTE) / SECOND)
const milliseconds = Math.floor(time % SECOND)
return {
days,
hours,
minutes,
seconds,
milliseconds
}
}
export function parseFormat(format, timeData) {
let {
days,
hours,
minutes,
seconds,
milliseconds
} = timeData
// 如果格式化字符串中不存在DD(天),则将天的时间转为小时中去
if (format.indexOf('DD') === -1) {
hours += days * 24
} else {
// 对天补0
format = format.replace('DD', padZero(days))
}
// 其他同理于DD的格式化处理方式
if (format.indexOf('HH') === -1) {
minutes += hours * 60
} else {
format = format.replace('HH', padZero(hours))
}
if (format.indexOf('mm') === -1) {
seconds += minutes * 60
} else {
format = format.replace('mm', padZero(minutes))
}
if (format.indexOf('ss') === -1) {
milliseconds += seconds * 1000
} else {
format = format.replace('ss', padZero(seconds))
}
return format.replace('SSS', padZero(milliseconds, 3))
}
export function isSameSecond(time1, time2) {
return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
}
@@ -0,0 +1,169 @@
<template>
<view
class="uv-count-down"
:style="[$uv.addStyle(customStyle)]">
<slot>
<text class="uv-count-down__text">{{ formattedTime }}</text>
</slot>
</view>
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
import {
isSameSecond,
parseFormat,
parseTimeData
} from './utils';
/**
* uv-count-down 倒计时
* @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。
* @tutorial https://www.uvui.cn/components/countDown.html
* @property {String | Number} time 倒计时时长,单位ms (默认 0 )
* @property {String} format 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒 (默认 'HH:mm:ss'
* @property {Boolean} autoStart 是否自动开始倒计时 (默认 true )
* @property {Boolean} millisecond 是否展示毫秒倒计时 (默认 false )
* @event {Function} finish 倒计时结束时触发
* @event {Function} change 倒计时变化时触发
* @event {Function} start 开始倒计时
* @event {Function} pause 暂停倒计时
* @event {Function} reset 重设倒计时,若 auto-start 为 true,重设后会自动开始倒计时
* @example <uv-count-down :time="time"></uv-count-down>
*/
export default {
name: 'uv-count-down',
mixins: [mpMixin, mixin, props],
data() {
return {
timer: null,
// 各单位(天,时,分等)剩余时间
timeData: parseTimeData(0),
// 格式化后的时间,如"03:23:21"
formattedTime: '0',
// 倒计时是否正在进行中
runing: false,
endTime: 0, // 结束的毫秒时间戳
remainTime: 0, // 剩余的毫秒时间
}
},
watch: {
time(n) {
this.reset()
}
},
mounted() {
this.init()
},
methods: {
init() {
this.reset()
},
// 开始倒计时
start() {
if (this.runing) return
// 标识为进行中
this.runing = true
// 结束时间戳 = 此刻时间戳 + 剩余的时间
this.endTime = Date.now() + this.remainTime
this.toTick()
},
// 根据是否展示毫秒,执行不同操作函数
toTick() {
if (this.millisecond) {
this.microTick()
} else {
this.macroTick()
}
},
macroTick() {
this.clearTimeout()
// 每隔一定时间,更新一遍定时器的值
// 同时此定时器的作用也能带来毫秒级的更新
this.timer = setTimeout(() => {
// 获取剩余时间
const remain = this.getRemainTime()
// 重设剩余时间
if (!isSameSecond(remain, this.remainTime) || remain === 0) {
this.setRemainTime(remain)
}
// 如果剩余时间不为0,则继续检查更新倒计时
if (this.remainTime !== 0) {
this.macroTick()
}
}, 30)
},
microTick() {
this.clearTimeout()
this.timer = setTimeout(() => {
this.setRemainTime(this.getRemainTime())
if (this.remainTime !== 0) {
this.microTick()
}
}, 50)
},
// 获取剩余的时间
getRemainTime() {
// 取最大值,防止出现小于0的剩余时间值
return Math.max(this.endTime - Date.now(), 0)
},
// 设置剩余的时间
setRemainTime(remain) {
this.remainTime = remain
// 根据剩余的毫秒时间,得出该有天,小时,分钟等的值,返回一个对象
const timeData = parseTimeData(remain)
this.$emit('change', timeData)
// 得出格式化后的时间
this.formattedTime = parseFormat(this.format, timeData)
// 如果时间已到,停止倒计时
if (remain <= 0) {
this.pause()
this.$emit('finish')
}
},
// 重置倒计时
reset() {
this.pause()
this.remainTime = this.time
this.setRemainTime(this.remainTime)
if (this.autoStart) {
this.start()
}
},
// 暂停倒计时
pause() {
this.runing = false;
this.clearTimeout()
},
// 清空定时器
clearTimeout() {
clearTimeout(this.timer)
this.timer = null
}
},
// #ifdef VUE2
beforeDestroy() {
this.clearTimeout()
},
// #endif
// #ifdef VUE3
unmounted() {
this.clearTimeout()
}
// #endif
}
</script>
<style lang="scss" scoped>
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
$uv-count-down-text-color: $uv-content-color !default;
$uv-count-down-text-font-size: 15px !default;
$uv-count-down-text-line-height: 22px !default;
.uv-count-down {
&__text {
color: $uv-count-down-text-color;
font-size: $uv-count-down-text-font-size;
line-height: $uv-count-down-text-line-height;
}
}
</style>
@@ -0,0 +1,87 @@
{
"id": "uv-count-down",
"displayName": "uv-count-down 倒计时 全面兼容小程序、nvue、vue2、vue3等多端",
"version": "1.0.3",
"description": "该倒计时组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。",
"keywords": [
"uv-count-down",
"uvui",
"uv-ui",
"countDown",
"倒计时"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [
"uv-ui-tools"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
## CountDown 倒计时
> **组件名:uv-count-down**
该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。
### <a href="https://www.uvui.cn/components/countDown.html" target="_blank">查看文档</a>
### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
@@ -0,0 +1,7 @@
## 1.0.22023-06-20
1. 适配height参数携带单位
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
uv-line-progress 线形进度条
@@ -0,0 +1,29 @@
export default {
props: {
// 激活部分的颜色
activeColor: {
type: String,
default: '#19be6b'
},
inactiveColor: {
type: String,
default: '#ececec'
},
// 进度百分比,数值
percentage: {
type: [String, Number],
default: 0
},
// 是否在进度条内部显示百分比的值
showText: {
type: Boolean,
default: true
},
// 进度条的高度,单位px
height: {
type: [String, Number],
default: 12
},
...uni.$uv?.props?.lineProgress
}
}
@@ -0,0 +1,146 @@
<template>
<view
class="uv-line-progress"
:style="[$uv.addStyle(customStyle)]"
>
<view
class="uv-line-progress__background"
ref="uv-line-progress__background"
:style="[{
backgroundColor: inactiveColor,
height: $uv.addUnit($uv.getPx(height))
}]"
>
</view>
<view
class="uv-line-progress__line"
:style="[progressStyle]"
>
<slot>
<text v-if="showText && percentage >= 10" class="uv-line-progress__text">{{innserPercentage + '%'}}</text>
</slot>
</view>
</view>
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* lineProgress 线型进度条
* @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。
* @tutorial https://www.uvui.cn/components/lineProgress.html
* @property {String} activeColor 激活部分的颜色 ( 默认 '#19be6b' )
* @property {String} inactiveColor 背景色 ( 默认 '#ececec' )
* @property {String | Number} percentage 进度百分比,数值 ( 默认 0 )
* @property {Boolean} showText 是否在进度条内部显示百分比的值 ( 默认 true )
* @property {String | Number} height 进度条的高度,单位px ( 默认 12 )
*
* @example <uv-line-progress :percent="70" :show-percent="true"></uv-line-progress>
*/
export default {
name: "uv-line-progress",
mixins: [mpMixin, mixin, props],
data() {
return {
lineWidth: 0,
}
},
watch: {
percentage(n) {
this.resizeProgressWidth()
}
},
computed: {
progressStyle() {
let style = {}
style.width = this.lineWidth
style.backgroundColor = this.activeColor
style.height = this.$uv.addUnit(this.$uv.getPx(this.height))
return style
},
innserPercentage() {
// 控制范围在0-100之间
return this.$uv.range(0, 100, this.percentage)
}
},
mounted() {
this.init()
},
methods: {
init() {
this.$uv.sleep(20).then(() => {
this.resizeProgressWidth()
})
},
getProgressWidth() {
// #ifndef APP-NVUE
return this.$uvGetRect('.uv-line-progress__background')
// #endif
// #ifdef APP-NVUE
// 返回一个promise
return new Promise(resolve => {
dom.getComponentRect(this.$refs['uv-line-progress__background'], (res) => {
resolve(res.size)
})
})
// #endif
},
resizeProgressWidth() {
this.getProgressWidth().then(size => {
const {
width
} = size
// 通过设置的percentage值,计算其所占总长度的百分比
this.lineWidth = width * this.innserPercentage / 100 + 'px'
})
}
}
}
</script>
<style lang="scss" scoped>
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
.uv-line-progress {
align-items: stretch;
position: relative;
@include flex(row);
flex: 1;
overflow: hidden;
border-radius: 100px;
&__background {
background-color: #ececec;
border-radius: 100px;
flex: 1;
}
&__line {
position: absolute;
top: 0;
left: 0;
bottom: 0;
align-items: center;
@include flex(row);
color: #ffffff;
border-radius: 100px;
transition: width 0.5s ease;
justify-content: flex-end;
}
&__text {
font-size: 10px;
align-items: center;
text-align: right;
color: #FFFFFF;
margin-right: 5px;
transform: scale(0.9);
}
}
</style>
@@ -0,0 +1,87 @@
{
"id": "uv-line-progress",
"displayName": "uv-line-progress 线形进度条 全面兼容小程序、nvue、vue2、vue3等多端",
"version": "1.0.2",
"description": "uv-line-progress 该组件展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。",
"keywords": [
"uv-line-progress",
"uvui",
"uv-ui",
"progress",
"进度条"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [
"uv-ui-tools"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
@@ -0,0 +1,11 @@
## LineProgress 线形进度条
> **组件名:uv-line-progress**
展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。
### <a href="https://www.uvui.cn/components/lineProgress.html" target="_blank">查看文档</a>
### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>