web修改

This commit is contained in:
2025-12-19 17:34:30 +08:00
parent cc372bc7ea
commit 9c4f73ac9c
22 changed files with 1660 additions and 822 deletions

View File

@@ -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">支持 PDFWordTXT 等文档格式</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
}
ElMessage.success('文件上传成功')
showUploadDialog.value = false
uploadForm.value = { kbType: '', category: '', file: null }
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 = { 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>

View File

@@ -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>