美化dashboard个人资料页面
This commit is contained in:
parent
023cbf428c
commit
34577e321d
@ -1,114 +1,140 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="mt-6 flex items-center justify-center"
|
||||
v-if="$resources.qrUrl.loading"
|
||||
>
|
||||
<LoadingText />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="is2FAEnabled && $route.name !== 'Enable2FA'"
|
||||
class="space-y-4"
|
||||
>
|
||||
<AlertBanner
|
||||
:title="$t('If you disable two-factor authentication, your account will become insecure')"
|
||||
type="error"
|
||||
/>
|
||||
<n-spin :show="$resources.qrUrl.loading">
|
||||
<n-space vertical :size="24">
|
||||
<!-- 禁用 2FA 模式 -->
|
||||
<div v-if="is2FAEnabled && $route.name !== 'Enable2FA'">
|
||||
<n-alert
|
||||
type="error"
|
||||
:title="$t('If you disable two-factor authentication, your account will become insecure')"
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<!-- 用户需要遵循的步骤 -->
|
||||
<div class="rounded border border-gray-200 bg-gray-50 p-4">
|
||||
<h3 class="text-lg font-semibold">{{ $t('Steps to Disable Two-Factor Authentication') }}</h3>
|
||||
<ol class="mt-2 list-disc pl-2 text-sm">
|
||||
<li>{{ $t('Open the authenticator app') }}</li>
|
||||
<li>{{ $t('Enter the code from the app below') }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
<n-card class="mb-4">
|
||||
<h3 class="text-lg font-semibold mb-3">{{ $t('Steps to Disable Two-Factor Authentication') }}</h3>
|
||||
<ol class="ml-4 list-decimal space-y-2 text-sm text-gray-700">
|
||||
<li>{{ $t('Open the authenticator app') }}</li>
|
||||
<li>{{ $t('Enter the code from the app below') }}</li>
|
||||
</ol>
|
||||
</n-card>
|
||||
|
||||
<FormControl
|
||||
:label="$t('Verify the code in the app to disable two-factor authentication')"
|
||||
v-model="totpCode"
|
||||
/>
|
||||
</div>
|
||||
<n-form-item :label="$t('Verify the code in the app to disable two-factor authentication')" class="mt-6">
|
||||
<n-input
|
||||
v-model:value="totpCode"
|
||||
:placeholder="$t('Enter the code from the authenticator app')"
|
||||
size="large"
|
||||
class="w-full"
|
||||
/>
|
||||
</n-form-item>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-4">
|
||||
<div class="w-full">
|
||||
<VueQrcode
|
||||
v-if="qrUrl"
|
||||
class="mx-auto"
|
||||
:value="qrUrl"
|
||||
type="image/png"
|
||||
:color="{ dark: '#000000ff', light: '#ffffffff' }"
|
||||
/>
|
||||
</div>
|
||||
<!-- 启用 2FA 模式 -->
|
||||
<div v-else>
|
||||
<!-- QR 码 -->
|
||||
<div class="flex justify-center">
|
||||
<VueQrcode
|
||||
v-if="qrUrl"
|
||||
class="mx-auto"
|
||||
:value="qrUrl"
|
||||
type="image/png"
|
||||
:color="{ dark: '#000000ff', light: '#ffffffff' }"
|
||||
:size="240"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 用户需要遵循的步骤 -->
|
||||
<div class="rounded border border-gray-200 bg-gray-50 p-4">
|
||||
<h3 class="text-lg font-semibold">{{ $t('Steps to Enable Two-Factor Authentication') }}</h3>
|
||||
<ol class="ml-1 mt-2 list-disc pl-2 text-sm text-gray-700">
|
||||
<li>{{ $t('Download an authenticator app on your phone, such as Alibaba Cloud APP, etc.') }}</li>
|
||||
<li>{{ $t('Scan the QR code') }}</li>
|
||||
<li>{{ $t('Enter the code from the authenticator app below') }}</li>
|
||||
</ol>
|
||||
<p class="mt-4 text-sm text-gray-700">
|
||||
<strong>{{ $t('Note') }}:</strong> {{ $t('If you cannot access the authenticator app, your account will be locked. Please ensure you back up your vault/key.') }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- 步骤说明 -->
|
||||
<n-card>
|
||||
<h3 class="text-lg font-semibold mb-3">{{ $t('Steps to Enable Two-Factor Authentication') }}</h3>
|
||||
<ol class="ml-4 list-decimal space-y-2 text-sm text-gray-700 mb-4">
|
||||
<li>{{ $t('Download an authenticator app on your phone, such as Alibaba Cloud APP, etc.') }}</li>
|
||||
<li>{{ $t('Scan the QR code') }}</li>
|
||||
<li>{{ $t('Enter the code from the authenticator app below') }}</li>
|
||||
</ol>
|
||||
<n-alert type="warning" class="mt-4">
|
||||
<template #header>
|
||||
<strong>{{ $t('Note') }}:</strong>
|
||||
</template>
|
||||
{{ $t('If you cannot access the authenticator app, your account will be locked. Please ensure you back up your vault/key.') }}
|
||||
</n-alert>
|
||||
</n-card>
|
||||
|
||||
<div
|
||||
v-if="showSetupKey"
|
||||
class="rounded border border-gray-200 bg-gray-50 p-4"
|
||||
>
|
||||
<h3 class="text-lg font-semibold">{{ $t('Setup Key') }}</h3>
|
||||
<p class="mt-2 text-sm">
|
||||
{{ setupKey }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Setup Key -->
|
||||
<n-card v-if="showSetupKey">
|
||||
<h3 class="text-lg font-semibold mb-2">{{ $t('Setup Key') }}</h3>
|
||||
<p class="text-sm font-mono text-gray-700 break-all">
|
||||
{{ setupKey }}
|
||||
</p>
|
||||
</n-card>
|
||||
|
||||
<FormControl
|
||||
:label="$t('Verify the code in the app to enable two-factor authentication')"
|
||||
v-model="totpCode"
|
||||
/>
|
||||
</div>
|
||||
<!-- 验证码输入 -->
|
||||
<n-form-item :label="$t('Verify the code in the app to enable two-factor authentication')" class="mt-6">
|
||||
<n-input
|
||||
v-model:value="totpCode"
|
||||
:placeholder="$t('Enter the code from the authenticator app')"
|
||||
size="large"
|
||||
class="w-full"
|
||||
/>
|
||||
</n-form-item>
|
||||
</div>
|
||||
|
||||
<div class="!mt-8 flex justify-center">
|
||||
<Button
|
||||
v-if="!is2FAEnabled"
|
||||
class="w-full"
|
||||
variant="solid"
|
||||
:label="$t('Enable Two-Factor Authentication')"
|
||||
:disabled="!totpCode"
|
||||
:loading="$resources.enable2FA.loading"
|
||||
@click="enable2FA"
|
||||
/>
|
||||
<Button
|
||||
v-else
|
||||
class="w-full"
|
||||
variant="solid"
|
||||
:label="$t('Disable Two-Factor Authentication')"
|
||||
:disabled="!totpCode"
|
||||
:loading="$resources.disable2FA.loading"
|
||||
@click="disable2FA"
|
||||
/>
|
||||
</div>
|
||||
<!-- 操作按钮 -->
|
||||
<n-button
|
||||
v-if="!is2FAEnabled"
|
||||
type="primary"
|
||||
size="large"
|
||||
block
|
||||
:disabled="!totpCode"
|
||||
:loading="$resources.enable2FA.loading"
|
||||
@click="enable2FA"
|
||||
>
|
||||
{{ $t('Enable Two-Factor Authentication') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
v-else
|
||||
type="error"
|
||||
size="large"
|
||||
block
|
||||
:disabled="!totpCode"
|
||||
:loading="$resources.disable2FA.loading"
|
||||
@click="disable2FA"
|
||||
>
|
||||
{{ $t('Disable Two-Factor Authentication') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
NSpace,
|
||||
NAlert,
|
||||
NCard,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NButton,
|
||||
NSpin,
|
||||
} from 'naive-ui';
|
||||
import VueQrcode from 'vue-qrcode';
|
||||
import { toast } from 'vue-sonner';
|
||||
import AlertBanner from '../AlertBanner.vue';
|
||||
|
||||
export default {
|
||||
emits: ['enabled', 'disabled'],
|
||||
data() {
|
||||
return {
|
||||
qrUrl: '', // not storing as computed property to avoid re-fetching on dialog close
|
||||
qrUrl: '', // not storing as computed property to avoid re-fetching on dialog close
|
||||
totpCode: '',
|
||||
showSetupKey: false
|
||||
};
|
||||
},
|
||||
components: {
|
||||
AlertBanner,
|
||||
NSpace,
|
||||
NAlert,
|
||||
NCard,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NButton,
|
||||
NSpin,
|
||||
VueQrcode
|
||||
},
|
||||
methods: {
|
||||
|
||||
@ -309,6 +309,53 @@
|
||||
v-model="showResetPasswordDialog"
|
||||
/>
|
||||
<TFADialog v-model="show2FADialog" />
|
||||
|
||||
<!-- 成为开发者确认对话框 -->
|
||||
<n-modal
|
||||
v-model:show="showBecomeDeveloperDialog"
|
||||
preset="dialog"
|
||||
:title="$t('Become a Marketplace Developer?')"
|
||||
:positive-text="$t('Confirm')"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
@positive-click="handleBecomeDeveloper"
|
||||
>
|
||||
<p class="text-base">
|
||||
{{ $t('After confirmation, you will be able to publish apps to our marketplace.') }}
|
||||
</p>
|
||||
</n-modal>
|
||||
|
||||
<!-- 多个未付发票对话框 -->
|
||||
<n-modal
|
||||
v-model:show="showMultipleInvoicesDialog"
|
||||
preset="dialog"
|
||||
:title="$t('Multiple Unpaid Invoices')"
|
||||
:positive-text="$t('Go to Invoices')"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
@positive-click="() => { router.push({ name: 'BillingInvoices' }); showMultipleInvoicesDialog = false; }"
|
||||
>
|
||||
<p class="text-base">
|
||||
{{ $t('You have multiple unpaid invoices. Please pay them from the invoices page.') }}
|
||||
</p>
|
||||
</n-modal>
|
||||
|
||||
<!-- 清除未付发票对话框 -->
|
||||
<n-modal
|
||||
v-model:show="showClearInvoiceDialog"
|
||||
preset="dialog"
|
||||
:title="$t('Clear Unpaid Invoice')"
|
||||
:positive-text="$t('Settle Now')"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
@positive-click="handleSettleInvoice"
|
||||
>
|
||||
<p class="text-base">
|
||||
{{ $t('You have an unpaid invoice of {amount}. Please settle it before deactivating your account.', {
|
||||
amount: currentInvoice ? `${currentInvoice.currency === 'CNY' ? '¥' : '$'} ${currentInvoice.amount_due}` : ''
|
||||
}) }}
|
||||
</p>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -333,7 +380,7 @@ import {
|
||||
NIcon,
|
||||
} from 'naive-ui';
|
||||
import FileUploader from '@/components/FileUploader.vue';
|
||||
import { confirmDialog, renderDialog } from '../../../utils/components';
|
||||
import { renderDialog } from '../../../utils/components';
|
||||
import TFADialog from './TFADialog.vue';
|
||||
import ResetPasswordDialog from './ResetPasswordDialog.vue';
|
||||
import router from '../../../router';
|
||||
@ -382,6 +429,10 @@ export default {
|
||||
draftInvoice: {},
|
||||
unpaidInvoices: [] | {},
|
||||
windowWidth: window.innerWidth,
|
||||
showBecomeDeveloperDialog: false,
|
||||
showMultipleInvoicesDialog: false,
|
||||
showClearInvoiceDialog: false,
|
||||
currentInvoice: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -552,46 +603,14 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
|
||||
if (this.$team.pg.payment_mode === 'Prepaid Credits') {
|
||||
this.showAddPrepaidCreditsDialog = true;
|
||||
} else {
|
||||
confirmDialog({
|
||||
title: this.$t('Multiple Unpaid Invoices'),
|
||||
message: this.$t('You have multiple unpaid invoices. Please pay them from the invoices page.'),
|
||||
primaryAction: {
|
||||
label: this.$t('Go to Invoices'),
|
||||
variant: 'solid',
|
||||
onClick: ({ hide }) => {
|
||||
router.push({ name: 'BillingInvoices' });
|
||||
hide();
|
||||
},
|
||||
},
|
||||
});
|
||||
this.showMultipleInvoicesDialog = true;
|
||||
}
|
||||
} else {
|
||||
let invoice = this.unpaidInvoices;
|
||||
if (invoice.amount_due > minAmount) {
|
||||
this.showDisableAccountDialog = false;
|
||||
confirmDialog({
|
||||
title: this.$t('Clear Unpaid Invoice'),
|
||||
message: this.$t('You have an unpaid invoice of {amount}. Please settle it before deactivating your account.', {
|
||||
amount: `${invoice.currency === 'CNY' ? '¥' : '$'} ${invoice.amount_due}`
|
||||
}),
|
||||
primaryAction: {
|
||||
label: this.$t('Settle Now'),
|
||||
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();
|
||||
},
|
||||
},
|
||||
});
|
||||
this.currentInvoice = invoice;
|
||||
this.showClearInvoiceDialog = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -609,39 +628,47 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
|
||||
});
|
||||
},
|
||||
confirmPublisherAccount() {
|
||||
confirmDialog({
|
||||
title: this.$t('Become a Marketplace Developer?'),
|
||||
message: this.$t('After confirmation, you will be able to publish apps to our marketplace.'),
|
||||
primaryAction: {
|
||||
label: this.$t('Confirm'),
|
||||
variant: 'solid'
|
||||
},
|
||||
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: this.$t('You can now publish apps to our marketplace'),
|
||||
error: this.$t('Failed to mark you as a developer'),
|
||||
loading: this.$t('Setting you as a developer...'),
|
||||
this.showBecomeDeveloperDialog = true;
|
||||
},
|
||||
handleBecomeDeveloper() {
|
||||
toast.promise(
|
||||
this.$team.setValue.submit(
|
||||
{
|
||||
is_developer: 1,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
this.showBecomeDeveloperDialog = false;
|
||||
this.$router.push({
|
||||
name: 'Marketplace App List',
|
||||
});
|
||||
},
|
||||
);
|
||||
onError(e) {
|
||||
console.error(e);
|
||||
},
|
||||
},
|
||||
),
|
||||
{
|
||||
success: this.$t('You can now publish apps to our marketplace'),
|
||||
error: this.$t('Failed to mark you as a developer'),
|
||||
loading: this.$t('Setting you as a developer...'),
|
||||
},
|
||||
});
|
||||
);
|
||||
},
|
||||
handleSettleInvoice() {
|
||||
if (this.currentInvoice) {
|
||||
if (
|
||||
this.currentInvoice.stripe_invoice_url &&
|
||||
this.$team.pg.payment_mode === 'Card'
|
||||
) {
|
||||
window.open(
|
||||
`/api/action/jcloud.api.client.run_pg_method?dt=Invoice&dn=${this.currentInvoice.name}&method=stripe_payment_url`,
|
||||
);
|
||||
} else {
|
||||
this.showAddPrepaidCreditsDialog = true;
|
||||
}
|
||||
this.showClearInvoiceDialog = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,76 +1,106 @@
|
||||
<template>
|
||||
<Dialog v-model="showDialog" :options="{ title: $t('Reset Password') }">
|
||||
<template #body-content>
|
||||
<div class="space-y-4">
|
||||
<div v-if="!isResetMode" class="space-y-4">
|
||||
<FormControl
|
||||
v-model="oldPassword"
|
||||
type="password"
|
||||
:fieldtype="'Password'"
|
||||
:label="$t('Current Password')"
|
||||
:fieldname="'old_password'"
|
||||
:reqd="1"
|
||||
:placeholder="$t('Please enter current password')"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<FormControl
|
||||
v-model="newPassword"
|
||||
type="password"
|
||||
:fieldtype="'Password'"
|
||||
:label="$t('New Password')"
|
||||
:fieldname="'new_password'"
|
||||
:reqd="1"
|
||||
:placeholder="$t('Please enter new password')"
|
||||
/>
|
||||
<div v-if="passwordStrengthMessage" class="text-sm" :class="passwordStrengthClass">
|
||||
{{ passwordStrengthMessage }}
|
||||
</div>
|
||||
<FormControl
|
||||
v-model="confirmPassword"
|
||||
type="password"
|
||||
:fieldtype="'Password'"
|
||||
:label="$t('Confirm Password')"
|
||||
:fieldname="'confirm_password'"
|
||||
:reqd="1"
|
||||
:placeholder="$t('Please re-enter new password')"
|
||||
/>
|
||||
<div v-if="passwordMismatchMessage" class="text-sm text-red-600">
|
||||
{{ passwordMismatchMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ErrorMessage class="mt-2" :message="error" />
|
||||
<n-modal
|
||||
v-model:show="showDialog"
|
||||
preset="card"
|
||||
:title="$t('Reset Password')"
|
||||
:style="modalStyle"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
class="reset-password-dialog"
|
||||
>
|
||||
<template #header>
|
||||
<span class="text-lg font-semibold">{{ $t('Reset Password') }}</span>
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="flex gap-2 w-full">
|
||||
<Button
|
||||
variant="outline"
|
||||
class="flex-1"
|
||||
@click="hide"
|
||||
>
|
||||
<n-space vertical :size="20">
|
||||
<n-form-item v-if="!isResetMode" :label="$t('Current Password')">
|
||||
<n-input
|
||||
v-model:value="oldPassword"
|
||||
type="password"
|
||||
:placeholder="$t('Please enter current password')"
|
||||
:size="inputSize"
|
||||
show-password-on="click"
|
||||
class="w-full"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('New Password')">
|
||||
<n-input
|
||||
v-model:value="newPassword"
|
||||
type="password"
|
||||
:placeholder="$t('Please enter new password')"
|
||||
:size="inputSize"
|
||||
show-password-on="click"
|
||||
class="w-full"
|
||||
/>
|
||||
<n-alert
|
||||
v-if="passwordStrengthMessage"
|
||||
:type="passwordStrengthClass === 'text-green-600' ? 'success' : 'error'"
|
||||
class="mt-2"
|
||||
:title="passwordStrengthMessage"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('Confirm Password')">
|
||||
<n-input
|
||||
v-model:value="confirmPassword"
|
||||
type="password"
|
||||
:placeholder="$t('Please re-enter new password')"
|
||||
:size="inputSize"
|
||||
show-password-on="click"
|
||||
class="w-full"
|
||||
/>
|
||||
<n-alert
|
||||
v-if="passwordMismatchMessage"
|
||||
type="error"
|
||||
class="mt-2"
|
||||
:title="passwordMismatchMessage"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-alert
|
||||
v-if="error"
|
||||
type="error"
|
||||
:title="error"
|
||||
/>
|
||||
</n-space>
|
||||
<template #action>
|
||||
<n-space :vertical="isMobile" :size="isMobile ? 12 : 16" class="w-full">
|
||||
<n-button @click="hide" :block="isMobile" :size="buttonSize">
|
||||
{{ $t('Cancel') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
class="flex-1"
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="isLoading"
|
||||
:disabled="!isFormValid"
|
||||
@click="onConfirm"
|
||||
:block="isMobile"
|
||||
:size="buttonSize"
|
||||
>
|
||||
{{ $t('Confirm') }}
|
||||
</Button>
|
||||
</div>
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</Dialog>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ErrorMessage, FormControl } from 'jingrow-ui';
|
||||
import {
|
||||
NModal,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NSpace,
|
||||
NAlert,
|
||||
NButton,
|
||||
} from 'naive-ui';
|
||||
import { toast } from 'vue-sonner';
|
||||
|
||||
export default {
|
||||
name: 'ResetPasswordDialog',
|
||||
components: {
|
||||
NModal,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NSpace,
|
||||
NAlert,
|
||||
NButton,
|
||||
},
|
||||
props: {
|
||||
isResetMode: {
|
||||
type: Boolean,
|
||||
@ -116,9 +146,23 @@ export default {
|
||||
} else {
|
||||
return this.oldPassword && hasNewPassword && hasConfirmPassword && passwordsMatch && passwordsDifferent;
|
||||
}
|
||||
}
|
||||
},
|
||||
isMobile() {
|
||||
return window.innerWidth <= 768;
|
||||
},
|
||||
modalStyle() {
|
||||
return {
|
||||
width: this.isMobile ? '95vw' : '700px',
|
||||
maxWidth: this.isMobile ? '95vw' : '90vw',
|
||||
};
|
||||
},
|
||||
inputSize() {
|
||||
return this.isMobile ? 'medium' : 'large';
|
||||
},
|
||||
buttonSize() {
|
||||
return this.isMobile ? 'medium' : 'medium';
|
||||
},
|
||||
},
|
||||
components: { FormControl, ErrorMessage },
|
||||
watch: {
|
||||
newPassword() {
|
||||
this.checkPasswordStrength();
|
||||
@ -300,3 +344,97 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.reset-password-dialog .n-card) {
|
||||
width: 700px;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-card-body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-form-item-label) {
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-card__action) {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:deep(.reset-password-dialog .n-card) {
|
||||
width: 95vw !important;
|
||||
max-width: 95vw !important;
|
||||
margin: 20px auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-card-body) {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-card__header) {
|
||||
padding: 16px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-card__action) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-space) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-button) {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-form-item-label) {
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-input) {
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超小屏幕优化 */
|
||||
@media (max-width: 480px) {
|
||||
:deep(.reset-password-dialog .n-card) {
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-card-body) {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
:deep(.reset-password-dialog .n-card__action) {
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid var(--n-border-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="show"
|
||||
:options="dialogOptions"
|
||||
<n-modal
|
||||
v-model:show="show"
|
||||
preset="card"
|
||||
:title="dialogTitle"
|
||||
:style="modalStyle"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
class="tfa-dialog"
|
||||
>
|
||||
<template #body-content>
|
||||
<Configure2FA @enabled="closeDialog" @disabled="closeDialog" />
|
||||
<template #header>
|
||||
<span class="text-lg font-semibold">{{ dialogTitle }}</span>
|
||||
</template>
|
||||
</Dialog>
|
||||
<Configure2FA @enabled="closeDialog" @disabled="closeDialog" />
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NModal } from 'naive-ui';
|
||||
import Configure2FA from '../../auth/Configure2FA.vue';
|
||||
|
||||
export default {
|
||||
@ -20,22 +27,25 @@ export default {
|
||||
}
|
||||
},
|
||||
components: {
|
||||
NModal,
|
||||
Configure2FA
|
||||
},
|
||||
methods: {
|
||||
closeDialog() {
|
||||
this.show = false;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
is2FAEnabled() {
|
||||
return this.$team.pg?.user_info?.is_2fa_enabled;
|
||||
},
|
||||
dialogOptions() {
|
||||
dialogTitle() {
|
||||
return this.is2FAEnabled
|
||||
? this.$t('Disable Two-Factor Authentication')
|
||||
: this.$t('Enable Two-Factor Authentication');
|
||||
},
|
||||
isMobile() {
|
||||
return window.innerWidth <= 768;
|
||||
},
|
||||
modalStyle() {
|
||||
return {
|
||||
title: this.is2FAEnabled
|
||||
? this.$t('Disable Two-Factor Authentication')
|
||||
: this.$t('Enable Two-Factor Authentication')
|
||||
width: this.isMobile ? '95vw' : '700px',
|
||||
maxWidth: this.isMobile ? '95vw' : '90vw',
|
||||
};
|
||||
},
|
||||
show: {
|
||||
@ -46,6 +56,26 @@ export default {
|
||||
this.$emit('update:modelValue', value);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeDialog() {
|
||||
this.show = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@media (max-width: 768px) {
|
||||
:deep(.tfa-dialog .n-card) {
|
||||
width: 95vw !important;
|
||||
max-width: 95vw !important;
|
||||
margin: 20px auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
:deep(.tfa-dialog .n-card-body) {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1036,6 +1036,7 @@ Steps to Enable Two-Factor Authentication,启用双重认证的步骤,
|
||||
Download an authenticator app on your phone, such as Alibaba Cloud APP, etc.,在手机上下载认证器应用,例如阿里云APP等。,
|
||||
Scan the QR code,扫描二维码,
|
||||
Enter the code from the authenticator app below,在下方输入认证器应用中的代码,
|
||||
Enter the code from the authenticator app,输入认证器应用中的代码,
|
||||
If you cannot access the authenticator app, your account will be locked. Please ensure you back up your vault/key.,如果您无法访问认证器应用,您的账户将被锁定。请确保备份您的保险库/密钥。,
|
||||
Setup Key,设置密钥,
|
||||
Verify the code in the app to enable two-factor authentication,验证应用中的代码以启用双重认证,
|
||||
|
||||
|
Can't render this file because it has a wrong number of fields in line 400.
|
Loading…
x
Reference in New Issue
Block a user