适配多语言

Squashed commit of the following:

commit 632f86c0228c68391c01865c7576f3aa0408c102
Author: Sun <95302870@qq.com>
Date:   Sun Jan 7 14:47:55 2024 +0800

    退出的时候清除appstore

commit b9d805e49a3c6b2ad38bc8d527cb12cc8709012e
Author: Sun <95302870@qq.com>
Date:   Sun Jan 7 13:55:20 2024 +0800

    系统状态监控适配国际化

commit daece99723ec96d210241d2ca4e5a85dc5ae69bd
Author: Sun <95302870@qq.com>
Date:   Sun Jan 7 13:09:46 2024 +0800

    适配添加项目页面的国际化配置还有时钟的星期*

commit 8ea2b2fe951f6266415c96a197cb8d00faef4058
Author: Sun <95302870@qq.com>
Date:   Sun Jan 7 12:01:55 2024 +0800

    完成适配所有apps国际化

commit 21ef54e0d4afb10f560c8cb7aff666374afe0f87
Author: Sun <95302870@qq.com>
Date:   Sat Jan 6 21:36:07 2024 +0800

    增加读取默认浏览器语言

commit 6f710bbebe63ab2800193f27c71e5c0034f11978
Author: Sun <95302870@qq.com>
Date:   Sat Jan 6 21:09:58 2024 +0800

    登录页面增加语言选择选项

commit cb7c4a89a160ed3ef91ad566ec98e75325e7601f
Author: Sun <95302870@qq.com>
Date:   Sat Jan 6 20:37:16 2024 +0800

    首次尝试增加英文语言,并在我的信息设置

commit fb996e17cd11611d30c0e12feee00ddf7b225e32
Author: Sun <95302870@qq.com>
Date:   Sat Jan 6 18:22:40 2024 +0800

    完成基础设置页面的语言国际化适配
