Files
wordpress-theme-aksrt/assets/admin/src/pages/ResourceDownload.vue
T
kate522 79f8c43970 fix(pay): unify download gate on cashier unlock not per-download credits
Route can_download/Download_State/permission card through Content_Visibility need_purchase; drop download_credit as a second toll; clarify admin labels for points purchase mode.
2026-07-14 20:38:17 +08:00

242 lines
9.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="aksrt-page-container">
<PageHeader
title="下载/付费内容中心"
desc="统一管理资源下载门槛、积分购买模式开关、角色免扣和人工交付说明。付费解锁走收银台订单,下载 token 不再按次扣费。"
>
<template #actions>
<n-space>
<n-button quaternary @click="go('/permissions/download')">下载权限</n-button>
<n-button quaternary @click="go('/payment/gateway')">收款接口</n-button>
</n-space>
</template>
</PageHeader>
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
<n-card class="aksrt-section-card">
<div class="aksrt-section-header">
<h3>当前联动摘要</h3>
</div>
<n-space>
<n-tag round :type="form.download_login ? 'warning' : 'default'">
登录门槛:{{ form.download_login ? '已启用' : '已关闭' }}
</n-tag>
<n-tag round :type="form.download_credit ? 'warning' : 'default'">
积分购买模式:{{ form.download_credit ? '已启用' : '已关闭' }}
</n-tag>
<n-tag round :type="form.download_captcha ? 'warning' : 'default'">
下载验证码:{{ form.download_captcha ? '已启用' : '已关闭' }}
</n-tag>
<n-tag round type="info">
默认积分价:{{ form.download_default_cost }} {{ currencyName }}
</n-tag>
<n-tag round :type="form.payment_vip_free_download ? 'success' : 'default'">
角色白名单免费:{{ form.payment_vip_free_download ? '已启用' : '未启用' }}
</n-tag>
<n-tag round type="info">
Token TTL:10 分钟
</n-tag>
</n-space>
</n-card>
<n-card class="aksrt-section-card">
<div class="aksrt-section-header">
<h3>下载门槛</h3>
</div>
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
<n-form-item label="下载需要登录">
<n-switch v-model:value="form.download_login" />
</n-form-item>
<n-form-item label="允许积分购买模式">
<n-switch v-model:value="form.download_credit" />
<template #feedback>
<span class="aksrt-hint-text">开启后,文章 pay_modo=积分 可走收银台一次购买解锁;下载 token 不再按次扣积分。关闭时积分模式资源按免费/需登录策略处理。</span>
</template>
</n-form-item>
<n-form-item label="下载启用验证码">
<n-switch v-model:value="form.download_captcha" />
</n-form-item>
<n-form-item :label="`默认积分价(${currencyName})`">
<n-input-number v-model:value="form.download_default_cost" :min="0" :max="100000" style="width: 180px;" />
<template #feedback>
<span class="aksrt-hint-text">仅作运营参考/兼容字段;实际价格以文章 _aksrt_pay 的积分价/现金价为准。角色白名单命中时免费下载。</span>
</template>
</n-form-item>
</n-form>
</n-card>
<n-card class="aksrt-section-card">
<div class="aksrt-section-header">
<h3>付费内容策略</h3>
</div>
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
<n-form-item label="受管内容类型">
<n-input v-model:value="form.payment_paid_post_types" placeholder="post,course,doc" />
<template #feedback>
<span class="aksrt-hint-text">逗号分隔,标记纳入统一付费策略的内容类型。</span>
</template>
</n-form-item>
<n-form-item label="角色白名单免扣下载">
<n-switch v-model:value="form.payment_vip_free_download" />
</n-form-item>
<n-form-item label="免扣角色列表" v-if="form.payment_vip_free_download">
<n-input v-model:value="form.payment_vip_exempt_roles" placeholder="administrator,editor,vip" />
<template #feedback>
<span class="aksrt-hint-text">命中这些 WordPress role 的用户可免费下载(无需购买)。</span>
</template>
</n-form-item>
<n-form-item label="前台付费提示">
<n-input
v-model:value="form.payment_paid_catalog_note"
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
placeholder="可填写资源购买须知、交付说明或人工确认提示。"
/>
</n-form-item>
<n-form-item label="人工交付备注">
<n-input
v-model:value="form.payment_manual_delivery_note"
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
/>
</n-form-item>
</n-form>
</n-card>
<n-card class="aksrt-section-card">
<n-alert type="info" :show-icon="false">
当前收款模式:{{ gatewayModeLabel }}。充值、VIP、返佣和分成页均已提供稳定结构,订单和结算台账直接衔接这些字段。
</n-alert>
</n-card>
<div class="aksrt-form-actions">
<n-button type="primary" :loading="saving" @click="handleSave">保存配置</n-button>
</div>
</n-spin>
</div>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useMessage } from 'naive-ui'
import api from '@/api'
import PageHeader from '@/components/PageHeader.vue'
const router = useRouter()
const message = useMessage()
const loading = ref(true)
const saving = ref(false)
const featureSnapshot = ref({})
const creditSnapshot = ref({})
const overview = ref({})
const form = reactive({
download_login: true,
download_credit: true,
download_captcha: false,
download_default_cost: 0,
payment_paid_post_types: 'post',
payment_vip_free_download: false,
payment_vip_exempt_roles: '',
payment_paid_catalog_note: '',
payment_manual_delivery_note: '付费或人工确认内容请在订单备注中记录交付方式。',
})
const currencyName = computed(() => overview.value.currency_name || creditSnapshot.value.currency_name || '积分')
const gatewayModeLabel = computed(() => {
const mode = overview.value.gateway?.mode || 'manual_review'
if (mode === 'test_gateway') return '测试网关'
if (mode === 'adapter_ready') return '适配器就绪'
return '人工确认'
})
function go(path) {
router.push(path)
}
function extractData(response) {
return response?.data ?? response ?? {}
}
async function load() {
loading.value = true
try {
const [featureRes, creditRes, schemaRes, overviewRes] = await Promise.all([
api.getCmsFeatures(),
api.getCreditSettings(),
api.getSchemaPage('payment', 'paid'),
api.getPaymentOverview(),
])
const featureData = featureRes?.cms_features ?? featureRes?.data?.cms_features ?? {}
const creditData = extractData(creditRes)
const schemaData = extractData(schemaRes)
featureSnapshot.value = featureData
creditSnapshot.value = creditData
overview.value = extractData(overviewRes)
form.download_login = featureData.download_login !== false
form.download_credit = featureData.download_credit !== false
form.download_captcha = !!featureData.download_captcha
form.download_default_cost = Number(creditData.download_default_cost || 0)
form.payment_paid_post_types = schemaData.payment_paid_post_types || 'post'
form.payment_vip_free_download = !!schemaData.payment_vip_free_download
form.payment_vip_exempt_roles = schemaData.payment_vip_exempt_roles || ''
form.payment_paid_catalog_note = schemaData.payment_paid_catalog_note || ''
form.payment_manual_delivery_note = schemaData.payment_manual_delivery_note || '付费或人工确认内容请在订单备注中记录交付方式。'
} catch (error) {
message.error(error.message || '加载下载/付费内容中心失败')
} finally {
loading.value = false
}
}
async function handleSave() {
saving.value = true
try {
const nextFeatures = {
...featureSnapshot.value,
download_login: form.download_login,
download_credit: form.download_credit,
download_captcha: form.download_captcha,
}
const nextCredits = {
...creditSnapshot.value,
download_default_cost: Number(form.download_default_cost || 0),
}
await Promise.all([
api.saveCmsFeatures(nextFeatures),
api.saveCreditSettings(nextCredits),
api.saveSchemaPage('payment', 'paid', {
payment_paid_post_types: form.payment_paid_post_types,
payment_vip_free_download: form.payment_vip_free_download,
payment_vip_exempt_roles: form.payment_vip_exempt_roles,
payment_paid_catalog_note: form.payment_paid_catalog_note,
payment_manual_delivery_note: form.payment_manual_delivery_note,
}),
])
featureSnapshot.value = nextFeatures
creditSnapshot.value = nextCredits
message.success('下载/付费内容中心已保存')
} catch (error) {
message.error(error.message || '保存失败')
} finally {
saving.value = false
}
}
onMounted(load)
</script>