diff --git a/src/api/user.ts b/src/api/user.ts index 815fac8..9e7da8c 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -108,3 +108,20 @@ export const verifyEmailCode = (verifyCode: string): Promise => { data: { verifyCode } }) } + +// 获取用户语言设置 +export const getUserLanguage = (): Promise => { + return request({ + url: '/buyer/profile/getLanguage', + method: 'post' + }) +} + +// 设置语言 +export const setUserLanguage = (language: string): Promise => { + return request({ + url: '/buyer/profile/setLanguage', + method: 'post', + data: { language } + }) +} \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index 288abb4..e166ef3 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,4 +1,16 @@ 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 = { + 'en': 'ENGLISH', + 'zh-CN': 'CHINESE_SIMPLIFIED' +} + +// 语言同步状态缓存(避免每次路由切换都请求) +let languageSynced = false /** * 路由缓存机制: @@ -83,6 +95,52 @@ router.beforeEach((to, from, 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 diff --git a/src/views/main-header.vue b/src/views/main-header.vue index 1eaf69f..fe65e9d 100644 --- a/src/views/main-header.vue +++ b/src/views/main-header.vue @@ -87,6 +87,8 @@ import myEvent from '@/utils/myEvent' import { useI18n } from 'vue-i18n' import { useUserInfoStore } from '@/stores/userInfo' + import { setUserLanguage } from '@/api/user' + const { t, locale } = useI18n() const userInfoStore = useUserInfoStore() const router = useRouter() @@ -153,6 +155,12 @@ const onLanguageClick = () => { locale.value = locale.value === 'ENGLISH' ? 'CHINESE_SIMPLIFIED' : 'ENGLISH' localStorage.setItem('language', locale.value) + console.log(locale.value) + const localeMap: Record = { + ENGLISH: 'en', + CHINESE_SIMPLIFIED: 'zh-CN' + } + setUserLanguage(localeMap[locale.value]) } diff --git a/src/views/setting/components/SecuritySection.vue b/src/views/setting/components/SecuritySection.vue index 6c73d08..c957f76 100644 --- a/src/views/setting/components/SecuritySection.vue +++ b/src/views/setting/components/SecuritySection.vue @@ -1,29 +1,42 @@ diff --git a/src/views/setting/index.vue b/src/views/setting/index.vue index a8a3edf..431dc9d 100644 --- a/src/views/setting/index.vue +++ b/src/views/setting/index.vue @@ -25,7 +25,11 @@ v-model:current-password="securityDraft.currentPassword" :email="displayData.email" :is-editing="isEditing" + :is-editing-email="isEditingEmail" + :is-editing-password="isEditingPassword" :is-email-verified="isEmailVerified" + @edit-email="handleEditEmail" + @edit-password="handleEditPassword" @reset-email="resetSecurityEmail" @reset-password="resetSecurityPassword" @verify-email="handleVerifyEmail" @@ -98,9 +102,13 @@ fullName, roleModel, needsEmailVerification, + isEditingEmail, + isEditingPassword, handleEdit, handleDiscard, handleSave, + handleEditEmail, + handleEditPassword, resetSecurityEmail, resetSecurityPassword, handleVerifyEmail, diff --git a/src/views/setting/useSettingsForm.ts b/src/views/setting/useSettingsForm.ts index 0b4a0bd..6ff417d 100644 --- a/src/views/setting/useSettingsForm.ts +++ b/src/views/setting/useSettingsForm.ts @@ -26,11 +26,24 @@ interface UseSettingsFormOptions { locale: Ref } +// 前端 UI 使用的语言值(用于下拉选择) const languageLocaleMap: Record = { english: 'ENGLISH', 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 createDefaultData = (): SettingsData => ({ @@ -58,8 +71,15 @@ const normalizeLanguage = (language: string | null | undefined): LanguageValue = return '' as LanguageValue } - const normalized = language.trim().toLowerCase() - return normalized.includes('chinese') ? 'chinese' : 'english' + // 后端返回 'en' 或 'zh-CN' + const trimmed = language.trim() + if (trimmed === 'en') { + return 'english' + } else if (trimmed === 'zh-CN') { + return 'chinese' + } + + return '' as LanguageValue } const buildSettingsDataFromProfile = (profile: Partial): SettingsData => ({ @@ -88,6 +108,8 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) { const verificationTargetEmail = shallowRef('') const verifiedEmail = shallowRef('') const verificationCode = shallowRef('') + const isEditingEmail = shallowRef(false) + const isEditingPassword = shallowRef(false) const roleList = computed(() => roleValues.map((value) => ({ @@ -178,6 +200,8 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) { draftData.value = cloneSettingsData(sourceData.value) securityDraft.value = createEmptySecurityDraft() resetEmailVerificationState() + isEditingEmail.value = false + isEditingPassword.value = false } const handleEdit = () => { @@ -188,11 +212,21 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) { const resetSecurityEmail = () => { securityDraft.value.newEmail = '' resetEmailVerificationState() + isEditingEmail.value = false } const resetSecurityPassword = () => { securityDraft.value.newPassword = '' securityDraft.value.currentPassword = '' + isEditingPassword.value = false + } + + const handleEditEmail = () => { + isEditingEmail.value = true + } + + const handleEditPassword = () => { + isEditingPassword.value = true } const handleDiscard = () => { @@ -294,15 +328,24 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) { 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 = { firstName: draftData.value.firstName.trim(), lastName: draftData.value.lastName.trim(), username: draftData.value.username.trim(), - email: nextEmail, + email: securityDraft.value.newEmail.trim(), roles: draftData.value.roles as string[], - language: draftData.value.language, - region: draftData.value.region + language: backendLanguage, + region: draftData.value.region, + newPassword: '', + oldPassword: '', + verifyCode: '' } // 如果改邮箱或改密码,需要添加验证码和密码信息 @@ -320,26 +363,34 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) { try { await updateUserProfile(nextData) + + // 将后端返回的语言值转换为前端格式 + const frontendLanguage = backendLanguage + ? backendToFrontendLanguage[backendLanguage as 'en' | 'zh-CN'] + : '' + const settingsData: SettingsData = { firstName: nextData.firstName, lastName: nextData.lastName, username: nextData.username, - email: nextData.email, + email: nextData.email || draftData.value.email, roles: nextData.roles as RoleValue[], - language: nextData.language as LanguageValue | '', + language: frontendLanguage, region: nextData.region as any } sourceData.value = cloneSettingsData(settingsData) console.log(nextData) - if (nextData.language && nextData.language !== previousLanguage) { - syncAppLanguage(nextData.language as LanguageValue) + if (frontendLanguage && frontendLanguage !== previousLanguage) { + syncAppLanguage(frontendLanguage as LanguageValue) } draftData.value = cloneSettingsData(sourceData.value) securityDraft.value = createEmptySecurityDraft() resetEmailVerificationState() isEditing.value = false + isEditingEmail.value = false + isEditingPassword.value = false ElMessage.success(t('Settings.messages.settingsUpdated')) } catch (error) { console.warn(error) @@ -385,11 +436,15 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) { fullName, roleModel, needsEmailVerification, + isEditingEmail, + isEditingPassword, handleEdit, handleDiscard, handleSave, resetSecurityEmail, resetSecurityPassword, + handleEditEmail, + handleEditPassword, handleVerifyEmail, handleSendVerifyCode, handleVerificationSubmit, diff --git a/src/views/wardrobe/Assets.vue b/src/views/wardrobe/Assets.vue index 0bce7d6..73c1072 100644 --- a/src/views/wardrobe/Assets.vue +++ b/src/views/wardrobe/Assets.vue @@ -32,7 +32,7 @@ class="assets-toolbar__download flex flex-center" :class="{ disabled: selectedCount < 1 }" v-loading="downloadingSelected" - @click="handleDownloadSelected" + @click="handleDownloadSelected(null)" > {{ t('Wardrobe.assets.downloadSelected') }} @@ -67,7 +67,7 @@ download :url="item.thumbnailUrl" :name="item.listingName" - @download.stop="handleDownloadSelected(item)" + @download="handleDownloadSelected(item)" :showPrice="false" > @@ -323,7 +323,7 @@ } const downloadingSelected = ref(false) - const handleDownloadSelected = (assets) => { + const handleDownloadSelected = (assets = null) => { const items = assets ? [assets] : dataList.value.filter((item) => item.checked) downloadingSelected.value = true @@ -350,7 +350,8 @@ }) .catch((error) => { console.error('Download failed:', error) - }).finally(() => { + }) + .finally(() => { downloadingSelected.value = false }) } diff --git a/src/views/wardrobe/Orders.vue b/src/views/wardrobe/Orders.vue index a0dca3d..5408ec3 100644 --- a/src/views/wardrobe/Orders.vue +++ b/src/views/wardrobe/Orders.vue @@ -97,7 +97,7 @@ import { computed, onMounted, ref, shallowRef } from 'vue' import { useI18n } from 'vue-i18n' 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 Empty from './Empty.vue' @@ -257,7 +257,26 @@ } 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) => {