989 lines
24 KiB
Vue
989 lines
24 KiB
Vue
<template>
|
|
<div class="page-container">
|
|
<el-card class="page-header">
|
|
<h2>客服聊天</h2>
|
|
<p>客服聊天</p>
|
|
</el-card>
|
|
|
|
<!-- 统计卡片 -->
|
|
<!-- <el-card class="stats-card">-->
|
|
<!-- <el-row :gutter="24" class="stats-row">-->
|
|
<!-- <el-col :span="8">-->
|
|
<!-- <el-card class="stat-card">-->
|
|
<!-- <div class="stat-content">-->
|
|
<!-- <div class="stat-number">{{ stats.totalTransfers }}</div>-->
|
|
<!-- <div class="stat-label">总转账数</div>-->
|
|
<!-- </div>-->
|
|
<!-- <el-icon class="stat-icon">-->
|
|
<!-- <Money/>-->
|
|
<!-- </el-icon>-->
|
|
<!-- </el-card>-->
|
|
<!-- </el-col>-->
|
|
<!-- <el-col :span="8">-->
|
|
<!-- <el-card class="stat-card">-->
|
|
<!-- <div class="stat-content">-->
|
|
<!-- <div class="stat-number">{{ stats.confirmedTransfers }}</div>-->
|
|
<!-- <div class="stat-label">已确认</div>-->
|
|
<!-- </div>-->
|
|
<!-- <el-icon class="stat-icon">-->
|
|
<!-- <Check/>-->
|
|
<!-- </el-icon>-->
|
|
<!-- </el-card>-->
|
|
<!-- </el-col>-->
|
|
<!-- <el-col :span="8">-->
|
|
<!-- <el-card class="stat-card">-->
|
|
<!-- <div class="stat-content">-->
|
|
<!-- <div class="stat-number">¥{{ stats.totalAmount }}</div>-->
|
|
<!-- <div class="stat-label">总欠额</div>-->
|
|
<!-- </div>-->
|
|
<!-- <el-icon class="stat-icon">-->
|
|
<!-- <Wallet/>-->
|
|
<!-- </el-icon>-->
|
|
<!-- </el-card>-->
|
|
<!-- </el-col>-->
|
|
<!-- </el-row>-->
|
|
<!-- </el-card>-->
|
|
|
|
<!-- 筛选和操作 -->
|
|
<el-card class="filter-card">
|
|
<el-row :gutter="20">
|
|
<!-- <el-col :span="4">-->
|
|
<!-- <el-select v-model="queryParams.status" placeholder="选择状态" clearable>-->
|
|
<!-- <el-option label="全部" value=""/>-->
|
|
<!-- <el-option label="待确认" value="pending"/>-->
|
|
<!-- <el-option label="已确认" value="confirmed"/>-->
|
|
<!-- <el-option label="已拒绝" value="rejected"/>-->
|
|
<!-- <el-option label="已取消" value="cancelled"/>-->
|
|
<!-- </el-select>-->
|
|
<!-- </el-col>-->
|
|
|
|
<el-col :span="4">
|
|
<el-input v-model="queryParams.keyword" placeholder="搜索项目名称或者项目公司" clearable/>
|
|
</el-col>
|
|
|
|
<!-- <el-col :span="4">-->
|
|
<!-- <el-date-picker-->
|
|
<!-- v-model="dateRange"-->
|
|
<!-- type="daterange"-->
|
|
<!-- range-separator="至"-->
|
|
<!-- start-placeholder="开始日期"-->
|
|
<!-- end-placeholder="结束日期"-->
|
|
<!-- format="YYYY-MM-DD"-->
|
|
<!-- value-format="YYYY-MM-DD"-->
|
|
<!-- />-->
|
|
<!-- </el-col>-->
|
|
|
|
<el-row style="margin-left: 50px;" :gutter="10">
|
|
<el-col :span="12">
|
|
<el-button type="primary" @click="getList">搜索</el-button>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-button @click="resetFilters">重置</el-button>
|
|
</el-col>
|
|
</el-row>
|
|
</el-row>
|
|
|
|
</el-card>
|
|
|
|
<!-- 数据列表 -->
|
|
<el-card class="table-card">
|
|
<el-row :gutter="10" class="mb8 mb-2">
|
|
<!-- <el-col :span="1.5">-->
|
|
<!-- <el-button-->
|
|
<!-- type="primary"-->
|
|
<!-- plain-->
|
|
<!-- icon="Plus"-->
|
|
<!-- @click="handleAdd"-->
|
|
<!-- >新增-->
|
|
<!-- </el-button>-->
|
|
<!-- </el-col>-->
|
|
<!-- <el-col :span="1.5">-->
|
|
<!-- <el-button-->
|
|
<!-- type="success"-->
|
|
<!-- plain-->
|
|
<!-- icon="Edit"-->
|
|
<!-- :disabled="single"-->
|
|
<!-- @click="handleUpdate"-->
|
|
<!-- >修改-->
|
|
<!-- </el-button>-->
|
|
<!-- </el-col>-->
|
|
<!-- <el-col :span="1.5">-->
|
|
<!-- <el-button-->
|
|
<!-- type="danger"-->
|
|
<!-- plain-->
|
|
<!-- icon="Delete"-->
|
|
<!-- :disabled="single"-->
|
|
<!-- @click="handleDelete"-->
|
|
<!-- >删除-->
|
|
<!-- </el-button>-->
|
|
<!-- </el-col>-->
|
|
<el-col :span="1.5">
|
|
<el-button
|
|
type="primary"
|
|
plain
|
|
icon="Refresh"
|
|
@click="handleRefresh"
|
|
>刷新
|
|
</el-button>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<el-table
|
|
:data="dataList"
|
|
v-loading="loading"
|
|
@selection-change="handleSelectionChange"
|
|
stripe
|
|
style="width: 100%"
|
|
>
|
|
<el-table-column type="selection" width="55" align="center"/>
|
|
<!-- <el-table-column prop="id" label="ID"/>-->
|
|
<el-table-column prop="name" label="项目名称">
|
|
<template #default="scope">
|
|
{{ scope.row.program.name }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="company" label="项目公司">
|
|
<template #default="scope">
|
|
{{ scope.row.program.company }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="项目负责人">
|
|
<template #default="scope">
|
|
<el-tag>{{ scope.row.chargeUser.username }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="咨询人">
|
|
<template #default="scope">
|
|
{{ scope.row.user.username }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="消息未读数">
|
|
<template #default="scope">
|
|
<el-tag type="danger">{{ scope.row.customerUnread.length }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="createTime" label="创建时间"/>
|
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="300">
|
|
<template #default="scope">
|
|
<!-- <el-button link type="primary" icon="Document" @click="handleDetail(scope.row)">详情</el-button>-->
|
|
<!-- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>-->
|
|
<el-button link type="primary" :icon="inChatSvg" @click="handleChat(scope.row)">进入聊天</el-button>
|
|
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>-->
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<!-- 分页 -->
|
|
<div class="pagination-container">
|
|
<el-pagination
|
|
v-model:current-page="pagination.page"
|
|
v-model:page-size="pagination.size"
|
|
:page-sizes="[10,50,100]"
|
|
:total="pagination.total"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
@size-change="getList"
|
|
@current-change="getList"
|
|
/>
|
|
</div>
|
|
</el-card>
|
|
|
|
<!-- 表单 -->
|
|
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
|
<el-form-item label="项目名称" prop="name">
|
|
<el-input v-model="form.name" placeholder="请输入项目名称"/>
|
|
</el-form-item>
|
|
<el-form-item label="公司名称" prop="company">
|
|
<el-input v-model="form.company" placeholder="请输入公司名称"/>
|
|
</el-form-item>
|
|
<el-form-item label="项目地点" prop="address">
|
|
<el-input v-model="form.address" placeholder="请输入项目地点"/>
|
|
</el-form-item>
|
|
<el-form-item label="结算方式" prop="paymentMethod">
|
|
<el-input v-model="form.paymentMethod" placeholder="请输入结算方式"/>
|
|
</el-form-item>
|
|
<el-form-item label="项目时间" prop="dataRange">
|
|
<el-date-picker
|
|
v-model="form.dataRange"
|
|
type="daterange"
|
|
range-separator="至"
|
|
start-placeholder="开始时间"
|
|
end-placeholder="结束时间"
|
|
/>
|
|
</el-form-item>
|
|
<!-- <el-form-item label="开始时间" prop="startDate">-->
|
|
<!-- <el-input v-model="form.startDate" placeholder="请选择开始时间"/>-->
|
|
<!-- </el-form-item>-->
|
|
<!-- <el-form-item label="结束时间" prop="endDate">-->
|
|
<!-- <el-input v-model="form.endDate" placeholder="请选择结束时间"/>-->
|
|
<!-- </el-form-item>-->
|
|
<el-form-item label="项目描述" prop="introduction">
|
|
<el-input v-model="form.introduction" placeholder="请输入项目简介"/>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<div class="dialog-footer">
|
|
<el-button type="primary" @click="submitForm">确 定</el-button>
|
|
<el-button @click="cancel">取 消</el-button>
|
|
</div>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 聊天室 -->
|
|
<el-dialog
|
|
v-model="chatDialog"
|
|
title="客服聊天室"
|
|
width="70%"
|
|
class="chat-dialog"
|
|
:before-close="closeChatDialog"
|
|
:close-on-click-modal="false"
|
|
>
|
|
<div class="chat-content">
|
|
<div class="chat-messages" ref="messagesContainer">
|
|
<div style="text-align: center">
|
|
<el-button v-if="canLoad" @click="loadData">加载更多</el-button>
|
|
</div>
|
|
<div
|
|
v-for="(message, index) in messageList"
|
|
:key="index"
|
|
class="message"
|
|
:class="{
|
|
'manager': messageChargeId === message.createId,
|
|
'consultant': messageUserId === message.createId,
|
|
'service': userStore.user.id === message.createId,
|
|
}"
|
|
>
|
|
<div class="message-avatar">
|
|
{{ getRoleInitial(message.createId) }}
|
|
</div>
|
|
<div class="message-content">
|
|
<div class="message-sender">{{ message.userInfo.username }}</div>
|
|
<div v-if="message.type==='text'">{{ message.content }}</div>
|
|
<div v-if="message.type==='img'">
|
|
<el-image class="message-img" :src="message.content" fit="contain"
|
|
:preview-src-list="[message.content]"/>
|
|
</div>
|
|
<div v-if="message.type==='mp3'">
|
|
<audio ref="audio" :src="message.content" controls="controls"></audio>
|
|
</div>
|
|
<div class="message-time">{{ message.createTime }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chat-input-area">
|
|
<el-input
|
|
v-model="inputText"
|
|
class="chat-input"
|
|
type="textarea"
|
|
:rows="3"
|
|
placeholder="请输入消息..."
|
|
></el-input>
|
|
<el-button type="primary" @click="handleSendMsg">发送</el-button>
|
|
</div>
|
|
</div>
|
|
</el-dialog>
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
|
|
import {getCurrentInstance, nextTick, onMounted, reactive, ref, toRefs} from "vue";
|
|
import {Check, Money, Wallet} from "@element-plus/icons-vue";
|
|
import {ElMessage, ElMessageBox} from "element-plus";
|
|
import {programAPI} from "@/api/program";
|
|
import {useUserStore} from "@/stores/user";
|
|
import {groupAPI} from "@/api/group";
|
|
import inChatSvg from '@/assets/svg/inChat.svg'
|
|
import {messageAPI} from "@/api/message";
|
|
import {io} from 'socket.io-client';
|
|
|
|
|
|
const {proxy} = getCurrentInstance();
|
|
const userStore = useUserStore()
|
|
|
|
// 统计
|
|
const stats = ref({
|
|
totalTransfers: 10,
|
|
confirmedTransfers: 10,
|
|
totalAmount: 10
|
|
})
|
|
const dateRange = ref([])
|
|
// 列表
|
|
const dataList = ref([])
|
|
const loading = ref(false)
|
|
const single = ref(true);
|
|
// 分页
|
|
const pagination = ref({
|
|
page: 1,
|
|
size: 10,
|
|
total: 0
|
|
});
|
|
// 表单
|
|
const open = ref(false)
|
|
const title = ref("");
|
|
const data = reactive({
|
|
form: {},
|
|
queryParams: {
|
|
page: 1,
|
|
size: 10,
|
|
customerId: userStore.user.id,
|
|
name: undefined,
|
|
status: undefined,
|
|
keyword: undefined
|
|
},
|
|
rules: {
|
|
name: [{required: true, message: "项目名称不能为空", trigger: "blur"}],
|
|
company: [{required: true, message: "公司不能为空", trigger: "blur"}],
|
|
address: [{required: true, message: "地址不能为空", trigger: "blur"}],
|
|
introduction: [{required: true, message: "简介不能为空", trigger: "blur"}],
|
|
paymentMethod: [{required: true, message: "结算方式不能为空", trigger: "blur"}],
|
|
dataRange: [{required: true, message: "时间不能为空", trigger: "blur"}],
|
|
},
|
|
});
|
|
|
|
const {queryParams, form, rules} = toRefs(data);
|
|
|
|
// TODO
|
|
const getStats = () => {
|
|
// programAPI.getStats().then(res => {
|
|
// stats.value = res.data.data.stats
|
|
// })
|
|
}
|
|
|
|
// 加载数据
|
|
const getList = async () => {
|
|
loading.value = true;
|
|
// api请求
|
|
queryParams.value.page = pagination.value.page;
|
|
queryParams.value.size = pagination.value.size;
|
|
groupAPI.list(queryParams.value).then(res => {
|
|
dataList.value = res.data.data.list
|
|
pagination.value.total = res.data.data.total
|
|
loading.value = false;
|
|
})
|
|
}
|
|
|
|
// 重置
|
|
const resetFilters = () => {
|
|
queryParams.value = {
|
|
page: 1,
|
|
keyword: undefined
|
|
}
|
|
dateRange.value = []
|
|
getList()
|
|
}
|
|
|
|
const handleSelectionChange = (selection) => {
|
|
single.value = selection.length != 1;
|
|
}
|
|
|
|
const handleAdd = () => {
|
|
title.value = "新增项目"
|
|
open.value = true;
|
|
form.value = {}
|
|
}
|
|
|
|
const handleUpdate = (row) => {
|
|
programAPI.getOne(row.id).then(res => {
|
|
form.value = res.data.data
|
|
form.value.dataRange = [form.value.startDate, form.value.endDate]
|
|
title.value = "修改项目"
|
|
open.value = true;
|
|
})
|
|
}
|
|
|
|
const handleDelete = (row) => {
|
|
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(() => {
|
|
programAPI.delete(row.id).then(res => {
|
|
ElMessage.success('删除成功')
|
|
getList()
|
|
})
|
|
}).catch(() => {
|
|
ElMessage.info('已取消删除')
|
|
})
|
|
}
|
|
|
|
// 查看详情
|
|
const handleDetail = (row) => {
|
|
ElMessage.info('查看详情')
|
|
}
|
|
|
|
const submitForm = () => {
|
|
proxy.$refs["formRef"].validate(valid => {
|
|
if (valid) {
|
|
form.value.linkmanId = userStore.user.id
|
|
form.value.startDate = form.value.dataRange[0]
|
|
form.value.endDate = form.value.dataRange[1]
|
|
if (form.value.id != undefined) {
|
|
delete form.value.user;
|
|
delete form.value.dataRange;
|
|
programAPI.update(form.value).then(res => {
|
|
ElMessage.success('修改成功')
|
|
open.value = false
|
|
getList()
|
|
})
|
|
} else {
|
|
programAPI.add(form.value).then(res => {
|
|
ElMessage.success('添加成功')
|
|
open.value = false
|
|
getList()
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const cancel = () => {
|
|
open.value = false
|
|
}
|
|
|
|
// 聊天
|
|
const chatDialog = ref(false)
|
|
const messagesContainer = ref(null)
|
|
const messageList = ref([])
|
|
const messageUserId = ref(null)
|
|
const messageChargeId = ref(null)
|
|
const params = ref({
|
|
groupId: undefined,
|
|
page: 1,
|
|
size: 10
|
|
})
|
|
const maxPage = ref(1)
|
|
const canLoad = ref(true)
|
|
const connected = ref(false)
|
|
const socket = ref(null)
|
|
const inputText = ref('')
|
|
|
|
// 打开聊天框
|
|
const handleChat = async (row) => {
|
|
// 加入连接
|
|
joinRoom(row)
|
|
setTimeout(() => {
|
|
if (!connected.value) {
|
|
ElMessage.error('连接失败')
|
|
return
|
|
}
|
|
// 加载聊天记录
|
|
messageUserId.value = row.userId
|
|
messageChargeId.value = row.chargeId
|
|
params.value.groupId = row.groupId
|
|
loadData()
|
|
// 未读变已读
|
|
messageAPI.read({
|
|
groupId: row.groupId, userId: userStore.user.id
|
|
})
|
|
chatDialog.value = true;
|
|
nextTick(() => {
|
|
setTimeout(() => {
|
|
scrollToBottom()
|
|
}, 500)
|
|
})
|
|
}, 1000)
|
|
}
|
|
|
|
const joinRoom = (row) => {
|
|
socket.value = io(`http://192.168.0.15:3007?userId=${userStore.user.id}`, {
|
|
transports: ['websocket', 'polling'],
|
|
timeout: 5000,
|
|
})
|
|
socket.value.on("connect", () => {
|
|
console.log("连接成功")
|
|
connected.value = true;
|
|
socket.value.emit('addGroup', {
|
|
groupId: row.groupId
|
|
})
|
|
socket.value.on('serverMsg', async (message) => {
|
|
console.log(message)
|
|
messageList.value.push(message)
|
|
setTimeout(() => {
|
|
scrollToBottom()
|
|
}, 100)
|
|
})
|
|
});
|
|
socket.value.on("leaveGroup", () => {
|
|
console.log("leaveGroup")
|
|
connected.value = false;
|
|
});
|
|
}
|
|
|
|
const loadData = async () => {
|
|
await messageAPI.list(params.value).then(res => {
|
|
for (let i = 0; i < res.data.data.list.length; i++) {
|
|
messageList.value.unshift(res.data.data.list[i])
|
|
}
|
|
maxPage.value = res.data.data.pages
|
|
params.value.page++
|
|
if (params.value.page > maxPage.value) canLoad.value = false
|
|
})
|
|
}
|
|
|
|
// 滚动到底部
|
|
const scrollToBottom = () => {
|
|
if (messagesContainer.value) {
|
|
let scrollElem = messagesContainer.value;
|
|
scrollElem.scrollTo({top: scrollElem.scrollHeight, behavior: 'instant'});
|
|
}
|
|
}
|
|
|
|
// 关闭聊天
|
|
const closeChatDialog = () => {
|
|
canLoad.value = true
|
|
params.value.page = 1
|
|
messageList.value = []
|
|
|
|
console.log("disConnection");
|
|
socket.value.emit('leaveGroup', {
|
|
groupId: params.value.groupId
|
|
})
|
|
chatDialog.value = false
|
|
|
|
getList()
|
|
}
|
|
|
|
// 发送消息
|
|
const handleSendMsg = () => {
|
|
socket.value.emit('clientMsg', {
|
|
groupId: params.value.groupId,
|
|
createId: userStore.user.id,
|
|
content: inputText.value,
|
|
type: 'text'
|
|
})
|
|
inputText.value = ''
|
|
}
|
|
|
|
// 头像字
|
|
const getRoleInitial = (createId) => {
|
|
if (messageUserId.value === createId) {
|
|
return '咨'
|
|
}
|
|
if (messageChargeId.value === createId) {
|
|
return '项'
|
|
}
|
|
if (userStore.user.id === createId) {
|
|
return '客'
|
|
}
|
|
};
|
|
|
|
const handleRefresh = () => {
|
|
getList()
|
|
}
|
|
|
|
onMounted(() => {
|
|
// 获取统计
|
|
getStats()
|
|
// 获取列表
|
|
getList();
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.page-container {
|
|
padding: 20px;
|
|
}
|
|
|
|
.page-header {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.page-header h2 {
|
|
margin: 0 0 8px 0;
|
|
color: #303133;
|
|
}
|
|
|
|
.page-header p {
|
|
margin: 0;
|
|
color: #909399;
|
|
}
|
|
|
|
.stats-card {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stats-row {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stat-card {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.stat-content {
|
|
padding: 20px;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
color: #409eff;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.stat-label {
|
|
color: #909399;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.stat-icon {
|
|
position: absolute;
|
|
right: 20px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
font-size: 40px;
|
|
color: #e4e7ed;
|
|
}
|
|
|
|
.stat-card.overdue {
|
|
border-left: 4px solid #e6a23c;
|
|
}
|
|
|
|
.stat-card.overdue .stat-icon {
|
|
color: #e6a23c;
|
|
}
|
|
|
|
.stat-card.overdue .stat-number {
|
|
color: #e6a23c;
|
|
}
|
|
|
|
.filter-card {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.table-card {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.amount {
|
|
font-weight: bold;
|
|
color: #67c23a;
|
|
}
|
|
|
|
.amount-red {
|
|
font-weight: bold;
|
|
color: #ff4949;
|
|
}
|
|
|
|
.pagination-container {
|
|
margin-top: 20px;
|
|
text-align: right;
|
|
}
|
|
|
|
.proof-container {
|
|
text-align: center;
|
|
}
|
|
|
|
.proof-image {
|
|
width: 60%;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.proof-section {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.proof-section h4 {
|
|
margin-bottom: 10px;
|
|
color: #303133;
|
|
}
|
|
|
|
.detail-container {
|
|
display: grid;
|
|
place-items: center;
|
|
}
|
|
|
|
.detail-buttom {
|
|
color: #409eff;
|
|
cursor: pointer;
|
|
font-size: 10px;
|
|
}
|
|
|
|
.detail-buttom:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.avatar {
|
|
width: 100px;
|
|
height: 100px;
|
|
display: block;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.pay-detail-text {
|
|
white-space: pre-line;
|
|
word-break: break-word;
|
|
padding: 10px;
|
|
background-color: #f5f7fa;
|
|
border-radius: 4px;
|
|
border-left: 3px solid #409eff;
|
|
font-size: large;
|
|
}
|
|
|
|
.user-balance-info {
|
|
margin-top: 5px;
|
|
padding: 5px 10px;
|
|
background-color: #f5f7fa;
|
|
border-radius: 4px;
|
|
border-left: 3px solid #409eff;
|
|
}
|
|
|
|
.real-name {
|
|
font-size: 12px;
|
|
color: #909399;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
// 定义变量
|
|
$primary-color: #409EFF;
|
|
$success-color: #67C23A;
|
|
$warning-color: #E6A23C;
|
|
$info-color: #909399;
|
|
$background-color: #f5f7fa;
|
|
$border-color: #ebeef5;
|
|
$text-color: #303133;
|
|
$text-light: #606266;
|
|
$shadow-light: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
$shadow-medium: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
|
|
// 定义混合
|
|
@mixin flex-center {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
@mixin message-bubble($bg-color, $text-color: $text-color) {
|
|
background-color: $bg-color;
|
|
color: $text-color;
|
|
box-shadow: $shadow-light;
|
|
padding: 12px 15px;
|
|
border-radius: 18px;
|
|
max-width: 70%;
|
|
position: relative;
|
|
|
|
.message-sender {
|
|
font-size: 12px;
|
|
margin-bottom: 4px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.message-time {
|
|
font-size: 11px;
|
|
color: rgba($text-light, 0.7);
|
|
margin-top: 5px;
|
|
text-align: right;
|
|
}
|
|
}
|
|
|
|
body {
|
|
font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
background-color: $background-color;
|
|
padding: 20px;
|
|
@include flex-center;
|
|
min-height: 100vh;
|
|
margin: 0;
|
|
}
|
|
|
|
.demo-container {
|
|
width: 100%;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.chat-dialog {
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
box-shadow: $shadow-medium;
|
|
|
|
.el-dialog__header {
|
|
background: linear-gradient(135deg, $primary-color 0%, $success-color 100%);
|
|
margin: 0;
|
|
|
|
.el-dialog__title {
|
|
color: white;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.el-dialog__headerbtn {
|
|
top: 15px;
|
|
|
|
.el-dialog__close {
|
|
color: white;
|
|
}
|
|
}
|
|
}
|
|
|
|
.el-dialog__body {
|
|
padding: 0;
|
|
}
|
|
}
|
|
|
|
.chat-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 700px;
|
|
}
|
|
|
|
.chat-messages {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 15px;
|
|
background-color: #f9f9f9;
|
|
|
|
.message {
|
|
margin-bottom: 15px;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
|
|
&.manager {
|
|
justify-content: flex-start;
|
|
|
|
.message-avatar {
|
|
background-color: $warning-color;
|
|
}
|
|
|
|
.message-content {
|
|
@include message-bubble(#fff);
|
|
border-top-left-radius: 4px;
|
|
|
|
.message-sender {
|
|
color: $warning-color;
|
|
}
|
|
}
|
|
}
|
|
|
|
&.consultant {
|
|
justify-content: flex-start;
|
|
|
|
.message-avatar {
|
|
background-color: $primary-color;
|
|
}
|
|
|
|
.message-content {
|
|
@include message-bubble($primary-color, white);
|
|
border-top-right-radius: 4px;
|
|
|
|
.message-sender {
|
|
color: rgba(white, 0.8);
|
|
}
|
|
|
|
.message-time {
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
|
|
&.service {
|
|
justify-content: flex-end;
|
|
|
|
.message-avatar {
|
|
background-color: $success-color;
|
|
}
|
|
|
|
.message-content {
|
|
@include message-bubble(#f0f9eb);
|
|
border-top-left-radius: 4px;
|
|
|
|
.message-sender {
|
|
color: $success-color;
|
|
}
|
|
}
|
|
}
|
|
|
|
.message-avatar {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 50%;
|
|
@include flex-center;
|
|
color: white;
|
|
font-weight: bold;
|
|
margin: 0 10px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
|
|
.message-img {
|
|
width: 400px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.chat-input-area {
|
|
padding: 15px;
|
|
background-color: white;
|
|
border-top: 1px solid $border-color;
|
|
display: flex;
|
|
align-items: flex-end;
|
|
|
|
.chat-input {
|
|
flex: 1;
|
|
margin-right: 10px;
|
|
}
|
|
}
|
|
|
|
// 响应式设计
|
|
@media (max-width: 768px) {
|
|
.chat-dialog {
|
|
width: 90% !important;
|
|
|
|
.chat-content {
|
|
height: 400px;
|
|
}
|
|
|
|
.message-content {
|
|
max-width: 85% !important;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 滚动条样式
|
|
.chat-messages::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.chat-messages::-webkit-scrollbar-track {
|
|
background: #f1f1f1;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.chat-messages::-webkit-scrollbar-thumb {
|
|
background: #c1c1c1;
|
|
border-radius: 3px;
|
|
|
|
&:hover {
|
|
background: #a8a8a8;
|
|
}
|
|
}
|
|
|
|
// 动画效果
|
|
.message {
|
|
animation: fadeInUp 0.3s ease-out;
|
|
|
|
@keyframes fadeInUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
.el-button--primary {
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba($primary-color, 0.3);
|
|
}
|
|
}
|
|
|
|
</style> |