积分与余额账务系统硬切重构
This commit is contained in:
1 parent
ca12226b39
commit
5cae5b5c93
159 files changed
+7275
-4718
No files matched your search
@@ -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 } },
|
||||
{
|
||||
|
||||
+16
-10
@@ -150,7 +150,7 @@
|
||||
if (data && data.vip_url) {
|
||||
extra = '<a class="btn-primary btn-sm" href="' + escapeAttr(data.vip_url) + '">' + tt('go_vip', '开通会员') + '</a>';
|
||||
}
|
||||
} else if (code === 'aksrt_credits_insufficient' || code === 'aksrt_balance_insufficient') {
|
||||
} else if (code === 'aksrt_points_insufficient' || code === 'aksrt_balance_insufficient') {
|
||||
title = tt('credit_low', '积分不够');
|
||||
const balance = typeof (data && data.balance) === 'number' ? data.balance : 0;
|
||||
const cost = typeof (data && data.cost) === 'number' ? data.cost : 0;
|
||||
@@ -355,15 +355,17 @@
|
||||
function showLowCreditModal(button) {
|
||||
const balance = parseInt(button.getAttribute('data-balance'), 10) || 0;
|
||||
const cost = parseInt(button.getAttribute('data-cost'), 10) || 0;
|
||||
renderError('', { balance: balance, cost: cost }, button, 'aksrt_credits_insufficient');
|
||||
renderError('', { balance: balance, cost: cost }, button, 'aksrt_points_insufficient');
|
||||
}
|
||||
|
||||
// ===== 收银台(内容购买) =====
|
||||
function restHeaders() {
|
||||
return {
|
||||
function restHeaders(idempotencyKey) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': window.aksrtData.restNonce || '',
|
||||
};
|
||||
if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
|
||||
return headers;
|
||||
}
|
||||
|
||||
function payApiBase(apiKind) {
|
||||
@@ -467,7 +469,7 @@
|
||||
}
|
||||
const title = escapeHtml(preview.title || '');
|
||||
const typeLabel = escapeHtml(preview.pay_type_label || '');
|
||||
const balance = typeof preview.balance === 'number' ? preview.balance : 0;
|
||||
const balance = isPoints && Number.isSafeInteger(preview.balance) ? preview.balance : 0;
|
||||
const methods = Array.isArray(preview.payment_methods) ? preview.payment_methods : [];
|
||||
const couponAllowed = !isPoints && !!preview.coupon_allowed;
|
||||
const couponCodeVal = escapeAttr(preview.coupon_code || preview._coupon_code || '');
|
||||
@@ -477,7 +479,7 @@
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('price', '价格') + '</span><b>' + baseLabel + '</b></div>';
|
||||
}
|
||||
// VIP list price row when VIP reduced list amount.
|
||||
if (typeof preview.list_amount === 'number' && typeof preview.base === 'number' && preview.list_amount !== preview.base) {
|
||||
if (preview.list_amount !== preview.base) {
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('vip_price', '会员价') + '</span><b class="is-sale">' + listLabel + '</b></div>';
|
||||
} else if (preview.amount !== preview.base && !preview.rebate_discount && !preview.coupon_discount) {
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('vip_price', '会员价') + '</span><b class="is-sale">' + amountLabel + '</b></div>';
|
||||
@@ -527,12 +529,12 @@
|
||||
}
|
||||
body += '</div>';
|
||||
}
|
||||
const walletBal = typeof preview.wallet_balance === 'number'
|
||||
const walletBal = typeof preview.wallet_balance === 'string'
|
||||
? preview.wallet_balance
|
||||
: (typeof preview.balance === 'number' ? preview.balance : 0);
|
||||
: (typeof preview.balance === 'string' ? preview.balance : '0.00');
|
||||
const hasBalanceMethod = methods.some(function (m) { return (m && m.id) === 'balance'; });
|
||||
if (hasBalanceMethod) {
|
||||
body += '<div class="aksrt-cashier__box"><div class="aksrt-cashier__row"><span>' + tt('my_wallet', '我的余额') + '</span><b class="is-ok">¥' + Number(walletBal).toFixed(2) + '</b></div></div>';
|
||||
body += '<div class="aksrt-cashier__box"><div class="aksrt-cashier__row"><span>' + tt('my_wallet', '我的余额') + '</span><b class="is-ok">¥' + escapeHtml(walletBal) + '</b></div></div>';
|
||||
if (preview.balance_enough === false) {
|
||||
body += '<div class="aksrt-cashier__warn">' + tt('wallet_short', '余额不足,请先充值或选择其他支付方式') + '</div>';
|
||||
if (preview.credits_url) {
|
||||
@@ -621,10 +623,13 @@
|
||||
if (pt) payload.price_type = pt;
|
||||
if (mode === 'cash' && couponCode) payload.coupon_code = couponCode;
|
||||
|
||||
if (!button.dataset.aksrtOperationKey) {
|
||||
button.dataset.aksrtOperationKey = window.AksrtRequest.createIdempotencyKey();
|
||||
}
|
||||
fetch(REST_BASE + payApiBase(apiKind) + postId, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: restHeaders(),
|
||||
headers: restHeaders(button.dataset.aksrtOperationKey),
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
.then(function (resp) { return resp.json().then(function (j) { return { ok: resp.ok, status: resp.status, json: j }; }); })
|
||||
@@ -642,6 +647,7 @@
|
||||
return;
|
||||
}
|
||||
const data = (r.json && r.json.data) ? r.json.data : r.json;
|
||||
delete button.dataset.aksrtOperationKey;
|
||||
if (data && data.redirect && (data.payment_url || data.qr_content)) {
|
||||
if (data.payment_url) {
|
||||
window.open(data.payment_url, '_blank');
|
||||
|
||||
@@ -13,6 +13,23 @@
|
||||
return window.aksrtData || window.aksrt || {};
|
||||
}
|
||||
|
||||
function createIdempotencyKey() {
|
||||
if (window.crypto && typeof window.crypto.randomUUID === "function") {
|
||||
return window.crypto.randomUUID();
|
||||
}
|
||||
if (!window.crypto || typeof window.crypto.getRandomValues !== "function") {
|
||||
throw new Error("当前浏览器无法生成安全的操作标识");
|
||||
}
|
||||
var bytes = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(bytes);
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
var hex = Array.prototype.map.call(bytes, function (value) {
|
||||
return value.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 restBase() {
|
||||
var config = data();
|
||||
var raw = config.restUrl || config.apiUrl || config.rest_url || "";
|
||||
@@ -144,6 +161,7 @@
|
||||
window.AksrtRequest = {
|
||||
url: url,
|
||||
request: request,
|
||||
createIdempotencyKey: createIdempotencyKey,
|
||||
get: function (path, options) {
|
||||
return request(path, Object.assign({}, options || {}, { method: "GET" }));
|
||||
},
|
||||
|
||||
+71
-21
@@ -16,6 +16,28 @@
|
||||
}
|
||||
var activePoll = null;
|
||||
|
||||
function operationKey(owner) {
|
||||
if (!owner.dataset.aksrtOperationKey) {
|
||||
owner.dataset.aksrtOperationKey = window.AksrtRequest.createIdempotencyKey();
|
||||
}
|
||||
return owner.dataset.aksrtOperationKey;
|
||||
}
|
||||
|
||||
function resetOperationKey(owner) {
|
||||
delete owner.dataset.aksrtOperationKey;
|
||||
}
|
||||
|
||||
function resetOperationKeyOnChange(owner) {
|
||||
owner.addEventListener('input', function () { resetOperationKey(owner); });
|
||||
owner.addEventListener('change', function () { resetOperationKey(owner); });
|
||||
}
|
||||
|
||||
function balanceMinor(value) {
|
||||
var match = /^(0|[1-9]\d*)(?:\.(\d{1,2}))?$/.exec(String(value || '').trim());
|
||||
if (!match) return null;
|
||||
return (BigInt(match[1]) * 100n) + BigInt((match[2] || '').padEnd(2, '0') || '0');
|
||||
}
|
||||
|
||||
function api(path, options) {
|
||||
return window.AksrtRequest.request(path, options || {}).then(function (response) {
|
||||
return {
|
||||
@@ -218,6 +240,7 @@
|
||||
Array.prototype.forEach.call(roots, function (root) {
|
||||
var tip = root.querySelector('[data-aksrt-card-tip]');
|
||||
var busy = false;
|
||||
resetOperationKeyOnChange(root);
|
||||
|
||||
function setTip(text, isError) {
|
||||
if (!tip) return;
|
||||
@@ -255,6 +278,7 @@
|
||||
|
||||
api('/pay/card-pass/redeem', {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': operationKey(root) },
|
||||
body: JSON.stringify(body)
|
||||
}).then(function (r) {
|
||||
busy = false;
|
||||
@@ -264,6 +288,7 @@
|
||||
return;
|
||||
}
|
||||
var data = (r.json && r.json.data) ? r.json.data : r.json;
|
||||
resetOperationKey(root);
|
||||
if (type === 'vip' && window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === 'function') {
|
||||
window.AksrtModalRuntime.invalidate(['vip', 'orders']);
|
||||
}
|
||||
@@ -278,7 +303,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ===== 余额充值 / 转账 / 积分兑余额(legacy 资产中心) =====
|
||||
// ===== 余额充值 / 转账 / 余额兑积分 =====
|
||||
function openModal(el) {
|
||||
if (!el) return;
|
||||
el.hidden = false;
|
||||
@@ -344,21 +369,23 @@
|
||||
});
|
||||
});
|
||||
if (!form) return;
|
||||
resetOperationKeyOnChange(form);
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
if (busy) return;
|
||||
var pkgInput = form.querySelector('input[name="balance_package"]:checked') || form.querySelector('input[name="balance_package"]');
|
||||
var pkg = pkgInput ? pkgInput.value : 'custom';
|
||||
var customEl = form.querySelector('input[name="custom_price"]');
|
||||
var custom = customEl ? parseFloat(customEl.value || '0') : 0;
|
||||
if (custom > 0) {
|
||||
var custom = customEl ? String(customEl.value || '').trim() : '';
|
||||
var validCustom = /^(0|[1-9]\d*)(?:\.\d{1,2})?$/.test(custom) && custom !== '0' && custom !== '0.0' && custom !== '0.00';
|
||||
if (validCustom) {
|
||||
pkg = 'custom';
|
||||
}
|
||||
if ((!pkg || pkg === 'custom') && !(custom > 0) && !pkgInput) {
|
||||
if ((!pkg || pkg === 'custom') && !validCustom && !pkgInput) {
|
||||
notyf('请选择或输入充值金额', 'danger');
|
||||
return;
|
||||
}
|
||||
if (pkg === 'custom' && !(custom > 0)) {
|
||||
if (pkg === 'custom' && !validCustom) {
|
||||
notyf('请输入有效充值金额', 'danger');
|
||||
return;
|
||||
}
|
||||
@@ -368,6 +395,7 @@
|
||||
notyf('正在创建充值订单…', 'load', '', 'aksrt-balance-charge-toast');
|
||||
api('/pay/balance/charge', {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': operationKey(form) },
|
||||
body: JSON.stringify({
|
||||
package: pkg,
|
||||
custom_price: custom,
|
||||
@@ -380,6 +408,7 @@
|
||||
return;
|
||||
}
|
||||
var data = (r.json && r.json.data) ? r.json.data : r.json;
|
||||
resetOperationKey(form);
|
||||
if (data && data.redirect && (data.payment_url || data.qr_content)) {
|
||||
if (data.payment_url) window.open(data.payment_url, '_blank');
|
||||
notyf(data.message || '请完成支付,支付成功后将自动刷新。', 'info', 5, 'aksrt-balance-charge-toast');
|
||||
@@ -478,6 +507,7 @@
|
||||
});
|
||||
}
|
||||
if (!form) return;
|
||||
resetOperationKeyOnChange(form);
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
if (busy) return;
|
||||
@@ -489,7 +519,12 @@
|
||||
notyf('请选择收款用户', 'danger');
|
||||
return;
|
||||
}
|
||||
if (!(parseFloat(amount) > 0) || (type === 'points' && (!Number.isInteger(Number(amount)) || Number(amount) < pointsTransferMin))) {
|
||||
var pointsAmount = /^[1-9]\d*$/.test(String(amount)) ? BigInt(String(amount)) : 0n;
|
||||
var cashAmount = balanceMinor(amount);
|
||||
var validAmount = type === 'points'
|
||||
? pointsAmount >= BigInt(pointsTransferMin) && pointsAmount <= BigInt(Number.MAX_SAFE_INTEGER)
|
||||
: cashAmount !== null && cashAmount > 0n;
|
||||
if (!validAmount) {
|
||||
notyf('请输入有效金额', 'danger');
|
||||
return;
|
||||
}
|
||||
@@ -497,7 +532,12 @@
|
||||
notyf('正在转账…', 'load', '', 'aksrt-balance-transfer-toast');
|
||||
api('/pay/transfer', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ type: type, recipient: recipient, amount: amount })
|
||||
headers: { 'Idempotency-Key': operationKey(form) },
|
||||
body: JSON.stringify({
|
||||
type: type,
|
||||
recipient: recipient,
|
||||
amount: type === 'points' ? Number(pointsAmount) : String(amount)
|
||||
})
|
||||
}).then(function (r) {
|
||||
busy = false;
|
||||
if (!r.ok || (r.json && r.json.code && !r.json.success)) {
|
||||
@@ -505,6 +545,7 @@
|
||||
return;
|
||||
}
|
||||
var data = (r.json && r.json.data) ? r.json.data : r.json;
|
||||
resetOperationKey(form);
|
||||
notyf((data && data.message) || '转账成功,正在刷新…', 'success', 5, 'aksrt-balance-transfer-toast');
|
||||
window.setTimeout(function () { window.location.reload(); }, 800);
|
||||
}).catch(function () {
|
||||
@@ -529,12 +570,15 @@
|
||||
btn.addEventListener('click', function () { closeModal(modal); });
|
||||
});
|
||||
if (!form) return;
|
||||
resetOperationKeyOnChange(form);
|
||||
function updatePreview() {
|
||||
if (!preview || !amountEl) return;
|
||||
var amount = Number(amountEl.value || '0');
|
||||
var rate = Number(amountEl.getAttribute('data-aksrt-points-exchange-rate') || '0');
|
||||
preview.textContent = Number.isInteger(amount) && amount > 0 && rate > 0
|
||||
? '预计获得 ' + (amount * rate) + ' 积分'
|
||||
var amount = balanceMinor(amountEl.value);
|
||||
var rateRaw = amountEl.getAttribute('data-aksrt-points-exchange-rate') || '';
|
||||
var rate = /^[1-9]\d*$/.test(rateRaw) ? BigInt(rateRaw) : 0n;
|
||||
var scaled = amount !== null ? amount * rate : 0n;
|
||||
preview.textContent = amount !== null && amount > 0n && rate > 0n && scaled % 100n === 0n
|
||||
? '预计获得 ' + String(scaled / 100n) + ' 积分'
|
||||
: '';
|
||||
}
|
||||
amountEl.addEventListener('input', updatePreview);
|
||||
@@ -542,15 +586,16 @@
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
if (busy) return;
|
||||
var amount = amountEl ? Number(amountEl.value || '0') : 0;
|
||||
if (!Number.isInteger(amount) || !(amount > 0)) {
|
||||
notyf('请输入正整数余额', 'danger');
|
||||
var amount = amountEl ? String(amountEl.value || '').trim() : '';
|
||||
if (!/^(0|[1-9]\d*)(?:\.\d{1,2})?$/.test(amount) || /^0(?:\.0{1,2})?$/.test(amount)) {
|
||||
notyf('请输入最多两位小数的正数余额', 'danger');
|
||||
return;
|
||||
}
|
||||
busy = true;
|
||||
notyf('正在购买积分…', 'load', '', 'aksrt-balance-points-toast');
|
||||
api('/pay/balance/points-exchange', {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': operationKey(form) },
|
||||
body: JSON.stringify({ amount: amount })
|
||||
}).then(function (r) {
|
||||
busy = false;
|
||||
@@ -559,6 +604,7 @@
|
||||
return;
|
||||
}
|
||||
var data = (r.json && r.json.data) ? r.json.data : r.json;
|
||||
resetOperationKey(form);
|
||||
notyf((data && data.message) || '购买成功,正在刷新…', 'success', 5, 'aksrt-balance-points-toast');
|
||||
window.setTimeout(function () { window.location.reload(); }, 800);
|
||||
}).catch(function () {
|
||||
@@ -581,6 +627,7 @@
|
||||
btn.addEventListener('click', function () { closeModal(modal); });
|
||||
});
|
||||
if (!form) return;
|
||||
resetOperationKeyOnChange(form);
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
if (busy) return;
|
||||
@@ -588,17 +635,18 @@
|
||||
var methodEl = form.querySelector('[data-aksrt-withdraw-method]');
|
||||
var accountEl = form.querySelector('[data-aksrt-withdraw-account]');
|
||||
var rawAmount = amountEl ? String(amountEl.value || '').trim() : '';
|
||||
var amount = rawAmount ? Number(rawAmount) : 0;
|
||||
var minAmount = amountEl ? Number(amountEl.getAttribute('data-withdraw-min') || '0') : 0;
|
||||
var remainingAmount = amountEl ? Number(amountEl.getAttribute('data-withdraw-remaining-amount') || '0') : 0;
|
||||
var amount = rawAmount;
|
||||
var amountMinor = balanceMinor(amount);
|
||||
var minAmount = amountEl ? balanceMinor(amountEl.getAttribute('data-withdraw-min') || '0') : 0n;
|
||||
var remainingAmount = amountEl ? balanceMinor(amountEl.getAttribute('data-withdraw-remaining-amount') || '0') : 0n;
|
||||
var remainingCount = amountEl ? Number(amountEl.getAttribute('data-withdraw-remaining-count') || '0') : 0;
|
||||
var method = methodEl ? methodEl.value : 'alipay';
|
||||
var account = accountEl ? String(accountEl.value || '').trim() : '';
|
||||
if (!Number.isInteger(amount) || !(amount > 0)) {
|
||||
notyf('提现金额必须为正整数', 'danger');
|
||||
if (amountMinor === null || amountMinor <= 0n) {
|
||||
notyf('提现金额必须为最多两位小数的正数', 'danger');
|
||||
return;
|
||||
}
|
||||
if (amount < minAmount) {
|
||||
if (minAmount === null || amountMinor < minAmount) {
|
||||
notyf('提现金额低于最低提现门槛', 'danger');
|
||||
return;
|
||||
}
|
||||
@@ -606,7 +654,7 @@
|
||||
notyf('当日提现次数已用尽', 'danger');
|
||||
return;
|
||||
}
|
||||
if (amount > remainingAmount) {
|
||||
if (remainingAmount === null || amountMinor > remainingAmount) {
|
||||
notyf('本次提现后将超出单日提现总额上限', 'danger');
|
||||
return;
|
||||
}
|
||||
@@ -618,6 +666,7 @@
|
||||
notyf('正在提交提现申请…', 'load', '', 'aksrt-balance-withdraw-toast');
|
||||
api('/earnings/withdrawals', {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': operationKey(form) },
|
||||
body: JSON.stringify({ amount: amount, method: method, account: account })
|
||||
}).then(function (r) {
|
||||
busy = false;
|
||||
@@ -626,6 +675,7 @@
|
||||
return;
|
||||
}
|
||||
var data = (r.json && r.json.data) ? r.json.data : r.json;
|
||||
resetOperationKey(form);
|
||||
notyf((data && data.message) || '申请已提交', 'success', 5, 'aksrt-balance-withdraw-toast');
|
||||
closeModal(modal);
|
||||
var panel = modal.closest ? modal.closest('.dashboard-panel') : null;
|
||||
|
||||
+33
-16
@@ -8,9 +8,10 @@
|
||||
|
||||
var instances = new WeakMap();
|
||||
|
||||
function request(path, body, method) {
|
||||
function request(path, body, method, operationKey) {
|
||||
var options = { method: method || "POST" };
|
||||
if (body !== undefined) options.body = JSON.stringify(body || {});
|
||||
if (operationKey) options.headers = { "Idempotency-Key": operationKey };
|
||||
return window.AksrtRequest.request(path, options);
|
||||
}
|
||||
|
||||
@@ -30,12 +31,15 @@
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function formatMoney(order) {
|
||||
var amount = Number(order && order.amount || 0);
|
||||
return order && order.is_points
|
||||
? escapeHtml(order.currency_symbol || "积分") + " " + Math.round(amount)
|
||||
: "¥" + amount.toFixed(2);
|
||||
}
|
||||
function formatMoney(order) {
|
||||
var raw = String(order && order.amount != null ? order.amount : "0");
|
||||
if (order && order.is_points) {
|
||||
return escapeHtml(order.currency_symbol || "积分") + " " + (/^\d+$/.test(raw) ? raw : "0");
|
||||
}
|
||||
var match = /^(0|[1-9]\d*)(?:\.(\d{1,2}))?$/.exec(raw);
|
||||
var amount = match ? match[1] + "." + (match[2] || "").padEnd(2, "0") : "0.00";
|
||||
return "¥" + amount;
|
||||
}
|
||||
|
||||
function orderTag(orderNumber) {
|
||||
return "order:" + String(orderNumber || "").toLowerCase();
|
||||
@@ -100,9 +104,12 @@
|
||||
}
|
||||
|
||||
function detailOptions(state, orderNumber, refresh) {
|
||||
var actionKey = refresh ? "refresh:" + orderNumber : "";
|
||||
if (actionKey && !state.operationKeys[actionKey]) state.operationKeys[actionKey] = window.AksrtRequest.createIdempotencyKey();
|
||||
return {
|
||||
url: orderPath(orderNumber, refresh ? "/refresh" : ""),
|
||||
method: refresh ? "POST" : "GET",
|
||||
headers: actionKey ? { "Idempotency-Key": state.operationKeys[actionKey] } : {},
|
||||
kind: "orders",
|
||||
title: "订单详情",
|
||||
responseType: "json",
|
||||
@@ -123,7 +130,10 @@
|
||||
renderDetail(state, data, session);
|
||||
},
|
||||
onData: function (data) {
|
||||
if (refresh) invalidateOrder(orderNumber, data);
|
||||
if (refresh) {
|
||||
delete state.operationKeys[actionKey];
|
||||
invalidateOrder(orderNumber, data);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -244,12 +254,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
function startPayment(state, button) {
|
||||
function startPayment(state, button) {
|
||||
var number = button.getAttribute("data-aksrt-order-pay") || "";
|
||||
var method = button.getAttribute("data-aksrt-order-method") || "";
|
||||
if (!number || button.disabled) return;
|
||||
button.disabled = true;
|
||||
request(orderPath(number, "/pay"), method ? { payment_method: method } : {}, "POST").then(function (result) {
|
||||
if (!number || button.disabled) return;
|
||||
var actionKey = "pay:" + number;
|
||||
if (!state.operationKeys[actionKey]) state.operationKeys[actionKey] = window.AksrtRequest.createIdempotencyKey();
|
||||
button.disabled = true;
|
||||
request(orderPath(number, "/pay"), method ? { payment_method: method } : {}, "POST", state.operationKeys[actionKey]).then(function (result) {
|
||||
delete state.operationKeys[actionKey];
|
||||
var data = result.data || {};
|
||||
invalidateOrder(number, data.order);
|
||||
if (data.redirect && data.payment_url) {
|
||||
@@ -273,8 +286,11 @@
|
||||
button.textContent = "确认关闭";
|
||||
return;
|
||||
}
|
||||
button.disabled = true;
|
||||
request(orderPath(number, "/cancel"), {}, "POST").then(function (result) {
|
||||
button.disabled = true;
|
||||
var actionKey = "cancel:" + number;
|
||||
if (!state.operationKeys[actionKey]) state.operationKeys[actionKey] = window.AksrtRequest.createIdempotencyKey();
|
||||
request(orderPath(number, "/cancel"), {}, "POST", state.operationKeys[actionKey]).then(function (result) {
|
||||
delete state.operationKeys[actionKey];
|
||||
var data = result.data || {};
|
||||
invalidateOrder(number, data.order);
|
||||
notyf("订单已关闭", "success");
|
||||
@@ -294,13 +310,14 @@
|
||||
|
||||
function initRoot(root) {
|
||||
if (instances.has(root)) return;
|
||||
var state = {
|
||||
var state = {
|
||||
root: root,
|
||||
detailOrderNumber: "",
|
||||
detailSession: null,
|
||||
detailReplacement: false,
|
||||
detailReplacementSession: null,
|
||||
countdownTimer: null,
|
||||
countdownTimer: null,
|
||||
operationKeys: {},
|
||||
};
|
||||
|
||||
state.onRootClick = function (event) {
|
||||
|
||||
+17
-1
@@ -11,6 +11,13 @@
|
||||
function initReward() {
|
||||
var activeTrigger = null;
|
||||
|
||||
function operationKey(modal) {
|
||||
if (!modal.dataset.aksrtOperationKey) {
|
||||
modal.dataset.aksrtOperationKey = window.AksrtRequest.createIdempotencyKey();
|
||||
}
|
||||
return modal.dataset.aksrtOperationKey;
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
document.querySelectorAll('.reward-modal.is-open').forEach(function (modal) {
|
||||
modal.classList.remove('is-open');
|
||||
@@ -52,6 +59,7 @@
|
||||
var amountButton = event.target.closest('[data-reward-amount]');
|
||||
if (amountButton) {
|
||||
var modal = amountButton.closest('.reward-modal');
|
||||
delete modal.dataset.aksrtOperationKey;
|
||||
modal.querySelectorAll('[data-reward-amount]').forEach(function (button) {
|
||||
button.classList.toggle('is-active', button === amountButton);
|
||||
});
|
||||
@@ -67,6 +75,10 @@
|
||||
var customInput = tipModal.querySelector('[data-aksrt-reward-custom]');
|
||||
var selected = tipModal.querySelector('[data-reward-amount].is-active');
|
||||
var amount = customInput && customInput.value ? customInput.value : (selected ? selected.dataset.rewardAmount : '');
|
||||
if (customInput && customInput.dataset.aksrtIdempotencyBound !== '1') {
|
||||
customInput.dataset.aksrtIdempotencyBound = '1';
|
||||
customInput.addEventListener('input', function () { delete tipModal.dataset.aksrtOperationKey; });
|
||||
}
|
||||
|
||||
submit.disabled = true;
|
||||
submit.textContent = '赞赏中…';
|
||||
@@ -82,13 +94,17 @@
|
||||
fetch(cfg.ajaxUrl || '/wp-admin/admin-ajax.php', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Idempotency-Key': operationKey(tipModal)
|
||||
},
|
||||
body: body.toString()
|
||||
}).then(function (response) {
|
||||
return response.json();
|
||||
}).then(function (response) {
|
||||
var text = response && response.data && response.data.message ? response.data.message : '赞赏失败';
|
||||
if (response && response.success) {
|
||||
delete tipModal.dataset.aksrtOperationKey;
|
||||
if (message) message.textContent = text;
|
||||
setTimeout(closeAll, 900);
|
||||
} else if (message) {
|
||||
|
||||
@@ -75,6 +75,11 @@
|
||||
var refreshTimeout = null;
|
||||
var refreshInFlight = false;
|
||||
var openSequence = 0;
|
||||
var pendingCreateKey = '';
|
||||
var pendingCreateSignature = '';
|
||||
var pendingRefreshKey = '';
|
||||
var pendingManualKey = '';
|
||||
var pendingManualSignature = '';
|
||||
|
||||
function isOpen(shell) {
|
||||
return !!shell && shell.classList.contains('is-open') && document.contains(shell);
|
||||
@@ -176,6 +181,7 @@
|
||||
var timedOut = false;
|
||||
var shell = activeShell;
|
||||
var timeoutMs = Math.max(5000, Number(config.refreshTimeout || 15000));
|
||||
if (!pendingRefreshKey) pendingRefreshKey = uuid();
|
||||
refreshTimeout = window.setTimeout(function () {
|
||||
timedOut = true;
|
||||
if (refreshController) {
|
||||
@@ -185,8 +191,10 @@
|
||||
|
||||
return request(config.restBase + '/' + encodeURIComponent(currentUuid) + '/refresh', config, {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': pendingRefreshKey },
|
||||
signal: refreshController ? refreshController.signal : undefined,
|
||||
}).then(function (data) {
|
||||
pendingRefreshKey = '';
|
||||
if (shell !== activeShell || !isOpen(shell)) return data;
|
||||
pollBackoff = explicit ? 0 : Math.min(4, pollBackoff + 1);
|
||||
render(data, shell);
|
||||
@@ -270,17 +278,23 @@
|
||||
var payload = {};
|
||||
form.forEach(function (value, key) { payload[key] = value; });
|
||||
payload.consent = payload.consent === '1';
|
||||
payload.client_request_id = uuid();
|
||||
var signature = JSON.stringify(payload);
|
||||
if (!pendingCreateKey || pendingCreateSignature !== signature) {
|
||||
pendingCreateKey = uuid();
|
||||
pendingCreateSignature = signature;
|
||||
}
|
||||
if (button) button.disabled = true;
|
||||
if (message) message.textContent = '';
|
||||
aliyunMetaInfo(config).then(function (metaInfo) {
|
||||
if (metaInfo) payload.meta_info = metaInfo;
|
||||
return request(config.restBase, config, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json', 'Idempotency-Key': pendingCreateKey },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}).then(function (data) {
|
||||
pendingCreateKey = '';
|
||||
pendingCreateSignature = '';
|
||||
render(data, shell);
|
||||
}).catch(function (error) {
|
||||
if (message) message.textContent = errorMessage(error, '创建认证会话失败,请重试。');
|
||||
@@ -300,11 +314,21 @@
|
||||
var button = manualForm.querySelector('[type="submit"]');
|
||||
var body = new FormData();
|
||||
Array.prototype.forEach.call((files && files.files) || [], function (file) { body.append('evidence[]', file); });
|
||||
var signature = Array.prototype.map.call((files && files.files) || [], function (file) {
|
||||
return [file.name, file.size, file.lastModified].join(':');
|
||||
}).join('|');
|
||||
if (!pendingManualKey || pendingManualSignature !== signature) {
|
||||
pendingManualKey = uuid();
|
||||
pendingManualSignature = signature;
|
||||
}
|
||||
if (button) button.disabled = true;
|
||||
request(config.restBase + '/' + encodeURIComponent(currentUuid) + '/manual', config, {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': pendingManualKey },
|
||||
body: body,
|
||||
}).then(function (data) {
|
||||
pendingManualKey = '';
|
||||
pendingManualSignature = '';
|
||||
manualForm.hidden = true;
|
||||
render(data, shell);
|
||||
}).catch(function (error) {
|
||||
|
||||
@@ -89,10 +89,6 @@
|
||||
$(this).text(aksrt__('admin_menu_title_income'));
|
||||
}
|
||||
|
||||
if (url.endsWith('aksrtpay_rebate_page')) {
|
||||
$(this).text(aksrt__('admin_menu_title_card'));
|
||||
}
|
||||
|
||||
if (url.endsWith('aksrtpay_product_page')) {
|
||||
$(this).text(aksrt__('admin_menu_title_product'));
|
||||
}
|
||||
@@ -448,6 +444,6 @@
|
||||
$('.permalink-structure [name="permalink_structure"]').val('/%post_id%.html').click();
|
||||
});
|
||||
|
||||
console.log('\n' + ' %c legacy Theme %c https://legacy.com ' + '\n', 'color: #fadfa3; background: #030307; padding:3px; font-size:12px;', 'color: #2abd4e;background: #16171a; padding: 3px; font-size: 12px;');
|
||||
console.log('%c legacy Theme %c https://legacy.com', 'color: #fadfa3; background: #030307; padding:3px; font-size:12px;', 'color: #2abd4e;background: #16171a; padding: 3px; font-size: 12px;');
|
||||
});
|
||||
})(jQuery, document);
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
(function(t,e){function i(t){var e=window.aksrt_admin_main_var&&window.aksrt_admin_main_var.i18n;return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:t}function n(t){var e=Array.prototype.slice.call(arguments,1),n=i(t),a=0;return n.replace(/%(\d+\$)?s/g,function(t,i){var n=i?parseInt(i,10)-1:a++;return void 0!==e[n]&&null!==e[n]?e[n]:t})}function a(t,e,i,n,a){var s,r,d;n&&(e+="px",i+="px",s="translate3D("+e+","+i+" , 0)",r={},d=o(),r[d+"transform"]=s,r[d+"transition"]=d+"transform 0s linear",r.cursor=a,"null"==n&&(r[d+"transform"]="",r[d+"transition"]=""),t.css(r))}function o(){var t=e.body||e.documentElement;return t=t.style,""==t.WebkitTransition?"-webkit-":""==t.MozTransition?"-moz-":""==t.OTransition?"-o-":""==t.transition?"":void 0}t.fn.minitouch=function(e){e=t.extend({direction:"bottom",selector:"",depreciation:50,onStart:!1,onEnd:!1},e);var i=t(this),n=(t("body"),e.depreciation),o=0,s=0,r=0,d=0,l=0,c=0,p=0,m=!1;i.on("touchstart pointerdown MSPointerDown",e.selector,function(t){o=0,s=0,r=0,d=0,l=0,c=0,p=0,o=t.originalEvent.pageX||t.originalEvent.touches[0].pageX,s=t.originalEvent.pageY||t.originalEvent.touches[0].pageY,m=!0}).on("touchmove pointermove MSPointerMove",e.selector,function(i){r=i.originalEvent.pageX||i.originalEvent.touches[0].pageX,d=i.originalEvent.pageY||i.originalEvent.touches[0].pageY,c=r-o,p=d-s,l=180*Math.atan2(p,c)/Math.PI,"right"==e.direction&&(p=0,c=l>-40&&l<40&&c>0?c:0),"left"==e.direction&&(p=0,c=(l>150||l<-150)&&0>c?c:0),"top"==e.direction&&(c=0,p=l>-130&&l<-50&&0>p?p:0),"bottom"==e.direction&&(c=0,p=l>50&&l<130&&p>0?p:0),0===c&&0===p||(i.preventDefault(),a(t(this),c,p,m,"grab"))}).on("touchend touchcancel pointerup MSPointerUp",e.selector,function(){(Math.abs(c)>n||Math.abs(p)>n)&&0!=e.onEnd&&e.onEnd(i),a(t(this),0,0,"null",""),m=!1,o=0,s=0,r=0,d=0,l=0,c=0,p=0})},t(e).ready(function(t){function a(e,i,n,a){t(".notyn").length||l.append('<div class="notyn"></div>'),i=i||"success",n=n||5e3,n=n<100?1e3*n:n;var s=a?' id="'+a+'"':"",r=t('<div class="noty1"'+s+'><div class="notyf '+i+'">'+e+"</div></div>"),d=!a;a&&t("#"+a).length?(t("#"+a).find(".notyf").removeClass().addClass("notyf "+i).html(e),r=t("#"+a),d=!0):t(".notyn").append(r),d&&setTimeout(function(){o(r)},n)}function o(t){t.addClass("notyn-out"),setTimeout(function(){t.remove()},1e3)}function s(i,n,a,o){function s(t,e,i){if(t.createTextRange){var n=t.createTextRange();n.collapse(!0),n.moveStart("character",e),n.moveEnd("character",i-e),n.select()}else t.setSelectionRange(e,i),t.select()}var r=i.toString(),d=e.querySelector("#copy-input");d||(d=e.createElement("input"),d.id="copy-input",d.readOnly="readOnly",d.style.position="fixed",d.style.left="-2000px",d.style.zIndex="-1000",o.parentNode.appendChild(d)),d.value=r,s(d,0,r.length),e.execCommand("copy")?t.isFunction(n)&&n():t.isFunction(a)&&a(),d.blur()}var r=t("#toplevel_page_aksrtpay_page");if(r.length&&"zh-Hans"!==t("html").attr("lang")){r.find(".toplevel_page_aksrtpay_page .wp-menu-name").text(i("admin_menu_title_shop_center"));var d=r.find(".wp-submenu");d.find("a.wp-first-item").text(i("admin_menu_title_shop_center")),d.find("a").each(function(){var e=t(this).attr("href");e.endsWith("aksrtpay_page")&&t(this).text(i("admin_menu_title_shop_center")),e.endsWith("aksrtpay_page#/order")&&t(this).text(i("admin_menu_title_order")),e.endsWith("aksrtpay_page#/shipping")&&t(this).text(i("admin_menu_title_shipping")),e.endsWith("aksrtpay_page#/after-sale")&&t(this).text(i("admin_menu_title_after_sale")),e.endsWith("aksrtpay_income_page")&&t(this).text(i("admin_menu_title_income")),e.endsWith("aksrtpay_rebate_page")&&t(this).text(i("admin_menu_title_card")),e.endsWith("aksrtpay_product_page")&&t(this).text(i("admin_menu_title_product")),e.endsWith("aksrtpay_charge_card_page")&&t(this).text(i("admin_menu_title_card")),e.endsWith("aksrtpay_coupon_page")&&t(this).text(i("admin_menu_title_coupon")),e.endsWith("aksrtpay_withdraw")&&t(this).text(i("admin_menu_title_withdraw")),e.endsWith("users.php")&&t(this).text(i("admin_menu_title_vip")),e.endsWith("aksrtpay_order_page")&&t(this).text(i("admin_menu_title_order_old"))})}var l=t("body");"function"!=typeof t.fn.serializeObject&&(t.fn.serializeObject=function(){var e={},i=this.serializeArray();return t.each(i,function(){void 0!==e[this.name]?(e[this.name].push||(e[this.name]=[e[this.name]]),e[this.name].push(this.value||"")):e[this.name]=this.value||""}),e}),l.width()<783&&t("#adminmenuwrap").minitouch({direction:"left",onEnd:function(){t("#wpwrap").removeClass("wp-responsive-open")}}),l.on("click",".noty1",function(){o(t(this))}),l.on("click","[data-clipboard-text]",function(){var e=t(this),o=e.attr("data-clipboard-text"),r=e.attr("data-clipboard-tag")||i("admin_content");s(o,function(){a(n("admin_copied",r))},function(){a(n("admin_copy_failed",r),"danger")},this)}),l.on("click",'[data-toggle="RefreshModal"]',function(){var e=t(this),i=e.attr("data-class")||"modal-mini",n=e.attr("data-remote"),a=e.attr("data-height")||200,o=e.attLine truncated
|
||||
(function(k,w){function e(t){var c=window.aksrt_admin_main_var&&window.aksrt_admin_main_var.i18n;return c&&Object.prototype.hasOwnProperty.call(c,t)?c[t]:t}function M(t){var c=Array.prototype.slice.call(arguments,1),g=e(t),n=0;return g.replace(/%(\d+\$)?s/g,function(v,h){var d=h?parseInt(h,10)-1:n++;return c[d]!==void 0&&c[d]!==null?c[d]:v})}function W(t,c,g,n,v){var h,d,r;n&&(c+="px",g+="px",h="translate3D("+c+","+g+" , 0)",d={},r=P(),d[r+"transform"]=h,d[r+"transition"]=r+"transform 0s linear",d.cursor=v,n=="null"&&(d[r+"transform"]="",d[r+"transition"]=""),t.css(d))}function P(){var t=w.body||w.documentElement;return t=t.style,t.WebkitTransition==""?"-webkit-":t.MozTransition==""?"-moz-":t.OTransition==""?"-o-":t.transition==""?"":void 0}k.fn.minitouch=function(t){t=k.extend({direction:"bottom",selector:"",depreciation:50,onStart:!1,onEnd:!1},t);var c=k(this),g=(k("body"),t.depreciation),n=0,v=0,h=0,d=0,r=0,p=0,l=0,x=!1;c.on("touchstart pointerdown MSPointerDown",t.selector,function(_){n=0,v=0,h=0,d=0,r=0,p=0,l=0,n=_.originalEvent.pageX||_.originalEvent.touches[0].pageX,v=_.originalEvent.pageY||_.originalEvent.touches[0].pageY,x=!0}).on("touchmove pointermove MSPointerMove",t.selector,function(_){h=_.originalEvent.pageX||_.originalEvent.touches[0].pageX,d=_.originalEvent.pageY||_.originalEvent.touches[0].pageY,p=h-n,l=d-v,r=180*Math.atan2(l,p)/Math.PI,t.direction=="right"&&(l=0,p=r>-40&&r<40&&p>0?p:0),t.direction=="left"&&(l=0,p=(r>150||r<-150)&&0>p?p:0),t.direction=="top"&&(p=0,l=r>-130&&r<-50&&0>l?l:0),t.direction=="bottom"&&(p=0,l=r>50&&r<130&&l>0?l:0),p===0&&l===0||(_.preventDefault(),W(k(this),p,l,x,"grab"))}).on("touchend touchcancel pointerup MSPointerUp",t.selector,function(){(Math.abs(p)>g||Math.abs(l)>g)&&t.onEnd!=0&&t.onEnd(c),W(k(this),0,0,"null",""),x=!1,n=0,v=0,h=0,d=0,r=0,p=0,l=0})},k(w).ready(function(t){var c=t("#toplevel_page_aksrtpay_page");if(c.length&&t("html").attr("lang")!=="zh-Hans"){c.find(".toplevel_page_aksrtpay_page .wp-menu-name").text(e("admin_menu_title_shop_center"));var g=c.find(".wp-submenu");g.find("a.wp-first-item").text(e("admin_menu_title_shop_center")),g.find("a").each(function(){var a=t(this).attr("href");a.endsWith("aksrtpay_page")&&t(this).text(e("admin_menu_title_shop_center")),a.endsWith("aksrtpay_page#/order")&&t(this).text(e("admin_menu_title_order")),a.endsWith("aksrtpay_page#/shipping")&&t(this).text(e("admin_menu_title_shipping")),a.endsWith("aksrtpay_page#/after-sale")&&t(this).text(e("admin_menu_title_after_sale")),a.endsWith("aksrtpay_income_page")&&t(this).text(e("admin_menu_title_income")),a.endsWith("aksrtpay_product_page")&&t(this).text(e("admin_menu_title_product")),a.endsWith("aksrtpay_charge_card_page")&&t(this).text(e("admin_menu_title_card")),a.endsWith("aksrtpay_coupon_page")&&t(this).text(e("admin_menu_title_coupon")),a.endsWith("aksrtpay_withdraw")&&t(this).text(e("admin_menu_title_withdraw")),a.endsWith("users.php")&&t(this).text(e("admin_menu_title_vip")),a.endsWith("aksrtpay_order_page")&&t(this).text(e("admin_menu_title_order_old"))})}var n=t("body");typeof t.fn.serializeObject!="function"&&(t.fn.serializeObject=function(){var a={},s=this.serializeArray();return t.each(s,function(){a[this.name]!==void 0?(a[this.name].push||(a[this.name]=[a[this.name]]),a[this.name].push(this.value||"")):a[this.name]=this.value||""}),a}),n.width()<783&&t("#adminmenuwrap").minitouch({direction:"left",onEnd:function(){t("#wpwrap").removeClass("wp-responsive-open")}});function v(a,s,i,m){t(".notyn").length||n.append('<div class="notyn"></div>'),s=s||"success",i=i||5e3,i=i<100?i*1e3:i;var f=m?' id="'+m+'"':"",o=t('<div class="noty1"'+f+'><div class="notyf '+s+'">'+a+"</div></div>"),b=!m;m&&t("#"+m).length?(t("#"+m).find(".notyf").removeClass().addClass("notyf "+s).html(a),o=t("#"+m),b=!0):t(".notyn").append(o),b&&setTimeout(function(){h(o)},i)}function h(a){a.addClass("notyn-out"),setTimeout(function(){a.remove()},1e3)}n.on("click",".noty1",function(){h(t(this))});function d(a,s,i,m){var f=a.toString(),o=w.querySelector("#copy-input");o||(o=w.createElement("input"),o.id="copy-input",o.readOnly="readOnly",o.style.position="fixed",o.style.left="-2000px",o.style.zIndex="-1000",m.parentNode.appendChild(o)),o.value=f,b(o,0,f.length),w.execCommand("copy")?t.isFunction(s)&&s():t.isFunction(i)&&i(),o.blur();function b(y,C,E){if(y.createTextRange){var T=y.createTextRange();T.collapse(!0),T.moveStart("character",C),T.moveEnd("character",E-C),T.select()}else y.setSelectionRange(C,E),y.select()}}n.on("click","[data-clipboard-text]",function(){var a=t(this),s=a.attr("data-clipboard-text"),i=a.attr("data-clipboard-tag")||e("admin_content");d(s,function(){v(M("admin_copied",i))},function(){v(M("admin_copy_failed",i),"danger")},this)}),n.on("click",'[data-toggle="RefreshModal"]',function(){var a=t(this),s=a.attr("data-class")||"modal-mini",i=a.attr("data-remote"),m=a.attr("data-height")||200,f=a.attr("mobile-bottom")&&t(window).width()<769?" bottom":"",o="aksrt-modal flex jLine truncated
|
||||
@@ -15,6 +15,7 @@
|
||||
var DEFAULT_MAX_ELAPSED = 120000;
|
||||
var MAX_DELAY = 15000;
|
||||
var createFlights = Object.create(null);
|
||||
var operationKeys = Object.create(null);
|
||||
var polls = Object.create(null);
|
||||
var lastScope = "";
|
||||
var scopeGeneration = 0;
|
||||
@@ -47,6 +48,7 @@
|
||||
if (flight && typeof flight.cancel === "function") flight.cancel();
|
||||
});
|
||||
createFlights = Object.create(null);
|
||||
operationKeys = Object.create(null);
|
||||
Object.keys(polls).forEach(function (pollKey) {
|
||||
var active = polls[pollKey];
|
||||
if (active && active.handle && typeof active.handle.abort === "function") active.handle.abort();
|
||||
@@ -115,6 +117,7 @@
|
||||
|
||||
var key = userScope() + ":" + String(packageId) + ":" + method;
|
||||
if (createFlights[key]) return createFlights[key].promise;
|
||||
if (!operationKeys[key]) operationKeys[key] = window.AksrtRequest.createIdempotencyKey();
|
||||
|
||||
var requestController = !options.signal && window.AbortController
|
||||
? new window.AbortController()
|
||||
@@ -122,6 +125,7 @@
|
||||
var flight = {
|
||||
promise: request("/pay/vip", {
|
||||
method: "POST",
|
||||
headers: { "Idempotency-Key": operationKeys[key] },
|
||||
body: JSON.stringify({
|
||||
package_id: packageId,
|
||||
payment_method: method,
|
||||
@@ -143,22 +147,31 @@
|
||||
}, function () {
|
||||
if (createFlights[key] === flight) delete createFlights[key];
|
||||
});
|
||||
flight.promise.then(function (response) {
|
||||
if (response.ok) delete operationKeys[key];
|
||||
});
|
||||
return flight.promise;
|
||||
}
|
||||
|
||||
function exchange(options) {
|
||||
options = options || {};
|
||||
userScope();
|
||||
var scope = userScope();
|
||||
var packageId = parseInt(options.packageId || options.package_id || 0, 10);
|
||||
if (!packageId) {
|
||||
return Promise.resolve({ ok: false, status: 400, json: { message: "兑换套餐无效" } });
|
||||
}
|
||||
var key = scope + ":points-exchange:" + String(packageId);
|
||||
if (!operationKeys[key]) operationKeys[key] = window.AksrtRequest.createIdempotencyKey();
|
||||
return request("/pay/vip/points-exchange", {
|
||||
method: "POST",
|
||||
headers: { "Idempotency-Key": operationKeys[key] },
|
||||
body: JSON.stringify({ package_id: packageId }),
|
||||
signal: options.signal,
|
||||
}).then(function (response) {
|
||||
if (response.ok) invalidate(options.tags || ["vip", "orders"]);
|
||||
if (response.ok) {
|
||||
delete operationKeys[key];
|
||||
invalidate(options.tags || ["vip", "orders"]);
|
||||
}
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user