vue3+ts项目04-国际化

yarn add vue-i18n
yarn add js-cookie
yarn add @types/js-cookie

src下新建i18n文件夹,该文件夹下新建lang和pages文件夹,
lang文件夹下新建en.ts

// 定义内容
export default {
	router: {
		home: 'home',
		system: {
			system: 'system',
			menu: 'systemMenu',
			role: 'systemRole',
			user: 'systemUser',
			dic: 'systemDic',
			city: 'systemDept'
		},
		personal: 'personal',
	},
	staticRoutes: {
		signIn: 'signIn',
		notFound: 'notFound',
		noPower: 'noPower',
	},
	user: {
		title0: 'Component size',
		title1: 'Language switching',
		title2: 'Menu search',
		title3: 'Layout configuration',
		title4: 'news',
		title5: 'Full screen on',
		title6: 'Full screen off',
		dropdownLarge: 'large',
		dropdownDefault: 'default',
		dropdownSmall: 'small',
		dropdown1: 'home page',
		dropdown2: 'Personal Center',
		dropdown3: '404',
		dropdown4: '401',
		dropdown5: 'Log out',
		dropdown6: 'Code warehouse',
		searchPlaceholder: 'Menu search: support Chinese, routing path',
		newTitle: 'notice',
		newBtn: 'All read',
		newGo: 'Go to the notification center',
		newDesc: 'No notice',
		logOutTitle: 'Tips',
		logOutMessage: 'This operation will log out. Do you want to continue?',
		logOutConfirm: 'determine',
		logOutCancel: 'cancel',
		logOutExit: 'Exiting',
	},
	tagsView: {
		refresh: 'refresh',
		close: 'close',
		closeOther: 'closeOther',
		closeAll: 'closeAll',
		fullscreen: 'fullscreen',
		closeFullscreen: 'closeFullscreen',
	},
	notFound: {
		foundTitle: 'Wrong address input, please re-enter the address~',
		foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
		foundBtn: 'Back to home page',
	},
	noAccess: {
		accessTitle: 'You are not authorized to operate~',
		accessMsg: 'Contact information: add QQ group discussion 665452019',
		accessBtn: 'Reauthorization',
	},
	layout: {
		configTitle: 'Layout configuration',
		oneTitle: 'Global Themes',
		twoTopTitle: 'top bar set up',
		twoMenuTitle: 'Menu set up',
		twoColumnsTitle: 'Columns set up',
		twoTopBar: 'Top bar background',
		twoTopBarColor: 'Top bar default font color',
		twoIsTopBarColorGradual: 'Top bar gradient',
		twoMenuBar: 'Menu background',
		twoMenuBarColor: 'Menu default font color',
		twoIsMenuBarColorGradual: 'Menu gradient',
		twoColumnsMenuBar: 'Column menu background',
		twoColumnsMenuBarColor: 'Default font color bar menu',
		twoIsColumnsMenuBarColorGradual: 'Column gradient',
		twoIsColumnsMenuHoverPreload: 'Column Menu Hover Preload',
		threeTitle: 'Interface settings',
		threeIsCollapse: 'Menu horizontal collapse',
		threeIsUniqueOpened: 'Menu accordion',
		threeIsFixedHeader: 'Fixed header',
		threeIsClassicSplitMenu: 'Classic layout split menu',
		threeIsLockScreen: 'Open the lock screen',
		threeLockScreenTime: 'screen locking(s/s)',
		fourTitle: 'Interface display',
		fourIsShowLogo: 'Sidebar logo',
		fourIsBreadcrumb: 'Open breadcrumb',
		fourIsBreadcrumbIcon: 'Open breadcrumb icon',
		fourIsTagsview: 'Open tagsview',
		fourIsTagsviewIcon: 'Open tagsview Icon',
		fourIsCacheTagsView: 'Enable tagsview cache',
		fourIsSortableTagsView: 'Enable tagsview drag',
		fourIsShareTagsView: 'Enable tagsview sharing',
		fourIsFooter: 'Open footer',
		fourIsGrayscale: 'Grey model',
		fourIsInvert: 'Color weak mode',
		fourIsDark: 'Dark Mode',
		fourIsWartermark: 'Turn on watermark',
		fourWartermarkText: 'Watermark copy',
		fiveTitle: 'Other settings',
		fiveTagsStyle: 'Tagsview style',
		fiveAnimation: 'page animation',
		fiveColumnsAsideStyle: 'Column style',
		fiveColumnsAsideLayout: 'Column layout',
		sixTitle: 'Layout switch',
		sixDefaults: 'One',
		sixClassic: 'Two',
		sixTransverse: 'Three',
		sixColumns: 'Four',
		tipText: 'Click the button below to copy the layout configuration to `/src/stores/themeConfig.ts` It has been modified in.',
		copyText: 'replication configuration',
		resetText: 'restore default',
		copyTextSuccess: 'Copy succeeded!',
		copyTextError: 'Copy failed!',
	},
};

