first commit
This commit is contained in:
@@ -0,0 +1,850 @@
|
||||
<template>
|
||||
<view class="chat-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="top_bar flex w-full" v-if="isMobile">
|
||||
<view class="chat-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</view>
|
||||
<view class="header-title">聊天</view>
|
||||
<view class="header-right" @click="goToChatDetail">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 浏览器环境下的导航栏 -->
|
||||
<view class="chat-header" v-else>
|
||||
<view class="header-left" @click="goBack">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</view>
|
||||
<view class="header-title">聊天</view>
|
||||
<view class="header-right" @click="goToChatDetail">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 聊天消息列表 -->
|
||||
<scroll-view
|
||||
class="message-list"
|
||||
scroll-y
|
||||
:scroll-top="scrollTop"
|
||||
:scroll-into-view="scrollIntoView"
|
||||
>
|
||||
<block v-for="(message, index) in messages" :key="index">
|
||||
<view
|
||||
:id="'message-' + index"
|
||||
:class="[
|
||||
'message-item',
|
||||
message.type === 'sent' ? 'sent' : 'received',
|
||||
]"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<view class="avatar" @click="goToUserDetail">
|
||||
<image
|
||||
:src="
|
||||
message.type === 'sent'
|
||||
? '/static/imgs/default_avatar.png'
|
||||
: '/static/imgs/default_avatar.png'
|
||||
"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
|
||||
<!-- 消息内容 -->
|
||||
<view class="message-content">
|
||||
<view class="message-bubble">
|
||||
<!-- 文本消息 -->
|
||||
<text class="message-text">{{
|
||||
parseEmoji(message.content)
|
||||
}}</text>
|
||||
</view>
|
||||
<!-- 消息时间 -->
|
||||
<view class="message-time" v-if="message.time">
|
||||
{{ formatTime(message.time) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<view class="input-area">
|
||||
<!-- 表情选择器 -->
|
||||
<view class="emoji-picker-container" v-if="showEmojiPicker">
|
||||
<EmojiPicker
|
||||
:visible="showEmojiPicker"
|
||||
@select="onEmojiSelect"
|
||||
@close="showEmojiPicker = false"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 输入工具栏 -->
|
||||
<view class="input-toolbar">
|
||||
<!-- 左侧切换按钮 -->
|
||||
<view class="left-switch">
|
||||
<view
|
||||
class="switch-btn"
|
||||
@click="switchInputMode"
|
||||
:class="{ active: inputMode === 'voice' }"
|
||||
>
|
||||
<i
|
||||
:class="
|
||||
inputMode === 'voice' ? 'fas fa-keyboard' : 'fas fa-microphone'
|
||||
"
|
||||
></i>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 中间输入区域 -->
|
||||
<view class="input-wrapper">
|
||||
<!-- 语音模式:按住说话按钮 -->
|
||||
<view
|
||||
v-if="inputMode === 'voice'"
|
||||
class="voice-input"
|
||||
:class="{ recording: isRecording }"
|
||||
@touchstart="startVoiceRecord"
|
||||
@touchend="endVoiceRecord"
|
||||
@touchcancel="cancelVoiceRecord"
|
||||
>
|
||||
<text class="voice-text">{{
|
||||
isRecording ? "松开结束" : "按住说话"
|
||||
}}</text>
|
||||
<view v-if="isRecording" class="recording-indicator">
|
||||
<view class="recording-dot"></view>
|
||||
<view class="recording-dot"></view>
|
||||
<view class="recording-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 文本模式:输入框 -->
|
||||
<view v-else class="text-input-container">
|
||||
<textarea
|
||||
v-model="inputMessage"
|
||||
placeholder="输入消息..."
|
||||
class="message-input"
|
||||
:style="{ height: inputHeight + 'rpx' }"
|
||||
:maxlength="500"
|
||||
@confirm="sendMessage"
|
||||
@focus="onInputFocus"
|
||||
@blur="onInputBlur"
|
||||
@input="onInputChange"
|
||||
@linechange="onLineChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧操作区 -->
|
||||
<view class="right-actions">
|
||||
<!-- 文本模式:表情按钮 -->
|
||||
<view
|
||||
v-if="inputMode === 'text'"
|
||||
class="emoji-btn"
|
||||
@click="toggleEmojiPicker"
|
||||
:class="{ active: showEmojiPicker }"
|
||||
>
|
||||
<i class="fas fa-smile"></i>
|
||||
</view>
|
||||
|
||||
<!-- 文本模式:发送按钮(有内容时显示) -->
|
||||
<view
|
||||
v-if="inputMode === 'text' && inputMessage.trim()"
|
||||
class="send-btn"
|
||||
@click="sendMessage"
|
||||
>
|
||||
发送
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmojiPicker from "../../src/components/EmojiPicker.vue";
|
||||
import { parseEmoji } from "../../src/utils/emojiParser.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EmojiPicker,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
messages: [
|
||||
{
|
||||
type: "received",
|
||||
content: "欢迎使用聊天功能!有什么可以帮您?",
|
||||
time: new Date(Date.now() - 3000 * 60 * 1000), // 30分钟前
|
||||
},
|
||||
{
|
||||
type: "received",
|
||||
content: "您可以咨询产品信息、下单流程等相关问题。",
|
||||
time: new Date(Date.now() - 29 * 60 * 1000), // 29分钟前
|
||||
},
|
||||
{
|
||||
type: "sent",
|
||||
content: "请问你们有哪些热门产品?",
|
||||
time: new Date(Date.now() - 28 * 60 * 1000), // 28分钟前
|
||||
},
|
||||
{
|
||||
type: "received",
|
||||
content: "我们的热门产品有A、B、C三款,您想了解哪一款?",
|
||||
time: new Date(Date.now() - 27 * 60 * 1000), // 27分钟前
|
||||
},
|
||||
{
|
||||
type: "received",
|
||||
content: "点击发送按钮或按回车键发送消息哦~",
|
||||
time: new Date(Date.now() - 26 * 60 * 1000), // 26分钟前
|
||||
},
|
||||
],
|
||||
inputMessage: "",
|
||||
showEmojiPicker: false,
|
||||
inputMode: "text", // 'text' 或 'voice'
|
||||
isRecording: false,
|
||||
recordingTimer: null,
|
||||
inputHeight: 80, // 输入框高度,默认1行
|
||||
scrollTop: 0, // 消息列表滚动位置
|
||||
scrollIntoView: "", // 滚动到指定元素
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// 确保输入框初始高度为1行
|
||||
this.inputHeight = 80;
|
||||
},
|
||||
computed: {
|
||||
// 从全局数据获取设备信息
|
||||
isMobile() {
|
||||
return getApp().globalData.isMobile;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听消息数组变化,自动滚动到最新消息
|
||||
messages: {
|
||||
handler(newMessages, oldMessages) {
|
||||
if (newMessages.length > (oldMessages ? oldMessages.length : 0)) {
|
||||
// 有新消息时,延迟滚动确保DOM更新完成
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.scrollToBottom();
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
immediate: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
parseEmoji(text) {
|
||||
// 使用工具函数解析emoji
|
||||
return parseEmoji(text);
|
||||
},
|
||||
sendMessage() {
|
||||
if (this.inputMessage.trim() !== "") {
|
||||
// 添加用户发送的消息
|
||||
const newMessage = {
|
||||
type: "sent",
|
||||
content: this.inputMessage,
|
||||
time: new Date(),
|
||||
};
|
||||
this.messages.push(newMessage);
|
||||
|
||||
// 强制更新视图
|
||||
this.$forceUpdate();
|
||||
|
||||
// 清空输入框并重置高度
|
||||
const message = this.inputMessage;
|
||||
this.inputMessage = "";
|
||||
this.inputHeight = 80; // 重置为1行高度
|
||||
this.showEmojiPicker = false;
|
||||
|
||||
// 立即滚动到最新消息
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
|
||||
// 模拟回复
|
||||
setTimeout(() => {
|
||||
this.simulateReply(message);
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
simulateReply(message) {
|
||||
// 简单的回复逻辑
|
||||
let reply = "感谢你的消息!";
|
||||
if (message.includes("你好")) {
|
||||
reply = "你好!很高兴见到你!";
|
||||
} else if (message.includes("产品")) {
|
||||
reply = "我们的产品非常棒,你可以查看我们的官网了解更多信息。";
|
||||
}
|
||||
|
||||
const replyMessage = {
|
||||
type: "received",
|
||||
content: reply,
|
||||
time: new Date(),
|
||||
};
|
||||
|
||||
this.messages.push(replyMessage);
|
||||
|
||||
// 强制更新视图
|
||||
this.$forceUpdate();
|
||||
|
||||
// 立即滚动到最新消息
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
},
|
||||
toggleEmojiPicker() {
|
||||
this.showEmojiPicker = !this.showEmojiPicker;
|
||||
},
|
||||
onEmojiSelect(emoji) {
|
||||
this.inputMessage += emoji.unicode;
|
||||
},
|
||||
onInputFocus() {
|
||||
this.showEmojiPicker = false;
|
||||
},
|
||||
onInputBlur() {
|
||||
// 输入框失焦时的处理
|
||||
},
|
||||
onInputChange(e) {
|
||||
// 输入内容变化时的处理
|
||||
this.inputMessage = e.detail.value;
|
||||
|
||||
// 根据内容长度估算行数并调整高度
|
||||
this.adjustInputHeight();
|
||||
},
|
||||
onLineChange(e) {
|
||||
// 行数变化时调整高度,限制最大10行
|
||||
const lineCount = e.detail.lineCount;
|
||||
const minHeight = 80; // 1行高度
|
||||
const lineHeight = 40; // 每行高度
|
||||
const maxLines = 10; // 最大10行
|
||||
|
||||
let newHeight = minHeight + (lineCount - 1) * lineHeight;
|
||||
newHeight = Math.min(
|
||||
Math.max(newHeight, minHeight),
|
||||
minHeight + (maxLines - 1) * lineHeight
|
||||
);
|
||||
|
||||
this.inputHeight = newHeight;
|
||||
},
|
||||
adjustInputHeight() {
|
||||
// 根据输入内容调整高度
|
||||
const content = this.inputMessage;
|
||||
if (!content.trim()) {
|
||||
// 如果内容为空,设置为1行高度
|
||||
this.inputHeight = 80;
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = content.split("\n").length;
|
||||
const minHeight = 80;
|
||||
const lineHeight = 40;
|
||||
const maxLines = 10;
|
||||
|
||||
let newHeight = minHeight + (lines - 1) * lineHeight;
|
||||
newHeight = Math.min(
|
||||
Math.max(newHeight, minHeight),
|
||||
minHeight + (maxLines - 1) * lineHeight
|
||||
);
|
||||
|
||||
this.inputHeight = newHeight;
|
||||
},
|
||||
// 切换输入模式
|
||||
switchInputMode() {
|
||||
if (this.inputMode === "text") {
|
||||
this.inputMode = "voice";
|
||||
this.showEmojiPicker = false;
|
||||
} else {
|
||||
this.inputMode = "text";
|
||||
}
|
||||
},
|
||||
// 切换更多选项
|
||||
toggleMoreOptions() {
|
||||
// 这里可以添加更多选项的弹窗
|
||||
},
|
||||
// 语音录制相关
|
||||
startVoiceRecord() {
|
||||
this.isRecording = true;
|
||||
// 这里可以添加实际的录音逻辑
|
||||
// 例如调用 uni.getRecorderManager()
|
||||
},
|
||||
endVoiceRecord() {
|
||||
this.isRecording = false;
|
||||
// 这里可以添加录音结束的处理逻辑
|
||||
// 例如发送语音消息
|
||||
},
|
||||
cancelVoiceRecord() {
|
||||
this.isRecording = false;
|
||||
// 这里可以添加取消录音的处理逻辑
|
||||
},
|
||||
scrollToBottom() {
|
||||
// 滚动到消息列表底部
|
||||
// 方法1:使用scroll-top
|
||||
this.scrollTop = 99999;
|
||||
|
||||
// 方法2:使用scroll-into-view滚动到最后一个消息
|
||||
if (this.messages.length > 0) {
|
||||
const lastIndex = this.messages.length - 1;
|
||||
this.scrollIntoView = "message-" + lastIndex;
|
||||
|
||||
// 重置scrollIntoView以允许重复滚动
|
||||
setTimeout(() => {
|
||||
this.scrollIntoView = "";
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 返回
|
||||
*/
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
/**
|
||||
* 跳转到聊天详情
|
||||
*/
|
||||
goToChatDetail() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/message/chatdetail",
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 跳转到用户详情
|
||||
*/
|
||||
goToUserDetail() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/message/userdetail",
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 格式化时间
|
||||
*/
|
||||
formatTime(time) {
|
||||
const date = new Date(time);
|
||||
const now = new Date();
|
||||
|
||||
// 如果是今天,只显示时间
|
||||
if (date.toDateString() === now.toDateString()) {
|
||||
return date.toLocaleTimeString("zh-CN", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
// 如果是昨天,显示"昨天"
|
||||
const yesterday = new Date(now);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
if (date.toDateString() === yesterday.toDateString()) {
|
||||
return (
|
||||
"昨天 " +
|
||||
date.toLocaleTimeString("zh-CN", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// 其他情况显示完整日期
|
||||
return (
|
||||
date.toLocaleDateString("zh-CN") +
|
||||
" " +
|
||||
date.toLocaleTimeString("zh-CN", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
/* 移动设备顶部状态栏 */
|
||||
.top_bar {
|
||||
background: var(--gradient-primary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: calc(var(--status-bar-height) + 88rpx);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding-top: var(--status-bar-height);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 支持安全区域的设备 */
|
||||
@supports (padding: max(0px)) {
|
||||
.top_bar {
|
||||
height: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
|
||||
}
|
||||
|
||||
.top_bar + .message-list {
|
||||
margin-top: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
}
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.chat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 88rpx;
|
||||
background-color: var(--surface);
|
||||
border-bottom: 1rpx solid var(--border);
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* 移动设备下的导航栏样式 */
|
||||
.top_bar .chat-header {
|
||||
background: transparent;
|
||||
border-bottom: none;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.top_bar .header-title {
|
||||
color: var(--white);
|
||||
font-weight: 600;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.top_bar .header-left i,
|
||||
.top_bar .header-right i {
|
||||
color: var(--white);
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.header-left,
|
||||
.header-right {
|
||||
width: 80rpx;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.header-right:active {
|
||||
background-color: var(--surface-hover);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
/* 移动设备下的按钮悬停效果 */
|
||||
.top_bar .header-left:active,
|
||||
.top_bar .header-right:active {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: var(--title-color);
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 36rpx;
|
||||
font-style: normal;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 消息列表 */
|
||||
.message-list {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
overflow-y: auto;
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
/* 移动设备下为消息列表添加顶部间距 */
|
||||
.top_bar + .message-list {
|
||||
margin-top: calc(var(--status-bar-height) + 88rpx);
|
||||
}
|
||||
|
||||
.message-item {
|
||||
display: flex;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.message-item.sent {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
/* 头像 */
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
|
||||
.avatar image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 消息内容 */
|
||||
.message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.message-item.sent .message-content {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-item.received .message-content {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* 消息气泡 */
|
||||
.message-bubble {
|
||||
position: relative;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.4;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.message-item.sent .message-bubble {
|
||||
background: var(--gradient-primary);
|
||||
color: var(--white);
|
||||
border-radius: 12rpx 2rpx 12rpx 12rpx;
|
||||
}
|
||||
|
||||
.message-item.received .message-bubble {
|
||||
background-color: var(--surface);
|
||||
color: var(--text-color);
|
||||
border-radius: 2rpx 12rpx 12rpx 12rpx;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* 消息时间 */
|
||||
.message-time {
|
||||
font-size: 20rpx;
|
||||
color: var(--text-muted);
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
/* 输入区域 */
|
||||
.input-area {
|
||||
background-color: var(--surface);
|
||||
border-top: 1rpx solid var(--border);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
/* 表情选择器容器 */
|
||||
.emoji-picker-container {
|
||||
border-bottom: 1rpx solid var(--border-light);
|
||||
}
|
||||
|
||||
/* 输入工具栏 */
|
||||
.input-toolbar {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 20rpx 16rpx;
|
||||
gap: 16rpx;
|
||||
min-height: 100rpx;
|
||||
}
|
||||
|
||||
/* 左侧切换按钮 */
|
||||
.left-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.switch-btn {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.switch-btn.active {
|
||||
background: var(--gradient-primary);
|
||||
}
|
||||
|
||||
.switch-btn i {
|
||||
font-size: 40rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.switch-btn.active i {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
/* 中间输入区域 */
|
||||
.input-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 文本输入容器 */
|
||||
.text-input-container {
|
||||
background-color: var(--surface);
|
||||
border-radius: 8rpx;
|
||||
border: 1rpx solid var(--border);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.message-input {
|
||||
width: 100%;
|
||||
height: 80rpx; /* 固定默认高度为1行 */
|
||||
min-height: 80rpx;
|
||||
max-height: 440rpx; /* 10行高度:80rpx + 9 * 40rpx = 440rpx */
|
||||
padding: 20rpx 24rpx;
|
||||
border: none;
|
||||
font-size: 32rpx;
|
||||
line-height: 1.4;
|
||||
background-color: transparent;
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
overflow-y: auto; /* 超出10行时显示滚动条 */
|
||||
}
|
||||
|
||||
.message-input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 语音输入 */
|
||||
.voice-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--gray-lighter);
|
||||
border-radius: 8rpx;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
border: 1rpx solid var(--border);
|
||||
}
|
||||
|
||||
.voice-input:active,
|
||||
.voice-input.recording {
|
||||
background-color: var(--gray);
|
||||
}
|
||||
|
||||
.voice-text {
|
||||
font-size: 32rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.recording-indicator {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.recording-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
background-color: #ff4444;
|
||||
border-radius: 50%;
|
||||
animation: recordingPulse 1s infinite;
|
||||
}
|
||||
|
||||
.recording-dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.recording-dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes recordingPulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* 右侧操作区 */
|
||||
.right-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.emoji-btn {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.emoji-btn.active {
|
||||
background: var(--gradient-primary);
|
||||
}
|
||||
|
||||
.emoji-btn:active {
|
||||
background-color: var(--surface-hover);
|
||||
}
|
||||
|
||||
.emoji-btn i {
|
||||
font-size: 40rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.emoji-btn.active i {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
padding: 16rpx 32rpx;
|
||||
background: var(--gradient-primary);
|
||||
color: var(--white);
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 120rpx;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.send-btn:active {
|
||||
background: var(--primary-dark);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,475 @@
|
||||
<template>
|
||||
<view class="chat-detail-container">
|
||||
<!-- 移动设备顶部状态栏 -->
|
||||
<view class="top_bar flex w-full" v-if="isMobile">
|
||||
<view class="chat-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</view>
|
||||
<view class="header-title">聊天详情</view>
|
||||
<view class="header-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 浏览器环境顶部导航栏 -->
|
||||
<view class="chat-header" v-else>
|
||||
<view class="header-left" @click="goBack">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</view>
|
||||
<view class="header-title">聊天详情</view>
|
||||
<view class="header-right"></view>
|
||||
</view>
|
||||
<!-- 顶部用户信息卡片 -->
|
||||
<view class="user-card">
|
||||
<view class="user-info">
|
||||
<view class="avatar-container">
|
||||
<image
|
||||
class="avatar"
|
||||
src="/static/imgs/default_avatar.png"
|
||||
mode="aspectFill"
|
||||
@error="onAvatarError"
|
||||
/>
|
||||
</view>
|
||||
<view class="user-base-info">
|
||||
<view class="nickname">{{ user.nickname }}</view>
|
||||
<view class="meteid">账号:{{ user.meteid }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能操作卡片 -->
|
||||
<view class="action-card">
|
||||
<view class="card-header">
|
||||
<i class="fas fa-search"></i>
|
||||
<text>聊天功能</text>
|
||||
</view>
|
||||
<view class="action-list">
|
||||
<view class="action-item" @click="goToSearchRecord">
|
||||
<view class="action-icon">
|
||||
<i class="fas fa-search"></i>
|
||||
</view>
|
||||
<text>查找聊天记录</text>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 聊天设置卡片 -->
|
||||
<view class="setting-card">
|
||||
<view class="card-header">
|
||||
<i class="fas fa-cog"></i>
|
||||
<text>聊天设置</text>
|
||||
</view>
|
||||
<view class="setting-list">
|
||||
<view class="setting-item">
|
||||
<view class="setting-icon">
|
||||
<i class="fas fa-bell-slash"></i>
|
||||
</view>
|
||||
<text>消息免打扰</text>
|
||||
<switch :checked="disturb" @change="switchDisturb" />
|
||||
</view>
|
||||
<view class="setting-item">
|
||||
<view class="setting-icon">
|
||||
<i class="fas fa-thumbtack"></i>
|
||||
</view>
|
||||
<text>置顶聊天</text>
|
||||
<switch :checked="pinned" @change="switchPinned" />
|
||||
</view>
|
||||
<view class="setting-item" @click="chooseChatBg">
|
||||
<view class="setting-icon">
|
||||
<i class="fas fa-image"></i>
|
||||
</view>
|
||||
<text>设置当前聊天背景</text>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 危险操作卡片 -->
|
||||
<view class="danger-card">
|
||||
<view class="card-header">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<text>危险操作</text>
|
||||
</view>
|
||||
<view class="danger-list">
|
||||
<view class="danger-item" @click="clearRecord">
|
||||
<view class="danger-icon">
|
||||
<i class="fas fa-trash"></i>
|
||||
</view>
|
||||
<text>清空聊天记录</text>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</view>
|
||||
<view class="danger-item" @click="complain">
|
||||
<view class="danger-icon">
|
||||
<i class="fas fa-flag"></i>
|
||||
</view>
|
||||
<text>投诉</text>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
avatar: "",
|
||||
nickname: "美天科技用户",
|
||||
meteid: "mete_123456",
|
||||
},
|
||||
disturb: false,
|
||||
pinned: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 从全局数据获取设备信息
|
||||
isMobile() {
|
||||
return getApp().globalData.isMobile;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
onAvatarError() {
|
||||
// 头像加载失败时的处理
|
||||
console.log('头像加载失败');
|
||||
},
|
||||
goToSearchRecord() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/message/chatsearch?userid=" + this.user.meteid,
|
||||
});
|
||||
},
|
||||
switchDisturb(e) {
|
||||
this.disturb = e.detail.value;
|
||||
uni.showToast({
|
||||
title: this.disturb ? "已开启免打扰" : "已关闭免打扰",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
switchPinned(e) {
|
||||
this.pinned = e.detail.value;
|
||||
uni.showToast({
|
||||
title: this.pinned ? "已置顶聊天" : "已取消置顶",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
remindOnce() {
|
||||
uni.showToast({
|
||||
title: "已为你开启消息提醒一次",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
chooseChatBg() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
const bgPath = res.tempFilePaths[0];
|
||||
uni.setStorageSync("chat_bg_" + this.user.meteid, bgPath);
|
||||
uni.showToast({ title: "聊天背景已设置", icon: "none" });
|
||||
},
|
||||
});
|
||||
},
|
||||
clearRecord() {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "确定要清空与该用户的聊天记录吗?",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 这里可以调用API或清空本地数据
|
||||
uni.showToast({ title: "聊天记录已清空", icon: "none" });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
complain() {
|
||||
uni.showModal({
|
||||
title: "投诉",
|
||||
content: "如发现对方有违规行为,请进行投诉,我们将协助核实处理。",
|
||||
confirmText: "投诉",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({
|
||||
title: "已投诉,平台将进行核查",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-detail-container {
|
||||
background: var(--background);
|
||||
min-height: 100vh;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
/* 移动设备顶部状态栏 */
|
||||
.top_bar {
|
||||
background: var(--gradient-primary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: calc(var(--status-bar-height) + 88rpx);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding-top: var(--status-bar-height);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 支持安全区域的设备 */
|
||||
@supports (padding: max(0px)) {
|
||||
.top_bar {
|
||||
height: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
|
||||
}
|
||||
|
||||
.top_bar + .user-card {
|
||||
margin-top: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
}
|
||||
|
||||
.chat-detail-container .chat-header:not(.top_bar .chat-header) {
|
||||
height: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
|
||||
}
|
||||
|
||||
.chat-detail-container .chat-header:not(.top_bar .chat-header) + .user-card {
|
||||
margin-top: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
}
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.chat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 88rpx;
|
||||
background-color: var(--surface);
|
||||
border-bottom: 1rpx solid var(--border);
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* 浏览器环境下的固定定位 */
|
||||
.chat-detail-container .chat-header:not(.top_bar .chat-header) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
height: calc(var(--status-bar-height) + 88rpx);
|
||||
padding-top: var(--status-bar-height);
|
||||
}
|
||||
|
||||
/* 移动设备下的导航栏样式 */
|
||||
.top_bar .chat-header {
|
||||
background: transparent;
|
||||
border-bottom: none;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.top_bar .header-title {
|
||||
color: var(--white);
|
||||
font-weight: 600;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.top_bar .header-left i,
|
||||
.top_bar .header-right i {
|
||||
color: var(--white);
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.header-left,
|
||||
.header-right {
|
||||
width: 80rpx;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.header-left:active,
|
||||
.header-right:active {
|
||||
background-color: var(--gray-lighter);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.top_bar .header-left:active,
|
||||
.top_bar .header-right:active {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: var(--title-color);
|
||||
}
|
||||
|
||||
/* 移动设备下为内容添加顶部间距 */
|
||||
.top_bar + .user-card {
|
||||
margin-top: calc(var(--status-bar-height) + 88rpx);
|
||||
}
|
||||
|
||||
/* 浏览器环境下为内容添加顶部间距 */
|
||||
.chat-detail-container .chat-header:not(.top_bar .chat-header) + .user-card {
|
||||
margin-top: calc(var(--status-bar-height) + 88rpx);
|
||||
}
|
||||
|
||||
/* 用户信息卡片 */
|
||||
.user-card {
|
||||
background: var(--surface);
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: var(--shadow-md);
|
||||
border: 1rpx solid var(--border-light);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 40rpx 30rpx;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 16rpx;
|
||||
background: var(--gray);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.user-base-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: var(--title-color);
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.meteid {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 卡片通用样式 */
|
||||
.action-card, .setting-card, .danger-card {
|
||||
background: var(--surface);
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1rpx solid var(--border-light);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx 30rpx 20rpx 30rpx;
|
||||
border-bottom: 1rpx solid var(--border-light);
|
||||
background: var(--gray-lighter);
|
||||
}
|
||||
|
||||
.card-header i {
|
||||
font-size: 28rpx;
|
||||
color: var(--icon-color);
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.card-header text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: var(--title-color);
|
||||
}
|
||||
|
||||
/* 操作列表 */
|
||||
.action-list, .setting-list, .danger-list {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.action-item, .setting-item, .danger-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid var(--border-light);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.action-item:last-child, .setting-item:last-child, .danger-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.action-item:active, .setting-item:active, .danger-item:active {
|
||||
background-color: var(--surface-hover);
|
||||
}
|
||||
|
||||
/* 图标样式 */
|
||||
.action-icon, .setting-icon, .danger-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
background: var(--gradient-primary);
|
||||
}
|
||||
|
||||
.setting-icon {
|
||||
background: var(--gradient-primary);
|
||||
}
|
||||
|
||||
.danger-icon {
|
||||
background: var(--gradient-error);
|
||||
}
|
||||
|
||||
.action-icon i, .setting-icon i, .danger-icon i {
|
||||
color: var(--white);
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 文字样式 */
|
||||
.action-item text, .setting-item text, .danger-item text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.danger-item text {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.action-item i.fas.fa-chevron-right, .setting-item i.fas.fa-chevron-right, .danger-item i.fas.fa-chevron-right {
|
||||
color: var(--text-muted);
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,683 @@
|
||||
<template>
|
||||
<view class="message-page">
|
||||
<!-- 统一顶部导航 -->
|
||||
<view class="unified-header">
|
||||
<view class="header-content">
|
||||
<view class="header-left">
|
||||
<!-- <i class="fas fa-search header-icon" @click="handleSearch"></i> -->
|
||||
</view>
|
||||
<view class="header-title">消息</view>
|
||||
<view class="header-right">
|
||||
<!-- <i class="fas fa-bell header-icon" @click="handleNotification">
|
||||
<view class="badge" v-if="unreadCount > 0">{{ unreadCount }}</view>
|
||||
</i> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分类标签 -->
|
||||
<view class="tabs-container">
|
||||
<view class="tabs">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'chat' }"
|
||||
@click="switchTab('chat')"
|
||||
>
|
||||
<text>会话</text>
|
||||
<view class="tab-badge" v-if="chatUnreadCount > 0">{{ chatUnreadCount }}</view>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'notification' }"
|
||||
@click="switchTab('notification')"
|
||||
>
|
||||
<text>通知</text>
|
||||
<view class="tab-badge" v-if="notificationUnreadCount > 0">{{ notificationUnreadCount }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<scroll-view scroll-y class="unified-content" @click="closeAllSwipes">
|
||||
<!-- 会话列表 -->
|
||||
<view v-if="activeTab === 'chat'" class="chat-list">
|
||||
<view
|
||||
class="chat-item"
|
||||
v-for="(chat, index) in chatList"
|
||||
:key="index"
|
||||
>
|
||||
<!-- 左侧点击区域 -->
|
||||
<view class="chat-left" @click="openChat(chat)">
|
||||
<view class="chat-avatar">
|
||||
<view class="avatar">
|
||||
<i class="fas fa-user avatar-icon"></i>
|
||||
</view>
|
||||
<view class="chat-badge" v-if="chat.unread > 0">{{ chat.unread }}</view>
|
||||
</view>
|
||||
<view class="chat-content">
|
||||
<view class="chat-header">
|
||||
<text class="chat-name">{{chat.name}}</text>
|
||||
<text class="chat-time">{{chat.time}}</text>
|
||||
</view>
|
||||
<view class="chat-preview">
|
||||
<text class="chat-message">{{chat.lastMessage}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧操作区域 -->
|
||||
<view class="chat-right">
|
||||
<view class="more-btn" @click="showActionMenu(chat, index)">
|
||||
<i class="fas fa-ellipsis-v more-icon"></i>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 通知列表 -->
|
||||
<view v-if="activeTab === 'notification'" class="notification-list">
|
||||
<view
|
||||
class="notification-item"
|
||||
v-for="(notification, index) in notificationList"
|
||||
:key="index"
|
||||
@click="openNotification(notification)"
|
||||
>
|
||||
<view class="notification-icon" :style="{ backgroundColor: notification.color + '20' }">
|
||||
<i :class="notification.iconClass" class="notification-icon-fa" :style="{ color: notification.color }"></i>
|
||||
</view>
|
||||
<view class="notification-content">
|
||||
<view class="notification-header">
|
||||
<text class="notification-title">{{notification.title}}</text>
|
||||
<text class="notification-time">{{notification.time}}</text>
|
||||
</view>
|
||||
<view class="notification-preview">
|
||||
<text class="notification-message">{{notification.content}}</text>
|
||||
</view>
|
||||
<view class="notification-source">
|
||||
<text>{{notification.source}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="notification-status" v-if="!notification.read">
|
||||
<view class="unread-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 操作菜单弹窗 -->
|
||||
<view class="action-menu" v-if="showMenu" @click="hideActionMenu">
|
||||
<view class="menu-mask"></view>
|
||||
<view class="menu-content" @click.stop>
|
||||
<view class="menu-item" @click="pinChat(currentChat, currentIndex)">
|
||||
<i class="fas fa-thumbtack menu-icon"></i>
|
||||
<text>{{ currentChat && currentChat.pinned ? '取消置顶' : '置顶' }}</text>
|
||||
</view>
|
||||
<view class="menu-item delete-item" @click="deleteChat(currentChat, currentIndex)">
|
||||
<i class="fas fa-trash menu-icon"></i>
|
||||
<text>删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
// 响应式数据
|
||||
const activeTab = ref('chat')
|
||||
const unreadCount = ref(5)
|
||||
const chatUnreadCount = ref(3)
|
||||
const notificationUnreadCount = ref(2)
|
||||
|
||||
// 会话列表数据
|
||||
const chatList = reactive([
|
||||
{
|
||||
id: 1,
|
||||
name: '技术部群聊',
|
||||
avatar: '/static/avatar/group1.png',
|
||||
lastMessage: '张工:项目进度如何?',
|
||||
time: '10:30',
|
||||
unread: 2,
|
||||
pinned: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李经理',
|
||||
avatar: '/static/avatar/manager.png',
|
||||
lastMessage: '好的,我马上处理',
|
||||
time: '09:45',
|
||||
unread: 1,
|
||||
pinned: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '财务部群聊',
|
||||
avatar: '/static/avatar/group2.png',
|
||||
lastMessage: '报销单据已审核',
|
||||
time: '昨天',
|
||||
unread: 0,
|
||||
pinned: false
|
||||
}
|
||||
])
|
||||
|
||||
// 菜单相关数据
|
||||
const showMenu = ref(false)
|
||||
const currentChat = ref(null)
|
||||
const currentIndex = ref(-1)
|
||||
|
||||
// 通知列表数据
|
||||
const notificationList = reactive([
|
||||
{
|
||||
id: 1,
|
||||
title: '审批通知',
|
||||
content: '您的请假申请已通过部门经理审批',
|
||||
source: '人事部',
|
||||
time: '2小时前',
|
||||
iconClass: 'fas fa-file-alt',
|
||||
color: '#10b981',
|
||||
read: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '考勤通知',
|
||||
content: '今日考勤打卡成功,上班时间:09:00',
|
||||
source: '考勤系统',
|
||||
time: '3小时前',
|
||||
iconClass: 'fas fa-clock',
|
||||
color: '#3b82f6',
|
||||
read: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '系统公告',
|
||||
content: '系统将于今晚22:00-24:00进行维护升级',
|
||||
source: 'IT部门',
|
||||
time: '1天前',
|
||||
iconClass: 'fas fa-info-circle',
|
||||
color: '#8b5cf6',
|
||||
read: true
|
||||
}
|
||||
])
|
||||
|
||||
// 方法
|
||||
const handleSearch = () => {
|
||||
uni.showToast({
|
||||
title: '搜索功能',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
const handleNotification = () => {
|
||||
uni.showToast({
|
||||
title: '通知设置',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
const switchTab = (tab) => {
|
||||
activeTab.value = tab
|
||||
}
|
||||
|
||||
const openChat = (chat) => {
|
||||
// 跳转到 chat.vue 页面,并传递会话 ID
|
||||
uni.navigateTo({
|
||||
// url: `/pages/message/chat/chat?id=${chat.id}`
|
||||
url: `/pages/message/chat`
|
||||
})
|
||||
}
|
||||
|
||||
const openNotification = (notification) => {
|
||||
uni.showToast({
|
||||
title: `查看通知:${notification.title}`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 菜单相关方法
|
||||
const showActionMenu = (chat, index) => {
|
||||
currentChat.value = chat
|
||||
currentIndex.value = index
|
||||
showMenu.value = true
|
||||
}
|
||||
|
||||
const hideActionMenu = () => {
|
||||
showMenu.value = false
|
||||
currentChat.value = null
|
||||
currentIndex.value = -1
|
||||
}
|
||||
|
||||
// 操作按钮方法
|
||||
const pinChat = (chat, index) => {
|
||||
chat.pinned = !chat.pinned
|
||||
hideActionMenu()
|
||||
|
||||
// 重新排序:置顶的放在前面
|
||||
const pinnedChats = chatList.filter(item => item.pinned)
|
||||
const unpinnedChats = chatList.filter(item => !item.pinned)
|
||||
|
||||
// 清空原数组并重新添加
|
||||
chatList.splice(0, chatList.length, ...pinnedChats, ...unpinnedChats)
|
||||
|
||||
uni.showToast({
|
||||
title: chat.pinned ? '已置顶' : '已取消置顶',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
const deleteChat = (chat, index) => {
|
||||
hideActionMenu()
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除与"${chat.name}"的会话吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
chatList.splice(index, 1)
|
||||
uni.showToast({
|
||||
title: '已删除',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
activeTab,
|
||||
unreadCount,
|
||||
chatUnreadCount,
|
||||
notificationUnreadCount,
|
||||
chatList,
|
||||
notificationList,
|
||||
showMenu,
|
||||
currentChat,
|
||||
currentIndex,
|
||||
handleSearch,
|
||||
handleNotification,
|
||||
switchTab,
|
||||
openChat,
|
||||
openNotification,
|
||||
showActionMenu,
|
||||
hideActionMenu,
|
||||
pinChat,
|
||||
deleteChat
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.message-page {
|
||||
height: 100vh;
|
||||
background-color: var(--background);
|
||||
position: relative;
|
||||
padding-top: calc(var(--status-bar-height) + 88rpx);
|
||||
}
|
||||
|
||||
/* 支持安全区域的设备 */
|
||||
@supports (padding: max(0px)) {
|
||||
.message-page {
|
||||
padding-top: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 25rpx;
|
||||
padding: 15rpx 20rpx;
|
||||
margin-right: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 10rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.notification {
|
||||
position: relative;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
font-size: 40rpx;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 0rpx;
|
||||
right: 15rpx;
|
||||
background-color: var(--error);
|
||||
color: var(--white);
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 50%;
|
||||
// min-width: 30rpx;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
background: var(--white);
|
||||
padding: 0 30rpx;
|
||||
border-bottom: 1rpx solid var(--border-light);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30rpx 0;
|
||||
position: relative;
|
||||
font-size: 28rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: var(--primary-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tab-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60rpx;
|
||||
height: 4rpx;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
.tab-badge {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 80rpx;
|
||||
background-color: var(--error);
|
||||
color: var(--white);
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 20rpx;
|
||||
min-width: 30rpx;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
height: calc(100vh - 200rpx);
|
||||
}
|
||||
|
||||
.chat-list, .notification-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.chat-item, .notification-item {
|
||||
background: var(--white);
|
||||
border-radius: 16rpx;
|
||||
// padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.notification-item{
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.chat-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-right: 1rpx solid var(--border-light);
|
||||
}
|
||||
|
||||
.chat-right {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.more-btn:active {
|
||||
background-color: var(--gray-lighter);
|
||||
}
|
||||
|
||||
.chat-avatar {
|
||||
position: relative;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar-icon {
|
||||
color: var(--white);
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.chat-badge {
|
||||
position: absolute;
|
||||
top: -5rpx;
|
||||
right: -5rpx;
|
||||
background-color: var(--error);
|
||||
color: var(--white);
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 20rpx;
|
||||
min-width: 30rpx;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.more-icon {
|
||||
font-size: 32rpx;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.chat-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.chat-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.chat-time {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.chat-preview {
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.chat-actions {
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.notification-icon-fa {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.unread-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
background-color: var(--error);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.notification-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.notification-time {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.notification-preview {
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.notification-source {
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.notification-source text {
|
||||
font-size: 22rpx;
|
||||
color: var(--text-muted);
|
||||
background: var(--gray-lighter);
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.notification-status {
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
/* 操作菜单弹窗 */
|
||||
.action-menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.menu-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
position: relative;
|
||||
background: var(--white);
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
padding: 40rpx 0 20rpx;
|
||||
width: 100%;
|
||||
max-width: 750rpx;
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx 40rpx;
|
||||
font-size: 32rpx;
|
||||
color: var(--text-color);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
background-color: var(--gray-lighter);
|
||||
}
|
||||
|
||||
.menu-item.delete-item {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 36rpx;
|
||||
margin-right: 20rpx;
|
||||
width: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,589 @@
|
||||
<template>
|
||||
<view class="user-detail-container">
|
||||
<!-- 移动设备顶部状态栏 -->
|
||||
<view class="top_bar flex w-full" v-if="isMobile">
|
||||
<view class="chat-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</view>
|
||||
<view class="header-title">用户详情</view>
|
||||
<view class="header-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 浏览器环境顶部导航栏 -->
|
||||
<view class="chat-header" v-else>
|
||||
<view class="header-left" @click="goBack">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</view>
|
||||
<view class="header-title">用户详情</view>
|
||||
<view class="header-right"></view>
|
||||
</view>
|
||||
<!-- 顶部用户信息卡片 -->
|
||||
<view class="user-card">
|
||||
<view class="user-info">
|
||||
<view class="avatar-container">
|
||||
<image
|
||||
class="avatar"
|
||||
:src="user.avatar || '/static/imgs/default_avatar.png'"
|
||||
mode="aspectFill"
|
||||
@error="onAvatarError"
|
||||
/>
|
||||
<view class="online-status" v-if="user.isOnline"></view>
|
||||
</view>
|
||||
<view class="user-base-info">
|
||||
<view class="nickname">{{ user.nickname }}</view>
|
||||
<view class="meteid">账号:{{ user.meteid }}</view>
|
||||
<view class="meta-info">
|
||||
<view class="meta-item" v-if="user.department">
|
||||
部门:<text>{{ user.department }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="meta-info">
|
||||
<view class="meta-item" v-if="user.position">
|
||||
职位:<text>{{ user.position }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能按钮区域 -->
|
||||
<view class="action-section">
|
||||
<view class="action-btn primary" @click="sendMessage">
|
||||
<i class="fas fa-comment-dots"></i>
|
||||
<text>发消息</text>
|
||||
</view>
|
||||
<view class="action-btn" @click="startAudioCall">
|
||||
<i class="fas fa-phone-alt"></i>
|
||||
<text>语音通话</text>
|
||||
</view>
|
||||
<view class="action-btn" @click="startVideoCall">
|
||||
<i class="fas fa-video"></i>
|
||||
<text>视频通话</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资料信息卡片 -->
|
||||
<view class="info-card">
|
||||
<view class="card-header">
|
||||
<i class="fas fa-user-circle"></i>
|
||||
<text>个人资料</text>
|
||||
</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item" v-if="user.phone" @click="copyToClipboard(user.phone, '手机号')">
|
||||
<view class="info-icon phone">
|
||||
<i class="fas fa-phone"></i>
|
||||
</view>
|
||||
<view class="info-content">
|
||||
<text class="info-label">手机号码</text>
|
||||
<text class="info-value">{{ user.phone }}</text>
|
||||
</view>
|
||||
<i class="fas fa-copy"></i>
|
||||
</view>
|
||||
<view class="info-item" v-if="user.wechat" @click="copyToClipboard(user.wechat, '微信号')">
|
||||
<view class="info-icon wechat">
|
||||
<i class="fab fa-weixin"></i>
|
||||
</view>
|
||||
<view class="info-content">
|
||||
<text class="info-label">微信</text>
|
||||
<text class="info-value">{{ user.wechat }}</text>
|
||||
</view>
|
||||
<i class="fas fa-copy"></i>
|
||||
</view>
|
||||
<view class="info-item" v-if="user.email" @click="copyToClipboard(user.email, '邮箱')">
|
||||
<view class="info-icon email">
|
||||
<i class="fas fa-envelope"></i>
|
||||
</view>
|
||||
<view class="info-content">
|
||||
<text class="info-label">邮箱</text>
|
||||
<text class="info-value">{{ user.email }}</text>
|
||||
</view>
|
||||
<i class="fas fa-copy"></i>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 更多功能卡片 -->
|
||||
<view class="more-card">
|
||||
<view class="card-header">
|
||||
<i class="fas fa-cog"></i>
|
||||
<text>更多功能</text>
|
||||
</view>
|
||||
<view class="more-list">
|
||||
<view class="more-item" @click="addToContacts">
|
||||
<view class="more-icon">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
</view>
|
||||
<text>添加到通讯录</text>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</view>
|
||||
<view class="more-item" @click="shareContact">
|
||||
<view class="more-icon">
|
||||
<i class="fas fa-share-alt"></i>
|
||||
</view>
|
||||
<text>发送名片</text>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</view>
|
||||
<view class="more-item" @click="viewMoments">
|
||||
<view class="more-icon">
|
||||
<i class="fas fa-images"></i>
|
||||
</view>
|
||||
<text>查看朋友圈</text>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
avatar: '',
|
||||
nickname: '张三',
|
||||
meteid: 'mete_10001',
|
||||
position: '产品经理',
|
||||
department: '产品部',
|
||||
phone: '13455668888',
|
||||
wechat: 'zhangsan_weixin',
|
||||
email: 'zhangsan@email.com',
|
||||
isOnline: true
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 从全局数据获取设备信息
|
||||
isMobile() {
|
||||
return getApp().globalData.isMobile;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
onAvatarError(e) {
|
||||
this.user.avatar = '/static/imgs/default_avatar.png'
|
||||
},
|
||||
sendMessage() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/message/chat?meteid=' + this.user.meteid
|
||||
});
|
||||
},
|
||||
startAudioCall() {
|
||||
uni.showToast({
|
||||
title: '语音通话功能暂未开放',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
startVideoCall() {
|
||||
uni.showToast({
|
||||
title: '视频通话功能暂未开放',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
copyToClipboard(text, type) {
|
||||
uni.setClipboardData({
|
||||
data: text,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: `${type}已复制`,
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
addToContacts() {
|
||||
uni.showToast({
|
||||
title: '已添加到通讯录',
|
||||
icon: 'success'
|
||||
});
|
||||
},
|
||||
shareContact() {
|
||||
uni.showToast({
|
||||
title: '已发送名片',
|
||||
icon: 'success'
|
||||
});
|
||||
},
|
||||
viewMoments() {
|
||||
uni.showToast({
|
||||
title: '朋友圈功能暂未开放',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-detail-container {
|
||||
background: var(--background);
|
||||
min-height: 100vh;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
/* 移动设备顶部状态栏 */
|
||||
.top_bar {
|
||||
background: var(--gradient-primary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: calc(var(--status-bar-height) + 88rpx);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding-top: var(--status-bar-height);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 支持安全区域的设备 */
|
||||
@supports (padding: max(0px)) {
|
||||
.top_bar {
|
||||
height: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
|
||||
}
|
||||
|
||||
.top_bar + .user-card {
|
||||
margin-top: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
}
|
||||
|
||||
.user-detail-container .chat-header:not(.top_bar .chat-header) {
|
||||
height: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
|
||||
}
|
||||
|
||||
.user-detail-container .chat-header:not(.top_bar .chat-header) + .user-card {
|
||||
margin-top: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
|
||||
}
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.chat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 88rpx;
|
||||
background-color: var(--surface);
|
||||
border-bottom: 1rpx solid var(--border);
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* 浏览器环境下的固定定位 */
|
||||
.user-detail-container .chat-header:not(.top_bar .chat-header) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
height: calc(var(--status-bar-height) + 88rpx);
|
||||
padding-top: var(--status-bar-height);
|
||||
}
|
||||
|
||||
/* 移动设备下的导航栏样式 */
|
||||
.top_bar .chat-header {
|
||||
background: transparent;
|
||||
border-bottom: none;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.top_bar .header-title {
|
||||
color: var(--white);
|
||||
font-weight: 600;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.top_bar .header-left i,
|
||||
.top_bar .header-right i {
|
||||
color: var(--white);
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.header-left,
|
||||
.header-right {
|
||||
width: 80rpx;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.header-left:active,
|
||||
.header-right:active {
|
||||
background-color: var(--gray-lighter);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.top_bar .header-left:active,
|
||||
.top_bar .header-right:active {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: var(--title-color);
|
||||
}
|
||||
|
||||
/* 移动设备下为内容添加顶部间距 */
|
||||
.top_bar + .user-card {
|
||||
margin-top: calc(var(--status-bar-height) + 88rpx);
|
||||
}
|
||||
|
||||
/* 浏览器环境下为内容添加顶部间距 */
|
||||
.user-detail-container .chat-header:not(.top_bar .chat-header) + .user-card {
|
||||
margin-top: calc(var(--status-bar-height) + 88rpx);
|
||||
}
|
||||
|
||||
/* 用户信息卡片 */
|
||||
.user-card {
|
||||
background: var(--surface);
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: var(--shadow-md);
|
||||
overflow: hidden;
|
||||
border: 1rpx solid var(--border-light);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 40rpx 30rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 16rpx;
|
||||
background: var(--gray-light);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.online-status {
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
right: 8rpx;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
background: var(--success);
|
||||
border: 4rpx solid var(--white);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-base-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: var(--title-color);
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.meteid {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.meta-info {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.meta-item i {
|
||||
margin-right: 12rpx;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
/* 功能按钮区域 */
|
||||
.action-section {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
background: var(--surface);
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1rpx solid var(--border-light);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: var(--shadow-md);
|
||||
background: var(--surface-hover);
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: var(--gradient-primary);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn i {
|
||||
font-size: 32rpx;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.action-btn text {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 信息卡片 */
|
||||
.info-card, .more-card {
|
||||
background: var(--surface);
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1rpx solid var(--border-light);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx 30rpx 20rpx 30rpx;
|
||||
border-bottom: 1rpx solid var(--border-light);
|
||||
background: var(--gray-lighter);
|
||||
}
|
||||
|
||||
.card-header i {
|
||||
font-size: 28rpx;
|
||||
color: var(--icon-color);
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.card-header text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: var(--title-color);
|
||||
}
|
||||
|
||||
.info-list, .more-list {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.info-item, .more-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid var(--border-light);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.info-item:last-child, .more-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-item:active, .more-item:active {
|
||||
background-color: var(--surface-hover);
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.info-icon.phone {
|
||||
background: var(--gradient-success);
|
||||
}
|
||||
|
||||
.info-icon.wechat {
|
||||
background: var(--gradient-success);
|
||||
}
|
||||
|
||||
.info-icon.email {
|
||||
background: var(--gradient-warning);
|
||||
}
|
||||
|
||||
.info-icon i {
|
||||
color: var(--white);
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-item i.fas.fa-copy, .more-item i.fas.fa-chevron-right {
|
||||
color: var(--text-muted);
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.more-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 12rpx;
|
||||
background: var(--gradient-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.more-icon i {
|
||||
color: var(--white);
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.more-item text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user