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