lang文件夹下新建zh-cn.ts

// 定义内容
export default {
	router: {
		home: '首页',
		system: {
			system: '系统设置',
			menu: '菜单管理',
			role: '角色管理',
			user: '用户管理',
			dic: '字典管理',
			city: '城市管理'
		},
		personal: '个人中心',
	},
	staticRoutes: {
		signIn: '登录',
		notFound: '找不到此页面',
		noPower: '没有权限',
	},
	user: {
		title0: '组件大小',
		title1: '语言切换',
		title2: '菜单搜索',
		title3: '布局配置',
		title4: '消息',
		title5: '开全屏',
		title6: '关全屏',
		dropdownLarge: '大型',
		dropdownDefault: '默认',
		dropdownSmall: '小型',
		dropdown1: '首页',
		dropdown2: '个人中心',
		dropdown3: '404',
		dropdown4: '401',
		dropdown5: '退出登录',
		dropdown6: '代码仓库',
		searchPlaceholder: '菜单搜索:支持中文、路由路径',
		newTitle: '通知',
		newBtn: '全部已读',
		newGo: '前往通知中心',
		newDesc: '暂无通知',
		logOutTitle: '提示',
		logOutMessage: '此操作将退出登录, 是否继续?',
		logOutConfirm: '确定',
		logOutCancel: '取消',
		logOutExit: '退出中',
	},
	tagsView: {
		refresh: '刷新',
		close: '关闭',
		closeOther: '关闭其它',
		closeAll: '全部关闭',
		fullscreen: '当前页全屏',
		closeFullscreen: '关闭全屏',
	},
	notFound: {
		foundTitle: '地址输入错误,请重新输入地址~',
		foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
		foundBtn: '返回首页',
	},
	noAccess: {
		accessTitle: '您未被授权,没有操作权限~',
		accessMsg: '联系方式:加QQ群探讨 665452019',
		accessBtn: '重新授权',
	},
	layout: {
		configTitle: '布局配置',
		oneTitle: '全局主题',
		twoTopTitle: '顶栏设置',
		twoMenuTitle: '菜单设置',
		twoColumnsTitle: '分栏设置',
		twoTopBar: '顶栏背景',
		twoTopBarColor: '顶栏默认字体颜色',
		twoIsTopBarColorGradual: '顶栏背景渐变',
		twoMenuBar: '菜单背景',
		twoMenuBarColor: '菜单默认字体颜色',
		twoIsMenuBarColorGradual: '菜单背景渐变',
		twoColumnsMenuBar: '分栏菜单背景',
		twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
		twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
		twoIsColumnsMenuHoverPreload: '分栏菜单鼠标悬停预加载',
		threeTitle: '界面设置',
		threeIsCollapse: '菜单水平折叠',
		threeIsUniqueOpened: '菜单手风琴',
		threeIsFixedHeader: '固定 Header',
		threeIsClassicSplitMenu: '经典布局分割菜单',
		threeIsLockScreen: '开启锁屏',
		threeLockScreenTime: '自动锁屏(s/秒)',
		fourTitle: '界面显示',
		fourIsShowLogo: '侧边栏 Logo',
		fourIsBreadcrumb: '开启 Breadcrumb',
		fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
		fourIsTagsview: '开启 Tagsview',
		fourIsTagsviewIcon: '开启 Tagsview 图标',
		fourIsCacheTagsView: '开启 TagsView 缓存',
		fourIsSortableTagsView: '开启 TagsView 拖拽',
		fourIsShareTagsView: '开启 TagsView 共用',
		fourIsFooter: '开启 Footer',
		fourIsGrayscale: '灰色模式',
		fourIsInvert: '色弱模式',
		fourIsDark: '深色模式',
		fourIsWartermark: '开启水印',
		fourWartermarkText: '水印文案',
		fiveTitle: '其它设置',
		fiveTagsStyle: 'Tagsview 风格',
		fiveAnimation: '主页面切换动画',
		fiveColumnsAsideStyle: '分栏高亮风格',
		fiveColumnsAsideLayout: '分栏布局风格',
		sixTitle: '布局切换',
		sixDefaults: '默认',
		sixClassic: '经典',
		sixTransverse: '横向',
		sixColumns: '分栏',
		tipText: '点击下方按钮,复制布局配置去 `src/stores/themeConfig.ts` 中修改。',
		copyText: '一键复制配置',
		resetText: '一键恢复默认',
		copyTextSuccess: '复制成功!',
		copyTextError: '复制失败!',
	},
};

