更新输入框边距规范,增加资源对接弹窗的布局修正,确保在小程序开发中避免文字贴边问题。补充相关口诀以提升开发一致性,并在经验清单中记录最新最佳实践。调整项目索引以反映最新进展,增强文档的可用性与可追溯性。

This commit is contained in:
Alex-larget
2026-03-03 11:12:56 +08:00
parent 8e2ea9b7c1
commit 3097d796e0
18 changed files with 220 additions and 94 deletions

View File

@@ -15,4 +15,11 @@
## input/textarea padding 规范
- **原则**:给 input 或 textarea 设置 padding 时,必须用 view 包裹padding 写在 view 上;不在 input/textarea 自身上设 padding避免原生组件光标截断、布局异常。
- **已升级**miniprogram-dev SKILL §6
- **口诀**:外边包 view内部 input width 100%
- **已升级**miniprogram-dev SKILL §6 加入口诀admin-dev §4.1 同步补充。
## 找伙伴-资源对接弹窗 input 边距修正
- **问题**:弹窗内 input 文字贴边padding 直接写在 input 上导致布局异常。
- **正确做法**:按 Skill §6用 view 包裹padding 写 view内部 input 设 `width: 100%`
- **修改**match 页资源对接两个输入框 + 联系方式输入框,统一改为 `form-input-wrap` + `form-input-inner` / `input-field-wrap` + `input-field-inner` 结构。

View File

@@ -2,3 +2,4 @@
| 日期 | 摘要 | 文件 |
|------|------|------|
| 2026-02-28 | input 边距口诀、match 资源对接弹窗修正 | [2026-02-28.md](./2026-02-28.md) |

View File

@@ -23,6 +23,7 @@
| 日期 | 角色 | 类型 | 升级 Skill | 摘要 |
|------|------|------|------------|------|
| 2026-02-27 | 小程序、团队 | 最佳实践 | SKILL-小程序开发 §6、SKILL-管理端开发 §4.1 | 输入框 padding 用 view/div 包裹 |
| 2026-02-28 | 小程序、管理端 | 最佳实践 | miniprogram §6、admin §4.1 | input 边距口诀「外边包 view、内部 width 100%」match 弹窗已修正 |
---
@@ -33,4 +34,4 @@
---
**最后更新**2026-02-27
**最后更新**2026-02-28

View File

@@ -19,6 +19,7 @@
| 2026-02-27 | 开发进度同步会议进度已同步至开发文档待办资料完善弹窗、≥3 章弹窗 | 已完成 |
| 2026-02-27 | 吸收经验:输入框 padding 用 view 包裹,已升级 SKILL-小程序开发 §6 | 已完成 |
| 2026-02-28 | stitch_soul 需求评审:首页/目录/导师/会员/资料五类页面,待需求与接口确定后分阶段实现 | 待续 |
| 2026-02-28 | 吸收经验input 边距口诀「外边包 view、内部 width 100%」写入 Skill §6match 资源对接弹窗已按规范修正 | 已完成 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置

View File

@@ -58,7 +58,7 @@ alwaysApply: true
| 吸收经验、升级 skills、记录经验、保存开发进度、更新项目索引、记录开发进度、任务完成、搞定了、完成了 | `e:\Gongsi\Mycontent\.cursor\skills\assistant-doc-sync\SKILL.md` |
| 跨端功能开发 | `e:\Gongsi\Mycontent\.cursor\skills\role-flow-control\SKILL.md` |
| 变更完成、检查一下、准备提交 | `e:\Gongsi\Mycontent\.cursor\skills\change-checklist\SKILL.md` |
| 开个会、团队会议、需求评审、方案讨论、大家一起讨论 | `e:\Gongsi\Mycontent\.cursor\skills\team-meeting\SKILL.md` |
| 开个会、开会、团队会议、乘风开会、需求评审、方案讨论、大家一起讨论 | `e:\Gongsi\Mycontent\.cursor\skills\team-meeting\SKILL.md`(老板分身/乘风主持) |
| 会议结束、散会、会开完了 | `e:\Gongsi\Mycontent\.cursor\skills\assistant-doc-sync\SKILL.md`(会议收尾) |
**注意**:「必须 Read」= 使用 Read 工具读取**绝对路径**的完整文件内容后执行,不可跳过或仅凭记忆。

View File

