fix: 修复自动升级和展览藏品下架的索引bug
This commit is contained in:
parent
1ebc23b832
commit
e761bde30b
@ -140,7 +140,7 @@ func CleanErrorMessage(err error) string {
|
|||||||
"invalid token": "Token无效",
|
"invalid token": "Token无效",
|
||||||
"token expired": "Token已过期",
|
"token expired": "Token已过期",
|
||||||
"user is inactive": "用户已被禁用",
|
"user is inactive": "用户已被禁用",
|
||||||
"maximum number of identities reached": "已达到最大身份数量限制(最多2个)",
|
"maximum number of identities reached": "已达到最大身份数量限制",
|
||||||
"invalid star_id": "明星ID无效",
|
"invalid star_id": "明星ID无效",
|
||||||
"invalid user_id": "用户ID无效",
|
"invalid user_id": "用户ID无效",
|
||||||
"invalid nickname": "昵称格式不正确",
|
"invalid nickname": "昵称格式不正确",
|
||||||
@ -148,6 +148,7 @@ func CleanErrorMessage(err error) string {
|
|||||||
"nickname too short": "昵称过短,至少需要2个字符",
|
"nickname too short": "昵称过短,至少需要2个字符",
|
||||||
"user info not found in Dubbo attachments": "请先登录",
|
"user info not found in Dubbo attachments": "请先登录",
|
||||||
"missing or invalid authorization token": "Token缺失或无效",
|
"missing or invalid authorization token": "Token缺失或无效",
|
||||||
|
"已达到最大好友数量限制": "已达到最大好友数量限制",
|
||||||
}
|
}
|
||||||
|
|
||||||
msgLower := strings.ToLower(msg)
|
msgLower := strings.ToLower(msg)
|
||||||
|
|||||||
@ -43,6 +43,7 @@ var (
|
|||||||
ErrRequestExpired = errors.New("好友请求已过期")
|
ErrRequestExpired = errors.New("好友请求已过期")
|
||||||
ErrInvalidAction = errors.New("无效的操作")
|
ErrInvalidAction = errors.New("无效的操作")
|
||||||
ErrNotFriends = errors.New("你们不是好友")
|
ErrNotFriends = errors.New("你们不是好友")
|
||||||
|
ErrMaxFriendsReached = errors.New("已达到最大好友数量限制")
|
||||||
|
|
||||||
// 资产服务相关错误
|
// 资产服务相关错误
|
||||||
ErrAssetNotFound = errors.New("资产不存在")
|
ErrAssetNotFound = errors.New("资产不存在")
|
||||||
|
|||||||
17
backend/pkg/models/system_config.go
Normal file
17
backend/pkg/models/system_config.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// SystemConfig 系统配置表模型
|
||||||
|
type SystemConfig struct {
|
||||||
|
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||||
|
ConfigKey string `gorm:"type:varchar(100);uniqueIndex;not null;column:config_key"`
|
||||||
|
ConfigValue int `gorm:"not null;column:config_value"`
|
||||||
|
Description string `gorm:"type:varchar(500);column:description"`
|
||||||
|
IsActive bool `gorm:"default:true;column:is_active"`
|
||||||
|
CreatedAt int64 `gorm:"column:created_at"`
|
||||||
|
UpdatedAt int64 `gorm:"column:updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定表名
|
||||||
|
func (SystemConfig) TableName() string {
|
||||||
|
return "system_configs"
|
||||||
|
}
|
||||||
@ -3,6 +3,11 @@ package config
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/topfans/backend/pkg/database"
|
||||||
|
"github.com/topfans/backend/pkg/logger"
|
||||||
|
"github.com/topfans/backend/pkg/models"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GalleryRulesConfig 展馆规则配置(硬编码)
|
// GalleryRulesConfig 展馆规则配置(硬编码)
|
||||||
@ -107,3 +112,28 @@ func InitConfig() {
|
|||||||
log.Printf(" User Service: %s", ServiceURLsConfig.UserService)
|
log.Printf(" User Service: %s", ServiceURLsConfig.UserService)
|
||||||
log.Printf(" Task Service: %s", ServiceURLsConfig.TaskService)
|
log.Printf(" Task Service: %s", ServiceURLsConfig.TaskService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetExhibitionDuration 获取展览时长(秒),从数据库配置读取
|
||||||
|
func GetExhibitionDuration() int64 {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.Logger.Error("Panic in GetExhibitionDuration, using default=14400",
|
||||||
|
zap.Any("error", r))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
logger.Logger.Warn("Database not initialized, using default GrabSlotDuration=14400")
|
||||||
|
return 14400
|
||||||
|
}
|
||||||
|
var config models.SystemConfig
|
||||||
|
err := db.Where("config_key = ? AND is_active = ?", "exhibition_duration", true).First(&config).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Warn("Failed to get exhibition_duration config, using default=14400",
|
||||||
|
zap.Error(err))
|
||||||
|
return 14400
|
||||||
|
}
|
||||||
|
// 数据库存储的是小时,转换为秒
|
||||||
|
return int64(config.ConfigValue) * 3600
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,11 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/topfans/backend/pkg/database"
|
||||||
|
"github.com/topfans/backend/pkg/logger"
|
||||||
|
"github.com/topfans/backend/pkg/models"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SocialConfig 社交服务配置
|
// SocialConfig 社交服务配置
|
||||||
@ -149,3 +154,20 @@ func (c *SocialConfig) UpdateFriendLimit(enabled bool, defaultLimit int) {
|
|||||||
c.FriendLimit.Enabled = enabled
|
c.FriendLimit.Enabled = enabled
|
||||||
c.FriendLimit.DefaultLimit = defaultLimit
|
c.FriendLimit.DefaultLimit = defaultLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMaxFriends 获取最大好友数量限制(从数据库配置)
|
||||||
|
func GetMaxFriends() int {
|
||||||
|
db := database.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
logger.Logger.Warn("Database not initialized, using default MaxFriends=50")
|
||||||
|
return 50
|
||||||
|
}
|
||||||
|
var config models.SystemConfig
|
||||||
|
err := db.Where("config_key = ? AND is_active = ?", "max_friends", true).First(&config).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Warn("Failed to get max_friends config, using default=50",
|
||||||
|
zap.Error(err))
|
||||||
|
return 50
|
||||||
|
}
|
||||||
|
return config.ConfigValue
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/topfans/backend/pkg/database"
|
"github.com/topfans/backend/pkg/database"
|
||||||
@ -15,6 +16,31 @@ func contains(s, substr string) bool {
|
|||||||
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CalculateLevel 根据经验值计算等级
|
||||||
|
// 公式: 升级到等级L需要的累计经验 = (L-1) * L * 50
|
||||||
|
// Level 1: 0经验, Level 2: 100经验, Level 3: 300经验, Level 4: 600经验...
|
||||||
|
func CalculateLevel(experience int64) int32 {
|
||||||
|
if experience < 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// 使用公式: (L-1) * L * 50 <= experience
|
||||||
|
// 解方程: L^2 - L - experience/50 <= 0
|
||||||
|
// L = (1 + sqrt(1 + 4*experience/50)) / 2
|
||||||
|
level := int32((1 + math.Sqrt(1+4*float64(experience)/50)) / 2)
|
||||||
|
if level < 1 {
|
||||||
|
level = 1
|
||||||
|
}
|
||||||
|
return level
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExperienceForLevel 获取指定等级需要的经验值
|
||||||
|
func GetExperienceForLevel(level int32) int64 {
|
||||||
|
if level <= 1 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return int64((level-1) * level * 50)
|
||||||
|
}
|
||||||
|
|
||||||
// FanProfileRepository 粉丝档案Repository接口
|
// FanProfileRepository 粉丝档案Repository接口
|
||||||
type FanProfileRepository interface {
|
type FanProfileRepository interface {
|
||||||
// Create 创建粉丝档案
|
// Create 创建粉丝档案
|
||||||
@ -41,6 +67,9 @@ type FanProfileRepository interface {
|
|||||||
// IncrementAssetsCount 增加资产计数
|
// IncrementAssetsCount 增加资产计数
|
||||||
IncrementAssetsCount(userID, starID int64, delta int32) error
|
IncrementAssetsCount(userID, starID int64, delta int32) error
|
||||||
|
|
||||||
|
// SyncLevelFromExperience 根据经验值同步等级(只升级不降级)
|
||||||
|
SyncLevelFromExperience(userID, starID int64) (int32, error)
|
||||||
|
|
||||||
// DecrementAssetsCount 减少资产计数
|
// DecrementAssetsCount 减少资产计数
|
||||||
DecrementAssetsCount(userID, starID int64, delta int32) error
|
DecrementAssetsCount(userID, starID int64, delta int32) error
|
||||||
|
|
||||||
@ -390,7 +419,7 @@ func (r *fanProfileRepository) UpdateCrystalBalance(userID, starID int64, delta
|
|||||||
return newBalance, nil
|
return newBalance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateExperience 更新经验值
|
// UpdateExperience 更新经验值(同时自动更新等级)
|
||||||
// delta: 变化量,正数表示增加,负数表示减少
|
// delta: 变化量,正数表示增加,负数表示减少
|
||||||
// 返回: 更新后的经验值
|
// 返回: 更新后的经验值
|
||||||
func (r *fanProfileRepository) UpdateExperience(userID, starID int64, delta int64) (int64, error) {
|
func (r *fanProfileRepository) UpdateExperience(userID, starID int64, delta int64) (int64, error) {
|
||||||
@ -415,7 +444,7 @@ func (r *fanProfileRepository) UpdateExperience(userID, starID int64, delta int6
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算新值
|
// 计算新经验值
|
||||||
newExperience = profile.Experience + delta
|
newExperience = profile.Experience + delta
|
||||||
|
|
||||||
// 确保不会小于 0
|
// 确保不会小于 0
|
||||||
@ -423,10 +452,16 @@ func (r *fanProfileRepository) UpdateExperience(userID, starID int64, delta int6
|
|||||||
newExperience = 0
|
newExperience = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 experience 字段
|
// 根据新经验值计算新等级
|
||||||
|
newLevel := CalculateLevel(newExperience)
|
||||||
|
|
||||||
|
// 更新 experience 和 level 字段
|
||||||
if err := tx.Model(&models.FanProfile{}).
|
if err := tx.Model(&models.FanProfile{}).
|
||||||
Where("user_id = ? AND star_id = ?", userID, starID).
|
Where("user_id = ? AND star_id = ?", userID, starID).
|
||||||
Update("experience", newExperience).Error; err != nil {
|
Updates(map[string]interface{}{
|
||||||
|
"experience": newExperience,
|
||||||
|
"level": newLevel,
|
||||||
|
}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,3 +503,24 @@ func (r *fanProfileRepository) UpdateAvatar(userID, starID int64, avatarURL stri
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SyncLevelFromExperience 根据经验值同步等级(只升级不降级)
|
||||||
|
// 在获取用户信息时调用,确保等级与经验值匹配
|
||||||
|
func (r *fanProfileRepository) SyncLevelFromExperience(userID, starID int64) (int32, error) {
|
||||||
|
var profile models.FanProfile
|
||||||
|
if err := r.db.Where("user_id = ? AND star_id = ?", userID, starID).First(&profile).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newLevel := CalculateLevel(profile.Experience)
|
||||||
|
|
||||||
|
// 只升级,不降级
|
||||||
|
if newLevel > profile.Level {
|
||||||
|
if err := r.db.Model(&profile).Update("level", newLevel).Error; err != nil {
|
||||||
|
return profile.Level, err
|
||||||
|
}
|
||||||
|
return newLevel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile.Level, nil
|
||||||
|
}
|
||||||
|
|||||||
119
docs/硬编码常量审批文档.md
Normal file
119
docs/硬编码常量审批文档.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# 硬编码常量汇总
|
||||||
|
|
||||||
|
> 生成时间: 2026-04-23
|
||||||
|
> 用途: 供团队审批,确认这些硬编码值是否需要改为可配置
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、前端硬编码
|
||||||
|
|
||||||
|
### 1. API Base URL
|
||||||
|
| 文件 | 行号 | 硬编码值 | 风险等级 |
|
||||||
|
|------|------|----------|----------|
|
||||||
|
| `frontend/utils/api.js` | 4 | `http://localhost:8080` | 高 |
|
||||||
|
|
||||||
|
### 2. 性能配置
|
||||||
|
| 文件 | 行号 | 硬编码值 | 说明 |
|
||||||
|
|------|------|----------|------|
|
||||||
|
| `frontend/utils/performance-config.js:36` | 36 | `30000` | 数据过时阈值(30秒) |
|
||||||
|
| `frontend/utils/performance-config.js:41` | 41 | `3000` | 关键资源加载超时(毫秒) |
|
||||||
|
|
||||||
|
### 3. UI/动画配置
|
||||||
|
| 文件 | 行号 | 硬编码值 | 说明 |
|
||||||
|
|------|------|----------|------|
|
||||||
|
| `frontend/pages/support-activity/components/FloatingBubbles.vue:337` | 337 | `30000` | 气泡刷新间隔(30秒) |
|
||||||
|
| `frontend/pages/components/WelcomeAnimation.vue:704` | 704 | `3000` | 欢迎动画延迟(3秒) |
|
||||||
|
| `frontend/pages/components/WelcomeAnimationWebview.vue:83` | 83 | `3000` | Webview动画延迟(3秒) |
|
||||||
|
| `frontend/pages/support-activity/components/ActionBar.vue:97` | 97 | `3000` | 行动条动画延迟(3秒) |
|
||||||
|
| `frontend/pages/components/CastloveContent.vue:19` | 19 | `interval="3000"` | 轮播间隔(3秒) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、后端硬编码
|
||||||
|
|
||||||
|
### 1. JWT 密钥(安全风险)
|
||||||
|
| 文件 | 行号 | 硬编码值 | 风险等级 |
|
||||||
|
|------|------|----------|----------|
|
||||||
|
| `backend/pkg/jwt/jwt.go` | 18 | `your-secret-key-change-in-production` | **高** |
|
||||||
|
| `backend/gateway/config/config.go` | 83 | `topfans-secret-key-please-change-in-production` | **高** |
|
||||||
|
|
||||||
|
### 2. 展馆相关 (`galleryService/config/gallery_config.go`)
|
||||||
|
| 配置项 | 硬编码值 | 说明 |
|
||||||
|
|--------|----------|------|
|
||||||
|
| `InitialSlotCount` | **6** | 初始展位数量(3公开+3私有) |
|
||||||
|
| `GrabSlotDuration` | **14400秒(4小时)** | 抢展位后未放置的过期时间 |
|
||||||
|
| `MaxSlotCount` | **10** | 最大展位数量 |
|
||||||
|
| `UnlockLevelBySlot` | 第4-10展位需等级5-11 | 解锁展位需要的等级 |
|
||||||
|
| `UnlockValueBySlot` | 水晶100-700 | 解锁展位需要的水晶 |
|
||||||
|
|
||||||
|
### 3. 社交相关 (`socialService/config/social_config.go`)
|
||||||
|
| 配置项 | 硬编码值 | 说明 |
|
||||||
|
|--------|----------|------|
|
||||||
|
| `RejectionCooldownDays` | **7天** | 拒绝好友冷却期 |
|
||||||
|
| `RequestExpiryDays` | **30天** | 好友请求过期时间 |
|
||||||
|
| `MaxLimit` | **0(未启用)** | 好友数量上限(预留) |
|
||||||
|
|
||||||
|
### 4. 用户/身份相关
|
||||||
|
| 配置项 | 值 | 文件 |
|
||||||
|
|--------|-----|------|
|
||||||
|
| `MaxIdentities` | **2个** | `userService/service/identity_service.go:23` |
|
||||||
|
| `MaxPasswordLength` | **50** | `pkg/validator/validator.go:14` |
|
||||||
|
| `MaxNicknameLength` | **20** | `pkg/validator/validator.go:18` |
|
||||||
|
|
||||||
|
### 5. 任务相关 (`taskService/config/task_config.go`)
|
||||||
|
| 配置项 | 硬编码值 | 说明 |
|
||||||
|
|--------|----------|------|
|
||||||
|
| `ResetHour` | **5** | 每日重置小时(Asia/Shanghai) |
|
||||||
|
| `ResetMinute` | **0** | 每日重置分钟 |
|
||||||
|
| `RevenueBatchSize` | **100** | 收益自动发放批次大小 |
|
||||||
|
| `RevenueMaxRetries` | **3** | 收益自动发放最大重试次数 |
|
||||||
|
|
||||||
|
### 6. 铸造相关 (`assetService/config/asset_config.go`)
|
||||||
|
| 配置项 | 硬编码值 | 说明 |
|
||||||
|
|--------|----------|------|
|
||||||
|
| `MaxRetryCount` | **3次** | 铸造失败最大重试次数 |
|
||||||
|
| `MintSimulationDelaySeconds` | **3秒** | 模拟上链延迟 |
|
||||||
|
| `GetByOwner limit` | **1000** | 单次获取藏品上限 |
|
||||||
|
|
||||||
|
### 7. Token/认证相关
|
||||||
|
| 配置项 | 硬编码值 | 文件 |
|
||||||
|
|--------|----------|------|
|
||||||
|
| `TokenExpiration` | **7天** | `pkg/jwt/jwt.go:13` |
|
||||||
|
|
||||||
|
### 8. 预签名URL相关 (`gateway/controller/asset_controller.go`)
|
||||||
|
| 配置项 | 硬编码值 | 说明 |
|
||||||
|
|--------|----------|------|
|
||||||
|
| 默认过期时间 | **3600秒(1小时)** | 预签名URL默认 |
|
||||||
|
| 最大过期时间 | **86400秒(24小时)** | 预签名URL最大 |
|
||||||
|
| STS最大 | **3600秒(1小时)** | STS Token最大 |
|
||||||
|
| STS最小 | **900秒(15分钟)** | STS Token最小 |
|
||||||
|
|
||||||
|
### 9. 数据库/服务连接(开发环境默认值)
|
||||||
|
| 服务 | 默认值 |
|
||||||
|
|------|--------|
|
||||||
|
| DB_HOST | `localhost` |
|
||||||
|
| Dubbo服务 | `tri://127.0.0.1:20000` ~ `20007` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、审批选项
|
||||||
|
|
||||||
|
请确认以下处理方式:
|
||||||
|
|
||||||
|
| 类别 | 选项A(保留硬编码) | 选项B(改为环境变量) |
|
||||||
|
|------|---------------------|----------------------|
|
||||||
|
| API Base URL | 继续使用localhost开发 | 改为环境变量切换dev/prod |
|
||||||
|
| JWT 密钥 | 生产部署时手动修改 | 改为环境变量,强制必须配置 |
|
||||||
|
| 展馆规则(时间/等级/水晶) | 保留硬编码 | 改为配置文件 |
|
||||||
|
| 社交规则(冷却期/过期) | 保留硬编码 | 改为配置文件 |
|
||||||
|
| 任务配置(重置时间) | 保留硬编码 | 改为环境变量 |
|
||||||
|
| 铸造配置(重试次数) | 保留硬编码 | 改为配置文件 |
|
||||||
|
| UI配置(动画时长) | 保留硬编码 | 改为样式变量 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、建议
|
||||||
|
|
||||||
|
1. **高风险**: JWT密钥硬编码存在安全隐患,建议改为强制环境变量配置
|
||||||
|
2. **中风险**: API Base URL建议改为环境变量,方便切换开发/生产环境
|
||||||
|
3. **低风险**: 业务规则类硬编码(如展馆规则、社交规则)可根据团队需求决定是否配置化
|
||||||
@ -352,7 +352,14 @@ async function handleClaim(task) {
|
|||||||
try {
|
try {
|
||||||
claimingTask.value = task.task_key
|
claimingTask.value = task.task_key
|
||||||
const res = await claimDailyTask(task.task_key, starId.value)
|
const res = await claimDailyTask(task.task_key, starId.value)
|
||||||
await loadTasks()
|
|
||||||
|
// 直接更新本地状态,而不是重新加载整个列表
|
||||||
|
const index = tasks.value.findIndex(t => t.task_key === task.task_key)
|
||||||
|
if (index !== -1) {
|
||||||
|
tasks.value[index].status = 'claimed'
|
||||||
|
tasks.value[index].can_claim = false
|
||||||
|
}
|
||||||
|
|
||||||
emit('updated')
|
emit('updated')
|
||||||
uni.$emit('balanceUpdated', { crystal_balance: res.data?.crystal_balance, experience: res.data?.experience })
|
uni.$emit('balanceUpdated', { crystal_balance: res.data?.crystal_balance, experience: res.data?.experience })
|
||||||
uni.showToast({ title: '领取成功', icon: 'success' })
|
uni.showToast({ title: '领取成功', icon: 'success' })
|
||||||
@ -369,7 +376,15 @@ async function handleClaimAll() {
|
|||||||
try {
|
try {
|
||||||
claimingAll.value = true
|
claimingAll.value = true
|
||||||
const res = await claimAllDailyTasks(starId.value)
|
const res = await claimAllDailyTasks(starId.value)
|
||||||
await loadTasks()
|
|
||||||
|
// 直接更新所有已领取的任务为 claimed 状态
|
||||||
|
tasks.value.forEach(task => {
|
||||||
|
if (task.can_claim) {
|
||||||
|
task.status = 'claimed'
|
||||||
|
task.can_claim = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
emit('updated')
|
emit('updated')
|
||||||
uni.$emit('balanceUpdated', { crystal_balance: res.data?.crystal_balance, experience: res.data?.experience })
|
uni.$emit('balanceUpdated', { crystal_balance: res.data?.crystal_balance, experience: res.data?.experience })
|
||||||
uni.showToast({ title: '领取成功', icon: 'success' })
|
uni.showToast({ title: '领取成功', icon: 'success' })
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user