Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front
This commit is contained in:
@@ -108,3 +108,20 @@ export const verifyEmailCode = (verifyCode: string): Promise<ApiResponse> => {
|
|||||||
data: { verifyCode }
|
data: { verifyCode }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取用户语言设置
|
||||||
|
export const getUserLanguage = (): Promise<ApiResponse> => {
|
||||||
|
return request({
|
||||||
|
url: '/buyer/profile/getLanguage',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置语言
|
||||||
|
export const setUserLanguage = (language: string): Promise<ApiResponse> => {
|
||||||
|
return request({
|
||||||
|
url: '/buyer/profile/setLanguage',
|
||||||
|
method: 'post',
|
||||||
|
data: { language }
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,16 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { useUserInfoStore } from '@/stores/userInfo'
|
||||||
|
import { getUserLanguage } from '@/api/user'
|
||||||
|
import i18n from '@/lang/index'
|
||||||
|
|
||||||
|
// 语言映射:后端格式 -> i18n 格式
|
||||||
|
const backendToI18nLanguage: Record<string, string> = {
|
||||||
|
'en': 'ENGLISH',
|
||||||
|
'zh-CN': 'CHINESE_SIMPLIFIED'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 语言同步状态缓存(避免每次路由切换都请求)
|
||||||
|
let languageSynced = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由缓存机制:
|
* 路由缓存机制:
|
||||||
@@ -83,6 +95,52 @@ router.beforeEach((to, from, next) => {
|
|||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
router.afterEach(() => {})
|
router.afterEach(async () => {
|
||||||
|
// 检查用户是否已登录
|
||||||
|
const userInfoStore = useUserInfoStore()
|
||||||
|
const token = userInfoStore.state.token
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
languageSynced = false // 未登录时重置同步状态
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经同步过,跳过
|
||||||
|
if (languageSynced) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取用户语言设置
|
||||||
|
const response = await getUserLanguage()
|
||||||
|
const userLanguage = (response as any)?.language // 后端返回 'en' 或 'zh-CN'
|
||||||
|
|
||||||
|
if (!userLanguage) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为 i18n 格式
|
||||||
|
const i18nLanguage = backendToI18nLanguage[userLanguage]
|
||||||
|
|
||||||
|
if (!i18nLanguage) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前 i18n 语言
|
||||||
|
const currentLocale = i18n.global.locale.value
|
||||||
|
|
||||||
|
// 如果用户语言和本地 i18n 不一致,更新 i18n
|
||||||
|
if (i18nLanguage !== currentLocale) {
|
||||||
|
i18n.global.locale.value = i18nLanguage as 'ENGLISH' | 'CHINESE_SIMPLIFIED'
|
||||||
|
localStorage.setItem('language', i18nLanguage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记已同步
|
||||||
|
languageSynced = true
|
||||||
|
} catch (error) {
|
||||||
|
// 静默失败,不影响页面正常加载
|
||||||
|
console.warn('Failed to sync user language:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -87,6 +87,8 @@
|
|||||||
import myEvent from '@/utils/myEvent'
|
import myEvent from '@/utils/myEvent'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useUserInfoStore } from '@/stores/userInfo'
|
import { useUserInfoStore } from '@/stores/userInfo'
|
||||||
|
import { setUserLanguage } from '@/api/user'
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
const userInfoStore = useUserInfoStore()
|
const userInfoStore = useUserInfoStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -153,6 +155,12 @@
|
|||||||
const onLanguageClick = () => {
|
const onLanguageClick = () => {
|
||||||
locale.value = locale.value === 'ENGLISH' ? 'CHINESE_SIMPLIFIED' : 'ENGLISH'
|
locale.value = locale.value === 'ENGLISH' ? 'CHINESE_SIMPLIFIED' : 'ENGLISH'
|
||||||
localStorage.setItem('language', locale.value)
|
localStorage.setItem('language', locale.value)
|
||||||
|
console.log(locale.value)
|
||||||
|
const localeMap: Record<string, string> = {
|
||||||
|
ENGLISH: 'en',
|
||||||
|
CHINESE_SIMPLIFIED: 'zh-CN'
|
||||||
|
}
|
||||||
|
setUserLanguage(localeMap[locale.value])
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,42 @@
|
|||||||
<template>
|
<template>
|
||||||
<SettingsSection
|
<SettingsSection
|
||||||
:title="t('Settings.security.title')"
|
:title="t('Settings.security.title')"
|
||||||
:description="t('Settings.security.description')"
|
:description="t('Settings.security.description')"
|
||||||
content-class="security-container"
|
content-class="security-container"
|
||||||
>
|
>
|
||||||
<div class="inner-divider" />
|
<div class="inner-divider" />
|
||||||
<div class="security-row">
|
<div class="security-row">
|
||||||
<div class="security-inline-row">
|
<div class="security-inline-row">
|
||||||
<div class="security-label inline">{{ t('Settings.security.email') }}</div>
|
<div class="security-label inline">{{ t('Settings.security.email') }}</div>
|
||||||
<div class="security-static">{{ email }}</div>
|
<div class="security-static">{{ email }}</div>
|
||||||
<button v-show="isEditing" type="button" class="small-btn" @click="emit('reset-email')">
|
<button
|
||||||
{{ t('Settings.buttons.cancel') }}
|
v-show="isEditing && !isEditingEmail"
|
||||||
</button>
|
type="button"
|
||||||
</div>
|
class="small-btn"
|
||||||
</div>
|
@click="emit('edit-email')"
|
||||||
|
>
|
||||||
|
{{ t('Settings.buttons.edit') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-show="isEditing && isEditingEmail"
|
||||||
|
type="button"
|
||||||
|
class="small-btn"
|
||||||
|
@click="emit('reset-email')"
|
||||||
|
>
|
||||||
|
{{ t('Settings.buttons.cancel') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-show="isEditing" class="security-row">
|
<div v-show="isEditing && isEditingEmail" class="security-row">
|
||||||
<div class="security-label">{{ t('Settings.security.newEmail') }}</div>
|
<div class="security-label">{{ t('Settings.security.newEmail') }}</div>
|
||||||
<div class="outlined-field verify-field">
|
<div class="outlined-field verify-field">
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="newEmail"
|
:model-value="newEmail"
|
||||||
:placeholder="t('Settings.security.newEmailPlaceholder')"
|
:placeholder="t('Settings.security.newEmailPlaceholder')"
|
||||||
@update:model-value="emit('update:newEmail', String($event))"
|
@update:model-value="emit('update:newEmail', String($event))"
|
||||||
/>
|
/>
|
||||||
<!-- <button
|
<!-- <button
|
||||||
type="button"
|
type="button"
|
||||||
class="verify-btn"
|
class="verify-btn"
|
||||||
:class="{ verified: isEmailVerified }"
|
:class="{ verified: isEmailVerified }"
|
||||||
@@ -31,212 +44,235 @@
|
|||||||
>
|
>
|
||||||
{{ isEmailVerified ? t('Settings.security.verified') : t('Settings.security.verify') }}
|
{{ isEmailVerified ? t('Settings.security.verified') : t('Settings.security.verify') }}
|
||||||
</button> -->
|
</button> -->
|
||||||
</div>
|
</div>
|
||||||
<!-- <div v-if="isEmailVerified" class="security-tip verified-tip">
|
<!-- <div v-if="isEmailVerified" class="security-tip verified-tip">
|
||||||
{{ t('Settings.security.verifiedTip') }}
|
{{ t('Settings.security.verifiedTip') }}
|
||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inner-divider" />
|
<div class="inner-divider" />
|
||||||
|
|
||||||
<div class="security-row">
|
<div class="security-row">
|
||||||
<div class="security-inline-row">
|
<div class="security-inline-row">
|
||||||
<div class="security-label inline">{{ t('Settings.security.password') }}</div>
|
<div class="security-label inline">{{ t('Settings.security.password') }}</div>
|
||||||
<div class="security-static password-mask">.........</div>
|
<div class="security-static password-mask">.........</div>
|
||||||
<button v-show="isEditing" type="button" class="small-btn" @click="emit('reset-password')">
|
<button
|
||||||
{{ t('Settings.buttons.cancel') }}
|
v-show="isEditing && !isEditingPassword"
|
||||||
</button>
|
type="button"
|
||||||
</div>
|
class="small-btn"
|
||||||
</div>
|
@click="emit('edit-password')"
|
||||||
|
>
|
||||||
|
{{ t('Settings.buttons.edit') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-show="isEditing && isEditingPassword"
|
||||||
|
type="button"
|
||||||
|
class="small-btn"
|
||||||
|
@click="emit('reset-password')"
|
||||||
|
>
|
||||||
|
{{ t('Settings.buttons.cancel') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-show="isEditing" class="security-row">
|
<div v-show="isEditing && isEditingPassword" class="security-row">
|
||||||
<div class="security-label">{{ t('Settings.security.newPassword') }}</div>
|
<div class="security-label">{{ t('Settings.security.newPassword') }}</div>
|
||||||
<div class="outlined-field">
|
<div class="outlined-field">
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="newPassword"
|
:model-value="newPassword"
|
||||||
type="password"
|
type="password"
|
||||||
show-password
|
show-password
|
||||||
:placeholder="t('Settings.security.newPasswordPlaceholder')"
|
:placeholder="t('Settings.security.newPasswordPlaceholder')"
|
||||||
@update:model-value="emit('update:newPassword', String($event))"
|
@update:model-value="emit('update:newPassword', String($event))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="security-tip">{{ t('Settings.security.passwordTip') }}</div>
|
<div class="security-tip">{{ t('Settings.security.passwordTip') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-show="isEditing" class="security-row">
|
<div v-show="isEditing && isEditingPassword" class="security-row">
|
||||||
<div class="security-label">{{ t('Settings.security.currentPassword') }}</div>
|
<div class="security-label">{{ t('Settings.security.currentPassword') }}</div>
|
||||||
<div class="outlined-field">
|
<div class="outlined-field">
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="currentPassword"
|
:model-value="currentPassword"
|
||||||
type="password"
|
type="password"
|
||||||
show-password
|
show-password
|
||||||
:placeholder="t('Settings.security.currentPasswordPlaceholder')"
|
:placeholder="t('Settings.security.currentPasswordPlaceholder')"
|
||||||
@update:model-value="emit('update:currentPassword', String($event))"
|
@update:model-value="emit('update:currentPassword', String($event))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inner-divider" />
|
<div class="inner-divider" />
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import SettingsSection from './SettingsSection.vue'
|
import SettingsSection from './SettingsSection.vue'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
email: string
|
email: string
|
||||||
newEmail: string
|
newEmail: string
|
||||||
newPassword: string
|
newPassword: string
|
||||||
currentPassword: string
|
currentPassword: string
|
||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
isEmailVerified: boolean
|
isEditingEmail: boolean
|
||||||
}>()
|
isEditingPassword: boolean
|
||||||
|
isEmailVerified: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'update:newEmail', value: string): void
|
(event: 'update:newEmail', value: string): void
|
||||||
(event: 'update:newPassword', value: string): void
|
(event: 'update:newPassword', value: string): void
|
||||||
(event: 'update:currentPassword', value: string): void
|
(event: 'update:currentPassword', value: string): void
|
||||||
(event: 'reset-email'): void
|
(event: 'edit-email'): void
|
||||||
(event: 'reset-password'): void
|
(event: 'edit-password'): void
|
||||||
(event: 'verify-email'): void
|
(event: 'reset-email'): void
|
||||||
}>()
|
(event: 'reset-password'): void
|
||||||
|
(event: 'verify-email'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.field-text() {
|
.field-text() {
|
||||||
font-family: 'KaiseiOpti-Regular';
|
font-family: 'KaiseiOpti-Regular';
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
line-height: 2.4rem;
|
line-height: 2.4rem;
|
||||||
color: #232323;
|
color: #232323;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-frame() {
|
.field-frame() {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 4rem;
|
min-height: 4rem;
|
||||||
border: 0.1rem solid #979797;
|
border: 0.1rem solid #979797;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-wrapper() {
|
.control-wrapper() {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 0 2rem;
|
padding: 0 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-row + .security-row {
|
.security-row + .security-row {
|
||||||
margin-top: 2.8rem;
|
margin-top: 2.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-label {
|
.security-label {
|
||||||
margin: 0 0 0.8rem;
|
margin: 0 0 0.8rem;
|
||||||
font-family: 'KaiseiOpti-Medium';
|
font-family: 'KaiseiOpti-Medium';
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
line-height: 2.4rem;
|
line-height: 2.4rem;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
color: #585858;
|
color: #585858;
|
||||||
|
|
||||||
&.inline {
|
&.inline {
|
||||||
width: 10.8rem;
|
width: 10.8rem;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-static {
|
.security-static {
|
||||||
.field-text();
|
.field-text();
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 2.4rem;
|
min-height: 2.4rem;
|
||||||
padding: 0.1rem 0 0;
|
padding: 0.1rem 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-inline-row {
|
.security-inline-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 2.8rem;
|
gap: 2.8rem;
|
||||||
min-height: 3.2rem;
|
min-height: 3.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-tip {
|
.security-tip {
|
||||||
margin-top: 0.6rem;
|
margin-top: 0.6rem;
|
||||||
font-family: 'KaiseiOpti-Regular';
|
font-family: 'KaiseiOpti-Regular';
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
line-height: 1.6rem;
|
line-height: 1.6rem;
|
||||||
color: #9f9f9f;
|
color: #9f9f9f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.outlined-field {
|
.outlined-field {
|
||||||
.field-frame();
|
.field-frame();
|
||||||
|
|
||||||
:deep(.el-input) {
|
:deep(.el-input) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 4rem;
|
min-height: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper) {
|
||||||
.control-wrapper();
|
.control-wrapper();
|
||||||
min-height: 4rem;
|
min-height: 4rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.verify-field {
|
.verify-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 0.8rem;
|
margin-top: 0.8rem;
|
||||||
|
|
||||||
:deep(.el-input) {
|
:deep(.el-input) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.verify-btn {
|
.verify-btn {
|
||||||
border: none;
|
border: none;
|
||||||
min-width: 11rem;
|
min-width: 11rem;
|
||||||
height: 2.8rem;
|
height: 2.8rem;
|
||||||
line-height: 2.8rem;
|
line-height: 2.8rem;
|
||||||
border-left: 0.1rem solid #979797;
|
border-left: 0.1rem solid #979797;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
font-family: 'KaiseiOpti-Medium';
|
font-family: 'KaiseiOpti-Medium';
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
color: #232323;
|
color: #232323;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0 2rem;
|
padding: 0 2rem;
|
||||||
|
|
||||||
&.verified {
|
&.verified {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
background: #232323;
|
||||||
|
border-left-color: #232323;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-mask {
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
letter-spacing: 0.08rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-divider {
|
||||||
|
height: 1px;
|
||||||
|
margin: 2rem 0;
|
||||||
|
background-color: #c4c4c4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-btn {
|
||||||
|
width: 10rem;
|
||||||
|
height: 3.2rem;
|
||||||
|
align-self: flex-start;
|
||||||
|
border: 0.1rem solid #c4c4c4;
|
||||||
|
background: #f6f6f6;
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 2.6rem;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
color: #232323;
|
||||||
|
cursor: pointer;
|
||||||
|
/*
|
||||||
|
&.edit-btn {
|
||||||
|
border-color: #232323;
|
||||||
background: #232323;
|
background: #232323;
|
||||||
border-left-color: #232323;
|
color: #ffffff;
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-mask {
|
.verified-tip {
|
||||||
font-family: 'KaiseiOpti-Bold';
|
color: #6f7f68;
|
||||||
letter-spacing: 0.08rem;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.inner-divider {
|
|
||||||
height: 1px;
|
|
||||||
margin: 2rem 0;
|
|
||||||
background-color: #c4c4c4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-btn {
|
|
||||||
width: 10rem;
|
|
||||||
height: 3.2rem;
|
|
||||||
align-self: flex-start;
|
|
||||||
border: 0.1rem solid #c4c4c4;
|
|
||||||
background: #f6f6f6;
|
|
||||||
font-family: 'KaiseiOpti-Bold';
|
|
||||||
font-size: 1.2rem;
|
|
||||||
line-height: 2.6rem;
|
|
||||||
letter-spacing: -0.03em;
|
|
||||||
color: #232323;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.verified-tip {
|
|
||||||
color: #6f7f68;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -25,7 +25,11 @@
|
|||||||
v-model:current-password="securityDraft.currentPassword"
|
v-model:current-password="securityDraft.currentPassword"
|
||||||
:email="displayData.email"
|
:email="displayData.email"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
|
:is-editing-email="isEditingEmail"
|
||||||
|
:is-editing-password="isEditingPassword"
|
||||||
:is-email-verified="isEmailVerified"
|
:is-email-verified="isEmailVerified"
|
||||||
|
@edit-email="handleEditEmail"
|
||||||
|
@edit-password="handleEditPassword"
|
||||||
@reset-email="resetSecurityEmail"
|
@reset-email="resetSecurityEmail"
|
||||||
@reset-password="resetSecurityPassword"
|
@reset-password="resetSecurityPassword"
|
||||||
@verify-email="handleVerifyEmail"
|
@verify-email="handleVerifyEmail"
|
||||||
@@ -98,9 +102,13 @@
|
|||||||
fullName,
|
fullName,
|
||||||
roleModel,
|
roleModel,
|
||||||
needsEmailVerification,
|
needsEmailVerification,
|
||||||
|
isEditingEmail,
|
||||||
|
isEditingPassword,
|
||||||
handleEdit,
|
handleEdit,
|
||||||
handleDiscard,
|
handleDiscard,
|
||||||
handleSave,
|
handleSave,
|
||||||
|
handleEditEmail,
|
||||||
|
handleEditPassword,
|
||||||
resetSecurityEmail,
|
resetSecurityEmail,
|
||||||
resetSecurityPassword,
|
resetSecurityPassword,
|
||||||
handleVerifyEmail,
|
handleVerifyEmail,
|
||||||
|
|||||||
@@ -26,11 +26,24 @@ interface UseSettingsFormOptions {
|
|||||||
locale: Ref<string>
|
locale: Ref<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 前端 UI 使用的语言值(用于下拉选择)
|
||||||
const languageLocaleMap: Record<LanguageValue, 'ENGLISH' | 'CHINESE_SIMPLIFIED'> = {
|
const languageLocaleMap: Record<LanguageValue, 'ENGLISH' | 'CHINESE_SIMPLIFIED'> = {
|
||||||
english: 'ENGLISH',
|
english: 'ENGLISH',
|
||||||
chinese: 'CHINESE_SIMPLIFIED'
|
chinese: 'CHINESE_SIMPLIFIED'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 后端 API 使用的语言值
|
||||||
|
const backendLanguageMap: Record<'ENGLISH' | 'CHINESE_SIMPLIFIED', 'en' | 'zh-CN'> = {
|
||||||
|
ENGLISH: 'en',
|
||||||
|
CHINESE_SIMPLIFIED: 'zh-CN'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后端语言值转换为前端语言值
|
||||||
|
const backendToFrontendLanguage: Record<'en' | 'zh-CN', LanguageValue> = {
|
||||||
|
en: 'english',
|
||||||
|
'zh-CN': 'chinese'
|
||||||
|
}
|
||||||
|
|
||||||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||||
|
|
||||||
const createDefaultData = (): SettingsData => ({
|
const createDefaultData = (): SettingsData => ({
|
||||||
@@ -58,8 +71,15 @@ const normalizeLanguage = (language: string | null | undefined): LanguageValue =
|
|||||||
return '' as LanguageValue
|
return '' as LanguageValue
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalized = language.trim().toLowerCase()
|
// 后端返回 'en' 或 'zh-CN'
|
||||||
return normalized.includes('chinese') ? 'chinese' : 'english'
|
const trimmed = language.trim()
|
||||||
|
if (trimmed === 'en') {
|
||||||
|
return 'english'
|
||||||
|
} else if (trimmed === 'zh-CN') {
|
||||||
|
return 'chinese'
|
||||||
|
}
|
||||||
|
|
||||||
|
return '' as LanguageValue
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildSettingsDataFromProfile = (profile: Partial<UserProfile>): SettingsData => ({
|
const buildSettingsDataFromProfile = (profile: Partial<UserProfile>): SettingsData => ({
|
||||||
@@ -88,6 +108,8 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
const verificationTargetEmail = shallowRef('')
|
const verificationTargetEmail = shallowRef('')
|
||||||
const verifiedEmail = shallowRef('')
|
const verifiedEmail = shallowRef('')
|
||||||
const verificationCode = shallowRef('')
|
const verificationCode = shallowRef('')
|
||||||
|
const isEditingEmail = shallowRef(false)
|
||||||
|
const isEditingPassword = shallowRef(false)
|
||||||
|
|
||||||
const roleList = computed(() =>
|
const roleList = computed(() =>
|
||||||
roleValues.map((value) => ({
|
roleValues.map((value) => ({
|
||||||
@@ -178,6 +200,8 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
draftData.value = cloneSettingsData(sourceData.value)
|
draftData.value = cloneSettingsData(sourceData.value)
|
||||||
securityDraft.value = createEmptySecurityDraft()
|
securityDraft.value = createEmptySecurityDraft()
|
||||||
resetEmailVerificationState()
|
resetEmailVerificationState()
|
||||||
|
isEditingEmail.value = false
|
||||||
|
isEditingPassword.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
@@ -188,11 +212,21 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
const resetSecurityEmail = () => {
|
const resetSecurityEmail = () => {
|
||||||
securityDraft.value.newEmail = ''
|
securityDraft.value.newEmail = ''
|
||||||
resetEmailVerificationState()
|
resetEmailVerificationState()
|
||||||
|
isEditingEmail.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetSecurityPassword = () => {
|
const resetSecurityPassword = () => {
|
||||||
securityDraft.value.newPassword = ''
|
securityDraft.value.newPassword = ''
|
||||||
securityDraft.value.currentPassword = ''
|
securityDraft.value.currentPassword = ''
|
||||||
|
isEditingPassword.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEditEmail = () => {
|
||||||
|
isEditingEmail.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEditPassword = () => {
|
||||||
|
isEditingPassword.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDiscard = () => {
|
const handleDiscard = () => {
|
||||||
@@ -294,15 +328,24 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextEmail = securityDraft.value.newEmail.trim() || draftData.value.email
|
// 将前端语言值转换为后端格式
|
||||||
|
let backendLanguage = ''
|
||||||
|
if (draftData.value.language) {
|
||||||
|
const i18nLocale = languageLocaleMap[draftData.value.language as LanguageValue]
|
||||||
|
backendLanguage = backendLanguageMap[i18nLocale]
|
||||||
|
}
|
||||||
|
|
||||||
const nextData: UserProfile = {
|
const nextData: UserProfile = {
|
||||||
firstName: draftData.value.firstName.trim(),
|
firstName: draftData.value.firstName.trim(),
|
||||||
lastName: draftData.value.lastName.trim(),
|
lastName: draftData.value.lastName.trim(),
|
||||||
username: draftData.value.username.trim(),
|
username: draftData.value.username.trim(),
|
||||||
email: nextEmail,
|
email: securityDraft.value.newEmail.trim(),
|
||||||
roles: draftData.value.roles as string[],
|
roles: draftData.value.roles as string[],
|
||||||
language: draftData.value.language,
|
language: backendLanguage,
|
||||||
region: draftData.value.region
|
region: draftData.value.region,
|
||||||
|
newPassword: '',
|
||||||
|
oldPassword: '',
|
||||||
|
verifyCode: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果改邮箱或改密码,需要添加验证码和密码信息
|
// 如果改邮箱或改密码,需要添加验证码和密码信息
|
||||||
@@ -320,26 +363,34 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await updateUserProfile(nextData)
|
await updateUserProfile(nextData)
|
||||||
|
|
||||||
|
// 将后端返回的语言值转换为前端格式
|
||||||
|
const frontendLanguage = backendLanguage
|
||||||
|
? backendToFrontendLanguage[backendLanguage as 'en' | 'zh-CN']
|
||||||
|
: ''
|
||||||
|
|
||||||
const settingsData: SettingsData = {
|
const settingsData: SettingsData = {
|
||||||
firstName: nextData.firstName,
|
firstName: nextData.firstName,
|
||||||
lastName: nextData.lastName,
|
lastName: nextData.lastName,
|
||||||
username: nextData.username,
|
username: nextData.username,
|
||||||
email: nextData.email,
|
email: nextData.email || draftData.value.email,
|
||||||
roles: nextData.roles as RoleValue[],
|
roles: nextData.roles as RoleValue[],
|
||||||
language: nextData.language as LanguageValue | '',
|
language: frontendLanguage,
|
||||||
region: nextData.region as any
|
region: nextData.region as any
|
||||||
}
|
}
|
||||||
sourceData.value = cloneSettingsData(settingsData)
|
sourceData.value = cloneSettingsData(settingsData)
|
||||||
console.log(nextData)
|
console.log(nextData)
|
||||||
|
|
||||||
if (nextData.language && nextData.language !== previousLanguage) {
|
if (frontendLanguage && frontendLanguage !== previousLanguage) {
|
||||||
syncAppLanguage(nextData.language as LanguageValue)
|
syncAppLanguage(frontendLanguage as LanguageValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
draftData.value = cloneSettingsData(sourceData.value)
|
draftData.value = cloneSettingsData(sourceData.value)
|
||||||
securityDraft.value = createEmptySecurityDraft()
|
securityDraft.value = createEmptySecurityDraft()
|
||||||
resetEmailVerificationState()
|
resetEmailVerificationState()
|
||||||
isEditing.value = false
|
isEditing.value = false
|
||||||
|
isEditingEmail.value = false
|
||||||
|
isEditingPassword.value = false
|
||||||
ElMessage.success(t('Settings.messages.settingsUpdated'))
|
ElMessage.success(t('Settings.messages.settingsUpdated'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(error)
|
console.warn(error)
|
||||||
@@ -385,11 +436,15 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
fullName,
|
fullName,
|
||||||
roleModel,
|
roleModel,
|
||||||
needsEmailVerification,
|
needsEmailVerification,
|
||||||
|
isEditingEmail,
|
||||||
|
isEditingPassword,
|
||||||
handleEdit,
|
handleEdit,
|
||||||
handleDiscard,
|
handleDiscard,
|
||||||
handleSave,
|
handleSave,
|
||||||
resetSecurityEmail,
|
resetSecurityEmail,
|
||||||
resetSecurityPassword,
|
resetSecurityPassword,
|
||||||
|
handleEditEmail,
|
||||||
|
handleEditPassword,
|
||||||
handleVerifyEmail,
|
handleVerifyEmail,
|
||||||
handleSendVerifyCode,
|
handleSendVerifyCode,
|
||||||
handleVerificationSubmit,
|
handleVerificationSubmit,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
class="assets-toolbar__download flex flex-center"
|
class="assets-toolbar__download flex flex-center"
|
||||||
:class="{ disabled: selectedCount < 1 }"
|
:class="{ disabled: selectedCount < 1 }"
|
||||||
v-loading="downloadingSelected"
|
v-loading="downloadingSelected"
|
||||||
@click="handleDownloadSelected"
|
@click="handleDownloadSelected(null)"
|
||||||
>
|
>
|
||||||
<SvgIcon name="downloadBtn" color="#fff" />
|
<SvgIcon name="downloadBtn" color="#fff" />
|
||||||
<span>{{ t('Wardrobe.assets.downloadSelected') }}</span>
|
<span>{{ t('Wardrobe.assets.downloadSelected') }}</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
download
|
download
|
||||||
:url="item.thumbnailUrl"
|
:url="item.thumbnailUrl"
|
||||||
:name="item.listingName"
|
:name="item.listingName"
|
||||||
@download.stop="handleDownloadSelected(item)"
|
@download="handleDownloadSelected(item)"
|
||||||
:showPrice="false"
|
:showPrice="false"
|
||||||
></CommodityItem>
|
></CommodityItem>
|
||||||
</div>
|
</div>
|
||||||
@@ -323,7 +323,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const downloadingSelected = ref(false)
|
const downloadingSelected = ref(false)
|
||||||
const handleDownloadSelected = (assets) => {
|
const handleDownloadSelected = (assets = null) => {
|
||||||
const items = assets ? [assets] : dataList.value.filter((item) => item.checked)
|
const items = assets ? [assets] : dataList.value.filter((item) => item.checked)
|
||||||
|
|
||||||
downloadingSelected.value = true
|
downloadingSelected.value = true
|
||||||
@@ -350,7 +350,8 @@
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Download failed:', error)
|
console.error('Download failed:', error)
|
||||||
}).finally(() => {
|
})
|
||||||
|
.finally(() => {
|
||||||
downloadingSelected.value = false
|
downloadingSelected.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
import { computed, onMounted, ref, shallowRef } from 'vue'
|
import { computed, onMounted, ref, shallowRef } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { fetchMyOrders ,fetchDownloadItemsByGet} from '@/api/user'
|
import { fetchMyOrders, fetchDownloadItemsByGet } from '@/api/user'
|
||||||
import ScItem from '@/views/shoppingCart/sc-item.vue'
|
import ScItem from '@/views/shoppingCart/sc-item.vue'
|
||||||
import Empty from './Empty.vue'
|
import Empty from './Empty.vue'
|
||||||
|
|
||||||
@@ -257,7 +257,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDownload = (order) => {
|
const handleDownload = (order) => {
|
||||||
console.log(order)
|
const ids = order.items.map((item) => item.id)
|
||||||
|
fetchDownloadItemsByGet({ ids }).then((res) => {
|
||||||
|
console.log(res)
|
||||||
|
const disposition = res.headers['content-disposition']
|
||||||
|
const fileName = disposition?.split('filename=')[1]?.replace(/"/g, '') || 'download.zip'
|
||||||
|
const blob = res.data
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
|
||||||
|
const timestamp = new Date().getTime()
|
||||||
|
link.download = fileName || `wardrobe_download_${timestamp}.zip`
|
||||||
|
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRouteBrand = (order: OrderRecord) => {
|
const handleRouteBrand = (order: OrderRecord) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user