增加首页自动弹出签到

This commit is contained in:
田岩
2025-11-21 18:11:58 +08:00
parent 6b8f5bf226
commit d154407ced
6 changed files with 350 additions and 124 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ ENV = 'development'
# VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001'
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
#VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
@@ -15,7 +15,7 @@ VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# VITE_APP_BASE_API_Url = 'http://aitscdn.jlbank.com.cn:7001'
# VITE_APP_BASE_API_Url = 'http://192.168.108.129'
# dev
#VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001'
VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001'
# sit
+1 -1
View File
@@ -56,7 +56,7 @@
const popup = ref(null);
const current = ref(0);
const opacity = ref(1);
let CloseComplete
let CloseComplete
const badgesList = ref([]);
const pointsList = ref([]);
const multiple = computed(() => badgesList.value.length > 1);
+27 -27
View File
@@ -82,6 +82,9 @@
const customStyle = {
borderRadius: '40rpx' //圆角
};
let checkInClickCallback;
let checkInSuccessCallback;
const dateGroups = ref([]);
const expand = ref(false); // 展开状态
const isSigned = ref(false); // 今日签到状态
@@ -103,37 +106,32 @@
// 退出签到
const close = () => {
popup.value.close();
typeof checkInClickCallback === 'function' && checkInClickCallback({
status:false
})
};
const checkInLoading = ref(false);
// 签到
const checkIn = () => {
if (checkInLoading.value) return;
checkInLoading.value = true;
addPointsByCheckIn({ days: continuousDay.value })
.then((res) => {
console.log('签到结果', res);
// 函数执行成功的回调
checkInClickCallback({
status:true,
days: continuousDay.value,
successCb: ({ days, res }) => {
console.log('签到成功', res);
checkInLoading.value = false;
isSigned.value = true;
continuousDay.value = continuousDay.value + 1;
// common.msg('签到成功,请明日继续!')
emits(
'checkInFun',
true,
res.body.points,
res.body.pointsBadges,
// [{ changePoints: 5 }],
// [
// {
// pointsBadgeImg: '/traoss/tras3/show/S3F0250181220722025110713501700000661177',
// pointsBadgeName: '黄金徽章'
// }
// ]
);
})
.catch((err) => {
continuousDay.value = days;
checkInSuccessCallback(true, days, res.body.points, res.body.pointsBadges);
},
failCb: (res) => {
checkInLoading.value = false;
console.log('签到失败', res);
common.msg('签到失败,请稍后再试');
});
}
});
};
/**
@@ -207,17 +205,19 @@
return groupedList;
}
const open = (checkInToday, days) => {
isSigned.value = checkInToday === 'Y' ? true : false; // 今日签到状态
// isSigned.value = false; // 今日签到状态
const open = (isSignedStatus, days, onCheckInClick = () => {}, onCheckInSuccess = () => {}) => {
console.log('isSignedStatus', isSignedStatus);
isSigned.value = isSignedStatus; // 今日签到状态
continuousDay.value = days; // 连续签到
checkInClickCallback = onCheckInClick;
checkInSuccessCallback = onCheckInSuccess;
// 示例: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天分组日期:`);
// console.log(`以${continuousDay.value}天`);
// console.log(`以${X}天前为第1天的35天分组日期:`);
dateGroups.value = generateGroupedDateList(X);
} catch (err) {
console.error(err.message);
+221 -11
View File
@@ -1,16 +1,226 @@
import { queryCheckIn } from '@/api/pointsAndRank.js';
import {
queryCheckIn,
addPointsByCheckIn
} from '@/api/pointsAndRank.js';
import common from '@/common/common';
import {
getUserInfo
} from '@/common/common';
import {
ref
} from 'vue';
export default function useCheckIn() {
const checkInRef = ref(null)
// ========== 核心工具方法 ==========
/**
* 获取当前用户的签到存储key(区分不同用户)
*/
const getKey = () => {
const userInfo = getUserInfo();
const userId = userInfo?.userId || ''; // 修复原代码userId重复赋值问题
if (!userId) {
console.warn('用户ID为空,无法获取签到状态');
return '';
}
return `${userId}_user_sign_in_status`;
};
/**
* 获取今日日期(YYYY-MM-DD
*/
const getTodayDate = () => {
const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
};
/**
* 读取本地存储的签到状态
* @returns {Object} { isSigned: boolean, signDate: string }
*/
const getLocalSignStatus = () => {
const key = getKey();
if (!key) return {
signDate: '',
checkInDays: 0,
PopupDay: ''
};
try {
// 读取本地存储的签到信息(兼容JSON格式)
const rawData = common.getValue(key);
if (!rawData) return {
signDate: '',
checkInDays: 0,
popupDay: ''
};
// 兼容存储的是字符串或JSON对象的情况
const signStatus = rawData;
return {
signDate: signStatus.signDate || '',
checkInDays: signStatus.checkInDays || 0,
popupDay: signStatus.popupDay || '',
};
} catch (e) {
console.error('读取本地签到状态失败', e);
return {
signDate: '',
checkInDays: 0,
popupDay: ''
};
}
};
/**
* 更新本地签到状态(支持按需更新字段)
* @param {boolean|undefined} isSigned - 是否签到(可选,传入则更新)
* @param {number|undefined} checkInDays - 签到天数(可选,传入则更新)
* @param {string|undefined} popupDay - 弹窗关闭日期(可选,传入则更新)
*/
const setLocalSignStatus = (isSigned, checkInDays, popupDay) => {
const key = getKey();
if (!key) return;
// 1. 读取本地原有数据(若无则初始化空对象)
let oldSignStatus = {};
try {
oldSignStatus = common.getValue(key) || {}; // 兼容common.getValue返回null/undefined的情况
} catch (e) {
console.warn('读取原有签到状态失败,使用空对象初始化', e);
oldSignStatus = {};
}
// 2. 初始化新状态:先继承原有数据
const newSignStatus = { ...oldSignStatus };
// 3. 按需更新字段(仅当参数传入时覆盖)
// 处理isSigned:传入则更新,且为true时自动设置signDate
if (isSigned !== undefined) {
// 保持原有逻辑:isSigned为true时,signDate设为今日
newSignStatus.signDate = isSigned ? getTodayDate() : oldSignStatus.signDate;
}
// 处理checkInDays:传入则更新,未传入保留原有值
if (checkInDays !== undefined) {
newSignStatus.checkInDays = checkInDays;
}
// 处理popupDay:传入非空则更新,未传入/空值保留原有值
if (popupDay !== undefined) {
newSignStatus.popupDay = popupDay? getTodayDate() : oldSignStatus.popupDay;
}
// 4. 重新存入本地(覆盖原有数据)
try {
console.log('存入的数据(原有+更新)', newSignStatus);
common.setValue(key, newSignStatus);
} catch (e) {
console.error('更新本地签到状态失败', e);
}
};
// ========== 对外暴露的核心方法 ==========
/**
* 初始化签到状态(页面加载时调用,同步本地与后端状态)
* @returns {Promise<{ isSigned: boolean, checkInDays?: number }>} 签到状态 + 连续签到天数
*/
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)
return true;
}
// 2. 本地记录过期/无记录 → 请求接口获取最新状态
try {
const res = await queryCheckIn(); // 调用查询签到状态接口
const {
body
} = res || {};
console.log('bodbodyy', body);
// // 接口返回格式适配(根据实际后端返回调整)
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)
} catch (e) {
console.error('请求签到状态接口失败', e);
}
};
/**
* 判断今日是否已签到(简化版,供快速判断)
* @returns {boolean}
*/
const getTodayCheckInStatus = () => {
// common.getValue()
}
// 获取今日是否签到消息
const getCheckInInfo = () => {
// todo 后续加防止重复多次请求逻辑
queryCheckIn().then(({ body }) => {
user_info.checkInToday = body.checkInToday; // 今日是否签到
user_info.checkInDays = body.days; // 连续签到天数
});
const localStatus = getLocalSignStatus();
const today = getTodayDate();
// 日期匹配且已签到,才返回true
return localStatus.signDate === today;
};
/**
* 执行签到操作(核心方法)
* @param {Function} successCb - 签到成功回调
* @param {Function} failCb - 签到失败回调
* @returns {Promise<boolean>} 签到是否成功
*/
const checkInFun = async ({
status,
days=0,
successCb=() =>{},
failCb=() =>{}
}) => {
if(!status) { // 点击了关闭
console.log('点击了关闭');
setLocalSignStatus(undefined, undefined, true);
return;
}
console.log('请求接口', days);
addPointsByCheckIn({
days
})
.then((res) => {
console.log('接口成功回调', res);
setLocalSignStatus(true, days + 1);
successCb?.({
days: days + 1,
res
});
}).catch((error) => {
failCb?.(error)
})
};
/**
* 重置签到状态(可选,用于特殊场景)
*/
const resetCheckInStatus = () => {
const key = getKey();
if (key) common.removeValue(key); // 需确保common有remove方法,无则补充
};
// ========== 返回对外暴露的方法 ==========
return {
checkInRef,
initCheckInStatus, // 初始化签到状态(页面加载时调用)
checkInFun, // 执行签到
getLocalSignStatus, // 获取签到缓存信息
getTodayCheckInStatus // 获取今天签到状态
};
}
+72 -54
View File
@@ -6,8 +6,15 @@
<image src="@/static/images/index/bg.png" class="bg_img"></image>
<view class="fixed_search_div" v-show="opacity" :style="{ opacity: opacity }">
<view class="fixed_search_input_div" @click="goto_search_page">
<uv-input shape="circle" :customStyle="fixedCustomStyles" placeholderStyle="color:#999999;font-size:26rpx"
placeholder="请输入关键字" suffixIcon="search" :suffixIconStyle="suffixIconStyle" :readonly="true"></uv-input>
<uv-input
shape="circle"
:customStyle="fixedCustomStyles"
placeholderStyle="color:#999999;font-size:26rpx"
placeholder="请输入关键字"
suffixIcon="search"
:suffixIconStyle="suffixIconStyle"
:readonly="true"
></uv-input>
</view>
<view class="message_div" @click="common.navigateTo('/pages/messageNotification/index')">
<image src="@/static/images/index/message_b.png" class="message_img"></image>
@@ -24,9 +31,16 @@
</view>
<!-- 搜索框 -->
<view class="search_div">
<uv-input shape="circle" @click="goto_search_page" :customStyle="customStyles"
:placeholderStyle="placeholderStyle" placeholder="请输入关键字" suffixIcon="search"
:suffixIconStyle="suffixIconStyle" :readonly="true"></uv-input>
<uv-input
shape="circle"
@click="goto_search_page"
:customStyle="customStyles"
:placeholderStyle="placeholderStyle"
placeholder="请输入关键字"
suffixIcon="search"
:suffixIconStyle="suffixIconStyle"
:readonly="true"
></uv-input>
<view class="message_div" @click="common.navigateTo('/pages/messageNotification/index')">
<image src="@/static/images/index/message.png" class="message_img"></image>
<view class="message_text">消息</view>
@@ -58,38 +72,54 @@
<view class="item">
<view class="top">本年学习时长</view>
<view class="bottom">
<uv-count-to :startVal="statistics_data['startStdtTmLen']" :endVal="statistics_data['stdtTmLen']"
:useEasing="true" :decimals="1" ref="stdtTmLenRef"></uv-count-to>
<uv-count-to
:startVal="statistics_data['startStdtTmLen']"
:endVal="statistics_data['stdtTmLen']"
:useEasing="true"
:decimals="1"
ref="stdtTmLenRef"
></uv-count-to>
h
</view>
</view>
<view class="item">
<view class="top">本年陪练次数</view>
<view class="bottom">
<uv-count-to :startVal="statistics_data['starAccmPracticeCnt']"
:endVal="statistics_data['accmPracticeCnt']" :useEasing="true" ref="accmPracticeCntRef"></uv-count-to>
<uv-count-to
:startVal="statistics_data['starAccmPracticeCnt']"
:endVal="statistics_data['accmPracticeCnt']"
:useEasing="true"
ref="accmPracticeCntRef"
></uv-count-to>
<span class="number"></span>
</view>
</view>
<view class="item">
<view class="top">本年考试次数</view>
<view class="bottom">
<uv-count-to :startVal="statistics_data['startAccmExamCnt']" :endVal="statistics_data['accmExamCnt']"
:useEasing="true" ref="accmExamCntRef"></uv-count-to>
<uv-count-to
:startVal="statistics_data['startAccmExamCnt']"
:endVal="statistics_data['accmExamCnt']"
:useEasing="true"
ref="accmExamCntRef"
></uv-count-to>
<span class="number"></span>
</view>
</view>
</view>
</view>
<!-- 8个导航 -->
<!-- 8个导航 -->
<view class="navigation_div">
<view class="group">
<view class="item" @click="common.switchTab('/pages/course/index')">
<image class="icon" src="@/static/images/index/navigation_course.png"></image>
<view class="title">AI学习</view>
</view>
<view class="item" @click="common.navigateTo('/pages/index/dialog', { animationType: 'slide-in-bottom' })">
<view
class="item"
@click="common.navigateTo('/pages/index/dialog', { animationType: 'slide-in-bottom' })"
>
<image class="icon" src="@/static/images/index/navigation_question_answering.png"></image>
<view class="title">AI问答</view>
</view>
@@ -127,7 +157,6 @@
<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">
@@ -163,44 +192,29 @@
</view>
</view>
<tabbar-shadow></tabbar-shadow>
<checkIn ref="checkInRef"></checkIn>
<badge ref="badgeRef"></badge>
<points ref="pointsRef"></points>
</view>
</template>
<script setup>
import {
computed,
nextTick,
onBeforeMount,
onMounted,
reactive,
ref
} from 'vue';
import {
onShow,
onPageScroll,
onLoad,
onHide
} from '@dcloudio/uni-app';
import {
customStyles,
fixedCustomStyles,
placeholderStyle,
suffixIconStyle
} from './index.js';
import {
getUserInfo
} from '@/common/common';
import { computed, nextTick, onBeforeMount, onMounted, reactive, ref } from 'vue';
import { onShow, onPageScroll, onLoad, onHide } from '@dcloudio/uni-app';
import { customStyles, fixedCustomStyles, placeholderStyle, suffixIconStyle } from './index.js';
import { getUserInfo } from '@/common/common';
import common from '@/common/common';
import {
setMascot
} from '@/common/mascot.js';
import {
getStatisticsData
} from '@/api/index.js';
import {
noticeMessageCount
} from '@/api/messageNotification.js';
import { setMascot } from '@/common/mascot.js';
import { getStatisticsData } from '@/api/index.js';
import { noticeMessageCount } from '@/api/messageNotification.js';
import classScroll from './components/class_scroll.vue';
import useCheckIn from '@/composables/useCheckIn';
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 usePointsAndBadge from '@/components/points-and-badge/usePointsAndBadge';
const { initCheckInStatus, checkInRef, checkInFun } = useCheckIn();
const { handlePointsBadgePopup, badgeRef, pointsRef } = usePointsAndBadge();
const scrollTop = ref(0);
const no_read_message_num = ref(0);
const classScrollRef = ref([]);
@@ -247,7 +261,7 @@
startAccmPracticeCnt: 0, // 起始训练
startAccmExamCnt: 0, // 起始考试
request_time: 0, // 上次请求时间
init: function() {
init: function () {
// 初始化统计数据
const index_statistics_data = common.getValue('statistics_data'); // 获取缓存
if (index_statistics_data) {
@@ -256,7 +270,7 @@
this.accmExamCnt = index_statistics_data.accmExamCnt;
}
},
get_statistics_data: async function() {
get_statistics_data: async function () {
try {
const time_now = Date.now();
let statistics_data = common.getValue('statistics_data') || {};
@@ -278,9 +292,7 @@
}
// 解构赋值获取最新值
const {
accmExamCnt = 0, accmPracticeCnt = 0, stdtTmLen = 0
} = statistics_data;
const { accmExamCnt = 0, accmPracticeCnt = 0, stdtTmLen = 0 } = statistics_data;
// 更新数据并触发动画
if (stdtTmLen !== this.stdtTmLen) {
@@ -326,8 +338,15 @@
common.navigateTo('/pages/login/login');
return;
}
initCheckInStatus((isSigned, checkInDays, popUpStatus) => {
if(!isSigned && !popUpStatus) {
checkInRef.value.open(isSigned, checkInDays, checkInFun, (status, days, pointsList = [], badgesList = []) => {
handlePointsBadgePopup(pointsList, badgesList);
});
}
});
const userInfo = getUserInfo();
// console.log('首页userInfo', userInfo);
if (userInfo) {
mascotInfo.value = userInfo?.mascotInfo;
}
@@ -336,7 +355,6 @@
nextTick(() => {
classScrollRef.value.forEach((item) => item?.get_data());
});
noticeMessageCount().then((res) => {
no_read_message_num.value = res.body;
});
@@ -734,4 +752,4 @@
flex-direction: column;
background-color: #f6f8ff;
}
</style>
</style>
+27 -29
View File
@@ -17,7 +17,7 @@
</view>
<view class="personal_information_data">
<view class="name_sex_div">
<view class="name">
<view class="name" @click="gotoTestPage">
{{ user_info.userName }}
</view>
<view class="sex">
@@ -48,8 +48,8 @@
<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 === 'N'">签到</view>
<view class="already-sign-in-div" v-if="user_info.checkInToday === 'Y'" @click="checkInClick">已签</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">
@@ -158,7 +158,7 @@
@cancel="avatarOnCancel"
@confirm="avatarOnConfirm"
></avatar-cropper>
<checkIn ref="checkInRef" @checkInFun="checkInFun"></checkIn>
<checkIn ref="checkInRef"></checkIn>
<badge ref="badgeRef"></badge>
<points ref="pointsRef"></points>
</view>
@@ -183,8 +183,10 @@
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;
@@ -194,30 +196,32 @@
const currentIndex = ref(0);
const socketStore = useSocketStore();
const badgeNumRef = ref(null);
const checkInRef = ref(null);
const currentPointsRef = ref(null);
const gotoTestPage = () => {
common.navigateTo('/pages/test/index');
};
const user_info = reactive({
userName: '',
userId: '',
imageAddr: '',
loginName: '',
checkInToday: '', // 今日是否签到
checkInDays: 0 // 连续签到天数
checkInToday: 0, // 今日是否签到 0未知, 1签到,2未签到
checkInDays: 0
});
// 签到打开
const checkInClick = () => {
checkInRef.value.open(user_info.checkInToday, user_info.checkInDays);
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;
handlePointsBadgePopup(pointsList, badgesList);
}
);
};
// 签到成功回调
const checkInFun = (status, pointsList = [], badgesList = []) => {
if (status) {
getCheckInInfo();
}
handlePointsBadgePopup(pointsList, badgesList)
};
const click_loginout = () => {
common.show('确定退出登录?').then(async (res) => {
if (!res) return;
@@ -285,7 +289,6 @@
if (currentPoints !== this.currentPoints) {
this.startCurrentPoints = this.currentPoints;
this.currentPoints = currentPoints;
console.log('this.startCurrentPointsx', this.startCurrentPoints, this.currentPoints);
currentPointsRef.value.start();
}
} catch (error) {
@@ -294,15 +297,7 @@
}
}
});
// 获取今日是否签到消息
const getCheckInInfo = () => {
// todo 后续加防止重复多次请求逻辑
queryCheckIn().then(({ body }) => {
user_info.checkInToday = body.checkInToday; // 今日是否签到
user_info.checkInDays = body.days; // 连续签到天数
});
};
onLoad(() => {
statistics_data['init'](); // 初始化数据
});
@@ -311,7 +306,10 @@
Object.assign(user_info, getUserInfo());
statistics_data['get_statistics_data'](); // 获取统计数据
// 获取今日是否签到消息
getCheckInInfo();
initCheckInStatus((isSigned, checkInDays, popUpStatus) => {
user_info.checkInToday = isSigned ? 1 : 2;
user_info.checkInDays = checkInDays;
});
});
onHide(() => {
checkInRef.value && checkInRef.value.close();