From 574f70be75fb9a76dd76a3e027c63dbf9cc78fd8 Mon Sep 17 00:00:00 2001 From: lion <120344285@qq.com> Date: Fri, 10 Apr 2026 11:24:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=A1=E6=81=AF=E7=BC=BA=E5=A4=B1=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/my/index.vue | 28 +++- packages/register/index.vue | 54 ++++++- pages/book/index.vue | 106 ++++++++++++- pages/index/index.vue | 291 +++++++++++++++++++++++++++++++++++- 4 files changed, 473 insertions(+), 6 deletions(-) diff --git a/packages/my/index.vue b/packages/my/index.vue index d69cc23..8a85fc5 100644 --- a/packages/my/index.vue +++ b/packages/my/index.vue @@ -236,6 +236,27 @@ required: true, message: '请选择出生日期', trigger: ['blur', 'change'], + }, { + validator: (rule, value, callback) => { + if (this.base.isNull(value)) return true; + const s = String(value).trim(); + // 仅年月、缺日 + if (/^\d{4}-\d{1,2}$/.test(s)) return false; + if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(s)) return false; + const parts = s.split('-'); + const y = parseInt(parts[0], 10); + const mo = parseInt(parts[1], 10); + const da = parseInt(parts[2], 10); + if (mo < 1 || mo > 12 || da < 1 || da > 31) return false; + const dt = new Date(y, mo - 1, da); + return ( + dt.getFullYear() === y && + dt.getMonth() === mo - 1 && + dt.getDate() === da + ); + }, + message: '出生日期须为完整年月日(例如 1993-01-15)', + trigger: ['blur', 'change'], }] } } @@ -269,9 +290,12 @@ url:'/packages/avatarUpload/index' }) }, - // 日期 + // 日期(规范为 yyyy-MM-dd,保证含年月日) dateConfirm(e) { - this.form.birthday = e.year + '-' + e.month + '-' + e.day + const y = e.year; + const m = String(e.month).padStart(2, '0'); + const d = String(e.day).padStart(2, '0'); + this.form.birthday = `${y}-${m}-${d}`; }, addMobile() { this.myMobile = this.form.mobile diff --git a/packages/register/index.vue b/packages/register/index.vue index 22c3649..c563f3c 100644 --- a/packages/register/index.vue +++ b/packages/register/index.vue @@ -185,6 +185,23 @@ required: true, message: '请选择出生日期', trigger: ['blur', 'change'], + }, { + validator: (rule, value, callback) => { + if (this.base.isNull(value)) return true; + const s = String(value).trim(); + // 仅年月(如 1993-01)视为不完整 + if (/^\d{4}-\d{1,2}$/.test(s)) return false; + if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(s)) return false; + const parts = s.split('-'); + const y = parseInt(parts[0], 10); + const mo = parseInt(parts[1], 10); + const da = parseInt(parts[2], 10); + if (mo < 1 || mo > 12 || da < 1 || da > 31) return false; + const dt = new Date(y, mo - 1, da); + return dt.getFullYear() === y && dt.getMonth() === mo - 1 && dt.getDate() === da; + }, + message: '出生日期须为完整年月日(例如 1993-01-15)', + trigger: ['blur', 'change'], }] } } @@ -209,7 +226,39 @@ } }, methods: { + isBirthdayMonthOnly(birthday) { + if (this.base.isNull(birthday)) return false; + return /^\d{4}-\d{1,2}$/.test(String(birthday).trim()); + }, + isBirthdayYmd(birthday) { + if (this.base.isNull(birthday)) return false; + const s = String(birthday).trim(); + if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(s)) return false; + const parts = s.split('-'); + const y = parseInt(parts[0], 10); + const mo = parseInt(parts[1], 10); + const da = parseInt(parts[2], 10); + if (mo < 1 || mo > 12 || da < 1 || da > 31) return false; + const dt = new Date(y, mo - 1, da); + return dt.getFullYear() === y && dt.getMonth() === mo - 1 && dt.getDate() === da; + }, + isBasicInfoIncomplete() { + const f = this.form || {}; + if (this.base.isNull(f.username)) return true; + if (this.base.isNull(f.sex)) return true; + if (this.base.isNull(f.email)) return true; + if (this.base.isNull(f.company_name)) return true; + if (this.base.isNull(f.company_position)) return true; + if (this.base.isNull(f.birthday)) return true; + if (this.isBirthdayMonthOnly(f.birthday)) return true; + if (!this.isBirthdayYmd(f.birthday)) return true; + return false; + }, addMobile() { + if (this.isBasicInfoIncomplete()) { + this.base.toast('请先完善基础信息后再绑定'); + return; + } // this.myMobile = this.form.mobile this.showMobile = true }, @@ -271,7 +320,10 @@ }, // 日期 dateConfirm(e) { - this.form.birthday = e.year + '-' + e.month + '-' + e.day + const y = e.year; + const m = String(e.month).padStart(2, '0'); + const d = String(e.day).padStart(2, '0'); + this.form.birthday = `${y}-${m}-${d}`; }, // 处理公司选择确认事件 handleCompanyConfirm(company) { diff --git a/pages/book/index.vue b/pages/book/index.vue index 22f0658..6c30f8e 100644 --- a/pages/book/index.vue +++ b/pages/book/index.vue @@ -40,6 +40,27 @@ --> + + + + + + 提示 + 关闭 + + + 如您已是我方校友,请先绑定账号 + + 去绑定 + + 如您还不是我方校友,请先注册 + + 去注册 + + + + + @@ -53,7 +74,9 @@ data() { return { user: {}, - enter_schoolmate: 0 + enter_schoolmate: 0, + showRegister: false, + hasMobile: false } }, onShareAppMessage() { @@ -69,16 +92,41 @@ } }, onShow() { + this.showRegister = false this.getUser() }, onLoad() { }, methods: { + ensureMobileOrPrompt() { + if (!this.hasMobile) { + this.base.toast("请先绑定或注册") + this.showRegister = true + return false + } + return true + }, + goBind() { + uni.navigateTo({ + url: '/packages/register/login' + }) + }, + toRegister() { + uni.navigateTo({ + url: '/packages/register/index' + }) + }, getUser() { this.$u.api.user().then(res => { console.log("res", res) this.enter_schoolmate = res.enter_schoolmate this.$u.vuex('vuex_user', res.user) + if (this.base.isNull(res.user.mobile)) { + this.hasMobile = false + } else { + this.showRegister = false + this.hasMobile = true + } }) }, goToProfile() { @@ -87,6 +135,9 @@ }); }, handleButtonClick(type) { + if (!this.ensureMobileOrPrompt()) { + return + } switch (type) { case 'alumni': if (this.enter_schoolmate) { @@ -227,5 +278,58 @@ font-weight: bold; color: #8f6e4d; } + + .modal { + ::v-deep .u-drawer-bottom { + border-radius: 40rpx; + } + + .modal-tip-wrap { + display: flex; + align-items: center; + justify-content: space-between; + padding: 30rpx; + } + + .modal-tip { + font-size: 32rpx; + } + + .modal-close { + color: #409eff; + font-size: 28rpx; + } + + .modal-content { + height: 450rpx; + padding: 0 30rpx; + font-size: 32rpx; + text-align: center; + + &>view { + margin: 30rpx auto; + } + } + + .modal-bind { + width: 45%; + text-align: center; + margin: 0 auto; + color: #fff; + border-radius: 30rpx; + padding: 20rpx; + background: linear-gradient(to right, #e4cdb4, #c69c6d); + } + + .modal-register { + width: 45%; + text-align: center; + margin: 0 auto; + color: #fff; + border-radius: 30rpx; + padding: 20rpx; + background: linear-gradient(to right, #5e5fbc, #0d0398); + } + } } \ No newline at end of file diff --git a/pages/index/index.vue b/pages/index/index.vue index d493853..3752eff 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -108,6 +108,32 @@ 返回首页 + + + + + + 您的个人信息有缺失,请尽快补充。 + 去完善 + + + + + + + + 提示 + 关闭 + + + 如您已是我方校友,请先绑定账号 + 去绑定 + 如您还不是我方校友,请先注册 + 去注册 + + + + @@ -118,6 +144,11 @@ import topBanner from "@/components/topBanner.vue"; import PrivacyPopup from "@/components/privacy-popup/privacy-popup.vue"; import CalendarWidget from "@/components/calendar-widget/calendar-widget.vue"; +/** 点遮罩关闭资料缺失提示时写入,值为当天 yyyy-MM-dd,当天内不再弹出 */ +const PROFILE_INCOMPLETE_DISMISSED_KEY = "stbc1_profileIncompleteDismissedDate"; +/** 手机号缺失提醒:当天首次进入首页仅弹出一次(展示过即记) */ +const MOBILE_MISSING_PROMPT_DATE_KEY = "stbc1_mobileMissingPromptDate"; + export default { components: { tabbar, @@ -137,6 +168,11 @@ export default { can_appointment: false, is_birthday: false, hasShowBirthday: false, + showProfileIncomplete: false, + showRegister: false, + pendingProfileIncomplete: false, + pendingMobileMissing: false, + isFirstShow: true, }; }, onShareAppMessage() { @@ -170,6 +206,25 @@ export default { this.getNoticesList(); this.getBannerList(); }, + onShow() { + if (this.isFirstShow) { + this.isFirstShow = false; + return; + } + this.showRegister = false; + let token = uni.getStorageSync("stbc1_lifeData") + ? uni.getStorageSync("stbc1_lifeData").vuex_token + : ""; + if (this.base.isNull(token)) return; + this.$u.api.user().then((res) => { + this.$u.vuex("vuex_user", res.user); + if (!this.isProfileIncomplete(res.user)) { + this.showProfileIncomplete = false; + this.pendingProfileIncomplete = false; + this.pendingMobileMissing = false; + } + }); + }, methods: { onAgreePrivacy() { // 用户同意隐私政策 @@ -183,6 +238,108 @@ export default { // wx.exitMiniProgram(); console.log("User rejected the privacy policy"); }, + /** 出生日期仅有年月、无具体日期(如 1993-01)视为未完善 */ + isBirthdayMonthOnly(birthday) { + if (this.base.isNull(birthday)) return false; + const s = String(birthday).trim(); + return /^\d{4}-\d{1,2}$/.test(s); + }, + isEmptyProfileField(v) { + if (this.base.isNull(v)) return true; + if (typeof v === "string" && v.trim() === "") return true; + return false; + }, + getTodayYmd() { + const d = new Date(); + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${y}-${m}-${day}`; + }, + /** 当天是否已通过点遮罩关闭过资料缺失提示 */ + isProfileIncompleteDismissedToday() { + return uni.getStorageSync(PROFILE_INCOMPLETE_DISMISSED_KEY) === this.getTodayYmd(); + }, + isMobileMissing(user) { + return !user || this.isEmptyProfileField(user.mobile); + }, + isMobileMissingPromptShownToday() { + return uni.getStorageSync(MOBILE_MISSING_PROMPT_DATE_KEY) === this.getTodayYmd(); + }, + markMobileMissingPromptShownToday() { + uni.setStorageSync(MOBILE_MISSING_PROMPT_DATE_KEY, this.getTodayYmd()); + }, + /** 已绑定手机的前提下,资料是否不完整 */ + isProfileIncomplete(user) { + if (!user || this.isEmptyProfileField(user.mobile)) return false; + if (this.isEmptyProfileField(user.company_name)) return true; + if (this.isEmptyProfileField(user.company_position)) return true; + if (this.isEmptyProfileField(user.email)) return true; + if (this.isEmptyProfileField(user.sex)) return true; + if (this.isEmptyProfileField(user.birthday)) return true; + if (this.isBirthdayMonthOnly(user.birthday)) return true; + return false; + }, + maybeShowProfileIncomplete(user, isBirthdayFlag) { + if (!user || !this.isProfileIncomplete(user)) { + this.pendingProfileIncomplete = false; + return; + } + if (this.isProfileIncompleteDismissedToday()) { + this.pendingProfileIncomplete = false; + return; + } + const showBirthdayLayer = + isBirthdayFlag && !this.hasShowBirthday; + if (showBirthdayLayer) { + this.pendingProfileIncomplete = true; + } else { + this.showProfileIncomplete = true; + this.pendingProfileIncomplete = false; + } + }, + maybeShowHomeReminder(user, isBirthdayFlag) { + if (this.isMobileMissing(user) && !this.isMobileMissingPromptShownToday()) { + const showBirthdayLayer = isBirthdayFlag && !this.hasShowBirthday; + if (showBirthdayLayer) { + this.pendingMobileMissing = true; + } else { + this.base.toast("请先绑定或注册"); + this.showRegister = true; + this.markMobileMissingPromptShownToday(); + } + this.pendingProfileIncomplete = false; + return; + } + this.maybeShowProfileIncomplete(user, isBirthdayFlag); + }, + /** 点遮罩或点「去完善」后,当天冷启动不再弹出资料缺失提示 */ + markProfileIncompleteDismissedToday() { + uni.setStorageSync(PROFILE_INCOMPLETE_DISMISSED_KEY, this.getTodayYmd()); + }, + goCompleteProfile() { + this.showProfileIncomplete = false; + this.markProfileIncompleteDismissedToday(); + uni.navigateTo({ + url: "/packages/my/index", + }); + }, + closeProfileIncomplete() { + this.showProfileIncomplete = false; + this.markProfileIncompleteDismissedToday(); + }, + goBind() { + this.showRegister = false; + uni.navigateTo({ + url: "/packages/register/login", + }); + }, + toRegister() { + this.showRegister = false; + uni.navigateTo({ + url: "/packages/register/index", + }); + }, changeCurrent(e) { this.show_current_swiper = e.detail.current + 1; }, @@ -233,11 +390,17 @@ export default { vuex_token: tokenResult, vuex_user: result1.data.user, }); - + this.$u.vuex("vuex_token", tokenResult); + this.$u.vuex("vuex_user", result1.data.user); + // 登录成功后检查用户生日 if (result1.data.is_birthday && !this.hasShowBirthday) { this.is_birthday = true; } + this.maybeShowHomeReminder( + result1.data.user, + result1.data.is_birthday + ); }, fail(err) { console.log("uesr-error", err); @@ -253,11 +416,12 @@ export default { // } // this.door_appointments = res.door_appointments ? true : false this.$u.vuex("vuex_user", res.user); - + // 检查用户是否今天生日(使用接口返回的is_birthday参数) if (res.is_birthday && !this.hasShowBirthday) { this.is_birthday = true; } + this.maybeShowHomeReminder(res.user, res.is_birthday); }); }, @@ -267,6 +431,27 @@ export default { // 标记今天已经显示过生日弹窗 uni.setStorageSync("hasShowBirthday", true); this.hasShowBirthday = true; + if (this.pendingProfileIncomplete) { + const life = uni.getStorageSync("stbc1_lifeData") || {}; + const u = life.vuex_user || this.vuex_user || {}; + if ( + !this.isProfileIncompleteDismissedToday() && + this.isProfileIncomplete(u) + ) { + this.showProfileIncomplete = true; + } + this.pendingProfileIncomplete = false; + } + if (this.pendingMobileMissing) { + const life = uni.getStorageSync("stbc1_lifeData") || {}; + const u = life.vuex_user || this.vuex_user || {}; + if (this.isMobileMissing(u) && !this.isMobileMissingPromptShownToday()) { + this.base.toast("请先绑定或注册"); + this.showRegister = true; + this.markMobileMissingPromptShownToday(); + } + this.pendingMobileMissing = false; + } }, async getBannerList() { @@ -575,4 +760,106 @@ export default { transition: all 0.3s ease; } } + +.profile-incomplete-popup { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 9998; + display: flex; + align-items: center; + justify-content: center; +} + +.profile-incomplete-mask { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.45); +} + +.profile-incomplete-dialog { + position: relative; + width: 620rpx; + padding: 56rpx 44rpx 46rpx; + background: #fff; + border-radius: 30rpx; + box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.12); +} + +.profile-incomplete-text { + font-size: 32rpx; + color: #333; + line-height: 1.6; + text-align: center; + margin-bottom: 46rpx; +} + +.profile-incomplete-btn { + width: 70%; + margin: 0 auto; + color: #fff; + text-align: center; + padding: 20rpx 0; + border-radius: 30rpx; + font-size: 30rpx; + background: linear-gradient(to right, #5e5fbc, #0d0398); +} + +.modal { + ::v-deep .u-drawer-bottom { + border-radius: 40rpx; + } + + &-tip-wrap { + display: flex; + align-items: center; + justify-content: space-between; + padding: 30rpx; + } + + &-tip { + font-size: 32rpx; + } + + &-close { + color: #409eff; + font-size: 28rpx; + } + + &-content { + height: 450rpx; + padding: 0 30rpx; + font-size: 32rpx; + text-align: center; + + & > view { + margin: 30rpx auto; + } + } + + &-bind { + width: 45%; + text-align: center; + margin: 0 auto; + color: #fff; + border-radius: 30rpx; + padding: 20rpx; + background: linear-gradient(to right, #e4cdb4, #c69c6d); + } + + &-register { + width: 45%; + text-align: center; + margin: 0 auto; + color: #fff; + border-radius: 30rpx; + padding: 20rpx; + background: linear-gradient(to right, #5e5fbc, #0d0398); + } +} \ No newline at end of file