1361 lines
35 KiB
Markdown
1361 lines
35 KiB
Markdown
|
|
# 运维管理系统前端开发API文档
|
|||
|
|
|
|||
|
|
## 📖 文档概述
|
|||
|
|
|
|||
|
|
本文档为前端开发团队提供完整的API使用指南,包含运维工单管理和AI智能回答功能的所有接口说明、数据结构定义和开发建议。
|
|||
|
|
|
|||
|
|
## 🌐 基础信息
|
|||
|
|
|
|||
|
|
### 服务器配置
|
|||
|
|
- **开发环境**: http://localhost:8080
|
|||
|
|
- **API文档**: http://localhost:8080/doc.html
|
|||
|
|
- **数据库监控**: http://localhost:8080/druid (admin/123456)
|
|||
|
|
|
|||
|
|
### 认证机制
|
|||
|
|
- **认证方式**: JWT Token + Apache Shiro
|
|||
|
|
- **Token有效期**: 10小时(后端Token)
|
|||
|
|
- **加密方式**: RSA加密 + SM3哈希
|
|||
|
|
|
|||
|
|
### 请求/响应格式
|
|||
|
|
```javascript
|
|||
|
|
// 统一请求格式
|
|||
|
|
{
|
|||
|
|
"headers": {
|
|||
|
|
"Content-Type": "application/json",
|
|||
|
|
"Authorization": "Bearer <JWT_TOKEN>"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 统一响应格式
|
|||
|
|
{
|
|||
|
|
"code": 200, // 状态码: 200=成功
|
|||
|
|
"message": "操作成功", // 消息说明
|
|||
|
|
"data": {}, // 数据载荷
|
|||
|
|
"timestamp": 1692000000 // 时间戳
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 错误响应格式
|
|||
|
|
{
|
|||
|
|
"code": 500,
|
|||
|
|
"message": "系统内部错误: 具体错误信息",
|
|||
|
|
"data": null
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🏗️ 核心API模块
|
|||
|
|
|
|||
|
|
### 1. 用户认证模块 (UserController)
|
|||
|
|
|
|||
|
|
#### 1.1 用户登录
|
|||
|
|
```http
|
|||
|
|
POST /user/login
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"username": "用户名",
|
|||
|
|
"password": "加密后的密码"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Response:
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"token": "jwt_token_string",
|
|||
|
|
"userInfo": {
|
|||
|
|
"id": "用户ID",
|
|||
|
|
"username": "用户名",
|
|||
|
|
"nickname": "昵称",
|
|||
|
|
"org": "所属组织",
|
|||
|
|
"role": "角色类型"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2 获取用户信息
|
|||
|
|
```http
|
|||
|
|
GET /user/info
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
|
|||
|
|
Response:
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"id": "用户ID",
|
|||
|
|
"username": "用户名",
|
|||
|
|
"nickname": "昵称",
|
|||
|
|
"org": "所属组织",
|
|||
|
|
"mobile": "手机号",
|
|||
|
|
"email": "邮箱"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.3 用户退出
|
|||
|
|
```http
|
|||
|
|
POST /user/logout
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 工单管理模块 (RepairController)
|
|||
|
|
|
|||
|
|
#### 2.1 创建工单
|
|||
|
|
```http
|
|||
|
|
POST /repair
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"title": "工单标题",
|
|||
|
|
"faultDescription": "故障描述",
|
|||
|
|
"business": "业务模块",
|
|||
|
|
"priority": 2, // 1=紧急, 2=高, 3=中, 4=低
|
|||
|
|
"org": "所属组织",
|
|||
|
|
"source": "1", // 来源: 1=PC, 2=微信, 3=APP
|
|||
|
|
"contactPhone": "联系电话"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Response:
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": "YW202408140001" // 工单ID
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.2 工单列表查询
|
|||
|
|
```http
|
|||
|
|
POST /repair/list
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"pageNum": 1,
|
|||
|
|
"pageSize": 20,
|
|||
|
|
"title": "搜索关键词",
|
|||
|
|
"business": "业务模块",
|
|||
|
|
"priority": 2,
|
|||
|
|
"dateStart": "2024-08-01",
|
|||
|
|
"dateEnd": "2024-08-14"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Response:
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"records": [
|
|||
|
|
{
|
|||
|
|
"repairId": "YW202408140001",
|
|||
|
|
"title": "工单标题",
|
|||
|
|
"faultDescription": "故障描述",
|
|||
|
|
"business": "业务模块",
|
|||
|
|
"priority": 2,
|
|||
|
|
"statusName": "状态名称",
|
|||
|
|
"createTime": "2024-08-14T10:30:00",
|
|||
|
|
"username": "提交人",
|
|||
|
|
"nickname": "提交人昵称"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"total": 100,
|
|||
|
|
"size": 20,
|
|||
|
|
"current": 1
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.3 工单详情
|
|||
|
|
```http
|
|||
|
|
GET /repair/detail?repairId=YW202408140001
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
|
|||
|
|
Response:
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"repairId": "YW202408140001",
|
|||
|
|
"title": "工单标题",
|
|||
|
|
"faultDescription": "故障描述",
|
|||
|
|
"business": "业务模块",
|
|||
|
|
"priority": 2,
|
|||
|
|
"createTime": "2024-08-14T10:30:00",
|
|||
|
|
"handles": [
|
|||
|
|
{
|
|||
|
|
"step": "submit",
|
|||
|
|
"stepName": "提交",
|
|||
|
|
"result": "工单已提交",
|
|||
|
|
"happenTime": "2024-08-14T10:30:00",
|
|||
|
|
"username": "操作人"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"files": [
|
|||
|
|
{
|
|||
|
|
"fileName": "截图.png",
|
|||
|
|
"fileUrl": "/upload/files/xxx.png"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.4 工单处理
|
|||
|
|
```http
|
|||
|
|
POST /repair/handle/next
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"repairId": "YW202408140001",
|
|||
|
|
"result": "处理结果",
|
|||
|
|
"nextStep": "resolve", // 下一步状态
|
|||
|
|
"assignTo": "分配给谁" // 可选
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. AI智能回答模块 (流式输出,已实现)
|
|||
|
|
|
|||
|
|
#### 3.1 流式生成AI回答 (SSE)
|
|||
|
|
```http
|
|||
|
|
GET /api/ai/answer/stream/{repairId}
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Accept: text/event-stream
|
|||
|
|
|
|||
|
|
Stream Response (Server-Sent Events):
|
|||
|
|
event: progress
|
|||
|
|
data: {"stage": "analyzing", "message": "正在分析工单内容...", "progress": 20}
|
|||
|
|
|
|||
|
|
event: progress
|
|||
|
|
data: {"stage": "searching", "message": "搜索相似案例...", "progress": 40}
|
|||
|
|
|
|||
|
|
event: progress
|
|||
|
|
data: {"stage": "generating", "message": "生成AI回答...", "progress": 60}
|
|||
|
|
|
|||
|
|
event: chunk
|
|||
|
|
data: {"text": "## 问题分析\n根据您描述的", "isComplete": false}
|
|||
|
|
|
|||
|
|
event: chunk
|
|||
|
|
data: {"text": "网络连接问题,可能的原因包括:", "isComplete": false}
|
|||
|
|
|
|||
|
|
event: complete
|
|||
|
|
data: {
|
|||
|
|
"answerId": "answer_20241016001",
|
|||
|
|
"repairId": "YW202408140001",
|
|||
|
|
"fullAnswer": "## 问题分析\n根据您描述的网络连接问题...",
|
|||
|
|
"confidenceScore": 0.85,
|
|||
|
|
"generateTime": "2024-08-14T10:35:00",
|
|||
|
|
"processingTimeMs": 3250,
|
|||
|
|
"usedMcpTools": "knowledge_search,similar_cases",
|
|||
|
|
"isComplete": true
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2 非流式生成AI回答
|
|||
|
|
```http
|
|||
|
|
POST /api/ai/answer/generate
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"repairId": "YW202408140001",
|
|||
|
|
"streaming": false,
|
|||
|
|
"includeHistory": true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Response:
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"answerId": "answer_20241016001",
|
|||
|
|
"repairId": "YW202408140001",
|
|||
|
|
"answer": "## 问题分析\n根据您描述的网络连接问题...",
|
|||
|
|
"confidenceScore": 0.85,
|
|||
|
|
"generateTime": "2024-08-14T10:35:00",
|
|||
|
|
"processingTimeMs": 1250,
|
|||
|
|
"usedMcpTools": "knowledge_search,similar_cases"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.3 流式聊天对话 (SSE)
|
|||
|
|
```http
|
|||
|
|
POST /api/ai/chat/stream
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Content-Type: application/json
|
|||
|
|
Accept: text/event-stream
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"message": "如何解决网络连接问题?",
|
|||
|
|
"sessionId": "chat_session_001", // 可选,用于多轮对话
|
|||
|
|
"context": {
|
|||
|
|
"repairId": "YW202408140001", // 可选,关联工单
|
|||
|
|
"previousMessages": [] // 可选,历史对话
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Stream Response:
|
|||
|
|
event: start
|
|||
|
|
data: {"sessionId": "chat_session_001", "timestamp": "2024-08-14T10:35:00"}
|
|||
|
|
|
|||
|
|
event: chunk
|
|||
|
|
data: {"text": "根据您的描述,网络连接问题", "index": 0}
|
|||
|
|
|
|||
|
|
event: chunk
|
|||
|
|
data: {"text": "通常由以下几个方面引起:", "index": 1}
|
|||
|
|
|
|||
|
|
event: complete
|
|||
|
|
data: {
|
|||
|
|
"sessionId": "chat_session_001",
|
|||
|
|
"fullResponse": "根据您的描述,网络连接问题通常由以下几个方面引起:...",
|
|||
|
|
"tokenCount": 245,
|
|||
|
|
"responseTime": 2100,
|
|||
|
|
"isComplete": true
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.4 中断流式生成
|
|||
|
|
```http
|
|||
|
|
POST /api/ai/chat/stream/{sessionId}/stop
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
|
|||
|
|
Response:
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"sessionId": "chat_session_001",
|
|||
|
|
"status": "stopped",
|
|||
|
|
"partialResponse": "已生成的部分内容...",
|
|||
|
|
"stopTime": "2024-08-14T10:35:30"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.5 获取AI回答历史
|
|||
|
|
```http
|
|||
|
|
GET /api/ai/answer/history/{repairId}
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
|
|||
|
|
Response:
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": [
|
|||
|
|
{
|
|||
|
|
"answerId": "answer_20241016001",
|
|||
|
|
"question": "用户提出的问题",
|
|||
|
|
"answer": "AI生成的回答",
|
|||
|
|
"confidenceScore": 0.85,
|
|||
|
|
"status": "generated", // generated, accepted, rejected
|
|||
|
|
"generateTime": "2024-08-14T10:35:00",
|
|||
|
|
"isStreaming": true,
|
|||
|
|
"responseTime": 3250
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.6 用户反馈AI回答
|
|||
|
|
```http
|
|||
|
|
POST /api/ai/answer/feedback
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"answerId": "answer_20241016001",
|
|||
|
|
"feedbackType": "accept", // accept, reject, escalate
|
|||
|
|
"userRating": 4, // 1-5分评价
|
|||
|
|
"userComment": "回答很有帮助",
|
|||
|
|
"improvement": "建议添加更详细的步骤"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 工单待办模块 (RepairTodoController)
|
|||
|
|
|
|||
|
|
#### 4.1 我的待办工单
|
|||
|
|
```http
|
|||
|
|
POST /repairTodo/list
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"pageNum": 1,
|
|||
|
|
"pageSize": 20,
|
|||
|
|
"step": "declare", // 可选: 按步骤筛选
|
|||
|
|
"urgent": true // 可选: 只看紧急工单
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Response:
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"records": [
|
|||
|
|
{
|
|||
|
|
"repairId": "YW202408140001",
|
|||
|
|
"title": "工单标题",
|
|||
|
|
"step": "declare",
|
|||
|
|
"stepName": "待分派",
|
|||
|
|
"priority": 2,
|
|||
|
|
"createTime": "2024-08-14T10:30:00",
|
|||
|
|
"deadline": "2024-08-15T18:00:00"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"total": 10
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. 统计分析模块 (StatisticController)
|
|||
|
|
|
|||
|
|
#### 5.1 工单统计概览
|
|||
|
|
```http
|
|||
|
|
GET /statistic/overview
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
|
|||
|
|
Response:
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"totalRepairs": 1250,
|
|||
|
|
"pendingRepairs": 45,
|
|||
|
|
"resolvedToday": 28,
|
|||
|
|
"avgResolutionTime": 4.2, // 小时
|
|||
|
|
"monthlyStats": [
|
|||
|
|
{
|
|||
|
|
"month": "2024-08",
|
|||
|
|
"submitted": 156,
|
|||
|
|
"resolved": 142,
|
|||
|
|
"resolutionRate": 91.0
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🎨 前端页面开发指南
|
|||
|
|
|
|||
|
|
### 1. 工单列表页面
|
|||
|
|
|
|||
|
|
#### 核心功能
|
|||
|
|
- 工单列表展示(支持分页、筛选、排序)
|
|||
|
|
- 工单状态标识(颜色区分优先级)
|
|||
|
|
- 快速操作按钮(查看详情、处理、分派)
|
|||
|
|
- 实时状态更新(WebSocket推送)
|
|||
|
|
|
|||
|
|
#### 关键组件
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<div class="repair-list">
|
|||
|
|
<!-- 搜索筛选区 -->
|
|||
|
|
<div class="filter-section">
|
|||
|
|
<el-form inline>
|
|||
|
|
<el-form-item label="关键词">
|
|||
|
|
<el-input v-model="searchForm.title" placeholder="工单标题或ID" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="业务模块">
|
|||
|
|
<el-select v-model="searchForm.business">
|
|||
|
|
<el-option label="全部" value="" />
|
|||
|
|
<el-option label="财务系统" value="财务系统" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="优先级">
|
|||
|
|
<el-select v-model="searchForm.priority">
|
|||
|
|
<el-option label="全部" value="" />
|
|||
|
|
<el-option label="紧急" :value="1" />
|
|||
|
|
<el-option label="高" :value="2" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-button type="primary" @click="searchRepairs">搜索</el-button>
|
|||
|
|
</el-form>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 工单列表 -->
|
|||
|
|
<div class="repair-table">
|
|||
|
|
<el-table :data="repairList" v-loading="loading">
|
|||
|
|
<el-table-column prop="repairId" label="工单ID" width="140" />
|
|||
|
|
<el-table-column prop="title" label="标题" min-width="200" />
|
|||
|
|
<el-table-column prop="business" label="业务模块" width="120" />
|
|||
|
|
<el-table-column prop="priority" label="优先级" width="80">
|
|||
|
|
<template #default="{ row }">
|
|||
|
|
<el-tag :type="getPriorityType(row.priority)">
|
|||
|
|
{{ getPriorityText(row.priority) }}
|
|||
|
|
</el-tag>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column prop="statusName" label="状态" width="100" />
|
|||
|
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
|||
|
|
<el-table-column label="操作" width="200" fixed="right">
|
|||
|
|
<template #default="{ row }">
|
|||
|
|
<el-button size="small" @click="viewDetail(row.repairId)">
|
|||
|
|
详情
|
|||
|
|
</el-button>
|
|||
|
|
<el-button size="small" type="primary"
|
|||
|
|
@click="handleRepair(row.repairId)"
|
|||
|
|
v-if="canHandle(row)">
|
|||
|
|
处理
|
|||
|
|
</el-button>
|
|||
|
|
<!-- AI回答按钮 (AI功能启用后) -->
|
|||
|
|
<el-dropdown v-if="needsAIAnswer(row)" trigger="click">
|
|||
|
|
<el-button size="small" type="success">
|
|||
|
|
AI回答 <el-icon><ArrowDown /></el-icon>
|
|||
|
|
</el-button>
|
|||
|
|
<template #dropdown>
|
|||
|
|
<el-dropdown-menu>
|
|||
|
|
<el-dropdown-item @click="generateAIAnswer(row.repairId, true)">
|
|||
|
|
流式生成
|
|||
|
|
</el-dropdown-item>
|
|||
|
|
<el-dropdown-item @click="generateAIAnswer(row.repairId, false)">
|
|||
|
|
标准生成
|
|||
|
|
</el-dropdown-item>
|
|||
|
|
</el-dropdown-menu>
|
|||
|
|
</template>
|
|||
|
|
</el-dropdown>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
</el-table>
|
|||
|
|
|
|||
|
|
<el-pagination
|
|||
|
|
v-model:current-page="pagination.current"
|
|||
|
|
v-model:page-size="pagination.size"
|
|||
|
|
:total="pagination.total"
|
|||
|
|
@size-change="handleSizeChange"
|
|||
|
|
@current-change="handleCurrentChange" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ref, onMounted } from 'vue'
|
|||
|
|
import { ElMessage } from 'element-plus'
|
|||
|
|
import { repairAPI } from '@/api/repair'
|
|||
|
|
|
|||
|
|
const loading = ref(false)
|
|||
|
|
const repairList = ref([])
|
|||
|
|
const searchForm = ref({
|
|||
|
|
title: '',
|
|||
|
|
business: '',
|
|||
|
|
priority: ''
|
|||
|
|
})
|
|||
|
|
const pagination = ref({
|
|||
|
|
current: 1,
|
|||
|
|
size: 20,
|
|||
|
|
total: 0
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 搜索工单
|
|||
|
|
const searchRepairs = async () => {
|
|||
|
|
loading.value = true
|
|||
|
|
try {
|
|||
|
|
const params = {
|
|||
|
|
pageNum: pagination.value.current,
|
|||
|
|
pageSize: pagination.value.size,
|
|||
|
|
...searchForm.value
|
|||
|
|
}
|
|||
|
|
const response = await repairAPI.getRepairList(params)
|
|||
|
|
repairList.value = response.data.records
|
|||
|
|
pagination.value.total = response.data.total
|
|||
|
|
} catch (error) {
|
|||
|
|
ElMessage.error('获取工单列表失败')
|
|||
|
|
} finally {
|
|||
|
|
loading.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查看详情
|
|||
|
|
const viewDetail = (repairId) => {
|
|||
|
|
router.push(`/repair/detail/${repairId}`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成AI回答 (支持流式和非流式)
|
|||
|
|
const generateAIAnswer = async (repairId, streaming = true) => {
|
|||
|
|
try {
|
|||
|
|
if (streaming) {
|
|||
|
|
ElMessage.info('正在启动AI流式回答生成...')
|
|||
|
|
// 跳转到AI回答页面并启动流式生成
|
|||
|
|
router.push(`/repair/ai-answer/${repairId}?streaming=true`)
|
|||
|
|
} else {
|
|||
|
|
ElMessage.info('AI正在分析问题,请稍候...')
|
|||
|
|
const response = await repairAPI.generateAIAnswer(repairId)
|
|||
|
|
ElMessage.success('AI回答生成成功')
|
|||
|
|
// 跳转到回答页面显示完整结果
|
|||
|
|
router.push(`/repair/ai-answer/${repairId}`)
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
ElMessage.error('AI回答生成失败: ' + error.message)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 优先级标识
|
|||
|
|
const getPriorityType = (priority) => {
|
|||
|
|
const types = { 1: 'danger', 2: 'warning', 3: 'info', 4: 'success' }
|
|||
|
|
return types[priority] || 'info'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const getPriorityText = (priority) => {
|
|||
|
|
const texts = { 1: '紧急', 2: '高', 3: '中', 4: '低' }
|
|||
|
|
return texts[priority] || '未知'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
searchRepairs()
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. AI智能回答页面 (支持流式输出)
|
|||
|
|
|
|||
|
|
#### 核心功能
|
|||
|
|
- 流式AI回答展示(实时显示生成过程)
|
|||
|
|
- 进度指示器(分析、搜索、生成阶段)
|
|||
|
|
- 用户反馈收集(满意度、评分、意见)
|
|||
|
|
- 相似案例推荐
|
|||
|
|
- 回答质量评估指标
|
|||
|
|
- 中断生成功能
|
|||
|
|
|
|||
|
|
#### 设计建议
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<div class="ai-answer-page">
|
|||
|
|
<!-- 工单信息概览 -->
|
|||
|
|
<div class="repair-summary">
|
|||
|
|
<h3>{{ repairInfo.title }}</h3>
|
|||
|
|
<p>{{ repairInfo.faultDescription }}</p>
|
|||
|
|
<div class="meta-info">
|
|||
|
|
<span>业务模块: {{ repairInfo.business }}</span>
|
|||
|
|
<span>优先级: {{ getPriorityText(repairInfo.priority) }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- AI回答内容(支持流式显示) -->
|
|||
|
|
<div class="ai-answer-content">
|
|||
|
|
<div class="answer-header">
|
|||
|
|
<h4>🤖 AI智能解答</h4>
|
|||
|
|
<div class="answer-controls">
|
|||
|
|
<div class="confidence-score" v-if="!isGenerating">
|
|||
|
|
<span>置信度: </span>
|
|||
|
|
<el-progress
|
|||
|
|
:percentage="aiAnswer.confidenceScore * 100"
|
|||
|
|
:color="getConfidenceColor(aiAnswer.confidenceScore)" />
|
|||
|
|
</div>
|
|||
|
|
<el-button
|
|||
|
|
v-if="isGenerating"
|
|||
|
|
type="danger"
|
|||
|
|
size="small"
|
|||
|
|
@click="stopGeneration">
|
|||
|
|
停止生成
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 生成进度指示器 -->
|
|||
|
|
<div class="generation-progress" v-if="isGenerating">
|
|||
|
|
<el-progress
|
|||
|
|
:percentage="generationProgress.progress"
|
|||
|
|
:status="generationProgress.status"
|
|||
|
|
striped
|
|||
|
|
striped-flow>
|
|||
|
|
<template #default="{ percentage }">
|
|||
|
|
<span class="progress-text">
|
|||
|
|
{{ generationProgress.message }} ({{ percentage }}%)
|
|||
|
|
</span>
|
|||
|
|
</template>
|
|||
|
|
</el-progress>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="answer-body">
|
|||
|
|
<!-- 流式显示的AI回答内容 -->
|
|||
|
|
<div class="markdown-content streaming" v-if="isGenerating">
|
|||
|
|
<div class="typing-indicator">
|
|||
|
|
{{ streamingContent }}
|
|||
|
|
<span class="cursor">|</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 完整的AI回答内容 -->
|
|||
|
|
<div class="markdown-content" v-else v-html="renderMarkdown(aiAnswer.answer)"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="answer-meta" v-if="!isGenerating">
|
|||
|
|
<span>生成时间: {{ formatTime(aiAnswer.generateTime) }}</span>
|
|||
|
|
<span>处理耗时: {{ aiAnswer.processingTimeMs }}ms</span>
|
|||
|
|
<span>使用工具: {{ aiAnswer.usedMcpTools }}</span>
|
|||
|
|
<span v-if="aiAnswer.isStreaming">流式生成: 是</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 用户反馈区 -->
|
|||
|
|
<div class="feedback-section">
|
|||
|
|
<h4>📝 回答评价</h4>
|
|||
|
|
<div class="feedback-actions">
|
|||
|
|
<el-button type="success" @click="submitFeedback('accept')">
|
|||
|
|
👍 回答有帮助
|
|||
|
|
</el-button>
|
|||
|
|
<el-button type="warning" @click="submitFeedback('reject')">
|
|||
|
|
👎 回答无效
|
|||
|
|
</el-button>
|
|||
|
|
<el-button type="danger" @click="submitFeedback('escalate')">
|
|||
|
|
🆘 需要人工处理
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 详细反馈表单 -->
|
|||
|
|
<div class="detailed-feedback" v-if="showFeedbackForm">
|
|||
|
|
<el-form :model="feedbackForm">
|
|||
|
|
<el-form-item label="评分">
|
|||
|
|
<el-rate v-model="feedbackForm.userRating" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="意见">
|
|||
|
|
<el-input type="textarea"
|
|||
|
|
v-model="feedbackForm.userComment"
|
|||
|
|
placeholder="请说明回答的有用程度或改进建议" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item>
|
|||
|
|
<el-button type="primary" @click="submitDetailedFeedback">
|
|||
|
|
提交反馈
|
|||
|
|
</el-button>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 相似案例推荐 -->
|
|||
|
|
<div class="similar-cases" v-if="similarCases.length > 0">
|
|||
|
|
<h4>📚 相似案例参考</h4>
|
|||
|
|
<div class="cases-list">
|
|||
|
|
<div v-for="case in similarCases" :key="case.repairId"
|
|||
|
|
class="case-item">
|
|||
|
|
<h5>{{ case.title }}</h5>
|
|||
|
|
<p>{{ case.solution }}</p>
|
|||
|
|
<div class="case-meta">
|
|||
|
|
<span>相似度: {{ (case.similarity * 100).toFixed(1) }}%</span>
|
|||
|
|
<span>解决时间: {{ case.resolveTime }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ref, onMounted } from 'vue'
|
|||
|
|
import { useRoute } from 'vue-router'
|
|||
|
|
import { repairAPI, aiAPI } from '@/api'
|
|||
|
|
import { marked } from 'marked'
|
|||
|
|
|
|||
|
|
const route = useRoute()
|
|||
|
|
const repairId = route.params.repairId
|
|||
|
|
|
|||
|
|
const repairInfo = ref({})
|
|||
|
|
const aiAnswer = ref({})
|
|||
|
|
const similarCases = ref([])
|
|||
|
|
const showFeedbackForm = ref(false)
|
|||
|
|
const feedbackForm = ref({
|
|||
|
|
userRating: 5,
|
|||
|
|
userComment: ''
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 流式生成相关状态
|
|||
|
|
const isGenerating = ref(false)
|
|||
|
|
const streamingContent = ref('')
|
|||
|
|
const generationProgress = ref({
|
|||
|
|
progress: 0,
|
|||
|
|
message: '准备开始...',
|
|||
|
|
status: 'active'
|
|||
|
|
})
|
|||
|
|
const eventSource = ref(null)
|
|||
|
|
const sessionId = ref(null)
|
|||
|
|
|
|||
|
|
// 启动流式AI回答生成
|
|||
|
|
const startStreamingGeneration = async () => {
|
|||
|
|
isGenerating.value = true
|
|||
|
|
streamingContent.value = ''
|
|||
|
|
generationProgress.value = { progress: 0, message: '连接AI服务...', status: 'active' }
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 建立SSE连接
|
|||
|
|
eventSource.value = new EventSource(
|
|||
|
|
`/api/ai/answer/stream/${repairId}`,
|
|||
|
|
{
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': `Bearer ${getToken()}`
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
eventSource.value.onmessage = (event) => {
|
|||
|
|
const data = JSON.parse(event.data)
|
|||
|
|
|
|||
|
|
switch (event.type) {
|
|||
|
|
case 'progress':
|
|||
|
|
generationProgress.value = {
|
|||
|
|
progress: data.progress,
|
|||
|
|
message: data.message,
|
|||
|
|
status: 'active'
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'chunk':
|
|||
|
|
streamingContent.value += data.text
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'complete':
|
|||
|
|
aiAnswer.value = data
|
|||
|
|
isGenerating.value = false
|
|||
|
|
eventSource.value.close()
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
eventSource.value.onerror = (error) => {
|
|||
|
|
console.error('流式生成出错:', error)
|
|||
|
|
isGenerating.value = false
|
|||
|
|
generationProgress.value.status = 'exception'
|
|||
|
|
ElMessage.error('AI回答生成失败')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('启动流式生成失败:', error)
|
|||
|
|
isGenerating.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 停止流式生成
|
|||
|
|
const stopGeneration = async () => {
|
|||
|
|
if (sessionId.value) {
|
|||
|
|
try {
|
|||
|
|
await aiAPI.stopGeneration(sessionId.value)
|
|||
|
|
ElMessage.info('已停止AI回答生成')
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('停止生成失败:', error)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (eventSource.value) {
|
|||
|
|
eventSource.value.close()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
isGenerating.value = false
|
|||
|
|
generationProgress.value.status = 'warning'
|
|||
|
|
generationProgress.value.message = '已停止生成'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取AI回答(非流式)
|
|||
|
|
const getAIAnswer = async () => {
|
|||
|
|
try {
|
|||
|
|
const response = await aiAPI.getAIAnswer(repairId)
|
|||
|
|
aiAnswer.value = response.data
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取AI回答失败:', error)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Markdown渲染
|
|||
|
|
const renderMarkdown = (content) => {
|
|||
|
|
return marked(content || '')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提交反馈
|
|||
|
|
const submitFeedback = (type) => {
|
|||
|
|
if (type === 'accept') {
|
|||
|
|
// 直接标记为满意
|
|||
|
|
submitDetailedFeedback({
|
|||
|
|
feedbackType: 'accept',
|
|||
|
|
userRating: 5,
|
|||
|
|
userComment: '回答有帮助'
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
// 显示详细反馈表单
|
|||
|
|
showFeedbackForm.value = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const submitDetailedFeedback = async (feedback = null) => {
|
|||
|
|
try {
|
|||
|
|
const params = feedback || {
|
|||
|
|
answerId: aiAnswer.value.answerId,
|
|||
|
|
feedbackType: 'detailed',
|
|||
|
|
...feedbackForm.value
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await aiAPI.submitFeedback(repairId, params)
|
|||
|
|
ElMessage.success('反馈提交成功')
|
|||
|
|
|
|||
|
|
// 根据反馈类型处理后续逻辑
|
|||
|
|
if (params.feedbackType === 'accept') {
|
|||
|
|
// 工单可能自动关闭
|
|||
|
|
ElMessage.info('工单已标记为已解决')
|
|||
|
|
} else if (params.feedbackType === 'escalate') {
|
|||
|
|
ElMessage.info('工单已转人工处理')
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
ElMessage.error('反馈提交失败')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
// 自动启动流式生成,或者加载已有回答
|
|||
|
|
if (route.query.streaming === 'true') {
|
|||
|
|
startStreamingGeneration()
|
|||
|
|
} else {
|
|||
|
|
getAIAnswer()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 组件卸载时清理资源
|
|||
|
|
onUnmounted(() => {
|
|||
|
|
if (eventSource.value) {
|
|||
|
|
eventSource.value.close()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔧 开发建议
|
|||
|
|
|
|||
|
|
### 1. 状态管理
|
|||
|
|
```javascript
|
|||
|
|
// Pinia Store for Repair Management
|
|||
|
|
import { defineStore } from 'pinia'
|
|||
|
|
|
|||
|
|
export const useRepairStore = defineStore('repair', {
|
|||
|
|
state: () => ({
|
|||
|
|
repairList: [],
|
|||
|
|
currentRepair: null,
|
|||
|
|
aiAnswers: {},
|
|||
|
|
loading: false
|
|||
|
|
}),
|
|||
|
|
|
|||
|
|
actions: {
|
|||
|
|
async fetchRepairList(params) {
|
|||
|
|
this.loading = true
|
|||
|
|
try {
|
|||
|
|
const response = await repairAPI.getRepairList(params)
|
|||
|
|
this.repairList = response.data.records
|
|||
|
|
return response.data
|
|||
|
|
} finally {
|
|||
|
|
this.loading = false
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async generateAIAnswer(repairId) {
|
|||
|
|
try {
|
|||
|
|
const response = await repairAPI.generateAIAnswer(repairId)
|
|||
|
|
this.aiAnswers[repairId] = response.data
|
|||
|
|
return response.data
|
|||
|
|
} catch (error) {
|
|||
|
|
throw error
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 错误处理
|
|||
|
|
```javascript
|
|||
|
|
// API错误统一处理
|
|||
|
|
import axios from 'axios'
|
|||
|
|
import { ElMessage } from 'element-plus'
|
|||
|
|
|
|||
|
|
const apiClient = axios.create({
|
|||
|
|
baseURL: '/api',
|
|||
|
|
timeout: 30000
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
apiClient.interceptors.response.use(
|
|||
|
|
response => {
|
|||
|
|
if (response.data.code !== 200) {
|
|||
|
|
ElMessage.error(response.data.message)
|
|||
|
|
throw new Error(response.data.message)
|
|||
|
|
}
|
|||
|
|
return response.data
|
|||
|
|
},
|
|||
|
|
error => {
|
|||
|
|
if (error.response?.status === 401) {
|
|||
|
|
// 跳转到登录页
|
|||
|
|
router.push('/login')
|
|||
|
|
} else {
|
|||
|
|
ElMessage.error(error.message || '网络请求失败')
|
|||
|
|
}
|
|||
|
|
return Promise.reject(error)
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. WebSocket实时通知
|
|||
|
|
```javascript
|
|||
|
|
// WebSocket连接管理
|
|||
|
|
class WebSocketManager {
|
|||
|
|
constructor() {
|
|||
|
|
this.ws = null
|
|||
|
|
this.reconnectCount = 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
connect(userId) {
|
|||
|
|
this.ws = new WebSocket(`ws://localhost:8080/websocket/${userId}`)
|
|||
|
|
|
|||
|
|
this.ws.onmessage = (event) => {
|
|||
|
|
const notification = JSON.parse(event.data)
|
|||
|
|
this.handleNotification(notification)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.ws.onclose = () => {
|
|||
|
|
// 自动重连逻辑
|
|||
|
|
if (this.reconnectCount < 5) {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.connect(userId)
|
|||
|
|
this.reconnectCount++
|
|||
|
|
}, 5000)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
handleNotification(notification) {
|
|||
|
|
switch (notification.type) {
|
|||
|
|
case 'ai_answer':
|
|||
|
|
ElMessage.success('AI助手已为您的问题提供了解决方案')
|
|||
|
|
// 更新页面状态
|
|||
|
|
break
|
|||
|
|
case 'repair_assigned':
|
|||
|
|
ElMessage.info('您有新的工单分配')
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🧪 AI功能测试Demo
|
|||
|
|
|
|||
|
|
### 测试页面实现
|
|||
|
|
创建一个专门的测试页面,用于验证AI功能的各个环节:
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<div class="ai-test-demo">
|
|||
|
|
<el-card header="AI智能回答功能测试">
|
|||
|
|
<!-- 测试用例选择 -->
|
|||
|
|
<div class="test-scenarios">
|
|||
|
|
<h4>选择测试场景</h4>
|
|||
|
|
<el-radio-group v-model="selectedScenario">
|
|||
|
|
<el-radio label="network">网络连接问题</el-radio>
|
|||
|
|
<el-radio label="login">系统登录问题</el-radio>
|
|||
|
|
<el-radio label="printer">打印机故障</el-radio>
|
|||
|
|
<el-radio label="custom">自定义问题</el-radio>
|
|||
|
|
</el-radio-group>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 测试参数配置 -->
|
|||
|
|
<div class="test-config">
|
|||
|
|
<el-form :model="testForm" label-width="120px">
|
|||
|
|
<el-form-item label="工单标题">
|
|||
|
|
<el-input v-model="testForm.title" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="问题描述">
|
|||
|
|
<el-input type="textarea" v-model="testForm.description" rows="4" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="业务模块">
|
|||
|
|
<el-select v-model="testForm.business">
|
|||
|
|
<el-option label="办公网络" value="办公网络" />
|
|||
|
|
<el-option label="财务系统" value="财务系统" />
|
|||
|
|
<el-option label="设备维护" value="设备维护" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 测试执行按钮 -->
|
|||
|
|
<div class="test-actions">
|
|||
|
|
<el-button type="primary" @click="runAITest" :loading="testing">
|
|||
|
|
{{ testing ? 'AI分析中...' : '开始AI测试' }}
|
|||
|
|
</el-button>
|
|||
|
|
<el-button @click="resetTest">重置</el-button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 测试结果展示 -->
|
|||
|
|
<div class="test-results" v-if="testResult">
|
|||
|
|
<el-divider>测试结果</el-divider>
|
|||
|
|
|
|||
|
|
<!-- AI回答 -->
|
|||
|
|
<div class="ai-response">
|
|||
|
|
<h4>🤖 AI智能回答</h4>
|
|||
|
|
<div class="answer-content" v-html="testResult.answer"></div>
|
|||
|
|
<div class="answer-metrics">
|
|||
|
|
<el-descriptions :column="2" border>
|
|||
|
|
<el-descriptions-item label="置信度">
|
|||
|
|
<el-progress :percentage="testResult.confidence * 100" />
|
|||
|
|
</el-descriptions-item>
|
|||
|
|
<el-descriptions-item label="响应时间">
|
|||
|
|
{{ testResult.responseTime }}ms
|
|||
|
|
</el-descriptions-item>
|
|||
|
|
<el-descriptions-item label="相似案例">
|
|||
|
|
{{ testResult.similarCases }}个
|
|||
|
|
</el-descriptions-item>
|
|||
|
|
<el-descriptions-item label="使用工具">
|
|||
|
|
{{ testResult.usedTools }}
|
|||
|
|
</el-descriptions-item>
|
|||
|
|
</el-descriptions>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 测试反馈 -->
|
|||
|
|
<div class="test-feedback">
|
|||
|
|
<h4>📝 测试反馈</h4>
|
|||
|
|
<el-button-group>
|
|||
|
|
<el-button type="success" @click="submitTestFeedback('good')">
|
|||
|
|
👍 回答质量好
|
|||
|
|
</el-button>
|
|||
|
|
<el-button type="warning" @click="submitTestFeedback('average')">
|
|||
|
|
👌 回答一般
|
|||
|
|
</el-button>
|
|||
|
|
<el-button type="danger" @click="submitTestFeedback('poor')">
|
|||
|
|
👎 回答质量差
|
|||
|
|
</el-button>
|
|||
|
|
</el-button-group>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</el-card>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ref, watch } from 'vue'
|
|||
|
|
import { ElMessage } from 'element-plus'
|
|||
|
|
|
|||
|
|
const testing = ref(false)
|
|||
|
|
const selectedScenario = ref('network')
|
|||
|
|
const testForm = ref({
|
|||
|
|
title: '',
|
|||
|
|
description: '',
|
|||
|
|
business: '办公网络'
|
|||
|
|
})
|
|||
|
|
const testResult = ref(null)
|
|||
|
|
|
|||
|
|
// 预定义测试场景
|
|||
|
|
const testScenarios = {
|
|||
|
|
network: {
|
|||
|
|
title: '办公室网络连接异常',
|
|||
|
|
description: '办公室所有电脑都无法连接网络,已检查网线连接正常,路由器指示灯正常。之前网络使用正常,今天上午开始出现问题。',
|
|||
|
|
business: '办公网络'
|
|||
|
|
},
|
|||
|
|
login: {
|
|||
|
|
title: '财务系统无法登录',
|
|||
|
|
description: '用户反映财务系统登录页面输入账号密码后提示"用户名或密码错误",但是密码确认无误。其他同事可以正常登录。',
|
|||
|
|
business: '财务系统'
|
|||
|
|
},
|
|||
|
|
printer: {
|
|||
|
|
title: '办公室打印机故障',
|
|||
|
|
description: '打印机显示"脱机"状态,无法打印任何文件。已检查电源连接正常,USB线已重新插拔,电脑上显示打印机驱动正常。',
|
|||
|
|
business: '设备维护'
|
|||
|
|
},
|
|||
|
|
custom: {
|
|||
|
|
title: '',
|
|||
|
|
description: '',
|
|||
|
|
business: '办公网络'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 监听场景切换
|
|||
|
|
watch(selectedScenario, (newScenario) => {
|
|||
|
|
if (newScenario !== 'custom') {
|
|||
|
|
Object.assign(testForm.value, testScenarios[newScenario])
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 运行AI测试
|
|||
|
|
const runAITest = async () => {
|
|||
|
|
testing.value = true
|
|||
|
|
testResult.value = null
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 模拟API调用 (实际环境中调用真实接口)
|
|||
|
|
const response = await mockAIAPICall(testForm.value)
|
|||
|
|
testResult.value = response
|
|||
|
|
ElMessage.success('AI测试完成')
|
|||
|
|
} catch (error) {
|
|||
|
|
ElMessage.error('AI测试失败: ' + error.message)
|
|||
|
|
} finally {
|
|||
|
|
testing.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 模拟AI API调用 (开发阶段使用)
|
|||
|
|
const mockAIAPICall = async (testData) => {
|
|||
|
|
// 模拟网络延迟
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
|||
|
|
|
|||
|
|
// 模拟AI回答生成
|
|||
|
|
const mockResponses = {
|
|||
|
|
network: {
|
|||
|
|
answer: `## 问题分析
|
|||
|
|
网络连接异常可能由以下原因导致:
|
|||
|
|
|
|||
|
|
## 解决方案
|
|||
|
|
1. **检查网络设备**
|
|||
|
|
- 重启路由器和交换机
|
|||
|
|
- 检查网线接头是否松动
|
|||
|
|
|
|||
|
|
2. **检查网络配置**
|
|||
|
|
- 确认IP地址设置正确
|
|||
|
|
- 检查DNS配置
|
|||
|
|
|
|||
|
|
3. **联系网络管理员**
|
|||
|
|
- 如上述方法无效,建议联系网络管理员检查上级网络设备
|
|||
|
|
|
|||
|
|
## 预计解决时间
|
|||
|
|
10-30分钟`,
|
|||
|
|
confidence: 0.87,
|
|||
|
|
responseTime: 1850,
|
|||
|
|
similarCases: 5,
|
|||
|
|
usedTools: 'knowledge_search,similar_cases'
|
|||
|
|
},
|
|||
|
|
login: {
|
|||
|
|
answer: `## 问题分析
|
|||
|
|
登录失败通常由账户状态或权限问题导致:
|
|||
|
|
|
|||
|
|
## 解决方案
|
|||
|
|
1. **密码重置**
|
|||
|
|
- 联系系统管理员重置密码
|
|||
|
|
- 检查账户是否被锁定
|
|||
|
|
|
|||
|
|
2. **浏览器清理**
|
|||
|
|
- 清除浏览器缓存和Cookie
|
|||
|
|
- 尝试使用其他浏览器登录
|
|||
|
|
|
|||
|
|
3. **权限检查**
|
|||
|
|
- 确认账户具有系统访问权限
|
|||
|
|
- 检查账户是否过期
|
|||
|
|
|
|||
|
|
## 预计解决时间
|
|||
|
|
5-15分钟`,
|
|||
|
|
confidence: 0.92,
|
|||
|
|
responseTime: 1420,
|
|||
|
|
similarCases: 8,
|
|||
|
|
usedTools: 'user_info,knowledge_search'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const scenario = testData.business === '办公网络' ? 'network' : 'login'
|
|||
|
|
return mockResponses[scenario]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提交测试反馈
|
|||
|
|
const submitTestFeedback = (feedbackType) => {
|
|||
|
|
const feedbackMessages = {
|
|||
|
|
good: '感谢反馈!AI回答质量评价:优秀',
|
|||
|
|
average: '感谢反馈!AI回答质量评价:良好',
|
|||
|
|
poor: '感谢反馈!我们会继续改进AI回答质量'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ElMessage.info(feedbackMessages[feedbackType])
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重置测试
|
|||
|
|
const resetTest = () => {
|
|||
|
|
testForm.value = { title: '', description: '', business: '办公网络' }
|
|||
|
|
testResult.value = null
|
|||
|
|
selectedScenario.value = 'network'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
selectedScenario.value = 'network'
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.ai-test-demo {
|
|||
|
|
max-width: 1000px;
|
|||
|
|
margin: 20px auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-scenarios, .test-config, .test-actions {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-results {
|
|||
|
|
margin-top: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ai-response {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.answer-content {
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
padding: 15px;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
border-left: 4px solid #28a745;
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
white-space: pre-wrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 流式生成样式 */
|
|||
|
|
.streaming .typing-indicator {
|
|||
|
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.streaming .cursor {
|
|||
|
|
animation: blink 1s infinite;
|
|||
|
|
color: #409eff;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes blink {
|
|||
|
|
0%, 50% { opacity: 1; }
|
|||
|
|
51%, 100% { opacity: 0; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.generation-progress {
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.progress-text {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #606266;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.answer-controls {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.answer-metrics {
|
|||
|
|
margin-top: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-feedback {
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📋 总结
|
|||
|
|
|
|||
|
|
### 开发优先级
|
|||
|
|
1. **Phase 1**: 基础工单管理功能(已可用)
|
|||
|
|
2. **Phase 2**: 用户认证和权限系统(已可用)
|
|||
|
|
3. **Phase 3**: AI智能回答功能(✅ 已实现,支持流式输出)
|
|||
|
|
4. **Phase 4**: 高级统计分析和可视化
|
|||
|
|
|
|||
|
|
### 技术栈建议
|
|||
|
|
- **前端框架**: Vue 3 + Element Plus
|
|||
|
|
- **状态管理**: Pinia
|
|||
|
|
- **路由**: Vue Router 4
|
|||
|
|
- **HTTP客户端**: Axios
|
|||
|
|
- **Markdown渲染**: marked
|
|||
|
|
- **图表库**: ECharts
|
|||
|
|
|
|||
|
|
### 部署说明
|
|||
|
|
- 开发环境已启动在 http://localhost:8080
|
|||
|
|
- API文档可访问 http://localhost:8080/doc.html
|
|||
|
|
- 当前可使用基础工单管理功能
|
|||
|
|
- ✅ **AI流式输出功能已实现并可用**
|
|||
|
|
|
|||
|
|
### 🚀 流式输出特性
|
|||
|
|
- **Server-Sent Events (SSE)**: 实时推送AI生成进度
|
|||
|
|
- **实时进度指示**: 分析→搜索→生成阶段可视化
|
|||
|
|
- **中断控制**: 用户可随时停止生成过程
|
|||
|
|
- **性能监控**: 完整的流程追踪和性能指标
|
|||
|
|
- **降级支持**: 自动切换到非流式模式作为备选
|
|||
|
|
|
|||
|
|
### 🎯 前端集成要点
|
|||
|
|
1. **EventSource API**: 用于接收SSE流式数据
|
|||
|
|
2. **状态管理**: 生成进度、内容缓冲、会话控制
|
|||
|
|
3. **用户体验**: 打字机效果、进度条、停止按钮
|
|||
|
|
4. **错误处理**: 连接失败、超时、异常恢复
|
|||
|
|
5. **资源清理**: 组件卸载时关闭SSE连接
|
|||
|
|
|
|||
|
|
这份文档为前端开发提供了完整的API接口规范、流式输出实现方案和测试Demo,确保前后端协作顺利进行。AI智能回答功能已完全就绪,支持现代化的流式用户体验。
|