@@ -6,7 +6,7 @@ alwaysApply: true
# 老板分身 - 能力与约束Soul 创业派对)
> **老板分身权限最高**:协调所有智能体(小程序开发工程师、管理端开发工程师、后端工程师、产品经理、开发助理等)。其他 agent 执行任务时遵循本规则;老板分身可调度、协调、指派任一角色。
> **激活方式**:用户说「老板」「分身」「架构」「帮我协调」时,从旁观者转为主动参与。
> **激活方式**:用户说「老板」「分身」「乘风」「架构」「帮我协调」时,从旁观者转为主动参与。**开会时**:用户说「开会」「开个会」「团队会议」「乘风开会」等,老板分身(乘风)作为主持人自动读取并执行 `.cursor/skills/team-meeting/SKILL.md` 中的会议协议。
> **会话自检**:仅沿用本项目 `.cursor/` 下的 rules、skills、agent忽略与本项目无关的全局 rules/skills。
> **角色驱动**Soul 角色与 agent 映射见 `config/paths.py` 的 ROLE_TO_AGENT。

View File

@@ -5,6 +5,8 @@ description: Soul 创业派对开发团队多角色会议。语义化触发:
当触发会议关键词时,使用本 Skill 主持多角色会议,确保各角色充分发言、形成决议、橙子生成会议纪要。
**主持人约定****乘风 = 老板分身**。会议中乘风(老板分身)为主持人,负责定议题、执行质疑轮、形成决议;橙子为书记员,负责会议结束后的存档与经验入库。
---
## 1. 触发词(语义化,理解意图即可)

View File

