feat(event): 添加酒局报名审核功能并优化前端组件

- 新增 ApproveEvent API 接口用于审核报名用户
- 在 EventCard 组件中显示待审核状态(⏳ 待审核)
- 实现酒局发起人审核报名用户的完整流程
- 添加用户协议和隐私政策页面
- 优化 FeedCard 组件中的图片展示逻辑
- 修复连续打卡天数计算问题
- 更新 HaveADrink SDK 至 1.0.15 版本
This commit is contained in:
cg
2026-08-16 14:50:50 +08:00
parent 527b0e8a8d
commit 81e7a3afa5
20 changed files with 1470 additions and 249 deletions
+190 -22
View File
@@ -32,10 +32,12 @@
<text class="stat-label">标准杯</text>
</view>
<view class="stat-card card">
<text class="stat-value text-num" :class="stats.streakType === 'drank' ? 'text-amber' : 'text-mint'">
{{ stats.streak }}
</text>
<text class="stat-label">{{ stats.streakType === 'drank' ? '连喝天数' : '戒酒天数' }}</text>
<text class="stat-value text-num text-amber">{{ stats.drankStreak }}</text>
<text class="stat-label">连喝天数</text>
</view>
<view class="stat-card card">
<text class="stat-value text-num text-mint">{{ stats.abstainStreak }}</text>
<text class="stat-label">戒酒天数</text>
</view>
</view>
@@ -83,9 +85,10 @@
<!-- 日期记录弹窗 -->
<view v-if="showDayDetail" class="day-overlay" @click="showDayDetail = false">
<view class="day-detail card-elevated" @click.stop>
<view class="day-detail-handle"></view>
<view class="day-detail-header flex-between">
<text class="text-h3">{{ selectedDate }}</text>
<text class="btn-text" @click="showDayDetail = false">关闭</text>
<text class="day-detail-date text-h3">{{ selectedDate }}</text>
<text class="day-close btn-text" @click="showDayDetail = false">关闭</text>
</view>
<view v-if="selectedRecord">
<view v-if="selectedRecord.mode === 'drank'" class="day-record-info">
@@ -101,9 +104,14 @@
</view>
</view>
</view>
<view class="day-meta flex-between">
<text class="text-caption">{{ getFeelingName(selectedRecord.feeling) }}</text>
<text class="text-caption text-amber text-num">{{ selectedRecord.standardCupsTotal }} 标准杯</text>
<view class="day-meta">
<view class="day-meta-chip day-feeling-chip">
<text class="day-feeling-text">{{ getFeelingName(selectedRecord.feeling) }}</text>
</view>
<view class="day-meta-chip day-cups-chip">
<text class="day-cups-num text-num">{{ selectedRecord.standardCupsTotal }}</text>
<text class="day-cups-unit">标准杯</text>
</view>
</view>
<view class="day-action-row">
<button class="btn-ghost day-action-btn" @click="editRecord">修改</button>
@@ -120,8 +128,9 @@
</view>
<view v-else class="day-empty">
<text class="text-caption">当天无记录</text>
<button v-if="isSelectedToday" class="btn-primary day-record-btn" @click="goRecordToday">去记录</button>
<button v-else class="btn-ghost" @click="backfillDate">补打卡</button>
<button v-if="isSelectedToday" class="btn-primary day-record-btn" @click="goRecordToday">记录饮酒</button>
<button v-else class="btn-ghost day-record-btn" @click="backfillDate">补打卡饮酒</button>
<button class="btn-ghost day-abstain-btn" @click="markAbstain">未饮酒</button>
</view>
</view>
</view>
@@ -132,7 +141,7 @@
import DrinkCalendar from '../../components/DrinkCalendar.vue'
import client from '../../common/api'
import { GetCalendarReq, DeleteRecordReq, CreateRecordReq, GetRecordDetailReq } from 'HaveADrink'
import { getGreeting, formatDate, getWeekStart, getCatIcon, isIconPath } from '../../common/utils'
import { getGreeting, formatDate, getWeekStart, getCatIcon, isIconPath, calcLocalStreak } from '../../common/utils'
import { FEELINGS, DRINK_CATEGORIES, DRINK_UNITS } from '../../common/constants'
import { savePendingInvite, handlePendingInvite } from '../../common/invite'
import themeMixin from '../../common/theme-mixin'
@@ -236,6 +245,8 @@ export default {
favCategory: s.favCategory ? String(s.favCategory) : null,
categoryVariety: s.categoryVariety || 0
}
// 后端 streak 不区分饮酒/戒酒模式,拉取记录本地重算修正
this.refreshLocalStreak()
}
// 日历数据
@@ -270,6 +281,22 @@ export default {
// 切换月份时重新加载记录
this.loadRecordsForMonth(year, month)
},
/** 后端 streak 不区分饮酒/戒酒模式,拉取全部记录后本地重算连续天数 */
async refreshLocalStreak() {
try {
const resp = await client.GetRecords({ page: 1, pageSize: 100 })
const payload = resp.data || resp
const list = payload.list || []
if (!list.length) return
const { streak, streakType, drankStreak, abstainStreak } = calcLocalStreak(list)
this.stats.streak = streak
this.stats.streakType = streakType
this.stats.drankStreak = drankStreak
this.stats.abstainStreak = abstainStreak
} catch (e) {
console.warn('本地计算连续天数失败:', e)
}
},
async loadRecordsForMonth(year, month) {
try {
const resp = await client.GetCalendar(new GetCalendarReq(year, month))
@@ -415,7 +442,31 @@ export default {
this.showDayDetail = false
uni.navigateTo({ url: '/pages/record/record' })
},
// 标记为未饮酒(直接创建 abstain 记录)
async markAbstain() {
const recordData = {
date: this.selectedDate,
mode: 'abstain',
drinks: [],
food: null,
feeling: null,
photos: [],
visibility: 'friends',
quote: ''
}
try {
uni.showLoading({ title: '保存中...' })
await client.CreateRecord(recordData)
uni.hideLoading()
this.showDayDetail = false
uni.showToast({ title: '已记录未饮酒', icon: 'success' })
this.loadData()
} catch (e) {
uni.hideLoading()
console.warn('标记未饮酒失败:', e)
uni.showToast({ title: (e && e.msg) || '保存失败,请重试', icon: 'none' })
}
},
}
}
</script>
@@ -434,7 +485,7 @@ export default {
.greeting-text {
display: block;
font-size:38rpx;
font-size:42rpx;
font-weight: $fw-bold;
color: $text-primary;
}
@@ -632,24 +683,83 @@ export default {
align-items: flex-end;
z-index: 100;
padding: $sp-lg;
animation: overlayIn 0.2s ease-out;
}
@keyframes overlayIn {
from { opacity: 0; }
to { opacity: 1; }
}
.day-detail {
position: relative;
width: 100%;
padding: $sp-xl;
padding: $sp-md $sp-xl $sp-xl;
color: $text-primary;
border-radius: $radius-xl $radius-xl 0 0;
border-radius: $radius-xl;
/* 背景层次:底部卡片色 + 顶部琥珀光晕,日夜主题自适应 */
background:
radial-gradient(120% 60% at 50% 0%, var(--amber-glow, rgba(232, 168, 56, 0.15)) 0%, transparent 60%),
linear-gradient(180deg, var(--bg-elevated, #28283E) 0%, var(--bg-card, #1A1A28) 55%);
border: 2rpx solid var(--border-faint, rgba(255, 255, 255, 0.08));
box-shadow: var(--shadow-lg);
animation: sheetIn 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes sheetIn {
from { opacity: 0; transform: translateY(48rpx); }
to { opacity: 1; transform: translateY(0); }
}
/* 顶部拖拽把手 */
.day-detail-handle {
width: 64rpx;
height: 8rpx;
border-radius: $radius-full;
background: var(--border-active, rgba(255, 255, 255, 0.12));
margin: 0 auto $sp-md;
}
.day-detail-header {
margin-bottom: $sp-lg;
}
.day-detail-date {
position: relative;
padding-left: $sp-md;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 6rpx;
height: 70%;
border-radius: $radius-full;
background: linear-gradient(180deg, var(--amber-light, #F5C563), var(--amber-deep, #C47F17));
}
}
.day-close {
padding: $sp-xs $sp-sm;
}
.day-drinks {
background: var(--bg-subtle, rgba(255, 255, 255, 0.03));
border-radius: $radius-md;
padding: $sp-xs $sp-md;
}
.day-drink-item {
padding: $sp-sm 0;
padding: $sp-md 0;
font-size: $fs-base;
color: $text-primary;
border-bottom: 2rpx solid var(--border-micro, rgba(255, 255, 255, 0.05));
&:last-child {
border-bottom: none;
}
}
.day-drink-row {
@@ -680,9 +790,44 @@ export default {
}
.day-meta {
display: flex;
gap: $sp-sm;
margin-top: $sp-lg;
}
.day-meta-chip {
display: flex;
align-items: center;
gap: $sp-xs;
padding: $sp-sm $sp-md;
border-radius: $radius-full;
}
.day-feeling-chip {
background: var(--bg-hover, rgba(255, 255, 255, 0.08));
}
.day-feeling-text {
font-size: $fs-sm;
color: $text-secondary;
}
.day-cups-chip {
background: var(--amber-glow, rgba(232, 168, 56, 0.15));
border: 2rpx solid rgba(232, 168, 56, 0.25);
}
.day-cups-num {
font-size: $fs-lg;
font-weight: $fw-bold;
color: $amber;
}
.day-cups-unit {
font-size: $fs-xs;
color: $amber;
}
.day-abstain {
text-align: center;
padding: $sp-xl 0;
@@ -702,6 +847,12 @@ export default {
margin-top: $sp-lg;
width: 100%;
}
.day-abstain-btn {
color: $mint;
border-color: rgba(94, 198, 160, 0.35);
background: rgba(94, 198, 160, 0.08);
}
}
.day-action-row {
@@ -710,20 +861,37 @@ export default {
margin-top: $sp-lg;
}
/* 修改:琥珀主操作 */
.day-action-btn {
flex: 1;
font-size: $fs-sm;
color: $text-secondary;
border-color: var(--border-active, rgba(255, 255, 255, 0.12));
&:first-child {
color: $text-on-amber;
background: linear-gradient(135deg, var(--amber-light, #F5C563), var(--amber-deep, #C47F17));
border: none;
box-shadow: var(--shadow-amber);
}
/* 改为未饮酒:薄荷淡底 */
&:last-child {
color: $mint;
background: rgba(94, 198, 160, 0.08);
border: 2rpx solid rgba(94, 198, 160, 0.30);
}
}
.day-delete-btn {
margin-top: $sp-md;
width: 100%;
font-size: $fs-sm;
color: #e85d3a;
background: rgba(232, 93, 58, 0.08);
border: 2rpx solid rgba(232, 93, 58, 0.2);
color: #E85D3A;
background: rgba(232, 93, 58, 0.06);
border: 2rpx solid rgba(232, 93, 58, 0.30);
&:active {
background: rgba(232, 93, 58, 0.14);
}
}
.day-toggle-btn {