增加功能报价

This commit is contained in:
2026-09-16 00:22:17 +08:00
parent 71a14c46f9
commit 60b2658d4b
32 changed files with 3578 additions and 77 deletions
@@ -23,10 +23,25 @@
<el-alert type="info" :closable="false" show-icon class="tip-alert">
<template #default>
<p>1、套餐决定租户能开通哪些功能:勾选「功能模块」后,绑定该套餐的租户端只展示对应功能菜单(如 ERP 套餐开通进销存)。</p>
<p>2、所有租户都有用户数上限(默认 20 人):达到上限后无法再开账号,可在「租户管理 → 租户详情 → 套餐与用户数」中按单个(如 200 元/人)或加购套餐(如 5 人 500 元)增购。</p>
<p>2、套餐按「有效时长」计费(默认 365 天,价格即元/年):到期后自动切换为基础套餐,续费后恢复对应功能。</p>
<p>3、所有租户都有用户数上限(默认 20 人):达到上限后无法再开账号,可在「租户管理 → 租户详情 → 套餐与用户数」中按单个(如 200 元/人)或加购套餐(如 5 人 500 元)增购。</p>
</template>
</el-alert>
<div class="purchase-url-bar">
<span class="bar-label">套餐购买链接</span>
<el-input
v-model="purchaseUrl"
placeholder="租户端未开通功能卡片「购买」按钮跳转的链接,如 https://www.example.com/package"
clearable
class="url-input"
/>
<el-button type="primary" :loading="purchaseUrlSaving" @click="handleSavePurchaseUrl">
保存
</el-button>
<span class="muted">留空时租户端点击「购买」仅提示联系管理员</span>
</div>
<el-tabs v-model="activeTab" class="package-tabs">
<!-- 功能套餐 -->
<el-tab-pane label="功能套餐" name="package">
@@ -45,6 +60,12 @@
<el-tag size="small" type="info" effect="plain">{{ row.code }}</el-tag>
</template>
</el-table-column>
<el-table-column label="套餐价格" width="120" align="center">
<template #default="{ row }">
<span :class="{ 'price-zero': !Number(row.price) }">{{ formatMoney(row.price) }}</span>
<span class="muted">{{ priceUnit(row) }}</span>
</template>
</el-table-column>
<el-table-column label="开通功能" min-width="260">
<template #default="{ row }">
<el-tag
@@ -139,6 +160,8 @@ import {
deleteTenantPackage,
getQuotaPackageList,
deleteQuotaPackage,
getPackagePurchaseUrl,
savePackagePurchaseUrl,
} from "@/api/tenantPackage";
import PackageEditDialog from "./components/edit.vue";
import QuotaEditDialog from "./components/quotaEdit.vue";
@@ -154,7 +177,46 @@ const quotaDialogVisible = ref(false);
const currentPackage = ref<any>(null);
const currentQuota = ref<any>(null);
// 套餐购买链接(租户端未开通功能卡片「购买」按钮跳转地址)
const purchaseUrl = ref("");
const purchaseUrlSaving = ref(false);
const loadPurchaseUrl = async () => {
try {
const res = await getPackagePurchaseUrl();
if (res.code === 200) {
purchaseUrl.value = res.data?.purchase_url || "";
}
} catch (e) {
console.error("获取套餐购买链接失败:", e);
}
};
const handleSavePurchaseUrl = async () => {
purchaseUrlSaving.value = true;
try {
const res = await savePackagePurchaseUrl(purchaseUrl.value);
if (res.code === 200) {
ElMessage.success("保存成功");
} else {
ElMessage.error(res.msg || "保存失败");
}
} catch (e: any) {
ElMessage.error(e?.message || "保存失败");
} finally {
purchaseUrlSaving.value = false;
}
};
const formatMoney = (v: any) => `¥${Number(v || 0).toFixed(2)}`;
/** 价格单位随有效时长变化:365天=元/年,30=元/月,90=元/季,其余=元/N天 */
const priceUnit = (row: any) => {
const d = Number(row.duration_days || 365);
if (d === 365) return "/年";
if (d === 30) return "/月";
if (d === 90) return "/季";
return `/${d}天`;
};
const unitPrice = (row: any) => {
const count = Number(row.user_count || 0);
if (count <= 0) return 0;
@@ -196,6 +258,7 @@ const loadQuotaPackages = async () => {
const refresh = () => {
loadPackages();
loadQuotaPackages();
loadPurchaseUrl();
};
const handleAddPackage = () => {
@@ -302,6 +365,25 @@ onMounted(refresh);
margin-top: 8px;
}
.purchase-url-bar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
.bar-label {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.url-input {
width: 420px;
max-width: 100%;
}
}
.module-tag {
margin: 0 6px 6px 0;
}
@@ -315,6 +397,10 @@ onMounted(refresh);
font-size: 12px;
}
.price-zero {
color: var(--el-text-color-secondary);
}
:deep(.el-empty) {
padding: 4px 0;
}