427 lines
12 KiB
Vue
427 lines
12 KiB
Vue
<template>
|
||
<Card title="个人资料" v-if="user" class="mx-auto max-w-3xl">
|
||
<div class="flex items-center border-b pb-3">
|
||
<div class="relative">
|
||
<Avatar size="2xl" :label="user.first_name" :image="user.user_image" />
|
||
<FileUploader
|
||
@success="onProfilePhotoChange"
|
||
fileTypes="image/*"
|
||
:upload-args="{
|
||
pagetype: 'User',
|
||
docname: user.name,
|
||
method: 'jcloud.api.account.update_profile_picture',
|
||
}"
|
||
>
|
||
<template v-slot="{ openFileSelector, uploading, progress, error }">
|
||
<div class="ml-4">
|
||
<button
|
||
@click="openFileSelector()"
|
||
class="absolute inset-0 grid h-10 w-full place-items-center rounded-full bg-black text-xs font-medium text-white opacity-0 transition hover:opacity-50 focus:opacity-50 focus:outline-none"
|
||
:class="{ 'opacity-50': uploading }"
|
||
>
|
||
<span v-if="uploading">{{ progress }}%</span>
|
||
<span v-else>编辑</span>
|
||
</button>
|
||
</div>
|
||
</template>
|
||
</FileUploader>
|
||
</div>
|
||
<div class="ml-4">
|
||
<h3 class="text-base font-semibold">
|
||
{{ user.first_name }} {{ user.last_name }}
|
||
</h3>
|
||
<p class="mt-1 text-base text-gray-600">用户名:{{ user.username }}</p>
|
||
<p class="mt-1 text-base text-gray-600">手机:{{ user.mobile_no }}</p>
|
||
<p class="mt-1 text-base text-gray-600">邮箱:{{ user.email }}</p>
|
||
</div>
|
||
<div class="ml-auto">
|
||
<Button icon-left="edit" @click="showProfileEditDialog = true">
|
||
编辑
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<ListItem
|
||
title="应用市场开发者"
|
||
subtitle="开发者可以在应用市场发布自己的应用,供用户付费或免费订阅。"
|
||
v-if="!$team.pg.is_developer"
|
||
>
|
||
<template #actions>
|
||
<Button @click="confirmPublisherAccount">
|
||
<span>成为开发者</span>
|
||
</Button>
|
||
</template>
|
||
</ListItem>
|
||
<ListItem
|
||
:title="user.is_2fa_enabled ? '禁用双因素认证' : '启用双因素认证'"
|
||
:subtitle="
|
||
user.is_2fa_enabled
|
||
? '为您的账户禁用双因素认证'
|
||
: '为您的账户启用双因素认证以增加额外的安全层'
|
||
"
|
||
>
|
||
<template #actions>
|
||
<Button @click="show2FADialog = true">
|
||
{{ user.is_2fa_enabled ? '禁用' : '启用' }}
|
||
</Button>
|
||
</template>
|
||
</ListItem>
|
||
<ListItem
|
||
:title="teamEnabled ? '禁用账户' : '启用账户'"
|
||
:subtitle="
|
||
teamEnabled
|
||
? '禁用您的账户并停止计费'
|
||
: '启用您的账户并恢复计费'
|
||
"
|
||
>
|
||
<template #actions>
|
||
<Button
|
||
@click="
|
||
() => {
|
||
if (teamEnabled) {
|
||
showDisableAccountDialog = true;
|
||
} else {
|
||
showEnableAccountDialog = true;
|
||
}
|
||
}
|
||
"
|
||
>
|
||
<span :class="{ 'text-red-600': teamEnabled }">{{
|
||
teamEnabled ? '禁用' : '启用'
|
||
}}</span>
|
||
</Button>
|
||
</template>
|
||
</ListItem>
|
||
</div>
|
||
<Dialog
|
||
:options="{
|
||
title: '更新个人资料信息',
|
||
actions: [
|
||
{
|
||
variant: 'solid',
|
||
label: '保存更改',
|
||
onClick: () => $resources.updateProfile.submit(),
|
||
},
|
||
],
|
||
}"
|
||
v-model="showProfileEditDialog"
|
||
>
|
||
<template v-slot:body-content>
|
||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||
<FormControl label="个人名称" v-model="user.first_name" />
|
||
<FormControl label="用户名" v-model="user.username" />
|
||
<FormControl label="手机" v-model="user.mobile_no" />
|
||
<FormControl label="邮箱" v-model="user.email" />
|
||
</div>
|
||
<ErrorMessage class="mt-4" :message="$resources.updateProfile.error" />
|
||
</template>
|
||
</Dialog>
|
||
|
||
<Dialog
|
||
:options="{
|
||
title: '禁用账户',
|
||
actions: [
|
||
{
|
||
label: '禁用账户',
|
||
variant: 'solid',
|
||
theme: 'red',
|
||
loading: $resources.disableAccount.loading,
|
||
onClick: () => deactivateAccount(disableAccount2FACode),
|
||
},
|
||
],
|
||
}"
|
||
v-model="showDisableAccountDialog"
|
||
>
|
||
<template v-slot:body-content>
|
||
<div class="prose text-base">
|
||
确认此操作后:
|
||
<ul>
|
||
<li>您的账户将被禁用</li>
|
||
<li>
|
||
您激活的站点将立即暂停,并在一周后被删除。
|
||
</li>
|
||
<li>您的账户计费将停止</li>
|
||
</ul>
|
||
您可以稍后登录以重新启用您的账户。您要继续吗?
|
||
</div>
|
||
<FormControl
|
||
v-if="user.is_2fa_enabled"
|
||
class="mt-4"
|
||
label="输入您的2FA代码以确认"
|
||
v-model="disableAccount2FACode"
|
||
/>
|
||
<ErrorMessage class="mt-2" :message="$resources.disableAccount.error" />
|
||
</template>
|
||
</Dialog>
|
||
|
||
<Dialog
|
||
:options="{
|
||
title: '启用账户',
|
||
actions: [
|
||
{
|
||
label: '启用账户',
|
||
variant: 'solid',
|
||
loading: $resources.enableAccount.loading,
|
||
onClick: () => $resources.enableAccount.submit(),
|
||
},
|
||
],
|
||
}"
|
||
v-model="showEnableAccountDialog"
|
||
>
|
||
<template v-slot:body-content>
|
||
<div class="prose text-base">
|
||
确认此操作后:
|
||
<ul>
|
||
<li>您的账户将被启用</li>
|
||
<li>您暂停的站点将重新激活</li>
|
||
<li>您的账户计费将恢复</li>
|
||
</ul>
|
||
您要继续吗?
|
||
</div>
|
||
<ErrorMessage class="mt-2" :message="$resources.enableAccount.error" />
|
||
</template>
|
||
</Dialog>
|
||
|
||
<AddPrepaidCreditsDialog
|
||
:showMessage="showMessage"
|
||
v-if="showAddPrepaidCreditsDialog"
|
||
v-model="showAddPrepaidCreditsDialog"
|
||
@success="reloadAccount"
|
||
/>
|
||
</Card>
|
||
<TFADialog v-model="show2FADialog" />
|
||
</template>
|
||
|
||
<script>
|
||
import { toast } from 'vue-sonner';
|
||
import { defineAsyncComponent, h } from 'vue';
|
||
import FileUploader from '@/components/FileUploader.vue';
|
||
import { confirmDialog, renderDialog } from '../../../utils/components';
|
||
import TFADialog from './TFADialog.vue';
|
||
import router from '../../../router';
|
||
import AddPrepaidCreditsDialog from '../../billing/AddPrepaidCreditsDialog.vue';
|
||
|
||
export default {
|
||
name: 'AccountProfile',
|
||
components: {
|
||
TFADialog,
|
||
FileUploader,
|
||
AddPrepaidCreditsDialog,
|
||
},
|
||
data() {
|
||
return {
|
||
show2FADialog: false,
|
||
disableAccount2FACode: '',
|
||
showProfileEditDialog: false,
|
||
showEnableAccountDialog: false,
|
||
showDisableAccountDialog: false,
|
||
showAddPrepaidCreditsDialog: false,
|
||
showActiveServersDialog: false,
|
||
showMessage: false,
|
||
draftInvoice: {},
|
||
unpaidInvoices: [] | {},
|
||
};
|
||
},
|
||
computed: {
|
||
teamEnabled() {
|
||
return this.$team.pg.enabled;
|
||
},
|
||
user() {
|
||
return this.$team?.pg?.user_info;
|
||
},
|
||
},
|
||
resources: {
|
||
updateProfile() {
|
||
let { first_name, last_name, email, username, mobile_no } = this.user;
|
||
return {
|
||
url: 'jcloud.api.account.update_profile',
|
||
params: {
|
||
first_name,
|
||
last_name,
|
||
email,
|
||
username,
|
||
mobile_no,
|
||
},
|
||
onSuccess() {
|
||
this.showProfileEditDialog = false;
|
||
this.notifySuccess();
|
||
},
|
||
};
|
||
},
|
||
disableAccount: {
|
||
url: 'jcloud.api.account.disable_account',
|
||
onSuccess() {
|
||
this.showDisableAccountDialog = false;
|
||
|
||
const ChurnFeedbackDialog = defineAsyncComponent(
|
||
() => import('../../ChurnFeedbackDialog.vue'),
|
||
);
|
||
|
||
renderDialog(
|
||
h(ChurnFeedbackDialog, {
|
||
team: this.$team.pg.name,
|
||
onUpdated: () => {
|
||
toast.success('您的反馈已成功提交');
|
||
},
|
||
}),
|
||
);
|
||
toast.success('您的账户已成功禁用');
|
||
this.reloadAccount();
|
||
},
|
||
},
|
||
enableAccount: {
|
||
url: 'jcloud.api.account.enable_account',
|
||
onSuccess() {
|
||
toast.success('您的账户已成功启用');
|
||
this.reloadAccount();
|
||
this.showEnableAccountDialog = false;
|
||
},
|
||
},
|
||
upcomingInvoice: {
|
||
url: 'jcloud.api.billing.upcoming_invoice',
|
||
auto: true,
|
||
onSuccess(data) {
|
||
this.draftInvoice = data.upcoming_invoice;
|
||
},
|
||
},
|
||
unPaidInvoices: {
|
||
url: 'jcloud.api.billing.get_unpaid_invoices',
|
||
auto: true,
|
||
onSuccess(data) {
|
||
this.unpaidInvoices = data;
|
||
},
|
||
},
|
||
hasActiveServers() {
|
||
return {
|
||
url: 'jcloud.api.account.has_active_servers',
|
||
auto: true,
|
||
params: {
|
||
team: this.$team.pg.name,
|
||
},
|
||
onSuccess(data) {
|
||
if (data) {
|
||
this.showActiveServersDialog = true;
|
||
}
|
||
},
|
||
};
|
||
},
|
||
},
|
||
methods: {
|
||
reloadAccount() {
|
||
this.$team.reload();
|
||
},
|
||
onProfilePhotoChange() {
|
||
this.reloadAccount();
|
||
this.notifySuccess();
|
||
},
|
||
notifySuccess() {
|
||
toast.success('您的个人资料已成功更新');
|
||
},
|
||
deactivateAccount(disableAccount2FACode) {
|
||
const currency = this.$team.pg.currency;
|
||
const minAmount = currency === 'CNY' ? 0.01 : 0.01;
|
||
if (this.draftInvoice && this.draftInvoice.amount_due > minAmount) {
|
||
const finalizeInvoicesDialog = defineAsyncComponent(
|
||
() => import('../../billing/FinalizeInvoicesDialog.vue'),
|
||
);
|
||
renderDialog(h(finalizeInvoicesDialog));
|
||
} else if (this.unpaidInvoices) {
|
||
if (this.unpaidInvoices.length > 1) {
|
||
this.showDisableAccountDialog = false;
|
||
if (this.$team.pg.payment_mode === 'Prepaid Credits') {
|
||
this.showAddPrepaidCreditsDialog = true;
|
||
} else {
|
||
confirmDialog({
|
||
title: '多张未支付发票',
|
||
message:
|
||
'您有多张未支付的发票。请从发票页面支付它们',
|
||
primaryAction: {
|
||
label: '前往发票',
|
||
variant: 'solid',
|
||
onClick: ({ hide }) => {
|
||
router.push({ name: 'BillingInvoices' });
|
||
hide();
|
||
},
|
||
},
|
||
});
|
||
}
|
||
} else {
|
||
let invoice = this.unpaidInvoices;
|
||
if (invoice.amount_due > minAmount) {
|
||
this.showDisableAccountDialog = false;
|
||
confirmDialog({
|
||
title: '清除未支付发票',
|
||
message: `您有一张未支付的发票,金额为${
|
||
invoice.currency === 'CNY' ? '¥' : '$'
|
||
} ${
|
||
invoice.amount_due
|
||
}。请在停用账户前结清它。`,
|
||
primaryAction: {
|
||
label: '立即结算',
|
||
variant: 'solid',
|
||
onClick: ({ hide }) => {
|
||
if (
|
||
invoice.stripe_invoice_url &&
|
||
this.$team.pg.payment_mode === 'Card'
|
||
) {
|
||
window.open(
|
||
`/api/action/jcloud.api.client.run_pg_method?dt=Invoice&dn=${invoice.name}&method=stripe_payment_url`,
|
||
);
|
||
} else {
|
||
this.showAddPrepaidCreditsDialog = true;
|
||
}
|
||
hide();
|
||
},
|
||
},
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// validate if any active servers
|
||
if (this.showActiveServersDialog) {
|
||
const activeServersDialog = defineAsyncComponent(
|
||
() => import('../../ActiveServersDialog.vue'),
|
||
);
|
||
renderDialog(h(activeServersDialog));
|
||
return;
|
||
}
|
||
this.$resources.disableAccount.submit({
|
||
totp_code: disableAccount2FACode,
|
||
});
|
||
},
|
||
confirmPublisherAccount() {
|
||
confirmDialog({
|
||
title: '成为市场应用开发者?',
|
||
message:
|
||
'确认后,您将能够将应用发布到我们的市场。',
|
||
onSuccess: ({ hide }) => {
|
||
toast.promise(
|
||
this.$team.setValue.submit(
|
||
{
|
||
is_developer: 1,
|
||
},
|
||
{
|
||
onSuccess: () => {
|
||
hide();
|
||
this.$router.push({
|
||
name: 'Marketplace App List',
|
||
});
|
||
},
|
||
onError(e) {
|
||
console.error(e);
|
||
},
|
||
},
|
||
),
|
||
{
|
||
success: '您现在可以将应用发布到我们的市场',
|
||
error: '标记您为开发者失败',
|
||
loading: '正在将您设为开发者...',
|
||
},
|
||
);
|
||
},
|
||
});
|
||
},
|
||
},
|
||
};
|
||
</script> |