389 lines
10 KiB
Vue
389 lines
10 KiB
Vue
<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-blue-600 !hover:bg-blue-700 !active:bg-blue-800 !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> |