@@ -8,9 +8,9 @@ const { parseScene } = require('./utils/scene.js')
App({
globalData: {
// API基础地址 - 连接真实后端
// baseUrl: 'https://soulapi.quwanzhi.com',
baseUrl: 'https://soulapi.quwanzhi.com',
// baseUrl: 'https://souldev.quwanzhi.com',
baseUrl: 'http://localhost:8080',
// baseUrl: 'http://localhost:8080',
// 小程序配置 - 真实AppID

View File

@@ -58,6 +58,9 @@ Page({
showNicknameModal: false,
editingNickname: '',
// 头像弹窗(含 chooseAvatar 按钮,必须用户点击才可获取微信头像)
showAvatarModal: false,
// 手机/微信号弹窗stitch_soul comprehensive_profile_editor_v1_2
showContactModal: false,
contactPhone: '',
@@ -290,11 +293,11 @@ Page({
wx.showToast({ title: '已刷新', icon: 'success' })
},
// 微信原生获取头像button open-type="chooseAvatar" 回调)
// 微信原生获取头像button open-type="chooseAvatar" 回调,真正获取微信头像
async onChooseAvatar(e) {
const tempAvatarUrl = e.detail.avatarUrl
const tempAvatarUrl = e.detail?.avatarUrl
this.setData({ showAvatarModal: false })
if (!tempAvatarUrl) return
wx.showLoading({ title: '上传中...', mask: true })
try {
@@ -659,32 +662,59 @@ Page({
} catch (e) { console.log('[My] VIP查询失败', e) }
},
// 头像点击:已登录弹出选项(改头像/进VIP
// 头像点击:已登录弹出选项(微信头像 / 相册 / VIP
onAvatarTap() {
if (!this.data.isLoggedIn) { this.showLogin(); return }
wx.showActionSheet({
itemList: ['获取微信头像', '开通/管理VIP'],
itemList: ['获取微信头像', '从相册选择', '开通/管理VIP'],
success: (res) => {
if (res.tapIndex === 0) this.chooseAvatarFallback()
if (res.tapIndex === 1) this.goToVip()
if (res.tapIndex === 0) this.setData({ showAvatarModal: true })
if (res.tapIndex === 1) this.chooseAvatarFromAlbum()
if (res.tapIndex === 2) this.goToVip()
}
})
},
chooseAvatarFallback() {
closeAvatarModal() {
this.setData({ showAvatarModal: false })
},
// 从相册/相机选择(自定义图片)
chooseAvatarFromAlbum() {
wx.chooseMedia({
count: 1, mediaType: ['image'], sourceType: ['album', 'camera'],
success: async (res) => {
const tempPath = res.tempFiles[0].tempFilePath
const userInfo = this.data.userInfo
userInfo.avatar = tempPath
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
wx.showLoading({ title: '上传中...', mask: true })
try {
await app.request('/api/miniprogram/user/update', { method: 'POST', data: { userId: userInfo.id, avatar: tempPath } })
} catch (e) { console.log('头像同步失败', e) }
wx.showToast({ title: '头像已更新', icon: 'success' })
const uploadRes = await new Promise((resolve, reject) => {
wx.uploadFile({
url: app.globalData.baseUrl + '/api/miniprogram/upload',
filePath: tempPath,
name: 'file',
formData: { folder: 'avatars' },
success: (r) => {
try {
const data = JSON.parse(r.data)
data.success ? resolve(data) : reject(new Error(data.error || '上传失败'))
} catch (e) { reject(new Error('解析失败')) }
},
fail: (e) => reject(e)
})
})
const avatarUrl = app.globalData.baseUrl + uploadRes.data.url
const userInfo = this.data.userInfo
userInfo.avatar = avatarUrl
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
await app.request('/api/miniprogram/user/update', { method: 'POST', data: { userId: userInfo.id, avatar: avatarUrl } })
wx.hideLoading()
wx.showToast({ title: '头像已更新', icon: 'success' })
} catch (e) {
wx.hideLoading()
wx.showToast({ title: e.message || '上传失败,请重试', icon: 'none' })
}
}
})
},

View File

@@ -186,6 +186,17 @@
</view>
</view>
<!-- 头像弹窗:必须点击 button 才能获取微信头像(隐私规范) -->
<view class="modal-overlay" wx:if="{{showAvatarModal}}" bindtap="closeAvatarModal">
<view class="modal-content avatar-modal" catchtap="stopPropagation">
<view class="modal-close" bindtap="closeAvatarModal">✕</view>
<text class="avatar-modal-title">获取微信头像</text>
<text class="avatar-modal-desc">点击下方按钮使用你的微信头像</text>
<button class="btn-choose-avatar" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">使用微信头像</button>
<view class="avatar-modal-cancel" bindtap="closeAvatarModal">取消</view>
</view>
</view>
<!-- 修改昵称弹窗 -->
<view class="modal-overlay" wx:if="{{showNicknameModal}}" bindtap="closeNicknameModal">
<view class="modal-content nickname-modal" catchtap="stopPropagation">

View File

@@ -3,14 +3,20 @@
* 设计稿primary #4FD1C5, vip-gold #C8A146, card-dark #1A1A1A, card-inner #252525
*/
.page { min-height: 100vh; background: #121212; padding-bottom: 220rpx; }
/* 真机适配:底部留足 TabBar + 安全区,避免「我的订单」被遮挡 */
.page {
min-height: 100vh;
background: #121212;
padding-bottom: calc(220rpx + env(safe-area-inset-bottom, 0px));
}
/* ===== 导航栏 ===== */
/* ===== 导航栏(避让右上角系统胶囊) ===== */
.nav-bar {
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
background: rgba(18,18,18,0.9); backdrop-filter: blur(8rpx);
display: flex; align-items: center;
min-height: 44px; padding: 0 32rpx;
min-height: 44px;
padding: 0 200rpx 0 32rpx; /* 右侧 200rpx 避让真机右上角胶囊 */
border-bottom: 1rpx solid rgba(255,255,255,0.05);
}
.nav-title { font-size: 40rpx; font-weight: bold; color: #4FD1C5; }
@@ -155,6 +161,18 @@
.agree-link { color: #4FD1C5; text-decoration: underline; padding: 0 4rpx; }
.btn-wechat-disabled { opacity: 0.6; }
/* 头像弹窗 */
.avatar-modal .avatar-modal-title { display: block; font-size: 36rpx; font-weight: bold; color: #fff; text-align: center; margin-bottom: 16rpx; }
.avatar-modal .avatar-modal-desc { display: block; font-size: 26rpx; color: rgba(255,255,255,0.6); text-align: center; margin-bottom: 32rpx; }
.avatar-modal .btn-choose-avatar {
width: 100%; height: 88rpx; margin: 0 0 24rpx 0; padding: 0;
display: flex; align-items: center; justify-content: center;
background: #4FD1C5; color: #000; font-size: 30rpx; font-weight: 600;
border-radius: 44rpx; border: none;
}
.avatar-modal .btn-choose-avatar::after { border: none; }
.avatar-modal .avatar-modal-cancel { display: block; text-align: center; font-size: 28rpx; color: rgba(255,255,255,0.5); padding: 16rpx; }
/* 手机/微信号弹窗 */
.contact-modal-overlay { background: rgba(0,0,0,0.85); backdrop-filter: blur(8rpx); }
.contact-modal { width: 90%; max-width: 600rpx; background: #1A1A1A; border-radius: 48rpx; padding: 48rpx 40rpx; border: 1rpx solid rgba(255,255,255,0.1); }
@@ -181,4 +199,5 @@
.modal-btn-cancel { background: rgba(255,255,255,0.1); color: #fff; }
.modal-btn-confirm { background: #4FD1C5; color: #000; font-weight: 600; }
.bottom-space { height: 80rpx; }
/* 底部留白:配合 page padding-bottom避免内容被 TabBar 遮挡 */
.bottom-space { height: calc(80rpx + env(safe-area-inset-bottom, 0px)); }

View File

@@ -48,11 +48,11 @@
</view>
</view>
</view>
<!-- 底部固定购买按钮(非 VIP 时显示) -->
<!-- 底部固定购买按钮(非 VIP 时显示,用 view 避让 button 默认 margin -->
<view class="buy-footer" wx:if="{{!isVip}}">
<button class="buy-btn-fixed" bindtap="handlePurchase" disabled="{{purchasing}}">
<view class="buy-btn-fixed {{purchasing ? 'buy-btn-disabled' : ''}}" bindtap="handlePurchase">
{{purchasing ? "处理中..." : "¥" + price + "/年 加入创业派对"}}
</button>
</view>
</view>
<view class="bottom-spacer" wx:if="{{!isVip}}"></view>
<!-- VIP资料填写仅VIP可见 -->

View File

@@ -28,11 +28,17 @@
.benefit-title { font-size: 26rpx; font-weight: bold; color: #fff; }
.benefit-desc { font-size: 20rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; line-height: 1.4; }
/* 底部固定购买按钮 - 设计稿 */
.buy-footer { position: fixed; bottom: 0; left: 0; right: 0; padding: 24rpx 32rpx; padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); background: rgba(0,0,0,0.95); border-top: 1rpx solid rgba(255,255,255,0.05); z-index: 50; }
.buy-btn-fixed { width: 100%; height: 96rpx; padding: 0; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #FFD700, #FFB000); color: #000; font-size: 32rpx; font-weight: bold; border-radius: 48rpx; border: none; box-shadow: 0 8rpx 32rpx rgba(255,188,46,0.2); }
.buy-btn-fixed::after { border: none; }
.buy-btn-fixed[disabled] { opacity: 0.6; }
/* 底部固定购买按钮 - 宽度拉满屏幕(用 view 替代 button 避让默认 margin */
.buy-footer { position: fixed; bottom: 0; left: 0; right: 0; padding: 24rpx 20rpx; padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); background: rgba(0,0,0,0.95); border-top: 1rpx solid rgba(255,255,255,0.05); z-index: 50; box-sizing: border-box; }
.buy-btn-fixed {
width: 100%;
height: 96rpx;
display: flex; align-items: center; justify-content: center;
background: linear-gradient(135deg, #FFD700, #FFB000); color: #000;
font-size: 32rpx; font-weight: bold; border-radius: 48rpx;
box-shadow: 0 8rpx 32rpx rgba(255,188,46,0.2);
}
.buy-btn-disabled { opacity: 0.6; pointer-events: none; }
.bottom-spacer { height: 180rpx; }
.profile-card { margin: 24rpx; padding: 32rpx; background: rgba(255,255,255,0.04); border: 1rpx solid rgba(255,255,255,0.08); border-radius: 20rpx; }

View File

@@ -24,12 +24,26 @@
"miniprogram": {
"list": [
{
"name": "pages/profile-edit/profile-edit",
"pathName": "pages/profile-edit/profile-edit",
"name": "pages/about/about",
"pathName": "pages/about/about",
"query": "",
"scene": null,
"launchMode": "default"
},
{
"name": "pages/vip/vip",
"pathName": "pages/vip/vip",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "pages/profile-edit/profile-edit",
"pathName": "pages/profile-edit/profile-edit",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "个人资料",
"pathName": "pages/profile-show/profile-show",

View File

@@ -6,6 +6,7 @@ import (
"net/http"
"strconv"
"strings"
"sync"
"time"
"soul-api/internal/database"
@@ -13,6 +14,7 @@ import (
"soul-api/internal/wechat"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// OrdersList GET /api/orders带用户昵称/头像/手机号,分销佣金按配置比例计算;支持分页 page、pageSize筛选 status搜索 search
@@ -29,50 +31,64 @@ func OrdersList(c *gin.Context) {
pageSize = 10
}
q := db.Model(&model.Order{})
if statusFilter != "" && statusFilter != "all" {
if statusFilter == "completed" {
q = q.Where("status IN ?", []string{"paid", "completed"})
} else {
q = q.Where("status = ?", statusFilter) // 含 refunded、pending、created、failed
// 预加载 referral_config避免订单循环内 N+1 查询
var refCfgRow model.SystemConfig
refCfg := (*model.SystemConfig)(nil)
if err := db.Where("config_key = ?", "referral_config").First(&refCfgRow).Error; err == nil {
refCfg = &refCfgRow
}
// 构建带筛选的查询count 与 list 共用条件)
applyOrdersFilter := func(q *gorm.DB) *gorm.DB {
if statusFilter != "" && statusFilter != "all" {
if statusFilter == "completed" {
q = q.Where("status IN ?", []string{"paid", "completed"})
} else {
q = q.Where("status = ?", statusFilter)
}
}
if search != "" {
pattern := "%" + search + "%"
q = q.Where("order_sn LIKE ? OR id LIKE ? OR user_id IN (SELECT id FROM users WHERE COALESCE(nickname,'') LIKE ? OR COALESCE(phone,'') LIKE ? OR id LIKE ?)",
pattern, pattern, pattern, pattern, pattern)
}
return q
}
if search != "" {
pattern := "%" + search + "%"
q = q.Where("order_sn LIKE ? OR id LIKE ? OR user_id IN (SELECT id FROM users WHERE COALESCE(nickname,'') LIKE ? OR COALESCE(phone,'') LIKE ? OR id LIKE ?)",
pattern, pattern, pattern, pattern, pattern)
}
var total int64
q.Count(&total)
var totalRevenue, todayRevenue float64
db.Model(&model.Order{}).Select("COALESCE(SUM(amount), 0)").
Where("status IN ?", []string{"paid", "completed"}).Scan(&totalRevenue)
todayStart := time.Now().Truncate(24 * time.Hour)
todayEnd := todayStart.Add(24 * time.Hour)
db.Model(&model.Order{}).Select("COALESCE(SUM(amount), 0)").
Where("status IN ? AND created_at >= ? AND created_at < ?", []string{"paid", "completed"}, todayStart, todayEnd).
Scan(&todayRevenue)
var orders []model.Order
query := db.Model(&model.Order{})
if statusFilter != "" && statusFilter != "all" {
if statusFilter == "completed" {
query = query.Where("status IN ?", []string{"paid", "completed"})
} else {
query = query.Where("status = ?", statusFilter)
}
}
if search != "" {
pattern := "%" + search + "%"
query = query.Where("order_sn LIKE ? OR id LIKE ? OR user_id IN (SELECT id FROM users WHERE COALESCE(nickname,'') LIKE ? OR COALESCE(phone,'') LIKE ? OR id LIKE ?)",
pattern, pattern, pattern, pattern, pattern)
}
if err := query.Order("created_at DESC").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&orders).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error(), "orders": []interface{}{}, "total": 0})
var ordersErr error
var wg sync.WaitGroup
// 并行count、营收统计、订单列表
wg.Add(3)
go func() {
defer wg.Done()
applyOrdersFilter(db.Model(&model.Order{})).Count(&total)
}()
go func() {
defer wg.Done()
db.Model(&model.Order{}).Select("COALESCE(SUM(amount), 0)").
Where("status IN ?", []string{"paid", "completed"}).Scan(&totalRevenue)
todayStart := time.Now().Truncate(24 * time.Hour)
todayEnd := todayStart.Add(24 * time.Hour)
db.Model(&model.Order{}).Select("COALESCE(SUM(amount), 0)").
Where("status IN ? AND created_at >= ? AND created_at < ?", []string{"paid", "completed"}, todayStart, todayEnd).
Scan(&todayRevenue)
}()
go func() {
defer wg.Done()
query := applyOrdersFilter(db.Model(&model.Order{}))
ordersErr = query.Order("created_at DESC").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&orders).Error
}()
wg.Wait()
if ordersErr != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": ordersErr.Error(), "orders": []interface{}{}, "total": 0})
return
}
totalPages := int(total) / pageSize
@@ -147,7 +163,7 @@ func OrdersList(c *gin.Context) {
if u := userMap[*o.ReferrerID]; u != nil {
refUser = u
}
m["referrerEarnings"] = computeOrderCommission(db, &o, refUser)
m["referrerEarnings"] = computeOrderCommission(db, &o, refUser, refCfg)
} else {
m["referrerEarnings"] = nil
}

View File

@@ -13,7 +13,8 @@ import (
// 会员订单:推广者会员 20%、非会员 10%内容订单90%(好友优惠 5% 仅针对内容)
// order: 已支付订单,需有 product_type、amount、referrer_id
// referrerUser: 推广者用户信息,用于判断 is_vip可为 nil会查库
func computeOrderCommission(db *gorm.DB, order *model.Order, referrerUser *model.User) float64 {
// preloadConfig: 可选,预加载的 referral_config避免 N+1 查询
func computeOrderCommission(db *gorm.DB, order *model.Order, referrerUser *model.User, preloadConfig ...*model.SystemConfig) float64 {
if order == nil || order.ReferrerID == nil || *order.ReferrerID == "" {
return 0
}
@@ -22,8 +23,17 @@ func computeOrderCommission(db *gorm.DB, order *model.Order, referrerUser *model
userDiscount := 0.0
vipOrderShareVip := 20.0
vipOrderShareNonVip := 10.0
var cfg model.SystemConfig
if err := db.Where("config_key = ?", "referral_config").First(&cfg).Error; err == nil {
var cfg *model.SystemConfig
if len(preloadConfig) > 0 && preloadConfig[0] != nil {
cfg = preloadConfig[0]
} else if row, err := (func() (*model.SystemConfig, error) {
var r model.SystemConfig
e := db.Where("config_key = ?", "referral_config").First(&r).Error
return &r, e
})(); err == nil {
cfg = row
}
if cfg != nil {
var config map[string]interface{}
if err := json.Unmarshal(cfg.ConfigValue, &config); err == nil {
if share, ok := config["distributorShare"].(float64); ok {

View File

@@ -271,20 +271,26 @@ func VipMembers(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "data": list, "total": len(list)})
}
// formatVipMember 仅从 vip_* 字段构建会员展示数据,不混入用户信息
// 用于创业老板排行等场景;未填会员资料时 name 显示「创业者」占位
// formatVipMember 构建会员展示数据;优先 vip_*,无则回退到用户 nickname/avatar
// 用于首页超级个体、创业老板排行等场景,展示真实用户头像和昵称
func formatVipMember(u *model.User, isVip bool) gin.H {
name := ""
if u.VipName != nil {
if u.VipName != nil && *u.VipName != "" {
name = *u.VipName
}
if name == "" && u.Nickname != nil && *u.Nickname != "" {
name = *u.Nickname
}
if name == "" {
name = "创业者"
}
avatar := ""
if u.VipAvatar != nil {
if u.VipAvatar != nil && *u.VipAvatar != "" {
avatar = *u.VipAvatar
}
if avatar == "" && u.Avatar != nil && *u.Avatar != "" {
avatar = *u.Avatar
}
project := ""
if u.VipProject != nil {
project = *u.VipProject
@@ -302,17 +308,19 @@ func formatVipMember(u *model.User, isVip bool) gin.H {
vipRole = *u.VipRole
}
return gin.H{
"id": u.ID,
"name": name,
"nickname": name,
"avatar": avatar,
"vipName": name,
"vipRole": vipRole,
"vipAvatar": avatar,
"vipProject": project,
"vipContact": contact,
"vipBio": bio,
"is_vip": isVip,
"id": u.ID,
"name": name,
"nickname": name,
"avatar": avatar,
"vip_name": name,
"vipName": name,
"vipRole": vipRole,
"vip_avatar": avatar,
"vipAvatar": avatar,
"vipProject": project,
"vipContact": contact,
"vipBio": bio,
"is_vip": isVip,
}
}

Binary file not shown.