feat(I18n): Add Ii8n
feat(LocaleDropdown): Add LocaleDropdown Component feat(store): Add localeStoremaster
parent
45d657d44c
commit
3810b8c3b2
@ -0,0 +1,19 @@
|
||||
{
|
||||
"0 debug pnpm:scope": {
|
||||
"selected": 1
|
||||
},
|
||||
"1 error pnpm": {
|
||||
"errno": 1,
|
||||
"code": "ELIFECYCLE",
|
||||
"pkgid": "butterfly-admin@3.0.0",
|
||||
"stage": "clean",
|
||||
"script": "npx rimraf docs/node_modules && npx rimraf node_modules",
|
||||
"pkgname": "butterfly-admin",
|
||||
"err": {
|
||||
"name": "pnpm",
|
||||
"message": "butterfly-admin@3.0.0 clean: `npx rimraf docs/node_modules && npx rimraf node_modules`\nExit status 1",
|
||||
"code": "ELIFECYCLE",
|
||||
"stack": "pnpm: butterfly-admin@3.0.0 clean: `npx rimraf docs/node_modules && npx rimraf node_modules`\nExit status 1\n at EventEmitter.<anonymous> (C:\\Users\\Saber\\AppData\\Roaming\\npm\\pnpm-global\\5\\node_modules\\.pnpm\\registry.npmmirror.com+pnpm@6.24.4\\node_modules\\pnpm\\dist\\pnpm.cjs:103873:20)\n at EventEmitter.emit (node:events:365:28)\n at ChildProcess.<anonymous> (C:\\Users\\Saber\\AppData\\Roaming\\npm\\pnpm-global\\5\\node_modules\\.pnpm\\registry.npmmirror.com+pnpm@6.24.4\\node_modules\\pnpm\\dist\\pnpm.cjs:91802:18)\n at ChildProcess.emit (node:events:365:28)\n at maybeClose (node:internal/child_process:1067:16)\n at Process.ChildProcess._handle.onexit (node:internal/child_process:301:5)"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.147.062a13 13 0 0 1 4.94.945c1.55.63 2.907 1.526 4.069 2.688a13.148 13.148 0 0 1 2.761 4.069c.678 1.55 1.017 3.245 1.017 5.086v102.3c0 3.681-1.187 6.733-3.56 9.155-2.373 2.422-5.352 3.633-8.937 3.633H12.992c-3.875 0-7-1.26-9.373-3.779-2.373-2.518-3.56-5.667-3.56-9.445V12.704c0-3.39 1.163-6.345 3.488-8.863C5.872 1.32 8.972.062 12.847.062h102.3zM81.434 109.047c1.744 0 3.003-.412 3.778-1.235.775-.824 1.163-1.914 1.163-3.27 0-1.26-.388-2.325-1.163-3.197-.775-.872-2.034-1.307-3.778-1.307H72.57c.097-.194.145-.485.145-.872V27.09h9.01c1.743 0 2.954-.436 3.633-1.308.678-.872 1.017-1.938 1.017-3.197 0-1.26-.34-2.325-1.017-3.197-.679-.872-1.89-1.308-3.633-1.308H46.268c-1.743 0-2.954.436-3.632 1.308-.678.872-1.018 1.938-1.018 3.197 0 1.26.34 2.325 1.018 3.197.678.872 1.889 1.308 3.632 1.308h8.138v72.075c0 .193.024.339.073.436.048.096.072.242.072.436H46.56c-1.744 0-3.003.435-3.778 1.307-.775.872-1.163 1.938-1.163 3.197 0 1.356.388 2.446 1.163 3.27.775.823 2.034 1.235 3.778 1.235h34.875z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -1,3 +1,3 @@
|
||||
import VConfigGlobal from './src/VConfigGlobal.vue'
|
||||
import ConfigGlobal from './src/ConfigGlobal.vue'
|
||||
|
||||
export { VConfigGlobal }
|
||||
export { ConfigGlobal }
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
import Icon from './src/Icon.vue'
|
||||
|
||||
export { Icon }
|
||||
@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, unref, ref, watch, nextTick } from 'vue'
|
||||
import { ElIcon } from 'element-plus'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import Iconify from '@purge-icons/generated'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('icon')
|
||||
|
||||
const props = defineProps({
|
||||
// icon name
|
||||
icon: propTypes.string,
|
||||
// icon color
|
||||
color: propTypes.string,
|
||||
// icon size
|
||||
size: propTypes.number.def(16)
|
||||
})
|
||||
|
||||
const elRef = ref<ElRef>(null)
|
||||
|
||||
const isLocal = computed(() => props.icon.startsWith('icon:'))
|
||||
|
||||
const symbolId = computed(() => {
|
||||
return unref(isLocal) ? `#icon-${props.icon.split('icon:')[1]}` : props.icon
|
||||
})
|
||||
|
||||
const getIconifyStyle = computed(() => {
|
||||
const { color, size } = props
|
||||
return {
|
||||
fontSize: `${size}px`,
|
||||
color
|
||||
}
|
||||
})
|
||||
|
||||
async function updateIcon(icon: string) {
|
||||
if (unref(isLocal)) return
|
||||
|
||||
const el = unref(elRef)
|
||||
if (!el) return
|
||||
|
||||
await nextTick()
|
||||
|
||||
if (!icon) return
|
||||
|
||||
const svg = Iconify.renderSVG(icon, {})
|
||||
if (svg) {
|
||||
el.textContent = ''
|
||||
el.appendChild(svg)
|
||||
} else {
|
||||
const span = document.createElement('span')
|
||||
span.className = 'iconify'
|
||||
span.dataset.icon = icon
|
||||
el.textContent = ''
|
||||
el.appendChild(span)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.icon,
|
||||
(icon: string) => {
|
||||
updateIcon(icon)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElIcon :class="[prefixCls, $attrs.class]" :size="size" :color="color">
|
||||
<svg v-if="isLocal" aria-hidden="true">
|
||||
<use :xlink:href="symbolId" />
|
||||
</svg>
|
||||
|
||||
<span v-else ref="elRef" :style="getIconifyStyle">
|
||||
<span class="iconify" :data-icon="symbolId"></span>
|
||||
</span>
|
||||
</ElIcon>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-icon';
|
||||
|
||||
.@{prefix-cls} {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@ -1,3 +1,3 @@
|
||||
import VInputPassword from './src/VInputPassword.vue'
|
||||
import InputPassword from './src/InputPassword.vue'
|
||||
|
||||
export { VInputPassword }
|
||||
export { InputPassword }
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
import VLocaleDropdown from './src/VLocaleDropdown.vue'
|
||||
import LocaleDropdown from './src/LocaleDropdown.vue'
|
||||
|
||||
export { VLocaleDropdown }
|
||||
export { LocaleDropdown }
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
|
||||
import { useLocaleStore } from '@/store/modules/locale'
|
||||
import { useCssVar } from '@vueuse/core'
|
||||
import { useLocale } from '@/hooks/web/useLocale'
|
||||
|
||||
const localeStore = useLocaleStore()
|
||||
|
||||
const langMap = computed(() => localeStore.localeMap)
|
||||
|
||||
const textColor = useCssVar('--el-text-color-primary', document.documentElement)
|
||||
|
||||
function setLang(lang: LocaleType) {
|
||||
localeStore.setLocale({
|
||||
lang
|
||||
})
|
||||
const { changeLocale } = useLocale()
|
||||
changeLocale(lang)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDropdown trigger="click" @command="setLang">
|
||||
<Icon icon="ion:language-sharp" :color="textColor" class="cursor-pointer" />
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem v-for="item in langMap" :key="item.lang" :command="item.lang">
|
||||
{{ item.name }}
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
</template>
|
||||
@ -1,19 +0,0 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>s</div>
|
||||
<!-- <el-dropdown trigger="click">
|
||||
<span class="el-dropdown-link">
|
||||
Dropdown List<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item :icon="Plus">Action 1</el-dropdown-item>
|
||||
<el-dropdown-item :icon="CirclePlusFilled"> Action 2 </el-dropdown-item>
|
||||
<el-dropdown-item :icon="CirclePlus">Action 3</el-dropdown-item>
|
||||
<el-dropdown-item :icon="Check">Action 4</el-dropdown-item>
|
||||
<el-dropdown-item :icon="CircleCheck">Action 5</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown> -->
|
||||
</template>
|
||||
@ -1,3 +1,3 @@
|
||||
import VThemeSwitch from './src/VThemeSwitch.vue'
|
||||
import ThemeSwitch from './src/ThemeSwitch.vue'
|
||||
|
||||
export { VThemeSwitch }
|
||||
export { ThemeSwitch }
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import Sun from '~icons/emojione-monotone/sun'
|
||||
import CrescentMoon from '~icons/emojione-monotone/crescent-moon'
|
||||
import { ElSwitch } from 'element-plus'
|
||||
import { useCssVar } from '@vueuse/core'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
|
||||
const Sun = useIcon({ icon: 'emojione-monotone:sun' })
|
||||
const CrescentMoon = useIcon({ icon: 'emojione-monotone:crescent-moon' })
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import type { App } from 'vue'
|
||||
import { Icon } from './Icon'
|
||||
|
||||
export function setupGlobCom(app: App<Element>): void {
|
||||
app.component('Icon', Icon)
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
import { h } from 'vue'
|
||||
import type { VNode } from 'vue'
|
||||
import { Icon } from '@/components/Icon'
|
||||
|
||||
export function useIcon(props: IconTypes): VNode {
|
||||
return h(Icon, props)
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
import { i18n } from '@/plugins/i18n'
|
||||
import { useLocaleStoreWithOut } from '@/store/modules/locale'
|
||||
import { setHtmlPageLang } from '@/plugins/i18n/helper'
|
||||
|
||||
function setI18nLanguage(locale: LocaleType) {
|
||||
const localeStore = useLocaleStoreWithOut()
|
||||
|
||||
if (i18n.mode === 'legacy') {
|
||||
i18n.global.locale = locale
|
||||
} else {
|
||||
;(i18n.global.locale as any).value = locale
|
||||
}
|
||||
localeStore.setLocale({
|
||||
lang: locale
|
||||
})
|
||||
setHtmlPageLang(locale)
|
||||
}
|
||||
|
||||
export function useLocale() {
|
||||
// Switching the language will change the locale of useI18n
|
||||
// And submit to configuration modification
|
||||
async function changeLocale(locale: LocaleType) {
|
||||
const globalI18n = i18n.global
|
||||
|
||||
const langModule = (await import(`../../locales/${locale}.ts`)) as any
|
||||
|
||||
globalI18n.setLocaleMessage(locale, langModule.default)
|
||||
|
||||
setI18nLanguage(locale)
|
||||
}
|
||||
|
||||
return {
|
||||
changeLocale
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,42 @@
|
||||
// 引入windi css
|
||||
import '@/plugins/windicss'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
const app = createApp(App)
|
||||
// 导入全局的svg图标
|
||||
import '@/plugins/svgIcon'
|
||||
|
||||
// 引入element-plus
|
||||
import { setupElementPlus } from '@/plugins/elementPlus'
|
||||
setupElementPlus(app)
|
||||
// 初始化多语言
|
||||
import { setupI18n } from '@/plugins/i18n'
|
||||
|
||||
// 引入状态管理
|
||||
import { setupStore } from '@/store'
|
||||
setupStore(app)
|
||||
|
||||
// 全局组件
|
||||
import { setupGlobCom } from '@/components'
|
||||
|
||||
// 引入element-plus
|
||||
import { setupElementPlus } from '@/plugins/elementPlus'
|
||||
|
||||
// 路由
|
||||
import router, { setupRouter } from './router'
|
||||
setupRouter(app)
|
||||
import { setupRouter } from './router'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import App from './App.vue'
|
||||
|
||||
async function setupAll() {
|
||||
const app = createApp(App)
|
||||
|
||||
await setupI18n(app)
|
||||
|
||||
setupStore(app)
|
||||
|
||||
setupGlobCom(app)
|
||||
|
||||
setupElementPlus(app)
|
||||
|
||||
setupRouter(app)
|
||||
|
||||
router.isReady().then(() => {
|
||||
app.mount('#app')
|
||||
})
|
||||
}
|
||||
|
||||
setupAll()
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export function setHtmlPageLang(locale: LocaleType) {
|
||||
document.querySelector('html')?.setAttribute('lang', locale)
|
||||
}
|
||||
@ -1,16 +1,42 @@
|
||||
import type { App } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import { useLocaleStoreWithOut } from '@/store/modules/locale'
|
||||
import type { I18n, I18nOptions } from 'vue-i18n'
|
||||
import { setHtmlPageLang } from './helper'
|
||||
|
||||
export const i18n = setupI18n()
|
||||
export let i18n: ReturnType<typeof createI18n>
|
||||
|
||||
function setupI18n() {
|
||||
const messages = Object.fromEntries(
|
||||
Object.entries(import.meta.globEager('../../locales/*.ts')).map(([key, value]) => {
|
||||
return [key.slice(14, -3), value.default]
|
||||
})
|
||||
)
|
||||
return createI18n({
|
||||
legacy: false,
|
||||
locale: 'zh-CN',
|
||||
messages
|
||||
async function createI18nOptions(): Promise<I18nOptions> {
|
||||
const localeStore = useLocaleStoreWithOut()
|
||||
const locale = localeStore.getLocale
|
||||
const localeMap = localeStore.getLocaleMap
|
||||
const defaultLocal = await import(`../../locales/${locale.lang}.ts`)
|
||||
const message = defaultLocal.default ?? {}
|
||||
|
||||
setHtmlPageLang(locale.lang)
|
||||
|
||||
localeStore.setLocale({
|
||||
lang: locale.lang
|
||||
// elLocale: elLocal
|
||||
})
|
||||
|
||||
return {
|
||||
legacy: false,
|
||||
locale: locale.lang,
|
||||
fallbackLocale: locale.lang,
|
||||
messages: {
|
||||
[locale.lang]: message
|
||||
},
|
||||
availableLocales: localeMap.map((v) => v.lang),
|
||||
sync: true,
|
||||
silentTranslationWarn: true,
|
||||
missingWarn: false,
|
||||
silentFallbackWarn: true
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupI18n(app: App) {
|
||||
const options = await createI18nOptions()
|
||||
i18n = createI18n(options) as I18n
|
||||
app.use(i18n)
|
||||
}
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
import 'virtual:svg-icons-register'
|
||||
|
||||
import '@purge-icons/generated'
|
||||
@ -0,0 +1,55 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
|
||||
import en from 'element-plus/lib/locale/lang/en'
|
||||
import { store } from '../index'
|
||||
|
||||
const elLocaleMap = {
|
||||
'zh-CN': zhCn,
|
||||
en: en
|
||||
}
|
||||
|
||||
export interface LocaleState {
|
||||
locale: LocaleDropdownType
|
||||
localeMap: LocaleDropdownType[]
|
||||
}
|
||||
|
||||
export const useLocaleStore = defineStore({
|
||||
id: 'locales',
|
||||
state: (): LocaleState => ({
|
||||
// 当前语言
|
||||
locale: {
|
||||
lang: 'zh-CN',
|
||||
elLocale: elLocaleMap['zh-CN']
|
||||
},
|
||||
// 多语言
|
||||
localeMap: [
|
||||
{
|
||||
lang: 'zh-CN',
|
||||
name: '简体中文'
|
||||
},
|
||||
{
|
||||
lang: 'en',
|
||||
name: 'English'
|
||||
}
|
||||
]
|
||||
}),
|
||||
getters: {
|
||||
getLocale(): LocaleDropdownType {
|
||||
return this.locale
|
||||
},
|
||||
getLocaleMap(): LocaleDropdownType[] {
|
||||
return this.localeMap
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setLocale(localeMap: LocaleDropdownType) {
|
||||
// this.locale = Object.assign(this.locale, localeMap)
|
||||
this.locale.lang = localeMap?.lang
|
||||
this.locale.elLocale = elLocaleMap[localeMap?.lang]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export function useLocaleStoreWithOut() {
|
||||
return useLocaleStore(store)
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
.text-color {
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
.dark .dark\:text-color {
|
||||
color: rgba(255, 255, 255, var(--dark-text-color));
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
:root {
|
||||
--dark-text-color: #c9d1d9;
|
||||
}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Icon: typeof import('../components/Icon/src/Icon.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
Loading…
Reference in New Issue