1
This commit is contained in:
653
admin/src/views/chat/chat_list.vue
Normal file
653
admin/src/views/chat/chat_list.vue
Normal file
@@ -0,0 +1,653 @@
|
||||
<template>
|
||||
<div class="chat-list-container">
|
||||
<!-- 左侧会话列表 -->
|
||||
<div class="conversation-list">
|
||||
<div class="conversation-header">
|
||||
<h3>聊天列表</h3>
|
||||
<div class="header-actions">
|
||||
<Select v-model="conversationFilter.platform" style="width: 120px" placeholder="平台" clearable @on-change="loadConversations">
|
||||
<Option value="boss">Boss直聘</Option>
|
||||
<Option value="liepin">猎聘</Option>
|
||||
</Select>
|
||||
<Input
|
||||
v-model="conversationFilter.search"
|
||||
placeholder="搜索公司/职位"
|
||||
search
|
||||
style="width: 200px; margin-left: 10px"
|
||||
@on-search="loadConversations"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conversation-items">
|
||||
<div
|
||||
v-for="conversation in conversations"
|
||||
:key="conversation.conversationId"
|
||||
:class="['conversation-item', { active: activeConversation?.conversationId === conversation.conversationId }]"
|
||||
@click="selectConversation(conversation)"
|
||||
>
|
||||
<div class="conversation-avatar">
|
||||
<Avatar :style="{ background: conversation.platform === 'boss' ? '#00b38a' : '#00a6ff' }">
|
||||
{{ conversation.companyName ? conversation.companyName.substring(0, 1) : 'C' }}
|
||||
</Avatar>
|
||||
<Badge v-if="conversation.unreadCount > 0" :count="conversation.unreadCount" />
|
||||
</div>
|
||||
<div class="conversation-info">
|
||||
<div class="conversation-title">
|
||||
<strong>{{ conversation.companyName || '未知公司' }}</strong>
|
||||
<Tag :color="conversation.platform === 'boss' ? 'success' : 'primary'" size="small">
|
||||
{{ conversation.platform === 'boss' ? 'Boss' : '猎聘' }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="conversation-subtitle">{{ conversation.jobTitle || '未知职位' }}</div>
|
||||
<div class="conversation-last-message">
|
||||
{{ conversation.lastMessage || '暂无消息' }}
|
||||
</div>
|
||||
<div class="conversation-time">{{ formatTime(conversation.lastMessageTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="conversations.length === 0" class="empty-state">
|
||||
<p>暂无聊天记录</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧聊天窗口 -->
|
||||
<div class="chat-window">
|
||||
<div v-if="activeConversation" class="chat-content">
|
||||
<!-- 聊天头部 -->
|
||||
<div class="chat-header">
|
||||
<div class="chat-header-info">
|
||||
<h3>{{ activeConversation.companyName || '未知公司' }}</h3>
|
||||
<p>{{ activeConversation.jobTitle || '未知职位' }} - {{ activeConversation.hrName || 'HR' }}</p>
|
||||
</div>
|
||||
<div class="chat-header-actions">
|
||||
<Button icon="md-refresh" @click="loadChatMessages">刷新</Button>
|
||||
<Button icon="md-information-circle" @click="showJobDetail">职位详情</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 聊天消息列表 -->
|
||||
<div class="chat-messages" ref="chatMessages">
|
||||
<div
|
||||
v-for="message in chatMessages"
|
||||
:key="message.id"
|
||||
:class="['message-item', message.direction === 'sent' ? 'message-sent' : 'message-received']"
|
||||
>
|
||||
<div class="message-avatar">
|
||||
<Avatar v-if="message.direction === 'received'" icon="ios-person" />
|
||||
<Avatar v-else icon="ios-chatbubbles" />
|
||||
</div>
|
||||
<div class="message-content">
|
||||
<div class="message-meta">
|
||||
<span class="message-sender">
|
||||
{{ message.direction === 'sent' ? '我' : (message.hrName || 'HR') }}
|
||||
</span>
|
||||
<span class="message-time">{{ formatTime(message.sendTime || message.receiveTime) }}</span>
|
||||
<Tag v-if="message.isAiGenerated" color="purple" size="small">AI生成</Tag>
|
||||
</div>
|
||||
<div class="message-bubble">
|
||||
{{ message.content }}
|
||||
</div>
|
||||
<div v-if="message.isInterviewInvitation" class="interview-invitation">
|
||||
<Icon type="md-calendar" />
|
||||
<span>面试邀约: {{ message.interviewType === 'online' ? '线上' : '线下' }}</span>
|
||||
<span v-if="message.interviewTime">{{ formatTime(message.interviewTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="chatMessages.length === 0" class="empty-messages">
|
||||
<p>暂无聊天消息</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息输入框 -->
|
||||
<div class="chat-input">
|
||||
<Input
|
||||
v-model="messageInput"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="输入消息内容..."
|
||||
@on-enter="handleSendMessage"
|
||||
/>
|
||||
<div class="chat-input-actions">
|
||||
<Button type="primary" icon="md-send" @click="handleSendMessage" :loading="sending">发送</Button>
|
||||
<Button icon="md-bulb" @click="generateAiMessage">AI生成</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 未选择会话的占位 -->
|
||||
<div v-else class="chat-placeholder">
|
||||
<Icon type="ios-chatbubbles" size="64" color="#dcdee2" />
|
||||
<p>请选择一个会话开始聊天</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import chatRecordsServer from '@/api/operation/chat_records_server.js'
|
||||
|
||||
export default {
|
||||
name: 'ChatList',
|
||||
data() {
|
||||
return {
|
||||
// 会话列表
|
||||
conversations: [],
|
||||
activeConversation: null,
|
||||
conversationFilter: {
|
||||
platform: null,
|
||||
search: ''
|
||||
},
|
||||
|
||||
// 聊天消息
|
||||
chatMessages: [],
|
||||
messageInput: '',
|
||||
sending: false,
|
||||
|
||||
// 定时刷新
|
||||
refreshTimer: null,
|
||||
refreshInterval: 10000, // 10秒刷新一次
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadConversations()
|
||||
this.startAutoRefresh()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopAutoRefresh()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 加载会话列表
|
||||
*/
|
||||
async loadConversations() {
|
||||
try {
|
||||
const res = await chatRecordsServer.page({
|
||||
seachOption: {
|
||||
platform: this.conversationFilter.platform,
|
||||
key: 'companyName',
|
||||
value: this.conversationFilter.search
|
||||
},
|
||||
pageOption: {
|
||||
page: 1,
|
||||
pageSize: 100
|
||||
}
|
||||
})
|
||||
|
||||
// 按 conversationId 分组聊天记录
|
||||
const conversationMap = new Map()
|
||||
const rows = res.data?.rows || res.data?.list || []
|
||||
|
||||
rows.forEach(record => {
|
||||
const convId = record.conversationId || `${record.jobId}_${record.sn_code}`
|
||||
|
||||
if (!conversationMap.has(convId)) {
|
||||
conversationMap.set(convId, {
|
||||
conversationId: convId,
|
||||
jobId: record.jobId,
|
||||
sn_code: record.sn_code,
|
||||
platform: record.platform,
|
||||
companyName: record.companyName,
|
||||
jobTitle: record.jobTitle,
|
||||
hrName: record.hrName,
|
||||
lastMessage: record.content,
|
||||
lastMessageTime: record.sendTime || record.receiveTime || new Date(),
|
||||
unreadCount: 0,
|
||||
messages: []
|
||||
})
|
||||
}
|
||||
|
||||
const conv = conversationMap.get(convId)
|
||||
conv.messages.push(record)
|
||||
|
||||
// 更新最后一条消息
|
||||
const lastTime = new Date(conv.lastMessageTime).getTime()
|
||||
const currentTime = new Date(record.sendTime || record.receiveTime || new Date()).getTime()
|
||||
if (currentTime > lastTime) {
|
||||
conv.lastMessage = record.content
|
||||
conv.lastMessageTime = record.sendTime || record.receiveTime
|
||||
}
|
||||
})
|
||||
|
||||
this.conversations = Array.from(conversationMap.values())
|
||||
.sort((a, b) => new Date(b.lastMessageTime) - new Date(a.lastMessageTime))
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载会话列表失败:', error)
|
||||
this.$Message.error('加载会话列表失败: ' + (error.message || '未知错误'))
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择会话
|
||||
*/
|
||||
selectConversation(conversation) {
|
||||
this.activeConversation = conversation
|
||||
this.loadChatMessages()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载聊天消息
|
||||
*/
|
||||
async loadChatMessages() {
|
||||
if (!this.activeConversation) return
|
||||
|
||||
try {
|
||||
const res = await chatRecordsServer.getByJobId({
|
||||
jobId: this.activeConversation.jobId,
|
||||
sn_code: this.activeConversation.sn_code
|
||||
})
|
||||
|
||||
this.chatMessages = res.data.sort((a, b) => {
|
||||
const timeA = new Date(a.sendTime || a.receiveTime).getTime()
|
||||
const timeB = new Date(b.sendTime || b.receiveTime).getTime()
|
||||
return timeA - timeB
|
||||
})
|
||||
|
||||
// 滚动到底部
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom()
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
this.$Message.error('加载聊天消息失败: ' + (error.message || '未知错误'))
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
async handleSendMessage() {
|
||||
if (!this.messageInput.trim()) {
|
||||
this.$Message.warning('请输入消息内容')
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.activeConversation) {
|
||||
this.$Message.warning('请先选择一个会话')
|
||||
return
|
||||
}
|
||||
|
||||
this.sending = true
|
||||
|
||||
try {
|
||||
await chatRecordsServer.sendMessage({
|
||||
sn_code: this.activeConversation.sn_code,
|
||||
jobId: this.activeConversation.jobId,
|
||||
content: this.messageInput,
|
||||
chatType: 'reply',
|
||||
platform: this.activeConversation.platform
|
||||
})
|
||||
|
||||
this.$Message.success('消息发送成功')
|
||||
this.messageInput = ''
|
||||
|
||||
// 重新加载消息
|
||||
await this.loadChatMessages()
|
||||
|
||||
} catch (error) {
|
||||
this.$Message.error('消息发送失败: ' + (error.message || '未知错误'))
|
||||
} finally {
|
||||
this.sending = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* AI生成消息
|
||||
*/
|
||||
generateAiMessage() {
|
||||
this.$Message.info('AI消息生成功能开发中...')
|
||||
// TODO: 调用AI接口生成消息
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示职位详情
|
||||
*/
|
||||
showJobDetail() {
|
||||
if (!this.activeConversation) return
|
||||
|
||||
this.$Modal.info({
|
||||
title: '职位详情',
|
||||
width: 600,
|
||||
render: (h) => {
|
||||
return h('div', [
|
||||
h('p', [h('strong', '公司名称: '), this.activeConversation.companyName]),
|
||||
h('p', [h('strong', '职位名称: '), this.activeConversation.jobTitle]),
|
||||
h('p', [h('strong', 'HR: '), this.activeConversation.hrName || '未知']),
|
||||
h('p', [h('strong', '平台: '), this.activeConversation.platform === 'boss' ? 'Boss直聘' : '猎聘']),
|
||||
h('p', [h('strong', '职位ID: '), this.activeConversation.jobId])
|
||||
])
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
*/
|
||||
formatTime(time) {
|
||||
if (!time) return ''
|
||||
|
||||
const date = new Date(time)
|
||||
const now = new Date()
|
||||
const diff = now - date
|
||||
|
||||
// 1分钟内
|
||||
if (diff < 60000) {
|
||||
return '刚刚'
|
||||
}
|
||||
|
||||
// 1小时内
|
||||
if (diff < 3600000) {
|
||||
return `${Math.floor(diff / 60000)}分钟前`
|
||||
}
|
||||
|
||||
// 今天
|
||||
if (date.toDateString() === now.toDateString()) {
|
||||
return date.toTimeString().substring(0, 5)
|
||||
}
|
||||
|
||||
// 昨天
|
||||
const yesterday = new Date(now)
|
||||
yesterday.setDate(yesterday.getDate() - 1)
|
||||
if (date.toDateString() === yesterday.toDateString()) {
|
||||
return '昨天 ' + date.toTimeString().substring(0, 5)
|
||||
}
|
||||
|
||||
// 其他
|
||||
return `${date.getMonth() + 1}-${date.getDate()} ${date.toTimeString().substring(0, 5)}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 滚动到底部
|
||||
*/
|
||||
scrollToBottom() {
|
||||
const messagesEl = this.$refs.chatMessages
|
||||
if (messagesEl) {
|
||||
messagesEl.scrollTop = messagesEl.scrollHeight
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 开始自动刷新
|
||||
*/
|
||||
startAutoRefresh() {
|
||||
// 启动定时器,每10秒刷新一次
|
||||
this.refreshTimer = setInterval(() => {
|
||||
// 如果有选中的会话,刷新消息
|
||||
if (this.activeConversation) {
|
||||
this.loadChatMessages()
|
||||
}
|
||||
// 刷新会话列表
|
||||
this.loadConversations()
|
||||
}, this.refreshInterval)
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止自动刷新
|
||||
*/
|
||||
stopAutoRefresh() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer)
|
||||
this.refreshTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-list-container {
|
||||
display: flex;
|
||||
height: calc(100vh - 120px);
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧会话列表 */
|
||||
.conversation-list {
|
||||
width: 320px;
|
||||
border-right: 1px solid #e8eaec;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.conversation-header {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.conversation-header h3 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.conversation-items {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.conversation-item {
|
||||
display: flex;
|
||||
padding: 12px 15px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.conversation-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.conversation-item.active {
|
||||
background-color: #e8f4ff;
|
||||
}
|
||||
|
||||
.conversation-avatar {
|
||||
position: relative;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.conversation-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.conversation-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.conversation-title strong {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.conversation-subtitle {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.conversation-last-message {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.conversation-time {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 右侧聊天窗口 */
|
||||
.chat-window {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chat-header-info h3 {
|
||||
margin: 0 0 5px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chat-header-info p {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.chat-header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.message-item.message-sent {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.message-avatar {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
.message-sent .message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
word-break: break-word;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.message-sent .message-bubble {
|
||||
background-color: #2d8cf0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.interview-invitation {
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background-color: #fff9e6;
|
||||
border-left: 3px solid #ff9900;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.empty-messages {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
padding: 15px 20px;
|
||||
border-top: 1px solid #e8eaec;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.chat-input-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.chat-placeholder {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.chat-placeholder p {
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.conversation-items::-webkit-scrollbar,
|
||||
.chat-messages::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.conversation-items::-webkit-scrollbar-thumb,
|
||||
.chat-messages::-webkit-scrollbar-thumb {
|
||||
background-color: #dcdee2;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.conversation-items::-webkit-scrollbar-track,
|
||||
.chat-messages::-webkit-scrollbar-track {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
</style>
|
||||
330
admin/src/views/chat/chat_records.vue
Normal file
330
admin/src/views/chat/chat_records.vue
Normal file
@@ -0,0 +1,330 @@
|
||||
<template>
|
||||
<div class="content-view">
|
||||
<div class="table-head-tool">
|
||||
<Button type="primary" @click="showAddWarp">新增聊天记录</Button>
|
||||
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
|
||||
<FormItem :label-width="20" class="flex">
|
||||
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
|
||||
:placeholder="seachTypePlaceholder">
|
||||
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
|
||||
</Select>
|
||||
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
|
||||
placeholder="请输入关键字" @on-search="query(1)" />
|
||||
</FormItem>
|
||||
<FormItem label="平台">
|
||||
<Select v-model="gridOption.param.seachOption.platform" style="width: 120px" clearable @on-change="query(1)">
|
||||
<Option value="boss">Boss直聘</Option>
|
||||
<Option value="liepin">猎聘</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="消息类型">
|
||||
<Select v-model="gridOption.param.seachOption.messageType" style="width: 120px" clearable @on-change="query(1)">
|
||||
<Option value="sent">发送</Option>
|
||||
<Option value="received">接收</Option>
|
||||
<Option value="system">系统</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Button type="primary" @click="query(1)">查询</Button>
|
||||
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
|
||||
<Button type="default" @click="exportCsv">导出</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
<div class="table-body">
|
||||
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
|
||||
@changePage="query"></tables>
|
||||
</div>
|
||||
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"></editModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import chatRecordsServer from '@/api/operation/chat_records_server.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
let rules = {}
|
||||
rules["sn_code"] = [{ required: true, message: '请填写设备SN码', trigger: 'blur' }]
|
||||
rules["platform"] = [{ required: true, message: '请选择平台', trigger: 'change' }]
|
||||
rules["messageContent"] = [{ required: true, message: '请填写消息内容', trigger: 'blur' }]
|
||||
|
||||
return {
|
||||
replyContent: '',
|
||||
seachTypes: [
|
||||
{ key: 'companyName', value: '公司名称' },
|
||||
{ key: 'contactName', value: '联系人' },
|
||||
{ key: 'sn_code', value: '设备SN码' }
|
||||
],
|
||||
gridOption: {
|
||||
param: {
|
||||
seachOption: {
|
||||
key: 'companyName',
|
||||
value: '',
|
||||
platform: null,
|
||||
messageType: null
|
||||
},
|
||||
pageOption: {
|
||||
page: 1,
|
||||
pageSize: 20
|
||||
}
|
||||
},
|
||||
data: [],
|
||||
rules: rules
|
||||
},
|
||||
listColumns: [
|
||||
{ title: 'ID', key: 'id', minWidth: 80 },
|
||||
{ title: '设备SN码', key: 'sn_code', minWidth: 120 },
|
||||
{
|
||||
title: '平台',
|
||||
key: 'platform',
|
||||
minWidth: 100,
|
||||
render: (h, params) => {
|
||||
const platformMap = {
|
||||
'boss': { text: 'Boss直聘', color: 'blue' },
|
||||
'liepin': { text: '猎聘', color: 'green' }
|
||||
}
|
||||
const platform = platformMap[params.row.platform] || { text: params.row.platform, color: 'default' }
|
||||
return h('Tag', { props: { color: platform.color } }, platform.text)
|
||||
}
|
||||
},
|
||||
{ title: '岗位名称', key: 'jobTitle', minWidth: 150 },
|
||||
{ title: '公司名称', key: 'companyName', minWidth: 150 },
|
||||
{ title: '联系人', key: 'contactName', minWidth: 120 },
|
||||
{
|
||||
title: '消息类型',
|
||||
key: 'messageType',
|
||||
minWidth: 100,
|
||||
render: (h, params) => {
|
||||
const typeMap = {
|
||||
'sent': { text: '发送', color: 'blue' },
|
||||
'received': { text: '接收', color: 'success' },
|
||||
'system': { text: '系统', color: 'default' }
|
||||
}
|
||||
const type = typeMap[params.row.messageType] || { text: params.row.messageType, color: 'default' }
|
||||
return h('Tag', { props: { color: type.color } }, type.text)
|
||||
}
|
||||
},
|
||||
{ title: '消息内容', key: 'messageContent', minWidth: 250 },
|
||||
{ title: '发送时间', key: 'sendTime', minWidth: 150 },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 300,
|
||||
type: 'template',
|
||||
render: (h, params) => {
|
||||
let btns = [
|
||||
{
|
||||
title: '查看详情',
|
||||
type: 'info',
|
||||
click: () => {
|
||||
this.showChatDetail(params.row)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '回复',
|
||||
type: 'success',
|
||||
click: () => {
|
||||
this.replyMessage(params.row)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '编辑',
|
||||
type: 'primary',
|
||||
click: () => {
|
||||
this.showEditWarp(params.row)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '删除',
|
||||
type: 'error',
|
||||
click: () => {
|
||||
this.delConfirm(params.row)
|
||||
},
|
||||
},
|
||||
]
|
||||
return window.framework.uiTool.getBtn(h, btns)
|
||||
},
|
||||
}
|
||||
],
|
||||
editColumns: [
|
||||
{ title: '设备SN码', key: 'sn_code', type: 'text', required: true },
|
||||
{ title: '平台', key: 'platform', type: 'select', required: true, options: [
|
||||
{ value: 'boss', label: 'Boss直聘' },
|
||||
{ value: 'liepin', label: '猎聘' }
|
||||
]},
|
||||
{ title: '投递记录ID', key: 'applyId', type: 'text' },
|
||||
{ title: '岗位名称', key: 'jobTitle', type: 'text' },
|
||||
{ title: '公司名称', key: 'companyName', type: 'text' },
|
||||
{ title: '联系人', key: 'contactName', type: 'text' },
|
||||
{ title: '消息类型', key: 'messageType', type: 'select', options: [
|
||||
{ value: 'sent', label: '发送' },
|
||||
{ value: 'received', label: '接收' },
|
||||
{ value: 'system', label: '系统' }
|
||||
]},
|
||||
{ title: '消息内容', key: 'messageContent', type: 'textarea', required: true },
|
||||
{ title: '是否已读', key: 'isRead', type: 'switch' }
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.query(1)
|
||||
},
|
||||
methods: {
|
||||
query(page) {
|
||||
this.gridOption.param.pageOption.page = page
|
||||
chatRecordsServer.page(this.gridOption.param).then(res => {
|
||||
this.gridOption.data = res.data.rows
|
||||
this.gridOption.param.pageOption.total = res.data.count
|
||||
})
|
||||
},
|
||||
showAddWarp() {
|
||||
this.$refs.editModal.showModal()
|
||||
},
|
||||
showEditWarp(row) {
|
||||
this.$refs.editModal.showModal(row)
|
||||
},
|
||||
delConfirm(row) {
|
||||
window.framework.uiTool.delConfirm(async () => {
|
||||
await chatRecordsServer.del(row)
|
||||
this.$Message.success('删除成功!')
|
||||
this.query(1)
|
||||
})
|
||||
},
|
||||
exportCsv() {
|
||||
chatRecordsServer.exportCsv(this.gridOption.param).then(res => {
|
||||
window.framework.funTool.downloadFile(res, '聊天记录.csv')
|
||||
})
|
||||
},
|
||||
resetQuery() {
|
||||
this.gridOption.param.seachOption = {
|
||||
key: 'companyName',
|
||||
value: '',
|
||||
platform: null,
|
||||
messageType: null
|
||||
}
|
||||
this.query(1)
|
||||
},
|
||||
showChatDetail(row) {
|
||||
this.$Modal.info({
|
||||
title: '聊天记录详情',
|
||||
width: 800,
|
||||
render: (h) => {
|
||||
return h('div', { style: { maxHeight: '500px', overflowY: 'auto' } }, [
|
||||
h('h3', { style: { marginBottom: '15px', color: '#2d8cf0' } }, '基本信息'),
|
||||
h('p', [h('strong', '设备SN码: '), row.sn_code || '未知']),
|
||||
h('p', [h('strong', '平台: '), row.platform === 'boss' ? 'Boss直聘' : row.platform === 'liepin' ? '猎聘' : row.platform]),
|
||||
h('p', [h('strong', '岗位名称: '), row.jobTitle || '未知']),
|
||||
h('p', [h('strong', '公司名称: '), row.companyName || '未知']),
|
||||
h('p', [h('strong', '联系人: '), row.contactName || '未知']),
|
||||
|
||||
h('h3', { style: { marginTop: '20px', marginBottom: '15px', color: '#2d8cf0' } }, '消息信息'),
|
||||
h('p', [h('strong', '消息类型: '), this.getMessageTypeText(row.messageType)]),
|
||||
h('p', [h('strong', '发送时间: '), row.sendTime || '未知']),
|
||||
h('p', [h('strong', '是否已读: '), row.isRead ? '是' : '否']),
|
||||
|
||||
h('div', { style: { marginTop: '20px' } }, [
|
||||
h('strong', '消息内容:'),
|
||||
h('div', {
|
||||
style: {
|
||||
whiteSpace: 'pre-wrap',
|
||||
marginTop: '10px',
|
||||
padding: '15px',
|
||||
backgroundColor: '#f5f5f5',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #e8e8e8'
|
||||
}
|
||||
}, row.messageContent)
|
||||
])
|
||||
])
|
||||
}
|
||||
})
|
||||
},
|
||||
replyMessage(row) {
|
||||
this.$Modal.confirm({
|
||||
title: '回复消息',
|
||||
render: (h) => {
|
||||
return h('div', [
|
||||
h('p', { style: { marginBottom: '10px' } }, [
|
||||
h('strong', '回复给: '),
|
||||
`${row.companyName} - ${row.contactName || 'HR'}`
|
||||
]),
|
||||
h('p', { style: { marginBottom: '10px' } }, [
|
||||
h('strong', '原消息: '),
|
||||
row.messageContent
|
||||
]),
|
||||
h('Input', {
|
||||
props: {
|
||||
type: 'textarea',
|
||||
rows: 4,
|
||||
placeholder: '请输入回复内容'
|
||||
},
|
||||
on: {
|
||||
input: (val) => {
|
||||
this.replyContent = val
|
||||
}
|
||||
}
|
||||
})
|
||||
])
|
||||
},
|
||||
onOk: async () => {
|
||||
if (!this.replyContent) {
|
||||
this.$Message.warning('请输入回复内容')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const loading = this.$Message.loading({
|
||||
content: '正在发送消息...',
|
||||
duration: 0
|
||||
})
|
||||
|
||||
const sn_code = row.sn_code || localStorage.getItem('current_sn_code') || 'GHJU'
|
||||
|
||||
await chatRecordsServer.sendMessage({
|
||||
sn_code: sn_code,
|
||||
jobId: row.jobId,
|
||||
content: this.replyContent,
|
||||
chatType: 'reply',
|
||||
platform: row.platform
|
||||
})
|
||||
|
||||
loading()
|
||||
this.$Message.success('消息发送成功!')
|
||||
this.replyContent = ''
|
||||
this.query(this.gridOption.param.pageOption.page)
|
||||
} catch (error) {
|
||||
this.$Message.error('消息发送失败:' + (error.message || '未知错误'))
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
getMessageTypeText(type) {
|
||||
const typeMap = {
|
||||
'sent': '发送',
|
||||
'received': '接收',
|
||||
'system': '系统'
|
||||
}
|
||||
return typeMap[type] || type
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
seachTypePlaceholder() {
|
||||
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key)
|
||||
return selected ? selected.value : '请选择搜索类型'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ml10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user