pages下新建login文件夹,该文件下新建en.ts和zh-cn.ts

// 定义内容
export default {
	label: {
		one1: 'User name login',
		two2: 'Mobile number',
	},
	link: {
		one3: 'Third party login',
		two4: 'Links',
	},
	account: {
		accountPlaceholder1: 'The user name admin or not is common',
		accountPlaceholder2: 'Password: 123456',
		accountPlaceholder3: 'Please enter the verification code',
		accountBtnText: 'Sign in',
	},
	mobile: {
		placeholder1: 'Please input mobile phone number',
		placeholder2: 'Please enter the verification code',
		codeText: 'Get code',
		btnText: 'Sign in',
		msgText:
			'Warm tip: it is recommended to use Google, Microsoft edge, version 79.0.1072.62 and above browsers, and 360 browser, please use speed mode',
	},
	scan: {
		text: 'Open the mobile phone to scan and quickly log in / register',
	},
	signInText: 'welcome back!',
};

zh-cn.ts

// 定义内容
export default {
	label: {
		one1: '用户名登录',
		two2: '手机号登录',
	},
	link: {
		one3: '第三方登录',
		two4: '友情链接',
	},
	account: {
		accountPlaceholder1: '用户名 admin 或不输均为 common',
		accountPlaceholder2: '密码:123456',
		accountPlaceholder3: '请输入验证码',
		accountBtnText: '登 录',
	},
	mobile: {
		placeholder1: '请输入手机号',
		placeholder2: '请输入验证码',
		codeText: '获取验证码',
		btnText: '登 录',
		msgText: '* 温馨提示:建议使用谷歌、Microsoft Edge,版本 79.0.1072.62 及以上浏览器,360浏览器请使用极速模式',
	},
	scan: {
		text: '打开手机扫一扫,快速登录/注册',
	},
	signInText: '欢迎回来!',
};

stores下interface下新建index.ts

//定义接口来定义对象的类型 `stores` 全部类型定义在这里

/**
 * 用户信息
 */
export interface UserInfosState {
	authBtnList: string[];
	photo: string;
	time: number;
	userName: string;
	roleId: number;
}
export interface UserInfosStates {
	userInfos: UserInfosState;
}

/**
 * 路由缓存列表
 */
export interface KeepAliveNamesState {
	keepAliveNames: string[];
	cachedViews: string[];
}

/**
 * 后端返回原始路由(未处理时)
 */
export interface RequestOldRoutesState {
	requestOldRoutes: string[];
}

/**
 * TagsView 路由列表
 */
export interface TagsViewRoutesState {
	tagsViewRoutes: string[];
	isTagsViewCurrenFull: Boolean;
}

/**
 * 路由列表
 */
