web修改
This commit is contained in:
@@ -28,16 +28,13 @@ declare module 'shared/components/iframe/IframeView.vue' {
|
||||
export default IframeView
|
||||
}
|
||||
|
||||
declare module 'shared/components/iframe/IframeView.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const IframeView: DefineComponent<{}, {}, any>
|
||||
export default IframeView
|
||||
}
|
||||
|
||||
// ========== API 模块 ==========
|
||||
declare module 'shared/api' {
|
||||
export const api: any
|
||||
export const TokenManager: any
|
||||
export const authAPI: any
|
||||
export const fileAPI: any
|
||||
export const workcaseAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/api/auth' {
|
||||
@@ -48,26 +45,64 @@ declare module 'shared/api/file' {
|
||||
export const fileAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/api' {
|
||||
export const authAPI: any
|
||||
export const fileAPI: any
|
||||
export const TokenManager: any
|
||||
export const api: any
|
||||
declare module 'shared/api/ai' {
|
||||
export const agentAPI: any
|
||||
export const aiKnowledgeAPI: any
|
||||
export const aiChatAPI: any
|
||||
}
|
||||
|
||||
// 保留旧的导出路径(向后兼容)
|
||||
declare module 'shared/FileUpload' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const FileUpload: DefineComponent<{}, {}, any>
|
||||
export default FileUpload
|
||||
declare module 'shared/api/workcase' {
|
||||
export const workcaseAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/DynamicFormItem' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const DynamicFormItem: DefineComponent<{}, {}, any>
|
||||
export default DynamicFormItem
|
||||
// ============ types模块 ==================
|
||||
declare module 'shared/types' {
|
||||
import type { BaseDTO } from '../../../shared/src/types/base'
|
||||
|
||||
// 重新导出 base
|
||||
export type { BaseDTO }
|
||||
|
||||
// 重新导出 response
|
||||
export type { ResultDomain } from '../../../shared/src/types/response'
|
||||
|
||||
// 重新导出 page
|
||||
export type { PageDomain, PageParam, PageRequest } from '../../../shared/src/types/page'
|
||||
|
||||
// 重新导出 auth
|
||||
export type { LoginParam, LoginDomain } from '../../../shared/src/types/auth'
|
||||
|
||||
// 重新导出 sys
|
||||
export type { SysUserVO, SysConfigVO, TbSysViewDTO } from '../../../shared/src/types/sys'
|
||||
|
||||
// 重新导出 file
|
||||
export type { TbSysFileDTO } from '../../../shared/src/types/file'
|
||||
|
||||
// 重新导出 ai
|
||||
export type {
|
||||
TbKnowledge,
|
||||
TbKnowledgeFile,
|
||||
TbAgent,
|
||||
PromptCard,
|
||||
TbChat,
|
||||
TbChatMessage,
|
||||
DifyFileInfo,
|
||||
ChatPrepareData,
|
||||
StopChatParams,
|
||||
CommentMessageParams
|
||||
} from '../../../shared/src/types/ai'
|
||||
|
||||
// 重新导出 workcase
|
||||
export type {
|
||||
TbWorkcaseDTO,
|
||||
TbWorkcaseProcessDTO,
|
||||
TbWorkcaseDeviceDTO
|
||||
} from '../../../shared/src/types/workcase'
|
||||
|
||||
// 重新导出 menu
|
||||
export type { MenuItem, toMenuItem, toMenuItems } from '../../../shared/src/types/menu'
|
||||
}
|
||||
|
||||
// ================ utils工具 ==========================
|
||||
declare module 'shared/utils' {
|
||||
export const initAesEncrypt: any
|
||||
export const getAesInstance: any
|
||||
@@ -78,47 +113,6 @@ declare module 'shared/utils' {
|
||||
export const getFilePreviewUrl: any
|
||||
}
|
||||
|
||||
declare module 'shared/types' {
|
||||
import { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export type LoginParam = any
|
||||
export type LoginDomain = any
|
||||
export type SysUserVO = any
|
||||
export type TbSysFileDTO = any
|
||||
export type SysConfigVO = any
|
||||
export type ResultDomain<T = any> = any
|
||||
|
||||
// 视图类型(用于路由和菜单)
|
||||
export interface TbSysViewDTO {
|
||||
viewId?: string
|
||||
name?: string
|
||||
parentId?: string
|
||||
url?: string
|
||||
component?: string
|
||||
iframeUrl?: string
|
||||
icon?: string
|
||||
type?: number
|
||||
viewType?: string
|
||||
service?: string
|
||||
layout?: string
|
||||
orderNum?: number
|
||||
description?: string
|
||||
children?: TbSysViewDTO[]
|
||||
}
|
||||
|
||||
// 菜单项类型(扩展 TbSysViewDTO)
|
||||
export interface MenuItem extends TbSysViewDTO {
|
||||
key: string
|
||||
label: string
|
||||
expanded?: boolean
|
||||
children?: MenuItem[]
|
||||
}
|
||||
|
||||
// 菜单工具函数
|
||||
export function toMenuItem(view: TbSysViewDTO, expanded?: boolean): MenuItem
|
||||
export function toMenuItems(views: TbSysViewDTO[], defaultExpanded?: boolean): MenuItem[]
|
||||
}
|
||||
|
||||
declare module 'shared/utils/route' {
|
||||
import { RouteRecordRaw } from 'vue-router'
|
||||
import type { TbSysViewDTO } from 'shared/types'
|
||||
|
||||
@@ -28,16 +28,13 @@ declare module 'shared/components/iframe/IframeView.vue' {
|
||||
export default IframeView
|
||||
}
|
||||
|
||||
declare module 'shared/components/iframe/IframeView.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const IframeView: DefineComponent<{}, {}, any>
|
||||
export default IframeView
|
||||
}
|
||||
|
||||
// ========== API 模块 ==========
|
||||
declare module 'shared/api' {
|
||||
export const api: any
|
||||
export const TokenManager: any
|
||||
export const authAPI: any
|
||||
export const fileAPI: any
|
||||
export const workcaseAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/api/auth' {
|
||||
@@ -48,26 +45,64 @@ declare module 'shared/api/file' {
|
||||
export const fileAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/api' {
|
||||
export const authAPI: any
|
||||
export const fileAPI: any
|
||||
export const TokenManager: any
|
||||
export const api: any
|
||||
declare module 'shared/api/ai' {
|
||||
export const agentAPI: any
|
||||
export const aiKnowledgeAPI: any
|
||||
export const aiChatAPI: any
|
||||
}
|
||||
|
||||
// 保留旧的导出路径(向后兼容)
|
||||
declare module 'shared/FileUpload' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const FileUpload: DefineComponent<{}, {}, any>
|
||||
export default FileUpload
|
||||
declare module 'shared/api/workcase' {
|
||||
export const workcaseAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/DynamicFormItem' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const DynamicFormItem: DefineComponent<{}, {}, any>
|
||||
export default DynamicFormItem
|
||||
// ============ types模块 ==================
|
||||
declare module 'shared/types' {
|
||||
import type { BaseDTO } from '../../../shared/src/types/base'
|
||||
|
||||
// 重新导出 base
|
||||
export type { BaseDTO }
|
||||
|
||||
// 重新导出 response
|
||||
export type { ResultDomain } from '../../../shared/src/types/response'
|
||||
|
||||
// 重新导出 page
|
||||
export type { PageDomain, PageParam, PageRequest } from '../../../shared/src/types/page'
|
||||
|
||||
// 重新导出 auth
|
||||
export type { LoginParam, LoginDomain } from '../../../shared/src/types/auth'
|
||||
|
||||
// 重新导出 sys
|
||||
export type { SysUserVO, SysConfigVO, TbSysViewDTO } from '../../../shared/src/types/sys'
|
||||
|
||||
// 重新导出 file
|
||||
export type { TbSysFileDTO } from '../../../shared/src/types/file'
|
||||
|
||||
// 重新导出 ai
|
||||
export type {
|
||||
TbKnowledge,
|
||||
TbKnowledgeFile,
|
||||
TbAgent,
|
||||
PromptCard,
|
||||
TbChat,
|
||||
TbChatMessage,
|
||||
DifyFileInfo,
|
||||
ChatPrepareData,
|
||||
StopChatParams,
|
||||
CommentMessageParams
|
||||
} from '../../../shared/src/types/ai'
|
||||
|
||||
// 重新导出 workcase
|
||||
export type {
|
||||
TbWorkcaseDTO,
|
||||
TbWorkcaseProcessDTO,
|
||||
TbWorkcaseDeviceDTO
|
||||
} from '../../../shared/src/types/workcase'
|
||||
|
||||
// 重新导出 menu
|
||||
export type { MenuItem, toMenuItem, toMenuItems } from '../../../shared/src/types/menu'
|
||||
}
|
||||
|
||||
// ================ utils工具 ==========================
|
||||
declare module 'shared/utils' {
|
||||
export const initAesEncrypt: any
|
||||
export const getAesInstance: any
|
||||
@@ -78,47 +113,6 @@ declare module 'shared/utils' {
|
||||
export const getFilePreviewUrl: any
|
||||
}
|
||||
|
||||
declare module 'shared/types' {
|
||||
import { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export type LoginParam = any
|
||||
export type LoginDomain = any
|
||||
export type SysUserVO = any
|
||||
export type TbSysFileDTO = any
|
||||
export type SysConfigVO = any
|
||||
export type ResultDomain<T = any> = any
|
||||
|
||||
// 视图类型(用于路由和菜单)
|
||||
export interface TbSysViewDTO {
|
||||
viewId?: string
|
||||
name?: string
|
||||
parentId?: string
|
||||
url?: string
|
||||
component?: string
|
||||
iframeUrl?: string
|
||||
icon?: string
|
||||
type?: number
|
||||
viewType?: string
|
||||
service?: string
|
||||
layout?: string
|
||||
orderNum?: number
|
||||
description?: string
|
||||
children?: TbSysViewDTO[]
|
||||
}
|
||||
|
||||
// 菜单项类型(扩展 TbSysViewDTO)
|
||||
export interface MenuItem extends TbSysViewDTO {
|
||||
key: string
|
||||
label: string
|
||||
expanded?: boolean
|
||||
children?: MenuItem[]
|
||||
}
|
||||
|
||||
// 菜单工具函数
|
||||
export function toMenuItem(view: TbSysViewDTO, expanded?: boolean): MenuItem
|
||||
export function toMenuItems(views: TbSysViewDTO[], defaultExpanded?: boolean): MenuItem[]
|
||||
}
|
||||
|
||||
declare module 'shared/utils/route' {
|
||||
import { RouteRecordRaw } from 'vue-router'
|
||||
import type { TbSysViewDTO } from 'shared/types'
|
||||
@@ -195,5 +189,4 @@ declare module 'shared/layouts' {
|
||||
|
||||
export const BlankLayout: DefineComponent<{}, {}, any>
|
||||
export const SubSidebarLayout: DefineComponent<{}, {}, any>
|
||||
|
||||
}
|
||||
|
||||
68
urbanLifelineWeb/packages/shared/src/api/ai/agent.ts
Normal file
68
urbanLifelineWeb/packages/shared/src/api/ai/agent.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { api } from '@/api/index'
|
||||
import type { ResultDomain, PageRequest } from '@/types'
|
||||
import type { TbAgent } from '@/types/ai'
|
||||
|
||||
/**
|
||||
* @description 智能体管理接口
|
||||
* @filename agent.ts
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
export const agentAPI = {
|
||||
baseUrl: '/urban-lifeline/ai/agent',
|
||||
|
||||
/**
|
||||
* 创建智能体
|
||||
* @param agent 智能体信息
|
||||
*/
|
||||
async createAgent(agent: TbAgent): Promise<ResultDomain<TbAgent>> {
|
||||
const response = await api.post<TbAgent>(`${this.baseUrl}`, agent)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新智能体
|
||||
* @param agent 智能体信息
|
||||
*/
|
||||
async updateAgent(agent: TbAgent): Promise<ResultDomain<TbAgent>> {
|
||||
const response = await api.put<TbAgent>(`${this.baseUrl}`, agent)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除智能体
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
async deleteAgent(agentId: string): Promise<ResultDomain<TbAgent>> {
|
||||
const response = await api.delete<TbAgent>(`${this.baseUrl}/${agentId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取智能体详情
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
async getAgent(agentId: string): Promise<ResultDomain<TbAgent>> {
|
||||
const response = await api.get<TbAgent>(`${this.baseUrl}/${agentId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 分页查询智能体
|
||||
* @param pageRequest 分页请求参数
|
||||
*/
|
||||
async getAgentPage(pageRequest: PageRequest<TbAgent>): Promise<ResultDomain<TbAgent>> {
|
||||
const response = await api.post<TbAgent>(`${this.baseUrl}/page`, pageRequest)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取智能体列表
|
||||
* @param filter 筛选条件
|
||||
*/
|
||||
async getAgentList(filter?: TbAgent): Promise<ResultDomain<TbAgent>> {
|
||||
const response = await api.get<TbAgent>(`${this.baseUrl}/list`, { params: filter })
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
262
urbanLifelineWeb/packages/shared/src/api/ai/aiKnowledge.ts
Normal file
262
urbanLifelineWeb/packages/shared/src/api/ai/aiKnowledge.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import { api } from '@/api/index'
|
||||
import type { ResultDomain, PageRequest } from '@/types'
|
||||
import type { TbKnowledge, TbKnowledgeFile, SegmentRequestBody, DocumentStatusRequestBody } from '@/types/ai'
|
||||
|
||||
/**
|
||||
* @description AI知识库相关接口
|
||||
* @filename aiKnowledge.ts
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
export const aiKnowledgeAPI = {
|
||||
baseUrl: '/urban-lifeline/ai/knowledge',
|
||||
|
||||
// ====================== 知识库管理 ======================
|
||||
|
||||
/**
|
||||
* 创建知识库
|
||||
* @param knowledge 知识库信息
|
||||
*/
|
||||
async createKnowledge(knowledge: TbKnowledge): Promise<ResultDomain<TbKnowledge>> {
|
||||
const response = await api.post<TbKnowledge>(`${this.baseUrl}`, knowledge)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新知识库
|
||||
* @param knowledge 知识库信息
|
||||
*/
|
||||
async updateKnowledge(knowledge: TbKnowledge): Promise<ResultDomain<TbKnowledge>> {
|
||||
const response = await api.put<TbKnowledge>(`${this.baseUrl}`, knowledge)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除知识库
|
||||
* @param knowledgeId 知识库ID
|
||||
*/
|
||||
async deleteKnowledge(knowledgeId: string): Promise<ResultDomain<boolean>> {
|
||||
const response = await api.delete<boolean>(`${this.baseUrl}/${knowledgeId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取知识库详情
|
||||
* @param knowledgeId 知识库ID
|
||||
*/
|
||||
async getKnowledge(knowledgeId: string): Promise<ResultDomain<TbKnowledge>> {
|
||||
const response = await api.get<TbKnowledge>(`${this.baseUrl}/${knowledgeId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询知识库列表
|
||||
* @param filter 筛选条件
|
||||
*/
|
||||
async listKnowledges(filter?: TbKnowledge): Promise<ResultDomain<TbKnowledge>> {
|
||||
const response = await api.post<TbKnowledge>(`${this.baseUrl}/list`, filter || {})
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 分页查询知识库
|
||||
* @param pageRequest 分页请求
|
||||
*/
|
||||
async pageKnowledges(pageRequest: PageRequest<TbKnowledge>): Promise<ResultDomain<TbKnowledge>> {
|
||||
const response = await api.post<TbKnowledge>(`${this.baseUrl}/page`, pageRequest)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取知识库统计信息
|
||||
* @param knowledgeId 知识库ID
|
||||
*/
|
||||
async getKnowledgeStats(knowledgeId: string): Promise<ResultDomain<TbKnowledge>> {
|
||||
const response = await api.get<TbKnowledge>(`${this.baseUrl}/${knowledgeId}/stats`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// ====================== 文件管理 ======================
|
||||
|
||||
/**
|
||||
* 获取知识库文档列表
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param page 页码
|
||||
* @param limit 每页条数
|
||||
*/
|
||||
async getDocumentList(knowledgeId: string, page = 1, limit = 20): Promise<ResultDomain<Record<string, any>>> {
|
||||
const response = await api.get<Record<string, any>>(`${this.baseUrl}/${knowledgeId}/documents`, {
|
||||
params: { page, limit }
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 上传文件到知识库
|
||||
* @param file 文件
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param indexingTechnique 索引方式
|
||||
*/
|
||||
async uploadToKnowledge(
|
||||
file: File,
|
||||
knowledgeId: string,
|
||||
indexingTechnique?: string
|
||||
): Promise<ResultDomain<TbKnowledgeFile>> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('knowledgeId', knowledgeId)
|
||||
if (indexingTechnique) {
|
||||
formData.append('indexingTechnique', indexingTechnique)
|
||||
}
|
||||
const response = await api.upload<TbKnowledgeFile>(`${this.baseUrl}/file/upload`, formData)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量上传文件到知识库
|
||||
* @param files 文件数组
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param indexingTechnique 索引方式
|
||||
*/
|
||||
async batchUploadToKnowledge(
|
||||
files: File[],
|
||||
knowledgeId: string,
|
||||
indexingTechnique?: string
|
||||
): Promise<ResultDomain<TbKnowledgeFile>> {
|
||||
const formData = new FormData()
|
||||
files.forEach(file => formData.append('files', file))
|
||||
formData.append('knowledgeId', knowledgeId)
|
||||
if (indexingTechnique) {
|
||||
formData.append('indexingTechnique', indexingTechnique)
|
||||
}
|
||||
const response = await api.upload<TbKnowledgeFile>(`${this.baseUrl}/file/batch-upload`, formData)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新知识库文件(上传新版本)
|
||||
* @param file 文件
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param fileRootId 文件根ID
|
||||
*/
|
||||
async updateFile(
|
||||
file: File,
|
||||
knowledgeId: string,
|
||||
fileRootId: string
|
||||
): Promise<ResultDomain<TbKnowledgeFile>> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('knowledgeId', knowledgeId)
|
||||
formData.append('fileRootId', fileRootId)
|
||||
const response = await api.uploadPut<TbKnowledgeFile>(`${this.baseUrl}/file/upload`, formData)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除知识库文件
|
||||
* @param fileId 文件ID
|
||||
*/
|
||||
async deleteFile(fileId: string): Promise<ResultDomain<boolean>> {
|
||||
const response = await api.delete<boolean>(`${this.baseUrl}/file/${fileId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取文件历史版本
|
||||
* @param fileRootId 文件根ID
|
||||
*/
|
||||
async getFileHistory(fileRootId: string): Promise<ResultDomain<TbKnowledgeFile>> {
|
||||
const response = await api.get<TbKnowledgeFile>(`${this.baseUrl}/file/${fileRootId}/history`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// ====================== 文档分段管理 ======================
|
||||
|
||||
/**
|
||||
* 获取文档分段列表
|
||||
* @param datasetId Dify数据集ID
|
||||
* @param documentId Dify文档ID
|
||||
*/
|
||||
async getDocumentSegments(datasetId: string, documentId: string): Promise<ResultDomain<Record<string, any>>> {
|
||||
const response = await api.get<Record<string, any>>(
|
||||
`${this.baseUrl}/datasets/${datasetId}/documents/${documentId}/segments`
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建文档分段
|
||||
* @param datasetId Dify数据集ID
|
||||
* @param documentId Dify文档ID
|
||||
* @param requestBody 分段内容
|
||||
*/
|
||||
async createSegment(
|
||||
datasetId: string,
|
||||
documentId: string,
|
||||
requestBody: SegmentRequestBody
|
||||
): Promise<ResultDomain<string>> {
|
||||
const response = await api.post<string>(
|
||||
`${this.baseUrl}/datasets/${datasetId}/documents/${documentId}/segments`,
|
||||
requestBody
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新文档分段
|
||||
* @param datasetId Dify数据集ID
|
||||
* @param documentId Dify文档ID
|
||||
* @param segmentId 分段ID
|
||||
* @param requestBody 分段内容
|
||||
*/
|
||||
async updateSegment(
|
||||
datasetId: string,
|
||||
documentId: string,
|
||||
segmentId: string,
|
||||
requestBody: SegmentRequestBody
|
||||
): Promise<ResultDomain<string>> {
|
||||
const response = await api.patch<string>(
|
||||
`${this.baseUrl}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`,
|
||||
requestBody
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除文档分段
|
||||
* @param datasetId Dify数据集ID
|
||||
* @param documentId Dify文档ID
|
||||
* @param segmentId 分段ID
|
||||
*/
|
||||
async deleteSegment(
|
||||
datasetId: string,
|
||||
documentId: string,
|
||||
segmentId: string
|
||||
): Promise<ResultDomain<string>> {
|
||||
const response = await api.delete<string>(
|
||||
`${this.baseUrl}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// ====================== 文档状态管理 ======================
|
||||
|
||||
/**
|
||||
* 更新文档状态(启用/禁用/归档)
|
||||
* @param datasetId Dify数据集ID
|
||||
* @param action 操作类型: enable/disable/archive/un_archive
|
||||
* @param requestBody 请求体
|
||||
*/
|
||||
async updateDocumentStatus(
|
||||
datasetId: string,
|
||||
action: 'enable' | 'disable' | 'archive' | 'un_archive',
|
||||
requestBody: DocumentStatusRequestBody
|
||||
): Promise<ResultDomain<string>> {
|
||||
const response = await api.patch<string>(
|
||||
`${this.baseUrl}/datasets/${datasetId}/documents/${action}/status`,
|
||||
requestBody
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
124
urbanLifelineWeb/packages/shared/src/api/ai/aichat.ts
Normal file
124
urbanLifelineWeb/packages/shared/src/api/ai/aichat.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { api } from '@/api/index'
|
||||
import type { ResultDomain } from '@/types'
|
||||
import type { TbChat, TbChatMessage, ChatPrepareData, StopChatParams, CommentMessageParams, DifyFileInfo } from '@/types/ai'
|
||||
|
||||
/**
|
||||
* @description AI对话相关接口
|
||||
* @filename aichat.ts
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
export const aiChatAPI = {
|
||||
baseUrl: '/urban-lifeline/ai/chat',
|
||||
|
||||
// ====================== 会话管理 ======================
|
||||
|
||||
/**
|
||||
* 创建会话
|
||||
* @param chat 会话信息
|
||||
*/
|
||||
async createChat(chat: TbChat): Promise<ResultDomain<TbChat>> {
|
||||
const response = await api.post<TbChat>(`${this.baseUrl}/conversation`, chat)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新会话
|
||||
* @param chat 会话信息
|
||||
*/
|
||||
async updateChat(chat: TbChat): Promise<ResultDomain<TbChat>> {
|
||||
const response = await api.put<TbChat>(`${this.baseUrl}/conversation`, chat)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
* @param chat 会话信息
|
||||
*/
|
||||
async deleteChat(chat: TbChat): Promise<ResultDomain<TbChat>> {
|
||||
const response = await api.delete<TbChat>(`${this.baseUrl}/conversation`, { data: chat })
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
* @param filter 筛选条件
|
||||
*/
|
||||
async getChatList(filter: TbChat): Promise<ResultDomain<TbChat>> {
|
||||
const response = await api.get<TbChat>(`${this.baseUrl}/conversations`, { data: filter })
|
||||
return response.data
|
||||
},
|
||||
|
||||
// ====================== 消息管理 ======================
|
||||
|
||||
/**
|
||||
* 获取对话消息列表
|
||||
* @param filter 筛选条件
|
||||
*/
|
||||
async getMessageList(filter: TbChat): Promise<ResultDomain<TbChatMessage>> {
|
||||
const response = await api.post<TbChatMessage>(`${this.baseUrl}/messages`, filter)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// ====================== 流式对话 ======================
|
||||
|
||||
/**
|
||||
* 准备流式对话会话数据
|
||||
* @param chatPrepareData 对话预处理数据
|
||||
*/
|
||||
async prepareStreamChat(chatPrepareData: ChatPrepareData): Promise<ResultDomain<string>> {
|
||||
const response = await api.post<string>(`${this.baseUrl}/stream/prepare`, chatPrepareData)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取流式对话SSE URL
|
||||
* @param sessionId 会话ID
|
||||
*/
|
||||
getStreamChatUrl(sessionId: string): string {
|
||||
return `${this.baseUrl}/stream?sessionId=${sessionId}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建流式对话 EventSource
|
||||
* @param sessionId 会话ID
|
||||
*/
|
||||
createStreamChat(sessionId: string): EventSource {
|
||||
const url = this.getStreamChatUrl(sessionId)
|
||||
return new EventSource(url)
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止对话
|
||||
* @param params 停止参数
|
||||
*/
|
||||
async stopChat(params: StopChatParams): Promise<ResultDomain<boolean>> {
|
||||
const response = await api.post<boolean>(`${this.baseUrl}/stop`, params)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 评价消息
|
||||
* @param params 评价参数
|
||||
*/
|
||||
async commentMessage(params: CommentMessageParams): Promise<ResultDomain<boolean>> {
|
||||
const response = await api.post<boolean>(`${this.baseUrl}/comment`, params)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// ====================== 文件上传 ======================
|
||||
|
||||
/**
|
||||
* 上传文件用于对话(图文多模态)
|
||||
* @param file 文件
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
async uploadFileForChat(file: File, agentId: string): Promise<ResultDomain<DifyFileInfo>> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('agentId', agentId)
|
||||
const response = await api.uploadPut<DifyFileInfo>(`${this.baseUrl}/file/upload`, formData)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
3
urbanLifelineWeb/packages/shared/src/api/ai/index.ts
Normal file
3
urbanLifelineWeb/packages/shared/src/api/ai/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './agent'
|
||||
export * from './aichat'
|
||||
export * from './aiKnowledge'
|
||||
@@ -265,16 +265,17 @@ export const api = {
|
||||
},
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
* 文件上传 (POST) - 使用 axios postForm
|
||||
*/
|
||||
upload<T = any>(url: string, formData: FormData, config?: CustomAxiosRequestConfig): Promise<AxiosResponse<ResultDomain<T>>> {
|
||||
return request.post<ResultDomain<T>>(url, formData, {
|
||||
...config,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
...(config?.headers as Record<string, string>)
|
||||
}
|
||||
});
|
||||
return request.postForm<ResultDomain<T>>(url, formData, config);
|
||||
},
|
||||
|
||||
/**
|
||||
* 文件上传 (PUT) - 使用 axios putForm
|
||||
*/
|
||||
uploadPut<T = any>(url: string, formData: FormData, config?: CustomAxiosRequestConfig): Promise<AxiosResponse<ResultDomain<T>>> {
|
||||
return request.putForm<ResultDomain<T>>(url, formData, config);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './workcase'
|
||||
185
urbanLifelineWeb/packages/shared/src/api/workcase/workcase.ts
Normal file
185
urbanLifelineWeb/packages/shared/src/api/workcase/workcase.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { api } from '@/api/index'
|
||||
import type { ResultDomain, PageRequest } from '@/types'
|
||||
import type { TbWorkcaseDTO, TbWorkcaseProcessDTO, TbWorkcaseDeviceDTO } from '@/types/workcase'
|
||||
|
||||
/**
|
||||
* @description 工单管理接口
|
||||
* @filename workcase.ts
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
export const workcaseAPI = {
|
||||
baseUrl: '/urban-lifeline/workcase',
|
||||
|
||||
// ========================= 工单管理 =========================
|
||||
|
||||
/**
|
||||
* 创建工单
|
||||
* @param workcase 工单信息
|
||||
*/
|
||||
async createWorkcase(workcase: TbWorkcaseDTO): Promise<ResultDomain<TbWorkcaseDTO>> {
|
||||
const response = await api.post<TbWorkcaseDTO>(`${this.baseUrl}`, workcase)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新工单
|
||||
* @param workcase 工单信息
|
||||
*/
|
||||
async updateWorkcase(workcase: TbWorkcaseDTO): Promise<ResultDomain<TbWorkcaseDTO>> {
|
||||
const response = await api.put<TbWorkcaseDTO>(`${this.baseUrl}`, workcase)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除工单
|
||||
* @param workcaseId 工单ID
|
||||
*/
|
||||
async deleteWorkcase(workcaseId: string): Promise<ResultDomain<TbWorkcaseDTO>> {
|
||||
const response = await api.delete<TbWorkcaseDTO>(`${this.baseUrl}/${workcaseId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取工单详情
|
||||
* @param workcaseId 工单ID
|
||||
*/
|
||||
async getWorkcaseById(workcaseId: string): Promise<ResultDomain<TbWorkcaseDTO>> {
|
||||
const response = await api.get<TbWorkcaseDTO>(`${this.baseUrl}/${workcaseId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询工单列表
|
||||
* @param filter 筛选条件
|
||||
*/
|
||||
async getWorkcaseList(filter?: TbWorkcaseDTO): Promise<ResultDomain<TbWorkcaseDTO>> {
|
||||
const response = await api.post<TbWorkcaseDTO>(`${this.baseUrl}/list`, filter || {})
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 分页查询工单
|
||||
* @param pageRequest 分页请求
|
||||
*/
|
||||
async getWorkcasePage(pageRequest: PageRequest<TbWorkcaseDTO>): Promise<ResultDomain<TbWorkcaseDTO>> {
|
||||
const response = await api.post<TbWorkcaseDTO>(`${this.baseUrl}/page`, pageRequest)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// ========================= CRM同步接口 =========================
|
||||
|
||||
/**
|
||||
* 同步工单到CRM
|
||||
* @param workcase 工单信息
|
||||
*/
|
||||
async syncWorkcaseToCrm(workcase: TbWorkcaseDTO): Promise<ResultDomain<void>> {
|
||||
const response = await api.post<void>(`${this.baseUrl}/sync/crm`, workcase)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 接收CRM工单更新(CRM回调)
|
||||
* @param jsonBody JSON字符串
|
||||
*/
|
||||
async receiveWorkcaseFromCrm(jsonBody: string): Promise<ResultDomain<void>> {
|
||||
const response = await api.post<void>(`${this.baseUrl}/receive/crm`, jsonBody)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// ========================= 工单处理过程 =========================
|
||||
|
||||
/**
|
||||
* 创建工单处理过程
|
||||
* @param process 处理过程信息
|
||||
*/
|
||||
async createWorkcaseProcess(process: TbWorkcaseProcessDTO): Promise<ResultDomain<TbWorkcaseProcessDTO>> {
|
||||
const response = await api.post<TbWorkcaseProcessDTO>(`${this.baseUrl}/process`, process)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新工单处理过程
|
||||
* @param process 处理过程信息
|
||||
*/
|
||||
async updateWorkcaseProcess(process: TbWorkcaseProcessDTO): Promise<ResultDomain<TbWorkcaseProcessDTO>> {
|
||||
const response = await api.put<TbWorkcaseProcessDTO>(`${this.baseUrl}/process`, process)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除工单处理过程
|
||||
* @param processId 处理过程ID
|
||||
*/
|
||||
async deleteWorkcaseProcess(processId: string): Promise<ResultDomain<TbWorkcaseProcessDTO>> {
|
||||
const response = await api.delete<TbWorkcaseProcessDTO>(`${this.baseUrl}/process/${processId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询工单处理过程列表
|
||||
* @param filter 筛选条件
|
||||
*/
|
||||
async getWorkcaseProcessList(filter?: TbWorkcaseProcessDTO): Promise<ResultDomain<TbWorkcaseProcessDTO>> {
|
||||
const response = await api.post<TbWorkcaseProcessDTO>(`${this.baseUrl}/process/list`, filter || {})
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 分页查询工单处理过程
|
||||
* @param pageRequest 分页请求
|
||||
*/
|
||||
async getWorkcaseProcessPage(pageRequest: PageRequest<TbWorkcaseProcessDTO>): Promise<ResultDomain<TbWorkcaseProcessDTO>> {
|
||||
const response = await api.post<TbWorkcaseProcessDTO>(`${this.baseUrl}/process/page`, pageRequest)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// ========================= 工单设备管理 =========================
|
||||
|
||||
/**
|
||||
* 创建工单设备
|
||||
* @param device 设备信息
|
||||
*/
|
||||
async createWorkcaseDevice(device: TbWorkcaseDeviceDTO): Promise<ResultDomain<TbWorkcaseDeviceDTO>> {
|
||||
const response = await api.post<TbWorkcaseDeviceDTO>(`${this.baseUrl}/device`, device)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新工单设备
|
||||
* @param device 设备信息
|
||||
*/
|
||||
async updateWorkcaseDevice(device: TbWorkcaseDeviceDTO): Promise<ResultDomain<TbWorkcaseDeviceDTO>> {
|
||||
const response = await api.put<TbWorkcaseDeviceDTO>(`${this.baseUrl}/device`, device)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除工单设备
|
||||
* @param workcaseId 工单ID
|
||||
* @param device 设备名称
|
||||
*/
|
||||
async deleteWorkcaseDevice(workcaseId: string, device: string): Promise<ResultDomain<TbWorkcaseDeviceDTO>> {
|
||||
const response = await api.delete<TbWorkcaseDeviceDTO>(`${this.baseUrl}/device/${workcaseId}/${device}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询工单设备列表
|
||||
* @param filter 筛选条件
|
||||
*/
|
||||
async getWorkcaseDeviceList(filter?: TbWorkcaseDeviceDTO): Promise<ResultDomain<TbWorkcaseDeviceDTO>> {
|
||||
const response = await api.post<TbWorkcaseDeviceDTO>(`${this.baseUrl}/device/list`, filter || {})
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 分页查询工单设备
|
||||
* @param pageRequest 分页请求
|
||||
*/
|
||||
async getWorkcaseDevicePage(pageRequest: PageRequest<TbWorkcaseDeviceDTO>): Promise<ResultDomain<TbWorkcaseDeviceDTO>> {
|
||||
const response = await api.post<TbWorkcaseDeviceDTO>(`${this.baseUrl}/device/page`, pageRequest)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
@@ -169,6 +169,8 @@ interface Props {
|
||||
maxCount?: number
|
||||
title?: string
|
||||
buttonText?: string
|
||||
// 自定义上传函数,如果提供则使用外部实现,否则使用默认上传逻辑
|
||||
customUpload?: (files: File[]) => Promise<TbSysFileDTO[] | void>
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -178,7 +180,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
maxSize: 10 * 1024 * 1024,
|
||||
maxCount: 10,
|
||||
title: '文件上传',
|
||||
buttonText: '上传文件'
|
||||
buttonText: '上传文件',
|
||||
customUpload: undefined
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -186,6 +189,8 @@ const emit = defineEmits<{
|
||||
'update:fileList': [value: TbSysFileDTO[]]
|
||||
'upload-success': [files: TbSysFileDTO[]]
|
||||
'upload-error': [error: string]
|
||||
// 文件准备就绪事件,当使用自定义上传时触发,传递文件列表供外部处理
|
||||
'files-ready': [files: File[]]
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
@@ -347,6 +352,41 @@ const handleDialogClose = (done: () => void) => {
|
||||
const uploadFiles = async () => {
|
||||
if (selectedFiles.value.length === 0) return
|
||||
|
||||
// 如果提供了自定义上传函数,则使用外部实现
|
||||
if (props.customUpload) {
|
||||
uploading.value = true
|
||||
try {
|
||||
// 触发 files-ready 事件,传递文件列表
|
||||
emit('files-ready', [...selectedFiles.value])
|
||||
|
||||
// 调用自定义上传函数
|
||||
const result = await props.customUpload([...selectedFiles.value])
|
||||
|
||||
// 如果自定义函数返回了文件列表,触发上传成功事件
|
||||
if (result && result.length > 0) {
|
||||
emit('upload-success', result)
|
||||
emit('update:fileList', [...props.fileList, ...result])
|
||||
|
||||
if (props.mode === 'cover' && result[0]?.url) {
|
||||
emit('update:coverImg', result[0].url)
|
||||
}
|
||||
}
|
||||
|
||||
// 清空文件列表
|
||||
selectedFiles.value = []
|
||||
if (props.mode === 'dialog') {
|
||||
closeDialog()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('自定义上传失败:', error)
|
||||
emit('upload-error', error instanceof Error ? error.message : '上传失败,请重试')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 默认上传逻辑
|
||||
uploading.value = true
|
||||
const uploadedFilesList: TbSysFileDTO[] = []
|
||||
|
||||
@@ -406,6 +446,29 @@ const uploadFiles = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法和状态供外部使用
|
||||
defineExpose({
|
||||
// 当前选中的文件列表
|
||||
selectedFiles,
|
||||
// 上传状态
|
||||
uploading,
|
||||
// 弹窗显示状态
|
||||
showDialog,
|
||||
// 手动触发文件选择
|
||||
triggerFileInput,
|
||||
// 手动添加文件
|
||||
addFiles,
|
||||
// 移除指定文件
|
||||
removeFile,
|
||||
// 清空所有文件
|
||||
clearFiles: () => { selectedFiles.value = [] },
|
||||
// 手动触发上传
|
||||
uploadFiles,
|
||||
// 关闭弹窗
|
||||
closeDialog,
|
||||
// 打开弹窗
|
||||
openDialog: () => { showDialog.value = true }
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
/**
|
||||
* Agent服务相关 types - 根据后端 VO 和 DTO 转换
|
||||
*/
|
||||
|
||||
import { BaseVO, BaseDTO } from "@/types/base"
|
||||
|
||||
// KnowledgeBaseVO - 知识库
|
||||
export interface KnowledgeBaseVO extends BaseVO {
|
||||
/** 知识库ID */
|
||||
knowledgeId?: string
|
||||
/** 智能体ID */
|
||||
agentId?: string
|
||||
/** 智能体名称 */
|
||||
agentName?: string
|
||||
/** 知识库名称 */
|
||||
name?: string
|
||||
/** 知识库类型 */
|
||||
kbType?: string
|
||||
/** 知识库类型名称 */
|
||||
kbTypeName?: string
|
||||
/** 访问级别 */
|
||||
accessLevel?: string
|
||||
/** 访问级别名称 */
|
||||
accessLevelName?: string
|
||||
/** 知识库描述 */
|
||||
description?: string
|
||||
/** 存储路径 */
|
||||
storagePath?: string
|
||||
/** 当前版本号 */
|
||||
version?: string
|
||||
/** 知识库配置 */
|
||||
config?: Record<string, any>
|
||||
/** 服务类型 */
|
||||
serviceType?: string
|
||||
/** 部门名称 */
|
||||
deptName?: string
|
||||
}
|
||||
|
||||
// KnowledgeDocumentVO - 知识文档
|
||||
export interface KnowledgeDocumentVO extends BaseVO {
|
||||
/** 文档ID */
|
||||
documentId?: string
|
||||
/** 知识库ID */
|
||||
knowledgeId?: string
|
||||
/** 文档名称 */
|
||||
name?: string
|
||||
/** 文档类型 */
|
||||
docType?: string
|
||||
/** 文档内容 */
|
||||
content?: string
|
||||
/** 文档描述 */
|
||||
description?: string
|
||||
/** 文档状态 */
|
||||
status?: string
|
||||
/** 文件路径 */
|
||||
filePath?: string
|
||||
/** 文件大小 */
|
||||
fileSize?: number
|
||||
}
|
||||
|
||||
// KnowledgeChunkVO - 知识块(分块存储)
|
||||
export interface KnowledgeChunkVO extends BaseVO {
|
||||
/** 块ID */
|
||||
chunkId?: string
|
||||
/** 文档ID */
|
||||
documentId?: string
|
||||
/** 知识库ID */
|
||||
knowledgeId?: string
|
||||
/** 块内容 */
|
||||
content?: string
|
||||
/** 块索引 */
|
||||
chunkIndex?: number
|
||||
/** 向量嵌入 */
|
||||
embedding?: number[]
|
||||
/** 相似度 */
|
||||
similarity?: number
|
||||
}
|
||||
|
||||
// KnowledgeBaseDTO - 知识库DTO(创建和更新)
|
||||
export interface KnowledgeBaseDTO extends BaseDTO {
|
||||
/** 知识库ID */
|
||||
knowledgeId?: string
|
||||
/** 智能体ID */
|
||||
agentId?: string
|
||||
/** 知识库名称 */
|
||||
name?: string
|
||||
/** 知识库类型 */
|
||||
kbType?: string
|
||||
/** 访问级别 */
|
||||
accessLevel?: string
|
||||
/** 知识库描述 */
|
||||
description?: string
|
||||
/** 存储路径 */
|
||||
storagePath?: string
|
||||
/** 当前版本号 */
|
||||
version?: string
|
||||
/** 知识库配置 */
|
||||
config?: Record<string, any>
|
||||
/** 服务类型 */
|
||||
serviceType?: string
|
||||
}
|
||||
|
||||
// KnowledgeDocumentDTO - 知识文档DTO(创建和更新)
|
||||
export interface KnowledgeDocumentDTO extends BaseDTO {
|
||||
/** 文档ID */
|
||||
documentId?: string
|
||||
/** 知识库ID */
|
||||
knowledgeId?: string
|
||||
/** 文档名称 */
|
||||
name?: string
|
||||
/** 文档类型 */
|
||||
docType?: string
|
||||
/** 文档内容 */
|
||||
content?: string
|
||||
/** 文档描述 */
|
||||
description?: string
|
||||
/** 文档状态 */
|
||||
status?: string
|
||||
/** 文件路径 */
|
||||
filePath?: string
|
||||
/** 文件大小 */
|
||||
fileSize?: number
|
||||
}
|
||||
|
||||
// KnowledgeChunkDTO - 知识块DTO(创建和更新)
|
||||
export interface KnowledgeChunkDTO extends BaseDTO {
|
||||
/** 块ID */
|
||||
chunkId?: string
|
||||
/** 文档ID */
|
||||
documentId?: string
|
||||
/** 知识库ID */
|
||||
knowledgeId?: string
|
||||
/** 块内容 */
|
||||
content?: string
|
||||
/** 块索引 */
|
||||
chunkIndex?: number
|
||||
/** 向量嵌入 */
|
||||
embedding?: number[]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./agent"
|
||||
35
urbanLifelineWeb/packages/shared/src/types/ai/agent.ts
Normal file
35
urbanLifelineWeb/packages/shared/src/types/ai/agent.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { BaseDTO } from '../base'
|
||||
|
||||
/**
|
||||
* 智能体提示卡
|
||||
*/
|
||||
export interface PromptCard {
|
||||
/** 文件ID */
|
||||
fileId?: string
|
||||
/** 提示词 */
|
||||
prompt?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能体 DTO
|
||||
*/
|
||||
export interface TbAgent extends BaseDTO {
|
||||
/** 智能体ID */
|
||||
agentId?: string
|
||||
/** 智能体名称 */
|
||||
name?: string
|
||||
/** 智能体描述 */
|
||||
description?: string
|
||||
/** 智能体url */
|
||||
link?: string
|
||||
/** 智能体APIKEY */
|
||||
apiKey?: string
|
||||
/** 是否是对外智能体,未登录可用 */
|
||||
isOuter?: boolean
|
||||
/** 引导词 */
|
||||
introduce?: string
|
||||
/** 提示卡片数组 */
|
||||
promptCards?: PromptCard[]
|
||||
/** 分类 */
|
||||
category?: string
|
||||
}
|
||||
111
urbanLifelineWeb/packages/shared/src/types/ai/aiChat.ts
Normal file
111
urbanLifelineWeb/packages/shared/src/types/ai/aiChat.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { BaseDTO } from '../base'
|
||||
|
||||
/**
|
||||
* Dify文件信息(用于对话文件上传和请求)
|
||||
*/
|
||||
export interface DifyFileInfo {
|
||||
/** 文件ID(Dify返回) */
|
||||
id?: string
|
||||
/** 文件名 */
|
||||
name?: string
|
||||
/** 文件大小(字节) */
|
||||
size?: number
|
||||
/** 文件扩展名 */
|
||||
extension?: string
|
||||
/** 文件MIME类型 */
|
||||
mimeType?: string
|
||||
/** 上传人ID */
|
||||
createdBy?: string
|
||||
/** 上传时间(时间戳) */
|
||||
createdAt?: number
|
||||
/** 预览URL */
|
||||
previewUrl?: string
|
||||
/** 源文件URL */
|
||||
sourceUrl?: string
|
||||
/** 文件类型:image、document、audio、video、file */
|
||||
type?: string
|
||||
/** 传输方式:remote_url、local_file */
|
||||
transferMethod?: string
|
||||
/** 文件URL或ID */
|
||||
url?: string
|
||||
/** 本地文件上传ID */
|
||||
uploadFileId?: string
|
||||
/** 系统文件ID */
|
||||
sysFileId?: string
|
||||
/** 文件路径(从系统文件表获取) */
|
||||
filePath?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* AI智能体对话
|
||||
*/
|
||||
export interface TbChat extends BaseDTO {
|
||||
/** 对话ID */
|
||||
chatId?: string
|
||||
/** 智能体ID */
|
||||
agentId?: string
|
||||
/** 用户ID */
|
||||
userId?: string
|
||||
/** 用户类型 true-系统内部人员 false-系统外部人员 */
|
||||
userType?: boolean
|
||||
/** 对话标题 */
|
||||
title?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* AI智能体对话消息
|
||||
*/
|
||||
export interface TbChatMessage extends BaseDTO {
|
||||
/** 消息ID */
|
||||
messageId?: string
|
||||
/** Dify消息ID */
|
||||
difyMessageId?: string
|
||||
/** 对话ID */
|
||||
chatId?: string
|
||||
/** 角色 */
|
||||
role?: string
|
||||
/** 内容 */
|
||||
content?: string
|
||||
/** 文件ID数组 */
|
||||
files?: string[]
|
||||
/** 评价 */
|
||||
comment?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话消息数据预处理对象
|
||||
*/
|
||||
export interface ChatPrepareData {
|
||||
/** 智能体Id */
|
||||
agentId?: string
|
||||
/** 对话Id */
|
||||
chatId?: string
|
||||
/** 用户问题 */
|
||||
query?: string
|
||||
/** 本次对话携带的dify文件对象 */
|
||||
files?: DifyFileInfo[]
|
||||
/** 用户ID(来客传wechatId,员工传userId) */
|
||||
userId?: string
|
||||
/** 用户类型(false=来客,true=员工) */
|
||||
userType?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止对话参数
|
||||
*/
|
||||
export interface StopChatParams {
|
||||
taskId: string
|
||||
agentId: string
|
||||
userId: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 评价消息参数
|
||||
*/
|
||||
export interface CommentMessageParams {
|
||||
agentId: string
|
||||
chatId: string
|
||||
messageId: string
|
||||
comment: string
|
||||
userId: string
|
||||
}
|
||||
73
urbanLifelineWeb/packages/shared/src/types/ai/aiKnowledge.ts
Normal file
73
urbanLifelineWeb/packages/shared/src/types/ai/aiKnowledge.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { BaseDTO } from '../base'
|
||||
|
||||
/**
|
||||
* 知识库配置
|
||||
*/
|
||||
export interface TbKnowledge extends BaseDTO {
|
||||
/** 知识库ID */
|
||||
knowledgeId?: string
|
||||
/** 知识库标题 */
|
||||
title?: string
|
||||
/** 知识库头像 */
|
||||
avatar?: string
|
||||
/** 知识库描述 */
|
||||
description?: string
|
||||
/** Dify知识库ID(Dataset ID) */
|
||||
difyDatasetId?: string
|
||||
/** Dify索引方式(high_quality/economy) */
|
||||
difyIndexingTechnique?: 'high_quality' | 'economy'
|
||||
/** 向量模型名称 */
|
||||
embeddingModel?: string
|
||||
/** 向量模型提供商 */
|
||||
embeddingModelProvider?: string
|
||||
/** Rerank模型名称 */
|
||||
rerankModel?: string
|
||||
/** Rerank模型提供商 */
|
||||
rerankModelProvider?: string
|
||||
/** 是否启用Rerank(0否 1是) */
|
||||
rerankingEnable?: number
|
||||
/** 检索Top K(返回前K个结果) */
|
||||
retrievalTopK?: number
|
||||
/** 检索分数阈值(0.00-1.00) */
|
||||
retrievalScoreThreshold?: number
|
||||
/** 文档数量 */
|
||||
documentCount?: number
|
||||
/** 总分段数 */
|
||||
totalChunks?: number
|
||||
/** 所属服务 workcase、bidding */
|
||||
service?: string
|
||||
/** bidding所属项目ID */
|
||||
projectId?: string
|
||||
/** 所属分类 workcase的内部知识库、外部知识库,其他服务default */
|
||||
category?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 知识库文件
|
||||
*/
|
||||
export interface TbKnowledgeFile extends BaseDTO {
|
||||
/** 知识库ID */
|
||||
knowledgeId?: string
|
||||
/** 文件ID */
|
||||
fileId?: string
|
||||
/** 文件根ID */
|
||||
fileRootId?: string
|
||||
/** Dify文件ID */
|
||||
difyFileId?: string
|
||||
/** 文件版本 */
|
||||
version?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 文档分段请求体
|
||||
*/
|
||||
export interface SegmentRequestBody {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* 文档状态请求体
|
||||
*/
|
||||
export interface DocumentStatusRequestBody {
|
||||
[key: string]: any
|
||||
}
|
||||
3
urbanLifelineWeb/packages/shared/src/types/ai/index.ts
Normal file
3
urbanLifelineWeb/packages/shared/src/types/ai/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './agent';
|
||||
export * from './aiKnowledge';
|
||||
export * from './aiChat';
|
||||
@@ -9,7 +9,7 @@ export * from "./menu"
|
||||
export * from "./auth"
|
||||
export * from "./file"
|
||||
export * from "./message"
|
||||
export * from "./agent"
|
||||
export * from "./ai"
|
||||
export * from "./crontab"
|
||||
export * from "./bidding"
|
||||
export * from "./workcase"
|
||||
@@ -1,279 +1,65 @@
|
||||
import type { BaseDTO } from '@/types/base'
|
||||
|
||||
/**
|
||||
* 工单服务相关 types - 根据后端 VO 和 DTO 转换
|
||||
* 工单表对象
|
||||
*/
|
||||
|
||||
import { BaseVO, BaseDTO } from "@/types/base"
|
||||
|
||||
// TicketVO - 工单详情
|
||||
export interface TicketVO extends BaseVO {
|
||||
export interface TbWorkcaseDTO extends BaseDTO {
|
||||
/** 工单ID */
|
||||
ticketId?: string
|
||||
/** 工单编号 */
|
||||
ticketNo?: string
|
||||
/** 客户ID */
|
||||
customerId?: string
|
||||
/** 客户姓名 */
|
||||
customerName?: string
|
||||
/** 客户电话 */
|
||||
customerPhone?: string
|
||||
/** 关联会话ID */
|
||||
conversationId?: string
|
||||
/** 工单类型 */
|
||||
ticketType?: string
|
||||
/** 工单类型名称 */
|
||||
ticketTypeName?: string
|
||||
/** 工单分类 */
|
||||
ticketCategory?: string
|
||||
/** 优先级 */
|
||||
priority?: string
|
||||
/** 优先级名称 */
|
||||
priorityName?: string
|
||||
/** 优先级颜色 */
|
||||
priorityColor?: string
|
||||
/** 工单标题 */
|
||||
title?: string
|
||||
/** 问题描述 */
|
||||
description?: string
|
||||
/** 附件ID数组 */
|
||||
attachments?: string[]
|
||||
/** 附件数量 */
|
||||
attachmentCount?: number
|
||||
/** 工单来源 */
|
||||
ticketSource?: string
|
||||
/** 工单来源名称 */
|
||||
ticketSourceName?: string
|
||||
/** 分配给(处理人ID) */
|
||||
assignedTo?: string
|
||||
/** 处理人姓名 */
|
||||
assignedToName?: string
|
||||
/** 分配部门 */
|
||||
assignedDept?: string
|
||||
/** 分配部门名称 */
|
||||
assignedDeptName?: string
|
||||
/** 工单状态 */
|
||||
ticketStatus?: string
|
||||
/** 工单状态名称 */
|
||||
ticketStatusName?: string
|
||||
/** 工单状态颜色 */
|
||||
statusColor?: string
|
||||
/** 解决方案 */
|
||||
resolution?: string
|
||||
/** 解决时间 */
|
||||
resolutionTime?: string
|
||||
/** 关闭时间 */
|
||||
closeTime?: string
|
||||
/** 首次响应时间 */
|
||||
responseTime?: string
|
||||
/** SLA截止时间 */
|
||||
slaDeadline?: string
|
||||
/** 是否逾期 */
|
||||
isOverdue?: boolean
|
||||
/** 距离SLA截止的剩余时间(分钟) */
|
||||
slaRemainingMinutes?: number
|
||||
/** 客户评分(1-5星) */
|
||||
customerRating?: number
|
||||
/** 客户反馈 */
|
||||
customerFeedback?: string
|
||||
/** CRM系统工单ID */
|
||||
crmTicketId?: string
|
||||
/** 同步状态 */
|
||||
syncStatus?: string
|
||||
/** 同步状态名称 */
|
||||
syncStatusName?: string
|
||||
/** 工单标签 */
|
||||
tags?: string[]
|
||||
/** 工单元数据 */
|
||||
metadata?: Record<string, any>
|
||||
/** 处理记录数量 */
|
||||
logCount?: number
|
||||
/** 创建者姓名 */
|
||||
creatorName?: string
|
||||
/** 更新者姓名 */
|
||||
updaterName?: string
|
||||
workcaseId?: string
|
||||
/** 来客ID */
|
||||
userId?: string
|
||||
/** 来客姓名 */
|
||||
username?: string
|
||||
/** 来客电话 */
|
||||
phone?: string
|
||||
/** 故障类型 */
|
||||
type?: string
|
||||
/** 设备名称 */
|
||||
device?: string
|
||||
/** 设备代码 */
|
||||
deviceCode?: string
|
||||
/** 工单图片列表 */
|
||||
imgs?: string[]
|
||||
/** 紧急程度 normal-普通 emergency-紧急 */
|
||||
emergency?: 'normal' | 'emergency'
|
||||
/** 状态 pending-待处理 processing-处理中 done-已完成 */
|
||||
status?: 'pending' | 'processing' | 'done'
|
||||
/** 处理人ID */
|
||||
processor?: string
|
||||
}
|
||||
|
||||
// TicketListVO - 工单列表(简化版)
|
||||
export interface TicketListVO extends BaseVO {
|
||||
/**
|
||||
* 工单过程表DTO
|
||||
*/
|
||||
export interface TbWorkcaseProcessDTO extends BaseDTO {
|
||||
/** 工单ID */
|
||||
ticketId?: string
|
||||
/** 工单编号 */
|
||||
ticketNo?: string
|
||||
/** 客户姓名 */
|
||||
customerName?: string
|
||||
/** 工单标题 */
|
||||
title?: string
|
||||
/** 工单类型名称 */
|
||||
ticketTypeName?: string
|
||||
/** 优先级 */
|
||||
priority?: string
|
||||
/** 优先级名称 */
|
||||
priorityName?: string
|
||||
/** 工单状态 */
|
||||
ticketStatus?: string
|
||||
/** 工单状态名称 */
|
||||
ticketStatusName?: string
|
||||
/** 处理人姓名 */
|
||||
assignedToName?: string
|
||||
/** SLA截止时间 */
|
||||
slaDeadline?: string
|
||||
/** 是否逾期 */
|
||||
isOverdue?: boolean
|
||||
/** 创建者姓名 */
|
||||
creatorName?: string
|
||||
workcaseId?: string
|
||||
/** 过程ID */
|
||||
processId?: string
|
||||
/** 动作 info:记录,assign:指派,redeploy:转派,repeal:撤销,finish:完成 */
|
||||
action?: 'info' | 'assign' | 'redeploy' | 'repeal' | 'finish'
|
||||
/** 消息 */
|
||||
message?: string
|
||||
/** 携带文件列表 */
|
||||
files?: string[]
|
||||
/** 处理人(指派、转派专属) */
|
||||
processor?: string
|
||||
}
|
||||
|
||||
// TbTicketDTO - 工单DTO(创建和更新)
|
||||
export interface TbTicketDTO extends BaseDTO {
|
||||
/** 工单ID(更新时需要) */
|
||||
ticketId?: string
|
||||
/** 工单编号 */
|
||||
ticketNo?: string
|
||||
/** 客户ID */
|
||||
customerId?: string
|
||||
/** 关联会话ID */
|
||||
conversationId?: string
|
||||
/** 工单类型 */
|
||||
ticketType?: string
|
||||
/** 工单分类 */
|
||||
ticketCategory?: string
|
||||
/** 优先级 */
|
||||
priority?: string
|
||||
/** 工单标题 */
|
||||
title?: string
|
||||
/** 问题描述 */
|
||||
description?: string
|
||||
/** 附件ID数组 */
|
||||
attachments?: string[]
|
||||
/** 工单来源 */
|
||||
ticketSource?: string
|
||||
/** 分配给(处理人) */
|
||||
assignedTo?: string
|
||||
/** 分配部门 */
|
||||
assignedDept?: string
|
||||
/** 工单状态 */
|
||||
ticketStatus?: string
|
||||
/** 解决方案 */
|
||||
resolution?: string
|
||||
/** 解决时间 */
|
||||
resolutionTime?: string
|
||||
/** 关闭时间 */
|
||||
closeTime?: string
|
||||
/** 首次响应时间 */
|
||||
responseTime?: string
|
||||
/** SLA截止时间 */
|
||||
slaDeadline?: string
|
||||
/** 是否逾期 */
|
||||
isOverdue?: boolean
|
||||
/** 客户评分(1-5星) */
|
||||
customerRating?: number
|
||||
/** 客户反馈 */
|
||||
customerFeedback?: string
|
||||
/** CRM系统工单ID */
|
||||
crmTicketId?: string
|
||||
/** 同步状态 */
|
||||
syncStatus?: string
|
||||
/** 工单标签 */
|
||||
tags?: string[]
|
||||
/** 工单元数据 */
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
// CustomerVO - 客户信息
|
||||
export interface CustomerVO extends BaseVO {
|
||||
/** 客户ID */
|
||||
customerId?: string
|
||||
/** 客户编号 */
|
||||
customerNo?: string
|
||||
/** 客户姓名 */
|
||||
customerName?: string
|
||||
/** 客户类型 */
|
||||
customerType?: string
|
||||
/** 公司名称 */
|
||||
companyName?: string
|
||||
/** 电话 */
|
||||
phone?: string
|
||||
/** 邮箱 */
|
||||
email?: string
|
||||
/** 微信OpenID */
|
||||
wechatOpenid?: string
|
||||
/** 微信UnionID */
|
||||
wechatUnionid?: string
|
||||
/** 头像URL */
|
||||
avatar?: string
|
||||
/** 性别 */
|
||||
gender?: number
|
||||
/** 地址 */
|
||||
address?: string
|
||||
/** 客户等级 */
|
||||
customerLevel?: string
|
||||
/** 客户来源 */
|
||||
customerSource?: string
|
||||
/** 客户标签数组 */
|
||||
tags?: string[]
|
||||
/** 备注 */
|
||||
notes?: string
|
||||
/** CRM系统客户ID */
|
||||
crmCustomerId?: string
|
||||
/** 最后联系时间 */
|
||||
lastContactTime?: string
|
||||
/** 咨询总次数 */
|
||||
totalConsultations?: number
|
||||
/** 订单总数 */
|
||||
totalOrders?: number
|
||||
/** 总消费金额 */
|
||||
totalAmount?: number
|
||||
/** 满意度评分(1-5) */
|
||||
satisfactionScore?: number
|
||||
/** 状态 */
|
||||
status?: string
|
||||
}
|
||||
|
||||
// TbCustomerDTO - 客户DTO(创建和更新)
|
||||
export interface TbCustomerDTO extends BaseDTO {
|
||||
/** 客户ID(更新时需要) */
|
||||
customerId?: string
|
||||
/** 客户编号 */
|
||||
customerNo?: string
|
||||
/** 客户姓名 */
|
||||
customerName?: string
|
||||
/** 客户类型 */
|
||||
customerType?: string
|
||||
/** 公司名称 */
|
||||
companyName?: string
|
||||
/** 电话 */
|
||||
phone?: string
|
||||
/** 邮箱 */
|
||||
email?: string
|
||||
/** 微信OpenID */
|
||||
wechatOpenid?: string
|
||||
/** 微信UnionID */
|
||||
wechatUnionid?: string
|
||||
/** 头像URL */
|
||||
avatar?: string
|
||||
/** 性别 */
|
||||
gender?: number
|
||||
/** 地址 */
|
||||
address?: string
|
||||
/** 客户等级 */
|
||||
customerLevel?: string
|
||||
/** 客户来源 */
|
||||
customerSource?: string
|
||||
/** 客户标签数组 */
|
||||
tags?: string[]
|
||||
/** 备注 */
|
||||
notes?: string
|
||||
/** CRM系统客户ID */
|
||||
crmCustomerId?: string
|
||||
/** 最后联系时间 */
|
||||
lastContactTime?: string
|
||||
/** 咨询总次数 */
|
||||
totalConsultations?: number
|
||||
/** 订单总数 */
|
||||
totalOrders?: number
|
||||
/** 总消费金额 */
|
||||
totalAmount?: number
|
||||
/** 满意度评分(1-5) */
|
||||
satisfactionScore?: number
|
||||
/** 状态 */
|
||||
status?: string
|
||||
/**
|
||||
* 工单设备涉及的文件DTO
|
||||
*/
|
||||
export interface TbWorkcaseDeviceDTO extends BaseDTO {
|
||||
/** 工单ID */
|
||||
workcaseId?: string
|
||||
/** 设备名称 */
|
||||
device?: string
|
||||
/** 设备代码 */
|
||||
deviceCode?: string
|
||||
/** 文件ID */
|
||||
fileId?: string
|
||||
/** 文件名 */
|
||||
fileName?: string
|
||||
/** 文件根ID */
|
||||
fileRootId?: string
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ export default defineConfig({
|
||||
'./api': './src/api/index.ts',
|
||||
'./api/auth': './src/api/auth/auth.ts',
|
||||
'./api/file': './src/api/file/file.ts',
|
||||
'./api/workcase': './src/api/workcase/index.ts',
|
||||
'./api/ai': './src/api/ai/index.ts',
|
||||
|
||||
// ========== Utils 工具模块 ==========
|
||||
'./utils': './src/utils/index.ts',
|
||||
@@ -56,6 +58,8 @@ export default defineConfig({
|
||||
'./types/auth': './src/types/auth/index.ts',
|
||||
'./types/file': './src/types/file/index.ts',
|
||||
'./types/sys': './src/types/sys/index.ts',
|
||||
'./types/workcase': './src/types/workcase/index.ts',
|
||||
'./types/ai': './src/types/ai/index.ts',
|
||||
|
||||
// ========== Config 配置模块 ==========
|
||||
'./config': './src/config/index.ts',
|
||||
|
||||
@@ -32,6 +32,9 @@ declare module 'shared/components/iframe/IframeView.vue' {
|
||||
declare module 'shared/api' {
|
||||
export const api: any
|
||||
export const TokenManager: any
|
||||
export const authAPI: any
|
||||
export const fileAPI: any
|
||||
export const workcaseAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/api/auth' {
|
||||
@@ -42,27 +45,64 @@ declare module 'shared/api/file' {
|
||||
export const fileAPI: any
|
||||
}
|
||||
|
||||
// 保留旧的导出路径(向后兼容)
|
||||
declare module 'shared/FileUpload' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const FileUpload: DefineComponent<{}, {}, any>
|
||||
export default FileUpload
|
||||
declare module 'shared/api/ai' {
|
||||
export const agentAPI: any
|
||||
export const aiKnowledgeAPI: any
|
||||
export const aiChatAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/DynamicFormItem' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const DynamicFormItem: DefineComponent<{}, {}, any>
|
||||
export default DynamicFormItem
|
||||
declare module 'shared/api/workcase' {
|
||||
export const workcaseAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/authAPI' {
|
||||
export const authAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/fileAPI' {
|
||||
export const fileAPI: any
|
||||
// ============ types模块 ==================
|
||||
declare module 'shared/types' {
|
||||
import type { BaseDTO } from '../../../shared/src/types/base'
|
||||
|
||||
// 重新导出 base
|
||||
export type { BaseDTO }
|
||||
|
||||
// 重新导出 response
|
||||
export type { ResultDomain } from '../../../shared/src/types/response'
|
||||
|
||||
// 重新导出 page
|
||||
export type { PageDomain, PageParam, PageRequest } from '../../../shared/src/types/page'
|
||||
|
||||
// 重新导出 auth
|
||||
export type { LoginParam, LoginDomain } from '../../../shared/src/types/auth'
|
||||
|
||||
// 重新导出 sys
|
||||
export type { SysUserVO, SysConfigVO, TbSysViewDTO } from '../../../shared/src/types/sys'
|
||||
|
||||
// 重新导出 file
|
||||
export type { TbSysFileDTO } from '../../../shared/src/types/file'
|
||||
|
||||
// 重新导出 ai
|
||||
export type {
|
||||
TbKnowledge,
|
||||
TbKnowledgeFile,
|
||||
TbAgent,
|
||||
PromptCard,
|
||||
TbChat,
|
||||
TbChatMessage,
|
||||
DifyFileInfo,
|
||||
ChatPrepareData,
|
||||
StopChatParams,
|
||||
CommentMessageParams
|
||||
} from '../../../shared/src/types/ai'
|
||||
|
||||
// 重新导出 workcase
|
||||
export type {
|
||||
TbWorkcaseDTO,
|
||||
TbWorkcaseProcessDTO,
|
||||
TbWorkcaseDeviceDTO
|
||||
} from '../../../shared/src/types/workcase'
|
||||
|
||||
// 重新导出 menu
|
||||
export type { MenuItem, toMenuItem, toMenuItems } from '../../../shared/src/types/menu'
|
||||
}
|
||||
|
||||
// ================ utils工具 ==========================
|
||||
declare module 'shared/utils' {
|
||||
export const initAesEncrypt: any
|
||||
export const getAesInstance: any
|
||||
@@ -73,47 +113,6 @@ declare module 'shared/utils' {
|
||||
export const getFilePreviewUrl: any
|
||||
}
|
||||
|
||||
declare module 'shared/types' {
|
||||
import { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export type LoginParam = any
|
||||
export type LoginDomain = any
|
||||
export type SysUserVO = any
|
||||
export type TbSysFileDTO = any
|
||||
export type SysConfigVO = any
|
||||
export type ResultDomain<T = any> = any
|
||||
|
||||
// 视图类型(用于路由和菜单)
|
||||
export interface TbSysViewDTO {
|
||||
viewId?: string
|
||||
name?: string
|
||||
parentId?: string
|
||||
url?: string
|
||||
component?: string
|
||||
iframeUrl?: string
|
||||
icon?: string
|
||||
type?: number
|
||||
viewType?: string
|
||||
service?: string
|
||||
layout?: string
|
||||
orderNum?: number
|
||||
description?: string
|
||||
children?: TbSysViewDTO[]
|
||||
}
|
||||
|
||||
// 菜单项类型(扩展 TbSysViewDTO)
|
||||
export interface MenuItem extends TbSysViewDTO {
|
||||
key: string
|
||||
label: string
|
||||
expanded?: boolean
|
||||
children?: MenuItem[]
|
||||
}
|
||||
|
||||
// 菜单工具函数
|
||||
export function toMenuItem(view: TbSysViewDTO, expanded?: boolean): MenuItem
|
||||
export function toMenuItems(views: TbSysViewDTO[], defaultExpanded?: boolean): MenuItem[]
|
||||
}
|
||||
|
||||
declare module 'shared/utils/route' {
|
||||
import { RouteRecordRaw } from 'vue-router'
|
||||
import type { TbSysViewDTO } from 'shared/types'
|
||||
|
||||
@@ -9,99 +9,62 @@
|
||||
|
||||
<div class="knowledge-container">
|
||||
<el-card>
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="外部知识库" name="external">
|
||||
<p class="tab-desc">面向客户的知识库内容,包含设备操作指南、故障解决方案等</p>
|
||||
|
||||
<div class="kb-categories">
|
||||
<div v-for="cat in externalCategories" :key="cat.key" class="kb-category-card"
|
||||
:class="{ active: activeExternalCat === cat.key }" @click="activeExternalCat = cat.key">
|
||||
<el-icon :style="{ color: cat.color }"><component :is="cat.icon" /></el-icon>
|
||||
<span class="cat-name">{{ cat.name }}</span>
|
||||
<span class="cat-count">{{ cat.count }} 个文件</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kb-files-section">
|
||||
<div class="section-toolbar">
|
||||
<h3>{{ currentExternalCatName }}</h3>
|
||||
<el-input v-model="externalSearch" placeholder="搜索文件名" style="width: 240px;" :prefix-icon="Search" clearable />
|
||||
</div>
|
||||
|
||||
<el-table :data="filteredExternalFiles" style="width: 100%">
|
||||
<el-table-column prop="name" label="文件名" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<div class="file-name-cell">
|
||||
<el-icon class="file-icon"><Document /></el-icon>
|
||||
<span>{{ row.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploader" label="上传人员" width="120" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="180" />
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="previewFile(row)">
|
||||
<el-icon><View /></el-icon>预览
|
||||
</el-button>
|
||||
<el-button type="success" link size="small" @click="downloadFile(row)">
|
||||
<el-icon><Download /></el-icon>下载
|
||||
</el-button>
|
||||
<el-button type="danger" link size="small" @click="deleteFile(row)">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="内部知识库" name="internal">
|
||||
<p class="tab-desc">内部技术资料与服务规范,仅供内部员工使用</p>
|
||||
|
||||
<div class="kb-categories">
|
||||
<div v-for="cat in internalCategories" :key="cat.key" class="kb-category-card"
|
||||
:class="{ active: activeInternalCat === cat.key }" @click="activeInternalCat = cat.key">
|
||||
<el-icon :style="{ color: cat.color }"><component :is="cat.icon" /></el-icon>
|
||||
<span class="cat-name">{{ cat.name }}</span>
|
||||
<span class="cat-count">{{ cat.count }} 个文件</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kb-files-section">
|
||||
<div class="section-toolbar">
|
||||
<h3>{{ currentInternalCatName }}</h3>
|
||||
<el-input v-model="internalSearch" placeholder="搜索文件名" style="width: 240px;" :prefix-icon="Search" clearable />
|
||||
</div>
|
||||
|
||||
<el-table :data="filteredInternalFiles" style="width: 100%">
|
||||
<el-table-column prop="name" label="文件名" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<div class="file-name-cell">
|
||||
<el-icon class="file-icon"><Document /></el-icon>
|
||||
<span>{{ row.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploader" label="上传人员" width="120" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="180" />
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="previewFile(row)">
|
||||
<el-icon><View /></el-icon>预览
|
||||
</el-button>
|
||||
<el-button type="success" link size="small" @click="downloadFile(row)">
|
||||
<el-icon><Download /></el-icon>下载
|
||||
</el-button>
|
||||
<el-button type="danger" link size="small" @click="deleteFile(row)">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
|
||||
<el-tab-pane v-for="tab in tabConfig" :key="tab.name" :label="tab.label" :name="tab.name" />
|
||||
</el-tabs>
|
||||
|
||||
<p class="tab-desc">{{ currentTabDesc }}</p>
|
||||
|
||||
<div class="kb-categories">
|
||||
<div v-for="kb in currentKnowledges" :key="kb.knowledgeId" class="kb-category-card"
|
||||
:class="{ active: activeKnowledgeId === kb.knowledgeId }" @click="selectKnowledge(kb.knowledgeId || '')">
|
||||
<el-icon :style="{ color: currentTabColor }"><Document /></el-icon>
|
||||
<span class="cat-name">{{ kb.title }}</span>
|
||||
<span class="cat-count">{{ kb.documentCount || 0 }} 个文件</span>
|
||||
</div>
|
||||
<el-empty v-if="currentKnowledges.length === 0" :description="'暂无' + currentTabLabel" :image-size="60" />
|
||||
</div>
|
||||
|
||||
<div class="kb-files-section">
|
||||
<div class="section-toolbar">
|
||||
<h3>{{ currentKnowledgeName }}</h3>
|
||||
<el-input v-model="searchKeyword" placeholder="搜索文件名" style="width: 240px;" :prefix-icon="Search" clearable />
|
||||
</div>
|
||||
|
||||
<el-table :data="filteredDocuments" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="name" label="文件名" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<div class="file-name-cell">
|
||||
<el-icon class="file-icon"><Document /></el-icon>
|
||||
<span>{{ row.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploader" label="上传人员" width="120" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="180" />
|
||||
<el-table-column prop="wordCount" label="字数" width="100" />
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.enabled ? 'success' : 'info'" size="small">
|
||||
{{ row.enabled ? '已启用' : '已禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="previewFile(row)">
|
||||
<el-icon><View /></el-icon>预览
|
||||
</el-button>
|
||||
<el-button type="success" link size="small" @click="downloadFile(row)">
|
||||
<el-icon><Download /></el-icon>下载
|
||||
</el-button>
|
||||
<el-button type="danger" link size="small" @click="deleteFile(row)">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
@@ -109,125 +72,258 @@
|
||||
<el-dialog v-model="showUploadDialog" title="上传文档" width="500px">
|
||||
<el-form :model="uploadForm" label-width="80px">
|
||||
<el-form-item label="知识库" required>
|
||||
<el-select v-model="uploadForm.kbType" placeholder="请选择知识库">
|
||||
<el-option label="外部知识库" value="external" />
|
||||
<el-option label="内部知识库" value="internal" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" required>
|
||||
<el-select v-model="uploadForm.category" placeholder="请选择分类">
|
||||
<el-option label="操作指南" value="guide" />
|
||||
<el-option label="故障排查" value="troubleshoot" />
|
||||
<el-option label="技术规范" value="spec" />
|
||||
<el-select v-model="uploadForm.knowledgeId" placeholder="请选择知识库" style="width: 100%">
|
||||
<el-option
|
||||
v-for="kb in knowledges"
|
||||
:key="kb.knowledgeId"
|
||||
:label="kb.title"
|
||||
:value="kb.knowledgeId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件" required>
|
||||
<el-upload action="#" :auto-upload="false" drag>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<el-upload
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:limit="1"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><Upload /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
拖拽文件到此或 <em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 PDF、Word、TXT 等文档格式</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showUploadDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="uploadFile">上传</el-button>
|
||||
<el-button type="primary" @click="handleUpload" :loading="uploading">上传</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import AdminLayout from '@/views/admin/AdminLayout.vue'
|
||||
import { Upload, Search, Document, View, Download, Delete } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox, type UploadFile } from 'element-plus'
|
||||
import { aiKnowledgeAPI } from 'shared/api/ai'
|
||||
import type { TbKnowledge } from 'shared/types'
|
||||
|
||||
// Tab 配置
|
||||
const tabConfig = [
|
||||
{ name: 'external', label: '外部知识库', desc: '面向客户的知识库内容,包含设备操作指南、故障解决方案等', color: '#409eff' },
|
||||
{ name: 'internal', label: '内部知识库', desc: '内部技术资料与服务规范,仅供内部员工使用', color: '#67c23a' }
|
||||
]
|
||||
|
||||
const activeTab = ref('external')
|
||||
const activeExternalCat = ref('guide')
|
||||
const activeInternalCat = ref('spec')
|
||||
const externalSearch = ref('')
|
||||
const internalSearch = ref('')
|
||||
const searchKeyword = ref('')
|
||||
const showUploadDialog = ref(false)
|
||||
const loading = ref(false)
|
||||
const uploading = ref(false)
|
||||
|
||||
// 知识库列表
|
||||
const knowledges = ref<TbKnowledge[]>([])
|
||||
const activeKnowledgeId = ref('')
|
||||
|
||||
// 文档列表
|
||||
interface DocumentItem {
|
||||
id: string
|
||||
name: string
|
||||
uploader: string
|
||||
uploadTime: string
|
||||
position: number
|
||||
dataSourceType: string
|
||||
wordCount: number
|
||||
hitCount: number
|
||||
indexingStatus: string
|
||||
enabled: boolean
|
||||
}
|
||||
const documents = ref<DocumentItem[]>([])
|
||||
|
||||
const uploadForm = ref({
|
||||
kbType: '',
|
||||
category: '',
|
||||
file: null
|
||||
knowledgeId: '',
|
||||
file: null as File | null
|
||||
})
|
||||
|
||||
const externalCategories = ref([
|
||||
{ key: 'guide', name: '操作指南', count: 12, color: '#409eff', icon: 'Document' },
|
||||
{ key: 'troubleshoot', name: '故障排查', count: 8, color: '#67c23a', icon: 'Document' },
|
||||
{ key: 'spec', name: '技术规范', count: 5, color: '#e6a23c', icon: 'Document' }
|
||||
])
|
||||
// 当前 Tab 配置
|
||||
const currentTabConfig = computed(() => tabConfig.find(t => t.name === activeTab.value) || tabConfig[0])
|
||||
const currentTabDesc = computed(() => currentTabConfig.value.desc)
|
||||
const currentTabLabel = computed(() => currentTabConfig.value.label)
|
||||
const currentTabColor = computed(() => currentTabConfig.value.color)
|
||||
|
||||
const internalCategories = ref([
|
||||
{ key: 'spec', name: '技术规范', count: 15, color: '#409eff', icon: 'Document' },
|
||||
{ key: 'process', name: '流程文档', count: 10, color: '#67c23a', icon: 'Document' },
|
||||
{ key: 'training', name: '培训资料', count: 7, color: '#e6a23c', icon: 'Document' }
|
||||
])
|
||||
// 当前 Tab 下的知识库列表
|
||||
const currentKnowledges = computed(() =>
|
||||
knowledges.value.filter((kb: TbKnowledge) => kb.category === activeTab.value)
|
||||
)
|
||||
|
||||
const externalFiles = ref([
|
||||
{ name: 'TH-500GF操作手册.pdf', uploader: '张三', uploadTime: '2024-12-10 14:30', category: 'guide' },
|
||||
{ name: 'TH-300D故障排查指南.pdf', uploader: '李四', uploadTime: '2024-12-09 10:15', category: 'troubleshoot' },
|
||||
{ name: '设备安装规范.pdf', uploader: '王五', uploadTime: '2024-12-08 09:45', category: 'spec' }
|
||||
])
|
||||
|
||||
const internalFiles = ref([
|
||||
{ name: '内部技术规范v2.0.pdf', uploader: '赵六', uploadTime: '2024-12-11 16:20', category: 'spec' },
|
||||
{ name: '售后服务流程.pdf', uploader: '孙七', uploadTime: '2024-12-10 11:00', category: 'process' },
|
||||
{ name: '员工培训手册.pdf', uploader: '周八', uploadTime: '2024-12-09 15:30', category: 'training' }
|
||||
])
|
||||
|
||||
const currentExternalCatName = computed(() => {
|
||||
const cat = externalCategories.value.find(c => c.key === activeExternalCat.value)
|
||||
return cat?.name || '操作指南'
|
||||
// 当前选中的知识库名称
|
||||
const currentKnowledgeName = computed(() => {
|
||||
const kb = knowledges.value.find((k: TbKnowledge) => k.knowledgeId === activeKnowledgeId.value)
|
||||
return kb?.title || '请选择知识库'
|
||||
})
|
||||
|
||||
const currentInternalCatName = computed(() => {
|
||||
const cat = internalCategories.value.find(c => c.key === activeInternalCat.value)
|
||||
return cat?.name || '技术规范'
|
||||
// 搜索过滤后的文档列表
|
||||
const filteredDocuments = computed(() => {
|
||||
if (!searchKeyword.value) return documents.value
|
||||
return documents.value.filter(f =>
|
||||
f.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
const filteredExternalFiles = computed(() => {
|
||||
return externalFiles.value.filter(f => {
|
||||
const matchCat = f.category === activeExternalCat.value
|
||||
const matchSearch = !externalSearch.value || f.name.toLowerCase().includes(externalSearch.value.toLowerCase())
|
||||
return matchCat && matchSearch
|
||||
})
|
||||
// 获取知识库列表
|
||||
const fetchKnowledges = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await aiKnowledgeAPI.listKnowledges({ service: 'workcase' })
|
||||
if (result.success && Array.isArray(result.data)) {
|
||||
knowledges.value = result.data
|
||||
// 默认选中当前 Tab 下的第一个知识库
|
||||
selectFirstKnowledge()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取知识库列表失败:', error)
|
||||
ElMessage.error('获取知识库列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 选中当前 Tab 下的第一个知识库
|
||||
const selectFirstKnowledge = () => {
|
||||
const firstKb = currentKnowledges.value[0]
|
||||
if (firstKb?.knowledgeId) {
|
||||
activeKnowledgeId.value = firstKb.knowledgeId
|
||||
} else {
|
||||
activeKnowledgeId.value = ''
|
||||
documents.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取文档列表
|
||||
const fetchDocuments = async (knowledgeId: string) => {
|
||||
if (!knowledgeId) {
|
||||
documents.value = []
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await aiKnowledgeAPI.getDocumentList(knowledgeId, 1, 100)
|
||||
if (result.success && result.data) {
|
||||
documents.value = (result.data.data || []).map((doc: any) => ({
|
||||
id: doc.id,
|
||||
name: doc.name,
|
||||
uploader: doc.created_by || '-',
|
||||
uploadTime: doc.created_at ? new Date(doc.created_at * 1000).toLocaleString() : '-',
|
||||
position: doc.position,
|
||||
dataSourceType: doc.data_source_type,
|
||||
wordCount: doc.word_count || 0,
|
||||
hitCount: doc.hit_count || 0,
|
||||
indexingStatus: doc.indexing_status,
|
||||
enabled: doc.enabled
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取文档列表失败:', error)
|
||||
ElMessage.error('获取文档列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 切换
|
||||
const handleTabChange = () => {
|
||||
searchKeyword.value = ''
|
||||
selectFirstKnowledge()
|
||||
}
|
||||
|
||||
// 选择知识库
|
||||
const selectKnowledge = (knowledgeId: string) => {
|
||||
activeKnowledgeId.value = knowledgeId
|
||||
}
|
||||
|
||||
// 监听知识库选择变化
|
||||
watch(activeKnowledgeId, (newVal) => {
|
||||
if (newVal) fetchDocuments(newVal)
|
||||
})
|
||||
|
||||
const filteredInternalFiles = computed(() => {
|
||||
return internalFiles.value.filter(f => {
|
||||
const matchCat = f.category === activeInternalCat.value
|
||||
const matchSearch = !internalSearch.value || f.name.toLowerCase().includes(internalSearch.value.toLowerCase())
|
||||
return matchCat && matchSearch
|
||||
})
|
||||
})
|
||||
|
||||
const previewFile = (row: any) => {
|
||||
const previewFile = (row: DocumentItem) => {
|
||||
ElMessage.info(`预览文件: ${row.name}`)
|
||||
}
|
||||
|
||||
const downloadFile = (row: any) => {
|
||||
const downloadFile = (row: DocumentItem) => {
|
||||
ElMessage.success(`下载文件: ${row.name}`)
|
||||
}
|
||||
|
||||
const deleteFile = (row: any) => {
|
||||
ElMessage.warning(`删除文件: ${row.name}`)
|
||||
const deleteFile = async (row: DocumentItem) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定要删除文件 "${row.name}" 吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
const result = await aiKnowledgeAPI.deleteFile(row.id)
|
||||
if (result.success) {
|
||||
ElMessage.success('删除成功')
|
||||
fetchDocuments(activeKnowledgeId.value)
|
||||
} else {
|
||||
ElMessage.error(result.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除文件失败:', error)
|
||||
ElMessage.error('删除文件失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uploadFile = () => {
|
||||
if (!uploadForm.value.kbType || !uploadForm.value.category) {
|
||||
ElMessage.error('请填写必填项')
|
||||
const handleFileChange = (uploadFile: UploadFile) => {
|
||||
uploadForm.value.file = uploadFile.raw || null
|
||||
}
|
||||
|
||||
const handleUpload = async () => {
|
||||
if (!uploadForm.value.knowledgeId) {
|
||||
ElMessage.error('请选择知识库')
|
||||
return
|
||||
}
|
||||
if (!uploadForm.value.file) {
|
||||
ElMessage.error('请选择要上传的文件')
|
||||
return
|
||||
}
|
||||
const targetKnowledgeId = uploadForm.value.knowledgeId
|
||||
uploading.value = true
|
||||
try {
|
||||
const result = await aiKnowledgeAPI.uploadToKnowledge(
|
||||
uploadForm.value.file,
|
||||
targetKnowledgeId
|
||||
)
|
||||
if (result.success) {
|
||||
ElMessage.success('文件上传成功')
|
||||
showUploadDialog.value = false
|
||||
uploadForm.value = { kbType: '', category: '', file: null }
|
||||
uploadForm.value = { knowledgeId: '', file: null }
|
||||
fetchKnowledges()
|
||||
if (targetKnowledgeId === activeKnowledgeId.value) {
|
||||
fetchDocuments(activeKnowledgeId.value)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(result.message || '上传失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传文件失败:', error)
|
||||
ElMessage.error('上传文件失败')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchKnowledges()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="pending">待处理</el-radio-button>
|
||||
<el-radio-button label="processing">处理中</el-radio-button>
|
||||
<el-radio-button label="completed">已完成</el-radio-button>
|
||||
<el-radio-button label="closed">已关闭</el-radio-button>
|
||||
<el-radio-button label="done">已完成</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<div class="filter-right">
|
||||
@@ -29,9 +28,8 @@
|
||||
<el-option label="其他" value="other" />
|
||||
</el-select>
|
||||
<el-select v-model="urgencyFilter" placeholder="紧急程度" clearable style="width: 120px;">
|
||||
<el-option label="紧急" value="urgent" />
|
||||
<el-option label="紧急" value="emergency" />
|
||||
<el-option label="普通" value="normal" />
|
||||
<el-option label="低" value="low" />
|
||||
</el-select>
|
||||
<el-input v-model="searchKeyword" placeholder="搜索工单号/客户/设备" style="width: 200px;" :prefix-icon="Search" clearable />
|
||||
</div>
|
||||
@@ -41,42 +39,42 @@
|
||||
<!-- 工单列表 -->
|
||||
<el-card>
|
||||
<el-table :data="filteredTickets" style="width: 100%">
|
||||
<el-table-column prop="ticketNo" label="工单号" width="140">
|
||||
<el-table-column prop="optsn" label="工单号" width="140">
|
||||
<template #default="{ row }">
|
||||
<span class="ticket-no">{{ row.ticketNo }}</span>
|
||||
<span class="ticket-no">{{ row.optsn }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="customerName" label="客户信息" width="150">
|
||||
<el-table-column prop="username" label="客户信息" width="150">
|
||||
<template #default="{ row }">
|
||||
<div class="customer-info">
|
||||
<span class="name">{{ row.customerName }}</span>
|
||||
<span class="phone">{{ row.customerPhone }}</span>
|
||||
<span class="name">{{ row.username }}</span>
|
||||
<span class="phone">{{ row.phone }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="faultType" label="故障类型" width="120">
|
||||
<el-table-column prop="type" label="故障类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">{{ row.faultTypeName }}</el-tag>
|
||||
<el-tag size="small">{{ typeMap[row.type] || row.type }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deviceModel" label="设备型号" width="120" />
|
||||
<el-table-column prop="urgency" label="紧急程度" width="100">
|
||||
<el-table-column prop="device" label="设备型号" width="120" />
|
||||
<el-table-column prop="emergency" label="紧急程度" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.urgency === 'urgent' ? 'danger' : row.urgency === 'normal' ? 'warning' : 'info'" size="small">
|
||||
{{ row.urgencyName }}
|
||||
<el-tag :type="row.emergency === 'emergency' ? 'danger' : 'warning'" size="small">
|
||||
{{ emergencyMap[row.emergency] || row.emergency }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.status)" size="small">
|
||||
{{ row.statusName }}
|
||||
{{ statusMap[row.status] || row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="assignee" label="处理人" width="100">
|
||||
<el-table-column prop="processor" label="处理人" width="100">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.assignee">{{ row.assignee }}</span>
|
||||
<span v-if="row.processor">{{ row.processor }}</span>
|
||||
<span v-else class="unassigned">未指派</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -91,7 +89,7 @@
|
||||
</el-table>
|
||||
|
||||
<div class="table-pagination">
|
||||
<el-pagination v-model:current-page="currentPage" :page-size="10" :total="tickets.length" layout="total, prev, pager, next" />
|
||||
<el-pagination v-model:current-page="currentPage" :page-size="pageSize" :total="total" layout="total, prev, pager, next" @current-change="handlePageChange" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -102,25 +100,47 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户姓名" required>
|
||||
<el-input v-model="formData.customerName" placeholder="请输入客户姓名" />
|
||||
<el-input v-model="formData.username" placeholder="请输入客户姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话" required>
|
||||
<el-input v-model="formData.customerPhone" placeholder="请输入联系电话" />
|
||||
<el-input v-model="formData.phone" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="故障类型" required>
|
||||
<el-select v-model="formData.type" placeholder="请选择故障类型" style="width: 100%;">
|
||||
<el-option label="电气系统故障" value="electrical" />
|
||||
<el-option label="机械故障" value="mechanical" />
|
||||
<el-option label="控制系统故障" value="control" />
|
||||
<el-option label="配件更换" value="parts" />
|
||||
<el-option label="安装调试" value="install" />
|
||||
<el-option label="其他" value="other" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="紧急程度" required>
|
||||
<el-select v-model="formData.emergency" placeholder="请选择紧急程度" style="width: 100%;">
|
||||
<el-option label="紧急" value="emergency" />
|
||||
<el-option label="普通" value="normal" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="设备型号" required>
|
||||
<el-select v-model="formData.deviceModel" placeholder="请选择设备型号" style="width: 100%;">
|
||||
<el-select v-model="formData.device" placeholder="请选择设备型号" style="width: 100%;">
|
||||
<el-option label="TH-500GF" value="TH-500GF" />
|
||||
<el-option label="TH-300D" value="TH-300D" />
|
||||
<el-option label="TH-800GF" value="TH-800GF" />
|
||||
<el-option label="S-200X" value="S-200X" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="故障描述" required>
|
||||
<el-input v-model="formData.description" type="textarea" rows="4" placeholder="请输入故障描述" />
|
||||
<el-form-item label="故障描述">
|
||||
<el-input v-model="formData.remark" type="textarea" rows="4" placeholder="请输入故障描述" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -132,90 +152,247 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import AdminLayout from '@/views/admin/AdminLayout.vue'
|
||||
import { Plus, Search } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { workcaseAPI } from 'shared/api'
|
||||
import type { TbWorkcaseDTO, TbWorkcaseProcessDTO } from 'shared/types'
|
||||
import type { PageParam } from 'shared/types'
|
||||
|
||||
const statusFilter = ref('all')
|
||||
const typeFilter = ref('')
|
||||
const urgencyFilter = ref('')
|
||||
const searchKeyword = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const showCreateDialog = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const formData = ref({
|
||||
customerName: '',
|
||||
customerPhone: '',
|
||||
deviceModel: '',
|
||||
description: ''
|
||||
const formData = ref<TbWorkcaseDTO>({
|
||||
username: '',
|
||||
phone: '',
|
||||
device: '',
|
||||
type: '',
|
||||
emergency: 'normal',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const tickets = ref([
|
||||
{ ticketNo: 'TK001', customerName: '张三', customerPhone: '13800138000', faultType: 'electrical', faultTypeName: '电气系统故障', deviceModel: 'TH-500GF', urgency: 'urgent', urgencyName: '紧急', status: 'pending', statusName: '待处理', assignee: '', createTime: '2024-12-13 10:30' },
|
||||
{ ticketNo: 'TK002', customerName: '李四', customerPhone: '13800138001', faultType: 'mechanical', faultTypeName: '机械故障', deviceModel: 'TH-300D', urgency: 'normal', urgencyName: '普通', status: 'processing', statusName: '处理中', assignee: '王五', createTime: '2024-12-13 09:15' },
|
||||
{ ticketNo: 'TK003', customerName: '王五', customerPhone: '13800138002', faultType: 'control', faultTypeName: '控制系统故障', deviceModel: 'S-200X', urgency: 'low', urgencyName: '低', status: 'completed', statusName: '已完成', assignee: '赵六', createTime: '2024-12-12 14:20' },
|
||||
{ ticketNo: 'TK004', customerName: '赵六', customerPhone: '13800138003', faultType: 'parts', faultTypeName: '配件更换', deviceModel: 'TH-800GF', urgency: 'normal', urgencyName: '普通', status: 'pending', statusName: '待处理', assignee: '', createTime: '2024-12-13 11:00' },
|
||||
{ ticketNo: 'TK005', customerName: '孙七', customerPhone: '13800138004', faultType: 'install', faultTypeName: '安装调试', deviceModel: 'G-100S', urgency: 'urgent', urgencyName: '紧急', status: 'processing', statusName: '处理中', assignee: '李四', createTime: '2024-12-13 08:45' }
|
||||
])
|
||||
const workcaseList = ref<TbWorkcaseDTO[]>([])
|
||||
|
||||
// ========================= API 调用方法 =========================
|
||||
/**
|
||||
* 加载工单列表
|
||||
*/
|
||||
const loadWorkcases = async () => {
|
||||
loading.value = true
|
||||
const filter: TbWorkcaseDTO = {}
|
||||
if (statusFilter.value !== 'all') {
|
||||
filter.status = statusFilter.value as TbWorkcaseDTO['status']
|
||||
}
|
||||
if (typeFilter.value) {
|
||||
filter.type = typeFilter.value
|
||||
}
|
||||
if (urgencyFilter.value) {
|
||||
filter.emergency = urgencyFilter.value as TbWorkcaseDTO['emergency']
|
||||
}
|
||||
|
||||
const pageParam: PageParam = {
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value
|
||||
}
|
||||
|
||||
const res = await workcaseAPI.getWorkcasePage({ filter, pageParam })
|
||||
if (res.success && res.data) {
|
||||
workcaseList.value = res.data.list || []
|
||||
total.value = res.data.pageParam?.total || 0
|
||||
} else {
|
||||
ElMessage.error(res.message || '加载失败')
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建工单 - API调用
|
||||
*/
|
||||
const createWorkcaseAPI = async () => {
|
||||
if (!formData.value.username || !formData.value.phone || !formData.value.device || !formData.value.type) {
|
||||
ElMessage.error('请填写必填项')
|
||||
return
|
||||
}
|
||||
|
||||
const res = await workcaseAPI.createWorkcase(formData.value)
|
||||
if (res.success) {
|
||||
ElMessage.success('工单创建成功')
|
||||
showCreateDialog.value = false
|
||||
formData.value = { username: '', phone: '', device: '', type: '', emergency: 'normal', remark: '' }
|
||||
loadWorkcases()
|
||||
} else {
|
||||
ElMessage.error(res.message || '创建失败')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指派工单 - API调用
|
||||
*/
|
||||
const assignWorkcaseAPI = async (workcase: TbWorkcaseDTO) => {
|
||||
const { value: processor } = await ElMessageBox.prompt('请输入处理人ID', '指派工单', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消'
|
||||
}).catch(() => ({ value: '' }))
|
||||
|
||||
if (!processor) return
|
||||
|
||||
const process: TbWorkcaseProcessDTO = {
|
||||
workcaseId: workcase.workcaseId,
|
||||
action: 'assign',
|
||||
processor: processor,
|
||||
message: '工单指派'
|
||||
}
|
||||
|
||||
const res = await workcaseAPI.createWorkcaseProcess(process)
|
||||
if (res.success) {
|
||||
ElMessage.success('指派成功')
|
||||
loadWorkcases()
|
||||
} else {
|
||||
ElMessage.error(res.message || '指派失败')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成工单 - API调用
|
||||
*/
|
||||
const completeWorkcaseAPI = async (workcase: TbWorkcaseDTO) => {
|
||||
const process: TbWorkcaseProcessDTO = {
|
||||
workcaseId: workcase.workcaseId,
|
||||
action: 'finish',
|
||||
message: '工单完成'
|
||||
}
|
||||
|
||||
const res = await workcaseAPI.createWorkcaseProcess(process)
|
||||
if (res.success) {
|
||||
ElMessage.success('完成成功')
|
||||
loadWorkcases()
|
||||
} else {
|
||||
ElMessage.error(res.message || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= Mock 数据 =========================
|
||||
const mockTickets = [
|
||||
{ workcaseId: 'WC001', optsn: 'TK001', username: '张三', phone: '13800138000', type: 'electrical', device: 'TH-500GF', emergency: 'emergency' as const, status: 'pending' as const, processor: '', createTime: '2024-12-13 10:30' },
|
||||
{ workcaseId: 'WC002', optsn: 'TK002', username: '李四', phone: '13800138001', type: 'mechanical', device: 'TH-300D', emergency: 'normal' as const, status: 'processing' as const, processor: '王五', createTime: '2024-12-13 09:15' },
|
||||
{ workcaseId: 'WC003', optsn: 'TK003', username: '王五', phone: '13800138002', type: 'control', device: 'S-200X', emergency: 'normal' as const, status: 'done' as const, processor: '赵六', createTime: '2024-12-12 14:20' },
|
||||
{ workcaseId: 'WC004', optsn: 'TK004', username: '赵六', phone: '13800138003', type: 'parts', device: 'TH-800GF', emergency: 'normal' as const, status: 'pending' as const, processor: '', createTime: '2024-12-13 11:00' },
|
||||
{ workcaseId: 'WC005', optsn: 'TK005', username: '孙七', phone: '13800138004', type: 'install', device: 'G-100S', emergency: 'emergency' as const, status: 'processing' as const, processor: '李四', createTime: '2024-12-13 08:45' }
|
||||
]
|
||||
|
||||
/**
|
||||
* 加载Mock数据
|
||||
*/
|
||||
const loadMockData = () => {
|
||||
workcaseList.value = mockTickets as TbWorkcaseDTO[]
|
||||
total.value = mockTickets.length
|
||||
}
|
||||
|
||||
// ========================= 字段映射 =========================
|
||||
const typeMap: Record<string, string> = {
|
||||
electrical: '电气系统故障',
|
||||
mechanical: '机械故障',
|
||||
control: '控制系统故障',
|
||||
parts: '配件更换',
|
||||
install: '安装调试',
|
||||
other: '其他'
|
||||
}
|
||||
|
||||
const emergencyMap: Record<string, string> = {
|
||||
emergency: '紧急',
|
||||
normal: '普通'
|
||||
}
|
||||
|
||||
const statusMap: Record<string, string> = {
|
||||
pending: '待处理',
|
||||
processing: '处理中',
|
||||
done: '已完成'
|
||||
}
|
||||
|
||||
// ========================= 计算属性 =========================
|
||||
const filteredTickets = computed(() => {
|
||||
let result = tickets.value
|
||||
let result = workcaseList.value
|
||||
// Mock模式下做本地筛选
|
||||
if (statusFilter.value !== 'all') {
|
||||
result = result.filter(t => t.status === statusFilter.value)
|
||||
}
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(t => t.faultType === typeFilter.value)
|
||||
result = result.filter(t => t.type === typeFilter.value)
|
||||
}
|
||||
if (urgencyFilter.value) {
|
||||
result = result.filter(t => t.urgency === urgencyFilter.value)
|
||||
result = result.filter(t => t.emergency === urgencyFilter.value)
|
||||
}
|
||||
if (searchKeyword.value) {
|
||||
const keyword = searchKeyword.value.toLowerCase()
|
||||
result = result.filter(t =>
|
||||
t.ticketNo.toLowerCase().includes(keyword) ||
|
||||
t.customerName.toLowerCase().includes(keyword) ||
|
||||
t.deviceModel.toLowerCase().includes(keyword)
|
||||
(t.optsn || '').toLowerCase().includes(keyword) ||
|
||||
(t.username || '').toLowerCase().includes(keyword) ||
|
||||
(t.device || '').toLowerCase().includes(keyword)
|
||||
)
|
||||
}
|
||||
return result.slice((currentPage.value - 1) * 10, currentPage.value * 10)
|
||||
return result
|
||||
})
|
||||
|
||||
const getStatusType = (status: string) => {
|
||||
const map: Record<string, string> = {
|
||||
pending: 'warning',
|
||||
processing: 'info',
|
||||
completed: 'success',
|
||||
closed: 'danger'
|
||||
done: 'success'
|
||||
}
|
||||
return map[status] || 'info'
|
||||
}
|
||||
|
||||
const handleFilterChange = () => {
|
||||
currentPage.value = 1
|
||||
// loadWorkcases() // API调用:取消注释启用
|
||||
}
|
||||
|
||||
const viewDetail = (row: any) => {
|
||||
ElMessage.info(`查看工单详情: ${row.ticketNo}`)
|
||||
const handlePageChange = (page: number) => {
|
||||
currentPage.value = page
|
||||
// loadWorkcases() // API调用:取消注释启用
|
||||
}
|
||||
|
||||
const assignTicket = (row: any) => {
|
||||
ElMessage.info(`指派工单: ${row.ticketNo}`)
|
||||
const viewDetail = (row: TbWorkcaseDTO) => {
|
||||
ElMessage.info(`查看工单详情: ${row.optsn}`)
|
||||
// TODO: 跳转到工单详情页面
|
||||
}
|
||||
|
||||
const completeTicket = (row: any) => {
|
||||
ElMessage.success(`完成工单: ${row.ticketNo}`)
|
||||
const assignTicket = (row: TbWorkcaseDTO) => {
|
||||
// assignWorkcaseAPI(row) // API调用:取消注释启用
|
||||
ElMessage.info(`指派工单: ${row.optsn}`)
|
||||
}
|
||||
|
||||
const completeTicket = (row: TbWorkcaseDTO) => {
|
||||
// completeWorkcaseAPI(row) // API调用:取消注释启用
|
||||
ElMessage.success(`完成工单: ${row.optsn}`)
|
||||
}
|
||||
|
||||
const createTicket = () => {
|
||||
if (!formData.value.customerName || !formData.value.customerPhone || !formData.value.deviceModel) {
|
||||
// createWorkcaseAPI() // API调用:取消注释启用
|
||||
|
||||
// Mock模式下的处理
|
||||
if (!formData.value.username || !formData.value.phone || !formData.value.device || !formData.value.type) {
|
||||
ElMessage.error('请填写必填项')
|
||||
return
|
||||
}
|
||||
ElMessage.success('工单创建成功')
|
||||
showCreateDialog.value = false
|
||||
formData.value = { customerName: '', customerPhone: '', deviceModel: '', description: '' }
|
||||
formData.value = { username: '', phone: '', device: '', type: '', emergency: 'normal', remark: '' }
|
||||
}
|
||||
|
||||
// ========================= 生命周期 =========================
|
||||
onMounted(() => {
|
||||
// loadWorkcases() // API调用:取消注释启用
|
||||
loadMockData() // Mock模式:注释此行以使用API
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user