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