feat: 添加员工门户项目及相关后端改造
- 新增 hzhub-portal-employee 员工门户前端项目(基于 Vue3 + Element Plus) - 后端登录接口增加返回 nickName 字段 - 移除 KnowledgeInfoController 的 @SaCheckPermission 注解 - 删除 hzhub-portal-company 旧门户项目 - 更新项目文档和架构说明 - 添加后台运行管理脚本(start-all.sh / status-all.sh / stop-all.sh) - 更新 docker-compose 配置 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
<!-- ChatWidget AI对话组件 -->
|
||||
<script setup lang="ts">
|
||||
import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard';
|
||||
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>
|
||||
Reference in New Issue
Block a user