Files
front/src/views/ops/pages/feedback/undo/index.vue
2026-03-15 23:16:00 +08:00

640 lines
17 KiB
Vue

<template>
<div class="container">
<a-tabs v-model:active-key="activeTab" @change="handleTabChange">
<a-tab-pane key="pending" title="待处理">
<search-table
:form-model="formModel"
:form-items="formItems"
:data="tableData"
:columns="pendingColumns"
:loading="loading"
:pagination="pagination"
title="待处理工单"
search-button-text="查询"
reset-button-text="重置"
@search="handleSearch"
@reset="handleReset"
@page-change="handlePageChange"
@refresh="handleRefresh"
>
<template #toolbar-left>
<a-button type="primary" @click="handleCreate">
<template #icon>
<icon-plus />
</template>
新建工单
</a-button>
</template>
<!-- 工单类型 -->
<template #type="{ record }">
<a-tag :color="typeMap[record.type]?.color || 'gray'">
{{ typeMap[record.type]?.text || record.type }}
</a-tag>
</template>
<!-- 工单状态 -->
<template #status="{ record }">
<a-tag :color="statusMap[record.status]?.color || 'gray'">
{{ statusMap[record.status]?.text || record.status }}
</a-tag>
</template>
<!-- 优先级 -->
<template #priority="{ record }">
<a-tag :color="priorityMap[record.priority]?.color || 'gray'">
{{ priorityMap[record.priority]?.text || record.priority }}
</a-tag>
</template>
<!-- 序号 -->
<template #index="{ rowIndex }">
{{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }}
</template>
<!-- 操作 -->
<template #actions="{ record }">
<a-button type="text" size="small" @click="handleDetail(record)">
详情
</a-button>
<a-button type="text" size="small" @click="handleTransfer(record)">
转交
</a-button>
<a-button type="text" size="small" @click="handleSuspend(record)">
挂起
</a-button>
<a-button v-if="record.status === 'suspended'" type="text" size="small" @click="handleResume(record)">
重启
</a-button>
<a-button type="text" size="small" @click="handleResolve(record)">
解决
</a-button>
<a-button type="text" size="small" @click="handleClose(record)">
关闭
</a-button>
</template>
</search-table>
</a-tab-pane>
<a-tab-pane key="my-created" title="我创建的">
<search-table
:form-model="formModel"
:form-items="formItems"
:data="tableData"
:columns="myCreatedColumns"
:loading="loading"
:pagination="pagination"
title="我创建的工单"
search-button-text="查询"
reset-button-text="重置"
@search="handleSearch"
@reset="handleReset"
@page-change="handlePageChange"
@refresh="handleRefresh"
>
<template #toolbar-left>
<a-button type="primary" @click="handleCreate">
<template #icon>
<icon-plus />
</template>
新建工单
</a-button>
</template>
<!-- 工单类型 -->
<template #type="{ record }">
<a-tag :color="typeMap[record.type]?.color || 'gray'">
{{ typeMap[record.type]?.text || record.type }}
</a-tag>
</template>
<!-- 工单状态 -->
<template #status="{ record }">
<a-tag :color="statusMap[record.status]?.color || 'gray'">
{{ statusMap[record.status]?.text || record.status }}
</a-tag>
</template>
<!-- 优先级 -->
<template #priority="{ record }">
<a-tag :color="priorityMap[record.priority]?.color || 'gray'">
{{ priorityMap[record.priority]?.text || record.priority }}
</a-tag>
</template>
<!-- 序号 -->
<template #index="{ rowIndex }">
{{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }}
</template>
<!-- 操作 -->
<template #actions="{ record }">
<a-button type="text" size="small" @click="handleDetail(record)">
详情
</a-button>
<a-button v-if="!['resolved', 'closed'].includes(record.status)" type="text" size="small" @click="handleEdit(record)">
编辑
</a-button>
<a-button type="text" size="small" @click="handleCancel(record)">
撤回
</a-button>
<a-button v-if="record.status === 'resolved'" type="text" size="small" @click="handleComment(record)">
评论
</a-button>
<a-button type="text" size="small" @click="handleClose(record)">
关闭
</a-button>
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">
删除
</a-button>
</template>
</search-table>
</a-tab-pane>
</a-tabs>
<!-- 工单详情对话框 -->
<ticket-detail-dialog
v-model:visible="detailVisible"
:ticket-id="currentTicket?.id"
@refresh="handleDetailSuccess"
/>
<!-- 转交对话框 -->
<transfer-dialog
v-model:visible="transferVisible"
:ticket="currentTicket"
@success="handleTransferSuccess"
/>
<!-- 挂起对话框 -->
<suspend-dialog
v-model:visible="suspendVisible"
:ticket="currentTicket"
@success="handleSuspendSuccess"
/>
<!-- 解决对话框 -->
<resolve-dialog
v-model:visible="resolveVisible"
:ticket="currentTicket"
@success="handleResolveSuccess"
/>
<!-- 评论对话框 -->
<comment-dialog
v-model:visible="commentVisible"
:ticket="currentTicket"
@success="handleCommentSuccess"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import { IconPlus } from '@arco-design/web-vue/es/icon'
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'
import type { FormItem } from '@/components/search-form/types'
import SearchTable from '@/components/search-table/index.vue'
import { searchFormConfig } from './config/search-form'
import {
fetchFeedbackTickets,
transferFeedbackTicket,
suspendFeedbackTicket,
resumeFeedbackTicket,
resolveFeedbackTicket,
cancelFeedbackTicket,
closeFeedbackTicket,
deleteFeedbackTicket,
} from '@/api/ops/feedbackTicket'
import TicketDetailDialog from '../components/TicketDetailDialog.vue'
import TransferDialog from '../components/TransferDialog.vue'
import SuspendDialog from '../components/SuspendDialog.vue'
import ResolveDialog from '../components/ResolveDialog.vue'
import CommentDialog from '../components/CommentDialog.vue'
// 工单状态映射
const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待接单', color: 'blue' },
accepted: { text: '已接单', color: 'cyan' },
processing: { text: '处理中', color: 'orange' },
suspended: { text: '已挂起', color: 'gray' },
resolved: { text: '已解决', color: 'green' },
closed: { text: '已关闭', color: 'red' },
cancelled: { text: '已撤回', color: 'red' },
}
// 工单类型映射
const typeMap: Record<string, { text: string; color: string }> = {
incident: { text: '故障', color: 'red' },
request: { text: '请求', color: 'blue' },
question: { text: '问题', color: 'orange' },
other: { text: '其他', color: 'gray' },
}
// 优先级映射
const priorityMap: Record<string, { text: string; color: string }> = {
low: { text: '低', color: 'gray' },
medium: { text: '中', color: 'blue' },
high: { text: '高', color: 'orange' },
urgent: { text: '紧急', color: 'red' },
}
// 当前激活的选项卡
const activeTab = ref<'pending' | 'my-created'>('pending')
// 状态管理
const loading = ref(false)
const tableData = ref<any[]>([])
const formModel = ref({
keyword: '',
type: '',
priority: '',
status: '',
})
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
})
// 表单项配置
const formItems = computed<FormItem[]>(() => searchFormConfig)
// 待处理选项卡的表格列配置
const pendingColumns = computed<TableColumnData[]>(() => [
{
title: '序号',
dataIndex: 'index',
slotName: 'index',
width: 80,
},
{
title: '工单编号',
dataIndex: 'ticket_no',
width: 160,
},
{
title: '工单标题',
dataIndex: 'title',
width: 200,
ellipsis: true,
tooltip: true,
},
{
title: '工单类型',
dataIndex: 'type',
slotName: 'type',
width: 100,
},
{
title: '工单状态',
dataIndex: 'status',
slotName: 'status',
width: 100,
},
{
title: '优先级',
dataIndex: 'priority',
slotName: 'priority',
width: 100,
},
{
title: '操作',
dataIndex: 'actions',
slotName: 'actions',
width: 280,
fixed: 'right' as const,
},
])
// 我创建的选项卡的表格列配置
const myCreatedColumns = computed<TableColumnData[]>(() => [
{
title: '序号',
dataIndex: 'index',
slotName: 'index',
width: 80,
},
{
title: '工单编号',
dataIndex: 'ticket_no',
width: 160,
},
{
title: '工单标题',
dataIndex: 'title',
width: 200,
ellipsis: true,
tooltip: true,
},
{
title: '工单类型',
dataIndex: 'type',
slotName: 'type',
width: 100,
},
{
title: '工单状态',
dataIndex: 'status',
slotName: 'status',
width: 100,
},
{
title: '优先级',
dataIndex: 'priority',
slotName: 'priority',
width: 100,
},
{
title: '操作',
dataIndex: 'actions',
slotName: 'actions',
width: 300,
fixed: 'right' as const,
},
])
// 当前选中的工单
const currentTicket = ref<any>(null)
// 对话框可见性
const detailVisible = ref(false)
const transferVisible = ref(false)
const suspendVisible = ref(false)
const resolveVisible = ref(false)
const commentVisible = ref(false)
// 获取工单列表
const fetchTickets = async () => {
loading.value = true
try {
const params: any = {
page: pagination.current,
page_size: pagination.pageSize,
keyword: formModel.value.keyword || undefined,
type: formModel.value.type || undefined,
priority: formModel.value.priority || undefined,
status: formModel.value.status || undefined,
}
if (activeTab.value === 'pending') {
// 待处理工单:分配给当前用户且状态为 pending/accepted/processing/suspended
params.status = 'accepted,processing,suspended'
} else {
// 我创建的工单:由当前用户创建
// 这里需要从用户信息中获取 creator_id
// params.creator_id = userStore.userId
}
const res = await fetchFeedbackTickets(params)
tableData.value = res.details?.data || []
pagination.total = res.details?.total || 0
} catch (error) {
console.error('获取工单列表失败:', error)
Message.error('获取工单列表失败')
tableData.value = []
pagination.total = 0
} finally {
loading.value = false
}
}
// 选项卡切换
const handleTabChange = (key: string) => {
console.log('切换到选项卡:', key)
activeTab.value = key as 'pending' | 'my-created'
pagination.current = 1
fetchTickets()
}
// 搜索
const handleSearch = () => {
pagination.current = 1
fetchTickets()
}
// 重置
const handleReset = () => {
formModel.value = {
keyword: '',
type: '',
priority: '',
status: '',
}
pagination.current = 1
fetchTickets()
}
// 分页变化
const handlePageChange = (current: number) => {
pagination.current = current
fetchTickets()
}
// 刷新
const handleRefresh = () => {
fetchTickets()
Message.success('数据已刷新')
}
// 新建工单
const handleCreate = () => {
// TODO: 打开新建工单对话框
Message.info('新建工单功能待实现')
}
// 详情
const handleDetail = (record: any) => {
console.log('查看详情:', record)
currentTicket.value = record
detailVisible.value = true
}
// 编辑
const handleEdit = (record: any) => {
console.log('编辑工单:', record)
// TODO: 打开编辑对话框
Message.info('编辑工单功能待实现')
}
// 转交
const handleTransfer = (record: any) => {
console.log('转交工单:', record)
currentTicket.value = record
transferVisible.value = true
}
// 挂起
const handleSuspend = (record: any) => {
console.log('挂起工单:', record)
currentTicket.value = record
suspendVisible.value = true
}
// 重启
const handleResume = async (record: any) => {
console.log('重启工单:', record)
try {
Modal.confirm({
title: '确认重启',
content: `确认重启工单 ${record.ticket_no} 吗?`,
onOk: async () => {
const res = await resumeFeedbackTicket(record.id)
if (res.code === 0) {
Message.success('重启成功')
fetchTickets()
} else {
Message.error(res.message || '重启失败')
}
},
})
} catch (error) {
console.error('重启工单失败:', error)
}
}
// 解决
const handleResolve = (record: any) => {
console.log('解决工单:', record)
currentTicket.value = record
resolveVisible.value = true
}
// 撤回
const handleCancel = async (record: any) => {
console.log('撤回工单:', record)
try {
Modal.confirm({
title: '确认撤回',
content: `确认撤回工单 ${record.ticket_no} 吗?`,
onOk: async () => {
const res = await cancelFeedbackTicket(record.id)
if (res.code === 0) {
Message.success('撤回成功')
fetchTickets()
} else {
Message.error(res.message || '撤回失败')
}
},
})
} catch (error) {
console.error('撤回工单失败:', error)
}
}
// 评论
const handleComment = (record: any) => {
console.log('评论工单:', record)
currentTicket.value = record
commentVisible.value = true
}
// 关闭
const handleClose = async (record: any) => {
console.log('关闭工单:', record)
try {
Modal.confirm({
title: '确认关闭',
content: `确认关闭工单 ${record.ticket_no} 吗?`,
onOk: async () => {
const res = await closeFeedbackTicket(record.id, {})
if (res.code === 0) {
Message.success('关闭成功')
fetchTickets()
} else {
Message.error(res.message || '关闭失败')
}
},
})
} catch (error) {
console.error('关闭工单失败:', error)
}
}
// 删除
const handleDelete = async (record: any) => {
console.log('删除工单:', record)
try {
Modal.confirm({
title: '确认删除',
content: `确认删除工单 ${record.ticket_no} 吗?此操作不可恢复。`,
okText: '删除',
okButtonProps: { status: 'danger' },
onOk: async () => {
const res = await deleteFeedbackTicket(record.id)
if (res.code === 0) {
Message.success('删除成功')
fetchTickets()
} else {
Message.error(res.message || '删除失败')
}
},
})
} catch (error) {
console.error('删除工单失败:', error)
}
}
// 详情成功回调
const handleDetailSuccess = () => {
detailVisible.value = false
fetchTickets()
}
// 转交成功回调
const handleTransferSuccess = () => {
transferVisible.value = false
fetchTickets()
}
// 挂起成功回调
const handleSuspendSuccess = () => {
suspendVisible.value = false
fetchTickets()
}
// 解决成功回调
const handleResolveSuccess = () => {
resolveVisible.value = false
fetchTickets()
}
// 评论成功回调
const handleCommentSuccess = () => {
commentVisible.value = false
fetchTickets()
}
// 初始化加载数据
fetchTickets()
</script>
<script lang="ts">
export default {
name: 'FeedbackUndo',
}
</script>
<style scoped lang="less">
.feedback-undo-container {
padding: 0 20px 20px 20px;
background-color: var(--color-bg-2);
min-height: 100%;
:deep(.arco-tabs) {
.arco-tabs-nav {
padding-bottom: 0;
margin-bottom: 16px;
}
.arco-tabs-content {
padding-top: 0;
}
}
}
</style>