fix: 后台用户按俱乐部分配角色,菜单与多俱乐部权限对齐
This commit is contained in:
@@ -3,6 +3,7 @@ import Layout from '@/views/Layout.vue'
|
||||
import request from '@/utils/request'
|
||||
import {
|
||||
ensureMenuAccess,
|
||||
syncClubContextFromServer,
|
||||
getMenuAccess,
|
||||
getDefaultLandingPath,
|
||||
getEffectiveVisiblePaths,
|
||||
@@ -362,6 +363,10 @@ router.beforeEach(async (to, from, next) => {
|
||||
}
|
||||
|
||||
try {
|
||||
await syncClubContextFromServer(async () => {
|
||||
const res = await request.get(JITUAN_API.meContext, { skipErrorToast: true })
|
||||
return res.code === 0 ? res.data : null
|
||||
})
|
||||
await ensureMenuAccess(async () => {
|
||||
const res = await request.get(JITUAN_API.menuAccess)
|
||||
return res.code === 0 ? res.data : null
|
||||
|
||||
@@ -3,7 +3,7 @@ const CLUB_SCOPE_KEY = 'admin_club_scope';
|
||||
const CLUB_CONTEXT_KEY = 'admin_club_context';
|
||||
const MENU_ACCESS_KEY = 'kefu_menu_access';
|
||||
const MENU_SESSION_KEY = 'kefu_menu_session_v';
|
||||
const MENU_SESSION_VERSION = '19';
|
||||
const MENU_SESSION_VERSION = '21';
|
||||
const DEFAULT_CLUB_ID = 'xq';
|
||||
|
||||
let menuAccessInflight = null;
|
||||
@@ -59,6 +59,10 @@ export function mergeServerClubContext(serverCtx) {
|
||||
}
|
||||
}
|
||||
|
||||
if (prevClubId && prevClubId !== clubId) {
|
||||
clearMenuAccess();
|
||||
}
|
||||
|
||||
setAdminClubContext(merged);
|
||||
return merged;
|
||||
}
|
||||
@@ -253,6 +257,21 @@ export function canSwitchClubByMenu() {
|
||||
return !!access?.can_switch_club;
|
||||
}
|
||||
|
||||
export async function syncClubContextFromServer(fetcher) {
|
||||
const prevClubId = getAdminClubId();
|
||||
try {
|
||||
const data = await fetcher();
|
||||
if (data) {
|
||||
mergeServerClubContext(data);
|
||||
if (getAdminClubId() !== prevClubId) {
|
||||
clearMenuAccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('syncClubContextFromServer failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureMenuAccess(fetcher, { force = false } = {}) {
|
||||
if (!localStorage.getItem('token')) return getMenuAccess();
|
||||
if (!force && isMenuSessionFresh() && getMenuAccess()) {
|
||||
|
||||
30
src/utils/penalty-status.js
Normal file
30
src/utils/penalty-status.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/** 金额罚单状态(与 Django utils/penalty_status.py 一致) */
|
||||
export const PENALTY_STATUS_MAP = {
|
||||
1: { text: '待缴纳', type: 'danger' },
|
||||
2: { text: '已缴纳', type: 'success' },
|
||||
3: { text: '申诉中', type: 'warning' },
|
||||
4: { text: '已驳回', type: 'info' },
|
||||
5: { text: '平台审核中', type: 'warning' },
|
||||
}
|
||||
|
||||
export const penaltyStatusText = (code) => PENALTY_STATUS_MAP[code]?.text || '未知'
|
||||
export const penaltyStatusType = (code) => PENALTY_STATUS_MAP[code]?.type || ''
|
||||
|
||||
/** 列表行展示状态(优先用后端 zhuangtai_text) */
|
||||
export const rowPenaltyStatusText = (row) => {
|
||||
if (row?.zhuangtai_text) return row.zhuangtai_text
|
||||
if (row?.platform_audit_pending) return '平台审核中'
|
||||
return penaltyStatusText(Number(row?.zhuangtai))
|
||||
}
|
||||
|
||||
export const rowPenaltyStatusType = (row) => {
|
||||
if (row?.platform_audit_pending) return 'warning'
|
||||
return penaltyStatusType(Number(row?.zhuangtai))
|
||||
}
|
||||
|
||||
/** 是否可平台审核(仅 zhuangtai=5) */
|
||||
export const isPlatformAuditPending = (row) => {
|
||||
if (!row) return false
|
||||
if (row.platform_audit_pending === true) return true
|
||||
return Number(row.zhuangtai) === 5
|
||||
}
|
||||
@@ -43,7 +43,7 @@
|
||||
<el-button type="primary" @click="fetchUserList">搜索</el-button>
|
||||
<el-button @click="resetFilters">重置</el-button>
|
||||
</div>
|
||||
<el-button type="success" @click="openAddUserDialog">
|
||||
<el-button v-if="canAddUser" type="success" @click="openAddUserDialog">
|
||||
<el-icon><Plus /></el-icon> 添加用户
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -399,6 +399,10 @@ const roleClubLabel = (role) => {
|
||||
return `[${cname}] ${role.role_name || role.role_code || ''}`
|
||||
}
|
||||
|
||||
const canAddUser = computed(() => {
|
||||
return canManageClubAssignments.value || manageableClubIds.value.length > 0
|
||||
})
|
||||
|
||||
const canManageRolesForClub = (clubId) => {
|
||||
const cid = clubId || ''
|
||||
if (canManageClubAssignments.value) return true
|
||||
@@ -411,7 +415,7 @@ const visibleRolesForRow = (row) => {
|
||||
return roles.filter((r) => manageableClubIds.value.includes(r.club_id || ''))
|
||||
}
|
||||
|
||||
// 权限状态
|
||||
// 权限状态:集团高管 / 俱乐部超管 / 000001 均可访问
|
||||
const hasPermission = ref(true)
|
||||
const canManageClubAssignments = ref(false)
|
||||
const manageableClubIds = ref([])
|
||||
@@ -605,6 +609,8 @@ const fetchUserList = async () => {
|
||||
total.value = res.data.total || 0
|
||||
canManageClubAssignments.value = !!res.data.can_manage_club_assignments
|
||||
manageableClubIds.value = res.data.manageable_club_ids || []
|
||||
} else if (res.code === 403) {
|
||||
hasPermission.value = false
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取用户列表失败')
|
||||
}
|
||||
@@ -853,7 +859,7 @@ const confirmAddUser = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
// 初始化:先拉用户列表(含 manageable_club_ids),再拉角色列表
|
||||
onMounted(async () => {
|
||||
if (!username) {
|
||||
ElMessage.error('未获取到用户信息')
|
||||
@@ -861,7 +867,9 @@ onMounted(async () => {
|
||||
}
|
||||
await loadClubOptions()
|
||||
await fetchUserList()
|
||||
await fetchRoleList()
|
||||
if (hasPermission.value) {
|
||||
await fetchRoleList()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -371,6 +371,7 @@ const removePermission = async (perm) => {
|
||||
username,
|
||||
action: 'remove_permission',
|
||||
role_code: currentRole.value.role_code,
|
||||
target_club_id: currentRole.value.club_id || '',
|
||||
perm_code: perm.perm_code
|
||||
})
|
||||
if (res.code === 0) {
|
||||
|
||||
@@ -526,7 +526,7 @@ onMounted(fetchData)
|
||||
}
|
||||
.member-data-table :deep(.el-table td.el-table__cell) {
|
||||
background: rgba(12, 18, 28, 0.55) !important;
|
||||
color: #d8ebff !important;
|
||||
color: #ffffff !important;
|
||||
border-bottom: 1px solid rgba(64, 158, 255, 0.1) !important;
|
||||
}
|
||||
.member-data-table :deep(.el-table__body tr:hover > td.el-table__cell) {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div v-else>
|
||||
<div class="header-bar">
|
||||
<h2>排行榜奖励配置</h2>
|
||||
<p class="desc">按俱乐部分奖;小程序可展示集团总榜,奖金仅发给本俱乐部用户。结算周期:昨日 / 上周 / 上月。</p>
|
||||
<p class="desc">按俱乐部分奖;结算周期:昨日 / 上周 / 上月。可设置活动起止日期,超出活动期不可结算与领取。</p>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeShenfen" @tab-change="loadSchemes">
|
||||
@@ -53,6 +53,12 @@
|
||||
<el-form-item label="领取有效天">
|
||||
<el-input-number v-model="form.claim_days" :min="1" :max="365" />
|
||||
</el-form-item>
|
||||
<el-form-item label="活动开始">
|
||||
<el-date-picker v-model="form.activity_start" type="date" value-format="YYYY-MM-DD" placeholder="如 2025-01-01" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="活动结束">
|
||||
<el-date-picker v-model="form.activity_end" type="date" value-format="YYYY-MM-DD" placeholder="如 2026-12-31" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规则说明">
|
||||
<el-input v-model="form.description" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
@@ -151,6 +157,8 @@ const form = reactive({
|
||||
sort_field: 'chengjiao_zongliang',
|
||||
min_metric: 0,
|
||||
claim_days: 30,
|
||||
activity_start: '',
|
||||
activity_end: '',
|
||||
description: '',
|
||||
tiers: [{ rank_from: 1, rank_to: 1, reward_amount: 0, label: '冠军' }],
|
||||
})
|
||||
@@ -184,6 +192,8 @@ const selectScheme = () => {
|
||||
sort_field: hit.sort_field || defaults,
|
||||
min_metric: hit.min_metric || 0,
|
||||
claim_days: hit.claim_days || 30,
|
||||
activity_start: hit.activity_start || '',
|
||||
activity_end: hit.activity_end || '',
|
||||
description: hit.description || '',
|
||||
tiers: hit.tiers?.length
|
||||
? hit.tiers.map((t) => ({ ...t, reward_amount: Number(t.reward_amount) }))
|
||||
@@ -196,6 +206,8 @@ const selectScheme = () => {
|
||||
sort_field: defaults,
|
||||
min_metric: 0,
|
||||
claim_days: 30,
|
||||
activity_start: '',
|
||||
activity_end: '',
|
||||
description: '',
|
||||
tiers: [{ rank_from: 1, rank_to: 1, reward_amount: 0, label: '冠军' }],
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<el-descriptions-item label="身份">{{ fadan.shenfen_text }}</el-descriptions-item>
|
||||
<el-descriptions-item label="金额">¥{{ fadan.fakuanjine }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="statusType(fadan.zhuangtai)">{{ statusText(fadan.zhuangtai) }}</el-tag>
|
||||
<el-tag :type="statusType(fadan)">{{ statusText(fadan) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="影响抢单">{{ fadan.yingxiang_qiangdan ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="罚款理由" :span="2">{{ fadan.chufaliyou }}</el-descriptions-item>
|
||||
@@ -36,8 +36,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 处理操作(仅申诉中) -->
|
||||
<div v-if="fadan.zhuangtai === 3" class="actions">
|
||||
<!-- 平台审核(商家提交的罚款,状态5) -->
|
||||
<div v-if="isPlatformAuditPending(fadan)" class="actions">
|
||||
<el-button type="success" @click="handlePlatformAction('platform_approve')">同意审核(进入待缴纳/申诉流程)</el-button>
|
||||
<el-button type="danger" @click="handlePlatformAction('platform_reject')">驳回申请</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 申诉处理(状态3) -->
|
||||
<div v-if="Number(fadan.zhuangtai) === 3" class="actions">
|
||||
<el-button type="success" @click="handleAction('agree')">同意申诉(驳回处罚)</el-button>
|
||||
<el-button type="danger" @click="handleAction('reject')">拒绝申诉</el-button>
|
||||
</div>
|
||||
@@ -48,7 +54,7 @@
|
||||
<el-form-item label="处理理由">
|
||||
<el-input v-model="reason" type="textarea" :rows="3" placeholder="请输入理由" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上传凭证(可选)">
|
||||
<el-form-item v-if="!isPlatformAction" label="上传凭证(可选)">
|
||||
<el-upload
|
||||
:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
@@ -73,10 +79,12 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import request from '@/utils/request'
|
||||
import { uploadFiles } from '@/utils/upload'
|
||||
import { JITUAN_API } from '@/utils/club-context'
|
||||
import { rowPenaltyStatusText, rowPenaltyStatusType, isPlatformAuditPending } from '@/utils/penalty-status'
|
||||
|
||||
const props = defineProps({
|
||||
visible: Boolean,
|
||||
@@ -90,16 +98,20 @@ const ossUrl = window.$ossURL || ''
|
||||
const fullZhengjuUrls = computed(() => (props.fadan?.zhengju_tupian || []).map(url => ossUrl + url))
|
||||
const fullShensuUrls = computed(() => (props.fadan?.shensu_tupian || []).map(url => ossUrl + url))
|
||||
|
||||
// 处理表单
|
||||
const actionDialog = ref(false)
|
||||
const actionTitle = ref('')
|
||||
const currentAction = ref('')
|
||||
const isPlatformAction = ref(false)
|
||||
const reason = ref('')
|
||||
const fileList = ref([])
|
||||
const selectedFiles = ref([])
|
||||
const submitting = ref(false)
|
||||
|
||||
const statusText = rowPenaltyStatusText
|
||||
const statusType = rowPenaltyStatusType
|
||||
|
||||
const handleAction = (action) => {
|
||||
isPlatformAction.value = false
|
||||
currentAction.value = action
|
||||
actionTitle.value = action === 'agree' ? '同意申诉(处罚将撤销)' : '拒绝申诉'
|
||||
reason.value = ''
|
||||
@@ -108,6 +120,16 @@ const handleAction = (action) => {
|
||||
actionDialog.value = true
|
||||
}
|
||||
|
||||
const handlePlatformAction = (action) => {
|
||||
isPlatformAction.value = true
|
||||
currentAction.value = action
|
||||
actionTitle.value = action === 'platform_approve' ? '同意平台审核' : '驳回罚款申请'
|
||||
reason.value = ''
|
||||
fileList.value = []
|
||||
selectedFiles.value = []
|
||||
actionDialog.value = true
|
||||
}
|
||||
|
||||
const handleFileChange = (file) => { selectedFiles.value.push(file.raw) }
|
||||
const handleFileRemove = (file) => {
|
||||
const idx = selectedFiles.value.indexOf(file.raw)
|
||||
@@ -117,21 +139,20 @@ const handleFileRemove = (file) => {
|
||||
const submitAction = async () => {
|
||||
submitting.value = true
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('username', localStorage.getItem('username') || '')
|
||||
formData.append('fadan_id', props.fadan.id)
|
||||
formData.append('action', currentAction.value)
|
||||
formData.append('chuli_liyou', reason.value)
|
||||
selectedFiles.value.forEach(f => formData.append('tupian', f))
|
||||
|
||||
// 直接使用 request 发送亦可,这里使用 uploadFiles 并附带字段
|
||||
// 因为 uploadFiles 接受额外数据和文件,但需要注意文件字段名是 'tupian'
|
||||
const res = await uploadFiles(JITUAN_API.penaltyAction, selectedFiles.value, {
|
||||
username: localStorage.getItem('username') || '',
|
||||
const username = localStorage.getItem('username') || ''
|
||||
const payload = {
|
||||
username,
|
||||
fadan_id: props.fadan.id,
|
||||
action: currentAction.value,
|
||||
chuli_liyou: reason.value
|
||||
}, 'tupian')
|
||||
}
|
||||
|
||||
let res
|
||||
if (isPlatformAction.value) {
|
||||
res = await request.post(JITUAN_API.penaltyPlatformAudit, payload)
|
||||
} else {
|
||||
res = await uploadFiles(JITUAN_API.penaltyAction, selectedFiles.value, payload, 'tupian')
|
||||
}
|
||||
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('处理成功')
|
||||
@@ -148,15 +169,6 @@ const submitAction = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const statusText = (code) => ({1:'待缴纳',2:'已缴纳',3:'申诉中',4:'申诉成功'}[code]||'未知')
|
||||
const statusType = (code) => {
|
||||
if (code === 1) return 'danger'
|
||||
if (code === 2) return 'success'
|
||||
if (code === 3) return 'warning'
|
||||
if (code === 4) return 'info'
|
||||
return ''
|
||||
}
|
||||
|
||||
watch(() => props.visible, (val) => { if (!val) actionDialog.value = false })
|
||||
</script>
|
||||
|
||||
@@ -166,5 +178,5 @@ watch(() => props.visible, (val) => { if (!val) actionDialog.value = false })
|
||||
.section-title { font-weight: 600; margin-bottom: 10px; }
|
||||
.img-grid { display: flex; flex-wrap: wrap; gap: 10px; }
|
||||
.grid-img { width: 100px; height: 100px; border-radius: 8px; }
|
||||
.actions { margin-top: 20px; display: flex; gap: 12px; }
|
||||
</style>
|
||||
.actions { margin-top: 20px; display: flex; flex-wrap: wrap; gap: 12px; }
|
||||
</style>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<el-table-column prop="chufaliyou" label="罚款理由" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusType(row.zhuangtai)">{{ statusText(row.zhuangtai) }}</el-tag>
|
||||
<el-tag :type="rowPenaltyStatusType(row)">{{ rowPenaltyStatusText(row) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="创建时间" width="160" />
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { rowPenaltyStatusText, rowPenaltyStatusType } from '@/utils/penalty-status'
|
||||
|
||||
const props = defineProps({
|
||||
list: Array,
|
||||
@@ -48,14 +49,8 @@ defineEmits(['page-change', 'size-change', 'detail'])
|
||||
const currentPage = computed({ get: () => props.page, set: () => {} })
|
||||
const currentSize = computed({ get: () => props.pageSize, set: () => {} })
|
||||
|
||||
const statusText = (code) => ({1:'待缴纳',2:'已缴纳',3:'申诉中',4:'申诉成功'}[code]||'未知')
|
||||
const statusType = (code) => {
|
||||
if (code === 1) return 'danger'
|
||||
if (code === 2) return 'success'
|
||||
if (code === 3) return 'warning'
|
||||
if (code === 4) return 'info'
|
||||
return ''
|
||||
}
|
||||
const statusText = rowPenaltyStatusText
|
||||
const statusType = rowPenaltyStatusType
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="fadan-management">
|
||||
<!-- 统计卡片 -->
|
||||
<FadanStats :stats="stats" @filter="handleStatsFilter" />
|
||||
<FadanStats :stats="stats" :active-status="currentStatus" @filter="handleStatsFilter" />
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<el-card class="control-card" shadow="hover">
|
||||
@@ -45,7 +45,7 @@
|
||||
:visible="detailVisible"
|
||||
:fadan="selectedFadan"
|
||||
@close="detailVisible = false"
|
||||
@processed="fetchData"
|
||||
@processed="onProcessed"
|
||||
/>
|
||||
|
||||
<!-- 添加弹窗 -->
|
||||
@@ -72,7 +72,7 @@ import AddFadan from './AddFadan.vue'
|
||||
const username = localStorage.getItem('username') || ''
|
||||
|
||||
const stats = reactive({
|
||||
total: 0, daijiaona: 0, shensuzhong: 0, yijiaona: 0, yibohui: 0
|
||||
total: 0, daijiaona: 0, shensuzhong: 0, yijiaona: 0, yibohui: 0, pingtai_shenhe: 0
|
||||
})
|
||||
|
||||
const list = ref([])
|
||||
@@ -133,7 +133,9 @@ const search = () => { page.value = 1; fetchData(1) }
|
||||
const resetSearch = () => {
|
||||
searchKey.value = ''
|
||||
filterShenfen.value = null
|
||||
search()
|
||||
currentStatus.value = null
|
||||
page.value = 1
|
||||
fetchData(1)
|
||||
}
|
||||
|
||||
const handlePageChange = (p) => { page.value = p; fetchData(p) }
|
||||
@@ -144,6 +146,11 @@ const openDetail = (row) => {
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
const onProcessed = async () => {
|
||||
await fetchStats()
|
||||
fetchData()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchStats()
|
||||
fetchData()
|
||||
|
||||
@@ -1,42 +1,15 @@
|
||||
<template>
|
||||
<el-row :gutter="20" class="stats-row">
|
||||
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
|
||||
<el-card class="stat-card" :class="{ 'no-permission': !permission }" shadow="hover" @click="emitFilter(null)">
|
||||
<el-col v-for="item in statItems" :key="item.code === null ? 'all' : item.code" :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
|
||||
<el-card
|
||||
class="stat-card"
|
||||
:class="{ active: isActive(item.code), 'no-permission': item.code === null && !permission }"
|
||||
shadow="hover"
|
||||
@click="emitFilter(item.code)"
|
||||
>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">总记录</div>
|
||||
<div class="stat-value">{{ stats.total || 0 }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
|
||||
<el-card class="stat-card" shadow="hover" @click="emitFilter(1)">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">待缴纳</div>
|
||||
<div class="stat-value danger">{{ stats.daijiaona || 0 }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
|
||||
<el-card class="stat-card" shadow="hover" @click="emitFilter(3)">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">申诉中</div>
|
||||
<div class="stat-value warning">{{ stats.shensuzhong || 0 }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
|
||||
<el-card class="stat-card" shadow="hover" @click="emitFilter(2)">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">已缴纳</div>
|
||||
<div class="stat-value success">{{ stats.yijiaona || 0 }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
|
||||
<el-card class="stat-card" shadow="hover" @click="emitFilter(4)">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">申诉成功</div>
|
||||
<div class="stat-value info">{{ stats.yibohui || 0 }}</div>
|
||||
<div class="stat-label">{{ item.label }}</div>
|
||||
<div class="stat-value" :class="item.cls">{{ item.value }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
@@ -44,20 +17,35 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
stats: Object,
|
||||
permission: Boolean
|
||||
permission: Boolean,
|
||||
activeStatus: { type: [Number, null], default: null },
|
||||
})
|
||||
const emit = defineEmits(['filter'])
|
||||
const emitFilter = (code) => {
|
||||
if (code === undefined) code = null
|
||||
emit('filter', code)
|
||||
}
|
||||
|
||||
const statItems = computed(() => [
|
||||
{ code: null, label: '总记录', value: props.stats?.total || 0, cls: '' },
|
||||
{ code: 1, label: '待缴纳', value: props.stats?.daijiaona || 0, cls: 'danger' },
|
||||
{ code: 3, label: '申诉中', value: props.stats?.shensuzhong || 0, cls: 'warning' },
|
||||
{ code: 2, label: '已缴纳', value: props.stats?.yijiaona || 0, cls: 'success' },
|
||||
{ code: 5, label: '平台审核中', value: props.stats?.pingtai_shenhe || 0, cls: 'warning' },
|
||||
{ code: 4, label: '申诉成功', value: props.stats?.yibohui || 0, cls: 'info' },
|
||||
])
|
||||
|
||||
const isActive = (code) => props.activeStatus === code
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stats-row { margin-bottom: 20px; }
|
||||
.stat-card { border-radius: 12px; cursor: pointer; transition: transform 0.2s; }
|
||||
.stat-card.active { border: 2px solid #409eff; box-shadow: 0 0 0 1px rgba(64,158,255,0.2); }
|
||||
.stat-card:hover { transform: translateY(-2px); }
|
||||
.stat-item { text-align: center; padding: 5px 0; }
|
||||
.stat-label { font-size: 14px; color: #6b7a88; }
|
||||
|
||||
Reference in New Issue
Block a user