feat(pay): card-pass redeem for VIP and balance (zibll-style)
Rewrite charge-card into typed single-use card_pass inventory with public redeem REST, user-center UI for balance/VIP, admin type-aware create/batch, and CAS mark-used fulfillment via Credits/activate_vip.
This commit is contained in:
@@ -51,21 +51,33 @@
|
||||
|
||||
<n-modal v-model:show="createVisible" preset="card" title="创建充值卡" style="max-width: 520px;">
|
||||
<n-form :model="createForm" label-placement="top">
|
||||
<n-form-item label="卡号">
|
||||
<n-input v-model:value="createForm.code" placeholder="手动填写唯一卡号" />
|
||||
<n-form-item label="卡类型">
|
||||
<n-select v-model:value="createForm.type" :options="typeOptions" />
|
||||
</n-form-item>
|
||||
<n-grid cols="2" :x-gap="12">
|
||||
<n-form-item label="卡密">
|
||||
<n-input v-model:value="createForm.code" 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-form-item>
|
||||
<n-grid v-if="createForm.type === 'vip'" cols="2" :x-gap="12">
|
||||
<n-grid-item>
|
||||
<n-form-item label="面额">
|
||||
<n-input-number v-model:value="createForm.amount" :min="0.01" :precision="2" style="width: 100%;" />
|
||||
<n-form-item label="VIP 等级">
|
||||
<n-select v-model:value="createForm.level" :options="levelOptions" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="过期时间">
|
||||
<n-date-picker v-model:value="createForm.expiresAt" type="datetime" clearable style="width: 100%;" />
|
||||
<n-form-item label="天数(0=永久)">
|
||||
<n-input-number v-model:value="createForm.days" :min="0" :max="3650" style="width: 100%;" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
<n-form-item v-if="createForm.type === 'credits'" label="积分">
|
||||
<n-input-number v-model:value="createForm.points" :min="1" style="width: 100%;" />
|
||||
</n-form-item>
|
||||
<n-form-item label="过期时间">
|
||||
<n-date-picker v-model:value="createForm.expiresAt" type="datetime" clearable style="width: 100%;" />
|
||||
</n-form-item>
|
||||
<n-form-item label="备注">
|
||||
<n-input v-model:value="createForm.note" type="textarea" :rows="3" />
|
||||
</n-form-item>
|
||||
@@ -78,22 +90,42 @@
|
||||
|
||||
<n-modal v-model:show="batchVisible" preset="card" title="批量生成充值卡" style="max-width: 520px;">
|
||||
<n-form :model="batchForm" label-placement="top">
|
||||
<n-form-item label="卡类型">
|
||||
<n-select v-model:value="batchForm.type" :options="typeOptions" />
|
||||
</n-form-item>
|
||||
<n-grid cols="2" :x-gap="12">
|
||||
<n-grid-item>
|
||||
<n-form-item label="数量">
|
||||
<n-input-number v-model:value="batchForm.count" :min="1" :max="100" style="width: 100%;" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<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-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item v-if="batchForm.type === 'credits'">
|
||||
<n-form-item label="积分">
|
||||
<n-input-number v-model:value="batchForm.points" :min="1" style="width: 100%;" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
<n-grid v-if="batchForm.type === 'vip'" cols="2" :x-gap="12">
|
||||
<n-grid-item>
|
||||
<n-form-item label="VIP 等级">
|
||||
<n-select v-model:value="batchForm.level" :options="levelOptions" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="天数(0=永久)">
|
||||
<n-input-number v-model:value="batchForm.days" :min="0" :max="3650" style="width: 100%;" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
<n-grid cols="2" :x-gap="12">
|
||||
<n-grid-item>
|
||||
<n-form-item label="前缀">
|
||||
<n-input v-model:value="batchForm.prefix" placeholder="CARD" />
|
||||
<n-input v-model:value="batchForm.prefix" placeholder="AK" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
@@ -152,21 +184,40 @@ const filters = reactive({
|
||||
})
|
||||
|
||||
const createForm = reactive({
|
||||
type: 'balance',
|
||||
code: '',
|
||||
amount: 10,
|
||||
level: 1,
|
||||
days: 30,
|
||||
points: 100,
|
||||
expiresAt: null,
|
||||
note: '',
|
||||
})
|
||||
|
||||
const batchForm = reactive({
|
||||
type: 'balance',
|
||||
count: 20,
|
||||
amount: 10,
|
||||
prefix: 'CARD',
|
||||
level: 1,
|
||||
days: 30,
|
||||
points: 100,
|
||||
prefix: 'AK',
|
||||
length: 12,
|
||||
expiresAt: null,
|
||||
note: '',
|
||||
})
|
||||
|
||||
const typeOptions = [
|
||||
{ label: '余额卡', value: 'balance' },
|
||||
{ label: '会员卡', value: 'vip' },
|
||||
{ label: '积分卡', value: 'credits' },
|
||||
]
|
||||
|
||||
const levelOptions = [
|
||||
{ label: 'VIP1', value: 1 },
|
||||
{ label: 'VIP2', value: 2 },
|
||||
]
|
||||
|
||||
const pager = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
@@ -214,9 +265,26 @@ const pagination = computed(() => ({
|
||||
|
||||
const columns = [
|
||||
{ title: 'ID', key: 'id', width: 70 },
|
||||
{ title: '卡号', key: 'code', minWidth: 190, ellipsis: { tooltip: true } },
|
||||
{ title: '面额', key: 'amount', width: 90 },
|
||||
{ title: '余额', key: 'balance', width: 90 },
|
||||
{ title: '卡密', key: 'code', minWidth: 170, ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: '类型',
|
||||
key: 'type_label',
|
||||
width: 90,
|
||||
render: (row) => row.type_label || row.type || '-',
|
||||
},
|
||||
{
|
||||
title: '面额/权益',
|
||||
key: 'amount',
|
||||
width: 120,
|
||||
render: (row) => {
|
||||
if (row.type === 'vip') {
|
||||
const days = Number(row.days || 0)
|
||||
return `VIP${row.level || 1} / ${days > 0 ? `${days}天` : '永久'}`
|
||||
}
|
||||
if (row.type === 'credits') return `${row.points || row.amount || 0} 积分`
|
||||
return row.amount
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status_label',
|
||||
@@ -339,8 +407,13 @@ async function handleCreate() {
|
||||
await api.request('/payment/charge-cards', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
type: createForm.type,
|
||||
code: createForm.code,
|
||||
password: createForm.code,
|
||||
amount: createForm.amount,
|
||||
level: createForm.level,
|
||||
days: createForm.days,
|
||||
points: createForm.points,
|
||||
expires_at: createForm.expiresAt ? formatDateValue(createForm.expiresAt) : '',
|
||||
note: createForm.note,
|
||||
},
|
||||
@@ -363,8 +436,12 @@ async function handleBatchCreate() {
|
||||
await api.request('/payment/charge-cards/batch', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
type: batchForm.type,
|
||||
count: batchForm.count,
|
||||
amount: batchForm.amount,
|
||||
level: batchForm.level,
|
||||
days: batchForm.days,
|
||||
points: batchForm.points,
|
||||
prefix: batchForm.prefix,
|
||||
length: batchForm.length,
|
||||
expires_at: batchForm.expiresAt ? formatDateValue(batchForm.expiresAt) : '',
|
||||
|
||||
@@ -71,9 +71,9 @@ export const rechargeSchema = {
|
||||
title: '充值卡免扣',
|
||||
icon: 'fas fa-ticket-alt',
|
||||
fields: [
|
||||
{ key: 'pay_balance_pass_charge_s', label: '启用充值卡免扣', type: 'switch', default: false },
|
||||
{ key: 'pay_balance_pass_charge_desc', label: '免扣说明', type: 'textarea', default: '', dependsOn: { key: 'pay_balance_pass_charge_s', value: true } },
|
||||
{ key: 'pay_balance_pass_charge_only_password', label: '仅密码免扣', type: 'switch', default: false, dependsOn: { key: 'pay_balance_pass_charge_s', value: true } },
|
||||
{ key: 'pay_balance_pass_charge_s', label: '启用余额卡密兑换', type: 'switch', default: false },
|
||||
{ key: 'pay_balance_pass_charge_desc', label: '卡密兑换说明', type: 'textarea', default: '', dependsOn: { key: 'pay_balance_pass_charge_s', value: true } },
|
||||
{ key: 'pay_balance_pass_charge_only_password', label: '仅密码兑换', type: 'switch', default: false, dependsOn: { key: 'pay_balance_pass_charge_s', value: true } },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ export const vipSchema = {
|
||||
pay_download_speed_vip_2: 0,
|
||||
pay_vip_pass_charge_s: false,
|
||||
pay_vip_pass_charge_desc: '',
|
||||
pay_vip_pass_charge_only_password: true,
|
||||
vip_renew: true,
|
||||
vip_renew_desc: '',
|
||||
vip_renew_price_type: 'discount',
|
||||
@@ -61,6 +62,7 @@ export const vipSchema = {
|
||||
{ key: 'pay_download_speed_vip_2', label: '二级会员下载速度(KB/s)', type: 'number', default: 0, min: 0, max: 99999, dependsOn: { key: 'pay_user_vip_2_s', value: true } },
|
||||
{ key: 'pay_vip_pass_charge_s', label: '会员卡密兑换', type: 'switch', default: false },
|
||||
{ key: 'pay_vip_pass_charge_desc', label: '卡密兑换说明', type: 'textarea', default: '', dependsOn: { key: 'pay_vip_pass_charge_s', value: true } },
|
||||
{ key: 'pay_vip_pass_charge_only_password', label: '仅密码兑换', type: 'switch', default: true, dependsOn: { key: 'pay_vip_pass_charge_s', value: true }, help: '开启后只需输入卡密;关闭则需卡号+卡密。' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user