feat(referrals): 添加邀请返利和注册确认功能

This commit is contained in:
Entropy.Xu
2026-05-16 17:41:52 +08:00
parent 328ac721ce
commit 973eb1a614
56 changed files with 6246 additions and 52 deletions
@@ -262,6 +262,203 @@
</p>
</div>
</div>
<div class="md:col-span-2 grid grid-cols-1 md:grid-cols-2 gap-4 border-t pt-5">
<div class="flex items-center h-full">
<div class="flex items-center space-x-2">
<Checkbox
id="referral-enabled"
:checked="referralEnabled"
@update:checked="$emit('update:referralEnabled', $event)"
/>
<div>
<Label
for="referral-enabled"
class="cursor-pointer"
>
邀请返利
</Label>
<p class="text-xs text-muted-foreground">
开启后可按充值比例、人头或两者同时发放赠款返利
</p>
</div>
</div>
</div>
<div>
<Label
for="referral-reward-mode"
class="block text-sm font-medium mb-2"
>
返利方式
</Label>
<Select
:model-value="referralRewardMode"
@update:model-value="$emit('update:referralRewardMode', $event)"
>
<SelectTrigger id="referral-reward-mode">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="percent">
按充值比例
</SelectItem>
<SelectItem value="headcount">
按邀请人头
</SelectItem>
<SelectItem value="both">
两者同时启用
</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label
for="referral-recharge-percent"
class="block text-sm font-medium"
>
充值返利比例 (%)
</Label>
<Input
id="referral-recharge-percent"
:model-value="referralRechargePercent"
type="number"
min="0"
step="0.01"
class="mt-1"
@update:model-value="$emit('update:referralRechargePercent', Number($event))"
/>
</div>
<div>
<Label
for="referral-headcount-amount"
class="block text-sm font-medium"
>
人头返利金额 (美元)
</Label>
<Input
id="referral-headcount-amount"
:model-value="referralHeadcountAmountUsd"
type="number"
min="0"
step="0.01"
class="mt-1"
@update:model-value="$emit('update:referralHeadcountAmountUsd', Number($event))"
/>
</div>
<div>
<Label
for="referral-headcount-trigger"
class="block text-sm font-medium mb-2"
>
人头返利触发时机
</Label>
<Select
:model-value="referralHeadcountTrigger"
@update:model-value="$emit('update:referralHeadcountTrigger', $event)"
>
<SelectTrigger id="referral-headcount-trigger">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="registration">
注册成功
</SelectItem>
<SelectItem value="email_verified">
邮箱验证完成
</SelectItem>
<SelectItem value="first_paid_order">
首笔真实支付完成
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div class="md:col-span-2 grid grid-cols-1 md:grid-cols-2 gap-4 border-t pt-5">
<div class="flex items-center h-full">
<div class="flex items-center space-x-2">
<Checkbox
id="privacy-policy-enabled"
:checked="registrationPrivacyPolicyEnabled"
@update:checked="$emit('update:registrationPrivacyPolicyEnabled', $event)"
/>
<div>
<Label
for="privacy-policy-enabled"
class="cursor-pointer"
>
注册隐私政策确认
</Label>
<p class="text-xs text-muted-foreground">
开启后注册时必须确认当前版本
</p>
</div>
</div>
</div>
<div>
<Label
for="privacy-policy-version"
class="block text-sm font-medium"
>
隐私政策版本
</Label>
<Input
id="privacy-policy-version"
:model-value="registrationPrivacyPolicyVersion"
type="text"
placeholder="2026-05-16"
class="mt-1"
@update:model-value="$emit('update:registrationPrivacyPolicyVersion', String($event || '').trim())"
/>
</div>
<div>
<Label
for="privacy-policy-format"
class="block text-sm font-medium mb-2"
>
隐私政策格式
</Label>
<Select
:model-value="registrationPrivacyPolicyFormat"
@update:model-value="$emit('update:registrationPrivacyPolicyFormat', $event)"
>
<SelectTrigger id="privacy-policy-format">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="markdown">
Markdown
</SelectItem>
<SelectItem value="html">
HTML
</SelectItem>
</SelectContent>
</Select>
</div>
<div class="md:col-span-2">
<Label
for="privacy-policy-content"
class="block text-sm font-medium"
>
隐私政策内容
</Label>
<Textarea
id="privacy-policy-content"
:model-value="registrationPrivacyPolicyContent"
rows="8"
class="mt-1"
placeholder="填写 Markdown 或 HTML 内容"
@update:model-value="$emit('update:registrationPrivacyPolicyContent', $event)"
/>
</div>
</div>
</div>
</CardSection>
</template>
@@ -270,6 +467,7 @@
import Button from '@/components/ui/button.vue'
import Input from '@/components/ui/input.vue'
import Label from '@/components/ui/label.vue'
import Textarea from '@/components/ui/textarea.vue'
import Checkbox from '@/components/ui/checkbox.vue'
import Select from '@/components/ui/select.vue'
import SelectTrigger from '@/components/ui/select-trigger.vue'
@@ -288,6 +486,15 @@ defineProps<{
turnstileSecretKey: string
turnstileSecretConfigured: boolean
turnstileAllowedHostnamesStr: string
referralEnabled: boolean
referralRewardMode: string
referralRechargePercent: number
referralHeadcountAmountUsd: number
referralHeadcountTrigger: string
registrationPrivacyPolicyEnabled: boolean
registrationPrivacyPolicyFormat: string
registrationPrivacyPolicyContent: string
registrationPrivacyPolicyVersion: string
autoDeleteExpiredKeys: boolean
enableFormatConversion: boolean
enableOpenaiImageSyncHeartbeat: boolean
@@ -306,6 +513,15 @@ defineEmits<{
'update:turnstileSecretKey': [value: string]
'update:turnstileAllowedHostnamesStr': [value: string]
clearTurnstileSecret: []
'update:referralEnabled': [value: boolean]
'update:referralRewardMode': [value: string]
'update:referralRechargePercent': [value: number]
'update:referralHeadcountAmountUsd': [value: number]
'update:referralHeadcountTrigger': [value: string]
'update:registrationPrivacyPolicyEnabled': [value: boolean]
'update:registrationPrivacyPolicyFormat': [value: string]
'update:registrationPrivacyPolicyContent': [value: string]
'update:registrationPrivacyPolicyVersion': [value: string]
'update:autoDeleteExpiredKeys': [value: boolean]
'update:enableFormatConversion': [value: boolean]
'update:enableOpenaiImageSyncHeartbeat': [value: boolean]
@@ -20,6 +20,15 @@ export interface SystemConfig {
turnstile_secret_key: string
turnstile_secret_key_is_set: boolean
turnstile_allowed_hostnames: string[]
referral_enabled: boolean
referral_reward_mode: string
referral_recharge_percent: number
referral_headcount_amount_usd: number
referral_headcount_trigger: string
registration_privacy_policy_enabled: boolean
registration_privacy_policy_format: string
registration_privacy_policy_content: string
registration_privacy_policy_version: string
// 独立余额 Key 过期管理
auto_delete_expired_keys: boolean
// 格式转换
@@ -65,6 +74,15 @@ const CONFIG_KEYS = [
'turnstile_site_key',
'turnstile_secret_key',
'turnstile_allowed_hostnames',
'referral_enabled',
'referral_reward_mode',
'referral_recharge_percent',
'referral_headcount_amount_usd',
'referral_headcount_trigger',
'registration_privacy_policy_enabled',
'registration_privacy_policy_format',
'registration_privacy_policy_content',
'registration_privacy_policy_version',
// 独立余额 Key 过期管理
'auto_delete_expired_keys',
// 格式转换
@@ -112,6 +130,15 @@ function createDefaultConfig(): SystemConfig {
turnstile_secret_key: '',
turnstile_secret_key_is_set: false,
turnstile_allowed_hostnames: [],
referral_enabled: false,
referral_reward_mode: 'percent',
referral_recharge_percent: 5,
referral_headcount_amount_usd: 0,
referral_headcount_trigger: 'registration',
registration_privacy_policy_enabled: false,
registration_privacy_policy_format: 'markdown',
registration_privacy_policy_content: '',
registration_privacy_policy_version: '1',
// 独立余额 Key 过期管理
auto_delete_expired_keys: false,
// 格式转换
@@ -184,6 +211,19 @@ export function useSystemConfig() {
systemConfig.value.turnstile_secret_key.trim() !== '' ||
JSON.stringify(systemConfig.value.turnstile_allowed_hostnames) !==
JSON.stringify(originalConfig.value.turnstile_allowed_hostnames) ||
systemConfig.value.referral_enabled !== originalConfig.value.referral_enabled ||
systemConfig.value.referral_reward_mode !== originalConfig.value.referral_reward_mode ||
systemConfig.value.referral_recharge_percent !== originalConfig.value.referral_recharge_percent ||
systemConfig.value.referral_headcount_amount_usd !== originalConfig.value.referral_headcount_amount_usd ||
systemConfig.value.referral_headcount_trigger !== originalConfig.value.referral_headcount_trigger ||
systemConfig.value.registration_privacy_policy_enabled !==
originalConfig.value.registration_privacy_policy_enabled ||
systemConfig.value.registration_privacy_policy_format !==
originalConfig.value.registration_privacy_policy_format ||
systemConfig.value.registration_privacy_policy_content !==
originalConfig.value.registration_privacy_policy_content ||
systemConfig.value.registration_privacy_policy_version !==
originalConfig.value.registration_privacy_policy_version ||
systemConfig.value.auto_delete_expired_keys !== originalConfig.value.auto_delete_expired_keys ||
systemConfig.value.enable_format_conversion !== originalConfig.value.enable_format_conversion ||
systemConfig.value.enable_openai_image_sync_heartbeat !== originalConfig.value.enable_openai_image_sync_heartbeat
@@ -386,6 +426,51 @@ export function useSystemConfig() {
value: systemConfig.value.turnstile_allowed_hostnames,
description: 'Cloudflare Turnstile 允许的 hostname 列表',
},
{
key: 'referral_enabled',
value: systemConfig.value.referral_enabled,
description: '邀请返利开关',
},
{
key: 'referral_reward_mode',
value: systemConfig.value.referral_reward_mode,
description: '邀请返利方式',
},
{
key: 'referral_recharge_percent',
value: systemConfig.value.referral_recharge_percent,
description: '邀请充值比例返利百分比',
},
{
key: 'referral_headcount_amount_usd',
value: systemConfig.value.referral_headcount_amount_usd,
description: '邀请人头返利金额(美元)',
},
{
key: 'referral_headcount_trigger',
value: systemConfig.value.referral_headcount_trigger,
description: '邀请人头返利触发时机',
},
{
key: 'registration_privacy_policy_enabled',
value: systemConfig.value.registration_privacy_policy_enabled,
description: '注册隐私政策确认开关',
},
{
key: 'registration_privacy_policy_format',
value: systemConfig.value.registration_privacy_policy_format,
description: '注册隐私政策内容格式',
},
{
key: 'registration_privacy_policy_content',
value: systemConfig.value.registration_privacy_policy_content,
description: '注册隐私政策内容',
},
{
key: 'registration_privacy_policy_version',
value: systemConfig.value.registration_privacy_policy_version,
description: '注册隐私政策版本',
},
{
key: 'auto_delete_expired_keys',
value: systemConfig.value.auto_delete_expired_keys,
@@ -426,6 +511,21 @@ export function useSystemConfig() {
originalConfig.value.turnstile_allowed_hostnames = [
...systemConfig.value.turnstile_allowed_hostnames,
]
originalConfig.value.referral_enabled = systemConfig.value.referral_enabled
originalConfig.value.referral_reward_mode = systemConfig.value.referral_reward_mode
originalConfig.value.referral_recharge_percent = systemConfig.value.referral_recharge_percent
originalConfig.value.referral_headcount_amount_usd =
systemConfig.value.referral_headcount_amount_usd
originalConfig.value.referral_headcount_trigger =
systemConfig.value.referral_headcount_trigger
originalConfig.value.registration_privacy_policy_enabled =
systemConfig.value.registration_privacy_policy_enabled
originalConfig.value.registration_privacy_policy_format =
systemConfig.value.registration_privacy_policy_format
originalConfig.value.registration_privacy_policy_content =
systemConfig.value.registration_privacy_policy_content
originalConfig.value.registration_privacy_policy_version =
systemConfig.value.registration_privacy_policy_version
if (turnstileSecret) {
systemConfig.value.turnstile_secret_key = ''
systemConfig.value.turnstile_secret_key_is_set = true