Commit befc0b59 authored by 罗超's avatar 罗超

更新国际化,守卫,加载逻辑

parent 15c0667c
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin-allow-popups"> <meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin-allow-popups">
<title>C-end</title> <title></title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
{ {
"name": "C-end", "name": "boyuecend",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
......
<script setup lang="ts"> <script setup lang="ts">
import { RouterView, useRoute } from 'vue-router' import { RouterView, useRoute } from 'vue-router'
import { computed, watch, onMounted, ref } from 'vue' import { watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { ConfigProvider } from '@arco-design/web-vue' import { ConfigProvider } from '@arco-design/web-vue'
import LanguageSwitcher from './components/common/LanguageSwitcher.vue'
import { configureArcoLocale, globalArcoLocale } from './i18n/arco' import { configureArcoLocale, globalArcoLocale } from './i18n/arco'
import { useUserStore } from './stores/user'
import { useSystemConfigStore } from './stores/systemConfig' import { useSystemConfigStore } from './stores/systemConfig'
import { loadThemeFromConfig } from './utils/themeUtils' import { loadThemeFromConfig } from './utils/themeUtils'
const route = useRoute() const route = useRoute()
const { t, locale } = useI18n() const { t, locale } = useI18n()
const useUser = useUserStore()
const systemConfigStore = useSystemConfigStore() const systemConfigStore = useSystemConfigStore()
// 监听语言变化,更新 Arco Design 国际化 // 监听语言变化,更新 Arco Design 国际化
...@@ -28,7 +25,7 @@ watch( ...@@ -28,7 +25,7 @@ watch(
() => route.meta.title, () => route.meta.title,
(title) => { (title) => {
if (title && typeof title === 'string') { if (title && typeof title === 'string') {
document.title = `${t(title)} - ${t('common.systemName')}` document.title = `${t(title)} - ${systemConfigStore.config?.webSiteName || ''}`
} }
}, },
{ immediate: true } { immediate: true }
......
...@@ -22,10 +22,33 @@ const service = axios.create({ ...@@ -22,10 +22,33 @@ const service = axios.create({
// 请求拦截器:自动加 token 或临时令牌 // 请求拦截器:自动加 token 或临时令牌
service.interceptors.request.use( service.interceptors.request.use(
config => { async config => {
const userStore = useUserStore(); const userStore = useUserStore();
const token = userStore.getUserToken; const token = userStore.getUserToken;
const systemConfigStore = useSystemConfigStore(); const systemConfigStore = useSystemConfigStore();
// 如果是初始化接口本身,直接放行(避免循环等待)
const isInitRequest = config.url?.includes('/tenant-confing-by-domain')
// 等待初始化完成(如果是新用户且正在初始化)
// 对于有缓存的用户(isInitialized === true),不需要等待
if (!isInitRequest && !systemConfigStore.isInitialized) {
// 如果正在初始化,等待完成(最多等待 5 秒,避免无限等待)
if (systemConfigStore.isLoading && systemConfigStore.initPromise) {
try {
await Promise.race([
systemConfigStore.initPromise,
new Promise((_, reject) => setTimeout(() => reject(new Error('Init timeout')), 5000))
])
} catch (error) {
// 初始化失败或超时,但继续请求(可能某些接口不需要 tenantId)
console.warn('System config initialization failed or timeout, continuing request:', error)
}
}
// 如果未初始化且未开始初始化,不等待,直接继续
// tenantId 可能为空,但不会阻塞请求
}
if(systemConfigStore.groupId > 0) { if(systemConfigStore.groupId > 0) {
config.headers['x-user-group'] = systemConfigStore.groupId; config.headers['x-user-group'] = systemConfigStore.groupId;
} }
......
...@@ -97,7 +97,7 @@ const updatePageTitle = () => { ...@@ -97,7 +97,7 @@ const updatePageTitle = () => {
case '/billing': case '/billing':
titleKey = 'billing.title' titleKey = 'billing.title'
break break
case '/account': case '/personalCenter/accountCenter/account':
titleKey = 'account.title' titleKey = 'account.title'
break break
} }
......
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
type="primary" type="primary"
@click.stop="handleClick(item)" @click.stop="handleClick(item)"
> >
查看更多 {{ $t('common.more') }}
</a-button> </a-button>
</div> </div>
</transition> </transition>
......
...@@ -108,6 +108,7 @@ export default { ...@@ -108,6 +108,7 @@ export default {
chinese: '简体中文', chinese: '简体中文',
english: 'English', english: 'English',
systemName: 'C-end', systemName: 'C-end',
more:'View More'
}, },
header: { header: {
destinations: 'Destinations', destinations: 'Destinations',
......
...@@ -108,6 +108,7 @@ export default { ...@@ -108,6 +108,7 @@ export default {
chinese: '简体中文', chinese: '简体中文',
english: 'English', english: 'English',
systemName: 'C-end', systemName: 'C-end',
more:'Xem thêm'
}, },
header: { header: {
destinations: 'Điểm đến', destinations: 'Điểm đến',
......
...@@ -108,6 +108,7 @@ export default { ...@@ -108,6 +108,7 @@ export default {
chinese: '简体中文', chinese: '简体中文',
english: 'English', english: 'English',
systemName: 'C-end', systemName: 'C-end',
more:'查看更多'
}, },
header: { header: {
destinations: '目的地', destinations: '目的地',
......
...@@ -108,6 +108,7 @@ export default { ...@@ -108,6 +108,7 @@ export default {
chinese: '繁體中文', chinese: '繁體中文',
english: 'English', english: 'English',
systemName: 'C-end', systemName: 'C-end',
more:'检索更多'
}, },
header: { header: {
destinations: '目的地', destinations: '目的地',
......
...@@ -157,11 +157,11 @@ const handleGoLogin = () => { ...@@ -157,11 +157,11 @@ const handleGoLogin = () => {
} }
const handleGoProfile = () => { const handleGoProfile = () => {
goPage('/myOrder') goPage('/personalCenter/myOrder')
} }
const handleGoHome = () => { const handleGoHome = () => {
goPage('/home') goPage('/')
} }
</script> </script>
......
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router' import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
// 白名单路由(不需要登录即可访问) /**
const whiteList = ['/login', '/register', '/forgePassword', '/', '/home'] * 路由权限守卫
const whiteListRegex = [/\/oa\//] * 通过路由 meta.requiresAuth 标识是否需要 token 验证
* 如果 requiresAuth 为 true,则检查用户是否已登录
*/
export const createPermissionGuard = ( export const createPermissionGuard = (
to: RouteLocationNormalized, to: RouteLocationNormalized,
from: RouteLocationNormalized, _from: RouteLocationNormalized,
next: NavigationGuardNext, next: NavigationGuardNext,
) => { ) => {
const userStore = useUserStore() const userStore = useUserStore()
// 检查是否是白名单路由 // 检查所有匹配的路由记录(包括父路由和子路由)是否需要认证
if (whiteList.includes(to.path)) { const requiresAuth = to.matched.some(record => record.meta.requiresAuth === true)
next()
return
}
// 检查是否是白名单正则路由
if (whiteListRegex.some((regex) => regex.test(to.path))) {
next()
return
}
// 检查用户是否登录 if (requiresAuth && !userStore.getUserToken) {
// if (!userStore.getUserToken) { next({
// next('/login') path: '/login',
// return query: { redirect: to.fullPath }
// } })
return
}
next() next()
} }
...@@ -7,69 +7,69 @@ const router = createRouter({ ...@@ -7,69 +7,69 @@ const router = createRouter({
routes: [ routes: [
{ {
path: '/', path: '/',
redirect:'/home', redirect:'',
component: () => import ('../layouts/HomeLayout.vue'), component: () => import ('../layouts/HomeLayout.vue'),
children: [ children: [
{ {
path: '/home', path: '/',
name: 'home', name: 'home',
meta: { title: 'page.home' }, meta: { title: 'page.home' },
component: () => import ('../views/home/index.vue') component: () => import ('../views/home/index.vue')
}, },
{ {
path: '/personalCenter', path: '/personalCenter',
meta: { title: "page.profile" }, meta: { title: "page.profile", requiresAuth: true },
component: () => import ('../views/personalCenter/index.vue'), component: () => import ('../views/personalCenter/index.vue'),
children: [{ children: [{
path: '/myOrder',//我的订单 path: 'myOrder',//我的订单
meta: { title: "page.myOrder" }, meta: { title: "page.myOrder", requiresAuth: true },
component: () => import ('../views/personalCenter/myOrder.vue') component: () => import ('../views/personalCenter/myOrder.vue')
}, },
{ {
path: '/systemMessage',//系统消息 path: 'systemMessage',//系统消息
meta: { title: "page.systemMessage" }, meta: { title: "page.systemMessage", requiresAuth: true },
component: () => import ('../views/personalCenter/systemMessage.vue') component: () => import ('../views/personalCenter/systemMessage.vue')
}, },
{ {
path: '/myCollection',//我的收藏 path: 'myCollection',//我的收藏
meta: { title: "page.myCollection" }, meta: { title: "page.myCollection", requiresAuth: true },
component: () => import ('../views/personalCenter/myCollection.vue') component: () => import ('../views/personalCenter/myCollection.vue')
}, },
{ {
path: '/myCoupon',//我的优惠券 path: 'myCoupon',//我的优惠券
meta: { title: "page.coupon" }, meta: { title: "page.coupon", requiresAuth: true },
component: () => import ('../views/personalCenter/myCoupon.vue') component: () => import ('../views/personalCenter/myCoupon.vue')
}, },
{ {
path: '/accountCenter',//账号中心 path: 'accountCenter',//账号中心
meta: { title: "page.accountCenter" }, meta: { title: "page.accountCenter", requiresAuth: true },
component: () => import ('../views/personalCenter/accountCenter.vue'), component: () => import ('../views/personalCenter/accountCenter.vue'),
children: [ children: [
{ {
path: '/basicInfor',//基础资料 path: 'basicInfor',//基础资料
meta: { title: "page.basicInfor" }, meta: { title: "page.basicInfor", requiresAuth: true },
component: () => import ('../views/personalCenter/accountPage/basicInfor.vue') component: () => import ('../views/personalCenter/accountPage/basicInfor.vue')
}, },
{ {
path: '/account/:reType?',//账户 path: 'account/:reType?',//账户
meta: { title: "page.account" }, meta: { title: "page.account", requiresAuth: true },
component: () => import ('../views/personalCenter/accountPage/account.vue') component: () => import ('../views/personalCenter/accountPage/account.vue')
}, },
{ {
path: '/passengerList',//乘客列表 path: 'passengerList',//乘客列表
meta: { title: "page.passengerList" }, meta: { title: "page.passengerList", requiresAuth: true },
component: () => import ('../views/personalCenter/accountPage/passengerList.vue') component: () => import ('../views/personalCenter/accountPage/passengerList.vue')
}, },
{ {
path: '/mailingAddressList',//邮寄地址列表 path: 'mailingAddressList',//邮寄地址列表
meta: { title: "page.mailingAddressList" }, meta: { title: "page.mailingAddressList", requiresAuth: true },
component: () => import ('../views/personalCenter/accountPage/mailingAddressList.vue') component: () => import ('../views/personalCenter/accountPage/mailingAddressList.vue')
}, },
] ]
}, },
{ {
path: '/distributionCenter',//分销中心 path: '/distributionCenter',//分销中心
meta: { title: "page.distributionCenter" }, meta: { title: "page.distributionCenter", requiresAuth: true },
component: () => import ('../views/personalCenter/distributionCenter.vue') component: () => import ('../views/personalCenter/distributionCenter.vue')
}, },
] ]
......
...@@ -7,6 +7,22 @@ import { ResultMessage } from '@/utils/message' ...@@ -7,6 +7,22 @@ import { ResultMessage } from '@/utils/message'
import router from '@/router' import router from '@/router'
import ErpUserService from '@/services/ErpUserService' import ErpUserService from '@/services/ErpUserService'
import { ApiResult } from '@/types/ApiResult' import { ApiResult } from '@/types/ApiResult'
import SecureLS from 'secure-ls'
import type { StorageLike } from 'pinia-plugin-persistedstate'
const ls = new SecureLS({
isCompression: false,
encryptionSecret: '38c31684-d00d-30dc-82e0-fad9eec46d1c',
})
const st: StorageLike = {
setItem(key: string, value: string) {
ls.set(key, value)
},
getItem(key: string): string | null {
return ls.get(key)
},
}
/** /**
* 系统配置 Store * 系统配置 Store
...@@ -36,6 +52,9 @@ export const useSystemConfigStore = defineStore('systemConfig', { ...@@ -36,6 +52,9 @@ export const useSystemConfigStore = defineStore('systemConfig', {
groupId:0 as number, groupId:0 as number,
erpAdminUserId:0 as number, erpAdminUserId:0 as number,
// 初始化 Promise(用于等待初始化完成)
initPromise: null as Promise<void> | null,
}), }),
getters: { getters: {
/** /**
...@@ -167,33 +186,50 @@ export const useSystemConfigStore = defineStore('systemConfig', { ...@@ -167,33 +186,50 @@ export const useSystemConfigStore = defineStore('systemConfig', {
} }
}, },
/**
* 等待初始化完成
* 如果已经初始化,立即返回;如果正在初始化,等待完成;如果未初始化,返回 resolved Promise
*/
async waitForInitialization(): Promise<void> {
// 如果已经初始化,立即返回
if (this.isInitialized) {
return
}
// 如果正在初始化,等待现有的 Promise
if (this.initPromise) {
return this.initPromise
}
// 如果未初始化且未开始初始化,返回 resolved Promise(调用者需要自己初始化)
return Promise.resolve()
},
/** /**
* 初始化系统配置 * 初始化系统配置
* 根据域名获取租户信息和平台配置 * 根据域名获取租户信息和平台配置
*/ */
async initialize(domainName?: string) { async initialize(domainName?: string): Promise<void> {
// 如果已经初始化过,直接返回 // 如果已经初始化过且没有指定新域名,直接返回
if (this.isInitialized && !domainName) { if (this.tenantId && this.tenantId.length > 0 && domainName) {
await this.queryTenantInfoAsyncAsync(domainName)
return return
} }
// 如果正在初始化,等待现有的 Promise
if (this.initPromise && this.isLoading) {
return this.initPromise
}
// 创建新的初始化 Promise
this.initPromise = (async () => {
this.isLoading = true this.isLoading = true
this.loadError = null this.loadError = null
try { try {
const data = await SystemConfigService.getTenantInfoByDomainAsync(domainName)
// 保存租户信息 await this.queryTenantInfoAsyncAsync(domainName)
this.tenantId = data.tenantId || null
this.navs = data.navList || []
this.domainName = data.config?.domainName || null
this.distributorId = data.distributorId || 0
// 保存平台配置
this.platformConfig = data.platform || null
this.config = data.config
this.groupId = data.erpGroupId
// 如果有租户ID,加载代理商配置 // 如果有租户ID,加载代理商配置
if (this.tenantId) { if (this.tenantId) {
await this.getGroupInfoAsync(this.groupId) await this.getGroupInfoAsync(this.groupId)
...@@ -225,16 +261,37 @@ export const useSystemConfigStore = defineStore('systemConfig', { ...@@ -225,16 +261,37 @@ export const useSystemConfigStore = defineStore('systemConfig', {
this.updateFavicon(this.config.favicon) this.updateFavicon(this.config.favicon)
} }
this.isInitialized = true
} catch (error) { } catch (error) {
this.loadError = error as Error this.loadError = error as Error
ResultMessage.Error('Failed to initialize system config:' + error) ResultMessage.Error('Failed to initialize system config:' + error)
router.push('/login') router.push('/login')
console.error('Failed to initialize system config:', error) console.error('Failed to initialize system config:', error)
//throw error throw error
} finally { } finally {
this.isLoading = false this.isLoading = false
this.isInitialized = true
} }
})()
return this.initPromise
},
async queryTenantInfoAsyncAsync(domainName?: string) {
console.log('enter in/queryTenantInfoAsyncAsync')
const data = await SystemConfigService.getTenantInfoByDomainAsync(domainName)
// 保存租户信息
this.tenantId = data.tenantId || null
this.navs = data.navList || []
this.domainName = data.config?.domainName || null
this.distributorId = data.distributorId || 0
// 保存平台配置
this.platformConfig = data.platform || null
this.config = data.config
this.groupId = data.erpGroupId
}, },
/** /**
...@@ -243,9 +300,81 @@ export const useSystemConfigStore = defineStore('systemConfig', { ...@@ -243,9 +300,81 @@ export const useSystemConfigStore = defineStore('systemConfig', {
*/ */
async refresh(domainName?: string) { async refresh(domainName?: string) {
this.isInitialized = false this.isInitialized = false
this.initPromise = null
await this.initialize(domainName) await this.initialize(domainName)
}, },
/**
* 部分更新配置字段
* 只更新指定的字段,不重新加载全部数据
*/
updateConfig(updates: {
tenantId?: string | null
domainName?: string | null
distributorId?: number | null
navs?: NavItemDto[] | null
platformConfig?: Partial<PlatformConfigDto> | null
partnerCenterConfig?: Partial<PartnerCenterConfigDto> | null
config?: any | null
groupId?: number
erpAdminUserId?: number
}) {
if (updates.tenantId !== undefined) {
this.tenantId = updates.tenantId
}
if (updates.domainName !== undefined) {
this.domainName = updates.domainName
}
if (updates.distributorId !== undefined) {
this.distributorId = updates.distributorId
}
if (updates.navs !== undefined) {
this.navs = updates.navs
}
if (updates.platformConfig !== undefined) {
if (updates.platformConfig === null) {
this.platformConfig = null
} else if (this.platformConfig) {
this.platformConfig = {
...this.platformConfig,
...updates.platformConfig,
}
} else {
this.platformConfig = updates.platformConfig as PlatformConfigDto
}
}
if (updates.partnerCenterConfig !== undefined) {
if (updates.partnerCenterConfig === null) {
this.partnerCenterConfig = null
} else if (this.partnerCenterConfig) {
this.partnerCenterConfig = {
...this.partnerCenterConfig,
...updates.partnerCenterConfig,
}
} else {
this.partnerCenterConfig = updates.partnerCenterConfig as PartnerCenterConfigDto
}
}
if (updates.config !== undefined) {
if (updates.config === null) {
this.config = null
} else if (this.config) {
this.config = {
...this.config,
...updates.config,
}
} else {
this.config = updates.config
}
}
if (updates.groupId !== undefined) {
this.groupId = updates.groupId
}
if (updates.erpAdminUserId !== undefined) {
this.erpAdminUserId = updates.erpAdminUserId
}
},
/** /**
* 更新平台配置(本地状态) * 更新平台配置(本地状态)
*/ */
...@@ -287,6 +416,10 @@ export const useSystemConfigStore = defineStore('systemConfig', { ...@@ -287,6 +416,10 @@ export const useSystemConfigStore = defineStore('systemConfig', {
this.isLoading = false this.isLoading = false
this.isInitialized = false this.isInitialized = false
this.loadError = null this.loadError = null
this.initPromise = null
},
},
persist: {
storage: st,
}, },
}
}) })
\ No newline at end of file
...@@ -11,6 +11,13 @@ declare module 'vue-router' { ...@@ -11,6 +11,13 @@ declare module 'vue-router' {
*/ */
title?: string title?: string
/**
* 是否需要 token 验证
* 如果为 true,访问该路由时需要检查用户是否已登录
* 如果未登录,将重定向到登录页
*/
requiresAuth?: boolean
/** /**
* 筛选器参数 * 筛选器参数
* 用于存储页面级的筛选配置 * 用于存储页面级的筛选配置
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
import CryptoJS from 'crypto-js' import CryptoJS from 'crypto-js'
import type { ComponentNode } from '@/types/pageBuilder' import type { ComponentNode } from '@/types/pageBuilder'
import { isTranslatableField } from './i18nFieldMap' import { isTranslatableField } from './i18nFieldMap'
import type { TextToTranslateDto, TranslationItemDto } from '@/types/page-builder/pageTranslation' import type { TextToTranslateDto, TranslationItemDto } from '@/types/translation/pageTranslation'
/** /**
* 计算文本的 MD5 哈希值 * 计算文本的 MD5 哈希值
...@@ -38,13 +38,26 @@ function extractTextsRecursive( ...@@ -38,13 +38,26 @@ function extractTextsRecursive(
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
obj.forEach((item, index) => { obj.forEach((item, index) => {
const itemPath = `${fieldPath}[${index}]`
// 如果数组元素是字符串,且路径可翻译,直接提取
if (typeof item === 'string' && item.trim() && isTranslatableField(componentType, itemPath)) {
const hash = calculateTextHash(item)
texts.push({
hash,
text: item,
fieldPath: itemPath,
sourceLanguage,
})
} else {
// 否则递归处理
const result = extractTextsRecursive( const result = extractTextsRecursive(
item, item,
componentType, componentType,
`${fieldPath}[${index}]`, itemPath,
sourceLanguage sourceLanguage
) )
texts.push(...result) texts.push(...result)
}
}) })
return texts return texts
} }
...@@ -142,9 +155,17 @@ export function collectTextHashes(components: ComponentNode[]): string[] { ...@@ -142,9 +155,17 @@ export function collectTextHashes(components: ComponentNode[]): string[] {
if (obj === null || obj === undefined) return if (obj === null || obj === undefined) return
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
obj.forEach((item, index) => obj.forEach((item, index) => {
collectFromObj(item, componentType, `${fieldPath}[${index}]`) const itemPath = `${fieldPath}[${index}]`
) // 如果数组元素是字符串,且路径可翻译,直接收集哈希
if (typeof item === 'string' && item.trim() && isTranslatableField(componentType, itemPath)) {
const hash = calculateTextHash(item)
hashes.add(hash)
} else {
// 否则递归处理
collectFromObj(item, componentType, itemPath)
}
})
return return
} }
...@@ -203,8 +224,17 @@ export function collectTranslationsByHash( ...@@ -203,8 +224,17 @@ export function collectTranslationsByHash(
// 数组:按索引对齐 // 数组:按索引对齐
if (Array.isArray(sourceNode)) { if (Array.isArray(sourceNode)) {
sourceNode.forEach((item, index) => { sourceNode.forEach((item, index) => {
const itemPath = `${fieldPath}[${index}]`
const tgt = Array.isArray(targetNode) ? targetNode[index] : undefined const tgt = Array.isArray(targetNode) ? targetNode[index] : undefined
traversePair(item, tgt, componentType, `${fieldPath}[${index}]`) // 如果数组元素是字符串,且路径可翻译,直接收集
if (typeof item === 'string' && item.trim() && isTranslatableField(componentType, itemPath)) {
const hash = calculateTextHash(item)
const text = typeof tgt === 'string' ? tgt : item
result.push({ hash, text, fieldPath: itemPath })
} else {
// 否则递归处理
traversePair(item, tgt, componentType, itemPath)
}
}) })
return return
} }
...@@ -259,7 +289,18 @@ export function applyTranslations( ...@@ -259,7 +289,18 @@ export function applyTranslations(
} }
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
return obj.map(item => replaceTextRecursive(item)) return obj.map(item => {
// 如果数组元素是字符串,检查是否需要替换
if (typeof item === 'string' && item.trim()) {
const hash = calculateTextHash(item)
const translationItem = translations[hash]
if (translationItem && translationItem.text) {
return translationItem.text
}
}
// 否则递归处理
return replaceTextRecursive(item)
})
} }
if (typeof obj === 'object') { if (typeof obj === 'object') {
......
...@@ -117,14 +117,11 @@ ...@@ -117,14 +117,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onMounted } from "vue"; import { ref, reactive, computed, onMounted } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useRouter } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/index' import { useUserStore } from '@/stores/index'
import { useSystemConfigStore } from '@/stores/index' import { useSystemConfigStore } from '@/stores/index'
import ErpUserService from '@/services/ErpUserService'
import { ApiResult } from '@/types/ApiResult'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import loginHeader from "./components/header.vue"; import loginHeader from "./components/header.vue";
import f from '@/assets/images/login/login_f.png'
import G from '@/assets/images/login/login_G.png' import G from '@/assets/images/login/login_G.png'
import tel from '@/assets/images/login/login_tel.png' import tel from '@/assets/images/login/login_tel.png'
import line from '@/assets/images/login/login_line.png' import line from '@/assets/images/login/login_line.png'
...@@ -139,9 +136,25 @@ const systemConfigStore = useSystemConfigStore() ...@@ -139,9 +136,25 @@ const systemConfigStore = useSystemConfigStore()
const loading = ref(true) const loading = ref(true)
const router = useRouter() const router = useRouter()
const route = useRoute()
const { params } = router.currentRoute.value const { params } = router.currentRoute.value
const googleButtonContainer = ref(null); const googleButtonContainer = ref(null);
// 获取登录后的重定向路径
// 优先级:1. 路由 query 参数中的 redirect 2. localStorage 中的 forward 3. 默认首页
const getRedirectPath = (): string => {
const queryRedirect = route.query.redirect as string | undefined
if (queryRedirect) {
return queryRedirect
}
const forward = localStorage.getItem('forward')
if (forward) {
localStorage.removeItem('forward')
return forward
}
return '/'
}
const loginMsg = reactive({ const loginMsg = reactive({
tenantId: systemConfigStore.tenantId || null, tenantId: systemConfigStore.tenantId || null,
...@@ -225,10 +238,8 @@ const useLineLogin = async(code:string) => { ...@@ -225,10 +238,8 @@ const useLineLogin = async(code:string) => {
if (response.status == 'SUCCESS') { if (response.status == 'SUCCESS') {
userStore.setLoginType(loginMsg.reType || 0) userStore.setLoginType(loginMsg.reType || 0)
Message.success(t('login.loginSuccess')) Message.success(t('login.loginSuccess'))
const forward = localStorage.getItem('forward')
localStorage.removeItem('forward')
router.push({ router.push({
path: forward ? forward : '/', path: getRedirectPath(),
}) })
}else if(response.status == 'ERROR'){ }else if(response.status == 'ERROR'){
router.push('/login') router.push('/login')
...@@ -254,10 +265,8 @@ const useWechatLogin = async(code:string) => { ...@@ -254,10 +265,8 @@ const useWechatLogin = async(code:string) => {
if (response.status == 'SUCCESS') { if (response.status == 'SUCCESS') {
userStore.setLoginType(loginMsg.reType || 0) userStore.setLoginType(loginMsg.reType || 0)
Message.success(t('login.loginSuccess')) Message.success(t('login.loginSuccess'))
const forward = localStorage.getItem('forward')
localStorage.removeItem('forward')
router.push({ router.push({
path: forward ? forward : '/', path: getRedirectPath(),
}) })
}else if(response.status == 'ERROR'){ }else if(response.status == 'ERROR'){
router.push('/login') router.push('/login')
...@@ -360,10 +369,8 @@ const handleSignInSuccess = async (googleUser:any) => { ...@@ -360,10 +369,8 @@ const handleSignInSuccess = async (googleUser:any) => {
if (response.status == 'SUCCESS') { if (response.status == 'SUCCESS') {
userStore.setLoginType(loginMsg.reType || 0) userStore.setLoginType(loginMsg.reType || 0)
Message.success(t('login.loginSuccess')) Message.success(t('login.loginSuccess'))
const forward = localStorage.getItem('forward')
localStorage.removeItem('forward')
router.push({ router.push({
path: forward ? forward : '/', path: getRedirectPath(),
}) })
}else if(response.status == 'ERROR'){ }else if(response.status == 'ERROR'){
router.push('/login') router.push('/login')
...@@ -409,10 +416,8 @@ const handleLogin = async () => { ...@@ -409,10 +416,8 @@ const handleLogin = async () => {
if (result.status == 'SUCCESS') { if (result.status == 'SUCCESS') {
userStore.setLoginType(loginMsg.reType || 0) userStore.setLoginType(loginMsg.reType || 0)
Message.success(t('login.loginSuccess')) Message.success(t('login.loginSuccess'))
const forward = localStorage.getItem('forward')
localStorage.removeItem('forward')
router.push({ router.push({
path: forward ? forward : '/', path: getRedirectPath(),
}) })
} }
} catch (error: any) { } catch (error: any) {
......
<template> <template>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex justify-center items-center cursor-pointer" <div class="flex justify-center items-center cursor-pointer"
@click="goHome('/home')"> @click="goHome('/')">
<img v-if="systemConfigStore.config?.logo" <img v-if="systemConfigStore.config?.logo"
:src="systemConfigStore.config.logo" :src="systemConfigStore.config.logo"
:alt="systemConfigStore.config.webSiteName" :alt="systemConfigStore.config.webSiteName"
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
</svg> </svg>
</div> </div>
<div class="flex items-center" v-if="!currentStep||currentStep<3"> <div class="flex items-center" v-if="!currentStep||currentStep<3">
<div class="flex items-center mr-[67px] cursor-pointer" @click="goHome('/home')"> <div class="flex items-center mr-[67px] cursor-pointer" @click="goHome('/')">
<img <img
src="../../../assets/images/login/login-home.png" src="../../../assets/images/login/login-home.png"
class="h-[20px]" class="h-[20px]"
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
:loading="loading" :loading="loading"
html-type="submit" html-type="submit"
class="w-[222px] flex-1 font-medium !h-[46px] !rounded-[13px] !text-[#FFFFFF] !text-[16px]" class="w-[222px] flex-1 font-medium !h-[46px] !rounded-[13px] !text-[#FFFFFF] !text-[16px]"
@click="goHome('/home')" @click="goHome('/')"
> >
{{ t('login.backToHome') }} {{ t('login.backToHome') }}
</a-button> </a-button>
......
...@@ -234,7 +234,7 @@ const goPage = (path:string) => { ...@@ -234,7 +234,7 @@ const goPage = (path:string) => {
router.push(path) router.push(path)
return return
} }
router.push('/basicInfor') router.push('/personalCenter/accountCenter/basicInfor')
} }
// 验证邮箱是否可用 // 验证邮箱是否可用
...@@ -370,7 +370,7 @@ const handleSubmit = async () => { ...@@ -370,7 +370,7 @@ const handleSubmit = async () => {
Message.success(t('login.resetSuccess')) Message.success(t('login.resetSuccess'))
// 延迟跳转到登录页 // 延迟跳转到登录页
if(params&&params.email){ if(params&&params.email){
router.push('/basicInfor') router.push('/personalCenter/accountCenter/basicInfor')
}else{ }else{
router.push('/login') router.push('/login')
} }
......
...@@ -15,11 +15,19 @@ ...@@ -15,11 +15,19 @@
const pageData = ref<any>(null) const pageData = ref<any>(null)
const translatedPageData = ref<any>(null) const translatedPageData = ref<any>(null)
const { locale } = useI18n() const { locale, t } = useI18n()
// 按目标语言缓存翻译结果,减少重复请求 // 按目标语言缓存翻译结果,减少重复请求
const translationsCache = new Map<string, Record<string, any>>() const translationsCache = new Map<string, Record<string, any>>()
// 更新页面标题
function updatePageTitle(pageDataValue: any) {
if (pageDataValue?.metadata?.pageTitle) {
const pageTitle = pageDataValue.metadata.pageTitle
document.title = `${pageTitle}`
}
}
async function loadPageData() { async function loadPageData() {
try { try {
const response = await WebSitePageService.getHomePageAsync({ const response = await WebSitePageService.getHomePageAsync({
...@@ -31,6 +39,8 @@ ...@@ -31,6 +39,8 @@
if (response.pageDataList && response.pageDataList.length > 0) { if (response.pageDataList && response.pageDataList.length > 0) {
pageData.value = JSON.parse(response.pageDataList[0].plugData as string) pageData.value = JSON.parse(response.pageDataList[0].plugData as string)
await applyI18nTranslations() await applyI18nTranslations()
// 更新页面标题
updatePageTitle(translatedPageData.value || pageData.value)
} }
} catch (error) { } catch (error) {
console.error('加载首页数据失败:', error) console.error('加载首页数据失败:', error)
...@@ -40,6 +50,7 @@ ...@@ -40,6 +50,7 @@
async function applyI18nTranslations() { async function applyI18nTranslations() {
if (!pageData.value?.components) { if (!pageData.value?.components) {
translatedPageData.value = pageData.value translatedPageData.value = pageData.value
updatePageTitle(pageData.value)
return return
} }
const sourceLang = pageData.value.metadata?.sourceLanguage || 'zh-CN' const sourceLang = pageData.value.metadata?.sourceLanguage || 'zh-CN'
...@@ -48,6 +59,7 @@ ...@@ -48,6 +59,7 @@
// 同源语言直接用原数据 // 同源语言直接用原数据
if (targetLang === sourceLang) { if (targetLang === sourceLang) {
translatedPageData.value = pageData.value translatedPageData.value = pageData.value
updatePageTitle(pageData.value)
return return
} }
...@@ -58,12 +70,14 @@ ...@@ -58,12 +70,14 @@
...pageData.value, ...pageData.value,
components: applyTranslations(pageData.value.components, cached), components: applyTranslations(pageData.value.components, cached),
} }
updatePageTitle(translatedPageData.value)
return return
} }
const hashes = collectTextHashes(pageData.value.components) const hashes = collectTextHashes(pageData.value.components)
if (!hashes.length) { if (!hashes.length) {
translatedPageData.value = pageData.value translatedPageData.value = pageData.value
updatePageTitle(pageData.value)
return return
} }
...@@ -79,9 +93,11 @@ ...@@ -79,9 +93,11 @@
...pageData.value, ...pageData.value,
components: applyTranslations(pageData.value.components, translations), components: applyTranslations(pageData.value.components, translations),
} }
updatePageTitle(translatedPageData.value)
} catch (err) { } catch (err) {
console.error('加载翻译失败:', err) console.error('加载翻译失败:', err)
translatedPageData.value = pageData.value translatedPageData.value = pageData.value
updatePageTitle(pageData.value)
} }
} }
...@@ -99,6 +115,17 @@ ...@@ -99,6 +115,17 @@
} }
} }
) )
// 监听翻译后的页面数据变化,更新页面标题
watch(
() => translatedPageData.value,
(newData) => {
if (newData) {
updatePageTitle(newData)
}
},
{ immediate: true }
)
</script> </script>
<style scoped> <style scoped>
......
...@@ -22,10 +22,6 @@ ...@@ -22,10 +22,6 @@
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter,useRoute } from 'vue-router' import { useRouter,useRoute } from 'vue-router'
import account from "./components/accountCenter/account.vue"
import basicInfor from "./components/accountCenter/basicInfor.vue"
import passengerList from "./components/accountCenter/passengerList.vue"
import mailingAddressList from "./components/accountCenter/mailingAddressList.vue"
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter() const router = useRouter()
...@@ -38,25 +34,25 @@ const TitleBars = [ ...@@ -38,25 +34,25 @@ const TitleBars = [
{ {
label: t('personal.basicInfo'), label: t('personal.basicInfo'),
value: 1, value: 1,
path: '/basicInfor', path: '/personalCenter/accountCenter/basicInfor',
key: 'basicInfor' key: 'basicInfor'
}, },
{ {
label: t('personal.accountInfor'), label: t('personal.accountInfor'),
value: 2, value: 2,
path: '/account', path: '/personalCenter/accountCenter/account',
key: 'account' key: 'account'
}, },
{ {
label: t('personal.commonPassenger'), label: t('personal.commonPassenger'),
value: 3, value: 3,
path: '/passengerList', path: '/personalCenter/accountCenter/passengerList',
key: 'passengerList' key: 'passengerList'
}, },
{ {
label: t('personal.postAddress'), label: t('personal.postAddress'),
value: 4, value: 4,
path: '/mailingAddressList', path: '/personalCenter/accountCenter/mailingAddressList',
key: 'mailingAddressList' key: 'mailingAddressList'
} }
] ]
......
...@@ -240,7 +240,7 @@ const loginWithLine = () => { ...@@ -240,7 +240,7 @@ const loginWithLine = () => {
// https://www.oytour.com/#/login/2/3 // https://www.oytour.com/#/login/2/3
console.log(openInfo.value,'----------') console.log(openInfo.value,'----------')
// return // return
const redirectUri = encodeURIComponent(openInfo.value.redirectUri || 'http://localhost:8002/account/3'); // 替换为你的重定向 URI const redirectUri = encodeURIComponent(openInfo.value.redirectUri || 'http://localhost:8002/personalCenter/accountCenter/account/3'); // 替换为你的重定向 URI
const state = generateState(); // 防止 CSRF 攻击,生成随机的 state 参数 const state = generateState(); // 防止 CSRF 攻击,生成随机的 state 参数
// 构造 LINE 授权 URL // 构造 LINE 授权 URL
const lineLoginUrl = `https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id=${channelId}&redirect_uri=${redirectUri}&state=${state}&scope=openid%20profile`; const lineLoginUrl = `https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id=${channelId}&redirect_uri=${redirectUri}&state=${state}&scope=openid%20profile`;
...@@ -257,7 +257,7 @@ const useLineBind = async(code:string) => { ...@@ -257,7 +257,7 @@ const useLineBind = async(code:string) => {
Message.success(t('personal.bindWechatSuccess')) Message.success(t('personal.bindWechatSuccess'))
getPersonalInfor() getPersonalInfor()
}else if(response.status == 'ERROR'){ }else if(response.status == 'ERROR'){
router.push('/account') router.push('/personalCenter/accountCenter/account')
} }
}catch (error: any) { }catch (error: any) {
Message.error(error.message) Message.error(error.message)
...@@ -267,7 +267,7 @@ const useLineBind = async(code:string) => { ...@@ -267,7 +267,7 @@ const useLineBind = async(code:string) => {
} }
// 微信授权 // 微信授权
const loginWechat = () => { const loginWechat = () => {
const redirect_url = openInfo.value.redirectUri || 'https://www.oytour.com/#/login/2' ||'http://localhost:8002/account/2' const redirect_url = openInfo.value.redirectUri || 'https://www.oytour.com/#/login/2' ||'http://localhost:8002/personalCenter/accountCenter/account/2'
const url = `https://open.weixin.qq.com/connect/qrconnect?appid=${openInfo.value.appId}&redirect_uri=${encodeURIComponent(redirect_url)}&response_type=code&scope=snsapi_login&state=${1}&wechat_redirect=${redirect_url}`; const url = `https://open.weixin.qq.com/connect/qrconnect?appid=${openInfo.value.appId}&redirect_uri=${encodeURIComponent(redirect_url)}&response_type=code&scope=snsapi_login&state=${1}&wechat_redirect=${redirect_url}`;
window.location.href = url; window.location.href = url;
} }
...@@ -280,7 +280,7 @@ const useWechatBind = async(code:string) => { ...@@ -280,7 +280,7 @@ const useWechatBind = async(code:string) => {
Message.success(t('personal.bindWechatSuccess')) Message.success(t('personal.bindWechatSuccess'))
getPersonalInfor() getPersonalInfor()
}else if(response.status == 'ERROR'){ }else if(response.status == 'ERROR'){
router.push('/account') router.push('/personalCenter/accountCenter/account')
} }
}catch (error: any) { }catch (error: any) {
Message.error(error.message) Message.error(error.message)
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<a-form-item field="" label="" class=""> <a-form-item field="" label="" class="">
<div class="flex justify-center relative"> <div class="flex justify-center relative">
<div class="flex justify-center items-center absolute left-[-300px] top-[5px]" <div class="flex justify-center items-center absolute left-[-300px] top-[5px]"
@click="goPage('/account')"> @click="goPage('/personalCenter/accountCenter/account')">
<div class="flex items-center justify-center cursor-pointer"> <div class="flex items-center justify-center cursor-pointer">
<icon-left class="font-semibold" size="20" strokeLinejoin="miter" /> <icon-left class="font-semibold" size="20" strokeLinejoin="miter" />
<span class="text-base ml-[9px]">{{ t('personal.return') }}</span> <span class="text-base ml-[9px]">{{ t('personal.return') }}</span>
...@@ -438,7 +438,7 @@ const handleSubmit = async () => { ...@@ -438,7 +438,7 @@ const handleSubmit = async () => {
if(params&&params.forward){ if(params&&params.forward){
goPage(`/${params.forward}`) goPage(`/${params.forward}`)
}else{ }else{
goPage('/account') goPage('/personalCenter/accountCenter/account')
} }
} }
} catch (error: any) { } catch (error: any) {
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<a-form-item field="" label="" class=""> <a-form-item field="" label="" class="">
<div class="flex justify-center relative"> <div class="flex justify-center relative">
<div class="flex justify-center items-center absolute left-[-300px] top-[5px]" <div class="flex justify-center items-center absolute left-[-300px] top-[5px]"
@click="currentStep==1?goPage('/account'):handlePrevious()"> @click="currentStep==1?goPage('/personalCenter/accountCenter/account'):handlePrevious()">
<div class="flex items-center justify-center cursor-pointer"> <div class="flex items-center justify-center cursor-pointer">
<icon-left class="font-semibold" size="20" strokeLinejoin="miter" /> <icon-left class="font-semibold" size="20" strokeLinejoin="miter" />
<span class="text-base ml-[9px]">{{ currentStep==1?t('personal.return'):t('personal.returnStep') }}</span> <span class="text-base ml-[9px]">{{ currentStep==1?t('personal.return'):t('personal.returnStep') }}</span>
...@@ -255,7 +255,7 @@ const goPage = (path:string) => { ...@@ -255,7 +255,7 @@ const goPage = (path:string) => {
router.push(path) router.push(path)
return return
} }
router.push('/account') router.push('/personalCenter/accountCenter/account')
} }
// 验证邮箱是否可用 // 验证邮箱是否可用
...@@ -391,7 +391,7 @@ const handleSubmit = async () => { ...@@ -391,7 +391,7 @@ const handleSubmit = async () => {
Message.success(t('login.resetSuccess')) Message.success(t('login.resetSuccess'))
// currentStep.value ++ // currentStep.value ++
// 延迟跳转 // 延迟跳转
router.push('/account') router.push('/personalCenter/accountCenter/account')
} }
} catch (error: any) { } catch (error: any) {
Message.error(error.message || t('login.resetFailed')) Message.error(error.message || t('login.resetFailed'))
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<a-form-item field="" label="" class=""> <a-form-item field="" label="" class="">
<div class="flex justify-center relative"> <div class="flex justify-center relative">
<div class="flex justify-center items-center absolute left-[-300px] top-[5px]" <div class="flex justify-center items-center absolute left-[-300px] top-[5px]"
@click="goPage('/account')"> @click="goPage('/personalCenter/accountCenter/account')">
<div class="flex items-center justify-center cursor-pointer"> <div class="flex items-center justify-center cursor-pointer">
<icon-left class="font-semibold" size="20" strokeLinejoin="miter" /> <icon-left class="font-semibold" size="20" strokeLinejoin="miter" />
<span class="text-base ml-[9px]">{{ t('personal.return') }}</span> <span class="text-base ml-[9px]">{{ t('personal.return') }}</span>
...@@ -371,7 +371,7 @@ const handleSubmit = async () => { ...@@ -371,7 +371,7 @@ const handleSubmit = async () => {
if(response){ if(response){
Message.success(t('login.resetSuccess')) Message.success(t('login.resetSuccess'))
// 延迟跳转到登录页 // 延迟跳转到登录页
goPage('/account') goPage('/personalCenter/accountCenter/account')
} }
} catch (error: any) { } catch (error: any) {
Message.error(error.message || t('login.resetFailed')) Message.error(error.message || t('login.resetFailed'))
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
</div> </div>
<div class="mt-[13px] text-lg font-medium text-center truncate">{{ userInfo?.name || '' }}</div> <div class="mt-[13px] text-lg font-medium text-center truncate">{{ userInfo?.name || '' }}</div>
<div class="flex justify-center items-center mt-[10px] cursor-pointer" <div class="flex justify-center items-center mt-[10px] cursor-pointer"
@click="goPage('/basicInfor')"> @click="goPage('/personalCenter/accountCenter/basicInfor')">
<span v-if="!userInfo?.IsComplete" class="LeftViewTisp w-[6px] h-[6px] rounded-full"></span> <span v-if="!userInfo?.IsComplete" class="LeftViewTisp w-[6px] h-[6px] rounded-full"></span>
<span class="LeftViewData ml-[5px] text-sm font-medium text-[#666]"> <span class="LeftViewData ml-[5px] text-sm font-medium text-[#666]">
{{ t('personal.completeProfile') }} {{ t('personal.completeProfile') }}
...@@ -109,7 +109,7 @@ const props = defineProps({ ...@@ -109,7 +109,7 @@ const props = defineProps({
}, },
activeMenu: { activeMenu: {
type: String, type: String,
default: '/myOrder', default: '/personalCenter/myOrder',
}, },
}) })
......
...@@ -28,7 +28,7 @@ const route = useRoute() ...@@ -28,7 +28,7 @@ const route = useRoute()
const activeMenu = computed(() => { const activeMenu = computed(() => {
const path = route.path const path = route.path
const menu = menuList.value.find(item => path.startsWith(item.path)) const menu = menuList.value.find(item => path.startsWith(item.path))
if(path=='/account'||path=='/passengerList'||path=='/mailingAddressList'){ if(path=='/personalCenter/accountCenter/account'||path=='/personalCenter/accountCenter/passengerList'||path=='/personalCenter/accountCenter/mailingAddressList'){
return 'basicInfor' return 'basicInfor'
} }
return menu?.key || 'myOrder' return menu?.key || 'myOrder'
...@@ -38,28 +38,28 @@ const activeMenu = computed(() => { ...@@ -38,28 +38,28 @@ const activeMenu = computed(() => {
const menuList = ref([ const menuList = ref([
{ {
name: t('personal.menu.myOrder'), name: t('personal.menu.myOrder'),
path: '/myOrder', path: '/personalCenter/myOrder',
key: 'myOrder', key: 'myOrder',
}, },
{ {
name: t('personal.menu.systemMessage'), name: t('personal.menu.systemMessage'),
path: '/systemMessage', path: '/personalCenter/systemMessage',
key: 'systemMessage', key: 'systemMessage',
}, },
{ {
name: t('personal.menu.myCollection'), name: t('personal.menu.myCollection'),
path: '/myCollection', path: '/personalCenter/myCollection',
key: 'myCollection', key: 'myCollection',
}, },
{ {
name: t('personal.menu.coupon'), name: t('personal.menu.coupon'),
path: '/myCoupon', path: '/personalCenter/myCoupon',
key: 'myCoupon', key: 'myCoupon',
count: 2, count: 2,
}, },
{ {
name: t('personal.menu.accountCenter'), name: t('personal.menu.accountCenter'),
path: '/basicInfor', path: '/personalCenter/accountCenter/basicInfor',
key: 'basicInfor', key: 'basicInfor',
}, },
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment