Files
tra-app/src/pages/me/index.vue
T

592 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="main_div max_page">
<nav-bar :is_seat="false"></nav-bar>
<view class="bg_div">
<image src="/static/images/me/bg.png" class="bg_img"></image>
<view class="user_data_div">
<!-- 用户个人信息 -->
<view class="personal_information">
<view class="profile" @click="click_update_avatar()">
<image class="img_100" v-if="user_info.imagePath" :src="user_info.imagePath"></image>
<image-preview
v-else-if="user_info.imageAddr"
class="img_100"
:src="user_info.imageAddr"
></image-preview>
<image class="img_100" :src="defaultAvatar"></image>
</view>
<view class="personal_information_data">
<view class="name_sex_div">
<view class="name" @click="gotoTestPage">
{{ user_info.userName }}
</view>
<view class="sex">
<image
v-if="user_info.gender === 'f'"
class="img_100"
src="/static/images/me/wuman.png"
></image>
<image
v-if="user_info.gender === 'm'"
class="img_100"
src="/static/images/me/man.png"
></image>
</view>
<!-- 显示当前选中的徽章 -->
<view class="dan" @click="common.navigateTo('/pages/pointsAndRank/rank')">
<view class="dan_img">
<image-preview
v-if="statistics_data['currentRankAddr']"
class="img_100"
:src="statistics_data['currentRankAddr']"
mode="heightFix"
alt="当前徽章"
></image-preview>
<image v-else src="@/static/images/me/default_rank.png" class="img_100" mode="heightFix"/>
</view>
</view>
<view class="fl1"></view>
</view>
<view class="work_number_div">工号{{ user_info.loginName }}</view>
<view class="fl1"></view>
</view>
<view class="sign-in-div" @click="checkInClick" v-if="user_info.checkInToday === 2">签到</view>
<view class="already-sign-in-div" v-if="user_info.checkInToday === 1" @click="checkInClick">已签</view>
</view>
<!-- 用户学习时间信息 -->
<view class="study_information">
<view class="item" @click="common.navigateTo('/pages/pointsAndRank/points')">
<view class="top">我的积分</view>
<view class="bottom">
<uv-count-to
:startVal="statistics_data['startCurrentPoints']"
:endVal="statistics_data['currentPoints']"
:useEasing="true"
ref="currentPointsRef"
></uv-count-to>
</view>
</view>
<view class="item-interval"></view>
<view class="item" @click="common.navigateTo('/pages/pointsAndRank/badge')">
<view class="top">徽章墙</view>
<view class="bottom">
<uv-count-to
:startVal="statistics_data['startBadgeNum']"
:endVal="statistics_data['badgeNum']"
:useEasing="true"
ref="badgeNumRef"
></uv-count-to>
</view>
</view>
</view>
</view>
</view>
<!-- 4个导航 -->
<view class="navigation_div">
<!-- <uv-scroll-list class="scroll-view"> -->
<!-- <scroll-view scroll-x="true" :show-scrollbar="false"> -->
<view class="scroll-view-item">
<view class="item" @click="common.navigateTo('/pages/courseRecord/index')">
<image class="icon" src="@/static/images/me/study.png"></image>
<view class="title">课程记录</view>
</view>
</view>
<view class="scroll-view-item" @click="common.navigateTo('/pages/examinationRecord/index')">
<view class="item">
<image class="icon" src="@/static/images/me/todo.png"></image>
<view class="title">考试记录</view>
</view>
</view>
<view class="scroll-view-item">
<view class="item" @click="common.navigateTo('/pages/traRecord/index')">
<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="common.navigateTo('/pages/competitionRecord/index')">
<image class="icon" src="@/static/images/me/examination.png"></image>
<view class="title">竞赛记录</view>
</view>
</view>
<!-- </uv-scroll-list> -->
</view>
<view class="cell_div">
<uv-cell-group :border="false">
<uv-cell
title="问答演示"
v-if="user_info.loginName === 'ADMIN'"
:isLink="true"
@click="common.navigateTo('/pages/example/dialog')"
></uv-cell>
<uv-cell
title="消息通知"
:isLink="true"
@click="common.navigateTo('/pages/messageNotification/index')"
></uv-cell>
<uv-cell title="我的收藏" :isLink="true" @click="common.navigateTo('/pages/favorites/index')"></uv-cell>
<uv-cell title="排行榜" :isLink="true" @click="common.navigateTo('/pages/charts/index')"></uv-cell>
<uv-cell
title="我的预览"
:isLink="true"
:clickable="false"
:border="false"
@click="common.navigateTo('/pages/preview/index')"
></uv-cell>
</uv-cell-group>
</view>
<view class="cell_div">
<uv-cell-group :border="false">
<uv-cell title="设置" :isLink="true" @click="common.navigateTo('/pages/setting/index')"></uv-cell>
<uv-cell
title="退出登录"
:isLink="true"
:clickable="false"
:border="false"
@click="click_loginout()"
></uv-cell>
</uv-cell-group>
</view>
<view style="height: 86rpx"></view>
<tabbar-shadow></tabbar-shadow>
<avatar-cropper
v-if="avatarCropperStatus"
mode="ratio"
:imageUrl="avatarCropperUrl"
:width="500"
:height="500"
:delay="150"
@cancel="avatarOnCancel"
@confirm="avatarOnConfirm"
></avatar-cropper>
<checkIn ref="checkInRef"></checkIn>
<badge ref="badgeRef"></badge>
<points ref="pointsRef"></points>
</view>
</template>
<script setup>
import { ref, computed, reactive, nextTick } from 'vue';
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app';
import common from '@/common/common';
import { getUserInfo } from '@/common/common';
import { onPullDownRefresh } from '@dcloudio/uni-app';
import { loginOut } from '@/api/login.js';
import { queryTraStdyStatisticsSelf } from '@/api/user.js';
import { useSocketStore } from '@/store/socket.js';
import { updateDfSysUserExtandInfoImageAddr } from '@/api/user.js';
import { uploadFile } from '@/api/common';
import { setMascot } from '@/common/mascot.js';
import { queryCurrentUser } from '@/api/login.js';
import { defaultAvatar } from '@/enum.js';
import useUpdateAvatar from '@/composables/useUpdateAvatar';
import { queryCurrentStatus, queryCheckIn } from '@/api/pointsAndRank.js';
import checkIn from '@/components/points-and-badge/check-in';
import badge from '@/components/points-and-badge/badge';
import points from '@/components/points-and-badge/points';
import useCheckIn from '@/composables/useCheckIn';
import usePointsAndBadge from '@/components/points-and-badge/usePointsAndBadge';
const { handlePointsBadgePopup, badgeRef, pointsRef } = usePointsAndBadge();
const { checkInRef, checkInFun, initCheckInStatus } = useCheckIn();
const { avatarCropperUrl, click_update_avatar, avatarCropperStatus, avatarOnCancel, avatarOnConfirm } = useUpdateAvatar({
success: (userData) => {
user_info.imageAddr = userData.imageAddr;
user_info.imagePath = '';
}
});
const currentIndex = ref(0);
const socketStore = useSocketStore();
const badgeNumRef = ref(null);
const currentPointsRef = ref(null);
const gotoTestPage = () => {
common.navigateTo('/pages/test/index');
};
const user_info = reactive({
userName: '',
userId: '',
imageAddr: '',
loginName: '',
checkInToday: 0, // 今日是否签到 0未知, 1签到,2未签到
checkInDays: 0
});
// 签到打开
const checkInClick = () => {
checkInRef.value.open(
user_info.checkInToday === 1,
user_info.checkInDays,
checkInFun,
(status, days, pointsList = [], badgesList = []) => {
user_info.checkInToday = status ? 1 : 2;
user_info.checkInDays = days;
statistics_data['get_statistics_data'](true); // 强制获取统计数据
handlePointsBadgePopup(pointsList, badgesList);
}
);
};
const click_loginout = () => {
common.show('确定退出登录?').then(async (res) => {
if (!res) return;
try {
common.loading('正在退出登录');
socketStore.closeSocket();
await loginOut();
} catch (e) {}
common.hideLoading();
// 主动退出登录后,再次登录应该进首页
common.reLaunch('/pages/login/login');
});
};
const statistics_data = reactive({
badgeNum: 0, // 徽章数
currentPoints: 0, // 当前积分
currentRankAddr: '', // 当前段位名称
currentRankName: '', // 当前段位名称
startBadgeNum: 0, // 起始 徽章数
startCurrentPoints: 0, // 起始 积分
request_time: 0, // 上次请求时间
init: function () {
// 初始化统计数据
const points_data = common.getValue('points_data');
if (points_data) {
this.badgeNum = points_data.badgeNum; // 徽章数
this.currentPoints = points_data.currentPoints; // 当前积分
this.currentRankAddr = points_data.currentRankAddr;
this.currentRankName = points_data.currentRankName;
}
},
get_statistics_data: async function (status = false) {
try {
const time_now = Date.now();
let points_data = common.getValue('points_data') || {};
if (time_now - this.request_time > 300 || status) {
this.request_time = time_now;
// 获取最新数据
const res = await queryCurrentStatus();
if (res && res.body) {
// 合并新数据与缓存数据,确保只更新有效字段
points_data = {
badgeNum: res.body['badgeNum'], // 徽章数
currentPoints: res.body['currentPoints'], // 当前积分
currentRankAddr: res.body['ossAddr'], // 当前段位图片
currentRankName: res.body['currentRankName'] // 当前段位名称
};
common.setValue('points_data', points_data);
this.currentRankAddr = points_data.currentRankAddr;
this.currentRankName = points_data.currentRankName;
}
}
// 解构赋值获取最新值
const { badgeNum = 0, currentPoints = 0, currentRankAddr = '' } = points_data;
this.currentRankAddr = currentRankAddr; // 赋值段位变化
// 更新数据并触发动画
if (badgeNum !== this.badgeNum) {
this.startBadgeNum = this.badgeNum;
this.badgeNum = badgeNum;
badgeNumRef.value.start();
}
if (currentPoints !== this.currentPoints) {
this.startCurrentPoints = this.currentPoints;
this.currentPoints = currentPoints;
currentPointsRef.value.start();
}
} catch (error) {
console.error('获取统计数据失败:', error);
}
}
});
onLoad(() => {
statistics_data['init'](); // 初始化数据
uni.$on('update_me_data', (status) => {
// 获取了徽章或者积分,把缓存时间重置,以便下次进入me页实时获取
console.log('获取了徽章或者积分,把缓存时间重置,以便下次进入me页实时获取');
if (status) {
statistics_data.request_time = 0;
}
});
});
onShow(() => {
setMascot();
Object.assign(user_info, getUserInfo());
statistics_data['get_statistics_data'](); // 获取统计数据
// 获取今日是否签到消息
initCheckInStatus(({isSigned, checkInDays, popUpStatus}) => {
user_info.checkInToday = isSigned ? 1 : 2;
user_info.checkInDays = checkInDays;
});
});
onHide(() => {
checkInRef.value && checkInRef.value.close();
});
onPullDownRefresh(() => {
uni.stopPullDownRefresh();
});
onUnload(() => {
uni.$off('update_me_data');
});
</script>
<style scoped lang="scss">
$bg-margin-left: 50rpx;
// 下方单元格
.cell_div {
margin: 30rpx $bg-margin-left 0 $bg-margin-left;
background: #ffffff;
border-radius: 15rpx;
z-index: 90;
overflow: hidden;
// 为了让线缩进来24单位
:deep(.uv-line) {
width: calc(100% - 48rpx) !important;
margin: 0 24rpx !important;
}
:deep(.uv-cell__body) {
padding: 12px 15px;
}
}
// 快捷导航栏
.navigation_div {
margin: 30rpx $bg-margin-left 0 $bg-margin-left;
padding: 24rpx 10rpx;
background-color: #ffffff;
border-radius: 23rpx;
display: flex;
justify-content: space-around;
z-index: 90;
.scroll-view {
white-space: nowrap;
width: 100%;
}
.scroll-view-item {
display: inline-block;
padding: 0 22rpx 0 26rpx;
white-space: nowrap;
}
.item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
// background-color: red;
.icon {
width: 88rpx;
height: 86rpx;
}
.title {
margin-top: 10rpx;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 25rpx;
color: #323e57;
line-height: 35rpx;
text-align: left;
font-style: normal;
}
}
}
.bg_div {
display: flex;
flex-direction: column;
position: relative;
padding: 0 $bg-margin-left;
z-index: 50;
.bg_img {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: -1;
height: 858rpx;
}
// 个人信息
.user_data_div {
margin-top: 158rpx;
background: linear-gradient(180deg, rgba(241, 245, 255, 0.5) 0%, #f6f9fe 50%, #ffffff 100%);
border-radius: 14rpx;
border: 2rpx solid #ffffff;
box-shadow: #f6f8ff 0 0 2rpx;
.personal_information {
padding: 20rpx 26rpx 0 26rpx;
display: flex;
// 个人头像框框
.profile {
border-radius: 100%;
border: 2px solid #ffffff;
width: 108rpx;
height: 108rpx;
overflow: hidden;
}
// 头像右边的信息
.personal_information_data {
flex: 1;
display: flex;
flex-direction: column;
margin-left: 26rpx;
overflow: hidden;
// 上边的姓名性别盒子
.name_sex_div {
display: flex;
margin-top: 6rpx;
justify-content: space-between;
.name {
font-family: PingFangSC;
font-weight: 600;
font-size: 28rpx;
color: #000000;
line-height: 40rpx;
}
.sex {
margin-left: 10rpx;
width: 30rpx;
height: 30rpx;
flex-shrink: 0;
}
.dan {
font-size: 16rpx;
padding-left: 20rpx;
.dan_img {
height: 44rpx;
}
}
}
.work_number_div {
font-family: PingFangSC;
font-weight: 400;
font-size: 25rpx;
color: #666666;
line-height: 38rpx;
text-align: left;
font-style: normal;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.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-bg.png');
background-size: 100%; /* 图片自适应元素大小(避免拉伸变形) */
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 {
display: flex;
justify-content: space-around;
z-index: 20;
margin: 30rpx 50rpx;
.item-interval {
position: relative;
&:before {
content: '';
position: absolute;
left: 50%;
top: 20%;
transform: translateX(-50%);
width: 1rpx;
height: 60%;
width: 4rpx;
height: 54rpx;
background: linear-gradient(
to bottom,
rgba(239, 246, 255, 0) 0%,
rgba(229, 239, 255, 1) 30%,
rgba(229, 239, 255, 1) 70%,
rgba(239, 246, 255, 0) 100%
);
}
}
.item {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.top {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 28rpx;
color: #666666;
line-height: 35rpx;
text-align: center;
font-style: normal;
}
.bottom {
margin-top: 5%;
font-weight: bold;
font-size: 50rpx;
color: #000000;
line-height: 58rpx;
text-align: left;
font-style: normal;
text-align: center;
// 数字滚动组件
:deep(.uv-count-num) {
font-weight: bold !important;
font-size: 50rpx !important;
color: #000000 !important;
}
}
}
}
}
}
.main_div {
display: flex;
flex-direction: column;
background-color: #f6f8ff;
}
</style>