export interface RoutesListState {
	routesList: string[];
	isColumnsMenuHover: Boolean;
	isColumnsNavHover: Boolean;
}

/**
 * 布局配置
 */
export interface ThemeConfigState {
	passwordKey: string;
	isDrawer: boolean;
	primary: string;
	topBar: string;
	topBarColor: string;
	isTopBarColorGradual: boolean;
	menuBar: string;
	menuBarColor: string;
	isMenuBarColorGradual: boolean;
	columnsMenuBar: string;
	columnsMenuBarColor: string;
	isColumnsMenuBarColorGradual: boolean;
	isColumnsMenuHoverPreload: boolean;
	isCollapse: boolean;
	isUniqueOpened: boolean;
	isFixedHeader: boolean;
	isFixedHeaderChange: boolean;
	isClassicSplitMenu: boolean;
	isLockScreen: boolean;
	lockScreenTime: number;
	isShowLogo: boolean;
	isShowLogoChange: boolean;
	isBreadcrumb: boolean;
	isTagsview: boolean;
	isBreadcrumbIcon: boolean;
	isTagsviewIcon: boolean;
	isCacheTagsView: boolean;
	isSortableTagsView: boolean;
	isShareTagsView: boolean;
	isFooter: boolean;
	isGrayscale: boolean;
	isInvert: boolean;
	isIsDark: boolean;
	isWartermark: boolean;
	wartermarkText: string;
	tagsStyle: string;
	animation: string;
	columnsAsideStyle: string;
	columnsAsideLayout: string;
	layout: string;
	isRequestRoutes: boolean;
	globalTitle: string;
	globalViceTitle: string;
	globalViceTitleMsg: string;
	globalI18n: string;
	globalComponentSize: string;
}

/**
 * 布局配置
 */
export interface ThemeConfigStates {
	themeConfig: ThemeConfigState;
}

stores下新建themeConfig.ts

import { defineStore } from 'pinia';
import { ThemeConfigStates, ThemeConfigState } from './interface';

export const useThemeConfig = defineStore('themeConfig', {
	state: (): ThemeConfigStates => ({
		themeConfig: {
			//密码加密盐
			passwordKey: `4vRk^ga52xVP$B2vYK$%r8a8hctLgbU9`,
			isDrawer: false,
			primary: '#409eff',
			isIsDark: false,
			topBar: '#ffffff',
			topBarColor: '#606266',
			isTopBarColorGradual: false,
			menuBar: '#545c64',
			menuBarColor: '#eaeaea',
			isMenuBarColorGradual: false,
			columnsMenuBar: '#545c64',
			columnsMenuBarColor: '#e6e6e6',
			isColumnsMenuBarColorGradual: false,
			isColumnsMenuHoverPreload: false,
			isCollapse: false,
			isUniqueOpened: true,
			isFixedHeader: false,
			isFixedHeaderChange: false,
			isClassicSplitMenu: false,
			isLockScreen: false,
			lockScreenTime: 30,
			isShowLogo: true,
			isShowLogoChange: false,
			isBreadcrumb: true,
			isTagsview: true,
			isBreadcrumbIcon: true,
			isTagsviewIcon: true,
			isCacheTagsView: false,
			isSortableTagsView: true,
			isShareTagsView: false,
			isFooter: true,
			isGrayscale: false,
			isInvert: false,
			isWartermark: false,
			wartermarkText: 'vue-next-admin',
			tagsStyle: 'tags-style-five',
			animation: 'opacitys',
			columnsAsideStyle: 'columns-round',
			columnsAsideLayout: 'columns-vertical',
			layout: 'defaults',
			isRequestRoutes: true,
			globalTitle: '甜蜜蜜开放平台',
			globalViceTitle: '甜蜜蜜开放平台',
			globalViceTitleMsg: '专注、免费、开源、维护、解疑',
			globalI18n: 'zh-cn',
			globalComponentSize: 'large',
		},
	}),
	actions: {
		setThemeConfig(data: ThemeConfigState) {
			this.themeConfig = data;
		},
	},
});

i18n文件下新建index.ts

