文件

530 行
24 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>骑手申诉处理管理</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<style>
.page-container {
padding: 20px;
background-color: #f5f5f5;
min-height: 100vh;
}
.stats-cards {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.stats-card {
flex: 1;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stats-number {
font-size: 24px;
font-weight: bold;
color: #409EFF;
}
.stats-label {
color: #666;
margin-top: 5px;
}
.search-form {
background: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.table-container {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.status-tag {
font-size: 12px;
}
.appeal-type-tag {
font-size: 12px;
}
.urgent-tag {
animation: blink 1s infinite;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.5; }
}
</style>
</head>
<body>
<div id="app" class="page-container">
<div class="stats-cards">
<div class="stats-card">
<div class="stats-number">{{ stats.total_appeals || 0 }}</div>
<div class="stats-label">总申诉数</div>
</div>
<div class="stats-card">
<div class="stats-number">{{ stats.pending_appeals || 0 }}</div>
<div class="stats-label">待处理申诉</div>
</div>
<div class="stats-card">
<div class="stats-number">{{ stats.approved_appeals || 0 }}</div>
<div class="stats-label">已通过申诉</div>
</div>
<div class="stats-card">
<div class="stats-number">{{ stats.approval_rate || 0 }}%</div>
<div class="stats-label">申诉通过率</div>
</div>
</div>
<div class="search-form">
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item label="关键词">
<el-input v-model="searchForm.keyword" placeholder="骑手姓名/手机号/订单号" clearable style="width: 200px;"></el-input>
</el-form-item>
<el-form-item label="申诉类型">
<el-select v-model="searchForm.appeal_type" placeholder="请选择" clearable style="width: 150px;">
<el-option label="全部" value=""></el-option>
<el-option label="异常订单" value="order_exception"></el-option>
<el-option label="差评申诉" value="bad_review"></el-option>
<el-option label="超时申诉" value="timeout"></el-option>
<el-option label="扣费申诉" value="deduction"></el-option>
<el-option label="其他" value="other"></el-option>
</el-select>
</el-form-item>
<el-form-item label="处理状态">
<el-select v-model="searchForm.status" placeholder="请选择" clearable style="width: 120px;">
<el-option label="全部" value=""></el-option>
<el-option label="待处理" value="pending"></el-option>
<el-option label="已通过" value="approved"></el-option>
<el-option label="已拒绝" value="rejected"></el-option>
</el-select>
</el-form-item>
<el-form-item label="紧急程度">
<el-select v-model="searchForm.urgency" placeholder="请选择" clearable style="width: 120px;">
<el-option label="全部" value=""></el-option>
<el-option label="紧急" value="urgent"></el-option>
<el-option label="普通" value="normal"></el-option>
<el-option label="低" value="low"></el-option>
</el-select>
</el-form-item>
<el-form-item label="申诉时间">
<el-date-picker
v-model="searchForm.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
<el-button type="success" @click="batchApprove" :disabled="selectedAppeals.length === 0">批量通过</el-button>
<el-button type="warning" @click="batchReject" :disabled="selectedAppeals.length === 0">批量拒绝</el-button>
</el-form-item>
</el-form>
</div>
<div class="table-container">
<el-table
:data="tableData"
v-loading="loading"
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="申诉ID" width="80"></el-table-column>
<el-table-column prop="order_no" label="订单号" width="120"></el-table-column>
<el-table-column prop="rider_name" label="骑手姓名" width="100"></el-table-column>
<el-table-column prop="rider_phone" label="手机号" width="120"></el-table-column>
<el-table-column prop="appeal_type" label="申诉类型" width="120">
<template slot-scope="scope">
<el-tag :type="getAppealTypeColor(scope.row.appeal_type)" class="appeal-type-tag">
{{ getAppealTypeText(scope.row.appeal_type) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="appeal_reason" label="申诉原因" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="urgency" label="紧急程度" width="100">
<template slot-scope="scope">
<el-tag
:type="getUrgencyColor(scope.row.urgency)"
:class="scope.row.urgency === 'urgent' ? 'urgent-tag' : ''"
class="status-tag">
{{ getUrgencyText(scope.row.urgency) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="create_time" label="申诉时间" width="180">
<template slot-scope="scope">
{{ formatDateTime(scope.row.create_time) }}
</template>
</el-table-column>
<el-table-column prop="status" label="处理状态" width="100">
<template slot-scope="scope">
<el-tag :type="getStatusColor(scope.row.status)" class="status-tag">
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="handler_name" label="处理人" width="100"></el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template slot-scope="scope">
<el-button size="mini" @click="viewDetail(scope.row)">详情</el-button>
<el-button
v-if="scope.row.status === 'pending'"
size="mini"
type="success"
@click="approveAppeal(scope.row)">通过</el-button>
<el-button
v-if="scope.row.status === 'pending'"
size="mini"
type="warning"
@click="rejectAppeal(scope.row)">拒绝</el-button>
<el-button
v-if="scope.row.status !== 'pending'"
size="mini"
type="info"
@click="viewResult(scope.row)">查看结果</el-button>
</template>
</el-table-column>
</el-table>
<div style="padding: 20px; text-align: right;">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pagination.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total">
</el-pagination>
</div>
</div>
<!-- 申诉详情对话框 -->
<el-dialog title="申诉详情" :visible.sync="detailDialogVisible" width="800px">
<div v-if="currentAppeal">
<el-descriptions :column="2" border>
<el-descriptions-item label="申诉ID">{{ currentAppeal.id }}</el-descriptions-item>
<el-descriptions-item label="订单号">{{ currentAppeal.order_no }}</el-descriptions-item>
<el-descriptions-item label="骑手姓名">{{ currentAppeal.rider_name }}</el-descriptions-item>
<el-descriptions-item label="手机号">{{ currentAppeal.rider_phone }}</el-descriptions-item>
<el-descriptions-item label="申诉类型">{{ getAppealTypeText(currentAppeal.appeal_type) }}</el-descriptions-item>
<el-descriptions-item label="紧急程度">{{ getUrgencyText(currentAppeal.urgency) }}</el-descriptions-item>
<el-descriptions-item label="申诉时间">{{ formatDateTime(currentAppeal.create_time) }}</el-descriptions-item>
<el-descriptions-item label="处理状态">{{ getStatusText(currentAppeal.status) }}</el-descriptions-item>
</el-descriptions>
<el-descriptions :column="1" border style="margin-top: 20px;">
<el-descriptions-item label="申诉原因">{{ currentAppeal.appeal_reason }}</el-descriptions-item>
<el-descriptions-item label="详细描述">{{ currentAppeal.description || '暂无详细描述' }}</el-descriptions-item>
</el-descriptions>
<div v-if="currentAppeal.evidence_images && currentAppeal.evidence_images.length > 0" style="margin-top: 20px;">
<h4>证据图片</h4>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<img
v-for="(image, index) in currentAppeal.evidence_images"
:key="index"
:src="image"
style="width: 100px; height: 100px; object-fit: cover; border-radius: 4px; cursor: pointer;"
@click="previewImage(image)">
</div>
</div>
<div v-if="currentAppeal.status !== 'pending'" style="margin-top: 20px;">
<h4>处理结果</h4>
<el-descriptions :column="2" border>
<el-descriptions-item label="处理人">{{ currentAppeal.handler_name }}</el-descriptions-item>
<el-descriptions-item label="处理时间">{{ formatDateTime(currentAppeal.handle_time) }}</el-descriptions-item>
<el-descriptions-item label="处理结果" :span="2">{{ currentAppeal.handle_result || '暂无处理结果' }}</el-descriptions-item>
</el-descriptions>
</div>
</div>
</el-dialog>
<!-- 处理申诉对话框 -->
<el-dialog :title="handleDialogTitle" :visible.sync="handleDialogVisible" width="500px">
<el-form :model="handleForm" :rules="handleRules" ref="handleForm" label-width="100px">
<el-form-item label="处理结果" prop="result">
<el-input
type="textarea"
v-model="handleForm.result"
placeholder="请输入处理结果说明"
:rows="4">
</el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmHandle">确定</el-button>
</div>
</el-dialog>
<!-- 处理结果对话框 -->
<el-dialog title="处理结果" :visible.sync="resultDialogVisible" width="600px">
<div v-if="currentAppeal">
<el-descriptions :column="2" border>
<el-descriptions-item label="处理人">{{ currentAppeal.handler_name }}</el-descriptions-item>
<el-descriptions-item label="处理时间">{{ formatDateTime(currentAppeal.handle_time) }}</el-descriptions-item>
<el-descriptions-item label="处理状态">
<el-tag :type="getStatusColor(currentAppeal.status)">
{{ getStatusText(currentAppeal.status) }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions :column="1" border style="margin-top: 20px;">
<el-descriptions-item label="处理结果">{{ currentAppeal.handle_result || '暂无处理结果' }}</el-descriptions-item>
</el-descriptions>
</div>
</el-dialog>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
loading: false,
tableData: [],
selectedAppeals: [],
stats: {},
searchForm: {
keyword: '',
appeal_type: '',
status: '',
urgency: '',
dateRange: []
},
pagination: {
page: 1,
pageSize: 20,
total: 0
},
detailDialogVisible: false,
handleDialogVisible: false,
resultDialogVisible: false,
handleDialogTitle: '',
currentAppeal: null,
currentAction: '',
handleForm: {
result: ''
},
handleRules: {
result: [
{ required: true, message: '请输入处理结果', trigger: 'blur' }
]
}
}
},
mounted() {
this.loadData();
this.loadStats();
},
methods: {
loadData() {
this.loading = true;
const params = {
page: this.pagination.page,
pageSize: this.pagination.pageSize,
...this.searchForm
};
// 模拟API调用
setTimeout(() => {
this.tableData = [
{
id: 1,
order_no: 'ORD202401001',
rider_name: '张三',
rider_phone: '13800138001',
appeal_type: 'order_exception',
appeal_reason: '客户地址错误导致配送延误',
urgency: 'urgent',
create_time: '2024-01-15 14:30:00',
status: 'pending',
handler_name: '',
description: '客户提供的地址不准确,导致我找了很久才找到正确位置,希望能够取消超时处罚',
evidence_images: ['/uploads/evidence1.jpg', '/uploads/evidence2.jpg']
},
{
id: 2,
order_no: 'ORD202401002',
rider_name: '李四',
rider_phone: '13800138002',
appeal_type: 'bad_review',
appeal_reason: '客户恶意差评',
urgency: 'normal',
create_time: '2024-01-15 10:20:00',
status: 'approved',
handler_name: '管理员',
handle_time: '2024-01-15 16:00:00',
handle_result: '经核实,客户确实存在恶意差评行为,申诉通过,已删除该差评记录',
description: '按时送达,服务态度良好,但客户给了差评'
}
];
this.pagination.total = 2;
this.loading = false;
}, 500);
},
loadStats() {
// 模拟统计数据
this.stats = {
total_appeals: 156,
pending_appeals: 23,
approved_appeals: 98,
approval_rate: 73.6
};
},
handleSelectionChange(selection) {
this.selectedAppeals = selection;
},
handleSizeChange(val) {
this.pagination.pageSize = val;
this.loadData();
},
handleCurrentChange(val) {
this.pagination.page = val;
this.loadData();
},
resetSearch() {
this.searchForm = {
keyword: '',
appeal_type: '',
status: '',
urgency: '',
dateRange: []
};
this.pagination.page = 1;
this.loadData();
},
viewDetail(row) {
this.currentAppeal = row;
this.detailDialogVisible = true;
},
approveAppeal(row) {
this.currentAppeal = row;
this.currentAction = 'approve';
this.handleDialogTitle = '通过申诉';
this.handleForm.result = '';
this.handleDialogVisible = true;
},
rejectAppeal(row) {
this.currentAppeal = row;
this.currentAction = 'reject';
this.handleDialogTitle = '拒绝申诉';
this.handleForm.result = '';
this.handleDialogVisible = true;
},
confirmHandle() {
this.$refs.handleForm.validate((valid) => {
if (valid) {
const action = this.currentAction === 'approve' ? '通过' : '拒绝';
// 模拟处理操作
this.$message.success(`申诉${action}成功`);
this.handleDialogVisible = false;
this.loadData();
}
});
},
batchApprove() {
this.$confirm(`确定要批量通过选中的 ${this.selectedAppeals.length} 个申诉吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 模拟批量通过
this.$message.success('批量通过成功');
this.loadData();
});
},
batchReject() {
this.$confirm(`确定要批量拒绝选中的 ${this.selectedAppeals.length} 个申诉吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 模拟批量拒绝
this.$message.success('批量拒绝成功');
this.loadData();
});
},
viewResult(row) {
this.currentAppeal = row;
this.resultDialogVisible = true;
},
previewImage(src) {
// 简单的图片预览
window.open(src, '_blank');
},
getAppealTypeText(type) {
const typeMap = {
'order_exception': '异常订单',
'bad_review': '差评申诉',
'timeout': '超时申诉',
'deduction': '扣费申诉',
'other': '其他'
};
return typeMap[type] || type;
},
getAppealTypeColor(type) {
const colorMap = {
'order_exception': 'warning',
'bad_review': 'danger',
'timeout': 'info',
'deduction': 'primary',
'other': ''
};
return colorMap[type] || '';
},
getStatusText(status) {
const statusMap = {
'pending': '待处理',
'approved': '已通过',
'rejected': '已拒绝'
};
return statusMap[status] || status;
},
getStatusColor(status) {
const colorMap = {
'pending': 'warning',
'approved': 'success',
'rejected': 'danger'
};
return colorMap[status] || '';
},
getUrgencyText(urgency) {
const urgencyMap = {
'urgent': '紧急',
'normal': '普通',
'low': '低'
};
return urgencyMap[urgency] || urgency;
},
getUrgencyColor(urgency) {
const colorMap = {
'urgent': 'danger',
'normal': 'primary',
'low': 'info'
};
return colorMap[urgency] || '';
},
formatDateTime(timestamp) {
if (!timestamp) return '';
return new Date(timestamp).toLocaleString('zh-CN');
}
}
});
</script>
</body>
</html>