Files
Vue3/src/views/Punishment.vue
2026-06-28 00:31:26 +08:00

238 lines
9.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="punishment">
<ClubScopeHint />
<div class="type-switch">
<el-radio-group v-model="activeType" size="large" @change="onTypeChange">
<el-radio-button value="jifen">积分处罚</el-radio-button>
<el-radio-button value="fakuan">罚款审核</el-radio-button>
</el-radio-group>
</div>
<!-- ==================== 积分处罚原有功能完全不变 ==================== -->
<template v-if="activeType === 'jifen'">
<!-- 顶部统计卡片 -->
<el-row :gutter="20" class="stats-cards">
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-card class="stat-card" shadow="hover">
<div class="stat-item">
<div class="stat-label">总记录</div>
<div class="stat-value">{{ stats.zongshu || 0 }}</div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-card class="stat-card" shadow="hover" @click="switchStatusTab('pending')">
<div class="stat-item">
<div class="stat-label">待处理</div>
<div class="stat-value warning">{{ stats.daichuli || 0 }}</div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-card class="stat-card" shadow="hover" @click="switchStatusTab('processed')">
<div class="stat-item">
<div class="stat-label">已处理</div>
<div class="stat-value success">{{ stats.yichuli || 0 }}</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 筛选区域 -->
<el-card class="filter-card" shadow="hover">
<el-row :gutter="20">
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-input v-model="filters.dingdan_id" placeholder="订单ID" clearable @keyup.enter="handleSearch" />
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-input v-model="filters.dashouid" placeholder="打手ID" clearable @keyup.enter="handleSearch" />
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="filter-actions">
<el-button type="primary" @click="handleSearch" :loading="loading">搜索</el-button>
<el-button @click="resetFilters">重置</el-button>
<el-button :icon="Refresh" @click="refreshList" circle />
</el-col>
</el-row>
<el-tabs v-model="activeStatus" class="status-tabs" @tab-change="handleStatusChange">
<el-tab-pane label="全部" name="all" />
<el-tab-pane label="待处理" name="pending" />
<el-tab-pane label="已处理" name="processed" />
</el-tabs>
</el-card>
<!-- 处罚记录表格 -->
<el-card class="table-card" shadow="hover">
<el-table :data="list" v-loading="loading" border stripe style="width: 100%" @row-click="goToDetail">
<el-table-column prop="dingdan_id" label="订单ID" width="180" />
<el-table-column prop="dashouid" label="打手ID" width="120" />
<el-table-column prop="qingqiuid" label="请求人ID" width="120" />
<el-table-column prop="cfliyou" label="处罚理由" min-width="200" show-overflow-tooltip />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row.sqzhuangtai)">{{ getStatusText(row.sqzhuangtai) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="create_time" label="创建时间" width="160" :formatter="formatTime" />
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click.stop="goToDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="page"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 30, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
</el-card>
</template>
<!-- ==================== 罚款审核模块新组件 ==================== -->
<FadanManagement v-if="activeType === 'fakuan'" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Refresh } from '@element-plus/icons-vue'
import request from '@/utils/request'
import { JITUAN_API } from '@/utils/club-context'
import ClubScopeHint from '@/components/ClubScopeHint.vue'
import FadanManagement from '@/views/punishment/components/FadanManagement.vue'
const router = useRouter()
// 当前选中的类型jifen / fakuan
const activeType = ref('jifen')
// ==================== 积分处罚相关数据(保持原样) ====================
const loading = ref(false)
const list = ref([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const stats = ref({ zongshu: 0, daichuli: 0, yichuli: 0 })
const filters = reactive({ dingdan_id: '', dashouid: '' })
const activeStatus = ref('all')
const getStatusArray = () => {
if (activeStatus.value === 'all') return undefined
if (activeStatus.value === 'pending') return [0, 3]
return [1, 2]
}
const fetchData = async () => {
loading.value = true
try {
const params = {
phone: localStorage.getItem('username'),
page: page.value,
page_size: pageSize.value,
dingdan_id: filters.dingdan_id || undefined,
dashouid: filters.dashouid || undefined
}
const statusArr = getStatusArray()
if (statusArr) params.status = statusArr
Object.keys(params).forEach(key => params[key] === undefined && delete params[key])
const res = await request.post(JITUAN_API.kefuCfgl, params)
if (res.code === 0) {
const data = res.data
list.value = (data.list || []).map((item) => ({
...item,
create_time: item.create_time || item.CreateTime || '',
update_time: item.update_time || item.UpdateTime || '',
}))
total.value = data.total || 0
stats.value = {
zongshu: data.stats?.zongshu || 0,
daichuli: data.stats?.daichuli || 0,
yichuli: data.stats?.yichuli || 0
}
} else {
ElMessage.error(res.msg || '获取失败')
}
} catch (error) {
console.error(error)
ElMessage.error('获取失败')
} finally {
loading.value = false
}
}
const handleSearch = () => { page.value = 1; fetchData() }
const resetFilters = () => { filters.dingdan_id = ''; filters.dashouid = ''; handleSearch() }
const refreshList = () => fetchData()
const handleStatusChange = () => { page.value = 1; fetchData() }
const switchStatusTab = (tab) => {
const map = { pending: 'pending', processed: 'processed' }
const next = map[tab]
if (!next || activeStatus.value === next) return
activeStatus.value = next
handleStatusChange()
}
const handleSizeChange = (val) => { pageSize.value = val; page.value = 1; fetchData() }
const handlePageChange = (val) => { page.value = val; fetchData() }
const goToDetail = (row) => {
router.push(`/punishment/detail/${row.dingdan_id}`)
}
const formatTime = (row, column, cellValue) => {
if (!cellValue) return '--'
const date = new Date(cellValue)
return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2,'0')}-${date.getDate().toString().padStart(2,'0')} ${date.getHours().toString().padStart(2,'0')}:${date.getMinutes().toString().padStart(2,'0')}`
}
const getStatusText = (status) => {
return { 0: '待处理', 1: '已处罚', 2: '已驳回', 3: '申诉中' }[status] || '未知'
}
const getStatusType = (status) => {
if (status === 0 || status === 3) return 'warning'
if (status === 1) return 'success'
if (status === 2) return 'info'
return ''
}
const onTypeChange = (val) => {
if (val === 'jifen') fetchData()
}
onMounted(() => {
if (activeType.value === 'jifen') fetchData()
})
</script>
<style scoped>
.punishment { padding: 20px; background-color: #f5f9fc; min-height: 100%; }
.type-switch { margin-bottom: 20px; text-align: center; }
.stats-cards { margin-bottom: 20px; }
.stat-card { border-radius: 12px; transition: transform 0.2s; cursor: pointer; }
.stat-card:hover { transform: translateY(-2px); box-shadow: 0 8px 16px rgba(0,0,0,0.05); }
.stat-item { text-align: center; padding: 10px 0; }
.stat-label { font-size: 14px; color: #6b7a88; margin-bottom: 8px; }
.stat-value { font-size: 32px; font-weight: 600; color: #1a2634; }
.stat-value.warning { color: #e6a23c; }
.stat-value.success { color: #67c23a; }
.filter-card { margin-bottom: 20px; border-radius: 12px; }
.filter-actions { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.status-tabs { margin-top: 16px; }
.table-card { border-radius: 12px; }
.pagination { margin-top: 20px; display: flex; justify-content: flex-end; }
@media screen and (max-width: 768px) {
.punishment { padding: 12px; }
.filter-actions { margin-top: 12px; justify-content: flex-start; }
.pagination { justify-content: center; }
}
</style>