import { createI18n } from 'vue-i18n';
import pinia from '@/stores/index';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/stores/themeConfig';
import zhcnLocale from 'element-plus/es/locale/lang/zh-cn';
import enLocale from 'element-plus/es/locale/lang/en';

import nextZhcn from '@/i18n/lang/zh-cn';
import nextEn from '@/i18n/lang/en';

import pagesLoginZhcn from '@/i18n/pages/login/zh-cn';
import pagesLoginEn from '@/i18n/pages/login/en';

// 定义语言国际化内容
/**
 * 说明:
 * /src/i18n/lang 下的 ts 为框架的国际化内容
 * /src/i18n/pages 下的 ts 为各页面的国际化内容
 */
const messages = {
	[zhcnLocale.name]: {
		...zhcnLocale,
		message: {
			...nextZhcn,
			...pagesLoginZhcn,
		},
	},
	[enLocale.name]: {
		...enLocale,
		message: {
			...nextEn,
			...pagesLoginEn,
		},
	},
};

// 读取 pinia 默认语言
const stores = useThemeConfig(pinia);
const { themeConfig } = storeToRefs(stores);

// 导出语言国际化
//https://vue-i18n.intlify.dev/
// https://vue-i18n.intlify.dev/guide/essentials/fallback.html#explicit-fallback-with-one-locale
export const i18n = createI18n({
	//是否在您的 Vue 应用程序上使用 vue-i18n Legacy API 模式,默认使用 Legacy API 模式。如果要使用Composition API模式,需要将其设置为false。
	legacy: false,
	//是否抑制本地化失败时输出的警告。如果为 true,则抑制本地化失败警告。如果您使用正则表达式,则可以抑制与翻译键(例如 t)匹配的本地化失败警告。
	silentTranslationWarn: true,
	//丢失的警告
	missingWarn: false,
	//当您的语言缺少键的翻译时,是否对翻译键进行模板插值。如果为 true,则跳过为您的“基本”语言编写模板;key是你的模板。
	silentFallbackWarn: true,
	//失败时的警告
	fallbackWarn: false,
	//当前语言,本地化的语言环境
	locale: themeConfig.value.globalI18n,
	//都失败的情况下使用的语言,此 VueI18n 实例正在使用的当前后备区域设置。
	fallbackLocale: zhcnLocale.name,
	messages,
});

utils下新建storage.ts

import Cookies from 'js-cookie';

/**
 * window.localStorage 浏览器永久缓存
 * @method set 设置永久缓存
 * @method get 获取永久缓存
 * @method remove 移除永久缓存
 * @method clear 移除全部永久缓存
 */
export const Local = {
	// 设置永久缓存
	set(key: string, val: any) {
		window.localStorage.setItem(key, JSON.stringify(val));
	},
	// 获取永久缓存
	get(key: string) {
		const json: any = window.localStorage.getItem(key);
		return JSON.parse(json);
	},
	// 移除永久缓存
	remove(key: string) {
		window.localStorage.removeItem(key);
	},
	// 移除全部永久缓存
	clear() {
		window.localStorage.clear();
	},
};

/**
 * window.sessionStorage 浏览器临时缓存
 * @method set 设置临时缓存
 * @method get 获取临时缓存
 * @method remove 移除临时缓存
 * @method clear 移除全部临时缓存
 */
export const Session = {
	// 设置临时缓存
	set(key: string, val: any) {
		if (key === 'token') return Cookies.set(key, val);
		window.sessionStorage.setItem(key, JSON.stringify(val));
	},
	// 获取临时缓存
	get(key: string) {
		if (key === 'token') return Cookies.get(key);
		const json: any = window.sessionStorage.getItem(key);
		return JSON.parse(json);
	},
	// 移除临时缓存
	remove(key: string) {
		if (key === 'token') return Cookies.remove(key);
		window.sessionStorage.removeItem(key);
	},
	// 移除全部临时缓存
	clear() {
		Cookies.remove('token');
		window.sessionStorage.clear();
	},
};

components下新建svgIcon该文件夹下新建index.vue

