Files
sale/frontend/src/views/user/TicketDetail.vue
T
admin 3a898e8aa0 fix: 全面修复项目问题
后端修复:
- 验证码存储与校验机制
- 订单创建事务+库存扣减
- GetStats字段名错误
- VerifyEmail改为POST
- 供应商更新字段白名单
- 文件删除安全检查
- 抽奖安全随机数
- 用户管理CRUD
- 订单取消/确认收货
- 工单回复
- Toggle返回新数据
- 移除死代码

前端修复:
- 404兜底路由
- 401软跳转
- API层统一
- 面包屑补充banners
- 国际化完善
- 购物车并行删除
- 退出清理购物车
- 供应商Dashboard数据
- 工单详情页
- 订单取消/确认收货
2026-05-06 09:55:01 +08:00

167 lines
4.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="ticket-detail-page">
<BackNav />
<div v-if="ticket" class="ticket-card">
<div class="ticket-header">
<h2>{{ ticket.subject }}</h2>
<el-tag :type="statusType(ticket.status)">{{ statusText(ticket.status) }}</el-tag>
</div>
<div class="ticket-meta">
<span>分类{{ ticket.category }}</span>
<span>创建时间{{ formatDate(ticket.created_at) }}</span>
</div>
<div class="ticket-content">
<p>{{ ticket.content }}</p>
</div>
<div v-if="ticket.reply" class="reply-section">
<h3>回复</h3>
<div class="reply-content" v-html="formatReply(ticket.reply)"></div>
</div>
<div v-if="ticket.status !== 'resolved' && ticket.status !== 'closed'" class="reply-form">
<h3>追加回复</h3>
<el-input v-model="replyContent" type="textarea" :rows="4" placeholder="请输入回复内容" />
<el-button type="primary" @click="submitReply" :loading="submitting" style="margin-top: 12px">提交回复</el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ticketApi } from '../../api'
import BackNav from '../../components/BackNav.vue'
const route = useRoute()
const ticket = ref<any>(null)
const replyContent = ref('')
const submitting = ref(false)
const statusMap: Record<string, string> = {
pending: '待处理', processing: '处理中', resolved: '已解决', closed: '已关闭',
}
const statusTypeMap: Record<string, string> = {
pending: 'warning', processing: 'primary', resolved: 'success', closed: 'info',
}
function statusText(s: string) { return statusMap[s] || s }
function statusType(s: string) { return statusTypeMap[s] || 'info' }
function formatDate(date: string) {
if (!date) return '-'
const d = new Date(date)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
}
function formatReply(reply: string) {
return reply.replace(/\n/g, '<br>').replace(/---/g, '<hr style="border-color: rgba(255,255,255,0.1); margin: 12px 0;">')
}
async function fetchTicket() {
try {
const id = Number(route.params.id)
const res: any = await ticketApi.getById(id)
ticket.value = res.data
} catch {
ElMessage.error('获取工单失败')
}
}
async function submitReply() {
if (!replyContent.value.trim()) {
ElMessage.warning('请输入回复内容')
return
}
submitting.value = true
try {
await ticketApi.reply(Number(route.params.id), { content: replyContent.value })
ElMessage.success('回复成功')
replyContent.value = ''
fetchTicket()
} catch {
ElMessage.error('回复失败')
} finally {
submitting.value = false
}
}
onMounted(fetchTicket)
</script>
<style scoped lang="scss">
.ticket-detail-page { padding: 0; }
.ticket-card {
background: #2d2d44;
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 12px;
padding: 24px;
}
.ticket-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
h2 {
font-size: 18px;
font-weight: 600;
color: #fff;
margin: 0;
}
}
.ticket-meta {
display: flex;
gap: 16px;
font-size: 13px;
color: rgba(255, 255, 255, 0.5);
margin-bottom: 16px;
}
.ticket-content {
padding: 16px;
background: rgba(255, 255, 255, 0.04);
border-radius: 8px;
color: rgba(255, 255, 255, 0.8);
line-height: 1.6;
margin-bottom: 20px;
}
.reply-section {
margin-bottom: 20px;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
h3 {
font-size: 15px;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 12px;
}
}
.reply-content {
padding: 16px;
background: rgba(78, 110, 242, 0.08);
border-radius: 8px;
color: rgba(255, 255, 255, 0.8);
line-height: 1.6;
}
.reply-form {
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
h3 {
font-size: 15px;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 12px;
}
}
</style>