diff --git a/app.js b/app.js
index 569f0b0..b40f29c 100644
--- a/app.js
+++ b/app.js
@@ -25,24 +25,45 @@ App({
async userLogin(options) {
try {
// 获取微信登录凭证
- const { code } = await new Promise((resolve, reject) => {
+ const loginResult = await new Promise((resolve, reject) => {
wx.login({
- success: resolve,
- fail: reject
+ success: (res) => {
+ if (res.code) {
+ resolve(res);
+ } else {
+ reject(new Error('获取微信登录凭证失败:' + (res.errMsg || '未知错误')));
+ }
+ },
+ fail: (error) => reject(new Error('微信登录失败:' + (error.errMsg || '未知错误')))
});
});
+ if (!loginResult.code) {
+ throw new Error('未获取到微信登录凭证');
+ }
+
// 调用后端登录接口
- const loginResult = await cloud.request({
- url: config.API_ENDPOINTS.AUTH.LOGIN,
+ const response = await cloud.request(config.API_ENDPOINTS.AUTH.LOGIN, {
method: 'POST',
- data: { code },
- needToken: false
+ data: { code: loginResult.code },
+ header: {
+ 'Content-Type': 'application/json'
+ }
});
- // 保存登录信息到storage
- wx.setStorageSync(config.JWT_CONFIG.storage.access, loginResult.accessToken);
- wx.setStorageSync(config.JWT_CONFIG.storage.refresh, loginResult.refreshToken);
+ // 检查响应数据
+ if (!response || typeof response !== 'object') {
+ throw new Error('登录响应格式错误');
+ }
+
+ // 检查并保存登录信息到storage
+ if (!response.access_token || !response.refresh_token) {
+ console.error('登录响应数据:', response);
+ throw new Error('登录响应缺少必要的token信息');
+ }
+
+ wx.setStorageSync(config.JWT_CONFIG.storage.access, response.access_token);
+ wx.setStorageSync(config.JWT_CONFIG.storage.refresh, response.refresh_token);
// 初始化基本用户信息
const userInfo = {
@@ -51,9 +72,6 @@ App({
};
this.globalData.userInfo = userInfo;
- // 同步OTP数据
- await this.syncOtpData();
-
return userInfo;
} catch (error) {
console.error('登录失败:', error);
@@ -62,28 +80,52 @@ App({
}
},
- async syncOtpData() {
- try {
- // 获取本地数据
- const localTokens = this.globalData.otpList;
-
- // 使用util.js中的syncTokens函数进行同步
- // 注意:syncTokens函数已经被更新为使用cloud模块
- const syncedTokens = await syncTokens(localTokens);
-
- // 更新本地存储和全局数据
- wx.setStorageSync('tokens', syncedTokens);
- this.globalData.otpList = syncedTokens;
-
- } catch (error) {
- console.error('同步OTP数据失败:', error);
- // 同步失败不显示错误提示,因为这是自动同步过程
+ // 全局退出登录方法
+ logout() {
+ // 清除token相关storage数据(保留tokens存储)
+ wx.removeStorageSync(config.JWT_CONFIG.storage.access);
+ wx.removeStorageSync(config.JWT_CONFIG.storage.refresh);
+
+ // 保留用户自定义头像和昵称
+ const customAvatar = wx.getStorageSync('customAvatar');
+ const customNickName = wx.getStorageSync('customNickName');
+
+ // 清除其他用户数据
+ wx.removeStorageSync('userAvatar');
+ wx.removeStorageSync('userNickName');
+
+ // 恢复自定义设置
+ if (customAvatar) wx.setStorageSync('userAvatar', customAvatar);
+ if (customNickName) wx.setStorageSync('userNickName', customNickName);
+
+ // 重置全局数据(保留otpList)
+ this.globalData.userInfo = null;
+
+ // 触发全局事件通知
+ if (this.globalData.eventEmitter) {
+ this.globalData.eventEmitter.emit('logout');
}
},
globalData: {
version: 103,
otpList: [],
- userInfo: null
+ userInfo: null,
+ // 添加简单的事件发射器
+ eventEmitter: {
+ listeners: {},
+ on(event, callback) {
+ if (!this.listeners[event]) {
+ this.listeners[event] = [];
+ }
+ this.listeners[event].push(callback);
+ },
+ emit(event, ...args) {
+ const callbacks = this.listeners[event];
+ if (callbacks) {
+ callbacks.forEach(cb => cb(...args));
+ }
+ }
+ }
}
})
\ No newline at end of file
diff --git a/pages/edit/edit.js b/pages/edit/edit.js
index a9824af..ec717a1 100644
--- a/pages/edit/edit.js
+++ b/pages/edit/edit.js
@@ -7,7 +7,7 @@ Page({
*/
data: {
issuer: '',
- remark: '',
+ account: '',
secret: '',
type: '',
counter: 0,
@@ -64,14 +64,18 @@ Page({
return
}
+ const type = (targetToken.type || 'totp').toLowerCase();
+ const isHotp = type === 'hotp';
+
self.setData({
tokens: tokens,
issuer: targetToken.issuer || '',
- remark: targetToken.remark || '',
+ account: targetToken.account || '',
secret: targetToken.secret || '',
- type: targetToken.type || 'totp',
- counter: targetToken.counter || 0,
- isHotp: targetToken.type === 'hotp',
+ type: type,
+ // 只有HOTP类型才设置counter
+ counter: isHotp ? (targetToken.counter || 0) : undefined,
+ isHotp: isHotp,
hasLoaded: true
})
},
@@ -160,18 +164,28 @@ Page({
}
// 更新令牌数据
- updatedTokens[tokenIndex] = {
+ const type = this.data.type.toLowerCase();
+ const isHotp = type === 'hotp';
+
+ // 创建基本的更新对象
+ const updatedToken = {
...updatedTokens[tokenIndex],
issuer: values.issuer.trim(),
- remark: (values.remark || '').trim(),
+ account: (values.account || '').trim(),
secret: this.data.secret, // 使用已加载的secret,而不是从表单获取
- type: this.data.type
- }
+ type: type
+ };
// 如果是HOTP类型,更新计数器值
- if (this.data.isHotp && values.counter !== undefined) {
- updatedTokens[tokenIndex].counter = parseInt(values.counter)
+ if (isHotp && values.counter !== undefined) {
+ updatedToken.counter = parseInt(values.counter);
+ } else if (!isHotp) {
+ // 如果是TOTP类型,删除counter字段
+ delete updatedToken.counter;
}
+
+ // 更新令牌数组
+ updatedTokens[tokenIndex] = updatedToken;
wx.setStorage({
key: 'tokens',
@@ -223,7 +237,7 @@ Page({
hasLoaded: false,
isSubmitting: false,
issuer: '',
- remark: '',
+ account: '',
secret: '',
type: '',
counter: 0,
diff --git a/pages/edit/edit.wxml b/pages/edit/edit.wxml
index 6d9be71..d376ba5 100644
--- a/pages/edit/edit.wxml
+++ b/pages/edit/edit.wxml
@@ -8,7 +8,7 @@
Account
-
+
diff --git a/pages/form/form.js b/pages/form/form.js
index be7e276..3d50fb6 100644
--- a/pages/form/form.js
+++ b/pages/form/form.js
@@ -20,16 +20,20 @@ Page({
*/
onLoad: function (options) {
// 准备初始数据
+ const type = (options.type || 'totp').toLowerCase();
+ const isHotp = type === 'hotp';
+
const initialData = {
- type: options.type || 'totp',
+ type: type,
formData: {
issuer: '',
- remark: '',
+ account: '',
secret: '',
- algo: 'SHA1',
+ algorithm: 'SHA1',
digits: '6',
period: '30',
- counter: '0'
+ // 只有HOTP类型才设置counter初始值
+ ...(isHotp ? { counter: '0' } : {})
},
pageReady: true // 直接设置为ready状态
};
@@ -41,15 +45,19 @@ Page({
const parsedToken = parseURL(scanData);
if (parsedToken) {
- initialData.type = parsedToken.type;
+ const type = (parsedToken.type || 'totp').toLowerCase();
+ const isHotp = type === 'hotp';
+
+ initialData.type = type;
initialData.formData = {
issuer: parsedToken.issuer || '',
- remark: parsedToken.remark || '',
+ account: parsedToken.account || '',
secret: parsedToken.secret || '',
- algo: parsedToken.algo || 'SHA1',
+ algorithm: parsedToken.algorithm || 'SHA1',
digits: parsedToken.digits || '6',
period: parsedToken.period || '30',
- counter: parsedToken.counter || '0'
+ // 只有HOTP类型才设置counter
+ ...(isHotp ? { counter: parsedToken.counter || '0' } : {})
};
// 立即显示成功提示
@@ -91,8 +99,8 @@ Page({
if (!values.issuer || !values.issuer.trim()) {
throw new Error('请输入服务名称');
}
- if (!values.remark || !values.remark.trim()) {
- throw new Error('请输入账号备注');
+ if (!values.account || !values.account.trim()) {
+ throw new Error('请输入账户名称');
}
if (!values.secret || !values.secret.trim()) {
throw new Error('请输入密钥');
@@ -100,16 +108,16 @@ Page({
// 格式化数据
const tokenData = {
- type: values.type,
+ type: this.data.type,
issuer: values.issuer.trim(),
- remark: values.remark.trim(),
+ account: values.account.trim(),
secret: values.secret.trim().toUpperCase(),
- algo: values.algo,
+ algorithm: values.algorithm,
digits: parseInt(values.digits, 10)
};
// 类型特定字段
- if (values.type === 'totp') {
+ if (this.data.type === 'totp') {
const period = parseInt(values.period, 10);
if (isNaN(period) || period < 15 || period > 300) {
throw new Error('更新周期必须在15-300秒之间');
diff --git a/pages/form/form.wxml b/pages/form/form.wxml
index f13face..48fda66 100644
--- a/pages/form/form.wxml
+++ b/pages/form/form.wxml
@@ -10,7 +10,7 @@
Account
-
+
KEY
@@ -35,10 +35,10 @@
高级设置
算法
-
- SHA1
- SHA256
- SHA512
+
+ SHA1
+ SHA256
+ SHA512
diff --git a/pages/index/index.js b/pages/index/index.js
index 7de06f9..9c7ef0f 100644
--- a/pages/index/index.js
+++ b/pages/index/index.js
@@ -222,14 +222,17 @@ Page({
// 为每个token计算正确的时间戳
const updatePromises = tokensToUpdate.map(token => {
+ // 优先使用令牌中的时间戳(如果有的话)
+ const tokenTimestamp = token.timestamp || currentTimestamp;
+
if (token.type === 'totp') {
// 计算时间窗口的开始时间
const period = token.period || 30;
- const windowStart = Math.floor(currentTimestamp / period) * period;
+ const windowStart = Math.floor(tokenTimestamp / period) * period;
return this.updateTokenCode(token, windowStart);
} else {
- // 对于HOTP类型,直接使用当前时间戳
- return this.updateTokenCode(token, currentTimestamp);
+ // 对于HOTP类型,直接使用时间戳
+ return this.updateTokenCode(token, tokenTimestamp);
}
});
@@ -263,14 +266,17 @@ Page({
// 并行更新所有令牌的验证码,为每个令牌计算其时间窗口的开始时间
const updatePromises = tokens.map(token => {
+ // 优先使用令牌中的时间戳(如果有的话)
+ const tokenTimestamp = token.timestamp || currentTimestamp;
+
if (token.type === 'totp') {
// 计算时间窗口的开始时间
const period = token.period || 30;
- const windowStart = Math.floor(currentTimestamp / period) * period;
+ const windowStart = Math.floor(tokenTimestamp / period) * period;
return this.updateTokenCode(token, windowStart);
} else {
- // 对于HOTP类型,直接使用当前时间戳
- return this.updateTokenCode(token, currentTimestamp);
+ // 对于HOTP类型,直接使用时间戳
+ return this.updateTokenCode(token, tokenTimestamp);
}
});
@@ -426,73 +432,33 @@ Page({
try {
// 解析二维码内容
const qrContent = res.result;
- // 如果是otpauth://格式的URL
- if (qrContent.startsWith('otpauth://')) {
- // 小程序兼容的URL解析
- const [protocolAndPath, search] = qrContent.split('?');
- const [protocol, path] = protocolAndPath.split('://');
- const type = protocol.replace('otpauth:', '');
-
- // 解析路径部分
- const decodedPath = decodeURIComponent(path.substring(1)); // 移除开头的/
- let [issuer, remark] = decodedPath.split(':');
- if (!remark) {
- remark = issuer;
- issuer = '';
- }
-
- // 解析查询参数
- const params = {};
- if (search) {
- search.split('&').forEach(pair => {
- const [key, value] = pair.split('=');
- if (key && value) {
- params[key] = decodeURIComponent(value);
- }
- });
- }
-
- // 从参数中获取issuer(如果存在)
- if (params.issuer) {
- issuer = params.issuer;
- }
-
- // 验证secret参数
- if (!params.secret) {
- wx.showToast({
- title: '无效的二维码:缺少secret参数',
- icon: 'none',
- duration: 2000
- });
- return;
- }
-
- // 将otpauth类型转换为实际类型
- let validType = type.toLowerCase();
- if (validType === 'otpauth') {
- // 从URI路径中提取实际类型
- validType = path.split('/')[0].toLowerCase();
- }
-
- // 构建表单数据,确保数字类型参数正确转换
+
+ // 导入util.js中的parseURL函数
+ const { parseURL } = require('../../utils/util');
+
+ // 使用parseURL函数解析二维码内容
+ const parsedToken = parseURL(qrContent);
+
+ if (parsedToken) {
+ // 构建表单数据
const formData = {
- type: validType,
- issuer,
- remark,
- secret: params.secret,
- algorithm: params.algorithm || 'SHA1',
- digits: params.digits ? parseInt(params.digits, 10) : 6,
- period: validType === 'totp' ? (params.period ? parseInt(params.period, 10) : 30) : undefined,
- counter: validType === 'hotp' ? (params.counter ? parseInt(params.counter, 10) : 0) : undefined
+ type: parsedToken.type,
+ issuer: parsedToken.issuer || '',
+ account: parsedToken.account || '',
+ secret: parsedToken.secret || '',
+ algorithm: parsedToken.algorithm || 'SHA1',
+ digits: parsedToken.digits || 6,
+ period: parsedToken.type === 'totp' ? (parsedToken.period || 30) : undefined,
+ counter: parsedToken.type === 'hotp' ? (parsedToken.counter || 0) : undefined
};
-
+
// 验证必要参数
if (formData.digits < 6 || formData.digits > 8) {
formData.digits = 6;
console.warn('验证码位数无效,已设置为默认值6');
}
- if (validType === 'totp' && (formData.period < 15 || formData.period > 60)) {
+ if (formData.type === 'totp' && (formData.period < 15 || formData.period > 60)) {
formData.period = 30;
console.warn('TOTP周期无效,已设置为默认值30秒');
}
@@ -640,14 +606,22 @@ Page({
// 获取当前令牌列表
const tokens = await wx.getStorageSync('tokens') || [];
+ // 生成唯一ID和时间戳
// 生成唯一ID和时间戳
const newToken = {
...tokenData,
id: Date.now().toString(),
- createdAt: new Date().toISOString(),
- lastUpdate: new Date().toISOString()
+ createTime: formatTime(new Date()),
+ lastUpdate: formatTime(new Date()),
+ code: '' // 初始化为空字符串
};
+ // 对于HOTP类型,添加counter字段
+ if (tokenData.type && tokenData.type.toUpperCase() === 'HOTP') {
+ newToken.counter = 0; // HOTP类型需要counter >= 0
+ }
+ // 对于TOTP类型,不设置counter字段,让它在JSON序列化时被忽略
+
// 如果是TOTP类型,先初始化剩余时间
if ((newToken.type || 'totp').toLowerCase() === 'totp') {
const period = parseInt(newToken.period || '30', 10);
diff --git a/pages/index/index.skeleton.wxml b/pages/index/index.skeleton.wxml
new file mode 100644
index 0000000..473c636
--- /dev/null
+++ b/pages/index/index.skeleton.wxml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+ 823945
+
+
+ ✏️
+
+
+ 🗑️
+
+
+
+
+
+ 剩余 29 秒
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ 添加验证器
+
+
+
+
\ No newline at end of file
diff --git a/pages/index/index.skeleton.wxss b/pages/index/index.skeleton.wxss
new file mode 100644
index 0000000..5285e5f
--- /dev/null
+++ b/pages/index/index.skeleton.wxss
@@ -0,0 +1,69 @@
+/*
+此文件为开发者工具生成,生成时间: 2025/6/12上午10:26:00
+
+在 D:\Project\go\src\otp\miniprogram\pages\index\index.wxss 中引入样式
+```
+@import "./index.skeleton.wxss";
+```
+
+更多详细信息可以参考文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/skeleton.html
+*/
+.sk-transparent {
+ color: transparent !important;
+ }
+.sk-text-14-2857-186 {
+ background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
+ background-size: 100% 32.3077rpx;
+ position: relative !important;
+ }
+.sk-text {
+ background-origin: content-box !important;
+ background-clip: content-box !important;
+ background-color: transparent !important;
+ color: transparent !important;
+ background-repeat: repeat-y !important;
+ }
+.sk-text-14-2857-326 {
+ background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
+ background-size: 100% 43.0769rpx;
+ position: relative !important;
+ }
+.sk-text-14-2857-212 {
+ background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
+ background-size: 100% 64.6154rpx;
+ position: relative !important;
+ }
+.sk-text-14-2857-783 {
+ background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
+ background-size: 100% 43.0769rpx;
+ position: relative !important;
+ }
+.sk-text-14-2857-193 {
+ background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
+ background-size: 100% 43.0769rpx;
+ position: relative !important;
+ }
+.sk-text-14-2857-416 {
+ background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
+ background-size: 100% 32.3077rpx;
+ position: relative !important;
+ }
+.sk-text-14-2857-977 {
+ background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
+ background-size: 100% 53.8462rpx;
+ position: relative !important;
+ }
+.sk-text-14-2857-863 {
+ background-image: linear-gradient(transparent 14.2857%, #EEEEEE 0%, #EEEEEE 85.7143%, transparent 0%) !important;
+ background-size: 100% 37.6923rpx;
+ position: relative !important;
+ }
+.sk-container {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ background-color: transparent;
+ }
diff --git a/pages/mine/mine.js b/pages/mine/mine.js
index 7f1b5b8..27b49d6 100644
--- a/pages/mine/mine.js
+++ b/pages/mine/mine.js
@@ -6,6 +6,8 @@ const {
showLoading,
hideLoading
} = require('../../utils/util');
+const config = require('../../utils/config');
+const { hasValidTokens, clearAllTokens } = require('../../utils/cloud');
Page({
/**
@@ -14,6 +16,7 @@ Page({
data: {
loading: false,
uploading: false,
+ clearing: false,
isLoggedIn: false,
userInfo: null,
currentYear: new Date().getFullYear()
@@ -22,9 +25,27 @@ Page({
onShow: function() {
// 每次显示页面时检查登录状态
const app = getApp();
+ const isLoggedIn = hasValidTokens();
+
+ // 如果未登录,强制重置用户信息
+ if (!isLoggedIn) {
+ this.setData({
+ isLoggedIn: false,
+ userInfo: {
+ avatarUrl: '/images/default-avatar.png',
+ nickName: '微信用户'
+ }
+ });
+ return;
+ }
+
+ // 如果已登录,优先使用globalData中的用户信息
this.setData({
- isLoggedIn: !!app.globalData.token,
- userInfo: app.globalData.userInfo
+ isLoggedIn: true,
+ userInfo: app.globalData.userInfo || {
+ avatarUrl: '/images/default-avatar.png',
+ nickName: '微信用户'
+ }
});
},
@@ -114,7 +135,10 @@ Page({
showToast('数据恢复成功', 'success');
} catch (error) {
- console.error('数据恢复失败:', error);
+ // 如果是404错误(云端无数据),不打印错误日志
+ if (error.statusCode !== 404) {
+ console.error('数据恢复失败:', error);
+ }
showToast('数据恢复失败,请重试');
} finally {
this.setData({ loading: false });
@@ -131,21 +155,33 @@ Page({
try {
showLoading('登录中...');
const app = getApp();
- await app.userLogin();
+ const userInfo = await app.userLogin();
- // 更新登录状态,使用默认值
+ // 使用hasValidTokens检查完整的登录状态
+ const isLoggedIn = hasValidTokens();
+
+ if (!isLoggedIn) {
+ throw new Error('登录失败:未能获取有效的访问令牌');
+ }
+
+ // 检查是否有自定义设置
+ const customAvatar = wx.getStorageSync('customAvatar');
+ const customNickName = wx.getStorageSync('customNickName');
+
+ // 更新登录状态,优先使用自定义设置
this.setData({
- isLoggedIn: !!app.globalData.token,
+ isLoggedIn: true,
userInfo: {
- avatarUrl: wx.getStorageSync('userAvatar') || '/images/default-avatar.png',
- nickName: wx.getStorageSync('userNickName') || '微信用户'
+ ...(userInfo || {}),
+ avatarUrl: customAvatar || wx.getStorageSync('userAvatar') || '/images/default-avatar.png',
+ nickName: customNickName || wx.getStorageSync('userNickName') || '微信用户'
}
});
showToast('登录成功', 'success');
} catch (error) {
console.error('登录失败:', error);
- showToast('登录失败,请重试');
+ showToast(error.message || '登录失败,请重试');
} finally {
hideLoading();
}
@@ -164,12 +200,25 @@ Page({
'userInfo.avatarUrl': avatarUrl
});
- // 保存到本地存储
- wx.setStorageSync('userAvatar', avatarUrl);
+ // 使用异步存储操作
+ await Promise.all([
+ wx.setStorage({
+ key: 'userAvatar',
+ data: avatarUrl
+ }),
+ wx.setStorage({
+ key: 'customAvatar',
+ data: avatarUrl
+ })
+ ]);
showToast('头像更新成功', 'success');
} catch (error) {
console.error('头像更新失败:', error);
+ // 恢复默认头像
+ this.setData({
+ 'userInfo.avatarUrl': '/images/default-avatar.png'
+ });
showToast('头像更新失败,请重试');
} finally {
hideLoading();
@@ -191,6 +240,8 @@ Page({
// 保存到本地存储
wx.setStorageSync('userNickName', nickName);
+ // 额外保存为自定义昵称
+ wx.setStorageSync('customNickName', nickName);
} catch (error) {
console.error('昵称更新失败:', error);
showToast('昵称更新失败,请重试');
@@ -204,6 +255,107 @@ Page({
wx.stopPullDownRefresh();
},
+ /**
+ * 退出登录
+ */
+ onLoad: function() {
+ const app = getApp();
+ // 监听全局logout事件
+ app.globalData.eventEmitter.on('logout', this.handleLogout.bind(this));
+ },
+
+ onUnload: function() {
+ const app = getApp();
+ // 移除事件监听
+ if (app.globalData.eventEmitter && this.handleLogout) {
+ app.globalData.eventEmitter.off('logout', this.handleLogout);
+ }
+ },
+
+ handleLogout: function() {
+ // 重置页面数据
+ this.setData({
+ isLoggedIn: false,
+ userInfo: {
+ avatarUrl: '/images/default-avatar.png',
+ nickName: '微信用户'
+ }
+ });
+ },
+
+ /**
+ * 清空云端数据
+ */
+ clearCloudData: async function() {
+ // 检查登录状态
+ if (!this.data.isLoggedIn) {
+ showToast('请先登录');
+ return;
+ }
+
+ try {
+ // 显示确认对话框
+ const confirmed = await new Promise(resolve => {
+ wx.showModal({
+ title: '清空云端数据',
+ content: '确定清空云端所有备份数据?此操作不可恢复!',
+ confirmText: '确定',
+ confirmColor: '#ff9c10',
+ success: res => resolve(res.confirm)
+ });
+ });
+
+ if (!confirmed) return;
+
+ this.setData({ clearing: true });
+ showLoading('正在清空...');
+
+ // 调用清空接口
+ await clearAllTokens();
+
+ showToast('云端数据已清空', 'success');
+ } catch (error) {
+ console.error('清空云端数据失败:', error);
+ showToast(error.message || '清空失败,请重试');
+ } finally {
+ this.setData({ clearing: false });
+ hideLoading();
+ }
+ },
+
+ logout: async function() {
+ try {
+ // 显示确认对话框
+ const confirmed = await new Promise(resolve => {
+ wx.showModal({
+ title: '退出登录',
+ content: '确定要退出登录吗?',
+ confirmText: '确定',
+ confirmColor: '#ff9c10',
+ success: res => resolve(res.confirm)
+ });
+ });
+
+ if (!confirmed) return;
+
+ showLoading('正在退出...');
+
+ // 调用全局logout方法
+ const app = getApp();
+ app.logout();
+
+ // 本地UI更新
+ this.handleLogout();
+
+ showToast('已退出登录', 'success');
+ } catch (error) {
+ console.error('退出登录失败:', error);
+ showToast('退出登录失败,请重试');
+ } finally {
+ hideLoading();
+ }
+ },
+
/**
* 转发
*/
diff --git a/pages/mine/mine.wxml b/pages/mine/mine.wxml
index fa0df1c..b403a51 100644
--- a/pages/mine/mine.wxml
+++ b/pages/mine/mine.wxml
@@ -5,13 +5,13 @@
@@ -20,6 +20,8 @@
+
+