first commit

This commit is contained in:
2025-10-27 23:13:08 +08:00
commit 476c5e7658
2968 changed files with 49547 additions and 0 deletions
+850
View File
@@ -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>
+475
View File
@@ -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>
+683
View File
@@ -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>
+589
View File
@@ -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>