积分与余额账务系统硬切重构
This commit is contained in:
@@ -12,6 +12,21 @@ import { apiMethods } from './modules/index.js'
|
||||
const API_ROOT = window.aksrtAdminData?.apiUrl || '/wp-json/aksrt/v1'
|
||||
const NONCE = window.aksrtAdminData?.nonce || ''
|
||||
|
||||
function createIdempotencyKey() {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
if (typeof crypto === 'undefined' || typeof crypto.getRandomValues !== 'function') {
|
||||
throw new Error('当前浏览器无法生成安全操作标识')
|
||||
}
|
||||
const bytes = new Uint8Array(16)
|
||||
crypto.getRandomValues(bytes)
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80
|
||||
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
|
||||
}
|
||||
|
||||
function normalizeDomainKey(domain) {
|
||||
return String(domain || 'global').trim().replace(/-/g, '_')
|
||||
}
|
||||
@@ -85,6 +100,7 @@ function joinRestUrl(baseUrl, endpoint) {
|
||||
class ApiClient {
|
||||
constructor() {
|
||||
this.baseUrl = API_ROOT
|
||||
this.pendingIdempotencyKeys = new Map()
|
||||
}
|
||||
|
||||
normalizeDomainKey(domain) {
|
||||
@@ -138,11 +154,22 @@ class ApiClient {
|
||||
finalBody = JSON.stringify(body)
|
||||
}
|
||||
|
||||
const isWrite = method !== 'GET' && method !== 'HEAD'
|
||||
const suppliedKey = headers?.['Idempotency-Key'] || headers?.['idempotency-key'] || ''
|
||||
const idempotencySignature = isWrite ? `${method}:${url}:${finalBody ?? ''}` : ''
|
||||
const idempotencyKey = isWrite && !suppliedKey
|
||||
? (this.pendingIdempotencyKeys.get(idempotencySignature) || createIdempotencyKey())
|
||||
: suppliedKey
|
||||
if (isWrite && !suppliedKey) {
|
||||
this.pendingIdempotencyKeys.set(idempotencySignature, idempotencyKey)
|
||||
}
|
||||
|
||||
const config = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': NONCE,
|
||||
...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
|
||||
...headers,
|
||||
},
|
||||
...rest,
|
||||
@@ -174,6 +201,9 @@ class ApiClient {
|
||||
throw new Error(msg)
|
||||
}
|
||||
|
||||
if (idempotencySignature !== '') {
|
||||
this.pendingIdempotencyKeys.delete(idempotencySignature)
|
||||
}
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error(`[API Error] ${endpoint}:`, error)
|
||||
|
||||
@@ -28,17 +28,17 @@ export const usersApiMethods = {
|
||||
return this.request('/identity/settings/test', { method: 'POST', body: JSON.stringify(data) })
|
||||
},
|
||||
|
||||
getCreditSettings() { return this.request('/credits/settings') },
|
||||
saveCreditSettings(data) { return this.request('/credits/settings', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
getCreditSettings() { return this.request('/assets/settings') },
|
||||
saveCreditSettings(data) { return this.request('/assets/settings', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
listCreditUsers(params = {}) {
|
||||
const qs = new URLSearchParams(params).toString()
|
||||
return this.request('/credits/users' + (qs ? `?${qs}` : ''))
|
||||
return this.request('/assets/accounts' + (qs ? `?${qs}` : ''))
|
||||
},
|
||||
adjustUserCredits(id, delta, reason) {
|
||||
return this.request(`/credits/users/${id}/adjust`, { method: 'POST', body: JSON.stringify({ delta, reason }) })
|
||||
adjustUserCredits(id, asset, delta, remark) {
|
||||
return this.request(`/assets/accounts/${id}/adjust`, { method: 'POST', body: JSON.stringify({ asset, delta, remark }) })
|
||||
},
|
||||
getCreditLog(params = {}) {
|
||||
const qs = new URLSearchParams(params).toString()
|
||||
return this.request('/credits/log' + (qs ? `?${qs}` : ''))
|
||||
return this.request('/assets/transactions' + (qs ? `?${qs}` : ''))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<h2 class=”aksrt-page-title”>积分配置</h2>
|
||||
<p class=”aksrt-page-desc”>
|
||||
配置积分规则与发放策略。可自定义货币名称(如”金币”、”魔石”等)。用户余额与手工调整已迁移到 WordPress 原生”用户”菜单中管理。
|
||||
<h2 class="aksrt-page-title">积分配置</h2>
|
||||
<p class="aksrt-page-desc">
|
||||
配置积分规则与发放策略。可自定义货币名称(如”金币”、”魔石”等)。用户余额与手工调整在 WordPress 原生”用户”菜单中管理。
|
||||
</p>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
@@ -23,6 +23,35 @@
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card title="统一资产账本" class="aksrt-section-card">
|
||||
<n-space vertical :size="12">
|
||||
<n-space wrap>
|
||||
<n-select v-model:value="ledgerFilters.asset" :options="assetOptions" clearable placeholder="资产" style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.transaction_id" placeholder="交易号" clearable style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.operation_key" placeholder="幂等键" clearable style="width: 240px" />
|
||||
<n-input v-model:value="ledgerFilters.ref_type" placeholder="引用类型" clearable style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.ref_key" placeholder="引用键" clearable style="width: 180px" />
|
||||
<n-input v-model:value="ledgerFilters.actor_user_id" placeholder="操作者 ID" clearable style="width: 130px" />
|
||||
<n-button type="primary" :loading="ledgerLoading" @click="searchLedger">查询</n-button>
|
||||
<n-button @click="resetLedger">重置</n-button>
|
||||
</n-space>
|
||||
<n-data-table
|
||||
:columns="ledgerColumns"
|
||||
:data="ledgerRows"
|
||||
:loading="ledgerLoading"
|
||||
:row-key="row => row.id"
|
||||
:scroll-x="1600"
|
||||
size="small"
|
||||
/>
|
||||
<n-pagination
|
||||
v-model:page="ledgerPage"
|
||||
:page-count="ledgerPageCount"
|
||||
:page-size="20"
|
||||
@update:page="loadLedger"
|
||||
/>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header"><h3>每日签到</h3></div>
|
||||
<n-form :model="cfg.daily_signin" label-placement="left" label-width="200" class="aksrt-form">
|
||||
@@ -97,7 +126,7 @@
|
||||
<n-form-item :label="`默认积分价参考(${cfg.currency_name || '积分'})`">
|
||||
<n-input-number v-model:value="cfg.download_default_cost" :min="0" :max="100000" />
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">兼容字段。实际价格以文章「付费内容设置」的积分价/现金价为准;下载解锁走收银台订单,不按次扣费。</span>
|
||||
<span class="aksrt-hint-text">实际价格以文章「付费内容设置」的积分价或现金价为准;下载解锁走收银台订单,不按次扣费。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
@@ -135,10 +164,88 @@ const cfg = reactive({
|
||||
})
|
||||
const streakJson = ref('{}')
|
||||
const saving = ref(false)
|
||||
const ledgerLoading = ref(false)
|
||||
const ledgerRows = ref([])
|
||||
const ledgerPage = ref(1)
|
||||
const ledgerPageCount = ref(1)
|
||||
const ledgerFilters = reactive({
|
||||
asset: null,
|
||||
transaction_id: '',
|
||||
operation_key: '',
|
||||
ref_type: '',
|
||||
ref_key: '',
|
||||
actor_user_id: '',
|
||||
})
|
||||
const assetOptions = [
|
||||
{ label: '积分', value: 'points' },
|
||||
{ label: '余额', value: 'balance' },
|
||||
]
|
||||
const ledgerColumns = [
|
||||
{ title: '交易号', key: 'transaction_label', width: 100, fixed: 'left' },
|
||||
{ title: '幂等键', key: 'operation_key', width: 260, ellipsis: { tooltip: true } },
|
||||
{ title: '资产', key: 'assets_label', width: 100 },
|
||||
{ title: '原因', key: 'reason', width: 150, ellipsis: { tooltip: true } },
|
||||
{ title: '业务引用', key: 'reference_label', width: 200, ellipsis: { tooltip: true } },
|
||||
{ title: '操作者', key: 'actor_label', width: 100 },
|
||||
{ title: '增减 / 手续费', key: 'delta_label', width: 180, ellipsis: { tooltip: true } },
|
||||
{ title: '变动后余额', key: 'balance_label', width: 150, ellipsis: { tooltip: true } },
|
||||
{ title: '备注', key: 'remark', width: 180, ellipsis: { tooltip: true } },
|
||||
{ title: 'UTC 时间', key: 'created_at', width: 190 },
|
||||
]
|
||||
|
||||
function formatAssetValue(asset, value) {
|
||||
if (asset === 'balance') return typeof value === 'string' && /^[+-]?\d+\.\d{2}$/.test(value) ? value : '0.00'
|
||||
const raw = String(value ?? '0')
|
||||
return /^-?\d+$/.test(raw) ? raw : '0'
|
||||
}
|
||||
|
||||
async function loadLedger() {
|
||||
ledgerLoading.value = true
|
||||
try {
|
||||
const params = { page: ledgerPage.value, per_page: 20 }
|
||||
Object.entries(ledgerFilters).forEach(([key, value]) => {
|
||||
if (value !== null && String(value).trim() !== '') params[key] = value
|
||||
})
|
||||
const response = await api.getCreditLog(params)
|
||||
const data = response?.data || response || {}
|
||||
ledgerRows.value = (data.items || []).map((row) => {
|
||||
const entries = Array.isArray(row.entries) ? row.entries : []
|
||||
return {
|
||||
...row,
|
||||
transaction_label: `#${row.id}`,
|
||||
assets_label: (row.assets || []).join(' / '),
|
||||
reference_label: [row.ref_type, row.ref_key].filter(Boolean).join(':') || '—',
|
||||
actor_label: row.actor_user_id ? `#${row.actor_user_id}` : '系统',
|
||||
delta_label: entries.map((entry) => {
|
||||
const value = formatAssetValue(entry.asset, entry.delta)
|
||||
const signed = entry.asset === 'points' && !value.startsWith('-') && value !== '0' ? `+${value}` : value
|
||||
return `${entry.asset}:${signed}`
|
||||
}).join(';'),
|
||||
balance_label: entries.map((entry) => `${entry.asset}:${formatAssetValue(entry.asset, entry.balance_after)}`).join(';'),
|
||||
}
|
||||
})
|
||||
ledgerPageCount.value = Math.max(1, Math.ceil(Number(data.total || 0) / Number(data.per_page || 20)))
|
||||
} catch (e) {
|
||||
message.error(e.message || '加载账本失败')
|
||||
} finally {
|
||||
ledgerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function searchLedger() {
|
||||
ledgerPage.value = 1
|
||||
loadLedger()
|
||||
}
|
||||
|
||||
function resetLedger() {
|
||||
Object.assign(ledgerFilters, { asset: null, transaction_id: '', operation_key: '', ref_type: '', ref_key: '', actor_user_id: '' })
|
||||
searchLedger()
|
||||
}
|
||||
|
||||
async function loadCfg() {
|
||||
try {
|
||||
const data = await api.getCreditSettings()
|
||||
const response = await api.getCreditSettings()
|
||||
const data = response?.data || response || {}
|
||||
Object.assign(cfg, data)
|
||||
streakJson.value = JSON.stringify(cfg.daily_signin?.streak || {}, null, 0)
|
||||
} catch (e) {
|
||||
@@ -163,5 +270,6 @@ async function saveCfg() {
|
||||
|
||||
onMounted(() => {
|
||||
loadCfg()
|
||||
loadLedger()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -22,10 +22,16 @@
|
||||
<n-switch v-model:value="settings.enabled_types.download_ready" />
|
||||
</n-form-item>
|
||||
<n-form-item label="积分增加通知">
|
||||
<n-switch v-model:value="settings.enabled_types.credits_added" />
|
||||
<n-switch v-model:value="settings.enabled_types.asset_points_increase" />
|
||||
</n-form-item>
|
||||
<n-form-item label="积分扣减通知">
|
||||
<n-switch v-model:value="settings.enabled_types.credits_deducted" />
|
||||
<n-switch v-model:value="settings.enabled_types.asset_points_decrease" />
|
||||
</n-form-item>
|
||||
<n-form-item label="余额增加通知">
|
||||
<n-switch v-model:value="settings.enabled_types.asset_balance_increase" />
|
||||
</n-form-item>
|
||||
<n-form-item label="余额扣减通知">
|
||||
<n-switch v-model:value="settings.enabled_types.asset_balance_decrease" />
|
||||
</n-form-item>
|
||||
<n-form-item label="邮箱待验证提醒">
|
||||
<n-switch v-model:value="settings.enabled_types.verify_email" />
|
||||
@@ -90,8 +96,10 @@ const settings = reactive({
|
||||
comment_reply: true,
|
||||
comment_approved: true,
|
||||
download_ready: true,
|
||||
credits_added: true,
|
||||
credits_deducted: true,
|
||||
asset_points_increase: true,
|
||||
asset_points_decrease: true,
|
||||
asset_balance_increase: true,
|
||||
asset_balance_decrease: true,
|
||||
verify_email: true,
|
||||
pending_comments: true,
|
||||
upload_moderation: true,
|
||||
|
||||
@@ -55,10 +55,10 @@
|
||||
<n-select v-model:value="createForm.type" :options="typeOptions" />
|
||||
</n-form-item>
|
||||
<n-form-item label="卡密">
|
||||
<n-input v-model:value="createForm.code" placeholder="手动填写唯一卡密" />
|
||||
<n-input v-model:value="createForm.password" placeholder="手动填写唯一卡密" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="createForm.type === 'balance'" label="面额">
|
||||
<n-input-number v-model:value="createForm.amount" :min="0.01" :precision="2" style="width: 100%;" />
|
||||
<n-input v-model:value="createForm.amount" inputmode="decimal" placeholder="例如 10.00" />
|
||||
</n-form-item>
|
||||
<n-grid v-if="createForm.type === 'vip'" cols="2" :x-gap="12">
|
||||
<n-grid-item>
|
||||
@@ -101,7 +101,7 @@
|
||||
</n-grid-item>
|
||||
<n-grid-item v-if="batchForm.type === 'balance'">
|
||||
<n-form-item label="面额">
|
||||
<n-input-number v-model:value="batchForm.amount" :min="0.01" :precision="2" style="width: 100%;" />
|
||||
<n-input v-model:value="batchForm.amount" inputmode="decimal" placeholder="例如 10.00" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item v-if="batchForm.type === 'credits'">
|
||||
@@ -185,8 +185,8 @@ const filters = reactive({
|
||||
|
||||
const createForm = reactive({
|
||||
type: 'balance',
|
||||
code: '',
|
||||
amount: 10,
|
||||
password: '',
|
||||
amount: '10.00',
|
||||
level: 1,
|
||||
days: 30,
|
||||
points: 100,
|
||||
@@ -197,7 +197,7 @@ const createForm = reactive({
|
||||
const batchForm = reactive({
|
||||
type: 'balance',
|
||||
count: 20,
|
||||
amount: 10,
|
||||
amount: '10.00',
|
||||
level: 1,
|
||||
days: 30,
|
||||
points: 100,
|
||||
@@ -265,7 +265,7 @@ const pagination = computed(() => ({
|
||||
|
||||
const columns = [
|
||||
{ title: 'ID', key: 'id', width: 70 },
|
||||
{ title: '卡密', key: 'code', minWidth: 170, ellipsis: { tooltip: true } },
|
||||
{ title: '卡密', key: 'password', minWidth: 170, ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: '类型',
|
||||
key: 'type_label',
|
||||
@@ -408,9 +408,8 @@ async function handleCreate() {
|
||||
method: 'POST',
|
||||
data: {
|
||||
type: createForm.type,
|
||||
code: createForm.code,
|
||||
password: createForm.code,
|
||||
amount: createForm.amount,
|
||||
password: createForm.password,
|
||||
amount: String(createForm.amount ?? ''),
|
||||
level: createForm.level,
|
||||
days: createForm.days,
|
||||
points: createForm.points,
|
||||
@@ -420,7 +419,7 @@ async function handleCreate() {
|
||||
})
|
||||
message.success('充值卡已创建')
|
||||
createVisible.value = false
|
||||
createForm.code = ''
|
||||
createForm.password = ''
|
||||
createForm.note = ''
|
||||
await loadData()
|
||||
} catch (error) {
|
||||
@@ -438,7 +437,7 @@ async function handleBatchCreate() {
|
||||
data: {
|
||||
type: batchForm.type,
|
||||
count: batchForm.count,
|
||||
amount: batchForm.amount,
|
||||
amount: String(batchForm.amount ?? ''),
|
||||
level: batchForm.level,
|
||||
days: batchForm.days,
|
||||
points: batchForm.points,
|
||||
@@ -461,7 +460,7 @@ async function handleBatchCreate() {
|
||||
function handleDisable(row) {
|
||||
dialog.warning({
|
||||
title: '确认禁用',
|
||||
content: `确定要禁用充值卡 ${row.code} 吗?`,
|
||||
content: `确定要禁用充值卡 ${row.password} 吗?`,
|
||||
positiveText: '禁用',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
@@ -488,7 +487,7 @@ async function handleExport() {
|
||||
const header = ['ID', '卡号', '面额', '余额', '状态', '使用人', '创建时间', '过期时间', '备注']
|
||||
const lines = items.map((row) => [
|
||||
row.id,
|
||||
row.code,
|
||||
row.password,
|
||||
row.amount,
|
||||
row.balance,
|
||||
row.status_label,
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader title="返佣管理" desc="按状态、类型和时间筛选返佣记录,自定义列展示并在详情中查看处理历史。" />
|
||||
|
||||
<div class="aksrt-rebate-page__stats">
|
||||
<n-card v-for="card in statCards" :key="card.key" size="small">
|
||||
<n-statistic :label="card.label" :value="stats[card.key] || 0" />
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<n-card size="small" title="筛选与列控制">
|
||||
<n-space vertical :size="12">
|
||||
<n-grid cols="1 s:2 m:5" :x-gap="12" :y-gap="12" responsive="screen">
|
||||
<n-grid-item>
|
||||
<n-input v-model:value="filters.keyword" clearable placeholder="搜索订单号、用户、备注" />
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-select v-model:value="filters.status" :options="statusOptions" placeholder="状态" />
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-select v-model:value="filters.type" :options="typeOptions" placeholder="类型" />
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-date-picker v-model:value="filters.dateRange" type="daterange" clearable style="width: 100%;" />
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="handleSearch">筛选</n-button>
|
||||
<n-button @click="resetFilters">重置</n-button>
|
||||
</n-space>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
|
||||
<n-checkbox-group v-model:value="visibleColumnKeys">
|
||||
<n-space wrap>
|
||||
<n-checkbox v-for="column in columnChoices" :key="column.value" :value="column.value">
|
||||
{{ column.label }}
|
||||
</n-checkbox>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-rebate-page__card" size="small" title="返佣台账">
|
||||
<n-data-table
|
||||
:columns="tableColumns"
|
||||
:data="rows"
|
||||
:loading="loading"
|
||||
:bordered="false"
|
||||
:pagination="pagination"
|
||||
:row-key="(row) => row.id"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<RebateDetailDrawer
|
||||
v-model:show="drawerVisible"
|
||||
:rebate-id="currentRebateId"
|
||||
@updated="loadData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue'
|
||||
import { NButton, NSpace, NTag, useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import RebateDetailDrawer from './components/RebateDetailDrawer.vue'
|
||||
|
||||
const message = useMessage()
|
||||
const loading = ref(false)
|
||||
const rows = ref([])
|
||||
const drawerVisible = ref(false)
|
||||
const currentRebateId = ref(null)
|
||||
|
||||
const stats = reactive({
|
||||
total: 0,
|
||||
pending: 0,
|
||||
settled: 0,
|
||||
total_amount: 0,
|
||||
settled_amount: 0,
|
||||
})
|
||||
|
||||
const filters = reactive({
|
||||
keyword: '',
|
||||
status: '',
|
||||
type: '',
|
||||
dateRange: null,
|
||||
})
|
||||
|
||||
const pager = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
itemCount: 0,
|
||||
})
|
||||
|
||||
const optionsState = reactive({
|
||||
statuses: [],
|
||||
types: [],
|
||||
})
|
||||
|
||||
const statCards = [
|
||||
{ key: 'total', label: '总记录' },
|
||||
{ key: 'pending', label: '待结算' },
|
||||
{ key: 'settled', label: '已结算' },
|
||||
{ key: 'total_amount', label: '总返佣' },
|
||||
{ key: 'settled_amount', label: '已结算金额' },
|
||||
]
|
||||
|
||||
const statusOptions = computed(() => optionsState.statuses.length
|
||||
? optionsState.statuses
|
||||
: [
|
||||
{ label: '全部状态', value: '' },
|
||||
{ label: '待结算', value: 'pending' },
|
||||
{ label: '已结算', value: 'settled' },
|
||||
{ label: '已取消', value: 'cancelled' },
|
||||
])
|
||||
|
||||
const typeOptions = computed(() => optionsState.types.length
|
||||
? optionsState.types
|
||||
: [
|
||||
{ label: '全部类型', value: '' },
|
||||
{ label: '订单返佣', value: 'order' },
|
||||
])
|
||||
|
||||
const columnChoices = [
|
||||
{ label: '来源用户', value: 'source_user' },
|
||||
{ label: '创建时间', value: 'created_at' },
|
||||
{ label: '结算时间', value: 'settled_at' },
|
||||
{ label: '备注', value: 'note' },
|
||||
{ label: '处理记录数', value: 'history_count' },
|
||||
]
|
||||
|
||||
const visibleColumnKeys = ref(['source_user', 'created_at', 'settled_at', 'history_count'])
|
||||
|
||||
const baseColumns = computed(() => {
|
||||
const columns = [
|
||||
{ title: 'ID', key: 'id', width: 70 },
|
||||
{ title: '订单号', key: 'order_number', minWidth: 180, ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: '返佣对象',
|
||||
key: 'user',
|
||||
width: 160,
|
||||
render: (row) => row.user?.display_name || '-',
|
||||
},
|
||||
{
|
||||
title: '返佣金额',
|
||||
key: 'amount',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
render: (row) => {
|
||||
const typeMap = { pending: 'warning', settled: 'success', cancelled: 'default' }
|
||||
return h(NTag, { size: 'small', round: true, type: typeMap[row.status] || 'default' }, () => row.status_label)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
key: 'type',
|
||||
width: 100,
|
||||
render: (row) => row.type_label || row.type,
|
||||
},
|
||||
]
|
||||
|
||||
if (visibleColumnKeys.value.includes('source_user')) {
|
||||
columns.push({
|
||||
title: '来源用户',
|
||||
key: 'source_user',
|
||||
width: 160,
|
||||
render: (row) => row.source_user?.display_name || '-',
|
||||
})
|
||||
}
|
||||
|
||||
if (visibleColumnKeys.value.includes('created_at')) {
|
||||
columns.push({ title: '创建时间', key: 'created_at', width: 170 })
|
||||
}
|
||||
|
||||
if (visibleColumnKeys.value.includes('settled_at')) {
|
||||
columns.push({
|
||||
title: '结算时间',
|
||||
key: 'settled_at',
|
||||
width: 170,
|
||||
render: (row) => row.settled_at || '-',
|
||||
})
|
||||
}
|
||||
|
||||
if (visibleColumnKeys.value.includes('note')) {
|
||||
columns.push({
|
||||
title: '备注',
|
||||
key: 'note',
|
||||
minWidth: 160,
|
||||
ellipsis: { tooltip: true },
|
||||
render: (row) => row.note || '-',
|
||||
})
|
||||
}
|
||||
|
||||
if (visibleColumnKeys.value.includes('history_count')) {
|
||||
columns.push({ title: '处理记录数', key: 'history_count', width: 100 })
|
||||
}
|
||||
|
||||
columns.push({
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 120,
|
||||
render: (row) => h(NSpace, { size: 'small' }, () => [
|
||||
h(NButton, {
|
||||
size: 'tiny',
|
||||
type: 'primary',
|
||||
quaternary: true,
|
||||
onClick: () => openDrawer(row.id),
|
||||
}, () => '详情'),
|
||||
]),
|
||||
})
|
||||
|
||||
return columns
|
||||
})
|
||||
|
||||
const tableColumns = computed(() => baseColumns.value)
|
||||
|
||||
const pagination = computed(() => ({
|
||||
page: pager.page,
|
||||
pageSize: pager.pageSize,
|
||||
itemCount: pager.itemCount,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
onUpdatePage: (page) => {
|
||||
pager.page = page
|
||||
loadData()
|
||||
},
|
||||
onUpdatePageSize: (pageSize) => {
|
||||
pager.pageSize = pageSize
|
||||
pager.page = 1
|
||||
loadData()
|
||||
},
|
||||
}))
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
function formatDateValue(value) {
|
||||
const date = new Date(value)
|
||||
const year = date.getFullYear()
|
||||
const month = `${date.getMonth() + 1}`.padStart(2, '0')
|
||||
const day = `${date.getDate()}`.padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
function buildParams() {
|
||||
const params = {
|
||||
page: pager.page,
|
||||
per_page: pager.pageSize,
|
||||
}
|
||||
if (filters.keyword) params.keyword = filters.keyword
|
||||
if (filters.status) params.status = filters.status
|
||||
if (filters.type) params.type = filters.type
|
||||
if (Array.isArray(filters.dateRange) && filters.dateRange.length === 2) {
|
||||
params.date_from = formatDateValue(filters.dateRange[0])
|
||||
params.date_to = formatDateValue(filters.dateRange[1])
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await api.request('/payment/rebate/log', { params: buildParams() })
|
||||
const data = res?.data || {}
|
||||
rows.value = data.items || []
|
||||
pager.itemCount = data.total || 0
|
||||
Object.assign(stats, data.stats || {})
|
||||
optionsState.statuses = data.options?.statuses || []
|
||||
optionsState.types = data.options?.types || []
|
||||
} catch (error) {
|
||||
message.error(error?.message || '加载返佣台账失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pager.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
filters.keyword = ''
|
||||
filters.status = ''
|
||||
filters.type = ''
|
||||
filters.dateRange = null
|
||||
pager.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function openDrawer(id) {
|
||||
currentRebateId.value = id
|
||||
drawerVisible.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-rebate-page__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.aksrt-rebate-page__card {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
<n-empty v-else size="small" description="暂无数据" />
|
||||
<div class="aksrt-revenue__chart-summary">
|
||||
<span>期间总计: <strong>{{ formatAmount(group.total, group.key) }}</strong></span>
|
||||
<span>期间总计: <strong>{{ formatAmount(group.total, group.key) }}</strong></span>
|
||||
<span>共 {{ group.count }} 笔</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,7 +91,7 @@
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<div class="aksrt-revenue__stat">
|
||||
<strong>{{ withdrawalStats.total_paid.toFixed(2) }}</strong>
|
||||
<strong>{{ formatAmount(withdrawalStats.total_paid, 'cash') }}</strong>
|
||||
<p>已提现 (元)</p>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
@@ -106,16 +106,21 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import PaymentSectionPage from '@/pages/payment/PaymentSectionPage.vue'
|
||||
import api from '@/api'
|
||||
|
||||
const emptyCurrencyStats = () => ({ total: 0, pending: 0, settled: 0, count: 0 })
|
||||
const revenueStats = ref({ cash: emptyCurrencyStats(), points: emptyCurrencyStats() })
|
||||
const withdrawalStats = ref({ total: 0, pending: 0, processing: 0, completed: 0, rejected: 0, total_paid: 0 })
|
||||
const emptyCurrencyStats = (currency) => ({
|
||||
total: currency === 'cash' ? '0.00' : 0,
|
||||
pending: currency === 'cash' ? '0.00' : 0,
|
||||
settled: currency === 'cash' ? '0.00' : 0,
|
||||
count: 0,
|
||||
})
|
||||
const revenueStats = ref({ cash: emptyCurrencyStats('cash'), points: emptyCurrencyStats('points') })
|
||||
const withdrawalStats = ref({ total: 0, pending: 0, processing: 0, completed: 0, rejected: 0, total_paid: '0.00' })
|
||||
const timeSeries = ref({ cash: [], points: [] })
|
||||
const granularity = ref('day')
|
||||
|
||||
const revenueSummary = computed(() => [
|
||||
{ key: 'cash-total', value: `${revenueStats.value.cash.total.toFixed(2)} 元`, label: '现金总收益' },
|
||||
{ key: 'cash-pending', value: `${revenueStats.value.cash.pending.toFixed(2)} 元`, label: '现金待结算' },
|
||||
{ key: 'cash-settled', value: `${revenueStats.value.cash.settled.toFixed(2)} 元`, label: '现金已结算' },
|
||||
{ key: 'cash-total', value: `${formatAmount(revenueStats.value.cash.total, 'cash')} 元`, label: '现金总收益' },
|
||||
{ key: 'cash-pending', value: `${formatAmount(revenueStats.value.cash.pending, 'cash')} 元`, label: '现金待结算' },
|
||||
{ key: 'cash-settled', value: `${formatAmount(revenueStats.value.cash.settled, 'cash')} 元`, label: '现金已结算' },
|
||||
{ key: 'cash-count', value: revenueStats.value.cash.count, label: '现金收益笔数' },
|
||||
{ key: 'points-total', value: `${formatAmount(revenueStats.value.points.total, 'points')}`, label: '积分总收益' },
|
||||
{ key: 'points-pending', value: `${formatAmount(revenueStats.value.points.pending, 'points')}`, label: '积分待结算' },
|
||||
@@ -134,21 +139,44 @@ const timeSeriesGroups = computed(() => [
|
||||
label: '积分收益',
|
||||
items: Array.isArray(timeSeries.value.points) ? timeSeries.value.points : [],
|
||||
},
|
||||
].map((group) => ({
|
||||
...group,
|
||||
total: group.items.reduce((sum, item) => sum + Number(item.amount || 0), 0),
|
||||
count: group.items.reduce((sum, item) => sum + Number(item.count || 0), 0),
|
||||
})))
|
||||
].map((group) => {
|
||||
const totalMinor = group.items.reduce((sum, item) => sum + amountMinor(item.amount, group.key), 0n)
|
||||
return {
|
||||
...group,
|
||||
total: formatMinor(totalMinor, group.key),
|
||||
count: group.items.reduce((sum, item) => sum + Number(item.count || 0), 0),
|
||||
}
|
||||
}))
|
||||
|
||||
const hasTimeSeries = computed(() => timeSeriesGroups.value.some((group) => group.items.length > 0))
|
||||
|
||||
function formatAmount(amount, currency) {
|
||||
return currency === 'points' ? `${Number(amount || 0).toFixed(0)} 积分` : `¥${Number(amount || 0).toFixed(2)}`
|
||||
return currency === 'points'
|
||||
? `${formatMinor(amountMinor(amount, currency), currency)} 积分`
|
||||
: `¥${formatMinor(amountMinor(amount, currency), currency)}`
|
||||
}
|
||||
|
||||
function amountMinor(amount, currency) {
|
||||
const raw = String(amount ?? '').trim()
|
||||
if (currency === 'points') return /^\d+$/.test(raw) ? BigInt(raw) : 0n
|
||||
const match = /^(0|[1-9]\d*)(?:\.(\d{1,2}))?$/.exec(raw)
|
||||
return match ? (BigInt(match[1]) * 100n) + BigInt((match[2] || '').padEnd(2, '0') || '0') : 0n
|
||||
}
|
||||
|
||||
function formatMinor(minor, currency) {
|
||||
if (currency === 'points') return String(minor)
|
||||
return `${minor / 100n}.${String(minor % 100n).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function getBarHeight(amount, items) {
|
||||
const maxAmount = Math.max(1, ...items.map((item) => Number(item.amount || 0)))
|
||||
return Math.max(2, (Number(amount || 0) / maxAmount) * 100)
|
||||
const currency = items === timeSeries.value.points ? 'points' : 'cash'
|
||||
const maxAmount = items.reduce((max, item) => {
|
||||
const current = amountMinor(item.amount, currency)
|
||||
return current > max ? current : max
|
||||
}, 0n)
|
||||
if (maxAmount <= 0n) return 2
|
||||
const scaled = Number((amountMinor(amount, currency) * 10000n) / maxAmount) / 100
|
||||
return Math.max(2, scaled)
|
||||
}
|
||||
|
||||
function formatLabel(date) {
|
||||
@@ -170,8 +198,8 @@ async function loadRevenue() {
|
||||
if (res?.data) {
|
||||
if (res.data.revenue) {
|
||||
revenueStats.value = {
|
||||
cash: { ...emptyCurrencyStats(), ...(res.data.revenue.cash || {}) },
|
||||
points: { ...emptyCurrencyStats(), ...(res.data.revenue.points || {}) },
|
||||
cash: { ...emptyCurrencyStats('cash'), ...(res.data.revenue.cash || {}) },
|
||||
points: { ...emptyCurrencyStats('points'), ...(res.data.revenue.points || {}) },
|
||||
}
|
||||
}
|
||||
if (res.data.withdrawal) {
|
||||
@@ -181,7 +209,7 @@ async function loadRevenue() {
|
||||
processing: Number(res.data.withdrawal.processing || 0),
|
||||
completed: Number(res.data.withdrawal.completed || 0),
|
||||
rejected: Number(res.data.withdrawal.rejected || 0),
|
||||
total_paid: Number(res.data.withdrawal.total_paid || 0),
|
||||
total_paid: String(res.data.withdrawal.total_paid || '0.00'),
|
||||
}
|
||||
}
|
||||
timeSeries.value = {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<n-spin :show="loading">
|
||||
<template v-if="detail.card">
|
||||
<n-descriptions bordered :column="1" size="small" label-placement="left">
|
||||
<n-descriptions-item label="卡号">{{ detail.card.code || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="卡密">{{ detail.card.password || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="面额">{{ detail.card.amount || 0 }}</n-descriptions-item>
|
||||
<n-descriptions-item label="余额">{{ detail.card.balance || 0 }}</n-descriptions-item>
|
||||
<n-descriptions-item label="状态">{{ detail.card.status_label || detail.card.status }}</n-descriptions-item>
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
<template>
|
||||
<n-drawer :show="show" :width="720" @update:show="$emit('update:show', $event)">
|
||||
<n-drawer-content title="返佣详情" closable>
|
||||
<n-spin :show="loading">
|
||||
<template v-if="detail.record">
|
||||
<n-descriptions bordered :column="1" size="small" label-placement="left">
|
||||
<n-descriptions-item label="订单号">{{ detail.record.order_number || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="返佣对象">{{ detail.record.user?.display_name || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="来源用户">{{ detail.record.source_user?.display_name || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="返佣金额">{{ detail.record.amount || 0 }}</n-descriptions-item>
|
||||
<n-descriptions-item label="类型">{{ detail.record.type_label || detail.record.type }}</n-descriptions-item>
|
||||
<n-descriptions-item label="状态">{{ detail.record.status_label || detail.record.status }}</n-descriptions-item>
|
||||
<n-descriptions-item label="创建时间">{{ detail.record.created_at || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="结算时间">{{ detail.record.settled_at || '-' }}</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
|
||||
<div class="aksrt-rebate-drawer__section">
|
||||
<div class="aksrt-rebate-drawer__title">备注</div>
|
||||
<n-alert type="info" :show-icon="false">
|
||||
{{ detail.record.note || '暂无备注' }}
|
||||
</n-alert>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-rebate-drawer__section">
|
||||
<div class="aksrt-rebate-drawer__title">处理记录</div>
|
||||
<div v-if="detail.history?.length" class="aksrt-rebate-drawer__timeline">
|
||||
<div v-for="(entry, index) in detail.history" :key="index" class="aksrt-rebate-drawer__timeline-item">
|
||||
<div class="aksrt-rebate-drawer__timeline-head">
|
||||
<strong>{{ entry.event || '-' }}</strong>
|
||||
<span>{{ entry.time || '-' }}</span>
|
||||
</div>
|
||||
<p>{{ entry.note || '无备注' }}</p>
|
||||
<small>{{ entry.operator_name || '系统' }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<n-empty v-else description="暂无处理记录" />
|
||||
</div>
|
||||
|
||||
<div v-if="detail.record.status === 'pending'" class="aksrt-rebate-drawer__section">
|
||||
<div class="aksrt-rebate-drawer__title">处理返佣</div>
|
||||
<n-space vertical :size="12">
|
||||
<n-input
|
||||
v-model:value="form.note"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="记录结算说明或取消原因"
|
||||
/>
|
||||
<n-space>
|
||||
<n-button type="primary" :loading="submitting === 'settle'" @click="handleAction('settle')">
|
||||
结算返佣
|
||||
</n-button>
|
||||
<n-button type="warning" secondary :loading="submitting === 'cancel'" @click="handleAction('cancel')">
|
||||
取消返佣
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<n-empty v-else description="暂无返佣详情" />
|
||||
</n-spin>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
rebateId: { type: [Number, String], default: null },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:show', 'updated'])
|
||||
const message = useMessage()
|
||||
const loading = ref(false)
|
||||
const submitting = ref('')
|
||||
const detail = ref({ record: null, history: [] })
|
||||
const form = reactive({ note: '' })
|
||||
|
||||
watch(
|
||||
() => [props.show, props.rebateId],
|
||||
async ([visible, id]) => {
|
||||
if (visible && id) {
|
||||
await loadDetail()
|
||||
}
|
||||
if (!visible) {
|
||||
detail.value = { record: null, history: [] }
|
||||
form.note = ''
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
async function loadDetail() {
|
||||
if (!props.rebateId) {
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await api.request(`/payment/rebate/${props.rebateId}`)
|
||||
detail.value = res?.data || { record: null, history: [] }
|
||||
form.note = ''
|
||||
} catch (error) {
|
||||
message.error(error?.message || '加载返佣详情失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAction(action) {
|
||||
if (!props.rebateId) {
|
||||
return
|
||||
}
|
||||
|
||||
submitting.value = action
|
||||
try {
|
||||
await api.request(`/payment/rebate/${props.rebateId}/${action}`, {
|
||||
method: 'POST',
|
||||
data: { note: form.note },
|
||||
})
|
||||
message.success(action === 'settle' ? '返佣已结算' : '返佣已取消')
|
||||
await loadDetail()
|
||||
emit('updated')
|
||||
} catch (error) {
|
||||
message.error(error?.message || '处理失败')
|
||||
} finally {
|
||||
submitting.value = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-rebate-drawer__section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.aksrt-rebate-drawer__title {
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.aksrt-rebate-drawer__timeline {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-rebate-drawer__timeline-item {
|
||||
padding: 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.aksrt-rebate-drawer__timeline-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.aksrt-rebate-drawer__timeline-item p {
|
||||
margin: 0 0 6px;
|
||||
color: #111827;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.aksrt-rebate-drawer__timeline-item small {
|
||||
color: #6b7280;
|
||||
}
|
||||
</style>
|
||||
@@ -163,8 +163,10 @@ function getTypeTag(type) {
|
||||
system: 'info',
|
||||
comment_reply: 'success',
|
||||
comment_approved: 'success',
|
||||
credits_added: 'warning',
|
||||
credits_deducted: 'warning',
|
||||
asset_points_increase: 'warning',
|
||||
asset_points_decrease: 'warning',
|
||||
asset_balance_increase: 'success',
|
||||
asset_balance_decrease: 'error',
|
||||
download_ready: 'primary',
|
||||
verify_email: 'info',
|
||||
pending_comments: 'warning',
|
||||
@@ -178,8 +180,10 @@ function getTypeLabel(type) {
|
||||
system: '系统通知',
|
||||
comment_reply: '评论回复',
|
||||
comment_approved: '评论审核',
|
||||
credits_added: '积分增加',
|
||||
credits_deducted: '积分扣减',
|
||||
asset_points_increase: '积分增加',
|
||||
asset_points_decrease: '积分扣减',
|
||||
asset_balance_increase: '余额增加',
|
||||
asset_balance_decrease: '余额扣减',
|
||||
download_ready: '下载就绪',
|
||||
verify_email: '邮箱验证',
|
||||
pending_comments: '待审评论',
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
<n-form-item label="云失败转人工"><n-switch v-model:value="verifyForm.user_verify_allow_manual_fallback" /></n-form-item>
|
||||
<n-form-item label="认证会话有效期(分钟)"><n-input-number v-model:value="verifyForm.user_verify_session_ttl_minutes" :min="5" :max="1440" /></n-form-item>
|
||||
<n-form-item label="结果轮询间隔(秒)"><n-input-number v-model:value="verifyForm.user_verify_poll_interval_seconds" :min="3" :max="60" /></n-form-item>
|
||||
<n-form-item label="个人认证费用"><n-input-number v-model:value="verifyForm.user_verify_personal_fee" :min="0" :step="0.01" /></n-form-item>
|
||||
<n-form-item label="企业认证费用"><n-input-number v-model:value="verifyForm.user_verify_enterprise_fee" :min="0" :step="0.01" /></n-form-item>
|
||||
<n-form-item label="个人认证费用"><n-input v-model:value="verifyForm.user_verify_personal_fee" placeholder="0.00" /></n-form-item>
|
||||
<n-form-item label="企业认证费用"><n-input v-model:value="verifyForm.user_verify_enterprise_fee" placeholder="0.00" /></n-form-item>
|
||||
<n-form-item label="个人认证徽标文案">
|
||||
<n-input v-model:value="verifyForm.user_verify_personal_badge_text" placeholder="个人认证" />
|
||||
</n-form-item>
|
||||
@@ -174,8 +174,8 @@ const verifyDefaults = {
|
||||
user_verify_allow_manual_fallback: true,
|
||||
user_verify_session_ttl_minutes: 30,
|
||||
user_verify_poll_interval_seconds: 5,
|
||||
user_verify_personal_fee: 0,
|
||||
user_verify_enterprise_fee: 0,
|
||||
user_verify_personal_fee: '0.00',
|
||||
user_verify_enterprise_fee: '0.00',
|
||||
user_verify_personal_badge_text: '个人认证',
|
||||
user_verify_enterprise_badge_text: '企业认证',
|
||||
user_verify_disable_desc: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
|
||||
@@ -78,7 +78,6 @@ function createSectionRedirectRoutes() {
|
||||
const NATIVE_OPS_REDIRECTS = [
|
||||
{ path: '/payment/charge-card', title: '充值卡', nativeAdminPath: 'admin.php?page=aksrt_charge_cards' },
|
||||
{ path: '/payment/income', title: '创作分成运营', nativeAdminPath: 'admin.php?page=aksrt_income' },
|
||||
{ path: '/payment/rebate', title: '返佣管理', nativeAdminPath: 'admin.php?page=aksrt_rebate' },
|
||||
{ path: '/users/messages', title: '消息中心', nativeAdminPath: 'users.php?page=aksrt_messages' },
|
||||
{ path: '/users/referral-codes', title: '推广码运营', nativeAdminPath: 'users.php?page=aksrt_referral_codes' },
|
||||
{ path: '/users/ban', title: '禁封管理', nativeAdminPath: 'users.php?page=aksrt_user_ban&tab=restrictions' },
|
||||
|
||||
@@ -12,7 +12,7 @@ export const cardPassSchema = {
|
||||
card_pass_batch_size: 50,
|
||||
card_pass_max_redeem: 1,
|
||||
card_pass_expiry: 0,
|
||||
card_pass_amount: 10,
|
||||
card_pass_amount: '10.00',
|
||||
card_pass_vip_days: 30,
|
||||
card_pass_credits: 0,
|
||||
card_pass_redeem_page: '',
|
||||
@@ -40,7 +40,7 @@ export const cardPassSchema = {
|
||||
fields: [
|
||||
{ key: 'card_pass_max_redeem', label: '单卡最大兑换次数', type: 'number', default: 1, min: 1, max: 100, dependsOn: { key: 'card_pass_enabled', value: true } },
|
||||
{ key: 'card_pass_expiry', label: '有效期(天)', type: 'number', default: 0, min: 0, help: '0为永久有效', dependsOn: { key: 'card_pass_enabled', value: true } },
|
||||
{ key: 'card_pass_amount', label: '面额(元)', type: 'number', default: 10, min: 1, max: 9999, dependsOn: { key: 'card_pass_enabled', value: true } },
|
||||
{ key: 'card_pass_amount', label: '面额(元)', type: 'input', default: '10.00', min: 1, max: 9999, dependsOn: { key: 'card_pass_enabled', value: true } },
|
||||
{ key: 'card_pass_vip_days', label: 'VIP天数', type: 'number', default: 30, min: 1, max: 3650, dependsOn: { key: 'card_pass_enabled', value: true } },
|
||||
{ key: 'card_pass_credits', label: '赠送积分', type: 'number', default: 0, min: 0, max: 99999, dependsOn: { key: 'card_pass_enabled', value: true } },
|
||||
],
|
||||
|
||||
@@ -6,7 +6,6 @@ export const rechargeSchema = {
|
||||
defaults: {
|
||||
pay_balance_s: true,
|
||||
pay_balance_desc: '',
|
||||
pay_auto_to_balance: true,
|
||||
pay_balance_product: '30|30|入门包\n98|108|推荐包',
|
||||
pay_balance_product_name: '',
|
||||
pay_balance_price: 0,
|
||||
@@ -34,7 +33,6 @@ export const rechargeSchema = {
|
||||
fields: [
|
||||
{ key: 'pay_balance_s', label: '启用余额功能', type: 'switch', default: true, help: '开启后用户中心显示现金钱包,并默认允许余额支付(可在收款接口单独关闭)。充值需另配支付宝/微信等外部网关。' },
|
||||
{ key: 'pay_balance_desc', label: '充值说明', type: 'textarea', default: '', dependsOn: { key: 'pay_balance_s', value: true } },
|
||||
{ key: 'pay_auto_to_balance', label: '自动到账到余额', type: 'switch', default: true, dependsOn: { key: 'pay_balance_s', value: true }, help: '充值订单支付成功后自动入账钱包(卡密/管理员调整不受此开关限制)。' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ export const vipSchema = {
|
||||
vip_renew_desc: '',
|
||||
vip_renew_price_type: 'discount',
|
||||
vip_renew_discount: 8,
|
||||
vip_renew_reduce: 20,
|
||||
vip_renew_reduce: '20.00',
|
||||
vip_upgrade: true,
|
||||
vip_upgrade_desc: '',
|
||||
vip_upgrade_product: 'unit|1|按剩余天数升级\njump|199|升级永久二级会员\npermanent|299|永久一级升永久二级',
|
||||
@@ -126,7 +126,7 @@ export const vipSchema = {
|
||||
{ key: 'vip_renew_desc', label: '续费说明', type: 'textarea', default: '', dependsOn: { key: 'vip_renew', value: true } },
|
||||
{ key: 'vip_renew_price_type', label: '续费价格类型', type: 'radio', default: 'discount', options: [{ label: '相同价格', value: 'same' }, { label: '折扣价格', value: 'discount' }, { label: '优惠立减', value: 'reduce' }], dependsOn: { key: 'vip_renew', value: true } },
|
||||
{ key: 'vip_renew_discount', label: '续费折扣(折,如 8=八折)', type: 'number', default: 8, min: 1, max: 100, dependsOn: { key: 'vip_renew_price_type', value: 'discount' } },
|
||||
{ key: 'vip_renew_reduce', label: '续费减免金额', type: 'number', default: 20, min: 0, max: 9999, dependsOn: { key: 'vip_renew_price_type', value: 'reduce' } },
|
||||
{ key: 'vip_renew_reduce', label: '续费减免金额', type: 'input', sanitize_type: 'money_decimal', default: '20.00', min: 0, max: 9999, dependsOn: { key: 'vip_renew_price_type', value: 'reduce' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,10 +4,10 @@ export const withdrawalSchema = {
|
||||
icon: 'fa fas fa-money-bill',
|
||||
description: '配置提现门槛、手续费比例与每日限制。',
|
||||
defaults: {
|
||||
affiliate_min_withdrawal: 50,
|
||||
affiliate_min_withdrawal: '50.00',
|
||||
affiliate_withdrawal_fee: 5,
|
||||
affiliate_daily_withdrawal_count: 3,
|
||||
affiliate_daily_withdrawal_limit: 500,
|
||||
affiliate_daily_withdrawal_limit: '500.00',
|
||||
affiliate_withdrawal_reset_time: '00:00',
|
||||
affiliate_payout_channels: ['alipay', 'wechat'],
|
||||
},
|
||||
@@ -17,10 +17,10 @@ export const withdrawalSchema = {
|
||||
title: '提现设置',
|
||||
icon: 'fa fas fa-sliders-h',
|
||||
fields: [
|
||||
{ key: 'affiliate_min_withdrawal', label: '最低提现金额', type: 'number', default: 50, min: 0, max: 99999 },
|
||||
{ key: 'affiliate_min_withdrawal', label: '最低提现金额', type: 'input', sanitize_type: 'money_decimal', default: '50.00', help: '仅接受最多两位小数的十进制字符串。' },
|
||||
{ key: 'affiliate_withdrawal_fee', label: '提现手续费(%)', type: 'number', default: 5, min: 0, max: 100 },
|
||||
{ key: 'affiliate_daily_withdrawal_count', label: '每日可提现次数', type: 'number', default: 3, min: 0, max: 99999 },
|
||||
{ key: 'affiliate_daily_withdrawal_limit', label: '每日提现上限', type: 'number', default: 500, min: 0, max: 999999 },
|
||||
{ key: 'affiliate_daily_withdrawal_limit', label: '每日提现上限', type: 'input', sanitize_type: 'money_decimal', default: '500.00', help: '仅接受最多两位小数的十进制字符串。' },
|
||||
{ key: 'affiliate_withdrawal_reset_time', label: '重置时间', type: 'input', default: '00:00', help: '按站点 WordPress 时区重置,格式 HH:MM。' },
|
||||
{ key: 'affiliate_payout_channels', label: '可用收款渠道', type: 'checkbox', default: ['alipay', 'wechat'], sanitize_type: 'array', options: [{ label: '支付宝收款码', value: 'alipay' }, { label: '微信收款码', value: 'wechat' }] },
|
||||
],
|
||||
|
||||
@@ -141,8 +141,8 @@ export const pageSchema = {
|
||||
{ key: 'post_rewards_text', label: '打赏按钮文案', type: 'input', default: '赞赏', dependsOn: { key: 'post_rewards_s', value: true } },
|
||||
{ key: 'post_rewards_amounts', label: '快捷金额', type: 'input', default: '5,10,20,50,100', help: '使用英文逗号分隔', dependsOn: { key: 'post_rewards_s', value: true } },
|
||||
{ key: 'post_rewards_custom', label: '允许自定义金额', type: 'switch', default: true, dependsOn: { key: 'post_rewards_s', value: true } },
|
||||
{ key: 'post_rewards_min', label: '最低金额', type: 'number', default: 1, min: 0.01, max: 1000, step: 0.01, dependsOn: { key: 'post_rewards_s', value: true } },
|
||||
{ key: 'post_rewards_max', label: '最高金额', type: 'number', default: 1000, min: 1, max: 100000, step: 0.01, dependsOn: { key: 'post_rewards_s', value: true } },
|
||||
{ key: 'post_rewards_min', label: '最低金额', type: 'input', default: '1.00', min: 0.01, max: 1000, step: 0.01, dependsOn: { key: 'post_rewards_s', value: true } },
|
||||
{ key: 'post_rewards_max', label: '最高金额', type: 'input', default: '1000.00', min: 1, max: 100000, step: 0.01, dependsOn: { key: 'post_rewards_s', value: true } },
|
||||
{ key: 'article_maxheight_kg', label: '正文限高', type: 'switch', default: false, help: '开启后长文默认折叠,可展开全文' },
|
||||
{ key: 'article_maxheight', label: '限高像素', type: 'number', default: 1000, min: 200, max: 5000, dependsOn: { key: 'article_maxheight_kg', value: true } },
|
||||
],
|
||||
|
||||
@@ -27,8 +27,8 @@ export const submitSchema = {
|
||||
post_article_title_strlen_limit_max: 30,
|
||||
post_article_user: 0,
|
||||
post_article_pay_free_s: true,
|
||||
post_article_pay_price_min: 0,
|
||||
post_article_pay_price_max: 100,
|
||||
post_article_pay_price_min: '0.00',
|
||||
post_article_pay_price_max: '100.00',
|
||||
post_article_pay_vip_price_s: true,
|
||||
post_article_pay_vip_1_discount: 80,
|
||||
post_article_pay_vip_2_discount: 50,
|
||||
@@ -114,8 +114,8 @@ export const submitSchema = {
|
||||
icon: 'fa fas fa-database',
|
||||
fields: [
|
||||
{ key: 'post_article_pay_free_s', label: '允许设置免费', type: 'switch', default: true, dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||
{ key: 'post_article_pay_price_min', label: '最低价格', type: 'number', default: 0, min: 0, max: 9999, help: '投稿可设置的最低付费金额', dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||
{ key: 'post_article_pay_price_max', label: '最高价格', type: 'number', default: 100, min: 0, max: 9999, help: '投稿可设置的最高付费金额', dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||
{ key: 'post_article_pay_price_min', label: '最低价格', type: 'input', sanitize_type: 'money_decimal', default: '0.00', min: 0, max: 9999, help: '投稿可设置的最低付费金额', dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||
{ key: 'post_article_pay_price_max', label: '最高价格', type: 'input', sanitize_type: 'money_decimal', default: '100.00', min: 0, max: 9999, help: '投稿可设置的最高付费金额', dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||
{ key: 'post_article_pay_vip_price_s', label: '允许设置会员价', type: 'switch', default: true, dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||
{ key: 'post_article_pay_vip_1_discount', label: 'VIP1 折扣(%)', type: 'number', default: 80, min: 0, max: 100, help: 'VIP1 会员折扣百分比,80 即 8 折', dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||
{ key: 'post_article_pay_vip_2_discount', label: 'VIP2 折扣(%)', type: 'number', default: 50, min: 0, max: 100, help: 'VIP2 会员折扣百分比,50 即 5 折', dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||
|
||||
@@ -12,8 +12,8 @@ export const MEDAL_GET_TYPES = [
|
||||
{ label: '文章获赞', value: 'like' },
|
||||
{ label: '评论获赞', value: 'comment_like' },
|
||||
{ label: '内容阅读量', value: 'views' },
|
||||
{ label: '现金创作分成累计(元)', value: 'pay_income' },
|
||||
{ label: '推广佣金累计(元)', value: 'pay_rebate' },
|
||||
{ label: '现金创作分成累计(元)', value: 'creator_income' },
|
||||
{ label: '推广佣金累计(元)', value: 'affiliate_commission' },
|
||||
{ label: '仅手动授予', value: 'manually_add' },
|
||||
]
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ export const creditsSchema = {
|
||||
points_deduction_s: false,
|
||||
points_deduction_rate: 100,
|
||||
points_deduction_max: 50,
|
||||
points_pass_exchange_s: false,
|
||||
points_pass_exchange_desc: '',
|
||||
points_pass_exchange_only_password: true,
|
||||
asset_points_card_enabled: false,
|
||||
asset_points_card_description: '',
|
||||
asset_points_card_password_only: true,
|
||||
points_show_balance: true,
|
||||
points_show_log: true,
|
||||
points_expire_days: 0,
|
||||
@@ -95,9 +95,9 @@ export const creditsSchema = {
|
||||
title: '积分卡密兑换',
|
||||
icon: 'fa fas fa-ticket-alt',
|
||||
fields: [
|
||||
{ key: 'points_pass_exchange_s', label: '启用积分卡密兑换', type: 'switch', default: false, help: '对齐 legacy points_pass_exchange_s。后台充值卡可生成积分卡,用户中心兑换入账。' },
|
||||
{ key: 'points_pass_exchange_desc', label: '兑换说明', type: 'textarea', default: '', dependsOn: { key: 'points_pass_exchange_s', value: true } },
|
||||
{ key: 'points_pass_exchange_only_password', label: '仅密码兑换', type: 'switch', default: true, dependsOn: { key: 'points_pass_exchange_s', value: true }, help: '开启后只需输入卡密;关闭则需卡号+卡密。' },
|
||||
{ key: 'asset_points_card_enabled', label: '启用积分卡密兑换', type: 'switch', default: false, help: '后台充值卡可生成积分卡,用户中心兑换入账。' },
|
||||
{ key: 'asset_points_card_description', label: '兑换说明', type: 'textarea', default: '', dependsOn: { key: 'asset_points_card_enabled', value: true } },
|
||||
{ key: 'asset_points_card_password_only', label: '仅密码兑换', type: 'switch', default: true, dependsOn: { key: 'asset_points_card_enabled', value: true }, help: '开启后只需输入卡密;关闭则需卡号+卡密。' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,11 +7,11 @@ export const verifySchema = {
|
||||
user_verify_enabled: true,
|
||||
user_verify_public_display: true,
|
||||
user_verify_personal_badge_text: '个人认证',
|
||||
user_verify_personal_fee: 0,
|
||||
user_verify_personal_fee: '0.00',
|
||||
user_verify_personal_free_applications: 0,
|
||||
user_verify_personal_require_real_name: false,
|
||||
user_verify_enterprise_badge_text: '企业认证',
|
||||
user_verify_enterprise_fee: 0,
|
||||
user_verify_enterprise_fee: '0.00',
|
||||
user_verify_enterprise_free_applications: 0,
|
||||
user_verify_enterprise_require_real_name: false,
|
||||
user_verify_allow_apply: true,
|
||||
@@ -67,10 +67,10 @@ export const verifySchema = {
|
||||
default: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
dependsOn: { key: 'user_verify_allow_apply', value: false },
|
||||
},
|
||||
{ key: 'user_verify_personal_fee', label: '个人认证每次扣余额(元)', type: 'number', default: 0, min: 0, step: 0.01, dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_personal_fee', label: '个人认证每次扣余额(元)', type: 'input', default: '0.00', help: '仅接受最多两位小数的十进制字符串。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_personal_free_applications', label: '个人认证赠送初始次数', type: 'number', default: 0, min: 0, max: 9999, help: '每位用户前 N 次个人认证申请免费,之后按单次费用扣余额。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_personal_require_real_name', label: '个人认证强制填写真实姓名', type: 'switch', default: false, dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_enterprise_fee', label: '企业认证每次扣余额(元)', type: 'number', default: 0, min: 0, step: 0.01, dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_enterprise_fee', label: '企业认证每次扣余额(元)', type: 'input', default: '0.00', help: '仅接受最多两位小数的十进制字符串。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_enterprise_free_applications', label: '企业认证赠送初始次数', type: 'number', default: 0, min: 0, max: 9999, help: '每位用户前 N 次企业认证申请免费,之后按单次费用扣余额。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_enterprise_require_real_name', label: '企业认证强制填写真实姓名', type: 'switch', default: false, dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user