2025-12-28 00:20:10 +08:00

389 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div
v-if="$site?.pg"
class="grid grid-cols-1 items-start gap-5 lg:grid-cols-2"
>
<AlertBanner
v-if="!isSetupWizardComplete"
class="col-span-1 lg:col-span-2"
title="请登录并完成您网站上的设置向导。只有在设置完成后才会收集分析数据。"
>
<Button
class="ml-auto"
variant="outline"
@click="loginAsTeam"
:loading="$site.loginAsAdmin.loading"
>
登录
</Button>
</AlertBanner>
<AlertBanner
v-if="$site.pg.current_plan?.is_trial_plan"
class="col-span-1 lg:col-span-2"
title="升级到付费计划以在试用期结束后继续使用您的网站。"
>
<Button class="ml-auto" variant="outline" @click="showPlanChangeDialog">
升级
</Button>
</AlertBanner>
<!-- 左侧区块当前计划 -->
<div class="col-span-1 space-y-5">
<!-- 当前计划卡片 -->
<div class="rounded-md border">
<div class="h-12 border-b px-5 py-4">
<h2 class="text-lg font-medium text-gray-900">当前计划</h2>
</div>
<div class="p-5">
<div class="flex h-full flex-col sm:flex-row sm:items-center sm:justify-between">
<div class="mb-4 sm:mb-0">
<div class="flex items-center">
<span class="text-base font-medium text-gray-900">
<template v-if="$site.pg.trial_end_date">
{{ trialDays($site.pg.trial_end_date) }}
</template>
<template v-else-if="currentPlan">
{{ currentPlan.plan_title}}
</template>
<template v-else> 未设置计划 </template>
</span>
<div
class="ml-2 text-sm leading-3 text-gray-600"
v-if="currentPlan && currentPlan.support_included"
>
<Tooltip text="包含支持">
<i-lucide-badge-check class="h-4 w-4" />
</Tooltip>
</div>
</div>
<div v-if="$site.pg.site_end_date" class="mt-2 inline-flex items-center rounded-full bg-amber-50 px-4 py-2 text-sm font-medium text-amber-800">
<i-lucide-clock class="mr-1.5 h-4 w-4 text-amber-500" />
到期时间{{ $format.date($site.pg.site_end_date) }}
</div>
</div>
<div class="flex gap-2">
<Button
@click="openRenewalDialog"
v-if="$site.pg.site_end_date && !$site.pg.current_plan?.is_trial_plan"
class="px-5 !bg-[#1fc76f] !hover:bg-[#19b862] !active:bg-[#169e54] !text-white"
>
续费
</Button>
<Button @click="showPlanChangeDialog" class="bg-gray-100 text-gray-700 hover:bg-gray-200">
{{ currentPlan?.is_trial_plan ? '升级' : '更改' }}
</Button>
</div>
</div>
</div>
</div>
<!-- 站点信息 -->
<div class="rounded-md border">
<div class="h-12 border-b px-5 py-4">
<h2 class="text-lg font-medium text-gray-900">站点信息</h2>
</div>
<div>
<div
v-for="d in siteInformation"
:key="d.label"
class="flex items-center px-5 py-3 last:pb-5 even:bg-gray-50/70"
>
<div class="w-1/3 text-base text-gray-600">{{ d.label }}</div>
<div
class="flex w-2/3 items-center space-x-2 text-base text-gray-900"
>
<div v-if="d.prefix">
<component :is="d.prefix" />
</div>
<span>
{{ d.value }}
</span>
<div v-if="d.suffix">
<component :is="d.suffix" />
</div>
</div>
</div>
</div>
</div>
<!-- 标签 -->
<div class="flex items-center space-x-2 flex-wrap">
<Badge
v-for="tag in $site.pg.tags"
:key="tag.tag"
:label="tag.tag_name"
size="lg"
class="group"
>
<template #suffix>
<button
@click="removeTag(tag)"
class="ml-1 hidden transition group-hover:block"
>
<i-lucide-x class="mt-0.5 h-3 w-3" />
</button>
</template>
</Badge>
<Badge
variant="outline"
size="lg"
label="添加标签"
class="cursor-pointer"
@click="showAddTagDialog"
>
<template #suffix>
<i-lucide-plus class="h-3 w-3" />
</template>
</Badge>
</div>
</div>
<!-- 右侧区块计算存储数据库 -->
<div class="col-span-1 space-y-5">
<!-- 存储 -->
<div class="rounded-md border p-5">
<div
class="flex items-center justify-between text-base text-gray-700"
>
<span>存储</span>
<div class="h-7"></div>
</div>
<div class="mt-2">
<Progress
size="md"
:value="
currentPlan
? (currentUsage.storage / currentPlan.max_storage_usage) * 100
: 0
"
/>
<div>
<div class="mt-2 flex justify-between">
<div class="text-sm text-gray-600">
{{ formatBytes(currentUsage.storage) }}
<template
v-if="currentPlan && !$site.pg.is_dedicated_server"
>
{{ formatBytes(currentPlan.max_storage_usage) }}
</template>
</div>
</div>
</div>
</div>
</div>
<!-- 数据库 -->
<div class="rounded-md border p-5">
<div
class="flex min-h-[1.75rem] items-center justify-between text-base text-gray-700"
>
<span>数据库</span>
<Button
v-if="
(currentPlan
? (currentUsage.database / currentPlan.max_database_usage) *
100
: 0) >= 80
"
variant="ghost"
link="https://jingrow.com/docs/faq/site#what-is-using-up-all-my-database-size"
icon="help-circle"
/>
</div>
<div class="mt-2">
<Progress
size="md"
:value="
currentPlan
? (currentUsage.database / currentPlan.max_database_usage) *
100
: 0
"
/>
<div>
<div class="mt-2 flex justify-between">
<div class="text-sm text-gray-600">
{{ formatBytes(currentUsage.database) }}
<template
v-if="currentPlan && !$site.pg.is_dedicated_server"
>
{{ formatBytes(currentPlan.max_database_usage) }}
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { getCachedDocumentResource, Progress, Tooltip } from 'jingrow-ui';
import { h, defineAsyncComponent } from 'vue';
import { toast } from 'vue-sonner';
import InfoIcon from '~icons/lucide/info';
import DismissableBanner from './DismissableBanner.vue';
import { getToastErrorMessage } from '../utils/toast';
import { renderDialog } from '../utils/components';
import SiteDailyUsage from './SiteDailyUsage.vue';
import AlertBanner from './AlertBanner.vue';
import { trialDays } from '../utils/site';
export default {
name: 'SiteOverview',
props: ['site'],
components: {
SiteDailyUsage,
Progress,
AlertBanner,
DismissableBanner,
},
data() {
return {
isSetupWizardComplete: true,
};
},
mounted() {
if (this.$site?.pg?.status === 'Active') {
this.$site.isSetupWizardComplete.submit().then((res) => {
this.isSetupWizardComplete = res;
});
}
},
methods: {
showPlanChangeDialog() {
let SitePlansDialog = defineAsyncComponent(
() => import('../components/ManageSitePlansDialog.vue'),
);
renderDialog(h(SitePlansDialog, { site: this.site }));
},
formatBytes(v) {
return this.$format.bytes(v, 2, 2);
},
loginAsAdmin() {
this.$site.loginAsAdmin
.submit({ reason: '' })
.then((url) => window.open(url, '_blank'));
},
loginAsTeam() {
if (this.$site.pg?.additional_system_user_created) {
this.$site.loginAsTeam
.submit({ reason: '' })
.then((url) => window.open(url, '_blank'));
} else {
this.loginAsAdmin();
}
},
removeTag(tag) {
toast.promise(
this.$site.removeTag.submit({
tag: tag.tag_name,
}),
{
loading: '正在移除标签...',
success: `标签 ${tag.tag_name} 已移除`,
error: (e) => getToastErrorMessage(e),
},
);
},
showAddTagDialog() {
const TagsDialog = defineAsyncComponent(
() => import('../dialogs/TagsDialog.vue'),
);
renderDialog(h(TagsDialog, { pagetype: 'Site', docname: this.site }));
},
trialDays,
openRenewalDialog() {
const SiteRenewalDialog = defineAsyncComponent(
() => import('../components/SiteRenewalDialog.vue'),
);
renderDialog(h(SiteRenewalDialog, {
site: this.site,
onSuccess: this.onRenewalSuccess
}));
},
onRenewalSuccess(data) {
toast.success('站点续费成功!');
// 刷新站点数据
this.$site.reload();
},
},
computed: {
siteInformation() {
return [
{
label: '所有者',
value: this.$site.pg?.owner_email,
},
{
label: '创建者',
value: this.$site.pg?.owner,
},
{
label: '创建时间',
value: this.$format.date(
this.$site.pg?.signup_time || this.$site.pg?.creation,
),
},
{
label: '区域',
value: this.$site.pg?.cluster.title,
prefix: h('img', {
src: this.$site.pg?.cluster.image,
alt: this.$site.pg?.cluster.title,
class: 'h-4 w-4',
}),
},
{
label: '入站 IP',
value: this.$site.pg?.inbound_ip,
suffix: h(
Tooltip,
{
text: '用于为您的站点添加 A 记录',
},
() => h(InfoIcon, { class: 'h-4 w-4 text-gray-500' }),
),
},
{
label: '出站 IP',
value: this.$site.pg?.outbound_ip,
suffix: h(
Tooltip,
{
text: '用于在第三方服务上白名单我们的服务器',
},
() => h(InfoIcon, { class: 'h-4 w-4 text-gray-500' }),
),
},
];
},
currentPlan() {
if (!this.$site?.pg?.current_plan || !this.$team?.pg) return null;
const currency = this.$team.pg.currency;
return {
price:
currency === 'CNY'
? this.$site.pg.current_plan.price_cny
: this.$site.pg.current_plan.price_usd,
price_per_day:
currency === 'CNY'
? this.$site.pg.current_plan.price_per_day_cny
: this.$site.pg.current_plan.price_per_day_usd,
currency: currency === 'CNY' ? '¥' : '$',
...this.$site.pg.current_plan,
};
},
currentUsage() {
return this.$site.pg?.current_usage;
},
$site() {
return getCachedDocumentResource('Site', this.site);
},
},
};
</script>