topfans/frontend/pages/support-activity/components/MessageInput.vue

317 lines
9.2 KiB
Vue

<template>
<!--
Figma 147:325 (Group 50, 296x42)
展示型留言条:左侧圆形头像 + 中间居中文本 + 右侧表情 + 发送箭头
点击整条 / 点击发送箭头会触发 send 事件,父组件可弹出真实输入面板
-->
<view class="message-row" @click="handleTap">
<!-- 外层 pill 容器 (296x42, radius 20) -->
<view class="message-row__pill">
<!-- 内嵌凹槽 (238x30, radius 16) -->
<view class="message-row__groove">
<!--
·输入框:输入框默认空,placeholder 复用 displayText 展示"轮播祝福语"
用户开始键入 placeholder 自动消失(native placeholder 行为),
emoji 切换只更新 placeholder,不会覆盖用户已经输入的内容
-->
<input
class="message-row__input"
:value="inputValue"
:placeholder="displayText"
placeholder-class="message-row__input-placeholder"
confirm-type="send"
:maxlength="200"
@input="onInput"
@confirm="handleSend"
/>
</view>
</view>
<!-- 左侧圆形头像 (35x35) -->
<view class="message-row__avatar">
<image
class="message-row__avatar-img"
:src="avatar || defaultAvatar"
mode="aspectFill"
/>
<image
class="message-row__avatar-fallback"
src="/static/square/gerentouxiangkuang.png"
mode="aspectFill"
/>
</view>
<!-- 骰子表情 (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) -->
<view class="message-row__send" @click.stop="handleSend">
<image
class="message-row__send-img"
src="/static/rank/activity-support-icon/message-row/send.png"
mode="aspectFit"
/>
</view>
</view>
</template>
<script setup>
import { computed, ref } from "vue";
import { useStore } from "vuex";
/**
* 点击骰子 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: "",
},
/**
* 头像 URL,未传时回退到当前登录用户的 avatar_url。
*/
avatar: {
type: String,
default: "",
},
/**
* 兼容旧用法:外部如果还在传 placeholder,作为 text 的兜底。
*/
placeholder: {
type: String,
default: "",
},
});
const emit = defineEmits(["send", "tap", "update:text"]);
// 兜底默认头像:不再硬编码,直接读取 Vuex user 模块中的 avatar_url
// (登录时已通过 SET_USER_INFO 写入 store 与本地缓存,头像更新后也会触发 userInfoUpdated 通知)
const store = useStore();
const userInfo = computed(() => store.state.user?.userInfo || null);
const defaultAvatar = computed(() => userInfo.value?.avatar_url || "");
// 按下反馈:触摸 / 按下时短暂为 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)
return props.placeholder;
return "今天第一天姐妹们大家冲鸭!!!";
});
// 输入框本地值:默认空串,只受用户键入控制;
// 父组件的 props.text(emoji 切换 / 直接赋值)不再回灌进 inputValue,
// 而是作为 placeholder(displayText)展示 —— 这样:
// 1) 输入框永远是空的,placeholder 一键消失(native placeholder 行为),用户不用手动删
// 2) emoji 切换不会覆盖用户已经输入的内容,只切换 placeholder
const inputValue = ref("");
function onInput(e) {
// uniapp 小程序 / h5: e.detail.value;某些端直接传字符串;两种都兼容
const val = typeof e === "string" ? e : e?.detail?.value ?? "";
inputValue.value = val;
emit("update:text", val);
}
function handleTap() {
emit("tap");
}
function handleEmoji() {
// 抛 update:text 让父组件以 v-model:text 接收;不修改 props.text 本地副本,
// 避免父组件没接 v-model 时出现"看起来生效但父组件状态没变"的伪成功。
emit("update:text", pickNextGreeting());
}
function handleSend() {
// 兜底语义:用户未输入时发送 displayText(占位文案);有输入时优先发输入内容
const text = inputValue.value || displayText.value;
emit("send", text);
// 发送后清空输入框:
// 1) 本地 inputValue 清空 → 下次点 send 才会真正走到 fallback 分支
// 2) emit update:text("") → 父组件 messageDraft 同步清空 → displayText 重新计算
// → placeholder 重新可见(native placeholder 行为)
// 否则 inputValue 一直残留旧值,后续点击 send 永远发的都是上一次的文字,fallback 不会触发
inputValue.value = "";
emit("update:text", "");
}
</script>
<style scoped>
/*
* 设计稿原始尺寸 (px) → rpx (按 750 / 375 = 2 倍换算)
* 容器 296 x 42 → 592rpx x 84rpx
* 凹槽 238 x 30 → 476rpx x 60rpx (left 12px=24rpx,top 6px=12rpx)
* 头像 35 x 35 → 70rpx x 70rpx (left 6px=12rpx,top 3px=6rpx)
* 文本 11px → 22rpx
* emoji 17 x 17 → 34rpx x 34rpx
* send 21 x 21 → 42rpx x 42rpx
*/
.message-row {
position: fixed;
left: 22rpx; /* 11px */
bottom: 32rpx;
width: 592rpx;
height: 84rpx;
z-index: 50;
}
/* 外层 pill */
.message-row__pill {
position: absolute;
inset: 0;
background: rgba(217, 217, 217, 0.4);
border-radius: 40rpx; /* 20px */
box-shadow: 2rpx 4rpx 8rpx 0 rgba(0, 0, 0, 0.25);
}
/* 内嵌凹槽 */
.message-row__groove {
position: absolute;
left: 24rpx; /* 12px */
top: 12rpx; /* 6px */
width: 412rpx; /* 238px */
height: 60rpx; /* 30px */
background: rgba(54, 51, 51, 0.1);
border-radius: 32rpx; /* 16px */
display: flex;
align-items: center;
/* 头像突出在 pill 左侧,文字从凹槽右半段开始;末尾留出表情 + 发送图标空间 */
padding-left: 80rpx;
/* padding-right: 120rpx; */
overflow: hidden;
}
.message-row__input {
display: block;
width: 100%;
height: 60rpx; /* 30px,撑满凹槽高度 */
font-size: 22rpx; /* 11px */
line-height: 1.3;
color: #ffffff;
font-weight: bold;
font-family: "Abhaya Libre ExtraBold", "PingFang SC", sans-serif;
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.45);
background: transparent;
border: none;
outline: none;
padding: 0;
/* 设计稿的轻微倾斜 */
transform: rotate(-0.44deg) skewX(-0.1deg);
transform-origin: center;
}
.message-row__input-placeholder {
color: rgba(255, 255, 255, 0.9);
font-weight: bold;
font-family: "Abhaya Libre ExtraBold", "PingFang SC", sans-serif;
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.45);
}
/* 左侧头像 (突出 pill 左缘) */
.message-row__avatar {
position: absolute;
left: 12rpx; /* 6px */
top: 6rpx; /* 3px */
width: 70rpx; /* 35px */
height: 70rpx;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 0 14rpx 0 rgba(203, 24, 24, 0.84);
background: #2a1111;
}
.message-row__avatar-img {
width: 100%;
height: 100%;
display: block;
}
.message-row__avatar-fallback {
width: 100%;
height: 100%;
display: block;
position: absolute;
top: 0;
transform: scale(1.05);
}
/* 骰子表情 */
.message-row__emoji {
position: absolute;
right: 92rpx; /* ≈ 46px from right edge */
top: 24rpx; /* (84-34)/2 ≈ 25 */
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);
}
/* 右侧发送箭头 (可点击) */
.message-row__send {
position: absolute;
right: 28rpx; /* ≈ 14px */
top: 20rpx; /* 21 */
width: 42rpx; /* 21px */
height: 42rpx;
display: flex;
align-items: center;
justify-content: center;
filter: drop-shadow(0 6rpx 8rpx rgba(0, 0, 0, 0.47));
}
.message-row__send-img {
width: 100%;
height: 100%;
}
.message-row__send:active {
transform: scale(0.92);
transition: transform 0.12s ease;
}
</style>