feat: 资金冻结客服三页(规则/明细/风险指标)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -39,6 +39,21 @@ const routes = [
|
||||
component: () => import('@/views/finance/FinanceData.vue'),
|
||||
meta: { title: '财务数据' }
|
||||
},
|
||||
{
|
||||
path: 'finance/fund-freeze-config',
|
||||
component: () => import('@/views/finance/FundFreezeConfig.vue'),
|
||||
meta: { title: '资金冻结规则' }
|
||||
},
|
||||
{
|
||||
path: 'finance/fund-freeze-ledger',
|
||||
component: () => import('@/views/finance/FundFreezeLedger.vue'),
|
||||
meta: { title: '资金冻结明细' }
|
||||
},
|
||||
{
|
||||
path: 'finance/fund-risk-stats',
|
||||
component: () => import('@/views/finance/FundFreezeRisk.vue'),
|
||||
meta: { title: '罚款投诉指标' }
|
||||
},
|
||||
|
||||
// ========== 考核管理 ==========
|
||||
{
|
||||
|
||||
@@ -125,6 +125,9 @@ export function shouldShowAllMenus(access) {
|
||||
const KEFU_MENU_ROWS = [
|
||||
{ page_id: 'finance.bankuai', parent_id: 'finance', path: '/finance/bankuai', sort_order: 11, perm_codes: ['bankuai'] },
|
||||
{ page_id: 'finance.data', parent_id: 'finance', path: '/finance/data', sort_order: 12, perm_codes: ['caiwu'] },
|
||||
{ page_id: 'finance.fund-freeze-config', parent_id: 'finance', path: '/finance/fund-freeze-config', sort_order: 13, perm_codes: ['djcfg001'] },
|
||||
{ page_id: 'finance.fund-freeze-ledger', parent_id: 'finance', path: '/finance/fund-freeze-ledger', sort_order: 14, perm_codes: ['djled001'] },
|
||||
{ page_id: 'finance.fund-risk-stats', parent_id: 'finance', path: '/finance/fund-risk-stats', sort_order: 15, perm_codes: ['djrisk001'] },
|
||||
{ page_id: 'assessment.examiner', parent_id: 'assessment', path: '/assessment/examiner', sort_order: 21, perm_codes: ['kaohepeizhi'] },
|
||||
{ page_id: 'assessment.config', parent_id: 'assessment', path: '/assessment/config', sort_order: 22, perm_codes: ['kaohepeizhi'] },
|
||||
{ page_id: 'assessment.record', parent_id: 'assessment', path: '/assessment/record', sort_order: 23, perm_codes: ['kaohepeizhi'] },
|
||||
@@ -172,7 +175,7 @@ const KEFU_MENU_ROWS = [
|
||||
|
||||
const KEFU_MENU_PARENT_IDS = {
|
||||
admin: ['admin.role', 'admin.data-scope', 'admin.club-config', 'admin.order-grab-share', 'admin.user', 'admin.operation-log'],
|
||||
finance: ['finance.bankuai', 'finance.data'],
|
||||
finance: ['finance.bankuai', 'finance.data', 'finance.fund-freeze-config', 'finance.fund-freeze-ledger', 'finance.fund-risk-stats'],
|
||||
assessment: ['assessment.examiner', 'assessment.config', 'assessment.record'],
|
||||
order: ['order.platform', 'order.merchant'],
|
||||
'cross-platform': ['cross-platform.order', 'cross-platform.settings', 'cross-platform.data', 'cross-platform.pre'],
|
||||
@@ -423,6 +426,9 @@ export const JITUAN_API = {
|
||||
adminAssignments: '/jituan/houtai/admin-assignments',
|
||||
clubManage: '/jituan/houtai/club-manage',
|
||||
orderGrabLink: '/jituan/houtai/order-grab-link',
|
||||
fundFreezeConfig: '/jituan/houtai/fund-freeze-config',
|
||||
fundFreezeLedger: '/jituan/houtai/fund-freeze-ledger',
|
||||
fundFreezeRisk: '/jituan/houtai/fund-freeze-risk',
|
||||
paymentChannelManage: '/jituan/houtai/payment-channel-manage',
|
||||
clubCertUpload: '/jituan/houtai/club-cert-upload',
|
||||
memberList: '/jituan/houtai/hthqhylb',
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
</template>
|
||||
<el-menu-item v-if="canShow('finance.bankuai') && isAllClubScope" index="/finance/bankuai">板块配置</el-menu-item>
|
||||
<el-menu-item v-if="canShow('finance.data')" index="/finance/data">财务数据</el-menu-item>
|
||||
<el-menu-item v-if="canShow('finance.fund-freeze-config')" index="/finance/fund-freeze-config">资金冻结规则</el-menu-item>
|
||||
<el-menu-item v-if="canShow('finance.fund-freeze-ledger')" index="/finance/fund-freeze-ledger">资金冻结明细</el-menu-item>
|
||||
<el-menu-item v-if="canShow('finance.fund-risk-stats')" index="/finance/fund-risk-stats">罚款投诉指标</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
|
||||
|
||||
282
src/views/finance/FundFreezeConfig.vue
Normal file
282
src/views/finance/FundFreezeConfig.vue
Normal file
@@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<div class="fund-freeze-config" v-loading="loading">
|
||||
<ClubScopeHint />
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">资金冻结规则</h2>
|
||||
<el-button @click="loadConfig">刷新</el-button>
|
||||
</div>
|
||||
<el-alert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="默认关闭。未开启时结算全额进可提现,与现网一致。仅对当前顶部所选俱乐部生效。"
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
<div v-if="clubId" class="club-hint">当前俱乐部:{{ clubId }}</div>
|
||||
|
||||
<el-tabs v-model="activeRole" @tab-change="onTabChange">
|
||||
<el-tab-pane
|
||||
v-for="item in roles"
|
||||
:key="item.role"
|
||||
:label="item.role_name"
|
||||
:name="item.role"
|
||||
/>
|
||||
</el-tabs>
|
||||
|
||||
<div v-if="form" class="form-card">
|
||||
<el-form label-width="140px">
|
||||
<el-form-item label="启用冻结">
|
||||
<el-switch v-model="form.enabled" />
|
||||
<span class="tip">关闭=该角色入账不冻结</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="冻结方式">
|
||||
<el-radio-group v-model="form.freeze_mode">
|
||||
<el-radio :label="1">按比例</el-radio>
|
||||
<el-radio :label="2">固定额</el-radio>
|
||||
<el-radio :label="3">比例+上限</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.freeze_mode !== 2" label="冻结比例">
|
||||
<el-input-number v-model="form.freeze_rate" :min="0" :max="1" :step="0.01" :precision="4" />
|
||||
<span class="tip">0.2 = 冻 20%</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.freeze_mode === 2" label="固定冻结额">
|
||||
<el-input-number v-model="form.freeze_fixed" :min="0" :precision="2" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.freeze_mode === 3" label="单笔上限">
|
||||
<el-input-number v-model="form.freeze_cap" :min="0" :precision="2" />
|
||||
<span class="tip">0=不限</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="最低入账才冻">
|
||||
<el-input-number v-model="form.min_credit_to_freeze" :min="0" :precision="2" />
|
||||
</el-form-item>
|
||||
<el-form-item label="入账来源">
|
||||
<el-checkbox-group v-model="form.source_scope">
|
||||
<el-checkbox label="order_settle">订单结算</el-checkbox>
|
||||
<el-checkbox label="fenhong">分红/分佣</el-checkbox>
|
||||
<el-checkbox label="kaohe_fee">考核代收费</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="解冻触发">
|
||||
<el-select v-model="form.unfreeze_trigger" style="width: 260px">
|
||||
<el-option label="仅到期" value="time_only" />
|
||||
<el-option label="仅条件达标" value="condition_only" />
|
||||
<el-option label="到期且达标" value="time_and_condition" />
|
||||
<el-option label="到期或达标" value="time_or_condition" />
|
||||
<el-option label="仅手动" value="manual_only" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="解冻节奏">
|
||||
<el-radio-group v-model="form.schedule_mode">
|
||||
<el-radio label="all_at_once">一次全解</el-radio>
|
||||
<el-radio label="installment">分期解冻</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="最短冻结天数">
|
||||
<el-input-number v-model="form.freeze_min_days" :min="0" />
|
||||
<span class="tip">一次全解时生效;分期以各步 after_days 为准</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.schedule_mode === 'installment'" label="分期 JSON">
|
||||
<el-input
|
||||
v-model="installmentText"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder='{"steps":[{"seq":1,"after_days":7,"type":"ratio","value":0.3,"need_condition":false}]}'
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="条件 JSON">
|
||||
<el-input
|
||||
v-model="conditionText"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder='{"min_completed_orders":30,"max_fine_rate":0.05,"fine_rate_window_days":30}'
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" maxlength="200" />
|
||||
</el-form-item>
|
||||
<el-form-item label="试算(入账100)">
|
||||
<span>可提现 {{ preview.available }} / 冻结 {{ preview.frozen }}</span>
|
||||
<el-button link type="primary" @click="refreshPreview">重算</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="save">保存该角色规则</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
import { JITUAN_API } from '@/utils/club-context'
|
||||
import ClubScopeHint from '@/components/ClubScopeHint.vue'
|
||||
|
||||
const username = localStorage.getItem('username') || ''
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const clubId = ref('')
|
||||
const roles = ref([])
|
||||
const activeRole = ref('dashou')
|
||||
const form = ref(null)
|
||||
const installmentText = ref('')
|
||||
const conditionText = ref('')
|
||||
const preview = reactive({ available: '100', frozen: '0' })
|
||||
|
||||
function syncFormFromRole(role) {
|
||||
const item = roles.value.find((r) => r.role === role)
|
||||
if (!item) {
|
||||
form.value = null
|
||||
return
|
||||
}
|
||||
form.value = {
|
||||
...item,
|
||||
freeze_rate: Number(item.freeze_rate || 0),
|
||||
freeze_fixed: Number(item.freeze_fixed || 0),
|
||||
freeze_cap: Number(item.freeze_cap || 0),
|
||||
min_credit_to_freeze: Number(item.min_credit_to_freeze || 0),
|
||||
freeze_min_days: Number(item.freeze_min_days || 0),
|
||||
source_scope: Array.isArray(item.source_scope) ? [...item.source_scope] : ['order_settle'],
|
||||
}
|
||||
installmentText.value = JSON.stringify(item.installment_json || {}, null, 2)
|
||||
conditionText.value = JSON.stringify(item.condition_json || {}, null, 2)
|
||||
preview.available = item.preview_100?.available || '100'
|
||||
preview.frozen = item.preview_100?.frozen || '0'
|
||||
}
|
||||
|
||||
function onTabChange(name) {
|
||||
syncFormFromRole(name)
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await request.post(JITUAN_API.fundFreezeConfig, { username, action: 'get' })
|
||||
if (res.code === 403) {
|
||||
ElMessage.error(res.msg || '无权限')
|
||||
return
|
||||
}
|
||||
if (res.code !== 0) {
|
||||
ElMessage.error(res.msg || '加载失败')
|
||||
return
|
||||
}
|
||||
clubId.value = res.data?.club_id || ''
|
||||
roles.value = res.data?.roles || []
|
||||
if (!roles.value.find((r) => r.role === activeRole.value) && roles.value[0]) {
|
||||
activeRole.value = roles.value[0].role
|
||||
}
|
||||
syncFormFromRole(activeRole.value)
|
||||
} catch {
|
||||
ElMessage.error('网络错误')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function refreshPreview() {
|
||||
if (!form.value) return
|
||||
const credit = 100
|
||||
let frozen = 0
|
||||
const rate = Number(form.value.freeze_rate || 0)
|
||||
const fixed = Number(form.value.freeze_fixed || 0)
|
||||
const cap = Number(form.value.freeze_cap || 0)
|
||||
if (!form.value.enabled) {
|
||||
preview.available = '100.00'
|
||||
preview.frozen = '0.00'
|
||||
return
|
||||
}
|
||||
if (form.value.freeze_mode === 2) frozen = fixed
|
||||
else {
|
||||
frozen = credit * rate
|
||||
if (form.value.freeze_mode === 3 && cap > 0) frozen = Math.min(frozen, cap)
|
||||
}
|
||||
frozen = Math.max(0, Math.min(credit, frozen))
|
||||
preview.frozen = frozen.toFixed(2)
|
||||
preview.available = (credit - frozen).toFixed(2)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [form.value?.enabled, form.value?.freeze_mode, form.value?.freeze_rate, form.value?.freeze_fixed, form.value?.freeze_cap],
|
||||
() => refreshPreview(),
|
||||
)
|
||||
|
||||
async function save() {
|
||||
if (!form.value) return
|
||||
let installment_json = {}
|
||||
let condition_json = {}
|
||||
try {
|
||||
installment_json = installmentText.value ? JSON.parse(installmentText.value) : {}
|
||||
} catch {
|
||||
ElMessage.error('分期 JSON 格式错误')
|
||||
return
|
||||
}
|
||||
try {
|
||||
condition_json = conditionText.value ? JSON.parse(conditionText.value) : {}
|
||||
} catch {
|
||||
ElMessage.error('条件 JSON 格式错误')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await request.post(JITUAN_API.fundFreezeConfig, {
|
||||
username,
|
||||
action: 'save',
|
||||
role: form.value.role,
|
||||
enabled: form.value.enabled,
|
||||
freeze_mode: form.value.freeze_mode,
|
||||
freeze_rate: form.value.freeze_rate,
|
||||
freeze_fixed: form.value.freeze_fixed,
|
||||
freeze_cap: form.value.freeze_cap,
|
||||
min_credit_to_freeze: form.value.min_credit_to_freeze,
|
||||
source_scope: form.value.source_scope,
|
||||
unfreeze_trigger: form.value.unfreeze_trigger,
|
||||
schedule_mode: form.value.schedule_mode,
|
||||
freeze_min_days: form.value.freeze_min_days,
|
||||
installment_json,
|
||||
condition_json,
|
||||
remark: form.value.remark,
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('已保存')
|
||||
await loadConfig()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '保存失败')
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('网络错误')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadConfig)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
.club-hint {
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.form-card {
|
||||
background: #fff;
|
||||
padding: 16px 20px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.tip {
|
||||
margin-left: 10px;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
195
src/views/finance/FundFreezeLedger.vue
Normal file
195
src/views/finance/FundFreezeLedger.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="fund-freeze-ledger" v-loading="loading">
|
||||
<ClubScopeHint />
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">资金冻结明细</h2>
|
||||
<el-button @click="fetchList">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<el-select v-model="filters.role" clearable placeholder="角色" style="width: 120px">
|
||||
<el-option label="打手" value="dashou" />
|
||||
<el-option label="管事" value="guanshi" />
|
||||
<el-option label="组长" value="zuzhang" />
|
||||
<el-option label="考核官" value="shenheguan" />
|
||||
</el-select>
|
||||
<el-select v-model="filters.status" clearable placeholder="状态" style="width: 130px">
|
||||
<el-option label="冻结中" :value="1" />
|
||||
<el-option label="部分解冻" :value="2" />
|
||||
<el-option label="已解冻" :value="3" />
|
||||
<el-option label="已作废" :value="4" />
|
||||
</el-select>
|
||||
<el-input v-model="filters.keyword" clearable placeholder="用户ID/订单号" style="width: 200px" />
|
||||
<el-button type="primary" @click="onSearch">查询</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="list" border style="margin-top: 12px">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="user_id" label="用户" width="110" />
|
||||
<el-table-column prop="role_name" label="角色" width="80" />
|
||||
<el-table-column prop="amount_total" label="冻结总额" width="100" />
|
||||
<el-table-column prop="amount_unfrozen" label="已解冻" width="90" />
|
||||
<el-table-column prop="amount_remain" label="剩余" width="90" />
|
||||
<el-table-column prop="status_label" label="状态" width="90" />
|
||||
<el-table-column prop="order_id" label="订单" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="frozen_at" label="冻结时间" min-width="160" />
|
||||
<el-table-column prop="next_eligible_at" label="下次可解" min-width="160" />
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="showEvents(row)">流水</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 1 || row.status === 2"
|
||||
link
|
||||
type="warning"
|
||||
@click="doUnfreeze(row, 'step')"
|
||||
>解一步</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 1 || row.status === 2"
|
||||
link
|
||||
type="danger"
|
||||
@click="doUnfreeze(row, 'all')"
|
||||
>全解</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
background
|
||||
layout="total, prev, pager, next"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page="page"
|
||||
@current-change="onPage"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="eventsVisible" title="解冻流水" width="640px">
|
||||
<el-table :data="events" border size="small">
|
||||
<el-table-column prop="step_seq" label="步" width="60" />
|
||||
<el-table-column prop="amount" label="金额" width="90" />
|
||||
<el-table-column prop="trigger" label="触发" width="90" />
|
||||
<el-table-column prop="reason" label="说明" min-width="140" />
|
||||
<el-table-column prop="unfrozen_at" label="时间" min-width="160" />
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
import { JITUAN_API } from '@/utils/club-context'
|
||||
import ClubScopeHint from '@/components/ClubScopeHint.vue'
|
||||
|
||||
const username = localStorage.getItem('username') || ''
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const filters = reactive({ role: '', status: '', keyword: '' })
|
||||
const eventsVisible = ref(false)
|
||||
const events = ref([])
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await request.post(JITUAN_API.fundFreezeLedger, {
|
||||
username,
|
||||
action: 'list',
|
||||
page: page.value,
|
||||
page_size: pageSize,
|
||||
role: filters.role || undefined,
|
||||
status: filters.status === '' || filters.status === null ? undefined : filters.status,
|
||||
keyword: filters.keyword || undefined,
|
||||
})
|
||||
if (res.code === 403) {
|
||||
ElMessage.error(res.msg || '无权限')
|
||||
return
|
||||
}
|
||||
if (res.code !== 0) {
|
||||
ElMessage.error(res.msg || '加载失败')
|
||||
return
|
||||
}
|
||||
list.value = res.data?.list || []
|
||||
total.value = res.data?.total || 0
|
||||
} catch {
|
||||
ElMessage.error('网络错误')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
page.value = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function onPage(p) {
|
||||
page.value = p
|
||||
fetchList()
|
||||
}
|
||||
|
||||
async function showEvents(row) {
|
||||
try {
|
||||
const res = await request.post(JITUAN_API.fundFreezeLedger, {
|
||||
username,
|
||||
action: 'events',
|
||||
ledger_id: row.id,
|
||||
})
|
||||
if (res.code === 0) {
|
||||
events.value = res.data?.list || []
|
||||
eventsVisible.value = true
|
||||
} else {
|
||||
ElMessage.error(res.msg || '加载流水失败')
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('网络错误')
|
||||
}
|
||||
}
|
||||
|
||||
async function doUnfreeze(row, mode) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
mode === 'all' ? `确认全部解冻剩余 ${row.amount_remain}?` : `确认解冻当前分期一步?`,
|
||||
'手动解冻',
|
||||
{ type: 'warning' },
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await request.post(JITUAN_API.fundFreezeLedger, {
|
||||
username,
|
||||
action: 'unfreeze',
|
||||
ledger_id: row.id,
|
||||
mode,
|
||||
reason: mode === 'all' ? '后台手动全部解冻' : '后台手动解冻一步',
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('解冻成功')
|
||||
fetchList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '解冻失败')
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('网络错误')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchList)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.page-title { margin: 0; font-size: 20px; }
|
||||
.filters { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.pager { margin-top: 16px; display: flex; justify-content: flex-end; }
|
||||
</style>
|
||||
130
src/views/finance/FundFreezeRisk.vue
Normal file
130
src/views/finance/FundFreezeRisk.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="fund-freeze-risk" v-loading="loading">
|
||||
<ClubScopeHint />
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">罚款投诉指标</h2>
|
||||
<el-button @click="fetchList">刷新</el-button>
|
||||
</div>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="展示角色扩展表上的风险快照与冻结池。日汇总任务上线后快照会自动刷新;当前可先看冻结金额。"
|
||||
style="margin-bottom: 12px"
|
||||
/>
|
||||
|
||||
<div class="filters">
|
||||
<el-select v-model="filters.role" style="width: 120px" @change="onSearch">
|
||||
<el-option label="打手" value="dashou" />
|
||||
<el-option label="管事" value="guanshi" />
|
||||
<el-option label="组长" value="zuzhang" />
|
||||
<el-option label="考核官" value="shenheguan" />
|
||||
</el-select>
|
||||
<el-input v-model="filters.keyword" clearable placeholder="用户ID" style="width: 180px" />
|
||||
<el-checkbox v-model="filters.only_frozen">仅看有冻结</el-checkbox>
|
||||
<el-button type="primary" @click="onSearch">查询</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="list" border style="margin-top: 12px">
|
||||
<el-table-column prop="user_id" label="用户ID" width="110" />
|
||||
<el-table-column prop="nicheng" label="昵称" width="120" />
|
||||
<el-table-column prop="role_name" label="角色" width="80" />
|
||||
<el-table-column prop="available" label="可提现" width="100" />
|
||||
<el-table-column prop="dongjie_yue" label="冻结池" width="100" />
|
||||
<el-table-column label="罚款率" width="100">
|
||||
<template #default="{ row }">{{ formatRate(row.risk_fine_rate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="投诉率" width="100">
|
||||
<template #default="{ row }">{{ formatRate(row.risk_complaint_rate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="risk_completed_orders" label="窗口完成单" width="110" />
|
||||
<el-table-column prop="risk_valid_invites" label="有效邀请" width="100" />
|
||||
<el-table-column prop="risk_stat_at" label="快照时间" min-width="160" />
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
background
|
||||
layout="total, prev, pager, next"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page="page"
|
||||
@current-change="onPage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
import { JITUAN_API } from '@/utils/club-context'
|
||||
import ClubScopeHint from '@/components/ClubScopeHint.vue'
|
||||
|
||||
const username = localStorage.getItem('username') || ''
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const filters = reactive({ role: 'dashou', keyword: '', only_frozen: false })
|
||||
|
||||
function formatRate(v) {
|
||||
const n = Number(v || 0)
|
||||
return `${(n * 100).toFixed(2)}%`
|
||||
}
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await request.post(JITUAN_API.fundFreezeRisk, {
|
||||
username,
|
||||
action: 'list',
|
||||
page: page.value,
|
||||
page_size: pageSize,
|
||||
role: filters.role,
|
||||
keyword: filters.keyword || undefined,
|
||||
only_frozen: filters.only_frozen,
|
||||
})
|
||||
if (res.code === 403) {
|
||||
ElMessage.error(res.msg || '无权限')
|
||||
return
|
||||
}
|
||||
if (res.code !== 0) {
|
||||
ElMessage.error(res.msg || '加载失败')
|
||||
return
|
||||
}
|
||||
list.value = res.data?.list || []
|
||||
total.value = res.data?.total || 0
|
||||
} catch {
|
||||
ElMessage.error('网络错误')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
page.value = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function onPage(p) {
|
||||
page.value = p
|
||||
fetchList()
|
||||
}
|
||||
|
||||
onMounted(fetchList)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.page-title { margin: 0; font-size: 20px; }
|
||||
.filters { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
||||
.pager { margin-top: 16px; display: flex; justify-content: flex-end; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user