feat: 添加登录失败处理及记录格式化功能

增加登录失败状态处理和重试机制
将记录格式化逻辑提取到工具函数
优化云数据库查询性能
This commit is contained in:
chenglijuan
2026-04-18 22:34:35 +08:00
parent 54556374b1
commit d5478429c0
7 changed files with 138 additions and 86 deletions
+8 -1
View File
@@ -34,12 +34,19 @@ App({
fail: (err) => { fail: (err) => {
console.error('静默登录失败', err) console.error('静默登录失败', err)
this.globalData.isLoggedIn = false this.globalData.isLoggedIn = false
this.globalData.loginFailed = true
// 通知页面登录失败
if (this.loginReadyCallback) {
this.loginReadyCallback(null)
}
} }
}) })
}, },
globalData: { globalData: {
userInfo: null, userInfo: null,
isLoggedIn: false isLoggedIn: false,
loginFailed: false
} }
}) })
+27 -21
View File
@@ -1,19 +1,26 @@
// index.js // index.js
const { appointmentDB } = require('../../utils/cloud') const { appointmentDB, formatRecord } = require('../../utils/cloud')
const app = getApp() const app = getApp()
Page({ Page({
data: { data: {
isLoggedIn: false, isLoggedIn: false,
loginFailed: false,
latestRecord: null latestRecord: null
}, },
onLoad() { onLoad() {
if (app.globalData.isLoggedIn) { if (app.globalData.isLoggedIn) {
this.onLoginReady() this.onLoginReady()
} else if (app.globalData.loginFailed) {
this.onLoginFailed()
} else { } else {
app.loginReadyCallback = () => { app.loginReadyCallback = (userInfo) => {
if (userInfo) {
this.onLoginReady() this.onLoginReady()
} else {
this.onLoginFailed()
}
} }
} }
}, },
@@ -25,20 +32,32 @@ Page({
}, },
onLoginReady() { onLoginReady() {
this.setData({ isLoggedIn: true }) this.setData({ isLoggedIn: true, loginFailed: false })
this.loadLatestRecord() this.loadLatestRecord()
}, },
async loadLatestRecord() { onLoginFailed() {
if (!this.data.isLoggedIn) { this.setData({ isLoggedIn: false, loginFailed: true })
this.setData({ latestRecord: null }) },
return
onRetry() {
this.setData({ loginFailed: false })
app.silentLogin()
app.loginReadyCallback = (userInfo) => {
if (userInfo) {
this.onLoginReady()
} else {
this.onLoginFailed()
} }
}
},
async loadLatestRecord() {
try { try {
const openid = app.globalData.userInfo.openid const openid = app.globalData.userInfo.openid
const record = await appointmentDB.getLatest(openid) const record = await appointmentDB.getLatest(openid)
if (record) { if (record) {
this.setData({ latestRecord: this.formatRecord(record) }) this.setData({ latestRecord: formatRecord(record) })
} else { } else {
this.setData({ latestRecord: null }) this.setData({ latestRecord: null })
} }
@@ -48,19 +67,6 @@ Page({
} }
}, },
formatRecord(record) {
const date = record.createTime
let createTimeStr = ''
if (date) {
if (typeof date === 'object' && date.$date) {
createTimeStr = new Date(date.$date).toLocaleString('zh-CN')
} else {
createTimeStr = new Date(date).toLocaleString('zh-CN')
}
}
return { ...record, createTime: createTimeStr }
},
goAppointment() { goAppointment() {
wx.navigateTo({ wx.navigateTo({
url: '/pages/appointment/appointment' url: '/pages/appointment/appointment'
+8 -1
View File
@@ -1,11 +1,18 @@
<!--index.wxml--> <!--index.wxml-->
<view class="page"> <view class="page">
<!-- loading 遮罩 --> <!-- loading 遮罩 -->
<view class="loading-mask" wx:if="{{!isLoggedIn}}"> <view class="loading-mask" wx:if="{{!isLoggedIn && !loginFailed}}">
<view class="loading-spinner"></view> <view class="loading-spinner"></view>
<text class="loading-text">正在获取身份信息...</text> <text class="loading-text">正在获取身份信息...</text>
</view> </view>
<!-- 登录失败 -->
<view class="loading-mask" wx:if="{{loginFailed}}">
<text class="fail-icon">⚠️</text>
<text class="fail-text">网络异常,请重试</text>
<view class="retry-btn" bindtap="onRetry">重新加载</view>
</view>
<view class="header"> <view class="header">
<view class="header-icon">🏢</view> <view class="header-icon">🏢</view>
<text class="header-title">访客预约系统</text> <text class="header-title">访客预约系统</text>
+27 -2
View File
@@ -56,6 +56,30 @@ page {
color: #999; color: #999;
} }
/* 登录失败 */
.fail-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.fail-text {
font-size: 28rpx;
color: #999;
margin-bottom: 32rpx;
}
.retry-btn {
font-size: 28rpx;
color: #fff;
background: #1890ff;
padding: 16rpx 56rpx;
border-radius: 36rpx;
}
.retry-btn:active {
background: #096dd9;
}
.header-title { .header-title {
font-size: 44rpx; font-size: 44rpx;
font-weight: 700; font-weight: 700;
@@ -80,11 +104,12 @@ page {
border-radius: 20rpx; border-radius: 20rpx;
padding: 36rpx 32rpx; padding: 36rpx 32rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06); box-shadow: 0 8rpx 32rpx rgba(24, 144, 255, 0.12), 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
} }
.action-card:active { .action-card:active {
background: #f0f0f0; background: #f0f0f0;
box-shadow: 0 4rpx 16rpx rgba(24, 144, 255, 0.08), 0 1rpx 4rpx rgba(0, 0, 0, 0.04);
} }
.action-icon-wrap { .action-icon-wrap {
@@ -140,7 +165,7 @@ page {
border-radius: 20rpx; border-radius: 20rpx;
padding: 28rpx 32rpx; padding: 28rpx 32rpx;
margin-top: 8rpx; margin-top: 8rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06); box-shadow: 0 8rpx 32rpx rgba(82, 196, 26, 0.12), 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
} }
.latest-header { .latest-header {
+6 -26
View File
@@ -1,5 +1,5 @@
// records.js // records.js
const { appointmentDB } = require('../../utils/cloud') const { appointmentDB, formatRecord } = require('../../utils/cloud')
const app = getApp() const app = getApp()
Page({ Page({
@@ -11,18 +11,12 @@ Page({
}, },
onLoad() { onLoad() {
if (!app.globalData.isLoggedIn) {
wx.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => {
wx.navigateBack()
}, 1500)
return
}
this.loadRecords() this.loadRecords()
}, },
onShow() { onShow() {
if (app.globalData.isLoggedIn) { // 仅从预约页返回时刷新,避免 onLoad + onShow 双重加载
if (this.data._loaded) {
this.loadRecords() this.loadRecords()
} }
}, },
@@ -32,26 +26,12 @@ Page({
try { try {
const openid = app.globalData.userInfo.openid const openid = app.globalData.userInfo.openid
const records = await appointmentDB.getList(openid) const records = await appointmentDB.getList(openid)
const formatted = records.map(item => formatRecord(item))
// 格式化时间 this.setData({ records: formatted, loading: false, _loaded: true })
const formatted = records.map(item => {
const date = item.createTime
let createTimeStr = ''
if (date) {
if (typeof date === 'object' && date.$date) {
createTimeStr = new Date(date.$date).toLocaleString('zh-CN')
} else {
createTimeStr = new Date(date).toLocaleString('zh-CN')
}
}
return { ...item, createTime: createTimeStr }
})
this.setData({ records: formatted, loading: false })
this.filterRecords() this.filterRecords()
} catch (err) { } catch (err) {
console.error('加载预约记录失败', err) console.error('加载预约记录失败', err)
this.setData({ records: [], loading: false }) this.setData({ records: [], loading: false, _loaded: true })
this.filterRecords() this.filterRecords()
} }
}, },
+4 -1
View File
@@ -14,13 +14,16 @@
<view class="tab {{currentTab === 'rejected' ? 'tab-active' : ''}}" bindtap="switchTab" data-tab="rejected"> <view class="tab {{currentTab === 'rejected' ? 'tab-active' : ''}}" bindtap="switchTab" data-tab="rejected">
已拒绝 已拒绝
</view> </view>
<view class="tab {{currentTab === 'cancelled' ? 'tab-active' : ''}}" bindtap="switchTab" data-tab="cancelled">
已取消
</view>
</view> </view>
<!-- 记录列表 --> <!-- 记录列表 -->
<view class="record-list" wx:if="{{!loading && filteredRecords.length > 0}}"> <view class="record-list" wx:if="{{!loading && filteredRecords.length > 0}}">
<view class="record-card" wx:for="{{filteredRecords}}" wx:key="_id"> <view class="record-card" wx:for="{{filteredRecords}}" wx:key="_id">
<view class="record-header"> <view class="record-header">
<text class="record-id">{{item.id}}</text> <text class="record-id">{{item._id}}</text>
<view class="status-tag status-{{item.status}}"> <view class="status-tag status-{{item.status}}">
{{item.statusText}} {{item.statusText}}
</view> </view>
+53 -29
View File
@@ -1,7 +1,33 @@
// 云数据库操作工具库 // 云数据库操作工具库
const db = wx.cloud.database() /**
const _ = db.command * 延迟获取 db 实例,确保 cloud.init 已完成
*/
function getDb() {
return wx.cloud.database()
}
function getCmd() {
return getDb().command
}
/**
* 格式化云数据库时间字段
* @param {object} record - 含 createTime 的记录
* @returns {object} 格式化后的记录
*/
function formatRecord(record) {
const date = record.createTime
let createTimeStr = ''
if (date) {
if (typeof date === 'object' && date.$date) {
createTimeStr = new Date(date.$date).toLocaleString('zh-CN')
} else {
createTimeStr = new Date(date).toLocaleString('zh-CN')
}
}
return { ...record, createTime: createTimeStr }
}
/** /**
* 用户相关操作 * 用户相关操作
@@ -10,50 +36,32 @@ const userDB = {
/** /**
* 通过 openId 查找用户,不存在则创建 * 通过 openId 查找用户,不存在则创建
* @param {string} openid * @param {string} openid
* @param {object} userInfo - { avatarUrl, nickName }
* @returns {Promise<object>} 用户记录 * @returns {Promise<object>} 用户记录
*/ */
async loginOrCreate(openid, userInfo) { async loginOrCreate(openid) {
const db = getDb()
const res = await db.collection('users').where({ _openid: openid }).get() const res = await db.collection('users').where({ _openid: openid }).get()
if (res.data.length > 0) { if (res.data.length > 0) {
// 已存在,更新头像昵称
const existing = res.data[0] const existing = res.data[0]
await db.collection('users').doc(existing._id).update({ await db.collection('users').doc(existing._id).update({
data: { data: {
avatarUrl: userInfo.avatarUrl,
nickName: userInfo.nickName,
lastLoginTime: db.serverDate() lastLoginTime: db.serverDate()
} }
}) })
return { ...existing, avatarUrl: userInfo.avatarUrl, nickName: userInfo.nickName } return existing
} else { } else {
// 不存在,新建
const addRes = await db.collection('users').add({ const addRes = await db.collection('users').add({
data: { data: {
_openid: openid, _openid: openid,
avatarUrl: userInfo.avatarUrl,
nickName: userInfo.nickName,
createTime: db.serverDate(), createTime: db.serverDate(),
lastLoginTime: db.serverDate() lastLoginTime: db.serverDate()
} }
}) })
return { return {
_id: addRes._id, _id: addRes._id,
_openid: openid, _openid: openid
avatarUrl: userInfo.avatarUrl,
nickName: userInfo.nickName
} }
} }
},
/**
* 根据 openId 获取用户信息
* @param {string} openid
* @returns {Promise<object|null>}
*/
async getByOpenId(openid) {
const res = await db.collection('users').where({ _openid: openid }).get()
return res.data.length > 0 ? res.data[0] : null
} }
} }
@@ -67,6 +75,7 @@ const appointmentDB = {
* @returns {Promise<string>} 新记录 _id * @returns {Promise<string>} 新记录 _id
*/ */
async create(data) { async create(data) {
const db = getDb()
const res = await db.collection('appointments').add({ const res = await db.collection('appointments').add({
data: { data: {
...data, ...data,
@@ -79,16 +88,29 @@ const appointmentDB = {
}, },
/** /**
* 获取当前用户的预约列表(按创建时间倒序) * 获取当前用户的预约列表(按创建时间倒序,自动分页取全部
* @param {string} openid * @param {string} openid
* @returns {Promise<Array>} * @returns {Promise<Array>}
*/ */
async getList(openid) { async getList(openid) {
const db = getDb()
const MAX_LIMIT = 100
let allData = []
let countResult = await db.collection('appointments').where({ _openid: openid }).count()
const total = countResult.total
const batchTimes = Math.ceil(total / MAX_LIMIT)
for (let i = 0; i < batchTimes; i++) {
const res = await db.collection('appointments') const res = await db.collection('appointments')
.where({ _openid: openid }) .where({ _openid: openid })
.orderBy('createTime', 'desc') .orderBy('createTime', 'desc')
.skip(i * MAX_LIMIT)
.limit(MAX_LIMIT)
.get() .get()
return res.data allData = allData.concat(res.data)
}
return allData
}, },
/** /**
@@ -97,6 +119,7 @@ const appointmentDB = {
* @returns {Promise<object|null>} * @returns {Promise<object|null>}
*/ */
async getLatest(openid) { async getLatest(openid) {
const db = getDb()
const res = await db.collection('appointments') const res = await db.collection('appointments')
.where({ _openid: openid }) .where({ _openid: openid })
.orderBy('createTime', 'desc') .orderBy('createTime', 'desc')
@@ -112,7 +135,7 @@ const appointmentDB = {
* @returns {Promise<boolean>} 是否成功 * @returns {Promise<boolean>} 是否成功
*/ */
async cancel(id, openid) { async cancel(id, openid) {
// 先校验该预约属于当前用户 const db = getDb()
const res = await db.collection('appointments').doc(id).get() const res = await db.collection('appointments').doc(id).get()
if (res.data._openid !== openid) { if (res.data._openid !== openid) {
return false return false
@@ -131,8 +154,9 @@ const appointmentDB = {
} }
module.exports = { module.exports = {
db, getDb,
_, getCmd,
formatRecord,
userDB, userDB,
appointmentDB appointmentDB
} }