first commit

This commit is contained in:
2026-02-06 20:21:10 +08:00
commit f9d7f980bb
3994 changed files with 1146030 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>
File diff suppressed because it is too large Load Diff
+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>