<template>
	<i v-if="isShowIconSvg" class="el-icon" :style="setIconSvgStyle">
		<component :is="getIconName" />
	</i>
	<div v-else-if="isShowIconImg" :style="setIconImgOutStyle">
		<img :src="getIconName" :style="setIconSvgInsStyle" />
	</div>
	<i v-else :class="getIconName" :style="setIconSvgStyle" />
</template>

<script lang="ts">
import { computed, defineComponent } from 'vue';

export default defineComponent({
	name: 'svgIcon',
	props: {
		// svg 图标组件名字
		name: {
			type: String,
		},
		// svg 大小
		size: {
			type: Number,
			default: () => 14,
		},
		// svg 颜色
		color: {
			type: String,
		},
	},
	setup(props) {
		// 在线链接、本地引入地址前缀
		const linesString = ['https', 'http', '/src', '/assets', import.meta.env.VITE_PUBLIC_PATH];

		// 获取 icon 图标名称
		const getIconName = computed(() => {
			return props?.name;
		});
		// 用于判断 element plus 自带 svg 图标的显示、隐藏
		const isShowIconSvg = computed(() => {
			return props?.name?.startsWith('ele-');
		});
		// 用于判断在线链接、本地引入等图标显示、隐藏
		const isShowIconImg = computed(() => {
			return linesString.find((str) => props.name?.startsWith(str));
		});
		// 设置图标样式
		const setIconSvgStyle = computed(() => {
			return `font-size: ${props.size}px;color: ${props.color};`;
		});
		// 设置图片样式
		const setIconImgOutStyle = computed(() => {
			return `width: ${props.size}px;height: ${props.size}px;display: inline-block;overflow: hidden;`;
		});
		// 设置图片样式
		// https://gitee.com/lyt-top/vue-next-admin/issues/I59ND0
		const setIconSvgInsStyle = computed(() => {
			const filterStyle: string[] = [];
			const compatibles: string[] = ['-webkit', '-ms', '-o', '-moz'];
			compatibles.forEach((j) => filterStyle.push(`${j}-filter: drop-shadow(${props.color} 30px 0);`));
			return `width: ${props.size}px;height: ${props.size}px;position: relative;left: -${props.size}px;${filterStyle.join('')}`;
		});
		return {
			getIconName,
			isShowIconSvg,
			isShowIconImg,
			setIconSvgStyle,
			setIconImgOutStyle,
			setIconSvgInsStyle,
		};
	},
});
</script>

utils下新建other.ts

import { nextTick } from 'vue';
import type { App } from 'vue';
import * as svg from '@element-plus/icons-vue';
import router from '@/router/index';
import pinia from '@/stores/index';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/stores/themeConfig';
import { i18n } from '@/i18n/index';
import { Local } from '@/utils/storage';
import SvgIcon from '@/components/svgIcon/index.vue';

/**
 * 导出全局注册 element plus svg 图标
 * @param app vue 实例
 * @description 使用:https://element-plus.gitee.io/zh-CN/component/icon.html
 */
export function elSvg(app: App) {
	const icons = svg as any;
	for (const i in icons) {
		app.component(`ele-${icons[i].name}`, icons[i]);
	}
	app.component('SvgIcon', SvgIcon);
}

/**
 * 设置浏览器标题国际化
 * @method const title = useTitle(); ==> title()
 */
export function useTitle() {
	const stores = useThemeConfig(pinia);
	const { themeConfig } = storeToRefs(stores);
	nextTick(() => {
		let webTitle = '';
		const globalTitle: string = themeConfig.value.globalTitle;
		const { path, meta } = router.currentRoute.value;
		if (path === '/login') {
			webTitle = <any>meta.title;
		} else {
			webTitle = setTagsViewNameI18n(router.currentRoute.value);
		}
		document.title = `${webTitle} - ${globalTitle}` || globalTitle;
	});
}

/**
 * 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化
 * @param params 路由 query、params 中的 tagsViewName
 * @returns 返回当前 tagsViewName 名称
 */
