美化dashboard个人资料页面
This commit is contained in:
parent
023cbf428c
commit
34577e321d
@ -1,114 +1,140 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<n-spin :show="$resources.qrUrl.loading">
|
||||||
class="mt-6 flex items-center justify-center"
|
<n-space vertical :size="24">
|
||||||
v-if="$resources.qrUrl.loading"
|
<!-- 禁用 2FA 模式 -->
|
||||||
>
|
<div v-if="is2FAEnabled && $route.name !== 'Enable2FA'">
|
||||||
<LoadingText />
|
<n-alert
|
||||||
</div>
|
type="error"
|
||||||
<div
|
:title="$t('If you disable two-factor authentication, your account will become insecure')"
|
||||||
v-else-if="is2FAEnabled && $route.name !== 'Enable2FA'"
|
class="mb-4"
|
||||||
class="space-y-4"
|
/>
|
||||||
>
|
|
||||||
<AlertBanner
|
|
||||||
:title="$t('If you disable two-factor authentication, your account will become insecure')"
|
|
||||||
type="error"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 用户需要遵循的步骤 -->
|
<n-card class="mb-4">
|
||||||
<div class="rounded border border-gray-200 bg-gray-50 p-4">
|
<h3 class="text-lg font-semibold mb-3">{{ $t('Steps to Disable Two-Factor Authentication') }}</h3>
|
||||||
<h3 class="text-lg font-semibold">{{ $t('Steps to Disable Two-Factor Authentication') }}</h3>
|
<ol class="ml-4 list-decimal space-y-2 text-sm text-gray-700">
|
||||||
<ol class="mt-2 list-disc pl-2 text-sm">
|
<li>{{ $t('Open the authenticator app') }}</li>
|
||||||
<li>{{ $t('Open the authenticator app') }}</li>
|
<li>{{ $t('Enter the code from the app below') }}</li>
|
||||||
<li>{{ $t('Enter the code from the app below') }}</li>
|
</ol>
|
||||||
</ol>
|
</n-card>
|
||||||
</div>
|
|
||||||
|
|
||||||
<FormControl
|
<n-form-item :label="$t('Verify the code in the app to disable two-factor authentication')" class="mt-6">
|
||||||
:label="$t('Verify the code in the app to disable two-factor authentication')"
|
<n-input
|
||||||
v-model="totpCode"
|
v-model:value="totpCode"
|
||||||
/>
|
:placeholder="$t('Enter the code from the authenticator app')"
|
||||||
</div>
|
size="large"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-else class="space-y-4">
|
<!-- 启用 2FA 模式 -->
|
||||||
<div class="w-full">
|
<div v-else>
|
||||||
<VueQrcode
|
<!-- QR 码 -->
|
||||||
v-if="qrUrl"
|
<div class="flex justify-center">
|
||||||
class="mx-auto"
|
<VueQrcode
|
||||||
:value="qrUrl"
|
v-if="qrUrl"
|
||||||
type="image/png"
|
class="mx-auto"
|
||||||
:color="{ dark: '#000000ff', light: '#ffffffff' }"
|
:value="qrUrl"
|
||||||
/>
|
type="image/png"
|
||||||
</div>
|
:color="{ dark: '#000000ff', light: '#ffffffff' }"
|
||||||
|
:size="240"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 用户需要遵循的步骤 -->
|
<!-- 步骤说明 -->
|
||||||
<div class="rounded border border-gray-200 bg-gray-50 p-4">
|
<n-card>
|
||||||
<h3 class="text-lg font-semibold">{{ $t('Steps to Enable Two-Factor Authentication') }}</h3>
|
<h3 class="text-lg font-semibold mb-3">{{ $t('Steps to Enable Two-Factor Authentication') }}</h3>
|
||||||
<ol class="ml-1 mt-2 list-disc pl-2 text-sm text-gray-700">
|
<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('Download an authenticator app on your phone, such as Alibaba Cloud APP, etc.') }}</li>
|
||||||
<li>{{ $t('Scan the QR code') }}</li>
|
<li>{{ $t('Scan the QR code') }}</li>
|
||||||
<li>{{ $t('Enter the code from the authenticator app below') }}</li>
|
<li>{{ $t('Enter the code from the authenticator app below') }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p class="mt-4 text-sm text-gray-700">
|
<n-alert type="warning" class="mt-4">
|
||||||
<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.') }}
|
<template #header>
|
||||||
</p>
|
<strong>{{ $t('Note') }}:</strong>
|
||||||
</div>
|
</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
|
<!-- Setup Key -->
|
||||||
v-if="showSetupKey"
|
<n-card v-if="showSetupKey">
|
||||||
class="rounded border border-gray-200 bg-gray-50 p-4"
|
<h3 class="text-lg font-semibold mb-2">{{ $t('Setup Key') }}</h3>
|
||||||
>
|
<p class="text-sm font-mono text-gray-700 break-all">
|
||||||
<h3 class="text-lg font-semibold">{{ $t('Setup Key') }}</h3>
|
{{ setupKey }}
|
||||||
<p class="mt-2 text-sm">
|
</p>
|
||||||
{{ setupKey }}
|
</n-card>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FormControl
|
<!-- 验证码输入 -->
|
||||||
:label="$t('Verify the code in the app to enable two-factor authentication')"
|
<n-form-item :label="$t('Verify the code in the app to enable two-factor authentication')" class="mt-6">
|
||||||
v-model="totpCode"
|
<n-input
|
||||||
/>
|
v-model:value="totpCode"
|
||||||
</div>
|
: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
|
<n-button
|
||||||
v-if="!is2FAEnabled"
|
v-if="!is2FAEnabled"
|
||||||
class="w-full"
|
type="primary"
|
||||||
variant="solid"
|
size="large"
|
||||||
:label="$t('Enable Two-Factor Authentication')"
|
block
|
||||||
:disabled="!totpCode"
|
:disabled="!totpCode"
|
||||||
:loading="$resources.enable2FA.loading"
|
:loading="$resources.enable2FA.loading"
|
||||||
@click="enable2FA"
|
@click="enable2FA"
|
||||||
/>
|
>
|
||||||
<Button
|
{{ $t('Enable Two-Factor Authentication') }}
|
||||||
v-else
|
</n-button>
|
||||||
class="w-full"
|
<n-button
|
||||||
variant="solid"
|
v-else
|
||||||
:label="$t('Disable Two-Factor Authentication')"
|
type="error"
|
||||||
:disabled="!totpCode"
|
size="large"
|
||||||
:loading="$resources.disable2FA.loading"
|
block
|
||||||
@click="disable2FA"
|
:disabled="!totpCode"
|
||||||
/>
|
:loading="$resources.disable2FA.loading"
|
||||||
</div>
|
@click="disable2FA"
|
||||||
|
>
|
||||||
|
{{ $t('Disable Two-Factor Authentication') }}
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-spin>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {
|
||||||
|
NSpace,
|
||||||
|
NAlert,
|
||||||
|
NCard,
|
||||||
|
NFormItem,
|
||||||
|
NInput,
|
||||||
|
NButton,
|
||||||
|
NSpin,
|
||||||
|
} from 'naive-ui';
|
||||||
import VueQrcode from 'vue-qrcode';
|
import VueQrcode from 'vue-qrcode';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import AlertBanner from '../AlertBanner.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['enabled', 'disabled'],
|
emits: ['enabled', 'disabled'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
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: '',
|
totpCode: '',
|
||||||
showSetupKey: false
|
showSetupKey: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
AlertBanner,
|
NSpace,
|
||||||
|
NAlert,
|
||||||
|
NCard,
|
||||||
|
NFormItem,
|
||||||
|
NInput,
|
||||||
|
NButton,
|
||||||
|
NSpin,
|
||||||
VueQrcode
|
VueQrcode
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@ -309,6 +309,53 @@
|
|||||||
v-model="showResetPasswordDialog"
|
v-model="showResetPasswordDialog"
|
||||||
/>
|
/>
|
||||||
<TFADialog v-model="show2FADialog" />
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -333,7 +380,7 @@ import {
|
|||||||
NIcon,
|
NIcon,
|
||||||
} from 'naive-ui';
|
} from 'naive-ui';
|
||||||
import FileUploader from '@/components/FileUploader.vue';
|
import FileUploader from '@/components/FileUploader.vue';
|
||||||
import { confirmDialog, renderDialog } from '../../../utils/components';
|
import { renderDialog } from '../../../utils/components';
|
||||||
import TFADialog from './TFADialog.vue';
|
import TFADialog from './TFADialog.vue';
|
||||||
import ResetPasswordDialog from './ResetPasswordDialog.vue';
|
import ResetPasswordDialog from './ResetPasswordDialog.vue';
|
||||||
import router from '../../../router';
|
import router from '../../../router';
|
||||||
@ -382,6 +429,10 @@ export default {
|
|||||||
draftInvoice: {},
|
draftInvoice: {},
|
||||||
unpaidInvoices: [] | {},
|
unpaidInvoices: [] | {},
|
||||||
windowWidth: window.innerWidth,
|
windowWidth: window.innerWidth,
|
||||||
|
showBecomeDeveloperDialog: false,
|
||||||
|
showMultipleInvoicesDialog: false,
|
||||||
|
showClearInvoiceDialog: false,
|
||||||
|
currentInvoice: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -552,46 +603,14 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
|
|||||||
if (this.$team.pg.payment_mode === 'Prepaid Credits') {
|
if (this.$team.pg.payment_mode === 'Prepaid Credits') {
|
||||||
this.showAddPrepaidCreditsDialog = true;
|
this.showAddPrepaidCreditsDialog = true;
|
||||||
} else {
|
} else {
|
||||||
confirmDialog({
|
this.showMultipleInvoicesDialog = true;
|
||||||
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();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let invoice = this.unpaidInvoices;
|
let invoice = this.unpaidInvoices;
|
||||||
if (invoice.amount_due > minAmount) {
|
if (invoice.amount_due > minAmount) {
|
||||||
this.showDisableAccountDialog = false;
|
this.showDisableAccountDialog = false;
|
||||||
confirmDialog({
|
this.currentInvoice = invoice;
|
||||||
title: this.$t('Clear Unpaid Invoice'),
|
this.showClearInvoiceDialog = true;
|
||||||
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();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -609,39 +628,47 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
confirmPublisherAccount() {
|
confirmPublisherAccount() {
|
||||||
confirmDialog({
|
this.showBecomeDeveloperDialog = true;
|
||||||
title: this.$t('Become a Marketplace Developer?'),
|
},
|
||||||
message: this.$t('After confirmation, you will be able to publish apps to our marketplace.'),
|
handleBecomeDeveloper() {
|
||||||
primaryAction: {
|
toast.promise(
|
||||||
label: this.$t('Confirm'),
|
this.$team.setValue.submit(
|
||||||
variant: 'solid'
|
{
|
||||||
},
|
is_developer: 1,
|
||||||
onSuccess: ({ hide }) => {
|
},
|
||||||
toast.promise(
|
{
|
||||||
this.$team.setValue.submit(
|
onSuccess: () => {
|
||||||
{
|
this.showBecomeDeveloperDialog = false;
|
||||||
is_developer: 1,
|
this.$router.push({
|
||||||
},
|
name: 'Marketplace App List',
|
||||||
{
|
});
|
||||||
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...'),
|
|
||||||
},
|
},
|
||||||
);
|
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>
|
<template>
|
||||||
<Dialog v-model="showDialog" :options="{ title: $t('Reset Password') }">
|
<n-modal
|
||||||
<template #body-content>
|
v-model:show="showDialog"
|
||||||
<div class="space-y-4">
|
preset="card"
|
||||||
<div v-if="!isResetMode" class="space-y-4">
|
:title="$t('Reset Password')"
|
||||||
<FormControl
|
:style="modalStyle"
|
||||||
v-model="oldPassword"
|
:mask-closable="true"
|
||||||
type="password"
|
:close-on-esc="true"
|
||||||
:fieldtype="'Password'"
|
class="reset-password-dialog"
|
||||||
:label="$t('Current Password')"
|
>
|
||||||
:fieldname="'old_password'"
|
<template #header>
|
||||||
:reqd="1"
|
<span class="text-lg font-semibold">{{ $t('Reset Password') }}</span>
|
||||||
: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" />
|
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<n-space vertical :size="20">
|
||||||
<div class="flex gap-2 w-full">
|
<n-form-item v-if="!isResetMode" :label="$t('Current Password')">
|
||||||
<Button
|
<n-input
|
||||||
variant="outline"
|
v-model:value="oldPassword"
|
||||||
class="flex-1"
|
type="password"
|
||||||
@click="hide"
|
: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') }}
|
{{ $t('Cancel') }}
|
||||||
</Button>
|
</n-button>
|
||||||
<Button
|
<n-button
|
||||||
variant="solid"
|
type="primary"
|
||||||
class="flex-1"
|
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
:disabled="!isFormValid"
|
:disabled="!isFormValid"
|
||||||
@click="onConfirm"
|
@click="onConfirm"
|
||||||
|
:block="isMobile"
|
||||||
|
:size="buttonSize"
|
||||||
>
|
>
|
||||||
{{ $t('Confirm') }}
|
{{ $t('Confirm') }}
|
||||||
</Button>
|
</n-button>
|
||||||
</div>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</n-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ErrorMessage, FormControl } from 'jingrow-ui';
|
import {
|
||||||
|
NModal,
|
||||||
|
NFormItem,
|
||||||
|
NInput,
|
||||||
|
NSpace,
|
||||||
|
NAlert,
|
||||||
|
NButton,
|
||||||
|
} from 'naive-ui';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ResetPasswordDialog',
|
name: 'ResetPasswordDialog',
|
||||||
|
components: {
|
||||||
|
NModal,
|
||||||
|
NFormItem,
|
||||||
|
NInput,
|
||||||
|
NSpace,
|
||||||
|
NAlert,
|
||||||
|
NButton,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
isResetMode: {
|
isResetMode: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -116,9 +146,23 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
return this.oldPassword && hasNewPassword && hasConfirmPassword && passwordsMatch && passwordsDifferent;
|
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: {
|
watch: {
|
||||||
newPassword() {
|
newPassword() {
|
||||||
this.checkPasswordStrength();
|
this.checkPasswordStrength();
|
||||||
@ -300,3 +344,97 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</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>
|
<template>
|
||||||
<Dialog
|
<n-modal
|
||||||
v-model="show"
|
v-model:show="show"
|
||||||
:options="dialogOptions"
|
preset="card"
|
||||||
|
:title="dialogTitle"
|
||||||
|
:style="modalStyle"
|
||||||
|
:mask-closable="true"
|
||||||
|
:close-on-esc="true"
|
||||||
|
class="tfa-dialog"
|
||||||
>
|
>
|
||||||
<template #body-content>
|
<template #header>
|
||||||
<Configure2FA @enabled="closeDialog" @disabled="closeDialog" />
|
<span class="text-lg font-semibold">{{ dialogTitle }}</span>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
<Configure2FA @enabled="closeDialog" @disabled="closeDialog" />
|
||||||
|
</n-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { NModal } from 'naive-ui';
|
||||||
import Configure2FA from '../../auth/Configure2FA.vue';
|
import Configure2FA from '../../auth/Configure2FA.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -20,22 +27,25 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
NModal,
|
||||||
Configure2FA
|
Configure2FA
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
closeDialog() {
|
|
||||||
this.show = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
is2FAEnabled() {
|
is2FAEnabled() {
|
||||||
return this.$team.pg?.user_info?.is_2fa_enabled;
|
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 {
|
return {
|
||||||
title: this.is2FAEnabled
|
width: this.isMobile ? '95vw' : '700px',
|
||||||
? this.$t('Disable Two-Factor Authentication')
|
maxWidth: this.isMobile ? '95vw' : '90vw',
|
||||||
: this.$t('Enable Two-Factor Authentication')
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
show: {
|
show: {
|
||||||
@ -46,6 +56,26 @@ export default {
|
|||||||
this.$emit('update:modelValue', value);
|
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等。,
|
Download an authenticator app on your phone, such as Alibaba Cloud APP, etc.,在手机上下载认证器应用,例如阿里云APP等。,
|
||||||
Scan the QR code,扫描二维码,
|
Scan the QR code,扫描二维码,
|
||||||
Enter the code from the authenticator app below,在下方输入认证器应用中的代码,
|
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.,如果您无法访问认证器应用,您的账户将被锁定。请确保备份您的保险库/密钥。,
|
If you cannot access the authenticator app, your account will be locked. Please ensure you back up your vault/key.,如果您无法访问认证器应用,您的账户将被锁定。请确保备份您的保险库/密钥。,
|
||||||
Setup Key,设置密钥,
|
Setup Key,设置密钥,
|
||||||
Verify the code in the app to enable two-factor authentication,验证应用中的代码以启用双重认证,
|
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