Translate VIP popup

pull/436/head
Lorenzo 2025-06-26 00:08:13 +02:00
parent daaef316e9
commit 34ec6210c6
3 changed files with 401 additions and 239 deletions

View File

@ -1,19 +1,15 @@
<template>
<div
v-if="!settingStore.isComm || userStore.isAdmin"
class="layout-vip isPlus"
@click="openUpgrade"
>
<contextHolder />
<fs-icon icon="mingcute:vip-1-line" :title="text.title" />
<div v-if="!settingStore.isComm || userStore.isAdmin" class="layout-vip isPlus" @click="openUpgrade">
<contextHolder />
<fs-icon icon="mingcute:vip-1-line" :title="text.title" />
<div v-if="mode !== 'icon'" class="text hidden md:block ml-0.5">
<a-tooltip>
<template #title> {{ text.title }}</template>
<span class="">{{ text.name }}</span>
</a-tooltip>
</div>
</div>
<div v-if="mode !== 'icon'" class="text hidden md:block ml-0.5">
<a-tooltip>
<template #title> {{ text.title }}</template>
<span class="">{{ text.name }}</span>
</a-tooltip>
</div>
</div>
</template>
<script lang="tsx" setup>
import { computed, onMounted, reactive } from "vue";
@ -133,22 +129,24 @@ const formState = reactive({
const router = useRouter();
async function doActive() {
if (!formState.code) {
message.error("请输入激活码");
throw new Error("请输入激活码");
message.error(t("vip.enterCode"));
throw new Error(t("vip.enterCode"));
}
const res = await api.doActive(formState);
if (res) {
await settingStore.init();
const vipLabel = settingStore.vipLabel;
Modal.success({
title: "激活成功",
content: `您已成功激活${vipLabel},有效期至:${dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD")}`,
title: t("vip.successTitle"),
content: t("vip.successContent", {
vipLabel,
expireDate: dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD"),
}),
onOk() {
if (!(settingStore.installInfo.bindUserId > 0)) {
//
Modal.confirm({
title: "是否绑定袖手账号",
content: "绑定账号后可以避免License丢失强烈建议绑定",
title: t("vip.bindAccountTitle"),
content: t("vip.bindAccountContent"),
onOk() {
router.push("/sys/account");
},
@ -159,6 +157,7 @@ async function doActive() {
}
}
const computedSiteId = computed(() => settingStore.installInfo?.siteId);
const [modal, contextHolder] = Modal.useModal();
const userStore = useUserStore();
@ -170,16 +169,17 @@ function goAccount() {
async function getVipTrial() {
const res = await api.getVipTrial();
message.success(`恭喜,您已获得专业版${res.duration}天试用`);
message.success(t('vip.congratulations_vip_trial', { duration: res.duration }));
await settingStore.init();
}
function openTrialModal() {
Modal.destroyAll();
modal.confirm({
title: "7天专业版试用获取",
okText: "立即获取",
title: t('vip.trial_modal_title'),
okText: t('vip.trial_modal_ok_text'),
onOk() {
getVipTrial();
},
@ -187,14 +187,15 @@ function openTrialModal() {
content: () => {
return (
<div class="flex-col mt-10 mb-10">
<div>感谢您对开源项目的支持</div>
<div>点击确认即可获取7天专业版试用</div>
<div>{t('vip.trial_modal_thanks')}</div>
<div>{t('vip.trial_modal_click_confirm')}</div>
</div>
);
},
});
}
function openStarModal() {
Modal.destroyAll();
const goGithub = () => {
@ -202,8 +203,8 @@ function openStarModal() {
};
modal.confirm({
title: "7天专业版试用获取",
okText: "立即去Star",
title: t("vip.get_7_day_pro_trial"),
okText: t("vip.star_now"),
onOk() {
goGithub();
openTrialModal();
@ -212,7 +213,7 @@ function openStarModal() {
content: () => {
return (
<div class="flex mt-10 mb-10">
<div>可以先请您帮忙点个star吗感谢感谢</div>
<div>{t("vip.please_help_star")}</div>
<img class="ml-5" src="https://img.shields.io/github/stars/certd/certd?logo=github" />
</div>
);
@ -220,174 +221,207 @@ function openStarModal() {
});
}
function openUpgrade() {
if (!userStore.isAdmin) {
message.info("仅限管理员操作");
return;
}
const placeholder = "请输入激活码";
const isPlus = settingStore.isPlus;
let title = "激活专业版/商业版";
if (settingStore.isComm) {
title = "续期商业版";
} else if (settingStore.isPlus) {
title = "续期专业版/升级商业版";
}
if (!userStore.isAdmin) {
message.info(t("vip.admin_only_operation"));
return;
}
const placeholder = t("vip.enter_activation_code");
const isPlus = settingStore.isPlus;
let title = t("vip.activate_pro_business");
if (settingStore.isComm) {
title = t("vip.renew_business");
} else if (settingStore.isPlus) {
title = t("vip.renew_pro_upgrade_business");
}
const productInfo = settingStore.productInfo;
const vipTypeDefine = {
free: {
title: "基础版",
desc: "社区免费版",
type: "free",
icon: "lucide:package-open",
privilege: ["证书申请无限制", "域名数量无限制", "证书流水线数量无限制", "常用的主机、云平台、cdn、宝塔、1Panel等部署插件", "邮件、webhook通知方式"],
},
plus: {
title: "专业版",
desc: "开源需要您的赞助支持",
type: "plus",
privilege: ["可加VIP群您的需求将优先实现", "站点证书监控无限制", "更多通知方式", "插件全开放,群辉等更多插件"],
trial: {
title: "点击获取7天试用",
click: () => {
openStarModal();
},
},
icon: "stash:thumb-up",
price: productInfo.plus.price,
price3: `¥${productInfo.plus.price3}/3年`,
tooltip: productInfo.plus.tooltip,
get() {
return (
<a-tooltip title="爱发电赞助“VIP会员”后获取一年期专业版激活码开源需要您的支持">
<a-button size="small" type="primary" href="https://afdian.com/a/greper" target="_blank">
爱发电赞助后获取
</a-button>
</a-tooltip>
);
},
},
comm: {
title: "商业版",
desc: "商业授权,可对外运营",
type: "comm",
icon: "vaadin:handshake",
privilege: ["拥有专业版所有特权", "允许商用可修改logo、标题", "数据统计", "插件管理", "多用户无限制", "支持用户支付"],
price: productInfo.comm.price,
price3: `¥${productInfo.comm.price3}/3年`,
tooltip: productInfo.comm.tooltip,
get() {
return <a-button size="small">请联系作者获取试用</a-button>;
},
},
};
const modalRef = modal.confirm({
title,
async onOk() {
return await doActive();
const productInfo = settingStore.productInfo;
const vipTypeDefine = {
free: {
title: t("vip.basic_edition"),
desc: t("vip.community_free_version"),
type: "free",
icon: "lucide:package-open",
privilege: [
t("vip.unlimited_certificate_application"),
t("vip.unlimited_domain_count"),
t("vip.unlimited_certificate_pipelines"),
t("vip.common_deployment_plugins"),
t("vip.email_webhook_notifications"),
],
},
plus: {
title: t("vip.professional_edition"),
desc: t("vip.open_source_support"),
type: "plus",
privilege: [
t("vip.vip_group_priority"),
t("vip.unlimited_site_certificate_monitoring"),
t("vip.more_notification_methods"),
t("vip.plugins_fully_open"),
],
trial: {
title: t("vip.click_to_get_7_day_trial"),
click: () => {
openStarModal();
},
},
maskClosable: true,
okText: "激活",
width: 1000,
content: () => {
let activationCodeGetWay = (
<span>
<a href="https://afdian.com/a/greper" target="_blank">
爱发电赞助VIP会员后获取一年期专业版激活码
</a>
<span> 商业版请直接联系作者</span>
</span>
icon: "stash:thumb-up",
price: productInfo.plus.price,
price3: `¥${productInfo.plus.price3}/3${t("vip.years")}`,
tooltip: productInfo.plus.tooltip,
get() {
return (
<a-tooltip title={t("vip.afdian_support_vip")}>
<a-button size="small" type="primary" href="https://afdian.com/a/greper" target="_blank">
{t("vip.get_after_support")}
</a-button>
</a-tooltip>
);
const vipLabel = settingStore.vipLabel;
const slots = [];
for (const key in vipTypeDefine) {
// @ts-ignore
const item = vipTypeDefine[key];
const vipBlockClass = `vip-block ${key === settingStore.plusInfo.vipType ? "current" : ""}`;
slots.push(
<a-col span={8}>
<div class={vipBlockClass}>
<h3 class="block-header ">
<span class="flex-o">{item.title}</span>
{item.trial && (
<span class="trial">
<a-tooltip title={item.trial.message}>
<a onClick={item.trial.click}>{item.trial.title}</a>
},
},
comm: {
title: t("vip.business_edition"),
desc: t("vip.commercial_license"),
type: "comm",
icon: "vaadin:handshake",
privilege: [
t("vip.all_pro_privileges"),
t("vip.allow_commercial_use_modify_logo_title"),
t("vip.data_statistics"),
t("vip.plugin_management"),
t("vip.unlimited_multi_users"),
t("vip.support_user_payment"),
],
price: productInfo.comm.price,
price3: `¥${productInfo.comm.price3}/3${t("vip.years")}`,
tooltip: productInfo.comm.tooltip,
get() {
return <a-button size="small">{t("vip.contact_author_for_trial")}</a-button>;
},
},
};
const modalRef = modal.confirm({
title,
async onOk() {
return await doActive();
},
maskClosable: true,
okText: t("vip.activate"),
width: 1000,
content: () => {
let activationCodeGetWay = (
<span>
<a href="https://afdian.com/a/greper" target="_blank">
{t("vip.get_pro_code_after_support")}
</a>
<span> {t("vip.business_contact_author")}</span>
</span>
);
const vipLabel = settingStore.vipLabel;
const slots = [];
for (const key in vipTypeDefine) {
// @ts-ignore
const item = vipTypeDefine[key];
const vipBlockClass = `vip-block ${key === settingStore.plusInfo.vipType ? "current" : ""}`;
slots.push(
<a-col span={8}>
<div class={vipBlockClass}>
<h3 class="block-header ">
<span class="flex-o">{item.title}</span>
{item.trial && (
<span class="trial">
<a-tooltip title={item.trial.message}>
<a onClick={item.trial.click}>{item.trial.title}</a>
</a-tooltip>
</span>
)}
</h3>
<div style="color:green" class="flex-o">
<fs-icon icon={item.icon} class="fs-16 flex-o" />
{item.desc}
</div>
<ul class="flex-1 privilege">
{item.privilege.map((p: string) => (
<li class="flex-baseline">
<fs-icon class="color-green" icon="ion:checkmark-sharp" />
{p}
</li>
))}
</ul>
<div class="footer flex-between flex-vc">
<div class="price-show">
{item.price && (
<span class="flex">
<span class="-text">¥{item.price}</span>
<span>/</span>
{t("vip.year")}
<a-tooltip class="ml-5" title={item.price3}>
<fs-icon class="pointer color-red" icon="ic:outline-discount"></fs-icon>
</a-tooltip>
</span>
)}
</h3>
<div style="color:green" class="flex-o">
<fs-icon icon={item.icon} class="fs-16 flex-o" />
{item.desc}
</div>
<ul class="flex-1 privilege">
{item.privilege.map((p: string) => (
<li class="flex-baseline">
<fs-icon class="color-green" icon="ion:checkmark-sharp" />
{p}
</li>
))}
</ul>
<div class="footer flex-between flex-vc">
<div class="price-show">
{item.price && (
<span class="flex">
<span class="-text">¥{item.price}</span>
<span>/</span>
<a-tooltip class="ml-5" title={item.price3}>
<fs-icon class="pointer color-red" icon="ic:outline-discount"></fs-icon>
</a-tooltip>
</span>
)}
{!item.price && (
<span>
<span class="price-text">免费</span>
</span>
)}
</div>
<div class="get-show">{item.get && <div>{item.get()}</div>}</div>
{!item.price && (
<span>
<span class="price-text">{t("vip.freee")}</span>
</span>
)}
</div>
<div class="get-show">{item.get && <div>{item.get()}</div>}</div>
</div>
</a-col>
);
}
return (
<div class="mt-10 mb-10 vip-active-modal">
{productInfo.notice && (
<div class="mb-10">
<a-alert type="error" message={productInfo.notice}></a-alert>
</div>
)}
<div class="vip-type-vs">
<a-row gutter={20}>{slots}</a-row>
</div>
</a-col>
);
}
return (
<div class="mt-10 mb-10 vip-active-modal">
{productInfo.notice && (
<div class="mb-10">
<a-alert type="error" message={productInfo.notice}></a-alert>
</div>
)}
<div class="vip-type-vs">
<a-row gutter={20}>{slots}</a-row>
</div>
<div class="mt-10">
<h3 class="block-header">{isPlus ? t("vip.renew") : t("vip.activate_immediately")}</h3>
<div>
{isPlus
? `${t("vip.current")} ${vipLabel} ${t("vip.activated_expire_time")}` +
dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD")
: ""}
</div>
<div class="mt-10">
<h3 class="block-header">{isPlus ? "续期" : "立刻激活"}</h3>
<div>{isPlus ? `当前${vipLabel}已激活,到期时间` + dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD") : ""}</div>
<div class="mt-10">
<div class="flex-o w-100">
<span>站点ID</span>
<fs-copyable class="flex-1" v-model={computedSiteId.value}></fs-copyable>
</div>
<a-input class="mt-10" v-model:value={formState.code} placeholder={placeholder} />
<a-input class="mt-10" v-model:value={formState.inviteCode} placeholder={"邀请码【选填】可额外获得专业版30天/商业版15天时长"} />
<div class="flex-o w-100">
<span>{t("vip.site_id")}</span>
<fs-copyable class="flex-1" v-model={computedSiteId.value}></fs-copyable>
</div>
<a-input class="mt-10" v-model:value={formState.code} placeholder={placeholder} />
<a-input
class="mt-10"
v-model:value={formState.inviteCode}
placeholder={t("vip.invite_code_optional")}
/>
</div>
<div class="mt-10">
没有激活码
{activationCodeGetWay}
</div>
<div class="mt-10">
激活码使用过一次之后不可再次使用如果要更换站点<a onClick={goAccount}>绑定账号</a>然后"转移VIP"即可
</div>
<div class="mt-10">
{t("vip.no_activation_code")}
{activationCodeGetWay}
</div>
<div class="mt-10">
{t("vip.activation_code_one_use")}<a onClick={goAccount}>{t("vip.bind_account")}</a>
{t("vip.transfer_vip")}
</div>
</div>
);
},
});
</div>
);
},
});
}
onMounted(() => {
mitter.on("openVipModal", () => {
@ -400,69 +434,75 @@ onMounted(() => {
<style lang="less">
.layout-vip {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&.isPlus {
color: #c5913f;
}
&.isPlus {
color: #c5913f;
}
.text {
}
.text {}
}
.vip-active-modal {
.vip-block {
display: flex;
flex-direction: column;
padding: 10px;
border: 1px solid #eee;
border-radius: 5px;
height: 250px;
//background-color: rgba(250, 237, 167, 0.79);
&.current {
border-color: green;
}
.block-header {
padding: 0px;
display: flex;
justify-content: space-between;
.trial {
font-size: 12px;
font-wight: 400;
}
}
.vip-block {
display: flex;
flex-direction: column;
padding: 10px;
border: 1px solid #eee;
border-radius: 5px;
height: 250px;
.footer {
padding-top: 5px;
margin-top: 0px;
border-top: 1px solid #eee;
.price-text {
font-size: 18px;
color: red;
}
}
}
//background-color: rgba(250, 237, 167, 0.79);
&.current {
border-color: green;
}
ul {
list-style-type: unset;
margin-left: 0px;
padding: 0;
}
.color-green {
color: green;
}
.vip-type-vs {
.privilege {
.fs-icon {
color: green;
}
}
.fs-icon {
margin-right: 5px;
}
}
.block-header {
padding: 0px;
display: flex;
justify-content: space-between;
.trial {
font-size: 12px;
font-wight: 400;
}
}
.footer {
padding-top: 5px;
margin-top: 0px;
border-top: 1px solid #eee;
.price-text {
font-size: 18px;
color: red;
}
}
}
ul {
list-style-type: unset;
margin-left: 0px;
padding: 0;
}
.color-green {
color: green;
}
.vip-type-vs {
.privilege {
.fs-icon {
color: green;
}
}
.fs-icon {
margin-right: 5px;
}
}
}
</style>

View File

@ -22,4 +22,65 @@ export default {
title: "Upgrade to Advanced for more VIP privileges",
},
},
enterCode: "Please enter the activation code",
successTitle: "Activation Successful",
successContent: "You have successfully activated {vipLabel}, valid until: {expireDate}",
bindAccountTitle: "Bind Your Account",
bindAccountContent: "Binding your account helps prevent license loss. Strongly recommended.",
congratulations_vip_trial: 'Congratulations, you have received a Pro version {duration} days trial',
trial_modal_title: '7-day Pro version trial acquisition',
trial_modal_ok_text: 'Get now',
trial_modal_thanks: 'Thank you for supporting the open source project',
trial_modal_click_confirm: 'Click confirm to get a 7-day Pro version trial',
get_7_day_pro_trial: "7-day professional version trial",
star_now: "Star Now",
please_help_star: "Could you please help by starring? Thanks a lot!",
admin_only_operation: "Admin operation only",
enter_activation_code: "Please enter the activation code",
activate_pro_business: "Activate Professional/Business Edition",
renew_business: "Renew Business Edition",
renew_pro_upgrade_business: "Renew Professional Edition / Upgrade to Business Edition",
basic_edition: "Basic Edition",
community_free_version: "Community Free Version",
unlimited_certificate_application: "Unlimited certificate applications",
unlimited_domain_count: "Unlimited domain count",
unlimited_certificate_pipelines: "Unlimited certificate pipelines",
common_deployment_plugins: "Common host, cloud platform, CDN, Baota, 1Panel deployment plugins",
email_webhook_notifications: "Email, webhook notification methods",
professional_edition: "Professional Edition",
open_source_support: "Open source requires your sponsorship support",
vip_group_priority: "Access to VIP group, your requests will have priority",
unlimited_site_certificate_monitoring: "Unlimited site certificate monitoring",
more_notification_methods: "More notification methods",
plugins_fully_open: "All plugins open, including Synology and more",
click_to_get_7_day_trial: "Click to get 7-day trial",
years: "years",
afdian_support_vip: 'Get a one-year professional activation code after supporting "VIP membership" on Afdian, open source needs your support',
get_after_support: "Get after sponsoring",
business_edition: "Business Edition",
commercial_license: "Commercial license, allowed for external operation",
all_pro_privileges: "All professional edition privileges",
allow_commercial_use_modify_logo_title: "Allows commercial use, can modify logo and title",
data_statistics: "Data statistics",
plugin_management: "Plugin management",
unlimited_multi_users: "Unlimited multi-users",
support_user_payment: "Supports user payments",
contact_author_for_trial: "Please contact the author for trial",
activate: "Activate",
get_pro_code_after_support: 'Get a one-year professional activation code after supporting "VIP membership" on Afdian',
business_contact_author: "Business edition please contact the author directly",
year: "year",
freee: "Free",
renew: "Renew",
activate_immediately: "Activate Immediately",
current: "Current",
activated_expire_time: " activated, expiration date: ",
site_id: "Site ID",
invite_code_optional: "Invite code [optional], can get extra 30 days for Professional / 15 days for Business",
no_activation_code: "No activation code?",
activation_code_one_use: "Activation code can only be used once. To change site, please ",
bind_account: "bind account",
transfer_vip: ' then "Transfer VIP"',
}

View File

@ -21,5 +21,66 @@ export default {
name: "基础版",
title: "升级专业版享受更多VIP特权",
},
}
},
enterCode: "请输入激活码",
successTitle: "激活成功",
successContent: "您已成功激活{vipLabel},有效期至:{expireDate}",
bindAccountTitle: "是否绑定袖手账号",
bindAccountContent: "绑定账号后可以避免License丢失强烈建议绑定",
congratulations_vip_trial: '恭喜,您已获得专业版{duration}天试用',
trial_modal_title: '7天专业版试用获取',
trial_modal_ok_text: '立即获取',
trial_modal_thanks: '感谢您对开源项目的支持',
trial_modal_click_confirm: '点击确认即可获取7天专业版试用',
get_7_day_pro_trial: "7天专业版试用获取",
star_now: "立即去Star",
please_help_star: "可以先请您帮忙点个star吗感谢感谢",
admin_only_operation: "仅限管理员操作",
enter_activation_code: "请输入激活码",
activate_pro_business: "激活专业版/商业版",
renew_business: "续期商业版",
renew_pro_upgrade_business: "续期专业版/升级商业版",
basic_edition: "基础版",
community_free_version: "社区免费版",
unlimited_certificate_application: "证书申请无限制",
unlimited_domain_count: "域名数量无限制",
unlimited_certificate_pipelines: "证书流水线数量无限制",
common_deployment_plugins: "常用的主机、云平台、cdn、宝塔、1Panel等部署插件",
email_webhook_notifications: "邮件、webhook通知方式",
professional_edition: "专业版",
open_source_support: "开源需要您的赞助支持",
vip_group_priority: "可加VIP群您的需求将优先实现",
unlimited_site_certificate_monitoring: "站点证书监控无限制",
more_notification_methods: "更多通知方式",
plugins_fully_open: "插件全开放,群辉等更多插件",
click_to_get_7_day_trial: "点击获取7天试用",
years: "年",
afdian_support_vip: '爱发电赞助“VIP会员”后获取一年期专业版激活码开源需要您的支持',
get_after_support: "爱发电赞助后获取",
business_edition: "商业版",
commercial_license: "商业授权,可对外运营",
all_pro_privileges: "拥有专业版所有特权",
allow_commercial_use_modify_logo_title: "允许商用可修改logo、标题",
data_statistics: "数据统计",
plugin_management: "插件管理",
unlimited_multi_users: "多用户无限制",
support_user_payment: "支持用户支付",
contact_author_for_trial: "请联系作者获取试用",
activate: "激活",
get_pro_code_after_support: '爱发电赞助“VIP会员”后获取一年期专业版激活码',
business_contact_author: "商业版请直接联系作者",
year: "年",
freee: "免费",
renew: "续期",
activate_immediately: "立刻激活",
current: "当前",
activated_expire_time: "已激活,到期时间:",
site_id: "站点ID",
invite_code_optional: "邀请码【选填】可额外获得专业版30天/商业版15天时长",
no_activation_code: "没有激活码?",
activation_code_one_use: "激活码使用过一次之后,不可再次使用,如果要更换站点,请",
bind_account: "绑定账号",
transfer_vip: ',然后"转移VIP"即可',
}