This commit is contained in:
Sun
2024-01-07 14:53:53 +08:00
parent ffc378a38f
commit 394c6ce20c
23 changed files with 609 additions and 467 deletions
@@ -3,6 +3,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
import { NLayout, NLayoutContent, NLayoutSider, NSpace } from 'naive-ui'
import { useAuthStore } from '@/store'
import { AppLoader, RoundCardModal, SvgIcon } from '@/components/common'
import { t } from '@/locales'
interface App {
name: string
@@ -23,38 +24,38 @@ const componentName = ref('UserInfo')
const collapsed = ref(false)
const screenWidth = ref(0)
const isSmallScreen = ref(false)
const defaultTitle = '系统应用 & 设置'
const defaultTitle = t('appLauncher.title')
const title = ref('')
const height = ref('500px')
const apps = ref<App[]>([
{
name: '用户信息',
name: t('apps.userInfo.appName'),
componentName: 'UserInfo',
icon: 'material-symbols-person-edit-outline-rounded',
},
{
name: '基本设置',
name: t('apps.baseSettings.appName'),
componentName: 'Style',
icon: 'ep-setting',
},
{
name: '分组管理',
name: t('apps.itemGroupManage.appName'),
componentName: 'ItemGroupManage',
icon: 'material-symbols-ad-group-outline-rounded',
},
{
name: '导入导出',
name: t('apps.exportImport.appName'),
componentName: 'ImportExport',
icon: 'icon-park-outline-import-and-export',
},
{
name: '上传文件管理',
name: t('apps.uploadsFileManager.appName'),
componentName: 'UploadFileManager',
icon: 'tabler:file-upload',
},
{
name: '关于',
name: t('apps.about.appName'),
componentName: 'About',
icon: 'lucide-info',
},
@@ -93,7 +94,7 @@ function handleResize() {
onMounted(() => {
const adminApp: App = {
name: '用户管理',
name: t('adminSettingUsers.appName'),
componentName: 'Users',
icon: 'lucide-users',
auth: 1,
@@ -116,7 +117,6 @@ onUnmounted(() => {
<RoundCardModal
v-model:show="show"
style="max-width: 900px;"
title="应用列表"
size="small"
>
<template #header>
@@ -4,6 +4,7 @@ import type { UploadFileInfo } from 'naive-ui'
import { computed, defineProps } from 'vue'
import { ItemIcon } from '@/components/common'
import { useAuthStore } from '@/store'
import { t } from '@/locales'
const props = defineProps<{
itemIcon: Panel.ItemIcon | null
@@ -41,7 +42,6 @@ const itemIconInfo = computed({
return v
},
set() {
console.log('aaaa')
handleChange()
},
})
@@ -53,7 +53,6 @@ function handleIconTypeRadioChange(type: number) {
}
function handleChange() {
console.log('21222')
emit('update:itemIcon', itemIconInfo.value || null)
}
@@ -76,7 +75,7 @@ const handleUploadFinish = ({
emit('update:itemIcon', itemIconInfo.value || null)
}
else {
ms.error(`上传错误:${res.msg}`)
ms.error(`${t('common.uploadFail')}:${res.msg}`)
}
return file
@@ -92,7 +91,7 @@ const handleUploadFinish = ({
name="iconType"
@change="handleIconTypeRadioChange(1)"
>
文字
{{ $t('common.text') }}
</NRadio>
<NRadio
@@ -101,7 +100,7 @@ const handleUploadFinish = ({
name="iconType"
@change="handleIconTypeRadioChange(2)"
>
图片/SVG
{{ $t('common.image') }}
</NRadio>
<NRadio
@@ -110,7 +109,7 @@ const handleUploadFinish = ({
name="iconType"
@change="handleIconTypeRadioChange(3)"
>
在线图标
{{ $t('iconItem.onlineIcon') }}
</NRadio>
</div>
@@ -125,22 +124,22 @@ const handleUploadFinish = ({
<div class="ml-[20px]">
<!-- <NImage :src="model.icon" preview-disabled /> -->
<div v-if="itemIconInfo.itemType === 1">
<NInput v-model:value="itemIconInfo.text" class="mb-[5px]" size="small" type="text" placeholder="请输入文字作为图标" @input="handleChange" />
<NInput v-model:value="itemIconInfo.text" class="mb-[5px]" size="small" type="text" @input="handleChange" />
</div>
<div v-if="itemIconInfo.itemType === 3">
<div>
<NInput v-model:value="itemIconInfo.text" class="mb-[5px]" size="small" type="text" placeholder="请输入图标名字" @input="handleChange" />
<NInput v-model:value="itemIconInfo.text" class="mb-[5px]" size="small" type="text" :placeholder="$t('iconItem.inputIconName')" @input="handleChange" />
<NButton quaternary type="info">
<a target="_blank" href="https://icon-sets.iconify.design/">在线图标库</a>
<a target="_blank" href="https://icon-sets.iconify.design/">{{ $t('iconItem.onlineIconLibrary') }}</a>
</NButton>
</div>
</div>
<!-- 图片 -->
<div v-if="itemIconInfo.itemType === 2">
<NInput v-model:value="itemIconInfo.src" class="mb-[5px] w-full" size="small" type="text" placeholder="输入图标地址或上传" @input="handleChange" />
<NInput v-model:value="itemIconInfo.src" class="mb-[5px] w-full" size="small" type="text" :placeholder="$t('iconItem.inputIconUrlOrUpload')" @input="handleChange" />
<NUpload
action="/api/file/uploadImg"
:show-file-list="false"
@@ -151,7 +150,7 @@ const handleUploadFinish = ({
@finish="handleUploadFinish"
>
<NButton size="small">
本地上传
{{ $t('iconItem.selectUpload') }}
</NButton>
</NUpload>
</div>
@@ -160,7 +159,7 @@ const handleUploadFinish = ({
<div class="flex items-center mt-[10px]">
<div class="w-auto text-slate-500 mr-[10px]">
背景色:
{{ $t('common.backgroundColor') }}
</div>
<div class="w-[150px] flex items-center mr-[10px]">
<NColorPicker
@@ -174,7 +173,7 @@ const handleUploadFinish = ({
</div>
<div v-if="itemIconInfo.backgroundColor !== initData.backgroundColor" class="w-auto text-slate-500 mr-[10px] cursor-pointer">
<NButton quaternary type="info" @click="handleResetBackgroundColor">
恢复默认
{{ $t('common.reset') }}
</NButton>
</div>
</div>
+26 -26
View File
@@ -5,6 +5,7 @@ import { NButton, NForm, NFormItem, NGrid, NGridItem, NInput, NInputGroup, NModa
import IconEditor from './IconEditor.vue'
import { edit, getSiteFavicon } from '@/api/panel/itemIcon'
import { getList as getGroupList } from '@/api/panel/itemIconGroup'
import { t } from '@/locales'
interface Props {
visible: boolean
@@ -43,33 +44,33 @@ const rules: FormRules = {
title: {
required: true,
trigger: 'blur',
message: '必填项',
message: t('form.required'),
},
url: {
required: true,
trigger: 'blur',
type: 'string',
message: '必填项',
message: t('form.required'),
},
// itemIconGroupId: {
// required: true,
// trigger: ['blur', 'change'],
// message: '必填项',
// message: t('form.required'),
// },
}
const options = [
{
default: true,
label: '当前页面打开',
label: t('iconItem.currentPageOpen'),
value: 1,
},
{
label: '新窗口打开',
label: t('iconItem.newWindowOpen'),
value: 2,
},
{
label: '当前页面弹窗打开',
label: t('iconItem.currentPageLayerOpen'),
value: 3,
},
]
@@ -89,16 +90,15 @@ async function editApi() {
if (code === 0) {
show.value = false
model.value = { ...restoreDefault }
console.log('重置完成', model.value)
emit('done', data)
}
else {
ms.error(`保存失败:${msg}`)
ms.error(`${t('common.saveFail')}:${msg}`)
}
}
catch (error) {
ms.error('保存失败')
ms.error(t('common.saveFail'))
}
submitLoading.value = false
}
@@ -122,11 +122,11 @@ async function getIconByUrl(url: string, loadingIndex: number) {
}
}
else {
ms.error('图标获取失败')
ms.error(t('iconItem.geticonFail'))
}
}
catch (error) {
ms.error('图标获取失败')
ms.error(t('iconItem.geticonFail'))
}
getIconLoading.value[loadingIndex] = false
}
@@ -160,53 +160,53 @@ function getGroupListOptions() {
}
}
else {
ms.error(`分组信息获取失败:${msg}`)
ms.error(`${t('iconItem.getGroupFail')}:${msg}`)
}
})
}
</script>
<template>
<NModal v-model:show="show" preset="card" size="small" style="width: 600px;border-radius: 1rem;" :title="itemInfo ? '修改项目' : '添加项目'">
<NModal v-model:show="show" preset="card" size="small" style="width: 600px;border-radius: 1rem;" :title="itemInfo ? t('iconItem.edit') : t('iconItem.add')">
<div class="h-[600px] overflow-auto p-[5px]">
<NForm ref="formRef" :model="model" :rules="rules">
<NGrid cols="2" :x-gap="10" item-responsive>
<NGridItem span="2 500:1">
<NFormItem path="itemIconGroupId" label="分组">
<NFormItem path="itemIconGroupId" :label="t('iconItem.iconGroup')">
<NSelect v-model:value="model.itemIconGroupId" :options="itemIconGroupOptions" />
</NFormItem>
</NGridItem>
<NGridItem span="2 500:1">
<NFormItem path="title" label="标题">
<NInput v-model:value="model.title" type="text" show-count :maxlength="20" placeholder="请输入标题" />
<NFormItem path="title" :label="$t('common.title')">
<NInput v-model:value="model.title" type="text" show-count :maxlength="20" />
</NFormItem>
</NGridItem>
</NGrid>
<NFormItem path="icon" label="图标">
<NFormItem path="icon" :label="$t('common.icon')">
<IconEditor v-model:item-icon="model.icon" />
</NFormItem>
<NFormItem path="url" label="跳转地址">
<NFormItem path="url" :label="$t('iconItem.url')">
<!-- <NSelect :style="{ width: '100px' }" :options="urlProtocolOptions" /> -->
<NInputGroup>
<NInput v-model:value="model.url" type="text" :maxlength="1000" placeholder="http(s)://" />
<NButton :disabled="!model.url" :loading="getIconLoading[0]" @click="getIconByUrl(model.url, 0)">
获取图标
{{ $t('iconItem.getIcon') }}
</NButton>
</NInputGroup>
</NFormItem>
<NFormItem path="lanUrl" label="局域网跳转地址">
<NFormItem path="lanUrl" :label="$t('iconItem.lanUrl')">
<NInputGroup>
<NInput v-model:value="model.lanUrl" type="text" :maxlength="1000" placeholder="http(s)://(可以留空,切换到局域网模式,点击会使用该地址)" />
<NInput v-model:value="model.lanUrl" type="text" :maxlength="1000" :placeholder="$t('iconItem.lanUrlInputPlaceholder')" />
<NButton :disabled="!model.lanUrl" :loading="getIconLoading[1]" @click="getIconByUrl(model.lanUrl || '', 1)">
获取图标
{{ $t('iconItem.getIcon') }}
</NButton>
</NInputGroup>
</NFormItem>
<NFormItem path="description" label="描述信息">
<NInput v-model:value="model.description" type="text" show-count :maxlength="100" placeholder="请填写描述信息" />
<NFormItem path="description" :label="$t('common.description')">
<NInput v-model:value="model.description" type="text" show-count :maxlength="100" />
</NFormItem>
<NFormItem path="openMethod" label="打开方式">
<NFormItem path="openMethod" :label="$t('iconItem.openMethod')">
<NSelect v-model:value="model.openMethod" :options="options" />
</NFormItem>
</NForm>
@@ -214,7 +214,7 @@ function getGroupListOptions() {
<template #footer>
<NButton type="success" :loading="submitLoading" style="float: right;" @click="handleValidateButtonClick">
确定
{{ $t('common.save') }}
</NButton>
</template>
</NModal>
+24 -23
View File
@@ -13,6 +13,7 @@ import { useAuthStore, usePanelState } from '@/store'
import { PanelPanelConfigStyleEnum, PanelStateNetworkModeEnum } from '@/enums'
import { VisitMode } from '@/enums/auth'
import { router } from '@/router'
import { t } from '@/locales'
interface ItemGroup extends Panel.ItemIconGroup {
sortStatus?: boolean
@@ -134,18 +135,18 @@ function handleRightMenuSelect(key: string | number) {
break
case 'delete':
dialog.warning({
title: '警告',
content: `你确定要删除图标 ${currentRightSelectItem.value?.title} ?`,
positiveText: '确定',
negativeText: '取消',
title: t('common.warning'),
content: t('common.deleteConfirmByName', { name: currentRightSelectItem.value?.title }),
positiveText: t('common.confirm'),
negativeText: t('common.cancel'),
onPositiveClick: () => {
deletes([currentRightSelectItem.value?.id as number]).then(({ code, msg }) => {
if (code === 0) {
ms.success('已删除')
ms.success(t('common.deleteSuccess'))
getList()
}
else {
ms.error(`删除失败:${msg}`)
ms.error(`${t('common.deleteFail')}:${msg}`)
}
})
},
@@ -183,10 +184,10 @@ function handleEditSuccess(item: Panel.ItemInfo) {
function handleChangeNetwork(mode: PanelStateNetworkModeEnum) {
panelState.setNetworkMode(mode)
if (mode === PanelStateNetworkModeEnum.lan)
ms.success('已经切换成局域网模式(此配置仅保存在本地)')
ms.success(t('panelHome.changeToLanModelSuccess'))
else
ms.success('已经切换成互联网模式(此配置仅保存在本地)')
ms.success(t('panelHome.changeToWanModelSuccess'))
}
// 结束拖拽
@@ -208,11 +209,11 @@ function handleSaveSort(itemGroup: ItemGroup) {
saveSort({ itemIconGroupId: itemGroup.id as number, sortItems: saveItems }).then(({ code, msg }) => {
if (code === 0) {
ms.success('保存成功')
ms.success(t('common.saveSuccess'))
itemGroup.sortStatus = false
}
else {
ms.error(`保存失败:${msg}`)
ms.error(`${t('common.saveFail')}:${msg}`)
}
})
}
@@ -221,7 +222,7 @@ function handleSaveSort(itemGroup: ItemGroup) {
function getDropdownMenuOptions() {
const dropdownMenuOptions = [
{
label: '新窗口打开',
label: t('iconItem.newWindowOpen'),
key: 'newWindows',
},
@@ -229,24 +230,24 @@ function getDropdownMenuOptions() {
if (currentRightSelectItem.value?.lanUrl && panelState.networkMode === PanelStateNetworkModeEnum.wan) {
dropdownMenuOptions.push({
label: '打开局域网地址',
label: t('panelHome.openLanUrl'),
key: 'openLanUrl',
})
}
if (currentRightSelectItem.value?.lanUrl && panelState.networkMode === PanelStateNetworkModeEnum.lan) {
dropdownMenuOptions.push({
label: '打开互联网地址',
label: t('panelHome.openWanUrl'),
key: 'openWanUrl',
})
}
if (authStore.visitMode === VisitMode.VISIT_MODE_LOGIN) {
dropdownMenuOptions.push({
label: '编辑',
label: t('common.edit'),
key: 'edit',
}, {
label: '删除',
label: t('common.delete'),
key: 'delete',
})
}
@@ -394,10 +395,10 @@ function handleAddItem(itemIconGroupId?: number) {
class="ml-2 delay-100 transition-opacity flex"
:class="itemGroup.hoverStatus ? 'opacity-100' : 'opacity-0'"
>
<span class="mr-2 cursor-pointer" title="添加快捷图标" @click="handleAddItem(itemGroup.id)">
<span class="mr-2 cursor-pointer" :title="t('common.add')" @click="handleAddItem(itemGroup.id)">
<SvgIcon class="text-white font-xl" icon="typcn:plus" />
</span>
<span class="mr-2 cursor-pointer " title="排序组快捷图标" @click="handleSetSortStatus(itemGroupIndex, !itemGroup.sortStatus)">
<span class="mr-2 cursor-pointer " :title="t('common.sort')" @click="handleSetSortStatus(itemGroupIndex, !itemGroup.sortStatus)">
<SvgIcon class="text-white font-xl" icon="ri:drag-drop-line" />
</span>
</div>
@@ -427,7 +428,7 @@ function handleAddItem(itemIconGroupId?: number) {
<div v-if="itemGroup.items.length === 0" class="not-drag">
<AppIcon
:class="itemGroup.sortStatus ? 'cursor-move' : 'cursor-pointer'"
:item-info="{ icon: { itemType: 3, text: 'subway:add' }, title: '添加图标', url: '', openMethod: 0 }"
:item-info="{ icon: { itemType: 3, text: 'subway:add' }, title: t('common.add'), url: '', openMethod: 0 }"
:icon-text-color="panelState.panelConfig.iconTextColor"
:icon-text-info-hide-description="panelState.panelConfig.iconTextInfoHideDescription || false"
:icon-text-icon-hide-title="panelState.panelConfig.iconTextIconHideTitle || false"
@@ -464,7 +465,7 @@ function handleAddItem(itemIconGroupId?: number) {
<div v-if="itemGroup.items.length === 0" class="not-drag">
<AppIcon
class="cursor-pointer"
:item-info="{ icon: { itemType: 3, text: 'subway:add' }, title: '添加图标', url: '', openMethod: 0 }"
:item-info="{ icon: { itemType: 3, text: 'subway:add' }, title: $t('common.add'), url: '', openMethod: 0 }"
:icon-text-color="panelState.panelConfig.iconTextColor"
:icon-text-info-hide-description="!panelState.panelConfig.iconTextInfoHideDescription"
:icon-text-icon-hide-title="panelState.panelConfig.iconTextIconHideTitle || false"
@@ -484,7 +485,7 @@ function handleAddItem(itemIconGroupId?: number) {
<SvgIcon class="text-white font-xl" icon="material-symbols:save" />
</template>
<div>
保存排序
{{ $t('common.saveSort') }}
</div>
</NButton>
</div>
@@ -506,7 +507,7 @@ function handleAddItem(itemIconGroupId?: number) {
<NButtonGroup vertical>
<NButton
v-if="panelState.networkMode === PanelStateNetworkModeEnum.lan" color="#2a2a2a6b"
title="当前:局域网模式,点击切换成互联网模式" @click="handleChangeNetwork(PanelStateNetworkModeEnum.wan)"
:title="t('panelHome.changeToWanModel')" @click="handleChangeNetwork(PanelStateNetworkModeEnum.wan)"
>
<template #icon>
<SvgIcon class="text-white font-xl" icon="material-symbols:lan-outline-rounded" />
@@ -515,7 +516,7 @@ function handleAddItem(itemIconGroupId?: number) {
<NButton
v-if="panelState.networkMode === PanelStateNetworkModeEnum.wan" color="#2a2a2a6b"
title="当前:互联网模式,点击切换成局域网模式" @click="handleChangeNetwork(PanelStateNetworkModeEnum.lan)"
:title="t('panelHome.changeToLanModel')" @click="handleChangeNetwork(PanelStateNetworkModeEnum.lan)"
>
<template #icon>
<SvgIcon class="text-white font-xl" icon="mdi:wan" />
@@ -528,7 +529,7 @@ function handleAddItem(itemIconGroupId?: number) {
</template>
</NButton>
<NButton v-if="authStore.visitMode === VisitMode.VISIT_MODE_PUBLIC" color="#2a2a2a6b" title="登录" @click="router.push('/login')">
<NButton v-if="authStore.visitMode === VisitMode.VISIT_MODE_PUBLIC" color="#2a2a2a6b" :title="t('panelHome.goToLogin')" @click="router.push('/login')">
<template #icon>
<SvgIcon class="text-white font-xl" icon="material-symbols:account-circle" />
</template>
+21 -2
View File
@@ -1,16 +1,21 @@
<script setup lang="ts">
import { NButton, NCard, NForm, NFormItem, NGradientText, NInput, useMessage } from 'naive-ui'
import { NButton, NCard, NForm, NFormItem, NGradientText, NInput, NSelect, useMessage } from 'naive-ui'
import { ref } from 'vue'
import { login } from '@/api'
import { useAuthStore } from '@/store'
import { useAppStore, useAuthStore } from '@/store'
import { SvgIcon } from '@/components/common'
import { router } from '@/router'
import { t } from '@/locales'
import { languageOptions } from '@/utils/defaultData'
import type { Language } from '@/store/modules/app/helper'
// const userStore = useUserStore()
const authStore = useAuthStore()
const appStore = useAppStore()
const ms = useMessage()
const loading = ref(false)
const languageValue = ref<Language>(appStore.language)
// const isShowCaptcha = ref<boolean>(false)
// const isShowRegister = ref<boolean>(false)
@@ -45,11 +50,25 @@ function handleSubmit() {
// 点击登录按钮触发
loginPost()
}
function handleChangeLanuage(value: Language) {
languageValue.value = value
appStore.setLanguage(value)
}
</script>
<template>
<div class="login-container">
<NCard class="login-card" style="border-radius: 20px;">
<div class="mb-5 flex items-center justify-end">
<div class="mr-2">
<SvgIcon icon="ion-language" style="width: 20;height: 20;" />
</div>
<div class="min-w-[100px]">
<NSelect v-model:value="languageValue" size="small" :options="languageOptions" @update-value="handleChangeLanuage" />
</div>
</div>
<div class="login-title ">
<NGradientText :size="30" type="success" class="!font-bold">
{{ $t('common.appName') }}