feat
This commit is contained in:
639
src/views/ops/pages/feedback/undo/index.vue
Normal file
639
src/views/ops/pages/feedback/undo/index.vue
Normal file
@@ -0,0 +1,639 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user