基于naive ui重构AccountProfile.vue
This commit is contained in:
parent
45359cd57f
commit
980f52ee3b
@ -1,8 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<Card :title="$t('Profile')" v-if="user" class="mx-auto max-w-3xl">
|
<div v-if="user" class="profile-container">
|
||||||
<div class="flex items-center border-b pb-3">
|
<n-space vertical :size="24">
|
||||||
<div class="relative">
|
<!-- 个人资料卡片 -->
|
||||||
<Avatar size="2xl" :label="user.first_name" :image="user.user_image" />
|
<n-card :title="$t('Profile')" class="profile-card">
|
||||||
|
<n-space vertical :size="20">
|
||||||
|
<div class="profile-header">
|
||||||
|
<div class="profile-avatar-wrapper">
|
||||||
|
<n-avatar
|
||||||
|
:size="avatarSize"
|
||||||
|
:src="user.user_image"
|
||||||
|
round
|
||||||
|
class="profile-avatar"
|
||||||
|
>
|
||||||
|
{{ user.first_name?.[0]?.toUpperCase() || 'A' }}
|
||||||
|
</n-avatar>
|
||||||
<FileUploader
|
<FileUploader
|
||||||
@success="onProfilePhotoChange"
|
@success="onProfilePhotoChange"
|
||||||
fileTypes="image/*"
|
fileTypes="image/*"
|
||||||
@ -13,71 +24,118 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template v-slot="{ openFileSelector, uploading, progress, error }">
|
<template v-slot="{ openFileSelector, uploading, progress, error }">
|
||||||
<div class="ml-4">
|
<n-button
|
||||||
<button
|
tertiary
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
class="avatar-edit-btn"
|
||||||
|
:loading="uploading"
|
||||||
@click="openFileSelector()"
|
@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>
|
<template #icon>
|
||||||
<span v-else>{{ $t('Edit') }}</span>
|
<n-icon>
|
||||||
</button>
|
<EditIcon />
|
||||||
</div>
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
</FileUploader>
|
</FileUploader>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="profile-info">
|
||||||
<h3 class="text-base font-semibold">
|
<h2 class="profile-name">
|
||||||
{{ user.first_name }} {{ user.last_name }}
|
{{ user.first_name }} {{ user.last_name }}
|
||||||
</h3>
|
</h2>
|
||||||
<p class="mt-1 text-base text-gray-600">{{ $t('Username') }}: {{ user.username }}</p>
|
<div class="profile-details">
|
||||||
<p class="mt-1 text-base text-gray-600">{{ $t('Phone') }}: {{ user.mobile_no }}</p>
|
<div class="profile-detail-item">
|
||||||
<p class="mt-1 text-base text-gray-600">{{ $t('Email') }}: {{ user.email }}</p>
|
<span class="profile-detail-label">{{ $t('Username') }}:</span>
|
||||||
|
<span class="profile-detail-value">{{ user.username }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-auto">
|
<div class="profile-detail-item">
|
||||||
<Button icon-left="edit" @click="showProfileEditDialog = true">
|
<span class="profile-detail-label">{{ $t('Phone') }}:</span>
|
||||||
|
<span class="profile-detail-value">{{ user.mobile_no || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="profile-detail-item">
|
||||||
|
<span class="profile-detail-label">{{ $t('Email') }}:</span>
|
||||||
|
<span class="profile-detail-value">{{ user.email }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="profile-actions">
|
||||||
|
<n-button type="primary" @click="showProfileEditDialog = true" :block="isMobile">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon><EditIcon /></n-icon>
|
||||||
|
</template>
|
||||||
{{ $t('Edit') }}
|
{{ $t('Edit') }}
|
||||||
</Button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</n-space>
|
||||||
<ListItem
|
</n-card>
|
||||||
:title="$t('Marketplace Developer')"
|
|
||||||
:subtitle="$t('Developers can publish their apps on the marketplace for users to subscribe to, either paid or free.')"
|
<!-- 功能设置卡片 -->
|
||||||
v-if="!$team.pg.is_developer"
|
<n-card class="settings-card">
|
||||||
>
|
<n-list>
|
||||||
<template #actions>
|
<n-list-item v-if="!$team.pg.is_developer">
|
||||||
<Button @click="confirmPublisherAccount">
|
<n-thing>
|
||||||
<span>{{ $t('Become a Developer') }}</span>
|
<template #header>
|
||||||
</Button>
|
<span class="text-base font-medium">{{ $t('Marketplace Developer') }}</span>
|
||||||
</template>
|
</template>
|
||||||
</ListItem>
|
<template #description>
|
||||||
<ListItem
|
<span class="text-sm text-gray-600">
|
||||||
:title="twoFactorAuthTitle"
|
{{ $t('Developers can publish their apps on the marketplace for users to subscribe to, either paid or free.') }}
|
||||||
:subtitle="twoFactorAuthSubtitle"
|
</span>
|
||||||
>
|
</template>
|
||||||
<template #actions>
|
<template #action>
|
||||||
<Button @click="show2FADialog = true">
|
<n-button type="primary" @click="confirmPublisherAccount">
|
||||||
|
{{ $t('Become a Developer') }}
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-thing>
|
||||||
|
</n-list-item>
|
||||||
|
|
||||||
|
<n-list-item>
|
||||||
|
<n-thing>
|
||||||
|
<template #header>
|
||||||
|
<span class="text-base font-medium">{{ twoFactorAuthTitle }}</span>
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
<span class="text-sm text-gray-600">{{ twoFactorAuthSubtitle }}</span>
|
||||||
|
</template>
|
||||||
|
<template #action>
|
||||||
|
<n-button @click="show2FADialog = true">
|
||||||
{{ twoFactorAuthButtonLabel }}
|
{{ twoFactorAuthButtonLabel }}
|
||||||
</Button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
</ListItem>
|
</n-thing>
|
||||||
<ListItem
|
</n-list-item>
|
||||||
:title="$t('Reset Password')"
|
|
||||||
:subtitle="$t('Change your account login password')"
|
<n-list-item>
|
||||||
>
|
<n-thing>
|
||||||
<template #actions>
|
<template #header>
|
||||||
<Button @click="showResetPasswordDialog = true">
|
<span class="text-base font-medium">{{ $t('Reset Password') }}</span>
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
<span class="text-sm text-gray-600">{{ $t('Change your account login password') }}</span>
|
||||||
|
</template>
|
||||||
|
<template #action>
|
||||||
|
<n-button @click="showResetPasswordDialog = true">
|
||||||
{{ $t('Reset Password') }}
|
{{ $t('Reset Password') }}
|
||||||
</Button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
</ListItem>
|
</n-thing>
|
||||||
<ListItem
|
</n-list-item>
|
||||||
:title="accountStatusTitle"
|
|
||||||
:subtitle="accountStatusSubtitle"
|
<n-list-item>
|
||||||
>
|
<n-thing>
|
||||||
<template #actions>
|
<template #header>
|
||||||
<Button
|
<span class="text-base font-medium">{{ accountStatusTitle }}</span>
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
<span class="text-sm text-gray-600">{{ accountStatusSubtitle }}</span>
|
||||||
|
</template>
|
||||||
|
<template #action>
|
||||||
|
<n-button
|
||||||
|
:type="teamEnabled ? 'error' : 'primary'"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
if (teamEnabled) {
|
if (teamEnabled) {
|
||||||
@ -88,101 +146,150 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span :class="{ 'text-red-600': teamEnabled }">{{
|
{{ accountStatusButtonLabel }}
|
||||||
accountStatusButtonLabel
|
</n-button>
|
||||||
}}</span>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
</ListItem>
|
</n-thing>
|
||||||
</div>
|
</n-list-item>
|
||||||
<Dialog
|
</n-list>
|
||||||
:options="{
|
</n-card>
|
||||||
title: $t('Update Profile Information'),
|
</n-space>
|
||||||
actions: [
|
<!-- 编辑资料对话框 -->
|
||||||
{
|
<n-modal
|
||||||
variant: 'solid',
|
v-model:show="showProfileEditDialog"
|
||||||
label: $t('Save Changes'),
|
preset="card"
|
||||||
onClick: () => $resources.updateProfile.submit(),
|
:title="$t('Update Profile Information')"
|
||||||
},
|
:style="modalStyle"
|
||||||
],
|
:mask-closable="true"
|
||||||
}"
|
:close-on-esc="true"
|
||||||
v-model="showProfileEditDialog"
|
class="profile-edit-modal"
|
||||||
>
|
>
|
||||||
<template v-slot:body-content>
|
<template #header>
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<span class="text-lg font-semibold">{{ $t('Update Profile Information') }}</span>
|
||||||
<FormControl :label="$t('First Name')" v-model="user.first_name" />
|
|
||||||
<FormControl :label="$t('Username')" v-model="user.username" />
|
|
||||||
<FormControl :label="$t('Phone')" v-model="user.mobile_no" />
|
|
||||||
<FormControl :label="$t('Email')" v-model="user.email" />
|
|
||||||
</div>
|
|
||||||
<ErrorMessage class="mt-4" :message="$resources.updateProfile.error" />
|
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
<n-form :model="user" label-placement="top" class="mt-4">
|
||||||
|
<n-space vertical :size="20">
|
||||||
|
<n-form-item :label="$t('First Name')">
|
||||||
|
<n-input
|
||||||
|
v-model:value="user.first_name"
|
||||||
|
:size="inputSize"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('Username')">
|
||||||
|
<n-input
|
||||||
|
v-model:value="user.username"
|
||||||
|
:size="inputSize"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('Phone')">
|
||||||
|
<n-input
|
||||||
|
v-model:value="user.mobile_no"
|
||||||
|
:size="inputSize"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('Email')">
|
||||||
|
<n-input
|
||||||
|
v-model:value="user.email"
|
||||||
|
type="email"
|
||||||
|
:size="inputSize"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-alert
|
||||||
|
v-if="$resources.updateProfile.error"
|
||||||
|
type="error"
|
||||||
|
:title="$resources.updateProfile.error"
|
||||||
|
/>
|
||||||
|
</n-space>
|
||||||
|
</n-form>
|
||||||
|
<template #action>
|
||||||
|
<n-space :vertical="isMobile" :size="isMobile ? 12 : 16" class="w-full">
|
||||||
|
<n-button
|
||||||
|
@click="showProfileEditDialog = false"
|
||||||
|
:block="isMobile"
|
||||||
|
:size="buttonSize"
|
||||||
|
>
|
||||||
|
{{ $t('Cancel') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleSaveProfile"
|
||||||
|
:loading="$resources.updateProfile.loading"
|
||||||
|
:block="isMobile"
|
||||||
|
:size="buttonSize"
|
||||||
|
>
|
||||||
|
{{ $t('Save Changes') }}
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
<Dialog
|
<!-- 禁用账户对话框 -->
|
||||||
:options="{
|
<n-modal
|
||||||
title: $t('Disable Account'),
|
v-model:show="showDisableAccountDialog"
|
||||||
actions: [
|
preset="dialog"
|
||||||
{
|
:title="$t('Disable Account')"
|
||||||
label: $t('Disable Account'),
|
:positive-text="$t('Disable Account')"
|
||||||
variant: 'solid',
|
:positive-button-props="{ type: 'error' }"
|
||||||
theme: 'red',
|
:loading="$resources.disableAccount.loading"
|
||||||
loading: $resources.disableAccount.loading,
|
:mask-closable="true"
|
||||||
onClick: () => deactivateAccount(disableAccount2FACode),
|
:close-on-esc="true"
|
||||||
},
|
@positive-click="() => deactivateAccount(disableAccount2FACode)"
|
||||||
],
|
|
||||||
}"
|
|
||||||
v-model="showDisableAccountDialog"
|
|
||||||
>
|
>
|
||||||
<template v-slot:body-content>
|
<div class="py-4">
|
||||||
<div class="prose text-base">
|
<p class="text-base mb-4">{{ $t('After confirming this action:') }}</p>
|
||||||
{{ $t('After confirming this action:') }}
|
<ul class="list-disc list-inside space-y-2 text-sm text-gray-700 mb-4">
|
||||||
<ul>
|
|
||||||
<li>{{ $t('Your account will be disabled') }}</li>
|
<li>{{ $t('Your account will be disabled') }}</li>
|
||||||
<li>
|
<li>{{ $t('Your activated sites will be suspended immediately and deleted after one week.') }}</li>
|
||||||
{{ $t('Your activated sites will be suspended immediately and deleted after one week.') }}
|
|
||||||
</li>
|
|
||||||
<li>{{ $t('Your account billing will stop') }}</li>
|
<li>{{ $t('Your account billing will stop') }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
{{ $t('You can log in later to re-enable your account. Do you want to continue?') }}
|
<p class="text-base mb-4">{{ $t('You can log in later to re-enable your account. Do you want to continue?') }}</p>
|
||||||
</div>
|
<n-form-item
|
||||||
<FormControl
|
|
||||||
v-if="user.is_2fa_enabled"
|
v-if="user.is_2fa_enabled"
|
||||||
class="mt-4"
|
|
||||||
:label="$t('Enter your 2FA code to confirm')"
|
:label="$t('Enter your 2FA code to confirm')"
|
||||||
v-model="disableAccount2FACode"
|
class="mt-4"
|
||||||
/>
|
|
||||||
<ErrorMessage class="mt-2" :message="$resources.disableAccount.error" />
|
|
||||||
</template>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<Dialog
|
|
||||||
:options="{
|
|
||||||
title: $t('Enable Account'),
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
label: $t('Enable Account'),
|
|
||||||
variant: 'solid',
|
|
||||||
loading: $resources.enableAccount.loading,
|
|
||||||
onClick: () => $resources.enableAccount.submit(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}"
|
|
||||||
v-model="showEnableAccountDialog"
|
|
||||||
>
|
>
|
||||||
<template v-slot:body-content>
|
<n-input v-model:value="disableAccount2FACode" />
|
||||||
<div class="prose text-base">
|
</n-form-item>
|
||||||
{{ $t('After confirming this action:') }}
|
<n-alert
|
||||||
<ul>
|
v-if="$resources.disableAccount.error"
|
||||||
|
type="error"
|
||||||
|
class="mt-4"
|
||||||
|
:title="$resources.disableAccount.error"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 启用账户对话框 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showEnableAccountDialog"
|
||||||
|
preset="dialog"
|
||||||
|
:title="$t('Enable Account')"
|
||||||
|
:positive-text="$t('Enable Account')"
|
||||||
|
:loading="$resources.enableAccount.loading"
|
||||||
|
:mask-closable="true"
|
||||||
|
:close-on-esc="true"
|
||||||
|
@positive-click="() => $resources.enableAccount.submit()"
|
||||||
|
>
|
||||||
|
<div class="py-4">
|
||||||
|
<p class="text-base mb-4">{{ $t('After confirming this action:') }}</p>
|
||||||
|
<ul class="list-disc list-inside space-y-2 text-sm text-gray-700 mb-4">
|
||||||
<li>{{ $t('Your account will be enabled') }}</li>
|
<li>{{ $t('Your account will be enabled') }}</li>
|
||||||
<li>{{ $t('Your suspended sites will be reactivated') }}</li>
|
<li>{{ $t('Your suspended sites will be reactivated') }}</li>
|
||||||
<li>{{ $t('Your account billing will resume') }}</li>
|
<li>{{ $t('Your account billing will resume') }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
{{ $t('Do you want to continue?') }}
|
<p class="text-base">{{ $t('Do you want to continue?') }}</p>
|
||||||
|
<n-alert
|
||||||
|
v-if="$resources.enableAccount.error"
|
||||||
|
type="error"
|
||||||
|
class="mt-4"
|
||||||
|
:title="$resources.enableAccount.error"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ErrorMessage class="mt-2" :message="$resources.enableAccount.error" />
|
</n-modal>
|
||||||
</template>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<AddPrepaidCreditsDialog
|
<AddPrepaidCreditsDialog
|
||||||
:showMessage="showMessage"
|
:showMessage="showMessage"
|
||||||
@ -194,27 +301,61 @@
|
|||||||
v-if="showResetPasswordDialog"
|
v-if="showResetPasswordDialog"
|
||||||
v-model="showResetPasswordDialog"
|
v-model="showResetPasswordDialog"
|
||||||
/>
|
/>
|
||||||
</Card>
|
|
||||||
<TFADialog v-model="show2FADialog" />
|
<TFADialog v-model="show2FADialog" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import { defineAsyncComponent, h } from 'vue';
|
import { defineAsyncComponent, h } from 'vue';
|
||||||
|
import {
|
||||||
|
NCard,
|
||||||
|
NSpace,
|
||||||
|
NButton,
|
||||||
|
NAvatar,
|
||||||
|
NList,
|
||||||
|
NListItem,
|
||||||
|
NThing,
|
||||||
|
NModal,
|
||||||
|
NForm,
|
||||||
|
NFormItem,
|
||||||
|
NFormItemGi,
|
||||||
|
NInput,
|
||||||
|
NGrid,
|
||||||
|
NAlert,
|
||||||
|
NIcon,
|
||||||
|
} from 'naive-ui';
|
||||||
import FileUploader from '@/components/FileUploader.vue';
|
import FileUploader from '@/components/FileUploader.vue';
|
||||||
import { confirmDialog, renderDialog } from '../../../utils/components';
|
import { confirmDialog, 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';
|
||||||
import AddPrepaidCreditsDialog from '../../billing/AddPrepaidCreditsDialog.vue';
|
import AddPrepaidCreditsDialog from '../../billing/AddPrepaidCreditsDialog.vue';
|
||||||
|
import EditIcon from '~icons/lucide/edit';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AccountProfile',
|
name: 'AccountProfile',
|
||||||
components: {
|
components: {
|
||||||
|
NCard,
|
||||||
|
NSpace,
|
||||||
|
NButton,
|
||||||
|
NAvatar,
|
||||||
|
NList,
|
||||||
|
NListItem,
|
||||||
|
NThing,
|
||||||
|
NModal,
|
||||||
|
NForm,
|
||||||
|
NFormItem,
|
||||||
|
NFormItemGi,
|
||||||
|
NInput,
|
||||||
|
NGrid,
|
||||||
|
NAlert,
|
||||||
|
NIcon,
|
||||||
TFADialog,
|
TFADialog,
|
||||||
ResetPasswordDialog,
|
ResetPasswordDialog,
|
||||||
FileUploader,
|
FileUploader,
|
||||||
AddPrepaidCreditsDialog,
|
AddPrepaidCreditsDialog,
|
||||||
|
EditIcon,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -229,6 +370,7 @@ export default {
|
|||||||
showMessage: false,
|
showMessage: false,
|
||||||
draftInvoice: {},
|
draftInvoice: {},
|
||||||
unpaidInvoices: [] | {},
|
unpaidInvoices: [] | {},
|
||||||
|
windowWidth: window.innerWidth,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -238,6 +380,24 @@ export default {
|
|||||||
user() {
|
user() {
|
||||||
return this.$team?.pg?.user_info;
|
return this.$team?.pg?.user_info;
|
||||||
},
|
},
|
||||||
|
isMobile() {
|
||||||
|
return this.windowWidth <= 768;
|
||||||
|
},
|
||||||
|
avatarSize() {
|
||||||
|
return this.isMobile ? 80 : 96;
|
||||||
|
},
|
||||||
|
modalStyle() {
|
||||||
|
return {
|
||||||
|
width: this.isMobile ? '95vw' : '800px',
|
||||||
|
maxWidth: this.isMobile ? '95vw' : '90vw',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inputSize() {
|
||||||
|
return this.isMobile ? 'medium' : 'large';
|
||||||
|
},
|
||||||
|
buttonSize() {
|
||||||
|
return this.isMobile ? 'medium' : 'medium';
|
||||||
|
},
|
||||||
twoFactorAuthTitle() {
|
twoFactorAuthTitle() {
|
||||||
return this.user.is_2fa_enabled
|
return this.user.is_2fa_enabled
|
||||||
? this.$t('Disable Two-Factor Authentication')
|
? this.$t('Disable Two-Factor Authentication')
|
||||||
@ -342,7 +502,21 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.handleResize();
|
||||||
|
window.addEventListener('resize', this.handleResize);
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener('resize', this.handleResize);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleResize() {
|
||||||
|
this.windowWidth = window.innerWidth;
|
||||||
|
},
|
||||||
|
handleSaveProfile() {
|
||||||
|
// 提交资源,onSuccess 中会关闭对话框
|
||||||
|
this.$resources.updateProfile.submit();
|
||||||
|
},
|
||||||
reloadAccount() {
|
reloadAccount() {
|
||||||
this.$team.reload();
|
this.$team.reload();
|
||||||
},
|
},
|
||||||
@ -461,3 +635,292 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.profile-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
transition: box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card:hover {
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
transition: box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card:hover {
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 个人资料头部布局 */
|
||||||
|
.profile-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 24px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar-wrapper {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
border: 3px solid #f3f4f6;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-edit-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -4px;
|
||||||
|
right: -4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-name {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
line-height: 1.2;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-detail-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-detail-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-detail-value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #111827;
|
||||||
|
word-break: break-word;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-actions {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗样式 */
|
||||||
|
:deep(.profile-edit-modal .n-card) {
|
||||||
|
width: 800px;
|
||||||
|
max-width: 90vw;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-card-body) {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-input) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-form-item-label) {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-card__action) {
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.profile-card,
|
||||||
|
.settings-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端个人资料头部布局 */
|
||||||
|
.profile-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-name {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-details {
|
||||||
|
align-items: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-detail-item {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-detail-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-detail-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
min-width: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-detail-value {
|
||||||
|
font-size: 14px;
|
||||||
|
width: 100%;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-actions {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-actions .n-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端弹窗优化 */
|
||||||
|
:deep(.profile-edit-modal .n-card) {
|
||||||
|
width: 95vw !important;
|
||||||
|
max-width: 95vw !important;
|
||||||
|
margin: 20px auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-card-body) {
|
||||||
|
padding: 20px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-card__header) {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-card__action) {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-space) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-button) {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-form-item-label) {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-input) {
|
||||||
|
font-size: 16px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设置列表移动端优化 */
|
||||||
|
:deep(.settings-card .n-list-item) {
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.settings-card .n-thing) {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.settings-card .n-thing-main) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.settings-card .n-thing-main__action) {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.settings-card .n-button) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 超小屏幕优化 */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
:deep(.profile-edit-modal .n-card) {
|
||||||
|
width: 100vw !important;
|
||||||
|
max-width: 100vw !important;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-card-body) {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.profile-edit-modal .n-card__action) {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid var(--n-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user