|
|
|
@ -51,16 +51,60 @@ |
|
|
|
<div class="business-panel account-panel" :class="{ active: tab === 'traffic' }" style="padding:18px"> |
|
|
|
<div class="traffic-balance-card"><span>当前云媒体流量余额</span><strong>{{ balance }} <small>GB</small></strong><p>流量用于直播、回放与原始素材下载</p></div> |
|
|
|
<div class="traffic-purchase-layout" style="margin-top:16px"> |
|
|
|
<div class="traffic-amount-block"><label><span>购买流量(GB)</span><div class="traffic-amount-input"><t-input-number v-model="trafficAmount" theme="normal" :min="1" /><b>GB</b></div></label></div> |
|
|
|
<div class="traffic-order-summary"><span>订单概要</span><strong>云媒体流量订单</strong><small>订单创建后由管理员人工确认,金额以服务端订单为准。</small><t-button theme="primary" @click="createOrder">创建订单</t-button></div> |
|
|
|
<div class="traffic-amount-block"> |
|
|
|
<label> |
|
|
|
<span>选择流量套餐</span> |
|
|
|
<t-radio-group |
|
|
|
v-model="selectedTrafficPackageId" |
|
|
|
:disabled="!trafficPackages.length || trafficOrdering" |
|
|
|
> |
|
|
|
<t-radio |
|
|
|
v-for="pkg in trafficPackages" |
|
|
|
:key="pkg.id" |
|
|
|
:value="pkg.id" |
|
|
|
> |
|
|
|
{{ pkg.name }} · {{ (Number(pkg.amountBytes) / 1024 / 1024 / 1024).toFixed(0) }}GB · {{ money(pkg.price) }} |
|
|
|
<small v-if="pkg.validityDays"> / {{ pkg.validityDays }}天</small> |
|
|
|
</t-radio> |
|
|
|
</t-radio-group> |
|
|
|
<small v-if="!trafficPackages.length">暂无可用套餐</small> |
|
|
|
</label> |
|
|
|
</div> |
|
|
|
<div class="traffic-order-summary"> |
|
|
|
<span>订单概要</span> |
|
|
|
<strong>云媒体流量订单</strong> |
|
|
|
<small>创建订单后发起微信支付;金额以服务端订单为准。</small> |
|
|
|
<t-button |
|
|
|
theme="primary" |
|
|
|
:loading="trafficOrdering" |
|
|
|
:disabled="!selectedTrafficPackageId || !trafficPackages.length" |
|
|
|
@click="createTrafficOrderAndPay" |
|
|
|
>购买并支付</t-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="account-subsection-title"><h3>订单记录</h3><span>共 {{ orderTotal }} 条</span></div> |
|
|
|
<t-table row-key="id" :data="orders" :columns="orderColumns" empty="暂无流量订单"> |
|
|
|
<template #content="{ row }">{{ row.amountGb }} GB 流量</template> |
|
|
|
<template #content="{ row }">{{ orderContentLabel(row) }}</template> |
|
|
|
<template #unitPrice="{ row }">{{ money(row.unitPrice) }}</template> |
|
|
|
<template #totalPrice="{ row }"><strong>{{ money(row.totalPrice) }}</strong></template> |
|
|
|
<template #payStatus="{ row }"><em :class="{ processing: row.payStatus === 'unpaid' }">{{ payStatusName(row.payStatus) }}</em></template> |
|
|
|
<template #createdAt="{ row }">{{ fmtTime(row.createdAt) }}</template> |
|
|
|
<template #actions="{ row }"> |
|
|
|
<t-button |
|
|
|
v-if="row.payStatus === 'unpaid'" |
|
|
|
variant="text" |
|
|
|
theme="primary" |
|
|
|
:loading="payingOrderId === row.id" |
|
|
|
@click="payTrafficOrder(row)" |
|
|
|
>去支付</t-button> |
|
|
|
<t-button |
|
|
|
v-if="row.payStatus === 'unpaid'" |
|
|
|
variant="text" |
|
|
|
theme="danger" |
|
|
|
@click="cancelTrafficOrder(row.id)" |
|
|
|
>取消</t-button> |
|
|
|
<span v-else>--</span> |
|
|
|
</template> |
|
|
|
</t-table> |
|
|
|
<div class="table-footer" v-if="orderTotal"><t-pagination v-model:current="orderPageNum" :total="orderTotal" :page-size="10" @current-change="loadOrders" /></div> |
|
|
|
<div class="account-subsection-title"><h3>消费明细</h3><span>共 {{ usageTotal }} 条</span></div> |
|
|
|
@ -206,7 +250,10 @@ const deviceCount = ref('--') |
|
|
|
const deviceLoadFailed = ref(false) |
|
|
|
const resourceSummary = ref(null) |
|
|
|
const downloads = ref([]); const downloadTotal = ref(0) |
|
|
|
const trafficAmount = ref(1) |
|
|
|
const trafficPackages = ref([]) |
|
|
|
const selectedTrafficPackageId = ref(0) |
|
|
|
const trafficOrdering = ref(false) |
|
|
|
const payingOrderId = ref(0) |
|
|
|
const sessions = ref([]) |
|
|
|
const invoiceProfiles = ref([]); const invoiceOrders = ref([]); const invoiceRequests = ref([]); const invoiceOrderId = ref(0) |
|
|
|
const invoiceForm = ref({ title: '', taxpayerNumber: '', email: '', isDefault: true }) |
|
|
|
@ -223,7 +270,8 @@ const orderColumns = [ |
|
|
|
{ colKey: 'unitPrice', title: '单价' }, |
|
|
|
{ colKey: 'totalPrice', title: '金额' }, |
|
|
|
{ colKey: 'payStatus', title: '状态' }, |
|
|
|
{ colKey: 'createdAt', title: '时间' } |
|
|
|
{ colKey: 'createdAt', title: '时间' }, |
|
|
|
{ colKey: 'actions', title: '操作' } |
|
|
|
] |
|
|
|
const usageColumns = [ |
|
|
|
{ colKey: 'sourceType', title: '来源' }, |
|
|
|
@ -291,8 +339,31 @@ function applyPage(targetRef, page) { |
|
|
|
function fmtTime(value) { const date = new Date(value); return value && !Number.isNaN(date.getTime()) ? date.toLocaleString('zh-CN', { hour12: false }) : '--' } |
|
|
|
function fmtGb(bytes) { return bytes == null ? '--' : `${(Number(bytes) / 1024 / 1024 / 1024).toFixed(2)} GB` } |
|
|
|
function money(value) { return value == null ? '--' : `¥${Number(value).toFixed(2)}` } |
|
|
|
function moneyFromFen(fen) { return fen == null ? '--' : `¥${(Number(fen) / 100).toFixed(2)}` } |
|
|
|
function sourceName(type) { return ({ live: '直播', replay: '回放', download: '下载' })[type] || type || '--' } |
|
|
|
function payStatusName(status) { return ({ unpaid: '待人工确认', paid: '已入账' })[status] || status || '--' } |
|
|
|
function payStatusName(status) { |
|
|
|
return ({ |
|
|
|
unpaid: '待支付', |
|
|
|
paid: '已支付', |
|
|
|
cancelled: '已取消', |
|
|
|
closed: '已关闭' |
|
|
|
})[status] || status || '--' |
|
|
|
} |
|
|
|
function describePayment(payment) { |
|
|
|
if (!payment) return '支付已创建' |
|
|
|
const amount = moneyFromFen(payment.totalFeeFen) |
|
|
|
if (payment.providerPayload) return `支付已创建(${amount}),请按返回凭证完成支付` |
|
|
|
return `支付已创建(${amount}),状态:${payment.status || '--'}` |
|
|
|
} |
|
|
|
async function createPayment(businessType, businessOrderId) { |
|
|
|
return request.post(urls.PAYMENTS, { businessType, businessOrderId }) |
|
|
|
} |
|
|
|
function orderContentLabel(row) { |
|
|
|
if (row?.amountGb != null) return `${row.amountGb} GB 流量` |
|
|
|
if (row?.packageName) return row.packageName |
|
|
|
if (row?.amountBytes != null) return fmtGb(row.amountBytes) |
|
|
|
return '--' |
|
|
|
} |
|
|
|
function rechargeStatusName(status) { return ({ pending: '处理中', success: '充值成功', failed: '充值失败' })[status] || status || '--' } |
|
|
|
function invoiceStatusName(status) { return ({ pending: '待人工处理', issued: '已开具', rejected: '已驳回', cancelled: '已取消' })[status] || status || '--' } |
|
|
|
function maskPhone(phone) { const value = String(phone || ''); return value.length > 7 ? `${value.slice(0, 3)}****${value.slice(-4)}` : value || '--' } |
|
|
|
@ -311,6 +382,17 @@ async function loadOrders() { try { const page = await request.get(urls.TRAFFIC_ |
|
|
|
async function loadUsage() { try { const page = await request.get(urls.TRAFFIC_USAGE, { params: { pageNum: usagePageNum.value, pageSize: 10 } }); const meta = applyPage(usage, page); usageTotal.value = meta.total; usagePages.value = meta.pages } catch { usage.value = [] } } |
|
|
|
async function loadSim() { const [cards, logs] = await Promise.all([request.get(urls.SIM_CARDS, { params: { pageNum: simPageNum.value, pageSize: 10 } }), request.get(urls.SIM_RECHARGE_LOGS, { params: { pageNum: simRechargePageNum.value, pageSize: 10 } })]); const cardMeta = applyPage(simCards, cards); const logMeta = applyPage(simRechargeLogs, logs); simTotal.value = cardMeta.total; simPages.value = cardMeta.pages; simRechargeTotal.value = logMeta.total; simRechargePages.value = logMeta.pages } |
|
|
|
async function loadDeviceCount() { try { await deviceStore.load(); deviceCount.value = deviceStore.docks.length + deviceStore.drones.length } catch { deviceLoadFailed.value = true } } |
|
|
|
async function loadTrafficPackages() { |
|
|
|
try { |
|
|
|
const list = await request.get(urls.TRAFFIC_PACKAGES) |
|
|
|
trafficPackages.value = Array.isArray(list) ? list : [] |
|
|
|
if (!selectedTrafficPackageId.value && trafficPackages.value.length) { |
|
|
|
selectedTrafficPackageId.value = trafficPackages.value[0].id |
|
|
|
} |
|
|
|
} catch { |
|
|
|
trafficPackages.value = [] |
|
|
|
} |
|
|
|
} |
|
|
|
async function load() { |
|
|
|
const results = await Promise.allSettled([ |
|
|
|
loadProfile(), |
|
|
|
@ -319,11 +401,56 @@ async function load() { |
|
|
|
loadUsage(), |
|
|
|
loadSim(), |
|
|
|
loadDeviceCount(), |
|
|
|
loadAccountExtensions() |
|
|
|
loadAccountExtensions(), |
|
|
|
loadTrafficPackages() |
|
|
|
]) |
|
|
|
if (results.some((result) => result.status === 'rejected')) ui.toast('部分账户数据加载失败') |
|
|
|
} |
|
|
|
async function createOrder() { try { const order = await request.post(urls.TRAFFIC_ORDERS, { amountGb: Number(trafficAmount.value) || 1 }); ui.toast(`订单已创建,金额 ${money(order.totalPrice)},请等待管理员确认`); orderPageNum.value = 1; await loadOrders() } catch (error) { ui.toast(error.message || '创建订单失败') } } |
|
|
|
async function createTrafficOrderAndPay() { |
|
|
|
if (!selectedTrafficPackageId.value) { |
|
|
|
ui.toast('请选择流量套餐') |
|
|
|
return |
|
|
|
} |
|
|
|
trafficOrdering.value = true |
|
|
|
try { |
|
|
|
const order = await request.post(urls.TRAFFIC_ORDERS, { |
|
|
|
packageId: Number(selectedTrafficPackageId.value) |
|
|
|
}) |
|
|
|
const payment = await createPayment('traffic_order', order.id) |
|
|
|
ui.toast(describePayment(payment)) |
|
|
|
orderPageNum.value = 1 |
|
|
|
await Promise.all([loadOrders(), request.get(urls.TRAFFIC_BALANCE).then((data) => { |
|
|
|
balance.value = data?.balanceGb == null ? '--' : Number(data.balanceGb).toFixed(1) |
|
|
|
})]) |
|
|
|
} catch (error) { |
|
|
|
ui.toast(error.message || '购买失败') |
|
|
|
} finally { |
|
|
|
trafficOrdering.value = false |
|
|
|
} |
|
|
|
} |
|
|
|
async function payTrafficOrder(row) { |
|
|
|
payingOrderId.value = row.id |
|
|
|
try { |
|
|
|
const payment = await createPayment('traffic_order', row.id) |
|
|
|
ui.toast(describePayment(payment)) |
|
|
|
await loadOrders() |
|
|
|
} catch (error) { |
|
|
|
ui.toast(error.message || '发起支付失败') |
|
|
|
} finally { |
|
|
|
payingOrderId.value = 0 |
|
|
|
} |
|
|
|
} |
|
|
|
async function cancelTrafficOrder(id) { |
|
|
|
const ok = await ui.confirm('取消该流量订单?', '仅未支付订单可取消。', ['订单将关闭', '可重新选择套餐下单']) |
|
|
|
if (!ok) return |
|
|
|
try { |
|
|
|
await request.delete(urls.TRAFFIC_ORDER(id)) |
|
|
|
ui.toast('订单已取消') |
|
|
|
await loadOrders() |
|
|
|
} catch (error) { |
|
|
|
ui.toast(error.message || '取消失败') |
|
|
|
} |
|
|
|
} |
|
|
|
async function saveProfile() { saving.value = true; try { const updated = await request.put(urls.ACCOUNT_PROFILE, editForm.value); profile.value = updated; userStore.user = { ...(userStore.user || {}), ...updated }; localStorage.setItem('user', JSON.stringify(userStore.user)); editOpen.value = false; ui.toast('账户资料已保存') } catch (error) { ui.toast(error.message || '保存账户资料失败') } finally { saving.value = false } } |
|
|
|
onMounted(load) |
|
|
|
</script> |
|
|
|
|