817 lines
19 KiB
Vue
817 lines
19 KiB
Vue
<template>
|
|
<div class="profile-page">
|
|
<div class="profile-screen">
|
|
<section class="card profile-card">
|
|
<div class="top-row">
|
|
<label class="avatar-wrap" :aria-label="t('profile.changeAvatar')">
|
|
<input
|
|
type="file"
|
|
accept="image/*"
|
|
class="avatar-input"
|
|
@change="onAvatarFileChange"
|
|
/>
|
|
<div class="avatar">
|
|
<v-progress-circular
|
|
v-if="avatarUploading"
|
|
indeterminate
|
|
size="32"
|
|
width="2"
|
|
class="avatar-loading"
|
|
/>
|
|
<template v-else>
|
|
<img v-if="avatarImage" :src="avatarImage" :alt="displayName" class="avatar-img" />
|
|
<span v-else>{{ avatarText }}</span>
|
|
<span class="avatar-overlay">
|
|
<v-icon size="18">mdi-camera</v-icon>
|
|
</span>
|
|
</template>
|
|
</div>
|
|
</label>
|
|
<div class="info-col">
|
|
<div class="name-text">{{ displayName }}</div>
|
|
<div class="acc-text">{{ t('profile.uidLabel', { uid: userIdText }) }}</div>
|
|
<div class="tag-text">{{ userTag }}</div>
|
|
</div>
|
|
<button class="edit-btn" type="button" @click="onEditProfile">{{ t('profile.edit') }}</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card wallet-card">
|
|
<div class="wallet-head">
|
|
<span class="wallet-title">{{ t('profile.walletOverview') }}</span>
|
|
<button class="wallet-link" type="button" @click="goWallet">{{ t('profile.walletDetail') }} ></button>
|
|
</div>
|
|
<div class="wallet-balance">${{ totalBalance }}</div>
|
|
<div class="wallet-sub">
|
|
{{ t('profile.walletSub', { available: availableBalance, frozen: frozenBalance }) }}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card menu-card">
|
|
<div class="menu-title">{{ t('profile.accountSettings') }}</div>
|
|
<button v-for="item in settingItems" :key="item.label" class="menu-item" type="button" @click="goSetting(item)">
|
|
<span>{{ item.label }}</span>
|
|
<span v-if="item.action === 'locale'" class="menu-locale">{{ currentLocaleLabel }}</span>
|
|
<span v-else-if="item.action === 'wallet'" class="menu-locale">{{ walletAddressShort }}</span>
|
|
<span v-else class="menu-arrow">></span>
|
|
</button>
|
|
</section>
|
|
|
|
<button class="logout-btn" type="button" :disabled="logoutLoading" @click="logout">
|
|
{{ t('common.logout') }}
|
|
</button>
|
|
</div>
|
|
|
|
<v-dialog v-model="localeDialogOpen" max-width="360">
|
|
<v-card class="locale-dialog-card" rounded="xl" elevation="0">
|
|
<div class="locale-dialog-title">{{ t('profile.selectLanguage') }}</div>
|
|
<button
|
|
v-for="opt in localeStore.localeOptions"
|
|
:key="opt.value"
|
|
class="locale-option"
|
|
type="button"
|
|
@click="chooseLocale(opt.value)"
|
|
>
|
|
<span>{{ opt.label }}</span>
|
|
<span v-if="opt.value === localeStore.currentLocale" class="locale-selected">✓</span>
|
|
</button>
|
|
</v-card>
|
|
</v-dialog>
|
|
|
|
<v-dialog v-model="walletDialogOpen" max-width="420">
|
|
<v-card class="wallet-dialog-card" rounded="xl" elevation="0">
|
|
<div class="wallet-dialog-title">{{ t('profile.currentWalletAddress') }}</div>
|
|
<div class="wallet-dialog-address">{{ walletAddressText }}</div>
|
|
<div class="wallet-dialog-actions">
|
|
<button class="wallet-copy-btn" type="button" :disabled="!walletAddress" @click="copyWalletAddress">
|
|
{{ t('profile.copyAddress') }}
|
|
</button>
|
|
</div>
|
|
</v-card>
|
|
</v-dialog>
|
|
|
|
<v-dialog v-model="editNameDialogOpen" max-width="420">
|
|
<v-card class="name-dialog-card" rounded="xl" elevation="0">
|
|
<div class="name-dialog-title">{{ t('profile.editNameTitle') }}</div>
|
|
<v-text-field
|
|
v-model="editingName"
|
|
:label="t('profile.newUserName')"
|
|
variant="outlined"
|
|
density="comfortable"
|
|
hide-details="auto"
|
|
:error-messages="nameError ? [nameError] : []"
|
|
:hint="t('profile.nameFormatHint')"
|
|
persistent-hint
|
|
/>
|
|
<div class="name-dialog-actions">
|
|
<button class="name-dialog-cancel-btn" type="button" :disabled="isSaving" @click="closeEditNameDialog">
|
|
{{ t('profile.cancel') }}
|
|
</button>
|
|
<button class="name-dialog-save-btn" type="button" :disabled="isSaving" @click="saveName">
|
|
<v-progress-circular
|
|
v-if="isSaving"
|
|
indeterminate
|
|
size="18"
|
|
width="2"
|
|
class="save-btn-spinner"
|
|
/>
|
|
<span v-else>{{ t('profile.save') }}</span>
|
|
</button>
|
|
</div>
|
|
</v-card>
|
|
</v-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useLocaleStore } from '../stores/locale'
|
|
import { useToastStore } from '../stores/toast'
|
|
import { useUserStore, type UserInfo } from '../stores/user'
|
|
import { setSelfUsername, setSelfHeaderImg } from '@/api/user'
|
|
import { upload } from '@/api/fileUpload'
|
|
import type { LocaleCode } from '@/plugins/i18n'
|
|
|
|
interface SettingItem {
|
|
label: string
|
|
route?: string
|
|
action?: 'locale' | 'wallet'
|
|
}
|
|
|
|
const router = useRouter()
|
|
const { t } = useI18n()
|
|
const localeStore = useLocaleStore()
|
|
const userStore = useUserStore()
|
|
const toastStore = useToastStore()
|
|
const localeDialogOpen = ref(false)
|
|
const walletDialogOpen = ref(false)
|
|
const logoutLoading = ref(false)
|
|
const editNameDialogOpen = ref(false)
|
|
const editingName = ref('')
|
|
const nameError = ref<string | null>(null)
|
|
const isSaving = ref(false)
|
|
const avatarUploading = ref(false)
|
|
|
|
const currentLocaleLabel = computed(() => {
|
|
return (
|
|
localeStore.localeOptions.find((opt) => opt.value === localeStore.currentLocale)?.label ??
|
|
String(localeStore.currentLocale)
|
|
)
|
|
})
|
|
|
|
function readStringFromUser(keys: string[]): string {
|
|
const user = userStore.user as Record<string, unknown> | null
|
|
if (!user) return ''
|
|
for (const key of keys) {
|
|
const value = user[key]
|
|
if (typeof value === 'string' && value.trim()) return value.trim()
|
|
}
|
|
return ''
|
|
}
|
|
|
|
const walletAddress = computed(() => {
|
|
const user = userStore.user as Record<string, unknown> | null
|
|
if (!user) return ''
|
|
const candidateKeys = ['walletAddress', 'address', 'wallet', 'walletAddr', 'ethAddress']
|
|
for (const key of candidateKeys) {
|
|
const value = user[key]
|
|
if (typeof value === 'string' && value.trim()) return value.trim()
|
|
}
|
|
return ''
|
|
})
|
|
|
|
const walletAddressText = computed(() => walletAddress.value || t('profile.walletAddressUnavailable'))
|
|
const walletAddressShort = computed(() => {
|
|
const value = walletAddress.value
|
|
if (!value) return t('profile.unbound')
|
|
if (value.length <= 14) return value
|
|
return `${value.slice(0, 6)}...${value.slice(-4)}`
|
|
})
|
|
|
|
const rawUser = computed(() => (userStore.user ?? {}) as Record<string, unknown>)
|
|
const userNameRaw = computed(() => {
|
|
const v = rawUser.value.userName
|
|
return typeof v === 'string' && v.trim() ? v.trim() : ''
|
|
})
|
|
const displayName = computed(() => userNameRaw.value || t('profile.defaultName'))
|
|
const userIdText = computed(() => {
|
|
const uid = rawUser.value.id ?? rawUser.value.ID
|
|
if (uid == null || uid === '') return '--'
|
|
return String(uid)
|
|
})
|
|
const avatarImage = computed(() => userStore.avatarUrl || '')
|
|
const avatarText = computed(() => {
|
|
const first = displayName.value.trim().charAt(0)
|
|
return first ? first.toUpperCase() : 'U'
|
|
})
|
|
const hasVip = computed(() => {
|
|
const user = rawUser.value
|
|
const candidates = [user.isVip, user.vip, user.vipLevel, user.memberLevel]
|
|
return candidates.some((v) => {
|
|
if (typeof v === 'boolean') return v
|
|
if (typeof v === 'number') return v > 0
|
|
if (typeof v === 'string') return v.trim() === '1' || v.trim().toLowerCase() === 'vip'
|
|
return false
|
|
})
|
|
})
|
|
const userTag = computed(() => (hasVip.value ? t('profile.vipTrader') : t('profile.trader')))
|
|
const totalBalance = computed(() => userStore.balance || '0.00')
|
|
const availableBalance = computed(() => {
|
|
const val = readStringFromUser(['availableBalance', 'available', 'walletAvailable'])
|
|
return val || totalBalance.value
|
|
})
|
|
const frozenBalance = computed(() => {
|
|
return readStringFromUser(['frozenBalance', 'frozen', 'walletFrozen']) || '0.00'
|
|
})
|
|
const settingItems = computed<SettingItem[]>(() => [
|
|
{ label: t('profile.walletManage'), action: 'wallet' },
|
|
{ label: t('profile.apiKeyManage'), route: '/api-key' },
|
|
{ label: t('profile.language'), action: 'locale' },
|
|
])
|
|
|
|
function goSetting(item: SettingItem) {
|
|
if (item.action === 'wallet') {
|
|
walletDialogOpen.value = true
|
|
return
|
|
}
|
|
if (item.action === 'locale') {
|
|
localeDialogOpen.value = true
|
|
return
|
|
}
|
|
if (!item.route) return
|
|
router.push(item.route)
|
|
}
|
|
|
|
function chooseLocale(locale: LocaleCode) {
|
|
localeStore.setLocale(locale)
|
|
localeDialogOpen.value = false
|
|
}
|
|
|
|
async function copyWalletAddress() {
|
|
if (!walletAddress.value) return
|
|
try {
|
|
await navigator.clipboard.writeText(walletAddress.value)
|
|
walletDialogOpen.value = false
|
|
toastStore.show(t('profile.copySuccess'))
|
|
} catch {
|
|
toastStore.show(t('profile.copyFailed'), 'error')
|
|
}
|
|
}
|
|
|
|
function onEditProfile() {
|
|
editNameDialogOpen.value = true
|
|
editingName.value = userNameRaw.value
|
|
nameError.value = null
|
|
}
|
|
|
|
function goWallet() {
|
|
router.push('/wallet')
|
|
}
|
|
|
|
function validateUserName(name: string): string | null {
|
|
const v = name.trim()
|
|
if (!v) return t('profile.nameRequired')
|
|
if (v.length < MIN_USER_NAME_LEN) return t('profile.nameTooShort', { min: MIN_USER_NAME_LEN })
|
|
if (v.length > MAX_USER_NAME_LEN) return t('profile.nameTooLong', { max: MAX_USER_NAME_LEN })
|
|
const allowedRe = /^[a-z0-9_]+$/i
|
|
if (!allowedRe.test(v)) return t('profile.nameInvalidFormat')
|
|
return null
|
|
}
|
|
|
|
function closeEditNameDialog() {
|
|
if (isSaving.value) return
|
|
editNameDialogOpen.value = false
|
|
nameError.value = null
|
|
}
|
|
|
|
async function saveName() {
|
|
if (isSaving.value) return
|
|
const err = validateUserName(editingName.value)
|
|
if (err) {
|
|
nameError.value = err
|
|
return
|
|
}
|
|
|
|
const trimmed = editingName.value.trim()
|
|
const token = userStore.token
|
|
const u = userStore.user as UserInfo | null
|
|
if (!token || !u) {
|
|
toastStore.show(t('error.pleaseLogin'), 'error')
|
|
return
|
|
}
|
|
|
|
isSaving.value = true
|
|
nameError.value = null
|
|
try {
|
|
const authHeaders = userStore.getAuthHeaders()
|
|
if (!authHeaders) {
|
|
toastStore.show(t('error.pleaseLogin'), 'error')
|
|
return
|
|
}
|
|
|
|
const res = await setSelfUsername(authHeaders, { username: trimmed })
|
|
if (res.code !== 0 && res.code !== 200) {
|
|
const errMsg = res.msg || t('profile.nameSaveFailed')
|
|
nameError.value = errMsg
|
|
toastStore.show(errMsg, 'error')
|
|
return
|
|
}
|
|
|
|
// 接口成功后刷新用户信息,确保 userName/headerImg 等字段一致
|
|
await userStore.fetchUserInfo()
|
|
toastStore.show(t('profile.nameSaved'))
|
|
editNameDialogOpen.value = false
|
|
} catch (e) {
|
|
const errMsg = e instanceof Error ? e.message : t('profile.nameSaveFailed')
|
|
nameError.value = errMsg
|
|
toastStore.show(errMsg, 'error')
|
|
} finally {
|
|
isSaving.value = false
|
|
}
|
|
}
|
|
|
|
async function onAvatarFileChange(e: Event) {
|
|
const input = e.target as HTMLInputElement
|
|
const file = input.files?.[0]
|
|
if (!file || !file.type.startsWith('image/')) return
|
|
if (!userStore.isLoggedIn) {
|
|
toastStore.show(t('error.pleaseLogin'), 'error')
|
|
return
|
|
}
|
|
const authHeaders = userStore.getAuthHeaders()
|
|
if (!authHeaders) {
|
|
toastStore.show(t('error.pleaseLogin'), 'error')
|
|
return
|
|
}
|
|
avatarUploading.value = true
|
|
try {
|
|
const uploadRes = await upload(file, authHeaders)
|
|
const fileUrl = uploadRes.data?.file?.url
|
|
if (!fileUrl || (uploadRes.code !== 0 && uploadRes.code !== 200)) {
|
|
toastStore.show(uploadRes.msg || t('profile.avatarUploadFailed'), 'error')
|
|
return
|
|
}
|
|
const setRes = await setSelfHeaderImg(authHeaders, { headerImg: fileUrl })
|
|
if (setRes.code !== 0 && setRes.code !== 200) {
|
|
toastStore.show(setRes.msg || t('profile.avatarUploadFailed'), 'error')
|
|
return
|
|
}
|
|
await userStore.fetchUserInfo()
|
|
toastStore.show(t('profile.avatarUploadSuccess'))
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : t('profile.avatarUploadFailed')
|
|
toastStore.show(msg, 'error')
|
|
} finally {
|
|
avatarUploading.value = false
|
|
input.value = ''
|
|
}
|
|
}
|
|
|
|
const MIN_USER_NAME_LEN = 2
|
|
const MAX_USER_NAME_LEN = 20
|
|
|
|
async function logout() {
|
|
if (logoutLoading.value) return
|
|
logoutLoading.value = true
|
|
try {
|
|
await userStore.logout()
|
|
router.push('/login')
|
|
} catch {
|
|
toastStore.show(t('error.requestFailed'), 'error')
|
|
} finally {
|
|
logoutLoading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
if (!userStore.isLoggedIn) return
|
|
userStore.fetchUserInfo()
|
|
userStore.fetchUsdcBalance()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.profile-page {
|
|
min-height: 100vh;
|
|
background: #ffffff;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: flex-start;
|
|
padding: 0;
|
|
}
|
|
|
|
.profile-screen {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
min-height: 0;
|
|
background: #fcfcfc;
|
|
font-family: Inter, sans-serif;
|
|
padding: 16px 16px 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.card {
|
|
width: 100%;
|
|
background: #ffffff;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 16px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.profile-card {
|
|
padding: 20px;
|
|
}
|
|
|
|
.top-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.avatar-wrap {
|
|
cursor: pointer;
|
|
display: block;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.avatar-input {
|
|
position: absolute;
|
|
width: 0.1px;
|
|
height: 0.1px;
|
|
opacity: 0;
|
|
overflow: hidden;
|
|
z-index: -1;
|
|
}
|
|
|
|
.avatar {
|
|
width: 56px;
|
|
height: 56px;
|
|
border-radius: 999px;
|
|
background: #5b5bd6;
|
|
color: #ffffff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
flex-shrink: 0;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.avatar-loading {
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
.avatar-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #fff;
|
|
opacity: 0;
|
|
transition: opacity 0.2s;
|
|
}
|
|
|
|
.avatar-wrap:hover .avatar-overlay {
|
|
opacity: 1;
|
|
}
|
|
|
|
.avatar-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.info-col {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.name-text {
|
|
color: #111827;
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
line-height: 1.1;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.acc-text {
|
|
color: #6b7280;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.tag-text {
|
|
color: #5b5bd6;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.edit-btn {
|
|
height: 34px;
|
|
padding: 0 12px;
|
|
border-radius: 12px;
|
|
border: 1px solid #e5e7eb;
|
|
background: #fcfcfc;
|
|
color: #111827;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.wallet-card {
|
|
padding: 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.wallet-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.wallet-title {
|
|
color: #111827;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.wallet-link {
|
|
border: 0;
|
|
background: transparent;
|
|
color: #5b5bd6;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
}
|
|
|
|
.wallet-balance {
|
|
color: #111827;
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.wallet-sub {
|
|
color: #6b7280;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.wallet-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.action-btn {
|
|
flex: 1;
|
|
height: 40px;
|
|
border-radius: 12px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.action-primary {
|
|
border: 0;
|
|
background: #5b5bd6;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.action-secondary {
|
|
border: 1px solid #e5e7eb;
|
|
background: #fcfcfc;
|
|
color: #111827;
|
|
}
|
|
|
|
.menu-card {
|
|
padding: 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.menu-title {
|
|
color: #111827;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.menu-item {
|
|
height: 44px;
|
|
padding: 0 4px;
|
|
border: 0;
|
|
background: transparent;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
color: #111827;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.menu-arrow {
|
|
color: #9ca3af;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.menu-locale {
|
|
color: #6b7280;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.logout-btn {
|
|
width: 100%;
|
|
height: 48px;
|
|
border: 0;
|
|
border-radius: 12px;
|
|
background: #fee2e2;
|
|
color: #dc2626;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.logout-btn:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.locale-dialog-card {
|
|
border: 1px solid #e5e7eb;
|
|
background: #ffffff;
|
|
padding: 8px;
|
|
}
|
|
|
|
.locale-dialog-title {
|
|
padding: 10px 10px 6px;
|
|
color: #111827;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.locale-option {
|
|
width: 100%;
|
|
height: 42px;
|
|
border: 0;
|
|
border-radius: 10px;
|
|
background: transparent;
|
|
padding: 0 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
color: #111827;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.locale-option:hover {
|
|
background: #f9fafb;
|
|
}
|
|
|
|
.locale-selected {
|
|
color: #5b5bd6;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.wallet-dialog-card {
|
|
border: 1px solid #e5e7eb;
|
|
background: #ffffff;
|
|
padding: 14px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.wallet-dialog-title {
|
|
color: #111827;
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.wallet-dialog-address {
|
|
border-radius: 10px;
|
|
border: 1px solid #e5e7eb;
|
|
background: #fcfcfc;
|
|
padding: 12px;
|
|
color: #111827;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
line-height: 1.35;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.wallet-dialog-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.wallet-copy-btn {
|
|
height: 34px;
|
|
border-radius: 10px;
|
|
border: 0;
|
|
padding: 0 14px;
|
|
background: #5b5bd6;
|
|
color: #ffffff;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.wallet-copy-btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.name-dialog-card {
|
|
border: 1px solid #e5e7eb;
|
|
background: #ffffff;
|
|
padding: 14px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.name-dialog-title {
|
|
color: #111827;
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
padding: 2px 2px 0;
|
|
}
|
|
|
|
.name-dialog-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
justify-content: flex-end;
|
|
padding-top: 2px;
|
|
}
|
|
|
|
.name-dialog-cancel-btn {
|
|
height: 34px;
|
|
border-radius: 10px;
|
|
border: 1px solid #e5e7eb;
|
|
background: #fcfcfc;
|
|
color: #111827;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
padding: 0 14px;
|
|
}
|
|
|
|
.name-dialog-cancel-btn:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.name-dialog-save-btn {
|
|
height: 34px;
|
|
min-width: 72px;
|
|
border-radius: 10px;
|
|
border: 0;
|
|
background: #5b5bd6;
|
|
color: #ffffff;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
padding: 0 14px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.name-dialog-save-btn:disabled {
|
|
opacity: 0.8;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.save-btn-spinner {
|
|
color: #ffffff;
|
|
}
|
|
</style>
|