feat(finance): manage token plans and simplify recharge checkout
This commit is contained in:
@@ -96,6 +96,8 @@ export const uploadAvatarPhoto = (id, formData) => request.post(`/avatars/${id}/
|
||||
|
||||
// Finance (数字分身积分订单)
|
||||
export const getFinanceSummary = () => request.get('/finance/summary')
|
||||
export const getFinancePlans = () => request.get('/finance/plans')
|
||||
export const updateFinancePlan = (id, data) => request.patch(`/finance/plans/${id}`, data)
|
||||
export const getFinanceOrders = (params) => request.get('/finance/orders', { params })
|
||||
export const updateFinanceOrderStatus = (orderNo, data) => request.patch(`/finance/orders/${orderNo}/status`, data)
|
||||
export const requestFinanceRefund = (orderNo, data) => request.post(`/finance/orders/${orderNo}/refund`, data)
|
||||
|
||||
@@ -16,6 +16,16 @@
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="finance-tabs" @tab-change="loadActive">
|
||||
<el-tab-pane label="充值套餐" name="plans">
|
||||
<el-table :data="plans" v-loading="loading" class="data-table">
|
||||
<el-table-column label="套餐名称" prop="name" min-width="150" />
|
||||
<el-table-column label="积分" min-width="160"><template #default="{ row }">{{ Number(row.amount).toLocaleString() }}</template></el-table-column>
|
||||
<el-table-column label="价格" width="140"><template #default="{ row }">¥{{ money(row.price) }}</template></el-table-column>
|
||||
<el-table-column label="标签" prop="badge" min-width="120" />
|
||||
<el-table-column label="说明" prop="desc" min-width="180" />
|
||||
<el-table-column label="操作" width="100"><template #default="{ row }"><el-button link type="primary" @click="editPlan(row)">调整</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="支付订单" name="orders">
|
||||
<div class="filter-bar">
|
||||
<el-input v-model="filters.keyword" placeholder="订单号、昵称或手机号" clearable style="width:240px" @keyup.enter="loadOrders" />
|
||||
@@ -23,13 +33,13 @@
|
||||
<el-option v-for="item in orderStatuses" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<el-select v-model="filters.provider" placeholder="支付渠道" clearable style="width:150px" @change="loadOrders">
|
||||
<el-option label="会会支付" value="huihui" /><el-option label="微信虚拟支付" value="wechat_virtual" />
|
||||
<el-option label="会会支付" value="huihui" /><el-option label="会会组织支付" value="huihui_org" /><el-option label="微信虚拟支付" value="wechat_virtual" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadOrders">查询</el-button>
|
||||
</div>
|
||||
<el-table :data="orders" v-loading="loading" class="data-table">
|
||||
<el-table-column label="订单 / 用户" min-width="230">
|
||||
<template #default="{ row }"><b>{{ row.order_no }}</b><div class="muted">{{ row.user_nickname || '会会用户' }} · {{ row.user_phone || '--' }}</div></template>
|
||||
<el-table-column label="订单 / 用户" min-width="260">
|
||||
<template #default="{ row }"><b>{{ row.order_no }}</b><div class="muted">支付编号:{{ row.provider_order_no || '--' }}</div><div class="muted">{{ row.user_nickname || '会会用户' }} · {{ row.user_phone || '--' }}</div></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="套餐积分" width="130"><template #default="{ row }">{{ Number(row.points_amount || 0).toLocaleString() }}</template></el-table-column>
|
||||
<el-table-column label="金额" width="100"><template #default="{ row }">¥{{ money(row.price) }}</template></el-table-column>
|
||||
@@ -87,25 +97,37 @@
|
||||
</el-form>
|
||||
<template #footer><el-button @click="invoiceDialog=false">取消</el-button><el-button type="primary" :loading="saving" @click="issueInvoice">确认开具</el-button></template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="planDialog" title="调整充值套餐" width="480px">
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="套餐名称"><el-input v-model="planForm.name" maxlength="80" /></el-form-item>
|
||||
<el-form-item label="积分数量"><el-input-number v-model="planForm.amount" :min="1" :step="10000" /></el-form-item>
|
||||
<el-form-item label="套餐价格"><el-input-number v-model="planForm.price" :min="0" :precision="2" :step="1" /></el-form-item>
|
||||
<el-form-item label="标签"><el-input v-model="planForm.badge" maxlength="80" /></el-form-item>
|
||||
<el-form-item label="说明"><el-input v-model="planForm.desc" maxlength="200" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="planDialog=false">取消</el-button><el-button type="primary" :loading="saving" @click="savePlan">保存</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { confirmFinanceRefund, getFinanceInvoices, getFinanceOrders, getFinanceRefunds, getFinanceSummary, requestFinanceRefund, updateFinanceInvoice, updateFinanceOrderStatus } from '@/api'
|
||||
import { confirmFinanceRefund, getFinanceInvoices, getFinanceOrders, getFinancePlans, getFinanceRefunds, getFinanceSummary, requestFinanceRefund, updateFinanceInvoice, updateFinanceOrderStatus, updateFinancePlan } from '@/api'
|
||||
|
||||
const activeTab = ref('orders')
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const summary = ref({ paid_revenue: 0, paid_orders: 0, pending_orders: 0, processing_refunds: 0, pending_invoices: 0 })
|
||||
const orders = ref([]), refunds = ref([]), invoices = ref([])
|
||||
const plans = ref([]), planDialog = ref(false), currentPlan = ref(null)
|
||||
const planForm = ref({ name: '', amount: 1, price: 0, badge: '', desc: '' })
|
||||
const orderPage = ref(1), orderTotal = ref(0)
|
||||
const filters = ref({ keyword: '', status: '', provider: '' })
|
||||
const invoiceDialog = ref(false), currentInvoice = ref(null)
|
||||
const invoiceForm = ref({ invoiceNo: '', invoiceUrl: '', remark: '' })
|
||||
const orderStatuses = [{ label: '待支付', value: 'pending' }, { label: '已支付', value: 'paid' }, { label: '失败', value: 'failed' }, { label: '已关闭', value: 'closed' }, { label: '已退款', value: 'refunded' }]
|
||||
const providerLabel = { huihui: '会会支付', wechat_virtual: '微信虚拟支付' }
|
||||
const providerLabel = { huihui: '会会支付', huihui_org: '会会组织支付', wechat_virtual: '微信虚拟支付' }
|
||||
const statusLabel = { pending: '待处理', processing: '处理中', paid: '已支付', succeeded: '已成功', failed: '失败', closed: '已关闭', refunded: '已退款' }
|
||||
const invoiceStatusLabel = { pending: '待开具', issued: '已开具', rejected: '已驳回' }
|
||||
const money = value => Number(value || 0).toFixed(2)
|
||||
@@ -113,11 +135,14 @@ const dateTime = value => value ? new Date(value).toLocaleString('zh-CN', { hour
|
||||
const tagType = value => ({ paid: 'success', succeeded: 'success', issued: 'success', pending: 'warning', processing: 'warning', failed: 'danger', rejected: 'danger', refunded: 'info', closed: 'info' }[value] || 'info')
|
||||
|
||||
async function loadSummary() { const res = await getFinanceSummary(); summary.value = res.data || summary.value }
|
||||
async function loadPlans() { const res = await getFinancePlans(); plans.value = res.data || [] }
|
||||
async function loadOrders() { const res = await getFinanceOrders({ page: orderPage.value, page_size: 20, ...filters.value }); orders.value = res.data?.items || []; orderTotal.value = res.data?.total || 0 }
|
||||
async function loadRefunds() { const res = await getFinanceRefunds({ page: 1, page_size: 100 }); refunds.value = res.data?.items || [] }
|
||||
async function loadInvoices() { const res = await getFinanceInvoices({ page: 1, page_size: 100 }); invoices.value = res.data?.items || [] }
|
||||
async function loadActive() { loading.value = true; try { if (activeTab.value === 'orders') await loadOrders(); if (activeTab.value === 'refunds') await loadRefunds(); if (activeTab.value === 'invoices') await loadInvoices() } finally { loading.value = false } }
|
||||
async function loadAll() { loading.value = true; try { await Promise.all([loadSummary(), loadOrders(), loadRefunds(), loadInvoices()]) } finally { loading.value = false } }
|
||||
async function loadActive() { loading.value = true; try { if (activeTab.value === 'plans') await loadPlans(); if (activeTab.value === 'orders') await loadOrders(); if (activeTab.value === 'refunds') await loadRefunds(); if (activeTab.value === 'invoices') await loadInvoices() } finally { loading.value = false } }
|
||||
async function loadAll() { loading.value = true; try { await Promise.all([loadSummary(), loadPlans(), loadOrders(), loadRefunds(), loadInvoices()]) } finally { loading.value = false } }
|
||||
function editPlan(row) { currentPlan.value = row; planForm.value = { name: row.name, amount: Number(row.amount), price: Number(row.price), badge: row.badge || '', desc: row.desc || '' }; planDialog.value = true }
|
||||
async function savePlan() { if (!currentPlan.value) return; saving.value = true; try { await updateFinancePlan(currentPlan.value.id, planForm.value); ElMessage.success('充值套餐已更新'); planDialog.value = false; await loadPlans() } finally { saving.value = false } }
|
||||
|
||||
async function refund(row) {
|
||||
try { const { value } = await ElMessageBox.prompt('退款成功后会扣回本订单发放的全部积分。', `退款 ¥${money(row.price)}`, { inputPlaceholder: '请输入退款原因', inputValidator: value => value?.trim() ? true : '退款原因不能为空', confirmButtonText: '提交退款', cancelButtonText: '取消' }); await requestFinanceRefund(row.order_no, { reason: value, operator: '后台管理员' }); ElMessage.success('退款已提交渠道处理'); await loadAll() } catch (error) { if (error !== 'cancel' && error !== 'close') console.error(error) }
|
||||
|
||||
Reference in New Issue
Block a user