This commit is contained in:
田岩
2025-11-04 09:35:41 +08:00
parent df1004edca
commit a35d0af64a
10 changed files with 542 additions and 61 deletions
+1 -1
View File
@@ -29,6 +29,6 @@ VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.78:9786'
#VITE_APP_BASE_H5_API_Url_TRAAPP = 'http://192.168.247.200'
# VITE_APP_BASE_H5_API_Url_TRASTUDY = 'http://25.64.32.154:9602'
#VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://192.168.247.200'
VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://192.168.247.200'
# VITE_APP_BASE_H5_API_Url_TRAASK = 'http://25.64.32.154:9605'
+3 -2
View File
@@ -31,9 +31,10 @@ Hb修改流程:
3.在HBuilder-Hello-Info.plist添加dcloud_appkey项,值为caf112ffeb8174f764076b6b109aa329
4.
25.64.16.140
25.64.32.152
255.255.255.0
25.64.16.254
25.64.32.254
http://25.13.9.101:9000/
+21
View File
@@ -101,3 +101,24 @@ export const queryExamHisByExamId = (data) => {
};
/**竞赛*/
// 查询竞赛记录
export const queryTraCompetitionRecordPaging = (data) => {
return request({
url: base_url + '/traCompetitionInfo/queryTraCompetitionRecordPaging',
method: 'post',
toastErrors: true,
data
});
};
// 查询竞赛答题列表
export const queryTraCompetitionRecordInfoPaging = (data) => {
return request({
url: base_url + '/traCompetitionInfo/queryTraCompetitionRecordInfoPaging',
method: 'post',
toastErrors: true,
data
});
};
+3 -3
View File
@@ -28,8 +28,8 @@ export const useAccountLoginApp = (data) => request({
}) => {
if (rtnCode === '0000') {
setToken(body)
const res = await Promise.all([queryCurrentUser(), queryCurrentValidMascotInfo()
// queryCurrentStatus()
const res = await Promise.all([queryCurrentUser(), queryCurrentValidMascotInfo(),
queryCurrentStatus()
])
const userInfo = getUserInfo()
const mascotInfo = res[1]
@@ -44,7 +44,7 @@ export const useAccountLoginApp = (data) => request({
badgeNum: pointsStatus['badgeNum'] ?? 0, // 徽章数
currentPoints: pointsStatus['currentPoints'] ?? 0, // 当前积分
currentRankAddr: pointsStatus['ossAddr'] ?? '', // 当前段位图片
currentRankName: pointsStatus['currentRankName']?? '未知', // 当前段位名称
currentRankName: pointsStatus['currentRankName'] ?? '未知', // 当前段位名称
})
return {
mascotState: !mascotInfo.mascotId
+64 -40
View File
@@ -21,13 +21,12 @@
</text>
</view>
<scroll-view scroll-y="true" class="scroll-Y" :show-scrollbar="false">
<view class="calendar-div" :class="{expand:expand}">
<view class="calendar-div" :class="{ expand: expand }">
<view
class="calendar-line"
v-for="(line, lineIndex) in dateGroups"
v-show="nowLine === lineIndex || expand"
>
<view
class="calendar-day"
v-for="(day, dayIndex) in line.days"
@@ -57,10 +56,13 @@
<view class="button-div">
<uv-button
class="button"
text="签到"
:text="isSigned ? '已签到' : '签到'"
loadingText="签到中"
:custom-style="customStyle"
:color="buttonColor"
@click="expand = !expand"
@click="checkIn"
:disabled="isSigned"
:loading="checkInLoading"
></uv-button>
</view>
</view>
@@ -73,7 +75,8 @@
<script setup>
import { ref, computed } from 'vue';
import { optionErrorIcon, optionSuccessIcon } from '@/common/imgSvg';
import { addPointsByCheckIn, queryCheckIn } from '@/api/pointsAndRank.js';
import common from '@/common/common';
const popup = ref(null);
const buttonColor = 'linear-gradient( 270deg, #E0914A 0%, #EDBE7F 100%)';
const customStyle = {
@@ -82,17 +85,17 @@
const dateGroups = ref([]);
const expand = ref(false); // 展开状态
const isSigned = ref(false); // 今日签到状态
const continuousDay = ref(6); // 连续签到
const continuousDay = ref(0); // 连续签到
// 判断一下当前的签到显示在第几周
const nowLine = computed(() => {
// 计算当前需要显示图标的天数(已签到则是连续天数,未签到则是下一天)
const currentDay = isSigned.value ? continuousDay.value : continuousDay.value + 1;
// 按每周7天分行,行数从0开始(例如:1-7天 → 第0行,8-14天 → 第1行...
// 注意:如果天数从1开始(不是0),需要减1再计算
return Math.floor((currentDay - 1) / 7);
// 计算当前需要显示图标的天数(已签到则是连续天数,未签到则是下一天)
const currentDay = isSigned.value ? continuousDay.value : continuousDay.value + 1;
// 按每周7天分行,行数从0开始(例如:1-7天 → 第0行,8-14天 → 第1行...
// 注意:如果天数从1开始(不是0),需要减1再计算
return Math.floor((currentDay - 1) / 7);
});
const emits = defineEmits(['checkInFun'])
// 切换显示状态
const switchStatus = () => {
expand.value = !expand.value;
@@ -101,6 +104,26 @@
const close = () => {
popup.value.close();
};
const checkInLoading = ref(false);
// 签到
const checkIn = () => {
if (checkInLoading.value) return;
checkInLoading.value = true;
addPointsByCheckIn({days:6})
.then((res) => {
console.log('签到结果', res);
checkInLoading.value = false;
isSigned.value = true;
continuousDay.value = continuousDay.value + 1;
common.msg('签到成功,请明日继续!')
emits('checkInFun', true)
})
.catch((err) => {
checkInLoading.value = false;
common.msg('签到失败,请稍后再试');
});
};
/**
* 生成35天日期列表(分5组,每组7天),包含类型标记和text字段
* 以"前X天"为第一天,第1天显示1,每7天显示5,第30天显示30
@@ -172,31 +195,33 @@
return groupedList;
}
// 示例:X=10时,以"10天前"为第1天,生成35天列表
try {
const X = continuousDay.value - (isSigned.value ? 1 : 0);
// const X = 10;
console.log(`${continuousDay.value}`);
console.log(`${X}天前为第1天的35天分组日期:`);
dateGroups.value = generateGroupedDateList(X);
dateGroups.value.forEach((group) => {
console.log(`\n第${group.groupIndex + 1}`);
console.log(
group.days.map((day) => ({
日期: day.date,
天数序号: day.index, // 显示当前是第几天
text: day.text,
类型: day.type
}))
);
});
} catch (err) {
console.error(err.message);
}
const open = () => {
const open = (checkInToday,days) => {
isSigned.value = checkInToday==='Y'?true:false// 今日签到状态
// isSigned.value = false// 今日签到状态
continuousDay.value = days // 连续签到
// 示例:X=10时,以"10天前"为第1天,生成35天列表
try {
const X = continuousDay.value - (isSigned.value ? 1 : 0);
// const X = 10;
console.log(`${continuousDay.value}`);
console.log(`${X}天前为第1天的35天分组日期`);
dateGroups.value = generateGroupedDateList(X);
// dateGroups.value.forEach((group) => {
// console.log(`\n第${group.groupIndex + 1}组:`);
// console.log(
// group.days.map((day) => ({
// 日期: day.date,
// 天数序号: day.index, // 显示当前是第几天
// text: day.text,
// 类型: day.type
// }))
// );
// });
} catch (err) {
console.error(err.message);
}
popup.value.open();
};
defineExpose({ open, close });
@@ -217,8 +242,7 @@
display: none;
}
}
.calendar-day-bg {
width: 70rpx;
height: 84rpx;
+9
View File
@@ -384,6 +384,15 @@
}
}
},
{
"path": "pages/competitionRecord/index",
"style": {
"navigationBarTitleText": "竞赛记录",
"app-plus": {
"titleNView": false
}
}
},
{
"path": "pages/course/index",
"style": {
@@ -0,0 +1,249 @@
<template>
<view class="item">
<view class="details_div">
<view class="right_div">
<view class="title ellipsis-text">
{{ item.examName }}
</view>
<view class="prompt_div">
<view class="prompt_div_left">
<view class="top">{{ item.flagQuery === 'Y' ? item.highestGrade : '--' || 0 }}</view>
<view class="bottom">历史最高分</view>
</view>
<view class="prompt_div_line"></view>
<view class="prompt_div_right" @click="show_list_click">
<view class="click_text">
{{ item.examCnt || 0 }}
<uv-icon
class="click_text_icon"
:class="{ click_text_icon_action: show_list_state }"
name="arrow-right"
color="#0066FF"
size="12"
:bold="true"
></uv-icon>
</view>
<view class="bottom">考试记录</view>
</view>
</view>
</view>
</view>
<view class="list_div" :style="{ height: list_div_height }">
<view class="list_item" v-for="item_list in data_list" @click="click_list_item(item_list)">
<view class="list_content">
<view class="list_small_item">
<image src="@/static/images/courseRecord/time.png" class="icon_img"></image>
<view class="prompt">考试时间{{ item_list.examStartTm }}</view>
</view>
<view class="list_small_item">
<image src="@/static/images/courseRecord/duration.png" class="icon_img"></image>
<view class="prompt">考试得分{{ item_list.flagQuery === 'Y' ? item_list.examGrade : '--' }}</view>
</view>
</view>
<view class="fl1"></view>
<uv-icon name="arrow-right" color="#979797" size="17" :bold="true"></uv-icon>
</view>
<uv-load-more
v-if="status !== 'nomore'"
@click="getData()"
:status="status"
loadmore-text="点击加载更多"
:height="30"
></uv-load-more>
</view>
</view>
</template>
<script setup>
import { computed, ref, reactive } from 'vue';
import common from '@/common/common';
import { useItemList } from '../../courseRecord/useItemList.js';
import { queryTraCompetitionRecordInfoPaging } from '@/api/courseRecord.js';
const props = defineProps({
item: {
type: Object,
default: () => {}
}
});
const item = computed(() => props.item);
const fetchFunction = (params) => queryTraCompetitionRecordInfoPaging({ examId: item.value.examId, ...params });
const { status, data_list, show_list_state, list_div_height, show_list_click, getData, clear } = useItemList(fetchFunction);
// 跳转到详情
const click_list_item = (list_item) => {
const papersName = item.value.examName;
const flagQueryDetail = list_item.flagQueryDetail === '' ? list_item.flagQuery : list_item.flagQueryDetail;
common.navigateTo(
`/pages/examination/result?execId=${list_item.execId}&papersName=${papersName}&mode=record&flagQuery=${list_item.flagQuery}&flagQueryDetail=${flagQueryDetail}`
);
};
defineExpose({clear})
</script>
<style scoped lang="scss">
.icon_img {
width: 24rpx;
height: 24rpx;
}
.list_div {
overflow: hidden; /* 确保内容不会溢出容器 */
transition: height 0.2s ease-out; /* 高度动画 */
.list_item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18rpx 32rpx 18rpx 28rpx;
background-color: #f9fbff;
border-radius: 8rpx;
margin: 20rpx 0 0 0;
.list_content {
.list_small_item {
display: flex;
align-items: center;
font-weight: 400;
font-size: 24rpx;
color: #666666;
line-height: 40rpx;
text-align: left;
font-style: normal;
.prompt {
margin-left: 16rpx;
}
}
}
}
}
.item {
margin: 17rpx 20rpx;
background-color: #fff;
border-radius: 16rpx;
padding: 32rpx 22rpx;
.details_div {
display: flex;
.img_div {
position: relative;
width: 226rpx;
height: 168rpx;
overflow: hidden;
border-radius: 22rpx;
.img {
width: 226rpx;
height: 168rpx;
}
.subscript {
position: absolute;
right: 0;
top: 0;
width: 92rpx;
height: 46rpx;
border-radius: 0px 0px 0px 22rpx;
font-family: PingFangSC;
font-weight: 400;
font-size: 22rpx;
line-height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
}
.right_div {
flex: 1;
overflow: hidden;
margin-left: 24rpx;
display: flex;
flex-direction: column;
.title {
font-family: PingFangSC;
color: #333333;
text-align: left;
font-style: normal;
font-weight: 600;
font-size: 28rpx; /* 字体大小30rpx */
overflow: hidden; /* 超出部分隐藏 */
margin-bottom: 8rpx;
min-height: 40rpx;
}
.prompt_div {
font-family: PingFangSC;
font-weight: 400;
font-size: 22rpx;
color: #666;
line-height: 34rpx;
display: flex;
align-items: center;
overflow: hidden;
height: 116rpx;
background-color: #f9fbff;
border-radius: 8rpx;
justify-content: space-around;
.prompt_div_line {
width: 4rpx;
height: 30rpx;
background: linear-gradient(
to bottom,
RGBA(242, 244, 248, 1) 0%,
RGBA(230, 232, 238, 1) 30%,
RGBA(230, 232, 238, 1) 70%,
RGBA(242, 244, 248, 1) 100%
);
}
.prompt_div_left,
.prompt_div_right {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.top,
.click_text {
font-family: Arial;
font-weight: 700;
font-size: 30rpx;
color: #333333;
line-height: 34rpx;
text-align: center;
font-style: normal;
}
.bottom {
}
.click_text {
position: relative;
.click_text_icon {
position: absolute;
margin-left: 18rpx;
top: calc(50% - 12rpx);
left: calc(100% - 14rpx);
/* 添加过渡动画,包含延迟效果 */
transition: transform 0.2s ease;
}
.click_text_icon_action {
transform: rotate(90deg);
}
}
}
}
.people_progress_div {
display: flex;
font-family: PingFangSC;
font-weight: 400;
font-size: 23rpx;
color: #666;
margin-bottom: 23rpx;
line-height: 23rpx;
flex-wrap: nowrap;
height: 23rpx;
overflow: hidden;
}
}
}
}
</style>
+138
View File
@@ -0,0 +1,138 @@
<template>
<z-paging
ref="pagingRef"
v-model="data_list"
@query="queryList"
class="scroll_div"
empty-view-text="暂无记录"
@refresherTouchend="refresherTouchend"
>
<template #top>
<view class="detail-main">
<nav-bar :is_seat="false"></nav-bar>
<view class="fixed_search_div">
<view class="back_div" @click="common.navigateBack()">
<view class="back-icon"></view>
</view>
<view class="title font_pf">竞赛记录</view>
</view>
</view>
</template>
<view class="scroll-Y">
<itemVue ref="itemVueRef" :item="item" v-for="item in data_list"></itemVue>
</view>
</z-paging>
</template>
<script setup>
import { ref } from 'vue';
import itemVue from './components/competitionRecordItem.vue';
import { queryTraCompetitionRecordPaging } from '@/api/courseRecord.js';
import useZpaging from '@/composables/useZpaging.js';
import common from '@/common/common.js';
const itemVueRef = ref([]);
const { pagingRef, queryList, data_list, reload, search, total, clear } = useZpaging({
fetchFunction: queryTraCompetitionRecordPaging
});
const clearAll = () => {
itemVueRef.value.forEach((child) => {
if (child && typeof child.clear === 'function') {
child.clear();
}
});
};
const refresherTouchend = () => {
clearAll();
};
</script>
<style lang="scss" scoped>
$bg-margin-left: 50rpx;
.detail-body {
background: #ffffff;
border-radius: 15rpx;
z-index: 101;
overflow: hidden;
.body-tab {
height: 88rpx;
}
}
.detail-main {
background-color: #f6f8ff;
overflow: hidden;
}
.title {
color: #000000;
font-size: 34rpx;
height: 34rpx;
font-weight: 600;
line-height: 34rpx;
}
.fixed_search_input_div {
width: 100%;
height: 80rpx;
padding: 0 22rpx;
// background-color: red;
background-color: #fff;
:deep(.uv-input.uv-input--radius) {
border-radius: 16rpx !important;
}
}
.fixed_search_div {
padding: calc(20rpx + var(--status-bar-height)) 15rpx 34rpx 30rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
background-color: #fff;
// background-color: red;
// height: 180rpx;
position: relative;
.back_div {
position: absolute;
left: 20rpx;
top: var(--status-bar-height);
padding: 10rpx;
.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 #000000;
border-left: 4rpx solid #000000;
transform: rotate(-45deg);
}
}
}
.scroll-Y {
background-color: #f1f5fa;
border-radius: 16rpx;
// padding: 17rpx 12rpx 0 22rpx;
// margin: 17rpx 20rpx;
}
.scroll_div {
background-color: #f1f5fa;
// padding: 22rpx;
}
</style>
+45 -15
View File
@@ -48,7 +48,8 @@
<view class="work_number_div">工号{{ user_info.loginName }}</view>
<view class="fl1"></view>
</view>
<view class="sign-in-div" @click="checkInFun">签到</view>
<view class="sign-in-div" v-if="user_info.checkInToday==='N'" @click="checkInClick">签到</view>
<view class="already-sign-in-div" v-if="user_info.checkInToday==='Y'" @click="checkInClick">已签</view>
</view>
<!-- 用户学习时间信息 -->
<view class="study_information">
@@ -96,13 +97,13 @@
</view>
</view>
<view class="scroll-view-item">
<view class="item">
<view class="item" @click="goTest()">
<image class="icon" src="@/static/images/me/report.png"></image>
<view class="title">AI陪练记录</view>
</view>
</view>
<view class="scroll-view-item">
<view class="item" @click="goTest()">
<view class="item" @click="common.navigateTo('/pages/competitionRecord/index')">
<image class="icon" src="@/static/images/me/examination.png"></image>
<view class="title">竞赛记录</view>
</view>
@@ -145,9 +146,7 @@
></uv-cell>
</uv-cell-group>
</view>
<view style="height: 86rpx;">
</view>
<view style="height: 86rpx"></view>
<tabbar-shadow></tabbar-shadow>
<avatar-cropper
v-if="avatarCropperStatus"
@@ -159,7 +158,7 @@
@cancel="avatarOnCancel"
@confirm="avatarOnConfirm"
></avatar-cropper>
<checkIn ref="checkInRef"></checkIn>
<checkIn ref="checkInRef" @checkInFun="checkInFun"></checkIn>
</view>
</template>
@@ -178,7 +177,7 @@
import { queryCurrentUser } from '@/api/login.js';
import { defaultAvatar } from '@/enum.js';
import useUpdateAvatar from '@/composables/useUpdateAvatar';
import { queryCurrentStatus } from '@/api/pointsAndRank.js';
import { queryCurrentStatus, queryCheckIn } from '@/api/pointsAndRank.js';
import checkIn from '@/components/points-and-badge/check-in';
const { avatarCropperUrl, click_update_avatar, avatarCropperStatus, avatarOnCancel, avatarOnConfirm } = useUpdateAvatar({
success: (userData) => {
@@ -218,25 +217,32 @@
}
];
const currentIndex = ref(0);
const socketStore = useSocketStore();
const badgeNumRef = ref(null);
const checkInRef = ref(null);
const currentPointsRef = ref(null);
const user_info = reactive({
userName: '',
userId: '',
imageAddr: '',
loginName: ''
loginName: '',
checkInToday: '', // 今日是否签到
checkInDays: 0 // 连续签到天数
});
const goTest = () => {
console.log('goTest');
// common.navigateTo('/pages/test/index');
};
const checkInFun = () => {
checkInRef.value.open();
// 签到打开
const checkInClick = () => {
checkInRef.value.open(user_info.checkInToday, user_info.checkInDays);
};
// 签到成功回调
const checkInFun = (status) => {
if(status) {
getCheckInInfo()
}
};
const click_loginout = () => {
common.show('确定退出登录?').then(async (res) => {
@@ -257,7 +263,6 @@
currentPoints: 0, // 当前积分
currentRankAddr: '', // 当前段位名称
currentRankName: '', // 当前段位名称
startBadgeNum: 0, // 起始 徽章数
startCurrentPoints: 0, // 起始 积分
request_time: 0, // 上次请求时间
@@ -315,6 +320,15 @@
}
}
});
// 获取今日是否签到消息
const getCheckInInfo = () => {
// todo 后续加防止重复多次请求逻辑
queryCheckIn().then(({ body }) => {
user_info.checkInToday = body.checkInToday; // 今日是否签到
user_info.checkInDays = body.days; // 连续签到天数
});
};
onLoad(() => {
statistics_data['init'](); // 初始化数据
});
@@ -322,6 +336,8 @@
setMascot();
Object.assign(user_info, getUserInfo());
statistics_data['get_statistics_data'](); // 获取统计数据
// 获取今日是否签到消息
getCheckInInfo();
});
onHide(() => {
checkInRef.value && checkInRef.value.close();
@@ -498,6 +514,20 @@
background-repeat: no-repeat; /* 禁止重复平铺 */
background-position: center; /* 图片居中显示 */
}
.already-sign-in-div {
margin-top: 10rpx;
width: 102rpx;
height: 48rpx;
font-weight: 500;
font-size: 26rpx;
line-height: 46rpx;
text-align: center;
color: #ffffff;
background-image: url('@/static/images/me/sign-in-button-bg2.png');
background-size: 100%; /* 图片自适应元素大小(避免拉伸变形) */
background-repeat: no-repeat; /* 禁止重复平铺 */
background-position: center; /* 图片居中显示 */
}
}
.study_information {
+9
View File
@@ -319,6 +319,15 @@
"titleNView": false
}
}
},
{
"path": "pages/competitionRecord/index",
"style": {
"navigationBarTitleText": "竞赛记录",
"app-plus": {
"titleNView": false
}
}
}
]
}