export function setTagsViewNameI18n(item: any) {
	let tagsViewName: any = '';
	const { query, params, meta } = item;
	if (query?.tagsViewName || params?.tagsViewName) {
		if (/\/zh-cn|en|zh-tw\//.test(query?.tagsViewName) || /\/zh-cn|en|zh-tw\//.test(params?.tagsViewName)) {
			// 国际化
			const urlTagsParams = (query?.tagsViewName && JSON.parse(query?.tagsViewName)) || (params?.tagsViewName && JSON.parse(params?.tagsViewName));
			tagsViewName = urlTagsParams[i18n.global.locale.value];
		} else {
			// 非国际化
			tagsViewName = query?.tagsViewName || params?.tagsViewName;
		}
	} else {
		// 非自定义 tagsView 名称
		tagsViewName = i18n.global.t(<any>meta.title);
	}
	return tagsViewName;
}

/**
 * 图片懒加载
 * @param el dom 目标元素
 * @param arr 列表数据
 * @description data-xxx 属性用于存储页面或应用程序的私有自定义数据
 */
export const lazyImg = (el: any, arr: any) => {
	const io = new IntersectionObserver((res) => {
		res.forEach((v: any) => {
			if (v.isIntersecting) {
				const { img, key } = v.target.dataset;
				v.target.src = img;
				v.target.onload = () => {
					io.unobserve(v.target);
					arr[key]['loading'] = false;
				};
			}
		});
	});
	nextTick(() => {
		document.querySelectorAll(el).forEach((img) => io.observe(img));
	});
};

/**
 * 全局组件大小
 * @returns 返回 `window.localStorage` 中读取的缓存值 `globalComponentSize`
 */
export const globalComponentSize = (): string => {
	const stores = useThemeConfig(pinia);
	const { themeConfig } = storeToRefs(stores);
	return Local.get('themeConfig')?.globalComponentSize || themeConfig.value?.globalComponentSize;
};

/**
 * 对象深克隆
 * @param obj 源对象
 * @returns 克隆后的对象
 */
export function deepClone(obj: any) {
	let newObj: any;
	try {
		newObj = obj.push ? [] : {};
	} catch (error) {
		newObj = {};
	}
	for (const attr in obj) {
		if (obj[attr] && typeof obj[attr] === 'object') {
			newObj[attr] = deepClone(obj[attr]);
		} else {
			newObj[attr] = obj[attr];
		}
	}
	return newObj;
}

/**
 * 判断是否是移动端
 */
export function isMobile() {
	if (
		navigator.userAgent.match(
			/('phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone')/i
		)
	) {
		return true;
	} else {
		return false;
	}
}

/**
 * 判断数组对象中所有属性是否为空,为空则删除当前行对象
 * @description @感谢大黄
 * @param list 数组对象
 * @returns 删除空值后的数组对象
 */
export function handleEmpty(list: any) {
	const arr = [];
	for (const i in list) {
		const d = [];
		for (const j in list[i]) {
			d.push(list[i][j]);
		}
		const leng = d.filter((item) => item === '').length;
		if (leng !== d.length) {
			arr.push(list[i]);
		}
	}
	return arr;
}

/**
 * 统一批量导出
 * @method elSvg 导出全局注册 element plus svg 图标
 * @method useTitle 设置浏览器标题国际化
 * @method setTagsViewNameI18n 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化
 * @method lazyImg 图片懒加载
 * @method globalComponentSize() element plus 全局组件大小
 * @method deepClone 对象深克隆
 * @method isMobile 判断是否是移动端
 * @method handleEmpty 判断数组对象中所有属性是否为空,为空则删除当前行对象
 */
const other = {
	elSvg: (app: App) => {
		elSvg(app);
	},
	useTitle: () => {
		useTitle();
	},
	setTagsViewNameI18n(route: any) {
		return setTagsViewNameI18n(route);
	},
	lazyImg: (el: any, arr: any) => {
		lazyImg(el, arr);
	},
	globalComponentSize: () => {
		return globalComponentSize();
	},
	deepClone: (obj: any) => {
		return deepClone(obj);
	},
	isMobile: () => {
		return isMobile();
	},
	handleEmpty: (list: any) => {
		return handleEmpty(list);
	},
};

