fix(web): 支付表列与表单语义校准
充值金额/实付金额文案与 dataIndex;期间两行;备注列收窄; 选用弹层 booting 不闪登录;订阅 paid_at 不默认当前时间。
This commit is contained in:
@@ -149,8 +149,8 @@ watch(
|
|||||||
s.paid_at != null ? unixToBeijingWall(s.paid_at) : null;
|
s.paid_at != null ? unixToBeijingWall(s.paid_at) : null;
|
||||||
subForm.remark = s.remark;
|
subForm.remark = s.remark;
|
||||||
} else {
|
} else {
|
||||||
|
// 订阅的 paid_at 是「套餐付款时刻」,可空——不默认填今天
|
||||||
resetSub();
|
resetSub();
|
||||||
subForm.paid_at = dayjs();
|
|
||||||
}
|
}
|
||||||
if (props.editingTop) {
|
if (props.editingTop) {
|
||||||
isSub.value = false;
|
isSub.value = false;
|
||||||
@@ -248,7 +248,7 @@ async function onSubmit() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (topForm.amount == null || topForm.amount <= 0) {
|
if (topForm.amount == null || topForm.amount <= 0) {
|
||||||
error.value = "请填写计费单位金额(须为正)";
|
error.value = "请填写充值金额 amount(须为正)";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (topForm.money == null || topForm.money < 0) {
|
if (topForm.money == null || topForm.money < 0) {
|
||||||
@@ -362,7 +362,7 @@ async function onSubmit() {
|
|||||||
{{ quotaPreview }} · 落库为原始 quota
|
{{ quotaPreview }} · 落库为原始 quota
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="付款金额 / 时间(可空)">
|
<a-form-item label="付款金额 / 付款时间(可空)">
|
||||||
<a-input-number
|
<a-input-number
|
||||||
v-model:value="subForm.paid_amount"
|
v-model:value="subForm.paid_amount"
|
||||||
:min="0"
|
:min="0"
|
||||||
@@ -374,10 +374,12 @@ async function onSubmit() {
|
|||||||
v-model:value="subForm.paid_at"
|
v-model:value="subForm.paid_at"
|
||||||
:format="TIME_FMT"
|
:format="TIME_FMT"
|
||||||
:show-time="{ defaultValue: undefined }"
|
:show-time="{ defaultValue: undefined }"
|
||||||
placeholder="付款时间(可空)"
|
placeholder="钱出手时刻(可空)"
|
||||||
style="width: 200px; margin-left: 8px"
|
style="width: 200px; margin-left: 8px"
|
||||||
/>
|
/>
|
||||||
<div class="form-note">金额数值——币种由你们约定,系统不预设;金额不可为负</div>
|
<div class="form-note">
|
||||||
|
社交层付款事实:金额数值、币种由你们约定;未知可留空。登记时刻由系统另记
|
||||||
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="备注">
|
<a-form-item label="备注">
|
||||||
<a-input v-model:value="subForm.remark" />
|
<a-input v-model:value="subForm.remark" />
|
||||||
@@ -385,7 +387,7 @@ async function onSubmit() {
|
|||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<a-form v-else layout="vertical">
|
<a-form v-else layout="vertical">
|
||||||
<a-form-item label="计费单位 amount" required>
|
<a-form-item label="充值金额 amount" required>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-input-number
|
<a-input-number
|
||||||
v-model:value="topForm.amount"
|
v-model:value="topForm.amount"
|
||||||
@@ -396,17 +398,18 @@ async function onSubmit() {
|
|||||||
<a-button @click="topPickerOpen = true">从平台选用</a-button>
|
<a-button @click="topPickerOpen = true">从平台选用</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
<div class="form-note">
|
<div class="form-note">
|
||||||
上游 amount(美元额度形态);换得 quota = amount × QPU
|
平台计费侧充值额(不标币种);与「实付」之间汇率由管理员约定。换得 quota ≈ amount × QPU
|
||||||
<template v-if="quotaPreview"> · {{ quotaPreview }}</template>
|
<template v-if="quotaPreview"> · {{ quotaPreview }}</template>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="实付 money" required>
|
<a-form-item label="实付金额 money" required>
|
||||||
<a-input-number
|
<a-input-number
|
||||||
v-model:value="topForm.money"
|
v-model:value="topForm.money"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
style="width: 180px"
|
style="width: 180px"
|
||||||
/>
|
/>
|
||||||
|
<div class="form-note">实际支付金额(不预设币种)</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="入账时间 paid_at" required>
|
<a-form-item label="入账时间 paid_at" required>
|
||||||
<a-date-picker
|
<a-date-picker
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ const username = ref("");
|
|||||||
const password = ref("");
|
const password = ref("");
|
||||||
const error = ref("");
|
const error = ref("");
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
/** 打开后检查登录并拉取中——避免闪现登录表单 */
|
||||||
|
const booting = ref(false);
|
||||||
const list = ref<PlatformSubscription[]>([]);
|
const list = ref<PlatformSubscription[]>([]);
|
||||||
|
|
||||||
function readToken(): string {
|
function readToken(): string {
|
||||||
@@ -51,13 +53,18 @@ watch(
|
|||||||
() => props.open,
|
() => props.open,
|
||||||
(v) => {
|
(v) => {
|
||||||
if (v) {
|
if (v) {
|
||||||
// 每次打开都重新拉取——上游续订/新订阅要能看见
|
// 有 token:先显示「检查中」,拉完再进列表/登录,不闪登录表单
|
||||||
if (readToken()) void load(true);
|
if (readToken()) {
|
||||||
|
booting.value = true;
|
||||||
|
void load(true).finally(() => {
|
||||||
|
booting.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
list.value = [];
|
list.value = [];
|
||||||
error.value = "";
|
error.value = "";
|
||||||
password.value = "";
|
password.value = "";
|
||||||
// 用户名可留作便利;密码绝不残留
|
booting.value = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -119,7 +126,11 @@ function onUse(s: PlatformSubscription) {
|
|||||||
destroy-on-hidden
|
destroy-on-hidden
|
||||||
@update:open="emit('update:open', $event)"
|
@update:open="emit('update:open', $event)"
|
||||||
>
|
>
|
||||||
<template v-if="list.length === 0">
|
<div v-if="booting || (loading && list.length === 0)" class="plat-pick__boot">
|
||||||
|
<a-spin />
|
||||||
|
<span class="plat-pick__boot-text">正在检查登录并拉取…</span>
|
||||||
|
</div>
|
||||||
|
<template v-else-if="list.length === 0">
|
||||||
<p class="plat-pick__hint">
|
<p class="plat-pick__hint">
|
||||||
使用 NewAPI 控制台账号登录后拉取订阅,仅用于预填登记;密码不保存。
|
使用 NewAPI 控制台账号登录后拉取订阅,仅用于预填登记;密码不保存。
|
||||||
</p>
|
</p>
|
||||||
@@ -183,6 +194,19 @@ function onUse(s: PlatformSubscription) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.plat-pick__boot {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 48px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plat-pick__boot-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
.plat-pick__hint {
|
.plat-pick__hint {
|
||||||
margin: 0 0 16px;
|
margin: 0 0 16px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ const username = ref("");
|
|||||||
const password = ref("");
|
const password = ref("");
|
||||||
const error = ref("");
|
const error = ref("");
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
/** 打开后检查登录并拉取中——避免闪现登录表单 */
|
||||||
|
const booting = ref(false);
|
||||||
const items = ref<PlatformTopup[]>([]);
|
const items = ref<PlatformTopup[]>([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const page = ref(1);
|
const page = ref(1);
|
||||||
@@ -121,14 +123,19 @@ watch(
|
|||||||
() => props.open,
|
() => props.open,
|
||||||
(v) => {
|
(v) => {
|
||||||
if (v) {
|
if (v) {
|
||||||
// 每次打开重拉第一页(有 token 则免再输密码)
|
if (readToken()) {
|
||||||
if (readToken()) void loadPage(1, { allowTokenOnly: true });
|
booting.value = true;
|
||||||
|
void loadPage(1, { allowTokenOnly: true }).finally(() => {
|
||||||
|
booting.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
items.value = [];
|
items.value = [];
|
||||||
total.value = 0;
|
total.value = 0;
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
error.value = "";
|
error.value = "";
|
||||||
password.value = "";
|
password.value = "";
|
||||||
|
booting.value = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -145,7 +152,14 @@ watch(
|
|||||||
destroy-on-hidden
|
destroy-on-hidden
|
||||||
@update:open="emit('update:open', $event)"
|
@update:open="emit('update:open', $event)"
|
||||||
>
|
>
|
||||||
<template v-if="items.length === 0 && !loading">
|
<div
|
||||||
|
v-if="booting || (loading && items.length === 0)"
|
||||||
|
class="top-pick__boot"
|
||||||
|
>
|
||||||
|
<a-spin />
|
||||||
|
<span class="top-pick__boot-text">正在检查登录并拉取…</span>
|
||||||
|
</div>
|
||||||
|
<template v-else-if="items.length === 0 && !loading">
|
||||||
<p class="top-pick__hint">
|
<p class="top-pick__hint">
|
||||||
登录 NewAPI 控制台拉取充值单(传统分页);密码不保存。
|
登录 NewAPI 控制台拉取充值单(传统分页);密码不保存。
|
||||||
</p>
|
</p>
|
||||||
@@ -222,6 +236,19 @@ watch(
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.top-pick__boot {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 48px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-pick__boot-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
.top-pick__hint {
|
.top-pick__hint {
|
||||||
margin: 0 0 16px;
|
margin: 0 0 16px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const topRows = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const subColumns = [
|
const subColumns = [
|
||||||
{ title: "订阅 ID", key: "subscription_id", width: 100 },
|
{ title: "订阅 ID", dataIndex: "subscription_id", key: "subscription_id", width: 100 },
|
||||||
{ title: "期间", key: "period", width: 200 },
|
{ title: "期间", key: "period", width: 200 },
|
||||||
{ title: "额度", key: "quota", width: 160 },
|
{ title: "额度", key: "quota", width: 160 },
|
||||||
{ title: "付款", key: "paid", width: 140 },
|
{ title: "付款", key: "paid", width: 140 },
|
||||||
@@ -68,17 +68,24 @@ const subColumns = [
|
|||||||
|
|
||||||
const topColumns = [
|
const topColumns = [
|
||||||
{ title: "时间", key: "paid_at", width: 150 },
|
{ title: "时间", key: "paid_at", width: 150 },
|
||||||
{ title: "计费单位", key: "amount", width: 100 },
|
{
|
||||||
{ title: "实付", key: "money", width: 100 },
|
title: "充值金额",
|
||||||
|
dataIndex: "amount",
|
||||||
|
key: "amount",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{ title: "实付金额", dataIndex: "money", key: "money", width: 100 },
|
||||||
{ title: "交易号 / 渠道", key: "trade", width: 180 },
|
{ title: "交易号 / 渠道", key: "trade", width: 180 },
|
||||||
{ title: "派生额度", key: "quota", width: 150 },
|
{ title: "派生额度", key: "quota", width: 150 },
|
||||||
{ title: "备注", key: "remark" },
|
{ title: "备注", key: "remark" },
|
||||||
{ title: "操作", key: "actions", width: 120 },
|
{ title: "操作", key: "actions", width: 120 },
|
||||||
];
|
];
|
||||||
|
|
||||||
function periodText(s: SubscriptionItem): string {
|
function periodLines(s: SubscriptionItem): { start: string; end: string } {
|
||||||
const end = s.period_end == null ? "进行中" : formatDateTime(s.period_end);
|
return {
|
||||||
return `${formatDateTime(s.period_start)} ~ ${end}`;
|
start: formatDateTime(s.period_start),
|
||||||
|
end: s.period_end == null ? "进行中" : formatDateTime(s.period_end),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCreateSub() {
|
function onCreateSub() {
|
||||||
@@ -226,7 +233,10 @@ watch(
|
|||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'period'">
|
<template v-if="column.key === 'period'">
|
||||||
{{ periodText(record) }}
|
<div class="pay-period">
|
||||||
|
<span>{{ periodLines(record).start }}</span>
|
||||||
|
<span class="pay-period__end">~ {{ periodLines(record).end }}</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'quota'">
|
<template v-else-if="column.key === 'quota'">
|
||||||
<QuotaText :quota="record.quota_granted" :quota-usd="record.quota_usd" />
|
<QuotaText :quota="record.quota_granted" :quota-usd="record.quota_usd" />
|
||||||
@@ -282,6 +292,9 @@ watch(
|
|||||||
<template v-if="column.key === 'paid_at'">
|
<template v-if="column.key === 'paid_at'">
|
||||||
{{ formatDateTime(record.paid_at) }}
|
{{ formatDateTime(record.paid_at) }}
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.key === 'amount'">
|
||||||
|
{{ formatPaidAmount(record.amount) }}
|
||||||
|
</template>
|
||||||
<template v-else-if="column.key === 'money'">
|
<template v-else-if="column.key === 'money'">
|
||||||
{{ formatPaidAmount(record.money) }}
|
{{ formatPaidAmount(record.money) }}
|
||||||
</template>
|
</template>
|
||||||
@@ -324,6 +337,17 @@ watch(
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.pay-period {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
line-height: 20px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-period__end {
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
.pay-missing__tags {
|
.pay-missing__tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -28,28 +28,46 @@ const extraPresets = computed(() =>
|
|||||||
.slice(0, 4),
|
.slice(0, 4),
|
||||||
);
|
);
|
||||||
|
|
||||||
function periodText(p: {
|
function periodLines(p: {
|
||||||
period_start: number;
|
period_start: number;
|
||||||
period_end: number | null;
|
period_end: number | null;
|
||||||
}): string {
|
}): { start: string; end: string } {
|
||||||
const end = p.period_end == null ? "进行中" : formatDateTime(p.period_end);
|
return {
|
||||||
return `${formatDateTime(p.period_start)} ~ ${end}`;
|
start: formatDateTime(p.period_start),
|
||||||
|
end: p.period_end == null ? "进行中" : formatDateTime(p.period_end),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const subColumns = [
|
const subColumns = [
|
||||||
{ title: "订阅 ID", key: "subscription_id", width: 100 },
|
{
|
||||||
|
title: "订阅 ID",
|
||||||
|
dataIndex: "subscription_id",
|
||||||
|
key: "subscription_id",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
{ title: "期间", key: "period", width: 200 },
|
{ title: "期间", key: "period", width: 200 },
|
||||||
{ title: "额度", key: "quota", width: 160 },
|
{ title: "额度", key: "quota", width: 160 },
|
||||||
{ title: "付款", key: "paid", width: 140 },
|
{ title: "付款", key: "paid", width: 150 },
|
||||||
{ title: "备注", key: "remark" },
|
{ title: "备注", dataIndex: "remark", key: "remark", width: 140, ellipsis: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
const topColumns = [
|
const topColumns = [
|
||||||
{ title: "时间", key: "paid_at", width: 150 },
|
{ title: "时间", key: "paid_at", width: 150 },
|
||||||
{ title: "计费单位 / 实付", key: "money", width: 140 },
|
{
|
||||||
|
title: "充值金额",
|
||||||
|
dataIndex: "amount",
|
||||||
|
key: "amount",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "实付金额",
|
||||||
|
dataIndex: "money",
|
||||||
|
key: "money",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
{ title: "交易号 / 渠道", key: "trade", width: 180 },
|
{ title: "交易号 / 渠道", key: "trade", width: 180 },
|
||||||
{ title: "派生额度", key: "quota", width: 150 },
|
{ title: "派生额度", key: "quota", width: 150 },
|
||||||
{ title: "备注", key: "remark" },
|
{ title: "备注", dataIndex: "remark", key: "remark", width: 140, ellipsis: true },
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -101,11 +119,14 @@ const topColumns = [
|
|||||||
:row-key="(r: { id: number }) => r.id"
|
:row-key="(r: { id: number }) => r.id"
|
||||||
size="middle"
|
size="middle"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
:scroll="{ x: 800 }"
|
:scroll="{ x: 760 }"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'period'">
|
<template v-if="column.key === 'period'">
|
||||||
{{ periodText(record) }}
|
<div class="mv-period">
|
||||||
|
<span>{{ periodLines(record).start }}</span>
|
||||||
|
<span class="mv-period__end">~ {{ periodLines(record).end }}</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'quota'">
|
<template v-else-if="column.key === 'quota'">
|
||||||
<QuotaText :quota="record.quota_granted" :quota-usd="record.quota_usd" />
|
<QuotaText :quota="record.quota_granted" :quota-usd="record.quota_usd" />
|
||||||
@@ -132,14 +153,17 @@ const topColumns = [
|
|||||||
:row-key="(r: { id: number }) => r.id"
|
:row-key="(r: { id: number }) => r.id"
|
||||||
size="middle"
|
size="middle"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
:scroll="{ x: 760 }"
|
:scroll="{ x: 840 }"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'paid_at'">
|
<template v-if="column.key === 'paid_at'">
|
||||||
{{ formatDateTime(record.paid_at) }}
|
{{ formatDateTime(record.paid_at) }}
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.key === 'amount'">
|
||||||
|
{{ formatPaidAmount(record.amount) }}
|
||||||
|
</template>
|
||||||
<template v-else-if="column.key === 'money'">
|
<template v-else-if="column.key === 'money'">
|
||||||
{{ record.amount }} · {{ formatPaidAmount(record.money) }}
|
{{ formatPaidAmount(record.money) }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'trade'">
|
<template v-else-if="column.key === 'trade'">
|
||||||
{{ record.trade_no || "—" }}
|
{{ record.trade_no || "—" }}
|
||||||
@@ -164,6 +188,17 @@ const topColumns = [
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.mv-period {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
line-height: 20px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mv-period__end {
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
.mv-range-label {
|
.mv-range-label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: rgba(0, 0, 0, 0.65);
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ payments
|
|||||||
subscriptions topups
|
subscriptions topups
|
||||||
id PK id PK
|
id PK id PK
|
||||||
subscription_id UNIQUE upstream_id? ← 上游充值单 id
|
subscription_id UNIQUE upstream_id? ← 上游充值单 id
|
||||||
period_start NOT NULL amount NOT NULL ← 计费单位(美元额度形态)
|
period_start NOT NULL amount NOT NULL ← 平台计费金额(充值额)
|
||||||
period_end NULL money NOT NULL ← 实付
|
period_end NULL money NOT NULL ← 实付
|
||||||
quota_granted NOT NULL trade_no / payment_* ← 渠道事实
|
quota_granted NOT NULL trade_no / payment_* ← 渠道事实
|
||||||
paid_amount NULL paid_at NOT NULL ← = 上游 complete_time(唯一事件时刻)
|
paid_amount NULL paid_at NOT NULL ← = 上游 complete_time(唯一事件时刻)
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
|----|------|----------|------|
|
|----|------|----------|------|
|
||||||
| `id` | bigserial PK | — | 本地主键 |
|
| `id` | bigserial PK | — | 本地主键 |
|
||||||
| `upstream_id` | bigint? | `id` | 上游充值单 ID(对账;手工补录可空) |
|
| `upstream_id` | bigint? | `id` | 上游充值单 ID(对账;手工补录可空) |
|
||||||
| `amount` | numeric | `amount` | **计费单位金额**(样本 `1`——美元额度形态,≠ 实付货币) |
|
| `amount` | numeric | `amount` | **平台计费金额**(充值记账额,样本 `1`)——与实付 `money` 之间由**管理员约定汇率**换算;**不标币种/单位** |
|
||||||
| `money` | numeric | `money` | **实付金额**(样本 `2.04`);币种无关纯数值 |
|
| `money` | numeric | `money` | **实付金额**(样本 `2.04`);同样不预设币种 |
|
||||||
| `trade_no` | text? | `trade_no` | 交易号 |
|
| `trade_no` | text? | `trade_no` | 交易号 |
|
||||||
| `payment_method` | text? | `payment_method` | 如 `alipay` |
|
| `payment_method` | text? | `payment_method` | 如 `alipay` |
|
||||||
| `payment_provider` | text? | `payment_provider` | 如 `epay` |
|
| `payment_provider` | text? | `payment_provider` | 如 `epay` |
|
||||||
@@ -37,9 +37,9 @@
|
|||||||
|
|
||||||
## 3. 「换得额度」是派生
|
## 3. 「换得额度」是派生
|
||||||
|
|
||||||
上游无 `quota_granted`:派生 `amount × quota_per_unit`(默认 QPU 500000)。
|
上游无 `quota_granted`:派生 `amount × quota_per_unit`(默认 QPU 500000)——`amount` 是平台计费侧充值额,与 quota 同域量纲。
|
||||||
|
|
||||||
- **只存事实**:`amount` 与 `money` 两真值;比率现算(R6/R9)
|
- **只存事实**:`amount`(平台计费)与 `money`(实付)两真值;**汇率不入库**(管理员社交约定,查询/展示可现算 `money/amount`)
|
||||||
- 不在库中固化派生额度
|
- 不在库中固化派生额度
|
||||||
|
|
||||||
## 4. 时刻语义
|
## 4. 时刻语义
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
| 列 | 内容 |
|
| 列 | 内容 |
|
||||||
|----|------|
|
|----|------|
|
||||||
| 时间 | `paid_at`(入账完成) |
|
| 时间 | `paid_at`(入账完成) |
|
||||||
| 计费单位 / 实付 | `amount` · `money` |
|
| 充值金额 / 实付金额 | `amount` · `money`(汇率约定,不标币种) |
|
||||||
| 交易号 / 渠道 | `trade_no` · 支付方式 |
|
| 交易号 / 渠道 | `trade_no` · 支付方式 |
|
||||||
| 派生额度 | `amount × qpu` + $(展示) |
|
| 派生额度 | `amount × qpu` + $(展示) |
|
||||||
| 备注 | `remark` |
|
| 备注 | `remark` |
|
||||||
|
|||||||
@@ -45,8 +45,8 @@
|
|||||||
| 列 | 内容 |
|
| 列 | 内容 |
|
||||||
|----|------|
|
|----|------|
|
||||||
| 时间 | `paid_at`(= 上游 `complete_time`,平台展示时刻;倒序) |
|
| 时间 | `paid_at`(= 上游 `complete_time`,平台展示时刻;倒序) |
|
||||||
| 计费单位 | `amount`(如 `1`) |
|
| 充值金额 | `amount`(平台计费侧充值额,不标币种) |
|
||||||
| 实付 | `money`(如 `2.04`,纯数字) |
|
| 实付金额 | `money`(如 `2.04`,纯数字) |
|
||||||
| 交易号 / 渠道 | `trade_no` · `payment_method` / `payment_provider` |
|
| 交易号 / 渠道 | `trade_no` · `payment_method` / `payment_provider` |
|
||||||
| 派生额度 | `amount × qpu` + $(展示,不入库) |
|
| 派生额度 | `amount × qpu` + $(展示,不入库) |
|
||||||
| 备注 / 登记 | `remark` · `created_at` |
|
| 备注 / 登记 | `remark` · `created_at` |
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ quota = ( (prompt_tokens − cache_tokens) ← 提示 tokens(未
|
|||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `id` | `17` | 上游充值单 ID |
|
| `id` | `17` | 上游充值单 ID |
|
||||||
| `user_id` | `6` | 账号 |
|
| `user_id` | `6` | 账号 |
|
||||||
| `amount` | `1` | **计费单位金额**(与「美元额度」同构,≠ 实付货币) |
|
| `amount` | `1` | **平台计费金额**(充值记账额;与 `money` 之间为管理员约定汇率,不标币种) |
|
||||||
| `money` | `2.04` | **实付金额** |
|
| `money` | `2.04` | **实付金额** |
|
||||||
| `trade_no` | `USR6…` | 交易号 |
|
| `trade_no` | `USR6…` | 交易号 |
|
||||||
| `payment_method` / `payment_provider` | `alipay` / `epay` | 渠道 |
|
| `payment_method` / `payment_provider` | `alipay` / `epay` | 渠道 |
|
||||||
|
|||||||
Reference in New Issue
Block a user