Files
hzhub/hzhub-portal-employee/src/pages/dashboard/components/ChatWidget.vue
大壮 226f119607 feat: 完善员工门户功能及ERP集成
主要修改:
- 完善员工门户CRM模块(经销商、线索管理)
- 添加ERP客户选择器集成
- 优化登录认证和租户选择
- 添加超时配置、企业微信集成等文档
- 更新docker-compose配置
- 将.pid临时文件加入gitignore

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:46:54 +00:00

252 lines
6.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- ChatWidget AI对话组件 -->
<script setup lang="ts">
import { Sender } from 'vue-element-plus-x';
import { getKnowledgeList } from '@/api/chat';
import ModelSelect from '@/components/ModelSelect/index.vue';
import { useUserStore } from '@/stores';
import { useChatStore } from '@/stores/modules/chat';
import { useFilesStore } from '@/stores/modules/files';
import { useSessionStore } from '@/stores/modules/session';
const userStore = useUserStore();
const sessionStore = useSessionStore();
const filesStore = useFilesStore();
const chatStore = useChatStore();
const senderValue = ref('');
const senderRef = ref<InstanceType<typeof Sender> | null>(null);
const isWidgetExpanded = ref(true);
// 知识库列表
const knowledgeList = ref<any[]>([]);
const selectedKnowledgeId = ref<string>('');
const selectedKnowledgeName = ref<string>('知识库');
// 推理开关
const isReasoningEnabled = ref(false);
// 当前会话ID使用 computed 避免 v-model 绑定可选链)
const currentSessionId = computed({
get: () => sessionStore.currentSession?.id || '',
set: (value: string) => {
const session = sessionStore.sessionList.find(s => s.id === value);
if (session) {
sessionStore.setCurrentSession(session);
}
},
});
// 加载知识库列表
async function loadKnowledgeList() {
try {
const response = await getKnowledgeList();
if (response?.rows && Array.isArray(response.rows)) {
knowledgeList.value = response.rows.map((item: any) => ({
id: item.id,
name: item.name,
icon: 'Document',
}));
}
}
catch {
// 静默失败,知识库列表为空即可,不影响其他功能
}
}
// 选择知识库
function insertKnowledgeTag(knowledgeId: string) {
const knowledge = knowledgeList.value.find(k => k.id === knowledgeId);
if (knowledge) {
selectedKnowledgeId.value = knowledgeId;
selectedKnowledgeName.value = knowledge.name;
chatStore.setKnowledgeId(knowledgeId);
}
}
// 发送消息
async function handleSend() {
const messageContent = senderValue.value;
senderValue.value = '';
await sessionStore.createSessionList({
userId: userStore.userInfo?.userId as number,
sessionContent: messageContent,
sessionTitle: messageContent.slice(0, 10),
remark: messageContent.slice(0, 10),
});
}
// 切换展开状态
function toggleWidget() {
isWidgetExpanded.value = !isWidgetExpanded.value;
}
onMounted(() => {
loadKnowledgeList();
});
</script>
<template>
<div class="chat-widget-card" :class="{ 'widget-collapsed': !isWidgetExpanded }">
<div class="card-header">
<div class="header-left">
<el-icon class="header-icon" color="#1d5af3">
<ChatLineRound />
</el-icon>
<h3 class="card-title">
AI 对话助手
</h3>
</div>
<el-button
class="toggle-btn"
:icon="isWidgetExpanded ? 'ArrowDown' : 'ArrowUp'"
size="small"
circle
@click="toggleWidget"
/>
</div>
<div v-if="isWidgetExpanded" class="card-body">
<!-- 会话选择器 -->
<div class="session-selector">
<el-select
v-model="currentSessionId"
placeholder="选择会话"
size="small"
style="width: 100%"
>
<el-option
v-for="session in sessionStore.sessionList"
:key="session.id"
:label="session.sessionTitle"
:value="session.id"
/>
</el-select>
</div>
<!-- 消息输入区 -->
<div class="message-input-wrapper">
<Sender
ref="senderRef"
v-model="senderValue"
class="chat-widget-sender"
:auto-size="{ maxRows: 4, minRows: 2 }"
variant="updown"
clearable
allow-speech
@submit="handleSend"
>
<template #header>
<div class="sender-header">
<ModelSelect />
<el-dropdown trigger="click" @command="insertKnowledgeTag">
<el-button size="small" type="primary" plain>
<el-icon><FolderOpened /></el-icon>
{{ selectedKnowledgeName }}
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="item in knowledgeList"
:key="item.id"
:command="item.id"
>
{{ item.name }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</Sender>
</div>
<!-- 提示信息 -->
<div class="widget-footer">
<el-button size="small" text @click="$router.push('/chat')">
<el-icon><FullScreen /></el-icon>
打开完整对话界面
</el-button>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.chat-widget-card {
background-color: #ffffff;
border-radius: var(--radius-lg);
padding: 20px;
border: 1px solid var(--color-border);
transition: all var(--transition);
.card-header {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.header-left {
display: flex;
gap: 12px;
align-items: center;
.header-icon {
width: 24px;
height: 24px;
font-size: 24px;
}
.card-title {
font-size: 16px;
font-weight: 600;
color: var(--color-text-primary);
margin: 0;
}
}
.toggle-btn {
flex-shrink: 0;
}
}
.card-body {
display: flex;
flex-direction: column;
gap: 12px;
.session-selector {
margin-bottom: 8px;
}
.message-input-wrapper {
.chat-widget-sender {
:deep(.el-textarea__inner) {
font-size: 14px;
}
}
.sender-header {
display: flex;
gap: 8px;
align-items: center;
padding: 8px;
}
}
.widget-footer {
display: flex;
justify-content: center;
padding-top: 8px;
border-top: 1px solid var(--color-border-light);
}
}
&.widget-collapsed {
.card-body {
display: none;
}
}
}
</style>