// 统一批量导出
export default other;

修改main.ts

import { createApp } from 'vue';
import pinia from '@/stores/index';
import App from './App.vue';
import router from '@/router/index';
import { i18n } from '@/i18n/index';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';

const app = createApp(App);

app.use(pinia).use(router).use(ElementPlus).use(i18n);

app.mount('#app');

修改App.vue

<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { computed } from 'vue';
import other from '@/utils/other';
// 获取全局组件大小
const getGlobalComponentSize = computed(() => {
	return other.globalComponentSize();
});
const { messages, locale } = useI18n();
// 获取全局 i18n
const getGlobalI18n = computed(() => {
	return messages.value[locale.value];
});
</script>

<template>
	<!--https://element-plus.org/zh-CN/component/config-provider.html#api-->
	<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
		<el-table mb-1 :data="[]" />
		<el-pagination :total="100" />
	</el-config-provider>
</template>

<style scoped></style>

运行项目查看结果

yarn dev

在这里插入图片描述
在这里插入图片描述

修改为en就会变成英文

参考学习代码地址vue-next-admin

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,首先你需要安装Vue CLI脚手架工具,然后创建一个Vue3项目。接下来你需要安装一些依赖包: ``` npm install --save-dev typescript @vue/cli-plugin-typescript @vue/compiler-sfc vite ``` 然后你可以使用以下命令来创建一个Vue3项目: ``` vue create my-project ``` 安装完毕后,你需要进入项目目录并执行以下命令来安装其他依赖包: ``` npm install --save vue-router axios element-plus pinia npm install --save-dev vite-plugin-style-import ``` 接下来,你需要创建一个 `src/router/routes.ts` 文件来定义你的路由。你可以根据自己的需求定义路由信息。例如: ``` import { RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/', component: () => import('@/views/Home.vue') }, { path: '/about', component: () => import('@/views/About.vue') } ] export default routes ``` 然后你需要修改 `src/router/index.ts` 文件来动态生成路由。你可以使用以下代码: ``` import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' import routes from './routes' const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [] as RouteRecordRaw[] }) router.beforeEach(async (to, from, next) => { if (router.getRoutes().length === 0) { router.addRoute(routes) next({ ...to, replace: true }) } else { next() } }) export default router ``` 接下来,你需要创建一个 `src/i18n` 目录来支持国际化。你可以使用 `vue-i18n` 库来实现。例如: ``` import { createI18n } from 'vue-i18n' import messages from './messages' const i18n = createI18n({ locale: 'en', fallbackLocale: 'en', messages }) export default i18n ``` 然后你需要创建一个 `src/i18n/messages.ts` 文件来定义你的翻译信息。例如: ``` const messages = { en: { hello: 'Hello World!' }, zh: { hello: '你好,世界!' } } export default messages ``` 接下来,你需要创建一个 `src/styles/variables.scss` 文件来定义你的主题变量。例如: ``` $primary-color: #1890ff; $success-color: #52c41a; $info-color: #1890ff; $warning-color: #faad14; $error-color: #f5222d; ``` 然后你需要在 `src/main.ts` 文件中引入这个文件: ``` import './styles/variables.scss' ``` 接下来,你需要安装 `vite-plugin-style-import` 来支持按需加载 Element Plus 的样式。例如: ``` import { createApp } from 'vue' import App from './App.vue' import router from './router' import i18n from './i18n' import 'element-plus/lib/theme-chalk/index.css' const app = createApp(App) app.use(router) app.use(i18n) app.mount('#app') ``` 最后,你需要在 `vite.config.ts` 文件中添加以下配置来支持换肤功能: ``` import { UserConfig } from 'vite' const config: UserConfig = { css: { preprocessorOptions: { scss: { additionalData: ` @import "src/styles/variables.scss"; ` } } } } export default config ``` 好了,现在你已经成功地使用 Vue3 + TypeScript + Element Plus + Axios + Router + Pinia + Vite 编译一个支持动态生成路由、国际化和换肤的项目了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

假装我不帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值