Kamixitong/app/web/templates/user/ticket.html
2025-12-12 11:35:14 +08:00

677 lines
30 KiB
HTML
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.

{% extends "user/base.html" %}
{% block title %}售后服务 - {{ config.SITE_NAME or '软件授权管理系统' }}{% endblock %}
{% block extra_css %}
<style>
.ticket-form {
background: #f8f9fa;
border-radius: 10px;
padding: 2rem;
}
.ticket-history {
border-left: 3px solid #667eea;
}
.ticket-item {
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
}
.ticket-status-open {
background-color: #d1ecf1;
border-color: #bee5eb;
}
.ticket-status-closed {
background-color: #d4edda;
border-color: #c3e6cb;
}
.emergency-contact {
background: linear-gradient(135deg, #ff6b6b, #ee5a24);
color: white;
border-radius: 10px;
padding: 2rem;
text-align: center;
}
</style>
{% endblock %}
{% block content %}
<div class="container py-4">
<div class="row">
<div class="col-12">
<h1 class="mb-4">售后服务</h1>
</div>
</div>
<!-- 选项卡导航 -->
<div class="row">
<div class="col-12">
<ul class="nav nav-tabs" id="serviceTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="ticket-tab" data-bs-toggle="tab" data-bs-target="#ticket-panel" type="button" role="tab">
<i class="fas fa-ticket-alt me-2"></i>工单查询
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="order-tab" data-bs-toggle="tab" data-bs-target="#order-panel" type="button" role="tab">
<i class="fas fa-file-invoice me-2"></i>订单查询
</button>
</li>
</ul>
</div>
</div>
<!-- 选项卡内容 -->
<div class="row">
<div class="col-12">
<div class="tab-content" id="serviceTabContent">
<!-- 工单查询面板 -->
<div class="tab-pane fade show active" id="ticket-panel" role="tabpanel">
<div class="row mt-3">
<!-- 紧急通道 -->
<div class="col-lg-4 mb-4">
<div class="emergency-contact">
<h3 class="mb-3">
<i class="fas fa-exclamation-circle me-2"></i>
紧急通道
</h3>
<p class="mb-4">遇到紧急问题?立即联系我们</p>
<button class="btn btn-light" data-bs-toggle="modal" data-bs-target="#wechatModal">
<i class="fab fa-weixin me-2"></i>微信紧急联系
</button>
</div>
</div>
<!-- 工单提交表单 -->
<div class="col-lg-8 mb-4">
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">提交工单</h5>
</div>
<div class="card-body">
<form id="ticketForm" class="ticket-form">
<div class="mb-3">
<label for="contactName" class="form-label">联系人姓名</label>
<input type="text" class="form-control" id="contactName" required>
</div>
<div class="mb-3">
<label for="contactPhone" class="form-label">联系电话</label>
<input type="tel" class="form-control" id="contactPhone" required>
</div>
<div class="mb-3">
<label for="contactEmail" class="form-label">联系邮箱</label>
<input type="email" class="form-control" id="contactEmail" required>
</div>
<div class="mb-3">
<label for="productSelect" class="form-label">相关产品</label>
<select class="form-select" id="productSelect" required>
<option value="">请选择相关产品</option>
<option value="1">产品一</option>
<option value="2">产品二</option>
<option value="3">产品三</option>
</select>
</div>
<div class="mb-3">
<label for="ticketTitle" class="form-label">工单标题</label>
<input type="text" class="form-control" id="ticketTitle" required>
</div>
<div class="mb-3">
<label for="ticketContent" class="form-label">问题描述</label>
<textarea class="form-control" id="ticketContent" rows="5" required></textarea>
</div>
<div class="mb-3">
<label for="prioritySelect" class="form-label">优先级</label>
<select class="form-select" id="prioritySelect">
<option value="1"></option>
<option value="2" selected></option>
<option value="3"></option>
<option value="4">紧急</option>
</select>
</div>
<button type="submit" class="btn btn-primary w-100" id="submitTicketBtn">
<i class="fas fa-paper-plane me-2"></i>提交工单
</button>
</form>
</div>
</div>
</div>
</div>
<!-- 工单历史 -->
<div class="row">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">工单历史</h5>
</div>
<div class="card-body">
<!-- 工单查询输入框 -->
<div class="mb-3">
<label for="ticketQueryPhone" class="form-label">手机号查询</label>
<div class="input-group">
<input type="tel" class="form-control" id="ticketQueryPhone" placeholder="请输入手机号查询工单历史">
<button class="btn btn-outline-primary" type="button" id="queryTicketsBtn" onclick="queryTicketsHistory()">
<i class="fas fa-search me-1"></i>查询
</button>
</div>
<div class="form-text">输入手机号查询该手机号的所有工单记录</div>
</div>
<!-- 工单列表 -->
<div id="ticketsContainer">
<!-- 工单列表将通过JavaScript动态加载 -->
<div class="text-center py-5">
<i class="fas fa-info-circle fa-2x text-muted mb-3"></i>
<h4 class="text-muted">请输入手机号查询工单历史</h4>
<p class="text-muted">输入手机号后将显示该手机号的所有工单记录</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 订单查询面板 -->
<div class="tab-pane fade" id="order-panel" role="tabpanel">
<div class="row mt-3">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">查询订单</h5>
</div>
<div class="card-body">
<form id="orderQueryForm">
<div class="row">
<div class="col-md-7 mb-3">
<label for="queryPhone" class="form-label">手机号</label>
<input type="tel" class="form-control" id="queryPhone" placeholder="请输入手机号" required>
<div class="form-text">输入手机号查询该手机号的所有订单</div>
</div>
<div class="col-md-3 mb-3 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100" id="queryOrderBtn">
<i class="fas fa-search me-1"></i>查询所有订单
</button>
</div>
<div class="col-md-2 mb-3 d-flex align-items-end">
<button type="button" class="btn btn-outline-secondary w-100" id="clearOrderBtn" onclick="clearOrderQuery()">
<i class="fas fa-eraser me-1"></i>清空
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- 订单查询结果 -->
<div class="row">
<div class="col-12">
<div id="orderResultContainer" class="mt-3">
<div class="text-center py-5">
<i class="fas fa-info-circle fa-2x text-muted mb-3"></i>
<h4 class="text-muted">请输入手机号查询订单</h4>
<p class="text-muted">输入手机号后将显示该手机号的所有订单记录</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 微信二维码模态框 -->
<div class="modal fade" id="wechatModal" tabindex="-1" aria-labelledby="wechatModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="wechatModalLabel">紧急联系</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center">
<p>扫码添加微信,快速响应紧急需求</p>
<img src="/static/images/wechat-qrcode.jpg" alt="微信二维码" class="img-fluid mb-3" style="max-width: 200px;">
<p class="text-muted small">微信ID: TaiYiSupport</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// 售后服务页面JavaScript
document.addEventListener('DOMContentLoaded', function() {
console.log('售后服务页面加载完成');
// 初始化产品列表
loadProducts();
// 工单提交表单事件
document.getElementById('ticketForm').addEventListener('submit', function(e) {
e.preventDefault();
submitTicket();
});
// 订单查询表单事件
document.getElementById('orderQueryForm').addEventListener('submit', function(e) {
e.preventDefault();
queryOrder();
});
// 工单历史查询输入框回车事件
document.getElementById('ticketQueryPhone').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
queryTicketsHistory();
}
});
});
// 提交工单
function submitTicket() {
const formData = {
product_id: document.getElementById('productSelect').value,
contact_person: document.getElementById('contactName').value.trim(),
phone: document.getElementById('contactPhone').value.trim(),
title: document.getElementById('ticketTitle').value.trim(),
description: document.getElementById('ticketContent').value.trim(),
priority: document.getElementById('prioritySelect').value
};
// 基础验证
if (!formData.contact_person || !formData.phone || !formData.product_id ||
!formData.title || !formData.description) {
showNotification('请填写所有必填字段', 'warning');
return;
}
// 验证手机号格式
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(formData.phone)) {
showNotification('请输入正确的手机号码', 'warning');
return;
}
// 显示加载状态
const submitBtn = document.getElementById('submitTicketBtn');
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span>提交中...';
submitBtn.disabled = true;
// 调用API提交工单
apiRequest('/api/v1/user/tickets', {
method: 'POST',
body: JSON.stringify(formData)
})
.then(data => {
if (data.success) {
showNotification('工单提交成功!', 'success');
document.getElementById('ticketForm').reset();
// 工单提交成功后,如果工单查询框有手机号,则刷新工单历史
const queryPhone = document.getElementById('ticketQueryPhone').value.trim();
if (queryPhone) {
loadTickets(queryPhone);
}
} else {
showNotification('工单提交失败: ' + data.message, 'error');
}
})
.catch(error => {
showNotification('工单提交失败,请稍后重试', 'error');
console.error('提交工单失败:', error);
})
.finally(() => {
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
});
}
// 加载工单历史(带手机号参数)
function loadTickets(phone) {
// 验证手机号格式
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(phone)) {
document.getElementById('ticketsContainer').innerHTML = `
<div class="text-center py-5">
<i class="fas fa-exclamation-circle fa-2x text-warning mb-3"></i>
<h4 class="text-warning">手机号格式不正确</h4>
<p class="text-muted">请输入正确的11位手机号码</p>
</div>
`;
return;
}
// 显示加载状态
document.getElementById('ticketsContainer').innerHTML = `
<div class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在加载工单历史...</p>
</div>
`;
// 调用API获取工单列表
apiRequest(`/api/v1/user/tickets?phone=${phone}`)
.then(data => {
if (data.success) {
renderTickets(data.data);
} else {
showNotification('加载工单历史失败: ' + data.message, 'error');
}
})
.catch(error => {
showNotification('加载工单历史失败,请稍后重试', 'error');
console.error('加载工单历史失败:', error);
});
}
// 查询工单历史(处理查询按钮点击)
function queryTicketsHistory() {
const phone = document.getElementById('ticketQueryPhone').value.trim();
// 验证输入
if (!phone) {
showNotification('请输入手机号', 'warning');
return;
}
// 验证手机号格式并加载工单
loadTickets(phone);
}
// 渲染工单列表
function renderTickets(tickets) {
const container = document.getElementById('ticketsContainer');
if (tickets.length === 0) {
container.innerHTML = `
<div class="text-center py-5">
<i class="fas fa-ticket-alt fa-3x text-muted mb-3"></i>
<h4 class="text-muted">暂无工单记录</h4>
<p class="text-muted">您还没有提交过工单</p>
</div>
`;
return;
}
let ticketsHtml = '';
tickets.forEach(ticket => {
const statusClass = ticket.status === 1 ? 'ticket-status-open' : 'ticket-status-closed';
const statusText = ticket.status === 1 ? '处理中' : '已关闭';
const priorityText = ['未知', '低', '中', '高', '紧急'][ticket.priority] || '未知';
ticketsHtml += `
<div class="ticket-item ${statusClass}">
<div class="row">
<div class="col-md-9">
<h5 class="mb-2">${ticket.title}</h5>
<p class="mb-2">${ticket.description.substring(0, 100)}${ticket.description.length > 100 ? '...' : ''}</p>
<div class="d-flex flex-wrap gap-3 text-muted">
<small><i class="fas fa-calendar me-1"></i> ${formatDate(ticket.create_time)}</small>
<small><i class="fas fa-user me-1"></i> ${ticket.contact_person}</small>
<small><i class="fas fa-box me-1"></i> ${ticket.product_name || '未知产品'}</small>
<small><i class="fas fa-exclamation-circle me-1"></i> ${priorityText}</small>
</div>
</div>
<div class="col-md-3 text-md-end">
<span class="badge ${ticket.status === 1 ? 'bg-info' : 'bg-success'}">
${statusText}
</span>
<div class="mt-2">
<button class="btn btn-sm btn-outline-primary" onclick="viewTicket('${ticket.ticket_number}')">
<i class="fas fa-eye me-1"></i>查看
</button>
</div>
</div>
</div>
</div>
`;
});
container.innerHTML = ticketsHtml;
}
// 查看工单详情
function viewTicket(ticketId) {
showNotification('查看工单详情功能待实现', 'info');
// 这里可以跳转到工单详情页面或显示模态框
}
// 加载产品列表
function loadProducts() {
// 调用API获取产品列表
apiRequest('/api/v1/user/products')
.then(data => {
if (data.success) {
renderProducts(data.data.products || []);
} else {
showNotification('加载产品列表失败: ' + data.message, 'error');
}
})
.catch(error => {
showNotification('加载产品列表失败,请稍后重试', 'error');
console.error('加载产品列表失败:', error);
});
}
// 渲染产品列表
function renderProducts(products) {
const select = document.getElementById('productSelect');
// 清空现有选项(保留默认选项)
select.innerHTML = '<option value="">请选择相关产品</option>';
// 添加产品选项
products.forEach(product => {
const option = document.createElement('option');
option.value = product.product_id;
option.textContent = product.product_name;
select.appendChild(option);
});
}
// 移除之前的监听器,因为现在有了专门的工单查询输入框
// 查询订单
function queryOrder() {
const phone = document.getElementById('queryPhone').value.trim();
// 验证输入
if (!phone) {
showNotification('请输入手机号', 'warning');
return;
}
// 验证手机号格式
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(phone)) {
showNotification('请输入正确的手机号码', 'warning');
return;
}
// 显示加载状态
const container = document.getElementById('orderResultContainer');
container.innerHTML = `
<div class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在查询订单...</p>
</div>
`;
// 调用API查询订单通过手机号查询所有订单
apiRequest(`/api/v1/user/orders?phone=${phone}`)
.then(data => {
if (data.success) {
renderOrderList(data.data.orders);
} else {
container.innerHTML = `
<div class="text-center py-5">
<i class="fas fa-times-circle fa-3x text-danger mb-3"></i>
<h4 class="text-danger">查询失败</h4>
<p class="text-muted">${data.message || '暂无订单记录'}</p>
</div>
`;
}
})
.catch(error => {
container.innerHTML = `
<div class="text-center py-5">
<i class="fas fa-exclamation-triangle fa-3x text-warning mb-3"></i>
<h4 class="text-warning">查询失败</h4>
<p class="text-muted">网络错误,请稍后重试</p>
</div>
`;
console.error('查询订单失败:', error);
});
}
// 清空订单查询
function clearOrderQuery() {
document.getElementById('queryPhone').value = '';
const container = document.getElementById('orderResultContainer');
container.innerHTML = `
<div class="text-center py-5">
<i class="fas fa-info-circle fa-2x text-muted mb-3"></i>
<h4 class="text-muted">请输入手机号查询订单</h4>
<p class="text-muted">输入手机号后将显示该手机号的所有订单记录</p>
</div>
`;
}
// 渲染订单列表
function renderOrderList(orders) {
const container = document.getElementById('orderResultContainer');
if (orders.length === 0) {
container.innerHTML = `
<div class="text-center py-5">
<i class="fas fa-file-invoice fa-3x text-muted mb-3"></i>
<h4 class="text-muted">暂无订单记录</h4>
<p class="text-muted">该手机号下暂无订单记录</p>
</div>
`;
return;
}
const statusClass = {
0: 'warning',
1: 'success',
2: 'danger',
3: 'secondary'
};
const statusText = {
0: '待支付',
1: '已支付',
2: '已取消',
3: '已完成'
};
let ordersHtml = '';
orders.forEach(order => {
ordersHtml += `
<div class="card shadow-sm mb-3">
<div class="card-body">
<div class="row">
<div class="col-md-8">
<h5 class="card-title">
<i class="fas fa-file-invoice me-2"></i>
${order.order_number}
<span class="badge bg-${statusClass[order.status]} ms-2">
${statusText[order.status]}
</span>
</h5>
<div class="row mt-3">
<div class="col-sm-6">
<p class="mb-2">
<strong>产品:</strong> ${order.product_name || '未知产品'}
</p>
<p class="mb-2">
<strong>套餐:</strong> ${order.package_id}
</p>
<p class="mb-2">
<strong>数量:</strong> ${order.quantity}
</p>
</div>
<div class="col-sm-6">
<p class="mb-2">
<strong>联系人:</strong> ${order.contact_person}
</p>
<p class="mb-2">
<strong>手机号:</strong> ${order.phone}
</p>
<p class="mb-2">
<strong>创建时间:</strong> ${order.create_time || '-'}
</p>
</div>
</div>
</div>
<div class="col-md-4 text-md-end">
<div class="mb-3">
<h4 class="text-success mb-0">¥${order.amount.toFixed(2)}</h4>
<small class="text-muted">订单金额</small>
</div>
<div class="mb-3">
<p class="mb-1">
<strong>支付方式:</strong> ${order.payment_method || '未支付'}
</p>
<p class="mb-1">
<strong>支付时间:</strong> ${order.payment_time || '未支付'}
</p>
</div>
<div class="d-grid gap-2">
<button class="btn btn-outline-primary btn-sm" onclick="viewOrderDetail('${order.order_number}')">
<i class="fas fa-eye me-1"></i>查看详情
</button>
</div>
</div>
</div>
</div>
</div>
`;
});
container.innerHTML = `
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-list me-2"></i>
订单列表 (共 ${orders.length} 条记录)
</h5>
</div>
<div class="card-body">
${ordersHtml}
</div>
</div>
`;
}
// 查看订单详情
function viewOrderDetail(orderNumber) {
// 这里可以跳转到订单详情页面或显示模态框
showNotification(`订单详情功能待实现,订单号: ${orderNumber}`, 'info');
}
</script>
{% endblock %}