refactor: remove i18n dependency and hardcode strings in components
- Updated ChatInput.vue to remove i18n and handle model value updates directly. - Refactored NavBar.vue to use a static title map instead of i18n. - Simplified TabBar.vue by replacing i18n calls with hardcoded titles. - Removed i18n usage in various pages (charts, counter, forgot-password, index, keepalive, llm-chat, login, mock, profile, register, scroll-cache, settings, unocss). - Deleted localization files (en-US.json, zh-CN.json) and i18n utility functions. - Updated constants to provide static app name and description. - Adjusted page titles in set-page-title.ts to use static names. - Cleaned up TypeScript types by removing unused i18n types.
This commit is contained in:
parent
3f8689f7ad
commit
118d10208b
|
|
@ -14,7 +14,6 @@ import { mockDevServerPlugin } from 'vite-plugin-mock-dev-server'
|
|||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
import Sitemap from 'vite-plugin-sitemap'
|
||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
|
||||
import { loadEnv } from 'vite'
|
||||
import { createViteVConsole } from './vconsole'
|
||||
|
||||
|
|
@ -60,8 +59,6 @@ export function createVitePlugins(mode: string) {
|
|||
VueRouterAutoImports,
|
||||
{
|
||||
'vue-router/auto': ['useLink'],
|
||||
'@/utils/i18n': ['i18n', 'locale'],
|
||||
'vue-i18n': ['useI18n'],
|
||||
},
|
||||
unheadVueComposablesImports,
|
||||
],
|
||||
|
|
@ -72,12 +69,6 @@ export function createVitePlugins(mode: string) {
|
|||
resolvers: [VantResolver()],
|
||||
}),
|
||||
|
||||
// https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n
|
||||
VueI18nPlugin({
|
||||
// locale messages resource pre-compile option
|
||||
include: resolve(dirname(fileURLToPath(import.meta.url)), '../../src/locales/**'),
|
||||
}),
|
||||
|
||||
legacy({
|
||||
targets: ['defaults', 'not IE 11'],
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -34,13 +34,11 @@
|
|||
"vant": "^4.9.21",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue": "^3.5.25",
|
||||
"vue-i18n": "^11.2.2",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "6.6.1",
|
||||
"@iconify-json/carbon": "^1.2.15",
|
||||
"@intlify/unplugin-vue-i18n": "^11.0.1",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.10.2",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouteCacheStore } from '@/stores'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
useHead({
|
||||
title: () => t('app.name'),
|
||||
title: 'Vue3 Vant Mobile',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: () => t('app.description'),
|
||||
content: 'An mobile web apps template based on the Vue 3 ecosystem',
|
||||
},
|
||||
{
|
||||
name: 'theme-color',
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ import type { ChatBubbleProps } from '@/types/chat'
|
|||
const props = defineProps<ChatBubbleProps>()
|
||||
|
||||
const messageClasses = computed(() => {
|
||||
const base = 'rounded-2xl p-3 max-w-80'
|
||||
const base = 'rounded-2xl p-3 max-w-80 break-words'
|
||||
const sender = props.message.sender === 'user'
|
||||
? 'bg-blue-100 dark:bg-blue-900 ml-auto text-right'
|
||||
: 'bg-gray-100 dark:bg-gray-800 mr-auto text-left'
|
||||
? 'bg-blue-100 dark:bg-blue-800'
|
||||
: 'bg-gray-100 dark:bg-gray-700'
|
||||
return `${base} ${sender}`
|
||||
})
|
||||
|
||||
const formattedTime = computed(() => {
|
||||
if (!props.message.timestamp) return ''
|
||||
if (!props.message.timestamp)
|
||||
return ''
|
||||
const date = typeof props.message.timestamp === 'string'
|
||||
? new Date(props.message.timestamp)
|
||||
: props.message.timestamp
|
||||
|
|
@ -21,37 +22,57 @@ const formattedTime = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-end gap-2 mb-4" :class="{ 'flex-row-reverse': message.sender === 'user' }">
|
||||
<!-- Avatar -->
|
||||
<van-image
|
||||
v-if="avatar"
|
||||
:src="avatar"
|
||||
class="w-8 h-8 rounded-full flex-shrink-0"
|
||||
round
|
||||
/>
|
||||
<div v-else class="w-8 h-8 rounded-full flex-shrink-0 flex items-center justify-center"
|
||||
:class="message.sender === 'user' ? 'bg-blue-100 dark:bg-blue-900' : 'bg-gray-100 dark:bg-gray-800'">
|
||||
<van-icon :name="message.sender === 'user' ? 'user-circle-o' : 'robot-o'" />
|
||||
<div class="mb-4 flex gap-2" :class="message.sender === 'user' ? 'justify-end' : 'justify-start'">
|
||||
<!-- Avatar for AI messages (left side) -->
|
||||
<div v-if="message.sender === 'ai'">
|
||||
<van-image
|
||||
v-if="avatar"
|
||||
:src="avatar"
|
||||
class="rounded-full flex-shrink-0 h-8 w-8"
|
||||
round
|
||||
/>
|
||||
<div v-else class="text-gray-600 rounded-full bg-gray-100 flex flex-shrink-0 h-8 w-8 items-center justify-center dark:text-gray-300 dark:bg-gray-700">
|
||||
<van-icon name="robot-o" class="text-lg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message bubble -->
|
||||
<div class="flex flex-col" :class="{ 'items-end': message.sender === 'user', 'items-start': message.sender === 'ai' }">
|
||||
<!-- Message bubble and timestamp -->
|
||||
<div class="flex flex-col max-w-[80%]">
|
||||
<div :class="messageClasses">
|
||||
<p class="text-sm">{{ message.content }}</p>
|
||||
<p class="text-sm">
|
||||
{{ message.content }}
|
||||
</p>
|
||||
|
||||
<!-- Status indicator -->
|
||||
<div v-if="message.status === 'sending'" class="mt-1">
|
||||
<van-icon name="clock-o" class="animate-pulse" />
|
||||
</div>
|
||||
<div v-else-if="message.status === 'error'" class="mt-1 text-red-500">
|
||||
<div v-else-if="message.status === 'error'" class="text-red-500 mt-1">
|
||||
<van-icon name="warning-o" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timestamp -->
|
||||
<div v-if="showTimestamp && formattedTime" class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
<div
|
||||
v-if="showTimestamp && formattedTime"
|
||||
class="text-xs text-gray-500 mt-1 dark:text-gray-400"
|
||||
:class="message.sender === 'user' ? 'text-right' : 'text-left'"
|
||||
>
|
||||
{{ formattedTime }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Avatar for user messages (right side) -->
|
||||
<div v-if="message.sender === 'user'">
|
||||
<van-image
|
||||
v-if="avatar"
|
||||
:src="avatar"
|
||||
class="rounded-full flex-shrink-0 h-8 w-8"
|
||||
round
|
||||
/>
|
||||
<div v-else class="text-blue-600 rounded-full bg-blue-100 flex flex-shrink-0 h-8 w-8 items-center justify-center dark:text-blue-300 dark:bg-blue-800">
|
||||
<van-icon name="user-circle-o" class="text-lg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { ChatInputProps, ChatInputEmits } from '@/types/chat'
|
||||
import type { ChatInputEmits, ChatInputProps } from '@/types/chat'
|
||||
|
||||
const props = defineProps<ChatInputProps>()
|
||||
const emit = defineEmits<ChatInputEmits>()
|
||||
|
|
@ -7,11 +7,6 @@ const emit = defineEmits<ChatInputEmits>()
|
|||
const inputRef = ref<HTMLTextAreaElement>()
|
||||
const isComposing = ref(false)
|
||||
|
||||
function handleInput(e: Event) {
|
||||
const target = e.target as HTMLInputElement
|
||||
emit('update:modelValue', target.value)
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
emit('keydown', e)
|
||||
|
||||
|
|
@ -38,10 +33,10 @@ defineExpose({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2 p-3 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="p-3 border-t border-gray-200 bg-white flex gap-2 items-center dark:border-gray-700 dark:bg-gray-900">
|
||||
<van-field
|
||||
ref="inputRef"
|
||||
v-model="props.modelValue"
|
||||
:model-value="props.modelValue"
|
||||
type="textarea"
|
||||
rows="1"
|
||||
autosize
|
||||
|
|
@ -49,7 +44,7 @@ defineExpose({
|
|||
:disabled="disabled"
|
||||
:readonly="loading"
|
||||
class="flex-1"
|
||||
@input="handleInput"
|
||||
@update:model-value="(value) => emit('update:modelValue', value)"
|
||||
@keydown="handleKeydown"
|
||||
@compositionstart="isComposing = true"
|
||||
@compositionend="isComposing = false"
|
||||
|
|
@ -60,12 +55,12 @@ defineExpose({
|
|||
round
|
||||
:disabled="!props.modelValue.trim() || disabled"
|
||||
:loading="loading"
|
||||
@click="handleSend"
|
||||
class="flex-shrink-0"
|
||||
@click="handleSend"
|
||||
>
|
||||
<template #icon>
|
||||
<van-icon name="send" />
|
||||
</template>
|
||||
</van-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,18 +3,33 @@ import { rootRouteList } from '@/config/routes'
|
|||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
const routeTitleMap: Record<string, string> = {
|
||||
'home': 'Home',
|
||||
'profile': 'Profile',
|
||||
'mock': 'Mock',
|
||||
'charts': 'Charts',
|
||||
'unocss': 'UnoCSS',
|
||||
'counter': 'Counter',
|
||||
'keepalive': 'KeepAlive',
|
||||
'scroll-cache': 'ScrollCache',
|
||||
'login': 'Login',
|
||||
'register': 'Register',
|
||||
'forgot-password': 'Forgot Password',
|
||||
'settings': 'Settings',
|
||||
'llm-chat': 'AI Chat',
|
||||
'unknown': '404',
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page title
|
||||
* Located in src/locales/json
|
||||
*/
|
||||
const title = computed(() => {
|
||||
if (route.name) {
|
||||
return t(`navbar.${route.name}`)
|
||||
return routeTitleMap[route.name.toString()] || route.name.toString()
|
||||
}
|
||||
|
||||
return t('navbar.Undefined')
|
||||
return 'Undefined'
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ const show = computed(() => {
|
|||
<template>
|
||||
<van-tabbar v-if="show" v-model="active" route placeholder>
|
||||
<van-tabbar-item replace to="/">
|
||||
{{ $t('tabbar.home') }}
|
||||
Home
|
||||
<template #icon>
|
||||
<div class="i-carbon:home" />
|
||||
</template>
|
||||
</van-tabbar-item>
|
||||
<van-tabbar-item replace to="/profile">
|
||||
{{ $t('tabbar.profile') }}
|
||||
Profile
|
||||
<template #icon>
|
||||
<div class="i-carbon:user" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,2 @@
|
|||
import { i18n } from '@/utils/i18n'
|
||||
|
||||
export const appName = () => i18n.global.t('app.name')
|
||||
export const appDescription = () => i18n.global.t('app.description')
|
||||
export const appName = () => 'Vue3 Vant Mobile'
|
||||
export const appDescription = () => 'An mobile web apps template based on the Vue 3 ecosystem'
|
||||
|
|
|
|||
|
|
@ -1,134 +0,0 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "Vue3 Vant Mobile",
|
||||
"description": "An mobile web apps template based on the Vue 3 ecosystem"
|
||||
},
|
||||
|
||||
"navbar": {
|
||||
"Home": "Home",
|
||||
"Profile": "Profile",
|
||||
"Mock": "🗂️ Mock",
|
||||
"Charts": "📊 Charts",
|
||||
"UnoCSS": "⚡ UnoCSS",
|
||||
"Counter": "🍍 Persistent State",
|
||||
"KeepAlive": "♻️ Page Cache",
|
||||
"ScrollCache": "📍 Scroll Cache",
|
||||
"Login": "🧑💻 Login",
|
||||
"Register": "🧑💻 Register",
|
||||
"ForgotPassword": "❓ Forgot Password",
|
||||
"Settings": "⚙️ Settings",
|
||||
"AIChat": "🤖 AI Chat",
|
||||
"404": "⚠️ Page 404",
|
||||
"Undefined": "🤷 Undefined title"
|
||||
},
|
||||
|
||||
"tabbar": {
|
||||
"home": "HOME",
|
||||
"profile": "PROFILE"
|
||||
},
|
||||
|
||||
"home": {
|
||||
"darkMode": "🌗 Dark Mode",
|
||||
"language": "📚 Language",
|
||||
"settings": "Settings",
|
||||
"examples": "Examples"
|
||||
},
|
||||
|
||||
"profile": {
|
||||
"login": "Login",
|
||||
"settings": "Settings",
|
||||
"docs": "Docs"
|
||||
},
|
||||
|
||||
"mock": {
|
||||
"fromAsyncData": "Data from asynchronous requests",
|
||||
"noData": "No data",
|
||||
"pull": "Pull",
|
||||
"reset": "Reset"
|
||||
},
|
||||
|
||||
"charts": {
|
||||
"January": "Jan",
|
||||
"February": "Feb",
|
||||
"March": "Mar",
|
||||
"April": "Apr",
|
||||
"May": "May",
|
||||
"June": "Jun"
|
||||
},
|
||||
|
||||
"counter": {
|
||||
"description": "This counter's state is persisted via Pinia. Try refreshing the page to see it in action."
|
||||
},
|
||||
|
||||
"unocss": {
|
||||
"title": "Hello, Unocss!",
|
||||
"description": "This is a simple example of Unocss in action.",
|
||||
"button": "Button"
|
||||
},
|
||||
|
||||
"keepAlive": {
|
||||
"label": "The current component will be cached"
|
||||
},
|
||||
|
||||
"scrollCache": {
|
||||
"sectionTitle": "Section title",
|
||||
"sectionText": "Section text text text text text text text text text text",
|
||||
"finished": "Already at the bottom ~",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
|
||||
"login": {
|
||||
"login": "Sign In",
|
||||
"logout": "Sign Out",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"pleaseEnterEmail": "Please enter email",
|
||||
"pleaseEnterPassword": "Please enter password",
|
||||
"signUp": "Click to sign up",
|
||||
"forgotPassword": "Forgot password?"
|
||||
},
|
||||
|
||||
"forgotPassword": {
|
||||
"email": "Email",
|
||||
"code": "Code",
|
||||
"password": "Password",
|
||||
"confirmPassword": "Password again",
|
||||
"pleaseEnterEmail": "Please enter email",
|
||||
"pleaseEnterCode": "Please enter code",
|
||||
"pleaseEnterPassword": "Please enter password",
|
||||
"pleaseEnterConfirmPassword": "Please enter password again",
|
||||
"passwordsDoNotMatch": "Passwords do not match",
|
||||
"confirm": "Confirm",
|
||||
"backToLogin": "Back to login",
|
||||
"getCode": "Get code",
|
||||
"gettingCode": "Getting code",
|
||||
"sendCodeSuccess": "Sent, the code is",
|
||||
"passwordResetSuccess": "Password reset succeeded"
|
||||
},
|
||||
|
||||
"register": {
|
||||
"email": "Email",
|
||||
"code": "Code",
|
||||
"nickname": "Nickname",
|
||||
"password": "Password",
|
||||
"confirmPassword": "Password again",
|
||||
"pleaseEnterEmail": "Please enter email",
|
||||
"pleaseEnterCode": "Please enter code",
|
||||
"pleaseEnterNickname": "Please enter nickname",
|
||||
"pleaseEnterPassword": "Please enter password",
|
||||
"pleaseEnterConfirmPassword": "Please enter password again",
|
||||
"passwordsDoNotMatch": "Passwords do not match",
|
||||
"confirm": "Confirm",
|
||||
"backToLogin": "Back to login",
|
||||
"getCode": "Get code",
|
||||
"gettingCode": "Getting code",
|
||||
"sendCodeSuccess": "Sent, the code is",
|
||||
"registerSuccess": "Register succeeded"
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"logout": "Sign Out",
|
||||
"currentVersion": "Current Version",
|
||||
"confirmTitle": "Confirm Exit?"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "Vue3 移动端模板",
|
||||
"description": "一个基于 Vue 3 生态系统的移动 web 应用模板"
|
||||
},
|
||||
|
||||
"navbar": {
|
||||
"Home": "主页",
|
||||
"Profile": "我的",
|
||||
"Mock": "🗂️ Mock",
|
||||
"Charts": "📊 图表",
|
||||
"UnoCSS": "⚡ UnoCSS",
|
||||
"Counter": "🍍 状态持久化",
|
||||
"KeepAlive": "♻️ 页面缓存",
|
||||
"ScrollCache": "📍 滚动缓存",
|
||||
"Login": "🧑💻 登录",
|
||||
"Register": "🧑💻 注册",
|
||||
"ForgotPassword": "❓ 忘记密码",
|
||||
"Settings": "⚙️ 设置",
|
||||
"AIChat": "🤖 AI聊天",
|
||||
"404": "⚠️ 404 页面",
|
||||
"Undefined": "🤷 未定义标题"
|
||||
},
|
||||
|
||||
"tabbar": {
|
||||
"home": "首页",
|
||||
"profile": "我的"
|
||||
},
|
||||
|
||||
"home": {
|
||||
"darkMode": "🌗 深色模式",
|
||||
"language": "📚 多语言",
|
||||
"settings": "设置",
|
||||
"examples": "示例"
|
||||
},
|
||||
|
||||
"profile": {
|
||||
"login": "登录",
|
||||
"settings": "设置",
|
||||
"docs": "文档"
|
||||
},
|
||||
|
||||
"mock": {
|
||||
"fromAsyncData": "来自异步请求的数据",
|
||||
"noData": "暂无数据",
|
||||
"pull": "请求",
|
||||
"reset": "清空"
|
||||
},
|
||||
|
||||
"charts": {
|
||||
"January": "1月",
|
||||
"February": "2月",
|
||||
"March": "3月",
|
||||
"April": "4月",
|
||||
"May": "5月",
|
||||
"June": "6月"
|
||||
},
|
||||
|
||||
"counter": {
|
||||
"description": "该计数器的状态通过 Pinia 持久化。刷新页面试试看!"
|
||||
},
|
||||
|
||||
"unocss": {
|
||||
"title": "你好, Unocss!",
|
||||
"description": "这是一个简单的 Unocss 使用示例。",
|
||||
"button": "按钮"
|
||||
},
|
||||
|
||||
"keepAlive": {
|
||||
"label": "当前组件将会被缓存"
|
||||
},
|
||||
|
||||
"scrollCache": {
|
||||
"sectionTitle": "段落标题",
|
||||
"sectionText": "段落内容段落内容段落内容段落内容段落内容段落内容",
|
||||
"finished": "已经到底啦 ~",
|
||||
"loading": "加载中..."
|
||||
},
|
||||
|
||||
"login": {
|
||||
"login": "登录",
|
||||
"logout": "退出登录",
|
||||
"email": "邮箱",
|
||||
"password": "密码",
|
||||
"pleaseEnterEmail": "请输入邮箱",
|
||||
"pleaseEnterPassword": "请输入密码",
|
||||
"signUp": "还没有账号?点击注册",
|
||||
"forgotPassword": "忘记密码?"
|
||||
},
|
||||
|
||||
"forgotPassword": {
|
||||
"email": "邮箱",
|
||||
"code": "验证码",
|
||||
"password": "密码",
|
||||
"confirmPassword": "再次输入密码",
|
||||
"pleaseEnterEmail": "请输入邮箱",
|
||||
"pleaseEnterCode": "请输入验证码",
|
||||
"pleaseEnterPassword": "请输入密码",
|
||||
"pleaseEnterConfirmPassword": "请再次输入密码",
|
||||
"passwordsDoNotMatch": "两次输入的密码不一致",
|
||||
"confirm": "确认",
|
||||
"backToLogin": "返回登录",
|
||||
"getCode": "获取验证码",
|
||||
"gettingCode": "获取中",
|
||||
"sendCodeSuccess": "已发送,验证码为",
|
||||
"passwordResetSuccess": "密码重置成功"
|
||||
},
|
||||
|
||||
"register": {
|
||||
"email": "邮箱",
|
||||
"code": "验证码",
|
||||
"nickname": "昵称",
|
||||
"password": "密码",
|
||||
"confirmPassword": "再次输入密码",
|
||||
"pleaseEnterEmail": "请输入邮箱",
|
||||
"pleaseEnterCode": "请输入验证码",
|
||||
"pleaseEnterNickname": "请输入昵称",
|
||||
"pleaseEnterPassword": "请输入密码",
|
||||
"pleaseEnterConfirmPassword": "请再次输入密码",
|
||||
"passwordsDoNotMatch": "两次输入的密码不一致",
|
||||
"confirm": "确认",
|
||||
"backToLogin": "返回登录",
|
||||
"getCode": "获取验证码",
|
||||
"gettingCode": "获取中",
|
||||
"sendCodeSuccess": "已发送,验证码为",
|
||||
"registerSuccess": "注册成功"
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"logout": "退出登录",
|
||||
"currentVersion": "当前版本",
|
||||
"confirmTitle": "确认退出?"
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import pinia from '@/stores'
|
|||
import 'virtual:uno.css'
|
||||
import '@/styles/app.less'
|
||||
import '@/styles/var.less'
|
||||
import { i18n } from '@/utils/i18n'
|
||||
|
||||
// Vant 桌面端适配
|
||||
import '@vant/touch-emulator'
|
||||
|
|
@ -28,6 +27,5 @@ const head = createHead()
|
|||
app.use(head)
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
app.use(i18n)
|
||||
|
||||
app.mount('#app')
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
|
||||
const barOption = ref({
|
||||
title: {},
|
||||
tooltip: {},
|
||||
xAxis: {
|
||||
data: [t('charts.January'), t('charts.February'), t('charts.March'), t('charts.April'), t('charts.May'), t('charts.June')],
|
||||
data: ['January', 'February', 'March', 'April', 'May', 'June'],
|
||||
},
|
||||
yAxis: {},
|
||||
series: [
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const { counter } = storeToRefs(counterStore)
|
|||
|
||||
<template>
|
||||
<div class="text-sm space-y-2">
|
||||
<p> {{ $t('counter.description') }}</p>
|
||||
<p>Counter Demo</p>
|
||||
<van-stepper v-model="counter" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { showNotify } from 'vant'
|
|||
import { useUserStore } from '@/stores'
|
||||
import vw from '@/utils/inline-px-to-vw'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const loading = ref(false)
|
||||
|
|
@ -21,17 +20,17 @@ const validatorPassword = (val: string) => val === postData.password
|
|||
|
||||
const rules = reactive({
|
||||
email: [
|
||||
{ required: true, message: t('forgotPassword.pleaseEnterEmail') },
|
||||
{ required: true, message: 'Please enter email' },
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: t('forgotPassword.pleaseEnterCode') },
|
||||
{ required: true, message: 'Please enter code' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t('forgotPassword.pleaseEnterPassword') },
|
||||
{ required: true, message: 'Please enter password' },
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: t('forgotPassword.pleaseEnterConfirmPassword') },
|
||||
{ required: true, validator: validatorPassword, message: t('forgotPassword.passwordsDoNotMatch') },
|
||||
{ required: true, message: 'Please enter password again' },
|
||||
{ required: true, validator: validatorPassword, message: 'Passwords do not match' },
|
||||
] as FieldRule[],
|
||||
})
|
||||
|
||||
|
|
@ -42,7 +41,7 @@ async function reset() {
|
|||
const res = await userStore.reset()
|
||||
|
||||
if (res.code === 0) {
|
||||
showNotify({ type: 'success', message: t('forgotPassword.passwordResetSuccess') })
|
||||
showNotify({ type: 'success', message: 'Password reset succeeded' })
|
||||
router.push({ name: 'Login' })
|
||||
}
|
||||
}
|
||||
|
|
@ -54,19 +53,19 @@ async function reset() {
|
|||
const isGettingCode = ref(false)
|
||||
|
||||
const buttonText = computed(() => {
|
||||
return isGettingCode.value ? t('forgotPassword.gettingCode') : t('forgotPassword.getCode')
|
||||
return isGettingCode.value ? 'Getting code' : 'Get code'
|
||||
})
|
||||
|
||||
async function getCode() {
|
||||
if (!postData.email) {
|
||||
showNotify({ type: 'warning', message: t('forgotPassword.pleaseEnterEmail') })
|
||||
showNotify({ type: 'warning', message: 'Please enter email' })
|
||||
return
|
||||
}
|
||||
|
||||
isGettingCode.value = true
|
||||
const res = await userStore.getCode()
|
||||
if (res.code === 0)
|
||||
showNotify({ type: 'success', message: `${t('forgotPassword.sendCodeSuccess')}: ${res.result}` })
|
||||
showNotify({ type: 'success', message: `Sent, the code is: ${res.result}` })
|
||||
|
||||
isGettingCode.value = false
|
||||
}
|
||||
|
|
@ -80,7 +79,7 @@ async function getCode() {
|
|||
v-model.trim="postData.email"
|
||||
:rules="rules.email"
|
||||
name="email"
|
||||
:placeholder="$t('forgotPassword.email')"
|
||||
placeholder="Email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -89,7 +88,7 @@ async function getCode() {
|
|||
v-model.trim="postData.code"
|
||||
:rules="rules.code"
|
||||
name="code"
|
||||
:placeholder="$t('forgotPassword.code')"
|
||||
placeholder="Code"
|
||||
>
|
||||
<template #button>
|
||||
<van-button size="small" type="primary" plain @click="getCode">
|
||||
|
|
@ -105,7 +104,7 @@ async function getCode() {
|
|||
type="password"
|
||||
:rules="rules.password"
|
||||
name="password"
|
||||
:placeholder="$t('forgotPassword.password')"
|
||||
placeholder="Password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -115,7 +114,7 @@ async function getCode() {
|
|||
type="password"
|
||||
:rules="rules.confirmPassword"
|
||||
name="confirmPassword"
|
||||
:placeholder="$t('forgotPassword.confirmPassword')"
|
||||
placeholder="Password again"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -126,13 +125,13 @@ async function getCode() {
|
|||
native-type="submit"
|
||||
round block
|
||||
>
|
||||
{{ $t('forgotPassword.confirm') }}
|
||||
Confirm
|
||||
</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
|
||||
<GhostButton to="login" block :style="{ 'margin-top': vw(8) }">
|
||||
{{ $t('forgotPassword.backToLogin') }}
|
||||
Back to login
|
||||
</GhostButton>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,38 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import type { PickerColumn } from 'vant'
|
||||
import { languageColumns, locale } from '@/utils/i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const checked = computed({
|
||||
get: () => isDark.value,
|
||||
set: () => toggleDark(),
|
||||
})
|
||||
|
||||
const menuItems = computed(() => ([
|
||||
{ title: t('navbar.Mock'), route: 'mock' },
|
||||
{ title: t('navbar.Charts'), route: 'charts' },
|
||||
{ title: t('navbar.UnoCSS'), route: 'unocss' },
|
||||
{ title: t('navbar.Counter'), route: 'counter' },
|
||||
{ title: t('navbar.KeepAlive'), route: 'keepalive' },
|
||||
{ title: t('navbar.ScrollCache'), route: 'scroll-cache' },
|
||||
{ title: t('navbar.AIChat'), route: 'llm-chat' },
|
||||
{ title: t('navbar.404'), route: 'unknown' },
|
||||
{ title: 'Mock', route: 'mock' },
|
||||
{ title: 'Charts', route: 'charts' },
|
||||
{ title: 'UnoCSS', route: 'unocss' },
|
||||
{ title: 'Counter', route: 'counter' },
|
||||
{ title: 'KeepAlive', route: 'keepalive' },
|
||||
{ title: 'ScrollCache', route: 'scroll-cache' },
|
||||
{ title: 'AI Chat', route: 'llm-chat' },
|
||||
{ title: '404', route: 'unknown' },
|
||||
]))
|
||||
|
||||
const showLanguagePicker = ref(false)
|
||||
const languageValues = ref<Array<string>>([locale.value])
|
||||
const language = computed(() => languageColumns.find(l => l.value === locale.value).text)
|
||||
|
||||
function onLanguageConfirm(event: { selectedOptions: PickerColumn }) {
|
||||
locale.value = event.selectedOptions[0].value as string
|
||||
showLanguagePicker.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<van-cell-group :title="$t('home.settings')" :border="false" :inset="true">
|
||||
<van-cell center :title="$t('home.darkMode')">
|
||||
<van-cell-group title="Settings" :border="false" :inset="true">
|
||||
<van-cell center title="Dark Mode">
|
||||
<template #right-icon>
|
||||
<van-switch
|
||||
v-model="checked"
|
||||
|
|
@ -41,29 +27,13 @@ function onLanguageConfirm(event: { selectedOptions: PickerColumn }) {
|
|||
/>
|
||||
</template>
|
||||
</van-cell>
|
||||
|
||||
<van-cell
|
||||
is-link
|
||||
:title="$t('home.language')"
|
||||
:value="language"
|
||||
@click="showLanguagePicker = true"
|
||||
/>
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group :title="$t('home.examples')" :border="false" :inset="true">
|
||||
<van-cell-group title="Examples" :border="false" :inset="true">
|
||||
<template v-for="item in menuItems" :key="item.route">
|
||||
<van-cell :title="item.title" :to="item.route" is-link />
|
||||
</template>
|
||||
</van-cell-group>
|
||||
|
||||
<van-popup v-model:show="showLanguagePicker" position="bottom">
|
||||
<van-picker
|
||||
v-model="languageValues"
|
||||
:columns="languageColumns"
|
||||
@confirm="onLanguageConfirm"
|
||||
@cancel="showLanguagePicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
</template>
|
||||
|
||||
<route lang="json5">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const value = ref(0)
|
|||
|
||||
<template>
|
||||
<div class="text-sm space-y-2">
|
||||
<p>{{ $t('keepAlive.label') }}</p>
|
||||
<p>KeepAlive Demo</p>
|
||||
<van-stepper v-model="value" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import type { ChatMessage } from '@/types/chat'
|
|||
import { useScroll } from '@vueuse/core'
|
||||
import { isDark, toggleDark } from '@/composables/dark'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// Sample chat data for demonstration
|
||||
const messages = ref<ChatMessage[]>([
|
||||
|
|
@ -48,7 +47,8 @@ watch(messages, () => {
|
|||
}, { deep: true })
|
||||
|
||||
function handleSend(text: string) {
|
||||
if (!text.trim()) return
|
||||
if (!text.trim())
|
||||
return
|
||||
|
||||
// Add user message
|
||||
const userMessage: ChatMessage = {
|
||||
|
|
@ -56,7 +56,7 @@ function handleSend(text: string) {
|
|||
content: text,
|
||||
sender: 'user',
|
||||
timestamp: new Date(),
|
||||
status: 'sending',
|
||||
status: 'sent',
|
||||
}
|
||||
messages.value.push(userMessage)
|
||||
|
||||
|
|
@ -87,13 +87,12 @@ function handleKeydown(e: KeyboardEvent) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-screen flex flex-col bg-gray-50 dark:bg-gray-950">
|
||||
<div class="bg-gray-50 flex flex-col h-screen dark:bg-gray-950">
|
||||
<!-- Header -->
|
||||
<van-nav-bar
|
||||
:title="t('llmChat.title', 'AI Chat')"
|
||||
fixed
|
||||
placeholder
|
||||
safe-area-inset-top
|
||||
title="AI Chat"
|
||||
|
||||
placeholder safe-area-inset-top fixed
|
||||
>
|
||||
<template #right>
|
||||
<van-icon
|
||||
|
|
@ -107,7 +106,7 @@ function handleKeydown(e: KeyboardEvent) {
|
|||
<!-- Messages container -->
|
||||
<van-list
|
||||
ref="messagesContainer"
|
||||
class="flex-1 p-4 pt-16 pb-24"
|
||||
class="p-4 pb-24 pt-16 flex-1"
|
||||
:finished="true"
|
||||
:loading="false"
|
||||
finished-text=""
|
||||
|
|
@ -130,7 +129,7 @@ function handleKeydown(e: KeyboardEvent) {
|
|||
</van-list>
|
||||
|
||||
<!-- Input area -->
|
||||
<div class="fixed bottom-0 left-0 right-0 z-10">
|
||||
<div class="bottom-0 left-0 right-0 fixed z-10">
|
||||
<ChatInput
|
||||
v-model="inputText"
|
||||
:disabled="isLoading"
|
||||
|
|
@ -151,4 +150,4 @@ function handleKeydown(e: KeyboardEvent) {
|
|||
requiresAuth: false
|
||||
}
|
||||
}
|
||||
</route>
|
||||
</route>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import logo from '~/images/logo.svg'
|
|||
import logoDark from '~/images/logo-dark.svg'
|
||||
import vw from '@/utils/inline-px-to-vw'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const loading = ref(false)
|
||||
|
|
@ -28,10 +27,10 @@ const postData = reactive({
|
|||
|
||||
const rules = reactive({
|
||||
email: [
|
||||
{ required: true, message: t('login.pleaseEnterEmail') },
|
||||
{ required: true, message: 'Please enter email' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t('login.pleaseEnterPassword') },
|
||||
{ required: true, message: 'Please enter password' },
|
||||
],
|
||||
})
|
||||
|
||||
|
|
@ -65,7 +64,7 @@ async function login(values: any) {
|
|||
v-model="postData.email"
|
||||
:rules="rules.email"
|
||||
name="email"
|
||||
:placeholder="$t('login.email')"
|
||||
placeholder="Email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -75,7 +74,7 @@ async function login(values: any) {
|
|||
type="password"
|
||||
:rules="rules.password"
|
||||
name="password"
|
||||
:placeholder="$t('login.password')"
|
||||
placeholder="Password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -86,17 +85,17 @@ async function login(values: any) {
|
|||
native-type="submit"
|
||||
round block
|
||||
>
|
||||
{{ $t('login.login') }}
|
||||
Sign In
|
||||
</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
|
||||
<GhostButton block to="register" :style="{ 'margin-top': vw(18) }">
|
||||
{{ $t('login.signUp') }}
|
||||
Click to sign up
|
||||
</GhostButton>
|
||||
|
||||
<GhostButton block to="forgot-password" class="mt-2">
|
||||
{{ $t('login.forgotPassword') }}
|
||||
Forgot password?
|
||||
</GhostButton>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -13,22 +13,22 @@ function pull() {
|
|||
|
||||
<template>
|
||||
<div class="data-label">
|
||||
{{ $t('mock.fromAsyncData') }}
|
||||
Data from async request
|
||||
</div>
|
||||
|
||||
<div class="data-content bg-white dark:bg-[--van-background-2]">
|
||||
<div v-if="messages">
|
||||
{{ messages }}
|
||||
</div>
|
||||
<VanEmpty v-else :description="$t('mock.noData')" />
|
||||
<VanEmpty v-else description="No data" />
|
||||
</div>
|
||||
|
||||
<van-space class="m-2" direction="vertical" fill>
|
||||
<VanButton type="primary" round block @click="pull">
|
||||
{{ $t('mock.pull') }}
|
||||
Pull data
|
||||
</VanButton>
|
||||
<VanButton type="default" round block @click="messages = ''">
|
||||
{{ $t('mock.reset') }}
|
||||
Reset
|
||||
</VanButton>
|
||||
</van-space>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -25,18 +25,18 @@ function login() {
|
|||
|
||||
<template #value>
|
||||
<span v-if="isLogin">{{ userInfo.name }}</span>
|
||||
<span v-else>{{ $t('profile.login') }}</span>
|
||||
<span v-else>Login</span>
|
||||
</template>
|
||||
</van-cell>
|
||||
</VanCellGroup>
|
||||
|
||||
<VanCellGroup :inset="true" class="!mt-4">
|
||||
<van-cell :title="$t('profile.settings')" icon="setting-o" is-link to="/settings">
|
||||
<van-cell title="Settings" icon="setting-o" is-link to="/settings">
|
||||
<template #icon>
|
||||
<div class="i-carbon:settings text-gray-400 mr-2 self-center" />
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell :title="$t('profile.docs')" is-link url="https://vue-zone.github.io/docs/vue3-vant-mobile/">
|
||||
<van-cell title="Documentation" is-link url="https://vue-zone.github.io/docs/vue3-vant-mobile/">
|
||||
<template #icon>
|
||||
<div class="i-carbon:doc text-gray-400 mr-2 self-center" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { showNotify } from 'vant'
|
|||
import { useUserStore } from '@/stores'
|
||||
import vw from '@/utils/inline-px-to-vw'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const loading = ref(false)
|
||||
|
|
@ -22,20 +21,20 @@ const validatorPassword = (val: string) => val === postData.password
|
|||
|
||||
const rules = reactive({
|
||||
email: [
|
||||
{ required: true, message: t('register.pleaseEnterEmail') },
|
||||
{ required: true, message: 'Please enter email' },
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: t('register.pleaseEnterCode') },
|
||||
{ required: true, message: 'Please enter code' },
|
||||
],
|
||||
nickname: [
|
||||
{ required: true, message: t('register.pleaseEnterNickname') },
|
||||
{ required: true, message: 'Please enter nickname' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t('register.pleaseEnterPassword') },
|
||||
{ required: true, message: 'Please enter password' },
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: t('register.pleaseEnterConfirmPassword') },
|
||||
{ required: true, validator: validatorPassword, message: t('register.passwordsDoNotMatch') },
|
||||
{ required: true, message: 'Please enter password again' },
|
||||
{ required: true, validator: validatorPassword, message: 'Passwords do not match' },
|
||||
] as FieldRule[],
|
||||
})
|
||||
|
||||
|
|
@ -46,7 +45,7 @@ async function register() {
|
|||
const res = await userStore.register()
|
||||
|
||||
if (res.code === 0) {
|
||||
showNotify({ type: 'success', message: t('register.registerSuccess') })
|
||||
showNotify({ type: 'success', message: 'Registration succeeded' })
|
||||
router.push({ name: 'Login' })
|
||||
}
|
||||
}
|
||||
|
|
@ -58,19 +57,19 @@ async function register() {
|
|||
const isGettingCode = ref(false)
|
||||
|
||||
const buttonText = computed(() => {
|
||||
return isGettingCode.value ? t('register.gettingCode') : t('register.getCode')
|
||||
return isGettingCode.value ? 'Getting code' : 'Get code'
|
||||
})
|
||||
|
||||
async function getCode() {
|
||||
if (!postData.email) {
|
||||
showNotify({ type: 'warning', message: t('register.pleaseEnterEmail') })
|
||||
showNotify({ type: 'warning', message: 'Please enter email' })
|
||||
return
|
||||
}
|
||||
|
||||
isGettingCode.value = true
|
||||
const res = await userStore.getCode()
|
||||
if (res.code === 0)
|
||||
showNotify({ type: 'success', message: `${t('register.sendCodeSuccess')}: ${res.result}` })
|
||||
showNotify({ type: 'success', message: `Sent, the code is: ${res.result}` })
|
||||
|
||||
isGettingCode.value = false
|
||||
}
|
||||
|
|
@ -84,7 +83,7 @@ async function getCode() {
|
|||
v-model.trim="postData.email"
|
||||
:rules="rules.email"
|
||||
name="email"
|
||||
:placeholder="$t('register.email')"
|
||||
placeholder="Email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -93,7 +92,7 @@ async function getCode() {
|
|||
v-model.trim="postData.code"
|
||||
:rules="rules.code"
|
||||
name="code"
|
||||
:placeholder="$t('register.code')"
|
||||
placeholder="Code"
|
||||
>
|
||||
<template #button>
|
||||
<van-button size="small" type="primary" plain @click="getCode">
|
||||
|
|
@ -108,7 +107,7 @@ async function getCode() {
|
|||
v-model.trim="postData.nickname"
|
||||
:rules="rules.nickname"
|
||||
name="nickname"
|
||||
:placeholder="$t('register.nickname')"
|
||||
placeholder="Nickname"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -118,7 +117,7 @@ async function getCode() {
|
|||
type="password"
|
||||
:rules="rules.password"
|
||||
name="password"
|
||||
:placeholder="$t('register.password')"
|
||||
placeholder="Password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -128,7 +127,7 @@ async function getCode() {
|
|||
type="password"
|
||||
:rules="rules.confirmPassword"
|
||||
name="confirmPassword"
|
||||
:placeholder="$t('register.confirmPassword')"
|
||||
placeholder="Password again"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -139,13 +138,13 @@ async function getCode() {
|
|||
native-type="submit"
|
||||
round block
|
||||
>
|
||||
{{ $t('register.confirm') }}
|
||||
Confirm
|
||||
</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
|
||||
<GhostButton to="login" block :style="{ 'margin-top': vw(8) }">
|
||||
{{ $t('register.backToLogin') }}
|
||||
Back to login
|
||||
</GhostButton>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ onBeforeRouteLeave(() => {
|
|||
<van-list
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
:finished-text="$t('scrollCache.finished')"
|
||||
:loading-text="$t('scrollCache.loading')"
|
||||
finished-text="No more data"
|
||||
loading-text="Loading..."
|
||||
@load="onLoad"
|
||||
>
|
||||
<ul class="space-y-2">
|
||||
|
|
@ -54,14 +54,14 @@ onBeforeRouteLeave(() => {
|
|||
<div class="flex-1 min-w-0">
|
||||
<div class="flex flex-row gap-2 w-full justify-between">
|
||||
<h3 class="text-base text-zinc-600 tracking-tight font-semibold w-1/2 dark:text-white">
|
||||
<van-text-ellipsis :content="`${$t('scrollCache.sectionTitle')}`" />
|
||||
<van-text-ellipsis content="Section Title" />
|
||||
</h3>
|
||||
|
||||
<time class="text-xs text-zinc-400 tabular-nums">2025-05-16</time>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-zinc-500">
|
||||
<van-text-ellipsis :rows="2" :content="$t('scrollCache.sectionText')" />
|
||||
<van-text-ellipsis rows="2" content="This is a sample section text for demonstration purposes. It can be longer and will be ellipsized." />
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ import router from '@/router'
|
|||
import { useUserStore } from '@/stores'
|
||||
import { version } from '~root/package.json'
|
||||
|
||||
const { t } = useI18n()
|
||||
const userStore = useUserStore()
|
||||
const userInfo = computed(() => userStore.userInfo)
|
||||
|
||||
function Logout() {
|
||||
showConfirmDialog({
|
||||
title: t('settings.confirmTitle'),
|
||||
title: 'Confirm logout?',
|
||||
})
|
||||
.then(() => {
|
||||
userStore.logout()
|
||||
|
|
@ -23,11 +22,11 @@ function Logout() {
|
|||
<template>
|
||||
<div class="text-center">
|
||||
<VanCellGroup :inset="true">
|
||||
<van-cell v-if="userInfo.uid" :title="$t('settings.logout')" clickable class="van-text-color" @click="Logout" />
|
||||
<van-cell v-if="userInfo.uid" title="Logout" clickable class="van-text-color" @click="Logout" />
|
||||
</VanCellGroup>
|
||||
|
||||
<div class="text-gray mt-2">
|
||||
{{ $t("settings.currentVersion") }}: v{{ version }}
|
||||
Current version: v{{ version }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<h1 class="text-base color-pink font-semibold">
|
||||
{{ $t('unocss.title') }}
|
||||
UnoCSS Demo
|
||||
</h1>
|
||||
|
||||
<p class="text-gray-700 mt-2 dark:text-white">
|
||||
{{ $t('unocss.description') }}
|
||||
This is a demo of UnoCSS.
|
||||
</p>
|
||||
|
||||
<button class="btn mt-2">
|
||||
{{ $t('unocss.button') }}
|
||||
Click me
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ declare global {
|
|||
const getCurrentScope: typeof import('vue').getCurrentScope
|
||||
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
|
||||
const h: typeof import('vue').h
|
||||
const i18n: typeof import('@/utils/i18n').i18n
|
||||
const ignorableWatch: typeof import('@vueuse/core').ignorableWatch
|
||||
const inject: typeof import('vue').inject
|
||||
const injectHead: typeof import('@unhead/vue').injectHead
|
||||
|
|
@ -50,7 +49,6 @@ declare global {
|
|||
const isReadonly: typeof import('vue').isReadonly
|
||||
const isRef: typeof import('vue').isRef
|
||||
const isShallow: typeof import('vue').isShallow
|
||||
const locale: typeof import('@/utils/i18n').locale
|
||||
const makeDestructurable: typeof import('@vueuse/core').makeDestructurable
|
||||
const manualResetRef: typeof import('@vueuse/core').manualResetRef
|
||||
const markRaw: typeof import('vue').markRaw
|
||||
|
|
@ -190,7 +188,6 @@ declare global {
|
|||
const useGeolocation: typeof import('@vueuse/core').useGeolocation
|
||||
const useHead: typeof import('@unhead/vue').useHead
|
||||
const useHeadSafe: typeof import('@unhead/vue').useHeadSafe
|
||||
const useI18n: typeof import('vue-i18n').useI18n
|
||||
const useId: typeof import('vue').useId
|
||||
const useIdle: typeof import('@vueuse/core').useIdle
|
||||
const useImage: typeof import('@vueuse/core').useImage
|
||||
|
|
|
|||
|
|
@ -23,4 +23,4 @@ export interface ChatInputEmits {
|
|||
(e: 'update:modelValue', value: string): void
|
||||
(e: 'send', value: string): void
|
||||
(e: 'keydown', event: KeyboardEvent): void
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
import { createI18n } from 'vue-i18n'
|
||||
import enUS from 'vant/es/locale/lang/en-US'
|
||||
import zhCN from 'vant/es/locale/lang/zh-CN'
|
||||
import { Locale } from 'vant'
|
||||
import type { PickerColumn } from 'vant'
|
||||
|
||||
const FALLBACK_LOCALE = 'zh-CN'
|
||||
|
||||
const vantLocales = {
|
||||
'zh-CN': zhCN,
|
||||
'en-US': enUS,
|
||||
}
|
||||
|
||||
export const languageColumns: PickerColumn = [
|
||||
{ text: '简体中文', value: 'zh-CN' },
|
||||
{ text: 'English', value: 'en-US' },
|
||||
]
|
||||
|
||||
export const i18n = setupI18n()
|
||||
type I18n = typeof i18n
|
||||
|
||||
export const locale = computed({
|
||||
get() {
|
||||
return i18n.global.locale.value
|
||||
},
|
||||
set(language: string) {
|
||||
setLang(language, i18n)
|
||||
},
|
||||
})
|
||||
|
||||
function setupI18n() {
|
||||
const locale = getI18nLocale()
|
||||
const i18n = createI18n({
|
||||
locale,
|
||||
legacy: false,
|
||||
})
|
||||
setLang(locale, i18n)
|
||||
return i18n
|
||||
}
|
||||
|
||||
async function setLang(lang: string, i18n: I18n) {
|
||||
await loadLocaleMsg(lang, i18n)
|
||||
|
||||
document.querySelector('html').setAttribute('lang', lang)
|
||||
localStorage.setItem('language', lang)
|
||||
i18n.global.locale.value = lang
|
||||
|
||||
// 设置 vant 组件语言包
|
||||
Locale.use(lang, vantLocales[lang])
|
||||
}
|
||||
|
||||
// 加载本地语言包
|
||||
async function loadLocaleMsg(locale: string, i18n: I18n) {
|
||||
const messages = await import(`../locales/${locale}.json`)
|
||||
i18n.global.setLocaleMessage(locale, messages.default)
|
||||
}
|
||||
|
||||
// 获取当前语言对应的语言包名称
|
||||
function getI18nLocale() {
|
||||
const storedLocale = localStorage.getItem('language') || navigator.language
|
||||
|
||||
const langs = languageColumns.map(v => v.value as string)
|
||||
|
||||
// 存在当前语言的语言包 或 存在当前语言的任意地区的语言包
|
||||
const foundLocale = langs.find(v => v === storedLocale || v.indexOf(storedLocale) === 0)
|
||||
|
||||
// 若未找到,则使用 默认语言包
|
||||
const locale = foundLocale || FALLBACK_LOCALE
|
||||
|
||||
return locale
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { appName } from '@/constants'
|
||||
import { i18n } from '@/utils/i18n'
|
||||
|
||||
export default function setPageTitle(name?: string): void {
|
||||
window.document.title = name ? `${i18n.global.t(`navbar.${name}`)} - ${appName()}` : appName()
|
||||
window.document.title = name ? `${name} - ${appName()}` : appName()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@
|
|||
"types": [
|
||||
"node",
|
||||
"unplugin-vue-router/client",
|
||||
"vite-plugin-pwa/client",
|
||||
"@intlify/unplugin-vue-i18n/messages"
|
||||
"vite-plugin-pwa/client"
|
||||
],
|
||||
"allowJs": true,
|
||||
"strictNullChecks": false,
|
||||
|
|
|
|||
Loading…
Reference in New Issue