完善购买和套餐功能

This commit is contained in:
2026-09-16 17:26:02 +08:00
parent 898dedd75a
commit b48688cbab
19 changed files with 554 additions and 58 deletions
@@ -1,5 +1,11 @@
<template>
<div class="tenant-package-tab" v-loading="loading">
<div class="panel-toolbar">
<el-button size="small" :loading="loading" @click="refresh">
<el-icon><Refresh /></el-icon>
刷新数据
</el-button>
</div>
<el-descriptions :column="2" border class="quota-desc">
<el-descriptions-item label="当前套餐">
<el-select
@@ -32,13 +38,27 @@
<span v-else class="muted">永久有效</span>
</el-descriptions-item>
<el-descriptions-item label="开通功能">
<template v-if="info.module_names && info.module_names.length">
<el-tag v-for="(m, i) in info.module_names" :key="i" size="small" class="module-tag">
{{ m }}
<template v-if="info.modules && info.modules.length">
<el-tag v-for="(m, i) in info.modules" :key="i" size="small" class="module-tag">
{{ m.module_name || m.module_code }}
</el-tag>
</template>
<span v-else class="muted">未配置功能</span>
</el-descriptions-item>
<el-descriptions-item label="单品功能">
<template v-if="info.product_modules && info.product_modules.length">
<el-tag
v-for="(m, i) in info.product_modules"
:key="i"
size="small"
type="success"
class="module-tag"
>
{{ m.module_name || m.module_code }}
</el-tag>
</template>
<span v-else class="muted">无单品购买</span>
</el-descriptions-item>
<el-descriptions-item label="用户数使用">
<span class="quota-text">
<b :class="{ danger: info.remaining <= 0 }">{{ info.used }}</b> / {{ info.quota }} 人
@@ -52,7 +72,10 @@
<span class="muted">剩余 {{ info.remaining }} 个名额</span>
</el-descriptions-item>
<el-descriptions-item label="增购单价">
<span>{{ formatMoney(info.extra_user_price) }}/人</span>
<template v-if="Number(info.extra_user_price) > 0">
<span>{{ formatMoney(info.extra_user_price) }}/人</span>
</template>
<span v-else class="muted">未设置</span>
<el-button type="primary" link class="ml8" @click="openRecharge">增购用户数</el-button>
</el-descriptions-item>
</el-descriptions>
@@ -74,6 +97,51 @@
title="该租户套餐即将到期,到期后将自动切换为基础套餐,请及时续费"
/>
<div class="section-title">已购功能(单品购买)</div>
<el-table :data="info.product_orders || []" style="width: 100%" size="small">
<el-table-column prop="product_name" label="产品" min-width="100" show-overflow-tooltip />
<el-table-column label="功能模块" width="150" align="center">
<template #default="{ row }">
<el-tag v-if="row.module_name || row.module_code" size="small" effect="plain">
{{ row.module_name || row.module_code }}
</el-tag>
<span v-else class="muted">未关联</span>
</template>
</el-table-column>
<el-table-column prop="order_no" label="订单号" min-width="180" show-overflow-tooltip />
<el-table-column label="金额" width="100" align="center">
<template #default="{ row }">{{ formatMoney(row.amount) }}</template>
</el-table-column>
<el-table-column label="开通时间" min-width="120" align="center">
<template #default="{ row }">
{{ row.activate_time ? formatDate(row.activate_time) : "-" }}
</template>
</el-table-column>
<el-table-column label="过期时间" min-width="180" align="center">
<template #default="{ row }">
<template v-if="row.expire_time">
<span>{{ formatDate(row.expire_time) }}</span>
<el-tag
size="small"
:type="row.expired ? 'danger' : 'success'"
effect="plain"
class="ml8"
>
{{ row.expired ? "已过期" : `剩余 ${remainingDays(row.expire_time)} 天` }}
</el-tag>
</template>
<span v-else class="muted">长期有效</span>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template #default="{ row }">
<el-button size="small" type="danger" link @click="handleCloseEntitlement(row)">
关闭功能
</el-button>
</template>
</el-table-column>
</el-table>
<div class="section-title">增购记录</div>
<el-table :data="orders" style="width: 100%" size="small" v-loading="ordersLoading">
<el-table-column prop="id" label="ID" width="70" align="center" />
@@ -171,7 +239,8 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { formatDateTime } from "@/utils/datetime";
import { Refresh } from "@element-plus/icons-vue";
import { formatDate, formatDateTime } from "@/utils/datetime";
import {
getTenantQuotaInfo,
getTenantQuotaOrders,
@@ -179,6 +248,7 @@ import {
setTenantPackage,
rechargeTenantQuota,
} from "@/api/tenantPackage";
import { closeProductOrder } from "@/api/product";
const props = defineProps<{
/** 当前租户 ID,为空时不请求 */
@@ -203,6 +273,11 @@ const rechargeForm = ref({
});
const formatMoney = (v: any) => `¥${Number(v || 0).toFixed(2)}`;
/** 距到期剩余天数(向上取整,最小 0) */
const remainingDays = (t: string) => {
const ms = new Date(t).getTime() - Date.now();
return Math.max(0, Math.ceil(ms / 86400000));
};
/** 套餐价格单位随有效时长变化:365天=元/年,30=元/月,90=元/季,其余=元/N天 */
const priceUnit = (row: any) => {
const d = Number(row?.duration_days || 365);
@@ -336,6 +411,32 @@ const handleSetPackage = async (id: number) => {
}
};
/** 关闭已开通的单品功能:订单置为已关闭,租户端对应模块权益即时回收 */
const handleCloseEntitlement = async (row: any) => {
if (!row?.order_id) return;
const label = row.module_name || row.product_name || "该";
try {
await ElMessageBox.confirm(
`关闭后该租户将立即失去「${label}」功能的使用权限(订单保留,可在「产品定价 - 购买订单」中重新开通),确定继续吗?`,
"关闭功能",
{ type: "warning", confirmButtonText: "确认关闭" }
);
} catch {
return;
}
try {
const res = await closeProductOrder(row.order_id);
if (res.code === 200) {
ElMessage.success("功能已关闭");
refresh();
} else {
ElMessage.error(res.msg || "关闭失败");
}
} catch (e: any) {
ElMessage.error(e?.message || "关闭失败");
}
};
const openRecharge = () => {
rechargeForm.value = {
type: 1,
@@ -391,6 +492,11 @@ defineExpose({ refresh });
</script>
<style scoped>
.panel-toolbar {
display: flex;
justify-content: flex-end;
margin-bottom: 8px;
}
.quota-desc {
margin-bottom: 12px;
}