feat(web): 支付记录双 Tab 与平台选用弹层
订阅/充值分形态表单(秒级时间、金额非负、额度换算预览); 选用弹层已登记灰显、每次打开重拉、关弹窗清密码。
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* 行展开 = 模型调用详情;来源徽章 = 订阅支付参数弹窗(R12)
|
||||
*/
|
||||
import { computed, h, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import type { LogItem, PaymentItem } from "@new-api-key-usage/core";
|
||||
import type { LogItem, SubscriptionItem } from "@new-api-key-usage/core";
|
||||
import { Tooltip } from "antdv-next";
|
||||
import { QuestionCircleOutlined } from "@antdv-next/icons";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
@@ -28,7 +28,9 @@ const page = ref(1);
|
||||
const pageSize = ref(50);
|
||||
|
||||
const overviewQuery = useMemberOverview(() => range.value);
|
||||
const payments = computed(() => overviewQuery.data.value?.payments ?? []);
|
||||
const payments = computed(
|
||||
() => overviewQuery.data.value?.subscriptions ?? [],
|
||||
);
|
||||
const keys = computed(() => overviewQuery.data.value?.keys ?? []);
|
||||
|
||||
const params = computed(() => ({
|
||||
@@ -51,9 +53,9 @@ const keyOptions = computed(() => [
|
||||
]);
|
||||
|
||||
const paymentBySub = computed(() => {
|
||||
const m = new Map<number, PaymentItem>();
|
||||
const m = new Map<number, SubscriptionItem>();
|
||||
for (const p of payments.value) {
|
||||
if (p.subscription_id != null) m.set(p.subscription_id, p);
|
||||
m.set(p.subscription_id, p);
|
||||
}
|
||||
return m;
|
||||
});
|
||||
@@ -115,8 +117,7 @@ function onBadge(record: LogItem, e: MouseEvent) {
|
||||
payModalOpen.value = true;
|
||||
}
|
||||
|
||||
function periodText(p: PaymentItem): string {
|
||||
if (p.kind !== "subscription") return formatDateTime(p.paid_at);
|
||||
function periodText(p: SubscriptionItem): string {
|
||||
const end = p.period_end == null ? "进行中" : formatDateTime(p.period_end);
|
||||
return `${formatDateTime(p.period_start)} ~ ${end}`;
|
||||
}
|
||||
|
||||
@@ -20,15 +20,18 @@ const overviewQuery = useMemberOverview(() => range.value);
|
||||
const usage = computed(() => overviewQuery.data.value?.usage);
|
||||
const bySource = computed(() => overviewQuery.data.value?.by_source ?? []);
|
||||
const keys = computed(() => overviewQuery.data.value?.keys ?? []);
|
||||
const payments = computed(() => overviewQuery.data.value?.payments ?? []);
|
||||
const payments = computed(
|
||||
() => overviewQuery.data.value?.subscriptions ?? [],
|
||||
);
|
||||
|
||||
const extraPresets = computed(() =>
|
||||
payments.value
|
||||
.filter((p) => p.kind === "subscription" && p.period_start != null)
|
||||
.map((p) => ({
|
||||
label: `订阅 #${p.subscription_id ?? p.id} 期间`,
|
||||
label: `订阅 #${p.subscription_id} 期间`,
|
||||
from: new Date((p.period_start ?? 0) * 1000).toISOString(),
|
||||
to: new Date((p.period_end ?? Math.floor(Date.now() / 1000)) * 1000).toISOString(),
|
||||
to: new Date(
|
||||
(p.period_end ?? Math.floor(Date.now() / 1000)) * 1000,
|
||||
).toISOString(),
|
||||
}))
|
||||
.slice(0, 4),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 成员支付参数(交互设计-成员视图 §5):完全公开的登记参数表
|
||||
* 成员支付参数(交互设计-成员视图 §5):订阅 + 充值清单
|
||||
*/
|
||||
import { computed } from "vue";
|
||||
import PageHeader from "@/components/common/page-header.vue";
|
||||
@@ -13,31 +13,42 @@ import { formatPaidAmount } from "@/utils/format.ts";
|
||||
|
||||
const { range, onRangeChange } = useMemberRange();
|
||||
const overviewQuery = useMemberOverview(() => range.value);
|
||||
const payments = computed(() => overviewQuery.data.value?.payments ?? []);
|
||||
const subs = computed(() => overviewQuery.data.value?.subscriptions ?? []);
|
||||
const tops = computed(() => overviewQuery.data.value?.topups ?? []);
|
||||
|
||||
const extraPresets = computed(() =>
|
||||
payments.value
|
||||
.filter((p) => p.kind === "subscription" && p.period_start != null)
|
||||
subs.value
|
||||
.map((p) => ({
|
||||
label: `订阅 #${p.subscription_id ?? p.id} 期间`,
|
||||
label: `订阅 #${p.subscription_id} 期间`,
|
||||
from: new Date((p.period_start ?? 0) * 1000).toISOString(),
|
||||
to: new Date((p.period_end ?? Math.floor(Date.now() / 1000)) * 1000).toISOString(),
|
||||
to: new Date(
|
||||
(p.period_end ?? Math.floor(Date.now() / 1000)) * 1000,
|
||||
).toISOString(),
|
||||
}))
|
||||
.slice(0, 4),
|
||||
);
|
||||
|
||||
function periodText(p: { kind: string; paid_at: number; period_start: number | null; period_end: number | null }): string {
|
||||
if (p.kind !== "subscription") return "—";
|
||||
function periodText(p: {
|
||||
period_start: number;
|
||||
period_end: number | null;
|
||||
}): string {
|
||||
const end = p.period_end == null ? "进行中" : formatDateTime(p.period_end);
|
||||
return `${formatDateTime(p.period_start)} ~ ${end}`;
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ title: "类型", key: "kind", width: 80 },
|
||||
{ title: "编号 / 时间", key: "id_or_time", width: 160 },
|
||||
{ title: "期间", key: "period", width: 220 },
|
||||
const subColumns = [
|
||||
{ title: "订阅 ID", key: "subscription_id", width: 100 },
|
||||
{ title: "期间", key: "period", width: 200 },
|
||||
{ title: "额度", key: "quota", width: 160 },
|
||||
{ title: "金额", key: "amount", width: 100 },
|
||||
{ title: "付款", key: "paid", width: 140 },
|
||||
{ title: "备注", key: "remark" },
|
||||
];
|
||||
|
||||
const topColumns = [
|
||||
{ title: "时间", key: "paid_at", width: 150 },
|
||||
{ title: "计费单位 / 实付", key: "money", width: 140 },
|
||||
{ title: "交易号 / 渠道", key: "trade", width: 180 },
|
||||
{ title: "派生额度", key: "quota", width: 150 },
|
||||
{ title: "备注", key: "remark" },
|
||||
];
|
||||
</script>
|
||||
@@ -77,52 +88,74 @@ const columns = [
|
||||
:paragraph="{ rows: 4 }"
|
||||
/>
|
||||
<a-empty
|
||||
v-else-if="!overviewQuery.isError.value && payments.length === 0"
|
||||
v-else-if="!overviewQuery.isError.value && subs.length === 0 && tops.length === 0"
|
||||
description="暂无支付记录信息"
|
||||
/>
|
||||
<template v-else-if="payments.length > 0">
|
||||
<template v-else>
|
||||
<h3 class="mv-sec">订阅</h3>
|
||||
<a-empty v-if="subs.length === 0" description="暂无订阅登记" />
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="payments"
|
||||
v-else
|
||||
:columns="subColumns"
|
||||
:data-source="subs"
|
||||
:row-key="(r: { id: number }) => r.id"
|
||||
size="middle"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 860 }"
|
||||
:loading="overviewQuery.isFetching.value"
|
||||
:scroll="{ x: 800 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'kind'">
|
||||
<a-tag :color="record.kind === 'subscription' ? 'blue' : 'gold'">
|
||||
{{ record.kind === "subscription" ? "订阅" : "充值" }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'id_or_time'">
|
||||
<template v-if="record.kind === 'subscription'">
|
||||
订阅 #{{ record.subscription_id ?? record.id }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formatDateTime(record.paid_at) }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'period'">
|
||||
<template v-if="column.key === 'period'">
|
||||
{{ periodText(record) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'quota'">
|
||||
<QuotaText
|
||||
v-if="record.quota_granted != null"
|
||||
:quota="record.quota_granted"
|
||||
:quota-usd="record.quota_usd"
|
||||
/>
|
||||
<span v-else>—</span>
|
||||
<QuotaText :quota="record.quota_granted" :quota-usd="record.quota_usd" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'amount'">
|
||||
{{ formatPaidAmount(record.paid_amount) }}
|
||||
<template v-else-if="column.key === 'paid'">
|
||||
{{
|
||||
record.paid_amount != null
|
||||
? `${formatPaidAmount(record.paid_amount)} · ${formatDateTime(record.paid_at)}`
|
||||
: "—"
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'remark'">
|
||||
{{ record.remark || "—" }}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<h3 class="mv-sec">充值</h3>
|
||||
<a-empty v-if="tops.length === 0" description="暂无充值登记" />
|
||||
<a-table
|
||||
v-else
|
||||
:columns="topColumns"
|
||||
:data-source="tops"
|
||||
:row-key="(r: { id: number }) => r.id"
|
||||
size="middle"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 760 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'paid_at'">
|
||||
{{ formatDateTime(record.paid_at) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'money'">
|
||||
{{ record.amount }} · {{ formatPaidAmount(record.money) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'trade'">
|
||||
{{ record.trade_no || "—" }}
|
||||
<span v-if="record.payment_method" class="mv-note">
|
||||
· {{ record.payment_method }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'quota'">
|
||||
<QuotaText :quota="record.quota_derived" :quota-usd="record.quota_usd" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'remark'">
|
||||
{{ record.remark || "—" }}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="mv-foot">
|
||||
支付参数对成员完全公开;币种由成员与作者约定
|
||||
</div>
|
||||
@@ -136,6 +169,17 @@ const columns = [
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
.mv-sec {
|
||||
margin: 20px 0 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mv-note {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.mv-foot {
|
||||
margin-top: 16px;
|
||||
font-size: 12px;
|
||||
|
||||
Reference in New Issue
Block a user