feat:完善应援活动
This commit is contained in:
parent
9410fd53e1
commit
a6ce0f5865
@ -1,7 +1,7 @@
|
||||
# 开发环境配置
|
||||
# HBuilderX「运行」时自动加载;CLI 用 --mode development
|
||||
# VITE_API_BASE_URL=http://192.168.110.60:8080
|
||||
VITE_API_BASE_URL=https://api.topfans.online
|
||||
VITE_API_BASE_URL=http://192.168.110.60:8080
|
||||
# VITE_API_BASE_URL=https://api.topfans.online
|
||||
# WebSocket 地址:如与 API 同源可省略(自动从 VITE_API_BASE_URL 推导 http→ws、https→wss)
|
||||
# 独立部署时直接覆盖,例如:ws://192.168.110.60:8081
|
||||
VITE_WS_BASE_URL=ws://192.168.110.60:8080
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
<!-- 藏品卡片区域 -->
|
||||
<view class="card-section">
|
||||
<view class="card-wrapper" :class="{ 'card-wrapper--lenticular': isLenticularAsset }">
|
||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFit">
|
||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill">
|
||||
</image>
|
||||
<view v-if="isLenticularAsset" class="detail-lenticular-slot">
|
||||
<LenticularCard class="detail-lenticular-card" :layers="lenticularLayers"
|
||||
|
||||
@ -96,9 +96,15 @@
|
||||
mode="aspectFill"
|
||||
:lazy-load="true"
|
||||
/>
|
||||
<view v-else class="cover-fallback">{{
|
||||
item.title || "活动"
|
||||
}}</view>
|
||||
<view class="date-row">
|
||||
<text class="date-text">{{ formatDateRange(item) }}</text>
|
||||
<text v-if="!isEnded(item)" class="countdown-text">
|
||||
距离活动结束还有{{ getDaysLeft(item) }}天
|
||||
</text>
|
||||
</view>
|
||||
<!-- <view v-else class="cover-fallback"
|
||||
>{{ item.title || "活动" }}
|
||||
</view> -->
|
||||
<view
|
||||
v-if="item.status === 'expired' || item.status === 'completed'"
|
||||
class="cover-ended"
|
||||
@ -115,14 +121,6 @@
|
||||
<text class="name-text">{{ getShortName(item) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="date-row">
|
||||
<text class="date-text">{{ formatDateRange(item) }}</text>
|
||||
<text v-if="!isEnded(item)" class="countdown-text">
|
||||
距离活动结束还有{{ getDaysLeft(item) }}天
|
||||
</text>
|
||||
</view> -->
|
||||
|
||||
<view class="stat-row">
|
||||
<view class="stat-block">
|
||||
<text class="block-value">{{
|
||||
@ -739,23 +737,37 @@ onShow(() => {
|
||||
}
|
||||
|
||||
.date-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
height: 72rpx;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
inset: 0;
|
||||
top: 88rpx;
|
||||
}
|
||||
|
||||
.date-text {
|
||||
font-size: 18rpx;
|
||||
color: #fffabd;
|
||||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.84);
|
||||
font-weight: bold;
|
||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||||
font-family: "Baloo Bhai";
|
||||
font-size: 20rpx;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 24rpx;
|
||||
}
|
||||
|
||||
.countdown-text {
|
||||
font-size: 14rpx;
|
||||
color: #fffabd;
|
||||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.84);
|
||||
opacity: 0.85;
|
||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||||
font-family: "Abhaya Libre ExtraBold";
|
||||
font-size: 14rpx;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: normal;
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.stat-row {
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
<text class="item-x">X</text>
|
||||
<text
|
||||
class="item-count"
|
||||
:class="`count-${(record.combo_count > 1 ? record.combo_count : record.quantity).toString().length}-digit`"
|
||||
:class="getCountSizeClass(record.combo_count > 1 ? record.combo_count : record.quantity)"
|
||||
>{{
|
||||
record.combo_count > 1 ? record.combo_count : record.quantity
|
||||
}}</text
|
||||
@ -96,6 +96,19 @@ const estimateTextWidth = (text, fontSize) => {
|
||||
return Math.round(width);
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据数量值映射数字字号档位
|
||||
* - >= 999 → count-largest(最大)
|
||||
* - 520 ~ 998 → count-mid(次大)
|
||||
* - < 520 → count-small(最小)
|
||||
*/
|
||||
const getCountSizeClass = (value) => {
|
||||
const n = Number(value) || 0;
|
||||
if (n >= 999) return "count-largest";
|
||||
if (n >= 520) return "count-mid";
|
||||
return "count-small";
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据昵称(+赠送道具名)动态计算胶囊宽度
|
||||
* 组成:左右内边距(32) + 头像(78) + 头像间距(16) + 文字宽度 + 文字右侧间距(16)
|
||||
@ -258,7 +271,7 @@ const getItemWidth = (record) => {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
display: block;
|
||||
transform: rotate(11deg);
|
||||
transform: rotate(10deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
@ -285,19 +298,15 @@ const getItemWidth = (record) => {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 数字位数不同对应不同尺寸,对照 Figma 设计:3位 76rpx / 2位 52rpx / 1位 48rpx */
|
||||
.count-1-digit {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
.count-2-digit {
|
||||
font-size: 52rpx;
|
||||
}
|
||||
.count-3-digit {
|
||||
/* 数量档位样式:999+ 顶格、520~998 中档、其他常规 */
|
||||
.count-largest {
|
||||
font-size: 76rpx;
|
||||
}
|
||||
.count-4-digit,
|
||||
.count-5-digit {
|
||||
font-size: 64rpx;
|
||||
.count-mid {
|
||||
font-size: 52rpx;
|
||||
}
|
||||
.count-small {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
|
||||
@ -38,12 +38,12 @@ watch(
|
||||
.message-board {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
bottom: 128rpx;
|
||||
padding: 0 34rpx; /* 对应 Figma 中 left: 17px */
|
||||
}
|
||||
|
||||
.message-board-scroll {
|
||||
height: 480rpx;
|
||||
height: 352rpx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -53,10 +53,10 @@ watch(
|
||||
|
||||
/* 留言气泡:对应 Figma 留言板 的圆角矩形 */
|
||||
.message-bubble {
|
||||
display: inline-flex;
|
||||
/* display: inline-flex; */
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
max-width: max-content;
|
||||
padding: 6rpx 16rpx; /* 适当内边距,让文字与气泡边距舒适 */
|
||||
background: rgba(42, 17, 17, 0.3);
|
||||
border-radius: 16rpx; /* 8px = 16rpx */
|
||||
|
||||
@ -22,11 +22,16 @@
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 骰子表情 (17x17) -->
|
||||
<!-- 骰子表情 (17x17):点击按顺序循环替换 text -->
|
||||
<image
|
||||
class="message-row__emoji"
|
||||
:class="{ 'is-pressing': pressing }"
|
||||
src="/static/rank/activity-support-icon/message-row/emoji.png"
|
||||
mode="aspectFit"
|
||||
@click.stop="handleEmoji"
|
||||
@touchstart="pressing = true"
|
||||
@touchend="pressing = false"
|
||||
@touchcancel="pressing = false"
|
||||
/>
|
||||
|
||||
<!-- 发送箭头 (21x21) -->
|
||||
@ -41,15 +46,41 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
/**
|
||||
* 点击骰子 emoji 时按顺序循环选用的祝福语。
|
||||
* 顺序循环:首次取第一条,再点取下一条,到末尾后回到开头。
|
||||
* 调整 / 增删文案直接改这里即可。
|
||||
*/
|
||||
const GREETINGS = [
|
||||
"今天第一天姐妹们大家冲鸭!!!",
|
||||
"冲冲冲!!!生日应援走起~",
|
||||
"永远支持你!生日快乐 🎂",
|
||||
"前排打卡!爱了爱了 ❤️",
|
||||
"姐妹们冲鸭,今天也要元气满满~",
|
||||
"生日快乐 🎉 永远守护你!",
|
||||
"第一第一!!我永远在你身后",
|
||||
"鸭鸭冲鸭!为爱发电不停歇~",
|
||||
];
|
||||
// 模块级游标:组件被卸载重建时不会从 0 开始,避免反复重置
|
||||
let greetingCursor = 0;
|
||||
|
||||
function pickNextGreeting() {
|
||||
if (GREETINGS.length === 0) return "";
|
||||
const next = GREETINGS[greetingCursor % GREETINGS.length];
|
||||
greetingCursor += 1;
|
||||
return next;
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
* 展示的文案:父组件可传入最新一条留言,默认使用 Figma 设计稿文案。
|
||||
* 推荐通过 v-model:text 双向绑定,以便点击 emoji 时组件能通知父组件更新。
|
||||
*/
|
||||
text: {
|
||||
type: String,
|
||||
default: "今天第一天姐妹们大家冲鸭!!!",
|
||||
default: "",
|
||||
},
|
||||
/**
|
||||
* 头像 URL,未传时回退到默认头像。
|
||||
@ -67,11 +98,14 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["send", "tap"]);
|
||||
const emit = defineEmits(["send", "tap", "update:text"]);
|
||||
|
||||
const defaultAvatar =
|
||||
"/static/rank/activity-support-icon/message-row/avatar.png";
|
||||
|
||||
// 按下反馈:触摸 / 按下时短暂为 true,松开恢复
|
||||
const pressing = ref(false);
|
||||
|
||||
const displayText = computed(() => {
|
||||
if (props.text && props.text.trim().length > 0) return props.text;
|
||||
if (props.placeholder && props.placeholder.trim().length > 0)
|
||||
@ -83,6 +117,12 @@ function handleTap() {
|
||||
emit("tap");
|
||||
}
|
||||
|
||||
function handleEmoji() {
|
||||
// 抛 update:text 让父组件以 v-model:text 接收;不修改 props.text 本地副本,
|
||||
// 避免父组件没接 v-model 时出现"看起来生效但父组件状态没变"的伪成功。
|
||||
emit("update:text", pickNextGreeting());
|
||||
}
|
||||
|
||||
function handleSend() {
|
||||
// 展示型组件:点击发送图标抛出事件,业务侧决定弹出输入面板 / 直接发送等行为
|
||||
emit("send", displayText.value);
|
||||
@ -179,6 +219,13 @@ function handleSend() {
|
||||
width: 34rpx; /* 17px */
|
||||
height: 34rpx;
|
||||
filter: drop-shadow(0 4rpx 8rpx rgba(138, 6, 6, 0.74));
|
||||
transition: transform 0.12s ease;
|
||||
}
|
||||
|
||||
/* emoji 按下反馈(顺序循环选祝福语的可点击提示) */
|
||||
.message-row__emoji.is-pressing,
|
||||
.message-row__emoji:active {
|
||||
transform: scale(0.85);
|
||||
}
|
||||
|
||||
/* 右侧发送箭头 (可点击) */
|
||||
|
||||
@ -106,7 +106,11 @@
|
||||
</view>
|
||||
|
||||
<!-- 留言输入框:对齐底部确认赠送按钮位置 -->
|
||||
<MessageInput placeholder="留下你的祝福..." @send="handleSendMessage" />
|
||||
<MessageInput
|
||||
v-model:text="messageDraft"
|
||||
placeholder="留下你的祝福..."
|
||||
@send="handleSendMessage"
|
||||
/>
|
||||
|
||||
<!-- 底部操作栏弹出框 -->
|
||||
<view v-if="actionBarVisible" class="action-bar-popup">
|
||||
@ -232,6 +236,10 @@ const messageBoardVisible = ref(false);
|
||||
const rankingModalVisible = ref(false);
|
||||
const currentActivityTitle = ref("");
|
||||
|
||||
// 留言草稿:MessageInput 通过 v-model:text 双向绑定,
|
||||
// 点击骰子 emoji 时按顺序循环选用内置祝福语填入这里
|
||||
const messageDraft = ref("今天第一天姐妹们大家冲鸭!!!");
|
||||
|
||||
// 留言板 mock 数据
|
||||
const messageList = ref([
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user