增加租户套餐
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<div class="container-box">
|
||||
<div class="header-bar">
|
||||
<h2>租户套餐</h2>
|
||||
<div class="header-actions">
|
||||
<el-button v-if="activeTab === 'package'" type="primary" @click="handleAddPackage">
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加套餐
|
||||
</el-button>
|
||||
<el-button v-else type="primary" @click="handleAddQuota">
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加加购套餐
|
||||
</el-button>
|
||||
<el-button @click="refresh">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<el-alert type="info" :closable="false" show-icon class="tip-alert">
|
||||
<template #default>
|
||||
<p>1、套餐决定租户能开通哪些功能:勾选「功能模块」后,绑定该套餐的租户端只展示对应功能菜单(如 ERP 套餐开通进销存)。</p>
|
||||
<p>2、所有租户都有用户数上限(默认 20 人):达到上限后无法再开账号,可在「租户管理 → 租户详情 → 套餐与用户数」中按单个(如 200 元/人)或加购套餐(如 5 人 500 元)增购。</p>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<el-tabs v-model="activeTab" class="package-tabs">
|
||||
<!-- 功能套餐 -->
|
||||
<el-tab-pane label="功能套餐" name="package">
|
||||
<el-table :data="packages" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="70" align="center" />
|
||||
<el-table-column prop="name" label="套餐名称" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.name }}</span>
|
||||
<el-tag v-if="Number(row.is_default) === 1" type="success" size="small" class="ml6">
|
||||
默认
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="code" label="套餐编码" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" type="info" effect="plain">{{ row.code }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="开通功能" min-width="260">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-for="m in row.modules"
|
||||
:key="m.module_code"
|
||||
size="small"
|
||||
class="module-tag"
|
||||
>
|
||||
{{ m.module_name || m.module_code }}
|
||||
</el-tag>
|
||||
<span v-if="!row.modules || row.modules.length === 0" class="muted">未配置功能</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="user_quota" label="包含用户数" width="110" align="center">
|
||||
<template #default="{ row }">{{ row.user_quota }} 人</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单用户增购价" width="120" align="center">
|
||||
<template #default="{ row }">{{ formatMoney(row.extra_user_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="tenant_count" label="使用租户" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.tenant_count || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sort" label="排序" width="80" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="Number(row.status) === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ Number(row.status) === 1 ? "启用" : "禁用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" type="primary" link @click="handleEditPackage(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" link @click="handleDeletePackage(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 用户数加购套餐 -->
|
||||
<el-tab-pane label="用户数加购套餐" name="quota">
|
||||
<el-table :data="quotaPackages" style="width: 100%" v-loading="quotaLoading">
|
||||
<el-table-column prop="id" label="ID" width="70" align="center" />
|
||||
<el-table-column prop="name" label="规格名称" min-width="160" align="center" />
|
||||
<el-table-column prop="user_count" label="包含用户数" width="120" align="center">
|
||||
<template #default="{ row }">{{ row.user_count }} 人</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售价" width="140" align="center">
|
||||
<template #default="{ row }">{{ formatMoney(row.price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="折算单价" width="140" align="center">
|
||||
<template #default="{ row }">{{ formatMoney(unitPrice(row)) }}/人</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sort" label="排序" width="80" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="Number(row.status) === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ Number(row.status) === 1 ? "启用" : "禁用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" type="primary" link @click="handleEditQuota(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" link @click="handleDeleteQuota(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<PackageEditDialog v-model="packageDialogVisible" :pkg="currentPackage" @success="loadPackages" />
|
||||
<QuotaEditDialog v-model="quotaDialogVisible" :item="currentQuota" @success="loadQuotaPackages" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus, Refresh } from "@element-plus/icons-vue";
|
||||
import {
|
||||
getTenantPackageList,
|
||||
deleteTenantPackage,
|
||||
getQuotaPackageList,
|
||||
deleteQuotaPackage,
|
||||
} from "@/api/tenantPackage";
|
||||
import PackageEditDialog from "./components/edit.vue";
|
||||
import QuotaEditDialog from "./components/quotaEdit.vue";
|
||||
|
||||
const activeTab = ref("package");
|
||||
const loading = ref(false);
|
||||
const quotaLoading = ref(false);
|
||||
const packages = ref<any[]>([]);
|
||||
const quotaPackages = ref<any[]>([]);
|
||||
|
||||
const packageDialogVisible = ref(false);
|
||||
const quotaDialogVisible = ref(false);
|
||||
const currentPackage = ref<any>(null);
|
||||
const currentQuota = ref<any>(null);
|
||||
|
||||
const formatMoney = (v: any) => `¥${Number(v || 0).toFixed(2)}`;
|
||||
const unitPrice = (row: any) => {
|
||||
const count = Number(row.user_count || 0);
|
||||
if (count <= 0) return 0;
|
||||
return Number(row.price || 0) / count;
|
||||
};
|
||||
|
||||
const loadPackages = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getTenantPackageList();
|
||||
if (res.code === 200) {
|
||||
packages.value = res.data?.list || [];
|
||||
} else {
|
||||
ElMessage.error(res.msg || "获取套餐列表失败");
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || "获取套餐列表失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadQuotaPackages = async () => {
|
||||
quotaLoading.value = true;
|
||||
try {
|
||||
const res = await getQuotaPackageList();
|
||||
if (res.code === 200) {
|
||||
quotaPackages.value = res.data?.list || [];
|
||||
} else {
|
||||
ElMessage.error(res.msg || "获取加购套餐失败");
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || "获取加购套餐失败");
|
||||
} finally {
|
||||
quotaLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
loadPackages();
|
||||
loadQuotaPackages();
|
||||
};
|
||||
|
||||
const handleAddPackage = () => {
|
||||
currentPackage.value = null;
|
||||
packageDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const handleEditPackage = (row: any) => {
|
||||
currentPackage.value = { ...row };
|
||||
packageDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const handleDeletePackage = async (row: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除套餐「${row.name}」吗?删除后绑定该套餐的租户将失去对应功能。`,
|
||||
"警告",
|
||||
{ type: "warning" }
|
||||
);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await deleteTenantPackage(row.id);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("删除成功");
|
||||
loadPackages();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "删除失败");
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || "删除失败");
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddQuota = () => {
|
||||
currentQuota.value = null;
|
||||
quotaDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const handleEditQuota = (row: any) => {
|
||||
currentQuota.value = { ...row };
|
||||
quotaDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const handleDeleteQuota = async (row: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定要删除加购套餐「${row.name}」吗?`, "警告", {
|
||||
type: "warning",
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await deleteQuotaPackage(row.id);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("删除成功");
|
||||
loadQuotaPackages();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "删除失败");
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || "删除失败");
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(refresh);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container-box {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tip-alert {
|
||||
margin-bottom: 8px;
|
||||
|
||||
p {
|
||||
margin: 4px 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
.package-tabs {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.module-tag {
|
||||
margin: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
.ml6 {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-empty) {
|
||||
padding: 4px 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user