增加租户套餐
This commit is contained in:
@@ -0,0 +1,389 @@
|
||||
<template>
|
||||
<div class="tenant-package-tab" v-loading="loading">
|
||||
<el-descriptions :column="2" border class="quota-desc">
|
||||
<el-descriptions-item label="当前套餐">
|
||||
<el-select
|
||||
v-model="selectedPackageId"
|
||||
placeholder="请选择套餐"
|
||||
size="small"
|
||||
style="width: 220px"
|
||||
@change="handleSetPackage"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in packageOptions"
|
||||
:key="p.id"
|
||||
:label="p.name"
|
||||
:value="p.id"
|
||||
/>
|
||||
</el-select>
|
||||
</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 }}
|
||||
</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 }} 人
|
||||
</span>
|
||||
<el-progress
|
||||
:percentage="usedPercent"
|
||||
:status="info.remaining <= 0 ? 'exception' : undefined"
|
||||
:stroke-width="8"
|
||||
class="quota-progress"
|
||||
/>
|
||||
<span class="muted">剩余 {{ info.remaining }} 个名额</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="增购单价">
|
||||
<span>{{ formatMoney(info.extra_user_price) }}/人</span>
|
||||
<el-button type="primary" link class="ml8" @click="openRecharge">增购用户数</el-button>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-alert
|
||||
v-if="info.remaining <= 0"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="quota-alert"
|
||||
title="该租户用户数已达上限,新增账号前请先增购用户数"
|
||||
/>
|
||||
|
||||
<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" />
|
||||
<el-table-column label="增购方式" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="Number(row.type) === 2 ? 'success' : 'info'" effect="plain">
|
||||
{{ Number(row.type) === 2 ? "套餐增购" : "单个增购" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="package_name" label="规格" min-width="120" align="center" />
|
||||
<el-table-column label="用户数" width="90" align="center">
|
||||
<template #default="{ row }">+{{ row.user_count }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="110" align="center">
|
||||
<template #default="{ row }">{{ formatMoney(row.unit_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" width="110" align="center">
|
||||
<template #default="{ row }">{{ formatMoney(row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上限变化" width="130" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.quota_before }} → <b>{{ row.quota_after }}</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operator" label="操作人" width="120" align="center" />
|
||||
<el-table-column label="时间" min-width="160" align="center">
|
||||
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip />
|
||||
</el-table>
|
||||
|
||||
<!-- 增购用户数 -->
|
||||
<el-dialog v-model="rechargeVisible" title="增购用户数" width="520px" destroy-on-close>
|
||||
<el-form :model="rechargeForm" label-width="100px">
|
||||
<el-form-item label="增购方式">
|
||||
<el-radio-group v-model="rechargeForm.type">
|
||||
<el-radio :label="1">单个增购</el-radio>
|
||||
<el-radio :label="2">按套餐增购</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="rechargeForm.type === 1">
|
||||
<el-form-item label="增购数量">
|
||||
<el-input-number
|
||||
v-model="rechargeForm.user_count"
|
||||
:min="1"
|
||||
:max="9999"
|
||||
controls-position="right"
|
||||
/>
|
||||
<span class="field-tip">个用户</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="单价">
|
||||
<el-input-number
|
||||
v-model="rechargeForm.unit_price"
|
||||
:min="0"
|
||||
:max="999999"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
/>
|
||||
<span class="field-tip">元/人(默认为套餐设置的单用户增购价)</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-form-item label="加购套餐">
|
||||
<el-select v-model="rechargeForm.quota_package_id" placeholder="请选择" style="width: 100%">
|
||||
<el-option
|
||||
v-for="q in info.quota_packages || []"
|
||||
:key="q.id"
|
||||
:label="`${q.name}(${q.user_count}人 / ${formatMoney(q.price)})`"
|
||||
:value="q.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item label="应付金额">
|
||||
<span class="amount-text">{{ formatMoney(rechargeAmount) }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="rechargeForm.remark" maxlength="200" placeholder="选填,如:客户已付款" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="rechargeVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="recharging" @click="submitRecharge">确认增购</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
import {
|
||||
getTenantQuotaInfo,
|
||||
getTenantQuotaOrders,
|
||||
getTenantPackageSelectList,
|
||||
setTenantPackage,
|
||||
rechargeTenantQuota,
|
||||
} from "@/api/tenantPackage";
|
||||
|
||||
const props = defineProps<{
|
||||
/** 当前租户 ID,为空时不请求 */
|
||||
tid: number | null;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const ordersLoading = ref(false);
|
||||
const recharging = ref(false);
|
||||
const info = ref<any>({});
|
||||
const orders = ref<any[]>([]);
|
||||
const packageOptions = ref<any[]>([]);
|
||||
const selectedPackageId = ref<number | undefined>(undefined);
|
||||
|
||||
const rechargeVisible = ref(false);
|
||||
const rechargeForm = ref({
|
||||
type: 1,
|
||||
user_count: 1,
|
||||
unit_price: 0,
|
||||
quota_package_id: undefined as number | undefined,
|
||||
remark: "",
|
||||
});
|
||||
|
||||
const formatMoney = (v: any) => `¥${Number(v || 0).toFixed(2)}`;
|
||||
|
||||
const usedPercent = computed(() => {
|
||||
const quota = Number(info.value.quota || 0);
|
||||
const used = Number(info.value.used || 0);
|
||||
if (quota <= 0) return 0;
|
||||
return Math.min(100, Math.round((used / quota) * 100));
|
||||
});
|
||||
|
||||
const rechargeAmount = computed(() => {
|
||||
if (Number(rechargeForm.value.type) === 2) {
|
||||
const pkg = (info.value.quota_packages || []).find(
|
||||
(q: any) => Number(q.id) === Number(rechargeForm.value.quota_package_id)
|
||||
);
|
||||
return Number(pkg?.price || 0);
|
||||
}
|
||||
return Number(rechargeForm.value.user_count || 0) * Number(rechargeForm.value.unit_price || 0);
|
||||
});
|
||||
|
||||
const loadInfo = async () => {
|
||||
const tid = props.tid;
|
||||
if (tid == null) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getTenantQuotaInfo(tid);
|
||||
if (res.code === 200) {
|
||||
info.value = res.data || {};
|
||||
selectedPackageId.value = Number(info.value.package_id || 0) || undefined;
|
||||
} else {
|
||||
ElMessage.error(res.msg || "获取租户套餐信息失败");
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || "获取租户套餐信息失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadOrders = async () => {
|
||||
const tid = props.tid;
|
||||
if (tid == null) return;
|
||||
ordersLoading.value = true;
|
||||
try {
|
||||
const res = await getTenantQuotaOrders(tid);
|
||||
if (res.code === 200) {
|
||||
orders.value = res.data?.list || [];
|
||||
}
|
||||
} catch {
|
||||
orders.value = [];
|
||||
} finally {
|
||||
ordersLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadPackageOptions = async () => {
|
||||
try {
|
||||
const res = await getTenantPackageSelectList();
|
||||
if (res.code === 200) {
|
||||
packageOptions.value = res.data || [];
|
||||
}
|
||||
} catch {
|
||||
packageOptions.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
loadInfo();
|
||||
loadOrders();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.tid,
|
||||
(val) => {
|
||||
if (val != null) {
|
||||
refresh();
|
||||
if (packageOptions.value.length === 0) loadPackageOptions();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const handleSetPackage = async (id: number) => {
|
||||
const tid = props.tid;
|
||||
if (tid == null || !id) return;
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
"调整套餐会改变该租户可用的功能模块(用户数上限只增不减),确定继续吗?",
|
||||
"提示",
|
||||
{ type: "warning" }
|
||||
);
|
||||
} catch {
|
||||
// 取消时回滚选择
|
||||
selectedPackageId.value = Number(info.value.package_id || 0) || undefined;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await setTenantPackage({ tid, package_id: id, sync_quota: true });
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("套餐设置成功");
|
||||
refresh();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "设置失败");
|
||||
selectedPackageId.value = Number(info.value.package_id || 0) || undefined;
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || "设置失败");
|
||||
}
|
||||
};
|
||||
|
||||
const openRecharge = () => {
|
||||
rechargeForm.value = {
|
||||
type: 1,
|
||||
user_count: 1,
|
||||
unit_price: Number(info.value.extra_user_price || 0),
|
||||
quota_package_id: (info.value.quota_packages || [])[0]?.id,
|
||||
remark: "",
|
||||
};
|
||||
rechargeVisible.value = true;
|
||||
};
|
||||
|
||||
const submitRecharge = async () => {
|
||||
const tid = props.tid;
|
||||
if (tid == null) return;
|
||||
const payload: any = {
|
||||
tid,
|
||||
type: rechargeForm.value.type,
|
||||
remark: rechargeForm.value.remark,
|
||||
};
|
||||
if (Number(rechargeForm.value.type) === 2) {
|
||||
if (!rechargeForm.value.quota_package_id) {
|
||||
ElMessage.warning("请选择加购套餐");
|
||||
return;
|
||||
}
|
||||
payload.quota_package_id = rechargeForm.value.quota_package_id;
|
||||
} else {
|
||||
if (Number(rechargeForm.value.user_count) <= 0) {
|
||||
ElMessage.warning("请输入增购数量");
|
||||
return;
|
||||
}
|
||||
payload.user_count = rechargeForm.value.user_count;
|
||||
payload.unit_price = rechargeForm.value.unit_price;
|
||||
}
|
||||
|
||||
recharging.value = true;
|
||||
try {
|
||||
const res = await rechargeTenantQuota(payload);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`增购成功,本次增加 ${res.data?.add || 0} 个用户数`);
|
||||
rechargeVisible.value = false;
|
||||
refresh();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "增购失败");
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || "增购失败");
|
||||
} finally {
|
||||
recharging.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ refresh });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.quota-desc {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.module-tag {
|
||||
margin: 0 6px 6px 0;
|
||||
}
|
||||
.quota-text b {
|
||||
font-size: 16px;
|
||||
}
|
||||
.quota-text b.danger {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
.quota-progress {
|
||||
margin: 6px 0 2px;
|
||||
max-width: 220px;
|
||||
}
|
||||
.muted {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.quota-alert {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.section-title {
|
||||
margin: 16px 0 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.amount-text {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
.field-tip {
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user