JLBA202505130001_关于吉林银行新建AI智能培训系统的需求_除了语音转文字都开发完成
@@ -4,13 +4,11 @@ ENV = 'development'
|
||||
|
||||
|
||||
|
||||
VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001'
|
||||
#VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001'
|
||||
|
||||
|
||||
|
||||
# VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
|
||||
|
||||
|
||||
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
|
||||
|
||||
|
||||
# VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
|
||||
@@ -46,7 +44,7 @@ VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001'
|
||||
|
||||
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.78:9786'
|
||||
|
||||
# VITE_APP_BASE_H5_API_Url_TRAAPP = 'http://25.64.16.130:9601'
|
||||
# VITE_APP_BASE_H5_API_Url_TRAAPP = 'http://25.64.32.150:9601'
|
||||
# VITE_APP_BASE_H5_API_Url_TRASTUDY = 'http://25.64.16.130:9602'
|
||||
|
||||
# VITE_APP_BASE_H5_API_Url_TRAASK = 'http://25.64.16.144:9605'
|
||||
|
||||
@@ -6,9 +6,11 @@ rm -rf dist
|
||||
rm -rf node_modules
|
||||
echo "-----删除成功-----"
|
||||
ls -a
|
||||
|
||||
export TLS_MAX_MEM=8388608
|
||||
npm install @dcloudio/uts-linux-x64-gnu --registry=http://25.12.10.69:8081/repository/aliyun-npm/ --force
|
||||
#下载依赖
|
||||
npm install --unsafe-perm=true --allow-root --registry=http://25.12.10.69:8081/repository/aliyun-npm/
|
||||
# npm install --unsafe-perm=true --allow-root --registry=http://25.12.10.69:8081/repository/aliyun-npm/
|
||||
npm install --registry=http://25.12.10.69:8081/repository/aliyun-npm/
|
||||
#打包成静态资源
|
||||
npm run build:app-plus:${ENV_NAME}
|
||||
echo "打包成功"
|
||||
|
||||
@@ -127,3 +127,26 @@ export const queryRankWall = (data) => {
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 查询当前时间是否存在假日积分奖励
|
||||
*/
|
||||
export const queryHolidayReward = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPointsInfo/queryHolidayReward',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 随机假日积分获得
|
||||
*/
|
||||
export const obtainRewardRandom = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPointsInfo/obtainRewardRandom',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,16 +3,23 @@
|
||||
<view class="line">
|
||||
<text class="tr">维度评分:</text>
|
||||
<text class="td">{{ dimension_all_score }}</text>
|
||||
|
||||
<view class="td_success_error_img">
|
||||
<image :src="select_status_icon" class="img"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="line" v-for="item in evalSettingVos">
|
||||
<view class="dimension-line" v-for="item in evalSettingVos">
|
||||
<text class="tr">{{ item.gradeDimens }}:</text>
|
||||
<text class="td">{{ item.anlyGrade }}</text>
|
||||
<view class="your_answer" v-if="item.dimensCode === '01'">
|
||||
<rich-text :nodes="highlightedText" class="content" />
|
||||
<view class="mt-10" v-if="missingKeywords.length">
|
||||
<text class="">未命中:</text>
|
||||
<text v-for="(keyword, index) in missingKeywords" :key="index" class="miss-keyword-item">
|
||||
<!-- 关键词:红色 -->
|
||||
<text class="keyword-text">{{ keyword }}</text>
|
||||
<text class="separator" v-if="index < missingKeywords.length - 1">、</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -57,7 +64,7 @@
|
||||
|
||||
const highlightedText = computed(() => {
|
||||
// 如果没有关键词,直接返回原文本
|
||||
if (!keywords.value.length) return '你的答案:' + props.anserResult;
|
||||
if (!keywords.value.length) return '命中:' + props.anserResult;
|
||||
// 构建正则表达式,匹配所有关键词(不区分大小写)
|
||||
// 对关键词进行转义,避免特殊字符影响正则
|
||||
const escapedKeywords = keywords.value.map((keyword) => keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
||||
@@ -66,7 +73,7 @@
|
||||
const regex = new RegExp(`(${escapedKeywords.join('|')})`, 'gi');
|
||||
|
||||
// 替换匹配到的关键词,用span包裹并添加高亮样式
|
||||
return '你的答案:' + props.anserResult.replace(regex, (match) => `<span class="highlight">${match}</span>`);
|
||||
return '命中:' + props.anserResult.replace(regex, (match) => `<span class="highlight">${match}</span>`);
|
||||
});
|
||||
const dimension_all_score = computed(() => {
|
||||
return (
|
||||
@@ -77,13 +84,51 @@
|
||||
.reduce((total, current) => total + current, 0)
|
||||
);
|
||||
});
|
||||
// 找出答案中不存在的关键词
|
||||
const missingKeywords = computed(() => {
|
||||
const relKeyword = props.value.relKeyword;
|
||||
|
||||
// 容错:答案为空时返回所有关键词
|
||||
if (!props.anserResult) {
|
||||
return relKeyword.split('、');
|
||||
}
|
||||
|
||||
// 步骤1:按顿号拆分关键词组
|
||||
const keywordGroups = relKeyword.split('、');
|
||||
const missing = [];
|
||||
|
||||
// 步骤2:遍历每个关键词组,检查是否存在任意一种说法
|
||||
keywordGroups.forEach((group) => {
|
||||
// 拆分同关键词的不同说法(/ 分隔)
|
||||
const keywordVariants = group.split('/');
|
||||
let isFound = false;
|
||||
|
||||
// 检查答案中是否包含任意一种说法
|
||||
for (const variant of keywordVariants) {
|
||||
// 可选:增加 trim() 忽略首尾空格,提升匹配容错
|
||||
if (props.anserResult.trim().includes(variant.trim())) {
|
||||
isFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 未找到则加入缺失列表
|
||||
if (!isFound) {
|
||||
missing.push(group);
|
||||
}
|
||||
});
|
||||
|
||||
return missing;
|
||||
});
|
||||
const click_option = () => {
|
||||
emit('click_option', props.analysisVo);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mt-10 {
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
.success {
|
||||
background-color: #e7f8ed;
|
||||
.td {
|
||||
@@ -102,21 +147,44 @@
|
||||
color: #f5212d;
|
||||
}
|
||||
}
|
||||
|
||||
.your_answer {
|
||||
:deep(.highlight) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.keyword-text {
|
||||
color: #d70c18;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.separator {
|
||||
}
|
||||
.analysis_div {
|
||||
margin-top: 30rpx;
|
||||
|
||||
border-radius: 16rpx;
|
||||
padding: 22rpx 15rpx 15rpx 34rpx;
|
||||
|
||||
.line {
|
||||
margin: 12rpx 0;
|
||||
position: relative;
|
||||
}
|
||||
.dimension-line {
|
||||
margin: 12rpx 0;
|
||||
position: relative;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
.dimension-line::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0.5em; /* 对齐padding-left的起始位置 */
|
||||
top: 1em; /* 垂直居中 */
|
||||
transform: translateY(-50%);
|
||||
width: 8px; /* 圆点大小(默认是4px左右,调大到8px) */
|
||||
height: 8px; /* 和宽度一致,保证圆形 */
|
||||
border-radius: 50%; /* 圆形 */
|
||||
background-color: #333; /* 圆点颜色,可自定义 */
|
||||
}
|
||||
|
||||
.tr {
|
||||
font-weight: 600;
|
||||
font-size: 30rpx;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<view class="reference_answer">
|
||||
<view class="reference_answer_title">参考答案:</view>
|
||||
<rich-text :nodes="highlightedText" class="content" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, nextTick, computed, watch } from 'vue';
|
||||
|
||||
const emit = defineEmits(['click_option']);
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
required: false
|
||||
},
|
||||
anserResult: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const keywords = computed(() => {
|
||||
if (!props.value.relKeyword) return [];
|
||||
return props.value.relKeyword.split(/[,、/]/).map((key) => key.trim());
|
||||
});
|
||||
|
||||
const highlightedText = computed(() => {
|
||||
// 如果没有关键词,直接返回原文本
|
||||
if (!keywords.value.length) return props.anserResult;
|
||||
// 构建正则表达式,匹配所有关键词(不区分大小写)
|
||||
// 对关键词进行转义,避免特殊字符影响正则
|
||||
const escapedKeywords = keywords.value.map((keyword) => keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
||||
|
||||
// 创建正则表达式,g表示全局匹配,i表示不区分大小写
|
||||
const regex = new RegExp(`(${escapedKeywords.join('|')})`, 'gi');
|
||||
|
||||
// 替换匹配到的关键词,用span包裹并添加高亮样式
|
||||
return props.anserResult.replace(regex, (match) => `<span class="highlight">${match}</span>`);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.reference_answer {
|
||||
.reference_answer_title {
|
||||
font-weight: 700;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
line-height: 30rpx;
|
||||
margin: 30rpx 0;
|
||||
|
||||
}
|
||||
font-weight: 400;
|
||||
font-size: 30rpx;
|
||||
color: #666666;
|
||||
line-height: 48rpx;
|
||||
text-align: left;
|
||||
:deep(.highlight) {
|
||||
color: #15b859;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -2,7 +2,9 @@
|
||||
<view class="item">
|
||||
<view class="top">
|
||||
<view class="type">{{ subject_type_text }}</view>
|
||||
<view class="score" v-if="['settlement', 'examination', 'pk_in'].includes(props.mode)">{{ item.tgtGrade }}分</view>
|
||||
<view class="score" v-if="['settlement', 'examination', 'pk_in'].includes(props.mode)">
|
||||
{{ item.tgtGrade }}分
|
||||
</view>
|
||||
<view class="fl1"></view>
|
||||
<view class="right">
|
||||
<!-- 反馈中心 -->
|
||||
@@ -66,7 +68,12 @@
|
||||
</view>
|
||||
<view
|
||||
class="option_div_button"
|
||||
v-if="qnsTyp === 'MU' && ['practice', 'study', 'pk_in'].includes(props.mode) && !props.isPreview && clearResult"
|
||||
v-if="
|
||||
qnsTyp === 'MU' &&
|
||||
['practice', 'study', 'pk_in'].includes(props.mode) &&
|
||||
!props.isPreview &&
|
||||
clearResult
|
||||
"
|
||||
>
|
||||
<uv-button
|
||||
type="primary"
|
||||
@@ -106,11 +113,14 @@
|
||||
></esAnalysis>
|
||||
|
||||
<!-- 问答题专属参考答案 -->
|
||||
<view class="reference_answer" v-if="qnsTyp === 'ES'">
|
||||
<view class="reference_answer_title">参考答案:</view>
|
||||
{{ subject_data.itemVos[0]?.itemAnswer ?? '' }}
|
||||
<referenceAnswer
|
||||
v-if="qnsTyp === 'ES'"
|
||||
:value="subject_data.itemVos[0]"
|
||||
:anserResult="subject_data.anserResult"
|
||||
></referenceAnswer>
|
||||
<view class="analysis_note" v-if="props.mode !== 'pk_in'">
|
||||
试题解析:{{ subject_data?.analyContent }}
|
||||
</view>
|
||||
<view class="analysis_note" v-if="props.mode !== 'pk_in'">试题解析:{{ subject_data?.analyContent }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -120,6 +130,7 @@
|
||||
import { ref, reactive, onMounted, nextTick, computed, getCurrentInstance, watch } from 'vue';
|
||||
import radioSubjectItem from './radio-subject-item.vue';
|
||||
import esAnalysis from './es-analysis.vue';
|
||||
import referenceAnswer from './reference-answer.vue';
|
||||
import questionVue from './question.vue';
|
||||
import { markIcon, feedbackIcon } from '@/common/imgSvg';
|
||||
import { SUBJECT_TYPE } from '@/enum.js';
|
||||
@@ -153,7 +164,9 @@
|
||||
// 学习: study
|
||||
// 考试结算页 settlement
|
||||
// 练习记录页 practice_record, 'practice_record'
|
||||
return ['examination', 'practice', 'mistake', 'study', 'settlement', 'practice_record', 'pk_in'].includes(value);
|
||||
return ['examination', 'practice', 'mistake', 'study', 'settlement', 'practice_record', 'pk_in'].includes(
|
||||
value
|
||||
);
|
||||
}
|
||||
},
|
||||
// 是否预览模式
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
<template>
|
||||
<uni-popup ref="popup" mask-background-color="rgba(0,0,0,0.9)">
|
||||
<view class="popup-content" @touchmove.stop catchmove="true" @touchmove.stop.prevent="handleTouchMove">
|
||||
<view class="fl1"></view>
|
||||
<view class="max-div">
|
||||
<view class="festival-text-pic-div">
|
||||
<image
|
||||
class="festival-text-img"
|
||||
mode="widthFix"
|
||||
src="@/static/images/pointsAndRank/festival-text.png"
|
||||
></image>
|
||||
</view>
|
||||
<image
|
||||
class="festival-bg-up-div"
|
||||
mode="widthFix"
|
||||
src="@/static/images/pointsAndRank/festival-bg-up.png"
|
||||
></image>
|
||||
<view class="max-area-div">
|
||||
<!-- 右上角百禄图标 -->
|
||||
<view class="festival-special-mascot">
|
||||
<image-preview
|
||||
class="festival-special-mascot-img"
|
||||
mode="widthFix"
|
||||
:src="holidayInfo.ossAddr"
|
||||
></image-preview>
|
||||
</view>
|
||||
<!-- 几个特殊节日的特殊背景 -->
|
||||
<view class="festival-special-bg" v-if="holidayInfo.backgroundOssAddr">
|
||||
<image-preview
|
||||
class="festival-special-bg-img"
|
||||
mode="widthFix"
|
||||
:src="holidayInfo.backgroundOssAddr"
|
||||
></image-preview>
|
||||
</view>
|
||||
<!-- 顶部的字,随机还是大额 -->
|
||||
<view class="festival-text-div">{{ holidayInfo.holidayDesc }}</view>
|
||||
<view class="festival-main-div">
|
||||
<image
|
||||
class="festival-main-img"
|
||||
mode="widthFix"
|
||||
src="@/static/images/pointsAndRank/festival-main.png"
|
||||
></image>
|
||||
<view class="festival-main-text">
|
||||
<view class="festival-main-title">
|
||||
{{ done.tipText }}
|
||||
</view>
|
||||
<view class="festival-main-number-div">
|
||||
<text class="festival-main-number">
|
||||
{{ score }}
|
||||
</text>
|
||||
<text class="festival-main-number-text">积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="festival-button-div" @click="startLottery()">{{ done.buttonText }}</view>
|
||||
</view>
|
||||
<view class="close-div" @click="close(false)"></view>
|
||||
</view>
|
||||
<view class="fl1"></view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HolidayInfo, CompleteCallback, StartLotteryCallback } from './types/holiday';
|
||||
import { RewardType, AnimationStatus } from './types/holiday';
|
||||
import { ref, computed, reactive } from 'vue';
|
||||
import { optionErrorIcon, optionSuccessIcon } from '@/common/imgSvg';
|
||||
import { obtainRewardRandom } from '@/api/pointsAndRank';
|
||||
import common from '@/common/common';
|
||||
|
||||
const countToRef = ref(null);
|
||||
const popup = ref(null);
|
||||
const animationLoading = ref<AnimationStatus>(AnimationStatus.UNSTARTED);
|
||||
const done = computed(() =>
|
||||
holidayInfo.rewardType === RewardType.RANDOM_POINT
|
||||
? {
|
||||
tipText: '最高可获',
|
||||
buttonText: animationLoading.value === AnimationStatus.COMPLETED ? '开心收下' : '立即抽取'
|
||||
}
|
||||
: { tipText: '恭喜获得', buttonText: '开心收下' }
|
||||
);
|
||||
// 显示的分数
|
||||
const score = ref(10);
|
||||
let lotteryTimer: number | null = null;
|
||||
|
||||
const emits = defineEmits(['checkInFun']);
|
||||
|
||||
// 开始抽奖
|
||||
const startLottery = async () => {
|
||||
// 固定积分,或者随机积分但是已经抽全完毕,再次点击按钮就是关闭
|
||||
if (holidayInfo.rewardType === RewardType.FIXED_POINT || animationLoading.value === AnimationStatus.COMPLETED) {
|
||||
close(true);
|
||||
return;
|
||||
}
|
||||
// 正在抽取中防止重复点击
|
||||
if (animationLoading.value === AnimationStatus.LOADING) return;
|
||||
animationLoading.value = AnimationStatus.LOADING;
|
||||
// 访问接口获取结果
|
||||
if (lotteryTimer) clearInterval(lotteryTimer);
|
||||
startLotteryCallback({
|
||||
holidayCode: holidayInfo.holidayCode,
|
||||
successCb: (body) => {
|
||||
// const { body } = await obtainRewardRandom({ holidayCode: holidayInfo.holidayCode });
|
||||
const finalScore = body.val;
|
||||
// 记录已完成的循环圈数
|
||||
let completedLoops = 0;
|
||||
// 1. 数值范围(可自定义)
|
||||
const LOTTERY_MIN = body.start; // 最小值
|
||||
const LOTTERY_MAX = body.end; // 最大值
|
||||
// 2. 循环圈数
|
||||
const LOOP_TIMES = finalScore - LOTTERY_MIN > 10 ? 1 : 2;
|
||||
// 3. 跳动速度(ms/次,越大越慢)
|
||||
const JUMP_SPEED = 80;
|
||||
// 3. 核心:循环指定圈数后,最后一轮停在目标数字
|
||||
lotteryTimer = setInterval(() => {
|
||||
// 先判断:是否已完成指定圈数 + 当前轮次能精准到目标数字
|
||||
if (completedLoops >= LOOP_TIMES) {
|
||||
if (score.value === finalScore) {
|
||||
clearInterval(lotteryTimer!);
|
||||
lotteryTimer = null;
|
||||
animationLoading.value = AnimationStatus.COMPLETED;
|
||||
return;
|
||||
}
|
||||
// 最后一轮:继续递增(不重置)
|
||||
score.value += 1;
|
||||
} else {
|
||||
// 未完成指定圈数:正常循环(到max重置,计数+1)
|
||||
score.value += 1;
|
||||
// 当数值到max时,代表完成1圈
|
||||
if (score.value >= LOTTERY_MAX) {
|
||||
completedLoops += 1;
|
||||
score.value = LOTTERY_MIN; // 重置为最小值,开始下一圈
|
||||
}
|
||||
}
|
||||
}, JUMP_SPEED);
|
||||
},
|
||||
failCb: (res) => {
|
||||
console.log('报错');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleTouchMove = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
// 节日奖励信息响应式对象
|
||||
const holidayInfo = reactive<HolidayInfo>({
|
||||
// 假日编码
|
||||
holidayCode: '',
|
||||
// 是否存在节日奖励(true:有奖励 false:无奖励)
|
||||
rewardFlag: false,
|
||||
// 奖励类型(枚举限定:1-固定积分 2-随机积分)
|
||||
rewardType: RewardType.RANDOM_POINT,
|
||||
// 文案
|
||||
holidayDesc: '',
|
||||
// 奖励分数(字符串类型,兼容不同格式的积分值)
|
||||
rewardPoints: 0,
|
||||
// 可以获得的最高分
|
||||
rangeEnd: 0,
|
||||
// 可以获得的最低分
|
||||
rangeStart: 0,
|
||||
// 奖励获取标识(true:已获取 false:未获取)
|
||||
obtainFlag: false,
|
||||
// 文案图片的OSS存储key(用于图片上传/读取标识)
|
||||
ossKey: '',
|
||||
// 文案图片的OSS访问地址(图片完整URL)
|
||||
ossAddr: '',
|
||||
// 背景图片的OSS存储key(用于图片上传/读取标识)
|
||||
backgroundOssKey: '',
|
||||
// 背景图片的OSS访问地址(图片完整URL)
|
||||
backgroundOssAddr: ''
|
||||
});
|
||||
let completeCallback: CompleteCallback | undefined; // 关闭弹窗的回调
|
||||
let startLotteryCallback: StartLotteryCallback | undefined; // 抽奖的回调
|
||||
|
||||
// 退出
|
||||
const close = (status: boolean): void => {
|
||||
uni.showTabBar({
|
||||
success() {
|
||||
popup.value.close();
|
||||
console.log('点击退出,执行回调函数');
|
||||
typeof completeCallback === 'function' &&
|
||||
completeCallback({
|
||||
status
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const open = (
|
||||
holidayBody: any,
|
||||
lotteryCb: StartLotteryCallback = () => {},
|
||||
completeCb: CompleteCallback = () => {}
|
||||
): void => {
|
||||
startLotteryCallback = lotteryCb; // 抽奖的回调
|
||||
completeCallback = completeCb; // 关闭弹窗的回调
|
||||
|
||||
Object.assign(holidayInfo, holidayBody);
|
||||
// if (!holidayInfo.rewardFlag || holidayInfo.obtainFlag) {
|
||||
if (!holidayInfo.rewardFlag) {
|
||||
// 如果当前没有节日窗口,立刻返回回调函数
|
||||
completeCallback({ status: false });
|
||||
return;
|
||||
}
|
||||
// 设置初始得分
|
||||
if (holidayInfo.rewardType === RewardType.RANDOM_POINT) {
|
||||
score.value = holidayInfo.rangeEnd;
|
||||
} else {
|
||||
score.value = holidayInfo.rewardPoints;
|
||||
}
|
||||
animationLoading.value = AnimationStatus.UNSTARTED;
|
||||
uni.hideTabBar({
|
||||
success() {
|
||||
popup.value.open();
|
||||
}
|
||||
});
|
||||
};
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.max-area-div {
|
||||
width: 520rpx;
|
||||
height: 542rpx;
|
||||
background-image: url('@/static/images/pointsAndRank/festival-bg.png');
|
||||
background-size: 100%;
|
||||
border-radius: 0 0 60rpx 60rpx;
|
||||
position: relative;
|
||||
|
||||
// .festival-tip-div {
|
||||
// z-index: 20;
|
||||
// position: relative;
|
||||
// margin-left: 16rpx;
|
||||
// margin-bottom: -16rpx;
|
||||
// .festival-tip-img {
|
||||
// margin-top: 30rpx;
|
||||
// height: 56rpx;
|
||||
// }
|
||||
// }
|
||||
.festival-text-div {
|
||||
position: relative;
|
||||
margin-left: 25rpx;
|
||||
z-index: 60;
|
||||
margin-top: 28rpx;
|
||||
color: #ffffff;
|
||||
font-style: normal;
|
||||
|
||||
font-weight: 600;
|
||||
font-size: 58rpx;
|
||||
color: #fffae8;
|
||||
letter-spacing: 0.05em;
|
||||
line-height: 77rpx;
|
||||
text-shadow: 0px 4px 8px #d6061b;
|
||||
text-align: left;
|
||||
font-style: italic;
|
||||
transform: skewX(2deg);
|
||||
}
|
||||
.festival-main-div {
|
||||
// margin-top: 8rpx;
|
||||
z-index: 20;
|
||||
position: relative;
|
||||
width: 530rpx;
|
||||
height: 270rpx;
|
||||
.festival-main-img {
|
||||
margin-left: 16rpx;
|
||||
width: 530rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.festival-main-text {
|
||||
position: absolute;
|
||||
left: 40rpx;
|
||||
top: 50rpx;
|
||||
width: 150rpx;
|
||||
// background-color:red;
|
||||
.festival-main-title {
|
||||
font-weight: 600;
|
||||
font-size: 34rpx;
|
||||
color: #b24325;
|
||||
line-height: 46rpx;
|
||||
text-align: right;
|
||||
}
|
||||
.festival-main-number-div {
|
||||
margin-top: 10rpx;
|
||||
font-family: DINAlternate, DINAlternate;
|
||||
color: #b24325;
|
||||
line-height: 64rpx;
|
||||
margin-right: 8rpx;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
.festival-main-number {
|
||||
font-size: 64rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.festival-main-number-text {
|
||||
font-size: 26rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.festival-button-div {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
width: 392rpx;
|
||||
height: 108rpx;
|
||||
margin: 0 auto;
|
||||
background-image: url('@/static/images/pointsAndRank/festival-button.png');
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
font-weight: 600;
|
||||
font-size: 38rpx;
|
||||
color: #b24325;
|
||||
line-height: 108rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.festival-special-bg {
|
||||
width: 520rpx;
|
||||
height: 542rpx;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0 0 60rpx 60rpx;
|
||||
z-index: 10;
|
||||
.festival-special-bg-img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.festival-special-mascot {
|
||||
z-index: 50;
|
||||
position: absolute;
|
||||
right: -36rpx;
|
||||
top: 70rpx;
|
||||
transform-origin: left bottom;
|
||||
transform: translateY(-100%);
|
||||
.festival-special-mascot-img {
|
||||
width: 180rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.festival-text-pic-div {
|
||||
width: 100%;
|
||||
.festival-text-img {
|
||||
width: 400rpx;
|
||||
}
|
||||
}
|
||||
.festival-bg-up-div {
|
||||
width: 580rpx;
|
||||
margin-bottom: -14rpx;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: calc(10rpx + 2%) 0;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
.max-div {
|
||||
width: 600rpx;
|
||||
border-radius: 38rpx;
|
||||
position: relative;
|
||||
padding: 26rpx;
|
||||
margin-top: var(--status-bar-height);
|
||||
margin-bottom: calc(180rpx + 15%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.close-div {
|
||||
// 基础样式:设置按钮大小、圆形、边框等
|
||||
margin-top: 50rpx;
|
||||
position: relative; // 为伪类定位做准备
|
||||
width: 62rpx; // 圆圈直径,可根据需求调整
|
||||
height: 62rpx;
|
||||
border: 4rpx solid #b9b9b9; // 圆圈边框
|
||||
border-radius: 50%; // 变成正圆
|
||||
box-sizing: border-box; // 保证边框不影响整体大小
|
||||
// 伪类通用样式:十字的基础线条
|
||||
&::before,
|
||||
&::after {
|
||||
content: ''; // 伪类必须有content
|
||||
position: absolute; // 绝对定位居中
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 24rpx; // 十字线条长度,可调整
|
||||
height: 4rpx; // 十字线条粗细,可调整
|
||||
background-color: #b9b9b9; // 十字颜色
|
||||
transform: translate(-50%, -50%); // 居中对齐
|
||||
}
|
||||
// 第一条线旋转45度
|
||||
&::before {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
// 第二条线旋转-45度,组合成×
|
||||
&::after {
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,412 @@
|
||||
<template>
|
||||
<uni-popup ref="popup" mask-background-color="rgba(0,0,0,0.9)">
|
||||
<view class="popup-content" @touchmove.stop catchmove="true" @touchmove.stop.prevent="handleTouchMove">
|
||||
<view class="fl1"></view>
|
||||
<view class="max-div">
|
||||
<view class="festival-text-pic-div">
|
||||
<image
|
||||
class="festival-text-img"
|
||||
mode="widthFix"
|
||||
src="@/static/images/pointsAndRank/festival-text.png"
|
||||
></image>
|
||||
</view>
|
||||
<image
|
||||
class="festival-bg-up-div"
|
||||
mode="widthFix"
|
||||
src="@/static/images/pointsAndRank/festival-bg-up.png"
|
||||
></image>
|
||||
<view class="max-area-div">
|
||||
<!-- 右上角百禄图标 -->
|
||||
<view class="festival-special-mascot">
|
||||
<image-preview
|
||||
class="festival-special-mascot-img"
|
||||
mode="widthFix"
|
||||
:src="holidayInfo.ossAddr"
|
||||
></image-preview>
|
||||
</view>
|
||||
<!-- 几个特殊节日的特殊背景 -->
|
||||
<view class="festival-special-bg" v-if="holidayInfo.backgroundOssAddr">
|
||||
<image-preview
|
||||
class="festival-special-bg-img"
|
||||
mode="widthFix"
|
||||
:src="holidayInfo.backgroundOssAddr"
|
||||
></image-preview>
|
||||
</view>
|
||||
<!-- 顶部的字,随机还是大额 -->
|
||||
<view class="festival-text-div">{{ holidayInfo.holidayDesc }}</view>
|
||||
<view class="festival-main-div">
|
||||
<image
|
||||
class="festival-main-img"
|
||||
mode="widthFix"
|
||||
src="@/static/images/pointsAndRank/festival-main.png"
|
||||
></image>
|
||||
<view class="festival-main-text">
|
||||
<view class="festival-main-title">
|
||||
{{ done.tipText }}
|
||||
</view>
|
||||
<view class="festival-main-number-div">
|
||||
<text class="festival-main-number">
|
||||
{{ score }}
|
||||
</text>
|
||||
<text class="festival-main-number-text">积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="festival-button-div" @click="startLottery()">{{ done.buttonText }}</view>
|
||||
</view>
|
||||
<view class="close-div" @click="close(false)"></view>
|
||||
</view>
|
||||
<view class="fl1"></view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HolidayInfo, CompleteCallback, StartLotteryCallback } from './types/holiday';
|
||||
import { RewardType, AnimationStatus } from './types/holiday';
|
||||
import { ref, computed, reactive } from 'vue';
|
||||
import { optionErrorIcon, optionSuccessIcon } from '@/common/imgSvg';
|
||||
import { obtainRewardRandom } from '@/api/pointsAndRank';
|
||||
import common from '@/common/common';
|
||||
|
||||
const countToRef = ref(null);
|
||||
const popup = ref(null);
|
||||
const animationLoading = ref<AnimationStatus>(AnimationStatus.UNSTARTED);
|
||||
const done = computed(() =>
|
||||
holidayInfo.rewardType === RewardType.RANDOM_POINT
|
||||
? {
|
||||
tipText: '最高可获',
|
||||
buttonText: animationLoading.value === AnimationStatus.COMPLETED ? '开心收下' : '立即抽取'
|
||||
}
|
||||
: { tipText: '恭喜获得', buttonText: '开心收下' }
|
||||
);
|
||||
// 显示的分数
|
||||
const score = ref(10);
|
||||
let lotteryTimer: number | null = null;
|
||||
|
||||
const emits = defineEmits(['checkInFun']);
|
||||
|
||||
// 开始抽奖
|
||||
const startLottery = async () => {
|
||||
// 固定积分,或者随机积分但是已经抽全完毕,再次点击按钮就是关闭
|
||||
if (holidayInfo.rewardType === RewardType.FIXED_POINT || animationLoading.value === AnimationStatus.COMPLETED) {
|
||||
close(true);
|
||||
return;
|
||||
}
|
||||
// 正在抽取中防止重复点击
|
||||
if (animationLoading.value === AnimationStatus.LOADING) return;
|
||||
animationLoading.value = AnimationStatus.LOADING;
|
||||
// 访问接口获取结果
|
||||
if (lotteryTimer) clearInterval(lotteryTimer);
|
||||
startLotteryCallback({
|
||||
holidayCode: holidayInfo.holidayCode,
|
||||
successCb: (body) => {
|
||||
// const { body } = await obtainRewardRandom({ holidayCode: holidayInfo.holidayCode });
|
||||
const finalScore = body.val;
|
||||
// 记录已完成的循环圈数
|
||||
let completedLoops = 0;
|
||||
// 1. 数值范围(可自定义)
|
||||
const LOTTERY_MIN = body.start; // 最小值
|
||||
const LOTTERY_MAX = body.end; // 最大值
|
||||
// 2. 循环圈数
|
||||
const LOOP_TIMES = finalScore - LOTTERY_MIN > 10 ? 1 : 2;
|
||||
// 3. 跳动速度(ms/次,越大越慢)
|
||||
const JUMP_SPEED = 80;
|
||||
// 3. 核心:循环指定圈数后,最后一轮停在目标数字
|
||||
lotteryTimer = setInterval(() => {
|
||||
// 先判断:是否已完成指定圈数 + 当前轮次能精准到目标数字
|
||||
if (completedLoops >= LOOP_TIMES) {
|
||||
if (score.value === finalScore) {
|
||||
clearInterval(lotteryTimer!);
|
||||
lotteryTimer = null;
|
||||
animationLoading.value = AnimationStatus.COMPLETED;
|
||||
return;
|
||||
}
|
||||
// 最后一轮:继续递增(不重置)
|
||||
score.value += 1;
|
||||
} else {
|
||||
// 未完成指定圈数:正常循环(到max重置,计数+1)
|
||||
score.value += 1;
|
||||
// 当数值到max时,代表完成1圈
|
||||
if (score.value >= LOTTERY_MAX) {
|
||||
completedLoops += 1;
|
||||
score.value = LOTTERY_MIN; // 重置为最小值,开始下一圈
|
||||
}
|
||||
}
|
||||
}, JUMP_SPEED);
|
||||
},
|
||||
failCb: (res) => {
|
||||
console.log('报错');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleTouchMove = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
// 节日奖励信息响应式对象
|
||||
const holidayInfo = reactive<HolidayInfo>({
|
||||
// 假日编码
|
||||
holidayCode: '',
|
||||
// 是否存在节日奖励(true:有奖励 false:无奖励)
|
||||
rewardFlag: false,
|
||||
// 奖励类型(枚举限定:1-固定积分 2-随机积分)
|
||||
rewardType: RewardType.RANDOM_POINT,
|
||||
// 文案
|
||||
holidayDesc: '',
|
||||
// 奖励分数(字符串类型,兼容不同格式的积分值)
|
||||
rewardPoints: 0,
|
||||
// 可以获得的最高分
|
||||
rangeEnd: 0,
|
||||
// 可以获得的最低分
|
||||
rangeStart: 0,
|
||||
// 奖励获取标识(true:已获取 false:未获取)
|
||||
obtainFlag: false,
|
||||
// 文案图片的OSS存储key(用于图片上传/读取标识)
|
||||
ossKey: '',
|
||||
// 文案图片的OSS访问地址(图片完整URL)
|
||||
ossAddr: '',
|
||||
// 背景图片的OSS存储key(用于图片上传/读取标识)
|
||||
backgroundOssKey: '',
|
||||
// 背景图片的OSS访问地址(图片完整URL)
|
||||
backgroundOssAddr: ''
|
||||
});
|
||||
let completeCallback: CompleteCallback | undefined; // 关闭弹窗的回调
|
||||
let startLotteryCallback: StartLotteryCallback | undefined; // 抽奖的回调
|
||||
|
||||
// 退出
|
||||
const close = (status: boolean): void => {
|
||||
uni.showTabBar({
|
||||
success() {
|
||||
popup.value.close();
|
||||
console.log('点击退出,执行回调函数');
|
||||
typeof completeCallback === 'function' &&
|
||||
completeCallback({
|
||||
status
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const open = (
|
||||
holidayBody: any,
|
||||
lotteryCb: StartLotteryCallback = () => {},
|
||||
completeCb: CompleteCallback = () => {}
|
||||
): void => {
|
||||
startLotteryCallback = lotteryCb; // 抽奖的回调
|
||||
completeCallback = completeCb; // 关闭弹窗的回调
|
||||
|
||||
Object.assign(holidayInfo, holidayBody);
|
||||
// if (!holidayInfo.rewardFlag || holidayInfo.obtainFlag) {
|
||||
if (!holidayInfo.rewardFlag) {
|
||||
// 如果当前没有节日窗口,立刻返回回调函数
|
||||
completeCallback({ status: false });
|
||||
return;
|
||||
}
|
||||
// 设置初始得分
|
||||
if (holidayInfo.rewardType === RewardType.RANDOM_POINT) {
|
||||
score.value = holidayInfo.rangeEnd;
|
||||
} else {
|
||||
score.value = holidayInfo.rewardPoints;
|
||||
}
|
||||
animationLoading.value = AnimationStatus.UNSTARTED;
|
||||
uni.hideTabBar({
|
||||
success() {
|
||||
popup.value.open();
|
||||
}
|
||||
});
|
||||
};
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.max-area-div {
|
||||
width: 520rpx;
|
||||
height: 542rpx;
|
||||
background-image: url('@/static/images/pointsAndRank/festival-bg.png');
|
||||
background-size: 100%;
|
||||
border-radius: 0 0 60rpx 60rpx;
|
||||
position: relative;
|
||||
|
||||
// .festival-tip-div {
|
||||
// z-index: 20;
|
||||
// position: relative;
|
||||
// margin-left: 16rpx;
|
||||
// margin-bottom: -16rpx;
|
||||
// .festival-tip-img {
|
||||
// margin-top: 30rpx;
|
||||
// height: 56rpx;
|
||||
// }
|
||||
// }
|
||||
.festival-text-div {
|
||||
position: relative;
|
||||
margin-left: 25rpx;
|
||||
z-index: 60;
|
||||
margin-top: 28rpx;
|
||||
color: #ffffff;
|
||||
font-style: normal;
|
||||
|
||||
font-weight: 600;
|
||||
font-size: 58rpx;
|
||||
color: #fffae8;
|
||||
letter-spacing: 0.05em;
|
||||
line-height: 77rpx;
|
||||
text-shadow: 0px 4px 8px #d6061b;
|
||||
text-align: left;
|
||||
font-style: italic;
|
||||
transform: skewX(2deg);
|
||||
}
|
||||
.festival-main-div {
|
||||
// margin-top: 8rpx;
|
||||
z-index: 20;
|
||||
position: relative;
|
||||
width: 530rpx;
|
||||
height: 270rpx;
|
||||
.festival-main-img {
|
||||
margin-left: 16rpx;
|
||||
width: 530rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.festival-main-text {
|
||||
position: absolute;
|
||||
left: 40rpx;
|
||||
top: 50rpx;
|
||||
width: 150rpx;
|
||||
// background-color:red;
|
||||
.festival-main-title {
|
||||
font-weight: 600;
|
||||
font-size: 34rpx;
|
||||
color: #b24325;
|
||||
line-height: 46rpx;
|
||||
text-align: right;
|
||||
}
|
||||
.festival-main-number-div {
|
||||
margin-top: 10rpx;
|
||||
font-family: DINAlternate, DINAlternate;
|
||||
color: #b24325;
|
||||
line-height: 64rpx;
|
||||
margin-right: 8rpx;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
.festival-main-number {
|
||||
font-size: 64rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.festival-main-number-text {
|
||||
font-size: 26rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.festival-button-div {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
width: 360rpx;
|
||||
height: 80rpx;
|
||||
margin: 0 auto;
|
||||
background-image: url('@/static/images/pointsAndRank/festival-button.png');
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
color: #B24325;
|
||||
line-height: 80rpx;
|
||||
}
|
||||
|
||||
.festival-special-bg {
|
||||
width: 520rpx;
|
||||
height: 542rpx;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0 0 60rpx 60rpx;
|
||||
z-index: 10;
|
||||
.festival-special-bg-img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.festival-special-mascot {
|
||||
z-index: 50;
|
||||
position: absolute;
|
||||
right: -36rpx;
|
||||
top: 70rpx;
|
||||
transform-origin: left bottom;
|
||||
transform: translateY(-100%);
|
||||
.festival-special-mascot-img {
|
||||
width: 180rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.festival-text-pic-div {
|
||||
width: 100%;
|
||||
.festival-text-img {
|
||||
width: 400rpx;
|
||||
}
|
||||
}
|
||||
.festival-bg-up-div {
|
||||
width: 580rpx;
|
||||
margin-bottom: -14rpx;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: calc(10rpx + 2%) 0;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
.max-div {
|
||||
width: 600rpx;
|
||||
border-radius: 38rpx;
|
||||
position: relative;
|
||||
padding: 26rpx;
|
||||
margin-top: var(--status-bar-height);
|
||||
margin-bottom: calc(180rpx + 15%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.close-div {
|
||||
// 基础样式:设置按钮大小、圆形、边框等
|
||||
margin-top: 50rpx;
|
||||
position: relative; // 为伪类定位做准备
|
||||
width: 62rpx; // 圆圈直径,可根据需求调整
|
||||
height: 62rpx;
|
||||
border: 4rpx solid #b9b9b9; // 圆圈边框
|
||||
border-radius: 50%; // 变成正圆
|
||||
box-sizing: border-box; // 保证边框不影响整体大小
|
||||
// 伪类通用样式:十字的基础线条
|
||||
&::before,
|
||||
&::after {
|
||||
content: ''; // 伪类必须有content
|
||||
position: absolute; // 绝对定位居中
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 24rpx; // 十字线条长度,可调整
|
||||
height: 4rpx; // 十字线条粗细,可调整
|
||||
background-color: #b9b9b9; // 十字颜色
|
||||
transform: translate(-50%, -50%); // 居中对齐
|
||||
}
|
||||
// 第一条线旋转45度
|
||||
&::before {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
// 第二条线旋转-45度,组合成×
|
||||
&::after {
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -188,6 +188,6 @@
|
||||
overflow: hidden;
|
||||
margin-top: var(--status-bar-height);
|
||||
margin-bottom: 100rpx;
|
||||
overflow: auto;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 奖励类型枚举(和接口相关的枚举也放一起)
|
||||
export enum RewardType {
|
||||
FIXED_POINT = '1', // 固定积分
|
||||
RANDOM_POINT = '2' // 随机积分
|
||||
}
|
||||
|
||||
// 定义抽奖动画状态枚举(码值)
|
||||
export enum AnimationStatus {
|
||||
// 未开始(初始状态)
|
||||
UNSTARTED = 'unstarted',
|
||||
// 动画进行中
|
||||
LOADING = 'loading',
|
||||
// 动画已完成
|
||||
COMPLETED = 'completed'
|
||||
}
|
||||
|
||||
// 节日奖励信息接口
|
||||
export interface HolidayInfo {
|
||||
holidayCode : string;
|
||||
rewardFlag : boolean;
|
||||
rewardType : RewardType;
|
||||
rewardPoints : number;
|
||||
holidayDesc : string;
|
||||
rangeEnd : number;
|
||||
rangeStart : number;
|
||||
obtainFlag : boolean;
|
||||
ossKey : string;
|
||||
ossAddr : string;
|
||||
backgroundOssKey : string;
|
||||
backgroundOssAddr : string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 节日弹窗关闭后的回调函数接口
|
||||
* @param params - 回调参数
|
||||
* @param params.status - 签到状态(false 表示关闭/未完成)
|
||||
*/
|
||||
export interface CompleteCallback {
|
||||
(params : {
|
||||
status : boolean;
|
||||
}) : void;
|
||||
}
|
||||
|
||||
|
||||
/** 抽取积分成功的回调类型 */
|
||||
export interface LotterySuccessCb {
|
||||
// val 为抽取到的积分,start积分的最小数,end为积分的最大值
|
||||
(data : { val : number; start : number; end : number }) : void;
|
||||
}
|
||||
|
||||
/** 抽取积分失败的回调类型 */
|
||||
export interface LotteryFailCb {
|
||||
// 失败回调接收错误信息
|
||||
(error : any) : void;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 需要随机抽取积分的节日,随机抽取积分的回调函数
|
||||
* @param params - 回调参数
|
||||
*/
|
||||
export interface StartLotteryCallback {
|
||||
(params : {
|
||||
holidayCode : string;
|
||||
successCb : LotterySuccessCb;
|
||||
failCb : LotteryFailCb;
|
||||
}) : void;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
queryCheckIn,
|
||||
addPointsByCheckIn
|
||||
addPointsByCheckIn,
|
||||
queryHolidayReward,
|
||||
obtainRewardRandom
|
||||
} from '@/api/pointsAndRank.js';
|
||||
import common from '@/common/common';
|
||||
import {
|
||||
@@ -11,6 +13,7 @@ import {
|
||||
} from 'vue';
|
||||
export default function useCheckIn() {
|
||||
const checkInRef = ref(null)
|
||||
const holidayRef = ref(null)
|
||||
// ========== 核心工具方法 ==========
|
||||
/**
|
||||
* 获取当前用户的签到存储key(区分不同用户)
|
||||
@@ -45,78 +48,96 @@ export default function useCheckIn() {
|
||||
PopupDay: ''
|
||||
};
|
||||
try {
|
||||
// 读取本地存储的签到信息(兼容JSON格式)
|
||||
// 读取本地存储的签到信息
|
||||
const rawData = common.getValue(key);
|
||||
if (!rawData) return {
|
||||
signDate: '',
|
||||
checkInDays: 0,
|
||||
popupDay: ''
|
||||
popupDay: '',
|
||||
rewardFlag: null,
|
||||
obtainFlag: null,
|
||||
};
|
||||
|
||||
// 兼容存储的是字符串或JSON对象的情况
|
||||
const signStatus = rawData;
|
||||
return {
|
||||
signDate: signStatus.signDate || '',
|
||||
checkInDays: signStatus.checkInDays || 0,
|
||||
popupDay: signStatus.popupDay || '',
|
||||
|
||||
rewardFlag: signStatus.rewardFlag || null,
|
||||
obtainFlag: signStatus.obtainFlag || null,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('读取本地签到状态失败', e);
|
||||
return {
|
||||
signDate: '',
|
||||
checkInDays: 0,
|
||||
popupDay: ''
|
||||
popupDay: '',
|
||||
rewardFlag: null,
|
||||
obtainFlag: null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新本地签到状态(支持按需更新字段)
|
||||
* @param {boolean|undefined} isSigned - 是否签到(可选,传入则更新)
|
||||
* @param {number|undefined} checkInDays - 签到天数(可选,传入则更新)
|
||||
* @param {string|undefined} popupDay - 弹窗关闭日期(可选,传入则更新)
|
||||
*/
|
||||
const setLocalSignStatus = (isSigned, checkInDays, popupDay) => {
|
||||
const key = getKey();
|
||||
if (!key) return;
|
||||
const setLocalSignStatus = (params) => {
|
||||
const {
|
||||
isSigned,
|
||||
checkInDays,
|
||||
popupDay,
|
||||
rewardFlag,
|
||||
obtainFlag
|
||||
} = params;
|
||||
const key = getKey();
|
||||
if (!key) return;
|
||||
|
||||
// 1. 读取本地原有数据(若无则初始化空对象)
|
||||
let oldSignStatus = {};
|
||||
try {
|
||||
oldSignStatus = common.getValue(key) || {}; // 兼容common.getValue返回null/undefined的情况
|
||||
} catch (e) {
|
||||
console.warn('读取原有签到状态失败,使用空对象初始化', e);
|
||||
oldSignStatus = {};
|
||||
}
|
||||
// 1. 读取本地原有数据(若无则初始化空对象)
|
||||
let oldSignStatus = {};
|
||||
try {
|
||||
oldSignStatus = common.getValue(key) || {}; // 兼容common.getValue返回null/undefined的情况
|
||||
} catch (e) {
|
||||
console.warn('读取原有签到状态失败,使用空对象初始化', e);
|
||||
oldSignStatus = {};
|
||||
}
|
||||
|
||||
// 2. 初始化新状态:先继承原有数据
|
||||
const newSignStatus = { ...oldSignStatus };
|
||||
// 2. 初始化新状态:先继承原有数据
|
||||
const newSignStatus = {
|
||||
...oldSignStatus
|
||||
};
|
||||
|
||||
// 3. 按需更新字段(仅当参数传入时覆盖)
|
||||
// 处理isSigned:传入则更新,且为true时自动设置signDate
|
||||
if (isSigned !== undefined) {
|
||||
// 保持原有逻辑:isSigned为true时,signDate设为今日
|
||||
newSignStatus.signDate = isSigned ? getTodayDate() : oldSignStatus.signDate;
|
||||
}
|
||||
// 3. 按需更新字段(仅当参数传入时覆盖)
|
||||
// 处理isSigned:传入则更新,且为true时自动设置signDate
|
||||
if (isSigned !== undefined) {
|
||||
// 保持原有逻辑:isSigned为true时,signDate设为今日
|
||||
newSignStatus.signDate = isSigned ? getTodayDate() : oldSignStatus.signDate;
|
||||
}
|
||||
|
||||
// 处理checkInDays:传入则更新,未传入保留原有值
|
||||
if (checkInDays !== undefined) {
|
||||
newSignStatus.checkInDays = checkInDays;
|
||||
}
|
||||
// 处理checkInDays:传入则更新,未传入保留原有值
|
||||
if (checkInDays !== undefined) {
|
||||
newSignStatus.checkInDays = checkInDays;
|
||||
}
|
||||
|
||||
// 处理popupDay:传入非空则更新,未传入/空值保留原有值
|
||||
if (popupDay !== undefined) {
|
||||
newSignStatus.popupDay = popupDay? getTodayDate() : oldSignStatus.popupDay;
|
||||
}
|
||||
// 处理popupDay:传入非空则更新,未传入/空值保留原有值
|
||||
if (popupDay !== undefined) {
|
||||
newSignStatus.popupDay = popupDay ? getTodayDate() : oldSignStatus.popupDay;
|
||||
}
|
||||
|
||||
// 4. 重新存入本地(覆盖原有数据)
|
||||
try {
|
||||
console.log('存入的数据(原有+更新)', newSignStatus);
|
||||
common.setValue(key, newSignStatus);
|
||||
} catch (e) {
|
||||
console.error('更新本地签到状态失败', e);
|
||||
}
|
||||
if (rewardFlag !== undefined) {
|
||||
newSignStatus.rewardFlag = rewardFlag;
|
||||
}
|
||||
if (obtainFlag !== undefined) {
|
||||
newSignStatus.obtainFlag = obtainFlag;
|
||||
}
|
||||
|
||||
|
||||
// 4. 重新存入本地
|
||||
try {
|
||||
console.log('存入的数据(原有+更新)', newSignStatus);
|
||||
common.setValue(key, newSignStatus);
|
||||
} catch (e) {
|
||||
console.error('更新本地签到状态失败', e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -129,32 +150,46 @@ export default function useCheckIn() {
|
||||
const initCheckInStatus = async (complete = () => {}) => {
|
||||
const today = getTodayDate();
|
||||
const localStatus = getLocalSignStatus();
|
||||
console.log('localStatus', localStatus);
|
||||
// 1. 本地已有今日签到记录 → 直接返回本地状态
|
||||
if (localStatus.signDate === today) {
|
||||
|
||||
complete(true, localStatus.checkInDays, localStatus.popupDay === today)
|
||||
const checkInInfo = {
|
||||
isSigned: true,
|
||||
checkInDays: localStatus.checkInDays,
|
||||
popUpStatus: localStatus.popupDay === today
|
||||
}
|
||||
const holidayBody = {
|
||||
rewardFlag: localStatus.rewardFlag,
|
||||
obtainFlag: localStatus.obtainFlag
|
||||
}
|
||||
complete(checkInInfo, holidayBody)
|
||||
return true;
|
||||
}
|
||||
// 2. 本地记录过期/无记录 → 请求接口获取最新状态
|
||||
try {
|
||||
const res = await queryCheckIn(); // 调用查询签到状态接口
|
||||
const {
|
||||
body
|
||||
} = res || {};
|
||||
console.log('bodbodyy', body);
|
||||
// // 接口返回格式适配(根据实际后端返回调整)
|
||||
|
||||
const [{
|
||||
body = {}
|
||||
}, {
|
||||
body: holidayBody = {}
|
||||
}] = await Promise.all([
|
||||
queryCheckIn(), // 第一个接口:查询签到状态
|
||||
queryHolidayReward() // 第二个接口:查询节日奖励
|
||||
]);
|
||||
|
||||
const isSigned = body?.checkInToday === 'Y';
|
||||
const checkInDays = body?.days || 0;
|
||||
setLocalSignStatus(isSigned, checkInDays);
|
||||
// if (!isSigned) { // 没签到执行签到弹窗
|
||||
// console.log('没签到执行签到弹窗');
|
||||
// //如果今日点击过x那么久不弹了
|
||||
// // if(localStatus.popupDay === today) return;
|
||||
// complete(isSigned, checkInDays, localStatus.popupDay === today)
|
||||
// }
|
||||
complete(isSigned, checkInDays, localStatus.popupDay === today)
|
||||
|
||||
setLocalSignStatus({
|
||||
isSigned,
|
||||
checkInDays,
|
||||
rewardFlag: holidayBody?.rewardFlag,
|
||||
obtainFlag: holidayBody?.obtainFlag,
|
||||
});
|
||||
const checkInInfo = {
|
||||
isSigned,
|
||||
checkInDays,
|
||||
popUpStatus: localStatus.popupDay === today
|
||||
}
|
||||
complete(checkInInfo, holidayBody)
|
||||
} catch (e) {
|
||||
console.error('请求签到状态接口失败', e);
|
||||
}
|
||||
@@ -181,14 +216,15 @@ export default function useCheckIn() {
|
||||
*/
|
||||
const checkInFun = async ({
|
||||
status,
|
||||
days=0,
|
||||
successCb=() =>{},
|
||||
failCb=() =>{}
|
||||
days = 0,
|
||||
successCb = () => {},
|
||||
failCb = () => {}
|
||||
}) => {
|
||||
if(!status) { // 点击了关闭
|
||||
if (!status) { // 点击了关闭
|
||||
console.log('点击了关闭');
|
||||
|
||||
setLocalSignStatus(undefined, undefined, true);
|
||||
setLocalSignStatus({
|
||||
popupDay: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log('请求接口', days);
|
||||
@@ -197,7 +233,10 @@ export default function useCheckIn() {
|
||||
})
|
||||
.then((res) => {
|
||||
console.log('接口成功回调', res);
|
||||
setLocalSignStatus(true, days + 1);
|
||||
setLocalSignStatus({
|
||||
isSigned: true,
|
||||
checkInDays: days + 1,
|
||||
});
|
||||
successCb?.({
|
||||
days: days + 1,
|
||||
res
|
||||
@@ -207,6 +246,30 @@ export default function useCheckIn() {
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* 执行抽取节日积分操作
|
||||
* @param {string} holidayCode - 节日积分
|
||||
* @param {Function} successCb - 抽取积分成功回调
|
||||
* @param {Function} failCb - 抽取积分失败回调
|
||||
* @returns {Promise<boolean>} 签到是否成功
|
||||
*/
|
||||
const startLotteryFun = async ({
|
||||
holidayCode,
|
||||
successCb = () => {},
|
||||
failCb = () => {}
|
||||
}) => {
|
||||
obtainRewardRandom({
|
||||
holidayCode
|
||||
}).then(({body}) => {
|
||||
console.log('抽取成功回调', body);
|
||||
// 把缓存中的记录设置为已经抽取积分
|
||||
setLocalSignStatus({ obtainFlag: true});
|
||||
successCb?.(body);
|
||||
}).catch((error) => {
|
||||
failCb?.(error)
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置签到状态(可选,用于特殊场景)
|
||||
*/
|
||||
@@ -217,9 +280,11 @@ export default function useCheckIn() {
|
||||
|
||||
// ========== 返回对外暴露的方法 ==========
|
||||
return {
|
||||
holidayRef,
|
||||
checkInRef,
|
||||
initCheckInStatus, // 初始化签到状态(页面加载时调用)
|
||||
checkInFun, // 执行签到
|
||||
startLotteryFun, // 抽取随机积分
|
||||
getLocalSignStatus, // 获取签到缓存信息
|
||||
getTodayCheckInStatus // 获取今天签到状态
|
||||
};
|
||||
|
||||
@@ -1,74 +1,99 @@
|
||||
<template>
|
||||
<view class="point-item mb-30" v-for="(item, index) in modelValue?.pgrphData || []" :key="item.index"
|
||||
@click="toDetail(item)" :class="item.pgrphStdyStateName == '已学习' ? 'learn' : 'unlearn'">
|
||||
<view class="top mb-30">
|
||||
<view class="">
|
||||
<view class="knowledge-tip-div">前序知识点学完后方可解锁下一知识点学习</view>
|
||||
<view
|
||||
class="point-item mb-30"
|
||||
v-for="(item, index) in modelValue?.pgrphData || []"
|
||||
:key="item.index"
|
||||
@click="toDetail(item)"
|
||||
:class="item.pgrphStdyStateName == '已学习' ? 'learn' : 'unlearn'"
|
||||
>
|
||||
<view class="blue-ball">
|
||||
<view class="ball"></view>
|
||||
<image :src="item.pgrphStdyStateName == '已学习'?knowledgeComplete:knowledgeUnfinished" class="img"></image>
|
||||
</view>
|
||||
<view class="fz-30 font_pf title">知识点{{ item.pgrphNm }}</view>
|
||||
<view class="fl1">
|
||||
|
||||
<view class="top mb-30">
|
||||
<view class="fz-30 font_pf title">知识点{{ item.pgrphNm }}</view>
|
||||
<view class="fl1"></view>
|
||||
</view>
|
||||
<view class="font_pf fz-24 study-status" >
|
||||
{{ item.pgrphStdyStateName }}
|
||||
<view class="bottom">
|
||||
<view class="fz-30 font_pf text ellipsis-text">{{ item.pgrphTitle || item.pgrphContent }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom">
|
||||
<view class="fz-30 font_pf text ellipsis-text">{{ item.pgrphTitle || item.pgrphContent }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed} from "vue"
|
||||
import { computed } from 'vue';
|
||||
import common from '@/common/common';
|
||||
const emit = defineEmits(['toStudyPrgh'])
|
||||
import knowledgeComplete from '@/static/images/courseDetail/knowledgeComplete.png'
|
||||
import knowledgeUnfinished from '@/static/images/courseDetail/knowledgeUnfinished.png'
|
||||
|
||||
const emit = defineEmits(['toStudyPrgh']);
|
||||
const props = defineProps({
|
||||
modelValue: Object
|
||||
})
|
||||
});
|
||||
const min_no_success_id = computed(() => {
|
||||
const find_item = props.modelValue?.pgrphData.find(res => (res.pgrphStdyStateName === '未学习' || res.pgrphStdyStateName === '学习中'))
|
||||
return find_item?.pgrphId || ''
|
||||
})
|
||||
const find_item = props.modelValue?.pgrphData.find(
|
||||
(res) => res.pgrphStdyStateName === '未学习' || res.pgrphStdyStateName === '学习中'
|
||||
);
|
||||
return find_item?.pgrphId || '';
|
||||
});
|
||||
|
||||
const toDetail = (item) => {
|
||||
if (item.pgrphStdyStateName === '已学习' || item.pgrphStdyStateName === '学习中' || min_no_success_id.value === item.pgrphId) {
|
||||
emit('toStudyPrgh', item)
|
||||
if (
|
||||
item.pgrphStdyStateName === '已学习' ||
|
||||
item.pgrphStdyStateName === '学习中' ||
|
||||
min_no_success_id.value === item.pgrphId
|
||||
) {
|
||||
emit('toStudyPrgh', item);
|
||||
} else {
|
||||
common.msg('请按顺序学习知识点')
|
||||
common.msg('请按顺序学习知识点');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.learn{
|
||||
.study-status{
|
||||
color: #1DD17A;
|
||||
}
|
||||
.title,.text{
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
}
|
||||
.unlearn{
|
||||
.study-status{
|
||||
color: #666666;
|
||||
}
|
||||
color: #666;
|
||||
}
|
||||
.point-item {
|
||||
background-color: #fff;
|
||||
// display: flex;
|
||||
.knowledge-tip-div {
|
||||
padding-top: 24rpx;
|
||||
padding-left: 36rpx;
|
||||
padding-right: 52rpx;
|
||||
padding-bottom: 24rpx;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #0066ff;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
background: #e4f2ff;
|
||||
border-radius: 22rpx;
|
||||
border: 4rpx solid #96c0ff;
|
||||
margin-bottom: 26rpx;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
.learn {
|
||||
.study-status {
|
||||
color: #1dd17a;
|
||||
}
|
||||
.title,
|
||||
.text {
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.unlearn {
|
||||
.study-status {
|
||||
color: #999999;
|
||||
}
|
||||
color: #999999;
|
||||
}
|
||||
.point-item {
|
||||
background-color: #fff;
|
||||
padding-top: 24rpx;
|
||||
padding-left: 88rpx;
|
||||
padding-right: 52rpx;
|
||||
padding-bottom: 36rpx;
|
||||
border-radius: 22rpx;
|
||||
|
||||
// justify-content: space-between;
|
||||
// margin-left: 16rpx;
|
||||
// margin-right: 16rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -77,16 +102,13 @@
|
||||
.bottom {
|
||||
padding-left: 12rpx;
|
||||
}
|
||||
|
||||
.blue-ball {
|
||||
padding-top: 16rpx;
|
||||
margin-right: 12rpx;
|
||||
|
||||
.ball {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
background-color: #0066ff;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
left: 0%;
|
||||
top: 0%;
|
||||
.img{
|
||||
width: 75rpx;
|
||||
height: 73rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,33 +24,36 @@
|
||||
<image
|
||||
class="title-icon"
|
||||
src="/src/static/images/courseDetail/zhishidian.png"
|
||||
mode="heightFix"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
<view class="mr-10">知识点</view>
|
||||
<view class="c-000">{{ form.pgrphNum || 0 }}</view>
|
||||
<view class="">知识点</view>
|
||||
</view>
|
||||
<view class="num-item">
|
||||
<image
|
||||
class="title-icon"
|
||||
src="/src/static/images/courseDetail/yixue.png"
|
||||
mode="heightFix"
|
||||
></image>
|
||||
<view class="mr-10">已学</view>
|
||||
<view class="c-000">{{ form.styParaGraphNum || 0 }}</view>
|
||||
|
||||
<view class="line-progress-div">
|
||||
<view class="text-1">{{ form.styParaGraphNum || 0 }}</view>
|
||||
<uv-line-progress
|
||||
:percentage="progress"
|
||||
:showText="false"
|
||||
activeColor="#0066FF"
|
||||
inactiveColor="#D9DEE8"
|
||||
:height="8"
|
||||
></uv-line-progress>
|
||||
<view class="text-2">{{ form.pgrphNum || 0 }}</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="num-item examination"
|
||||
@click="common.navigateTo(`/pages/examRanking/index?crsId=${crsId}`)"
|
||||
>
|
||||
<image
|
||||
class="title-icon"
|
||||
class="ranking-icon"
|
||||
src="/src/static/images/courseDetail/charts.png"
|
||||
mode="heightFix"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<view>考试排行榜</view>
|
||||
<view class="back-icon"></view>
|
||||
</view>
|
||||
<view class="fl1"></view>
|
||||
|
||||
<view class="num-item" @click="collect_click(form.collectId)">
|
||||
<image
|
||||
class="collect-icon"
|
||||
@@ -64,14 +67,15 @@
|
||||
<uv-sticky :customNavHeight="statusBarHeight" class="sticky">
|
||||
<view class="body-tab">
|
||||
<uv-tabs
|
||||
class="font_pf"
|
||||
class="font_pf my_tabs"
|
||||
:current="tabAct"
|
||||
:list="tabList"
|
||||
@change="tabSwitch"
|
||||
:scrollable="false"
|
||||
line-color="#0066FF"
|
||||
:active-style="{ color: '#0066FF', 'font-weight': 600, 'font-size': '30rpx' }"
|
||||
:inactive-style="{ color: '#666666', 'font-size': '30rpx' }"
|
||||
:inactive-style="{ color: '#333333', fontSize: '30rpx', fontWeight: '500' }"
|
||||
:activeStyle="{ color: '#0066FF', fontSize: '30rpx', fontWeight: '600' }"
|
||||
:lineColor="`url(${LineBg}) 10% 10%`"
|
||||
></uv-tabs>
|
||||
</view>
|
||||
</uv-sticky>
|
||||
@@ -110,6 +114,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { LineBg } from '@/enum.js';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { onShow, onLoad, onUnload } from '@dcloudio/uni-app';
|
||||
import { getCourseDetailApi } from '@/api/courseDetail.js';
|
||||
@@ -135,7 +140,13 @@
|
||||
const scrollTop = ref(0);
|
||||
const practiceButtonDisabled = computed(() => isPreview.value === 'preview' || form.value.examStat === '01');
|
||||
const examinationButtonDisabled = computed(() => isPreview.value === 'preview' || form.value.examStat !== '00');
|
||||
|
||||
const progress = computed(() => {
|
||||
// 计算进度百分比(保留两位小数)
|
||||
const total = form.value.pgrphNum;
|
||||
if (total === 0) return 0;
|
||||
const completed = form.value.styParaGraphNum;
|
||||
return Math.min(Math.round((completed / total) * 10000) / 100, 100);
|
||||
});
|
||||
const tabSwitch = (item) => {
|
||||
tabAct.value = item.index;
|
||||
};
|
||||
@@ -298,6 +309,16 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.my_tabs {
|
||||
// 解决这个圆角的图片,开穿透
|
||||
:deep(.uv-tabs__wrapper__nav__line) {
|
||||
height: 20rpx !important;
|
||||
left: 60rpx;
|
||||
background-size: 26rpx !important;
|
||||
background-repeat: no-repeat !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 1124新改样式 ↓ ↓ ↓ ↓ ↓ ↓
|
||||
.btn-box {
|
||||
:deep(.uv-button--disabled) {
|
||||
@@ -407,8 +428,14 @@
|
||||
|
||||
.title-icon {
|
||||
height: 30rpx;
|
||||
width: 30rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
.ranking-icon {
|
||||
height: 40rpx;
|
||||
width: 40rpx;
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
.collect-icon {
|
||||
width: 40rpx;
|
||||
@@ -419,6 +446,7 @@
|
||||
.title-num {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
|
||||
.examination {
|
||||
color: #0066ff;
|
||||
@@ -510,11 +538,26 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
margin-right: 26rpx;
|
||||
margin-right: 16rpx;
|
||||
color: #666666;
|
||||
height: 30rpx;
|
||||
}
|
||||
|
||||
.line-progress-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
margin-right: 20rpx;
|
||||
.text-1 {
|
||||
color: #0066ff;
|
||||
font-size: 30rpx;
|
||||
margin: 0 16rpx;
|
||||
}
|
||||
.text-2 {
|
||||
color: #999999;
|
||||
font-size: 30rpx;
|
||||
margin: 0 16rpx;
|
||||
}
|
||||
}
|
||||
.learn {
|
||||
color: #1dd17a;
|
||||
}
|
||||
@@ -527,4 +570,11 @@
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
}
|
||||
.c-000 {
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
}
|
||||
.c-0066FF {
|
||||
color: #0066ff;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -156,43 +156,13 @@
|
||||
<classScroll :ref="(el) => (classScrollRef[0] = el)" name="hot_class"></classScroll>
|
||||
<classScroll :ref="(el) => (classScrollRef[1] = el)" name="recommend_class"></classScroll>
|
||||
<classScroll :ref="(el) => (classScrollRef[2] = el)" name="new_class"></classScroll>
|
||||
|
||||
<!-- <view class="hot_class_div">
|
||||
<scroll-view class="scroll-view" :scroll-x="true" :show-scrollbar="false">
|
||||
<view class="scroll-view-item" v-for="(item, index) in hot_class_list" :key="index">
|
||||
<view class="item_div">
|
||||
<uv-skeleton :loading="!item.crsNum" :rows="3" avatar avatar-shape="square" :title="false">
|
||||
<view class="item" @click="goto_course_detail(item.crsId)">
|
||||
<view class="left">
|
||||
<image-preview
|
||||
class="img-box img"
|
||||
:imgId="item.imgId"
|
||||
:isCache="true"
|
||||
:width="240"
|
||||
:height="240"
|
||||
></image-preview>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="title ellipsis-text">{{ item.crsName }}</view>
|
||||
<view class="note ellipsis-text">更新:{{ (item.mtTime || '').substr(0, 10) }}</view>
|
||||
<view class="button_div">
|
||||
<view class="note ellipsis-text">已学:{{ item.accmStdyCnt }}人次</view>
|
||||
<view class="button">去学习</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uv-skeleton>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view> -->
|
||||
|
||||
<view class="bottom_line">
|
||||
<uv-divider text="我是有底线的" lineColor="#9cB0DC" :hairline="true"></uv-divider>
|
||||
</view>
|
||||
</view>
|
||||
<tabbar-shadow></tabbar-shadow>
|
||||
<checkIn ref="checkInRef"></checkIn>
|
||||
<holiday ref="holidayRef"></holiday>
|
||||
<badge ref="badgeRef"></badge>
|
||||
<points ref="pointsRef"></points>
|
||||
</view>
|
||||
@@ -210,10 +180,11 @@
|
||||
import classScroll from './components/class_scroll.vue';
|
||||
import useCheckIn from '@/composables/useCheckIn';
|
||||
import checkIn from '@/components/points-and-badge/check-in';
|
||||
import holiday from '@/components/points-and-badge/holiday';
|
||||
import badge from '@/components/points-and-badge/badge';
|
||||
import points from '@/components/points-and-badge/points';
|
||||
import usePointsAndBadge from '@/components/points-and-badge/usePointsAndBadge';
|
||||
const { initCheckInStatus, checkInRef, checkInFun } = useCheckIn();
|
||||
const { initCheckInStatus, checkInRef, checkInFun, startLotteryFun, holidayRef } = useCheckIn();
|
||||
const { handlePointsBadgePopup, badgeRef, pointsRef } = usePointsAndBadge();
|
||||
const scrollTop = ref(0);
|
||||
const no_read_message_num = ref(0);
|
||||
@@ -339,12 +310,19 @@
|
||||
return;
|
||||
}
|
||||
|
||||
initCheckInStatus((isSigned, checkInDays, popUpStatus) => {
|
||||
if(!isSigned && !popUpStatus) {
|
||||
checkInRef.value.open(isSigned, checkInDays, checkInFun, (status, days, pointsList = [], badgesList = []) => {
|
||||
handlePointsBadgePopup(pointsList, badgesList);
|
||||
});
|
||||
}
|
||||
initCheckInStatus((checkInInfo, holidayBody) => {
|
||||
// 先判断是否有节日活动
|
||||
const handleCheckInComplete = (params) => {
|
||||
const {isSigned, checkInDays, popUpStatus} = checkInInfo
|
||||
setTimeout(() => {
|
||||
if(!isSigned && !popUpStatus) {
|
||||
checkInRef.value.open(isSigned, checkInDays, checkInFun, (status, days, pointsList = [], badgesList = []) => {
|
||||
handlePointsBadgePopup(pointsList, badgesList);
|
||||
});
|
||||
}
|
||||
}, 140)
|
||||
};
|
||||
holidayRef.value?.open(holidayBody, startLotteryFun, handleCheckInComplete)
|
||||
});
|
||||
const userInfo = getUserInfo();
|
||||
if (userInfo) {
|
||||
|
||||
@@ -314,10 +314,13 @@
|
||||
Object.assign(user_info, getUserInfo());
|
||||
statistics_data['get_statistics_data'](); // 获取统计数据
|
||||
// 获取今日是否签到消息
|
||||
initCheckInStatus((isSigned, checkInDays, popUpStatus) => {
|
||||
initCheckInStatus(({isSigned, checkInDays, popUpStatus}) => {
|
||||
user_info.checkInToday = isSigned ? 1 : 2;
|
||||
user_info.checkInDays = checkInDays;
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
onHide(() => {
|
||||
checkInRef.value && checkInRef.value.close();
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 416 B After Width: | Height: | Size: 387 B |
|
Before Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 746 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 40 KiB |