fix UI and add coupon

This commit is contained in:
Felix
2025-11-08 20:38:43 +08:00
parent 003ca1effb
commit e9af707187
13 changed files with 459 additions and 115 deletions

View File

@@ -15,31 +15,30 @@
<view class="section">
<text class="subtitle">1. 我们收集的信息</text>
<text class="paragraph">为向您提供核心功能,我们会收集以下类型的信息:</text>
<text class="paragraph">微信身份标识信息: 当您使用微信登录时,我们会获取您的微信头像、昵称和唯一的OpenID。这是我们识别您身份的必要信息。</text>
<text class="paragraph">您上传的图片信息: 您主动选择并上传的用于物体识别的图片。请注意,请勿上传包含个人隐私、他人肖像、敏感信息的图片。</text>
<text class="paragraph">语音信息(未来功能): 如果您使用发音跟读与评测功能,我们会收集并处理您的语音录音。</text>
<text class="paragraph">设备与日志信息: 为保障服务安全运行我们会自动收集设备型号、操作系统版本、IP地址、访问时间、错误日志等信息。</text>
<text class="paragraph">微信身份标识信息:当您使用微信登录时,我们会获取您的微信 OpenID。这是我们识别您身份的必要信息。</text>
<text class="paragraph">您上传的图片信息:您主动选择并上传的用于识别的图片。请注意,请勿上传包含个人隐私、他人肖像、敏感信息的图片。</text>
<text class="paragraph">您上传的语音信息:如果您使用发音评测功能,我们会收集并处理您的语音录音。</text>
<text class="paragraph">设备与日志信息为保障服务安全运行我们会自动收集设备型号、操作系统版本、IP地址、访问时间、错误日志等信息。</text>
</view>
<view class="section">
<text class="subtitle">2. 我们如何使用这些信息</text>
<text class="paragraph">我们仅会出于以下目的处理您的信息:</text>
<text class="paragraph">提供与优化服务: 使用您的微信信息为您创建账户标识使用第三方AI模型处理您上传的图片以返回单词处理您的语音以提供发音反馈。</text>
<text class="paragraph">安全与运维: 利用日志信息排查故障、防范安全风险。</text>
<text class="paragraph">提供与优化服务使用您的微信信息为您创建账户标识使用第三方AI模型处理您上传的图片以返回单词处理您的语音以提供发音反馈。</text>
<text class="paragraph">安全与运维:利用日志信息排查故障、防范安全风险。</text>
</view>
<view class="section">
<text class="subtitle">3. 我们如何共享、转让、公开披露您的信息</text>
<text class="paragraph">共享: 我们不会出售您的个人信息。我们会在以下情况下与第三方共享信息:</text>
<text class="paragraph">与必要的第三方服务提供商共享: 为实现识别功能,我们必须将您上传的图片数据共享给阿里巴巴的Qwen模型等AI技术服务商。我们只会共享提供服务所必要的信息并采取严格的保密和安全措施。</text>
<text class="paragraph">应法律要求: 如为遵守适用的法律法规、法院指令或政府要求,我们可能会披露您的信息。</text>
<text class="paragraph">转让与公开披露: 我们不会将您的个人信息转让给任何公司、组织或个人,也不会公开披露,除非事先获得您的明确同意或法律强制要求。</text>
<text class="paragraph">与必要的第三方服务提供商共享:为实现识别功能,我们必须将您上传的图片数据共享给第三方 AI 技术服务商。我们只会共享提供服务所必要的信息,并采取严格的保密和安全措施。</text>
<text class="paragraph">应法律要求:如为遵守适用的法律法规、法院指令或政府要求,我们可能会披露您的信息。</text>
<text class="paragraph">转让与公开披露:我们不会将您的个人信息转让给任何公司、组织或个人,也不会公开披露,除非事先获得您的明确同意或法律强制要求。</text>
</view>
<view class="section">
<text class="subtitle">4. 数据的存储与保护</text>
<text class="paragraph">存储地点: 我们在中华人民共和国境内收集和产生的个人信息,将存储在境内。</text>
<text class="paragraph">存储期限: 您的微信OpenID我们会长期存储以维持您的账户。您上传的图片在我们向AI服务商发送并返回结果后AI服务商根据其政策可能会短暂缓存处理数据。语音数据若收集的处理方式将在该功能上线时在本政策中明确说明。</text>
<text class="paragraph">存储地点:我们在中华人民共和国境内收集和产生的个人信息,将存储在境内。</text>
<text class="paragraph">安全措施: 我们采用符合行业标准的安全技术如HTTPS加密传输和管理流程来防止信息泄露、篡改或损坏。</text>
</view>
@@ -53,12 +52,12 @@
<view class="section">
<text class="subtitle">6. 第三方服务说明</text>
<text class="paragraph">本服务依赖第三方AI服务商如阿里巴巴QWEN处理图像。该处理过程受其自身隐私政策的约束,我们无法直接控制其服务器端的数据处理行为。 我们建议您了解其主要隐私政策(例如,阿里巴巴的隐私政策)。</text>
<text class="paragraph">本服务依赖第三方AI服务商处理图像。该处理过程受其自身隐私政策的约束我们无法直接控制其服务器端的数据处理行为。 我们建议您了解其主要隐私政策(例如,通义千问的隐私政策)。</text>
</view>
<view class="section">
<text class="subtitle">7. 儿童隐私保护</text>
<text class="paragraph">我们不建议未满14周岁的儿童自行使用本服务。如果您是儿童监护人,请确保儿童在您的指导下使用,并已同意我们按照本政策处理儿童信息。如果您发现我们在不知情的情况下收集了儿童信息,请立即联系我们,我们将尽快删除。</text>
<text class="paragraph">我们不建议未满10周岁的儿童自行使用本服务。如果您是儿童监护人,请确保儿童在您的指导下使用,并已同意我们按照本政策处理儿童信息。如果您发现我们在不知情的情况下收集了儿童信息,请立即联系我们,我们将尽快删除。</text>
</view>
<view class="section">

View File

@@ -27,16 +27,16 @@
}
.subtitle {
font-size: 28rpx;
font-size: 32rpx;
font-weight: bold;
color: #007AFF;
color: #33272a;
display: block;
margin-bottom: 16rpx;
}
.paragraph {
font-size: 26rpx;
color: #666666;
font-size: 24rpx;
color: #594a4e;
line-height: 40rpx;
display: block;
}

View File

@@ -2,7 +2,9 @@
"usingComponents": {
"t-action-sheet": "tdesign-miniprogram/action-sheet/action-sheet",
"t-cell": "tdesign-miniprogram/cell/cell",
"t-dialog": "tdesign-miniprogram/dialog/dialog"
"t-dialog": "tdesign-miniprogram/dialog/dialog",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-input": "tdesign-miniprogram/input/input"
},
"navigationBarTitleText": "我的",
"navigationBarTextStyle": "black",

View File

@@ -10,9 +10,23 @@ Page({
isLoggedIn: false,
userInfo: null as IUserInfo | null,
// SVG data for avatar
avatarSvgData: '',
showClearConfirm: false,
dialogKey: '',
// 积分数据
points: {
balance: 0,
expired_time: ''
},
// 兑换码弹窗显示控制
showCouponInput: false,
couponCode: '',
couponCodeTips: '',
// 缓存统计信息
cacheStats: {
totalSize: '0KB',
@@ -49,6 +63,7 @@ Page({
onLoad() {
console.log('个人中心页面加载')
this.initPage()
this.generateAvatarSvg()
},
onShow() {
@@ -93,7 +108,7 @@ Page({
console.log('设置基础数据')
const basicData = {
isLoggedIn: app.globalData.isLoggedIn || false,
userInfo: app.globalData.userInfo || { nickName: '用户' },
userInfo: app.globalData.userInfo || null,
dictLevel: app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'LEVEL1'
}
@@ -113,8 +128,8 @@ Page({
this.setData({
...basicData,
cacheStat:cacheStats,
stats:stats
cacheStats: cacheStats,
stats: stats
})
console.log('基础数据设置完成')
@@ -133,7 +148,8 @@ Page({
await Promise.all([
// this.loadUserStats(),
// this.loadDictLevel(),
this.loadCacheStats()
this.loadCacheStats(),
this.loadPointsData()
])
// 更新用户信息
@@ -177,7 +193,7 @@ Page({
updateUserInfo() {
const userData = {
isLoggedIn: app.globalData.isLoggedIn || false,
userInfo: app.globalData.userInfo || { nickName: '用户' },
userInfo: app.globalData.userInfo || null,
dictLevel: app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'PRIMARY'
}
@@ -240,6 +256,33 @@ Page({
})
},
// 加载积分数据
async loadPointsData() {
try {
console.log('开始加载积分数据')
const startTime = Date.now()
// 调用API获取积分数据
const pointsData = await apiManager.getPointsData()
// 如果返回的数据为null则设置默认值
const finalPointsData = pointsData || { balance: 0, expired_time: '' }
const endTime = Date.now()
console.log('积分数据加载完成,耗时:', endTime - startTime, 'ms', finalPointsData)
this.setData({ points: finalPointsData })
} catch (error) {
console.error('加载积分数据失败:', error)
// 设置默认积分数据
this.setData({
points: {
balance: 0,
expired_time: ''
}
})
}
},
// 加载缓存统计信息
loadCacheStats() {
return new Promise<void>((resolve) => {
@@ -259,6 +302,9 @@ Page({
if (stats.sizeByType.image > 0) {
detailParts.push(`图片:${apiManager.formatFileSize(stats.sizeByType.image)}`)
}
if (stats.sizeByType.dict > 0) {
detailParts.push(`词典:${apiManager.formatFileSize(stats.sizeByType.dict)}`)
}
details = detailParts.join('')
}
@@ -499,5 +545,133 @@ Page({
query: '',
imageUrl: '/icon/share.png'
}
},
/**
* 更换头像方法
* 点击头像区域触发此方法
*/
changeAvatar() {
// TODO: 实现更换头像的逻辑
console.log('更换头像方法被触发');
// 这里可以添加选择图片、上传图片等逻辑
},
// Generate SVG data URL for avatar
generateAvatarSvg() {
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="470 50 20 40">
<path d="M 486 70 C 486 71.7614 483.761 74 481 74 C 478.239 74 476 71.7614 476 70 C 476 69 477 69 477 70 C 477 71 479 73 481 73 C 483 73 485 71 485 70 C 485 69 486 69 486 70 M 490 64 C 488.7 64 488.7 66 490 66 C 491.3 66 491.3 64 490 64 M 472 64 C 470.7 64 470.7 66 472 66 C 473.3 66 473.3 64 472 64" stroke="#001858" stroke-width="1.5" stroke-linecap="round"></path>
</svg>
`.trim();
const encodedSvg = encodeURIComponent(svgString);
const dataUrl = `data:image/svg+xml;utf8,${encodedSvg}`;
this.setData({
avatarSvgData: dataUrl
});
},
// 显示兑换码输入弹窗
showCouponDialog() {
this.setData({
showCouponInput: true,
couponCode: ''
});
},
// 关闭兑换码输入弹窗
closeCouponDialog() {
this.setData({
showCouponInput: false,
couponCode: '',
couponCodeTips: '' // Clear any error tips when closing dialog
});
},
// '' 处理兑换码输入
onCouponInput(e: any) {
let value = e.detail.value || '';
// 只允许字母和数字
value = value.replace(/[^A-Za-z0-9]/g, '');
// 转换为大写
value = value.toUpperCase();
this.setData({
couponCode: value,
couponCodeTips: '' // Clear any previous error tips when user types
});
},
// 确认兑换码
async confirmCouponDialog() {
const couponCode = this.data.couponCode;
// 检查兑换码是否为空
if (!couponCode) {
wx.showToast({
title: '请输入兑换码',
icon: 'none'
});
return;
}
// 检查兑换码长度假设为6位
console.log(couponCode)
console.log(couponCode.length)
if (couponCode.length !== 6) {
wx.showToast({
title: '兑换码应为6位',
icon: 'none'
});
return;
}
try {
// 显示加载提示
wx.showLoading({
title: '兑换中...'
});
// 调用API进行兑换
const response = await apiManager.redeemCoupon(couponCode);
// 隐藏加载提示
wx.hideLoading();
// 显示成功提示
wx.showToast({
title: '兑换成功',
icon: 'success'
});
// 关闭弹窗
this.closeCouponDialog();
// 如果有积分数据,更新积分显示
if (response.points) {
this.setData({
'points.balance': this.data.points.balance + response.points
});
}
} catch (error: any) {
// 隐藏加载提示
wx.hideLoading();
console.error('兑换失败:', error);
// 设置错误提示信息
const errorMessage = error.message || error.errMsg || '兑换失败,请稍后重试';
this.setData({
couponCodeTips: errorMessage
});
// 显示错误提示
wx.showToast({
title: errorMessage,
icon: 'none'
});
}
}
})

View File

@@ -8,8 +8,20 @@
refresher-enabled="{{false}}"
scroll-with-animation="{{true}}"
>
<!-- 用户信息 -->
<view class="avatar-section">
<view class="avatar-container" bindtap="changeAvatar">
<image wx:if="{{userInfo && userInfo.avatar_url}}" class="avatar-image" src="{{userInfo.avatar_url}}" mode="aspectFill"></image>
<view wx:else class="avatar-placeholder">
<image class="avatar-svg" src="{{avatarSvgData}}" mode="aspectFit"></image>
<!-- <text class="avatar-text">•ᴗ•</text> -->
<!-- <text class="avatar-text">•◡•</text> -->
</view>
</view>
</view>
<!-- 统计信息 -->
<view class="user-info-section">
<!-- <view class="user-info-section"> -->
<!-- <view class="stats-card" bindtap="navigateToHistory">
<text class="section-title">使用统计</text>
<view class="stats-grid">
@@ -27,25 +39,55 @@
</view>
</view>
</view> -->
</view>
<!-- </view> -->
<!-- 设置选项 -->
<view class="section-title">个人中心</view>
<view class="settings-section">
<view class="settings-card">
<text class="section-title">个人</text>
<t-cell title="兑换码" hover arrow/>
<t-cell title="消息" hover arrow/>
<t-dialog
visible="{{showCouponInput}}"
title="请输出兑换码"
confirm-btn="确定"
cancel-btn="取消"
bind:confirm="confirmCouponDialog"
bind:cancel="closeCouponDialog"
>
<t-input
class="coupon-input"
clearable
boderless
auto-focus
slot="content"
placeholder="6 位兑换码"
placeholder-class="placeholder"
value="{{couponCode}}"
type="text"
bind:change="onCouponInput"
maxcharacter="{{6}}"
/>
</t-dialog>
<t-cell title="积分" hover note="{{points.balance}}">
<t-icon slot="left-icon" name="star" size="44rpx"></t-icon>
</t-cell>
<t-cell title="兑换码" hover arrow bindtap="showCouponDialog">
<t-icon slot="left-icon" name="coupon" size="44rpx"></t-icon>
</t-cell>
<t-cell title="消息" hover arrow>
<t-icon slot="left-icon" name="notification" size="44rpx"></t-icon>
</t-cell>
</view>
</view>
<!-- 设置选项 -->
<view class="section-title">应用设置</view>
<view class="settings-section">
<view class="settings-card">
<text class="section-title">应用设置</text>
<!-- <t-action-sheet id="t-action-sheet" bind:selected="handleSelected" /> -->
<!-- <t-cell title="单词等级" hover note="{{dictLevelOptions[dictLevel] || '小学'}}" bindtap="handleAction" /> -->
<t-cell title="缓存数据" hover note="{{cacheStats.totalSize}}" data-key="showClearConfirm" bind:tap="showDialog" />
<t-cell title="缓存数据" hover note="{{cacheStats.totalSize}}" data-key="showClearConfirm" bind:tap="showDialog" >
<t-icon slot="left-icon" name="data-base" size="44rpx"></t-icon>
</t-cell>
<t-dialog
visible="{{showClearConfirm}}"
content="{{cacheStats.details || '暂无缓存数据'}}"
@@ -58,13 +100,18 @@
</view>
<!-- 帮助信息 -->
<view class="help-section">
<view class="help-card">
<text class="section-title">帮助与支持</text>
<t-cell title="使用条款" bindtap="handleViewTerms" hover arrow />
<t-cell title="隐私政策" bindtap="handleViewPrivacy" hover arrow />
<t-cell title="关于我们" bindtap="handleAboutUs" hover arrow />
<view class="section-title">帮助与支持</view>
<view class="settings-section">
<view class="settings-card">
<t-cell title="使用条款" bindtap="handleViewTerms" hover arrow>
<t-icon slot="left-icon" name="file-1" size="44rpx"></t-icon>
</t-cell>
<t-cell title="隐私政策" bindtap="handleViewPrivacy" hover arrow>
<t-icon slot="left-icon" name="secured" size="44rpx"></t-icon>
</t-cell>
<t-cell title="关于我们" bindtap="handleAboutUs" hover arrow>
<t-icon slot="left-icon" name="info-circle" size="44rpx"></t-icon>
</t-cell>
</view>
</view>

View File

@@ -95,16 +95,23 @@
padding: 0 32rpx 32rpx;
}
.settings-card {
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.05);
.t-cell--bordered {
--td-cell-border-right-space: 28rpx;
--td-cell-border-left-space: 28rpx;
}
.settings-card .section-title {
padding: 32rpx 32rpx 16rpx;
.settings-card {
background: #ffffff;
border-radius: 44rpx;
overflow: hidden;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.05);
--td-spacer-4: 28rpx;
}
.section-title {
padding: 24rpx 64rpx 16rpx;
margin-bottom: 0;
color: #b3b3b3;
}
.setting-item {
@@ -232,58 +239,6 @@
margin-left: 16rpx;
}
/* 帮助信息 */
.help-section {
padding: 0 32rpx 32rpx;
}
.help-card {
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.05);
}
.help-card .section-title {
padding: 32rpx 32rpx 16rpx;
margin-bottom: 0;
}
.help-list {
display: flex;
flex-direction: column;
}
.help-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
background: none;
border: none;
border-bottom: 1rpx solid #f0f0f0;
text-align: left;
}
.help-item:last-child {
border-bottom: none;
}
.help-item:active {
background-color: #f8f8f8;
}
.help-name {
font-size: 30rpx;
font-weight: 500;
color: #333333;
}
.help-arrow {
font-size: 28rpx;
color: #cccccc;
}
/* 退出登录 */
.logout-section {
padding: 0 32rpx 32rpx;
@@ -328,4 +283,66 @@
.version-text {
font-size: 24rpx;
color: #999999;
}
}
/* Avatar Styles */
.avatar-section {
display: flex;
justify-content: center;
padding: 40rpx 0;
}
.avatar-container {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 50%;
border: 8rpx solid #ffffff;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
}
.avatar-image {
width: 100%;
height: 100%;
border-radius: 50%;
display: block;
}
.avatar-placeholder {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #fef6e4;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-svg {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100rpx;
height: 100rpx;
z-index: 1;
}
.avatar-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 36rpx;
color: #999999;
}
.coupon-input {
padding: 20rpx;
font-size: 28rpx;
text-align: center;
border: 1rpx solid #e0e0e0;
border-radius: 8rpx;
margin: 20rpx;
}

View File

@@ -6,15 +6,15 @@
<view class="section">
<text class="subtitle">欢迎使用 KnowWords</text>
<text class="paragraph">本用户协议(以下简称"本协议")由您(以下简称"用户"或"您")与 KnowWords 小程序运营者(以下简称"我们")订立,旨在明确您在使用 KnowWords 微信小程序(以下简称"本小程序")时的权利与义务。</text>
<text class="paragraph">请您在使用本小程序前,仔细阅读并理解本协议及《隐私政策》 的全部内容。一旦您勾选"同意"或开始使用本小程序,即视为您已充分理解并接受本协议及《隐私政策》的所有条款。</text>
<text class="paragraph">请您在使用本小程序前,仔细阅读并理解本协议及《隐私政策》 的全部内容。</text>
</view>
<view class="section">
<text class="subtitle">1. 服务说明</text>
<text class="paragraph">KnowWords 是一款通过识别图片中的物体来帮助用户学习英语词汇和发音的工具。您需要使用微信账号授权登录。本服务主要包括:</text>
<text class="paragraph">KnowWords 是一款通过识别图片中的物体来帮助用户学习外语的工具。您需要使用微信账号授权登录。本服务主要包括:</text>
<text class="paragraph">提供基于微信身份的一键登录。</text>
<text class="paragraph">接收您上传的图片并利用第三方AI模型QWEN)进行识别,返回相应的英文单词、释义及发音。</text>
<text class="paragraph">(未来可能提供)录制并分析您的语音,提供发音评测与反馈。</text>
<text class="paragraph">接收您上传的图片并利用第三方AI模型通义千问)进行识别,返回相应的图片描述。</text>
<text class="paragraph">录制并分析您的语音,并利用第三方AI模型智聆口语评测进行评测提供发音反馈。</text>
</view>
<view class="section">
@@ -34,7 +34,7 @@
<view class="section">
<text class="subtitle">4. 隐私保护</text>
<text class="paragraph">我们高度重视您的隐私。关于我们如何收集、使用、存储和保护您的个人信息(如微信标识、上传的图片、语音等),请详细阅读我们另行制定的 《隐私政策》 。您同意为提供服务我们可能会与第三方技术服务商如AI模型提供商在严格遵守《隐私政策》的前提下共享必要的用户数据。</text>
<text class="paragraph">我们高度重视您的隐私。关于我们如何收集、使用、存储和保护您的个人信息(如微信标识、上传的图片、语音等),请详细阅读我们另行制定的 《隐私政策》 。</text>
</view>
<view class="section">

View File

@@ -27,16 +27,16 @@
}
.subtitle {
font-size: 28rpx;
font-size: 32rpx;
font-weight: bold;
color: #007AFF;
color: #33272a;
display: block;
margin-bottom: 16rpx;
}
.paragraph {
font-size: 26rpx;
color: #666666;
font-size: 24rpx;
color: #594a4e;
line-height: 40rpx;
display: block;
}

View File

@@ -36,6 +36,8 @@ Page({
fileBaseUrl: FILE_BASE_URL,
// 添加表情位置状态
facePosition: 'normal' as 'up' | 'down' | 'normal',
// SVG data for avatar
avatarSvgData: '',
scrollTop: 0
},
@@ -50,6 +52,29 @@ Page({
}).catch((error) => {
console.error('登录检查失败:', error)
})
this.generateAvatarSvg()
},
// Generate SVG data URL for avatar
generateAvatarSvg() {
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="470 50 20 40">
<path d="M 486 70 C 486 71.7614 483.761 74 481 74 C 478.239 74 476 71.7614 476 70 C 476 69 477 69 477 70 C 477 71 479 73 481 73 C 483 73 485 71 485 70 C 485 69 486 69 486 70 M 490 64 C 488.7 64 488.7 66 490 66 C 491.3 66 491.3 64 490 64 M 472 64 C 470.7 64 470.7 66 472 66 C 473.3 66 473.3 64 472 64" stroke="#001858" stroke-width="1.5" stroke-linecap="round"></path>
</svg>
`.trim();
// const svgString = `
// <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="470 50 20 40">
// <path d="M 486 70 C 486 71.7614 483.761 74 481 74 C 478.239 74 476 71.7614 476 70 C 476 69 476 69 476 70 C 476 72 478 74 481 74 C 484 74 486 72 486 70 C 486 69 486 69 486 70 M 490 64 C 488.7 64 488.7 66 490 66 C 491.3 66 491.3 64 490 64 M 472 64 C 470.7 64 470.7 66 472 66 C 473.3 66 473.3 64 472 64" stroke="#001858" stroke-width="3" stroke-linecap="round"></path>
// </svg>
// `.trim();
const encodedSvg = encodeURIComponent(svgString);
const dataUrl = `data:image/svg+xml;utf8,${encodedSvg}`;
this.setData({
avatarSvgData: dataUrl
});
},
onShow() {
@@ -91,8 +116,9 @@ Page({
checkDayType() {
const currentHour = new Date().getHours();
const currentDate = new Date().toLocaleDateString('zh-CN');
const dayType = currentHour >= 6 && currentHour < 12 ? 'morning' :
currentHour >= 12 && currentHour < 18 ? 'afternoon' : 'night';
// const dayType = currentHour >= 6 && currentHour < 12 ? 'morning' :
// currentHour >= 12 && currentHour < 18 ? 'afternoon' : 'night';
const dayType = 'morning';
this.setData({
current_date: currentDate,
day_type: dayType as IDayType

View File

@@ -22,7 +22,9 @@
<!-- 欢迎区域 -->
<view class="sunny-wrap" wx:if="{{ day_type !== 'night' }}">
<image src="/static/sun-2.png" class="sunny-icon" />
<view class="face {{facePosition}}">•ᴗ•</view>
<image class="face {{facePosition}}" src="{{avatarSvgData}}" mode="aspectFit"></image>
<!-- <view class="face {{facePosition}}">•ᴗ•</view> -->
<!-- <view class="face {{facePosition}}">•◡•</view> -->
</view>
<view wx:else>
<view class="moon-wrap">

View File

@@ -210,20 +210,22 @@
color: black;
transition: all 0.8s ease;
z-index: 10;
width: 100rpx;
height: 100rpx;
}
.face.normal {
top: 45%;
top: 56%;
transform: translate(-70%, -74%);
}
.face.up {
top: 55%;
top: 64%;
transform: translate(-70%, -74%);
}
.face.down {
top: 65%;
top: 72%;
transform: translate(-70%, -74%);
}

View File

@@ -65,6 +65,12 @@ export interface ExtendedWordDetail {
audio: string
}
// 积分数据接口
export interface IPointsData {
balance: number
expired_time: string
}
export interface YdWordDetail {
ee: {}
ec: {}

View File

@@ -318,11 +318,21 @@ class ApiManager {
this.handleTokenExpired()
reject(new Error('登录已过期'))
}
} else if (res.statusCode === 400) {
const response = res.data as IApiResponse<T>
const errorMsg = response.msg || '请求失败'
console.error('400 错误:', errorMsg, response)
reject(new Error(errorMsg))
} else if (res.statusCode === 403) {
const response = res.data as IApiResponse<T>
const errorMsg = response.msg || '请求失败'
console.error('403 错误:', errorMsg, response)
reject(new Error(errorMsg))
} else if (res.statusCode === 404 ) {
const response = res.data as IApiResponse<T>
const errorMsg = response.msg || '请求失败'
console.error('404 错误:', errorMsg, response)
reject(new Error(errorMsg))
} else {
console.error('HTTP错误:', res.statusCode, res.data)
wx.showToast({
@@ -1116,7 +1126,7 @@ class ApiManager {
let audioCount = 0
let imageCount = 0
let totalSize = 0
const sizeByType: Record<string, number> = { audio: 0, image: 0 }
const sizeByType: Record<string, number> = { audio: 0, image: 0, dict: 0 }
// 遍历缓存统计信息
cacheStats.forEach((size, key) => {
@@ -1130,6 +1140,30 @@ class ApiManager {
}
})
// 计算词典缓存大小
try {
const storageInfo = wx.getStorageInfoSync()
let dictSize = 0
storageInfo.keys.forEach(key => {
if (key.startsWith('word_detail_')) {
try {
const item = wx.getStorageSync(key)
if (item) {
// 估算 JSON 大小
const jsonString = JSON.stringify(item)
dictSize += jsonString.length
}
} catch (e) {
console.warn('读取词典缓存大小失败:', key, e)
}
}
})
sizeByType.dict = dictSize
totalSize += dictSize
} catch (e) {
console.error('获取词典缓存统计失败:', e)
}
const endTime = Date.now()
console.log('缓存统计信息获取完成,耗时:', endTime - startTime, 'ms', {
audioCount,
@@ -1465,9 +1499,44 @@ class ApiManager {
}
}
// 获取积分数据
async getPointsData(): Promise<import("../types/app").IPointsData> {
try {
console.log('开始获取积分数据');
const response = await this.request<import("../types/app").IPointsData>('/api/v1/points/self');
console.log('获取积分数据成功:', response.data);
// 如果返回的数据为null则返回默认值
if (!response.data) {
return { balance: 0, expired_time: '' };
}
return response.data;
} catch (error) {
console.error('获取积分数据失败:', error);
throw error;
}
}
// 兑换码兑换积分
async redeemCoupon(code: string): Promise<any> {
try {
console.log('开始兑换码兑换:', code);
const response = await this.request<any>('/api/v1/coupon/redeem', 'POST', {
code: code
});
console.log('兑换成功:', response.data);
return response.data;
} catch (error) {
console.error('兑换失败:', error);
throw error;
}
}
}
// 导出单例
const apiManager = new ApiManager()
export default apiManager
export { ApiManager }
export { ApiManager }