vue3+ts项目04-国际化

news2024/11/17 22:37:41
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1076760.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ubuntod安装datasophon问题记录

问题描述&#xff1a; 主机agent分发报红 解决步骤一&#xff1a; 修改datasophon-worker.tar.gz文件 解压/opt/datasophon/DDP/packages目录下的datasophon-worker.tar.gz文件修改datasophon-worker/bin目录下的datasophon-worker.sh文件 . /etc/profile解决步骤二&#xf…

非凸科技受邀出席源创会,探讨数据技术的未来发展

9月23日&#xff0c;由开源中国联合腾讯云TVP开展的“数据与前沿技术”源创会活动在成都顺利举行&#xff0c;非凸科技受邀出席&#xff0c;与业界专家们共同探讨了数据存储、数据分析、数据挖掘等前沿技术。 会上&#xff0c;非凸科技成都分公司研发总监赵海峰以“量化交易的数…

LLaVa大模型关键技术及在线演示

LLaVA&#xff0c;一种新的大型多模态模型&#xff0c;称为“大型语言和视觉助手”&#xff0c;旨在开发一种通用视觉助手&#xff0c;可以遵循语言和图像指令来完成各种现实世界的任务。 这个想法是将 GPT-4 等大型语言模型 (LLM) 的强大功能与 CLIP 等视觉编码器相结合&#…

Transformer模型 | 基于Spatial-Temporal Transformer的城市交通流预测

交通预测已成为智能交通系统的核心组成部分。然而,由于交通流的高度非线性特征和动态的时空依赖性,及时准确的交通预测,尤其是长时交通流预测仍然是一个开放性的挑战。在这篇文章中,作者提出了一种新的时空Transformer网络(STTNs)模型,该模型联合利用了动态有向的空间依…

Meta开源数字水印Stable Signature,极大增强生成式AI安全

全球社交、科技巨头Meta&#xff08;Facebook、Instagram等母公司&#xff09;在官网宣布&#xff0c;开源数字水印产品Stable Signature&#xff0c;并公开论文。 据悉&#xff0c;Stable Signature是由Meta和INRIA&#xff08;法国国家信息与自动化研究所&#xff09;联合开…

决策树算法——C4.5算法

目录 1.ID3算法 2.C4.5算法 3.信息增益率 &#xff08;1&#xff09;信息增益率 &#xff08;2&#xff09;案例 4.决策树的剪枝 5.总结 &#xff08;1&#xff09;优点与改进 &#xff08;2&#xff09;缺点 &#xff08;3&#xff09; 总结及展望 近年来决策树方…

算法——动态规划

一、 53. 最大子数组和 - 力扣&#xff08;LeetCode&#xff09; 最大子数组和&#xff0c;可以建立一个dp表&#xff0c;来存放当前的位置的累加的最大和 int maxSubArray(vector<int>& nums) {int nnums.size();if(n1)return nums[0];vector<int> dp(n);int…

3.(vue3.x+vite)class动态绑定的方式

前端技术社区总目录(订阅之前请先查看该博客) 效果浏览 代码如下 <template><div><div :class="{acti

学习Kotlin编程语言

官网地址 https://developer.android.google.cn/kotlin/learn?hlzh-cn 脑图

安全性第一!OpenWRT配置SFTP远程文件传输,实现数据安全保护

文章目录 前言1. openssh-sftp-server 安装2. 安装cpolar工具3.配置SFTP远程访问4.固定远程连接地址 前言 本次教程我们将在OpenWRT上安装SFTP服务&#xff0c;并结合cpolar内网穿透&#xff0c;创建安全隧道映射22端口&#xff0c;实现在公网环境下远程OpenWRT SFTP&#xff…

[鹏城杯 2022]简单的php - 无数字字母RCE(取反)【*】

[鹏城杯 2022]简单的php 一、解题流程二、思考总结 题目代码&#xff1a; <?php show_source(__FILE__);$code $_GET[code];if(strlen($code) > 80 or preg_match(/[A-Za-z0-9]|\|"||\ |,|\.|-|\||\/|\\|<|>|\$|\?|\^|&|\|/is,$code)){die( Hello);}e…

【SpringCloud微服务项目实战-mall4cloud项目(3)】——mall4cloud-auth

mall4cloud-auth-认证与授权 pom依赖nacos配置令牌认证介绍项目代码过滤器校验 总结 目前项目登录使用的认证授权方式较为简单&#xff0c;认证通过token令牌方式&#xff0c;授权通过用户名密码方式&#xff0c;并且结合了captcha验证码登录。下面的介绍中会增加OAuth2的授权方…

git 取消待推送内容

选择最后一次提交的记录&#xff0c;右键->软合并

Web测试的基础流程(外加测试过程需要的注意5点)

前言 在Web工程过程中&#xff0c;基于Web系统的测试、确认和验收是一项重要而富有挑战性的工作。基于Web的系统测试与传统的软件测试不同&#xff0c;它不但需要检查和验证是否按照设计的要求运行&#xff0c;而且还要测试系统在不同用户的浏览器端的显示是否合适。 重要的是…

灯具从深圳寄国际物流到墨西哥

在国际贸易的日益频繁的今天&#xff0c;越来越多的企业开始将产品销往海外市场。然而&#xff0c;如何将这些产品安全、快速地送达目的地&#xff0c;成为了每个企业都需要面对的问题。对于灯具这种重量大、体积大的物品来说&#xff0c;如何选择合适的国际物流方式&#xff0…

如何在 Spring Boot 中使用 WebSocket

在Spring Boot中使用WebSocket构建实时应用 WebSocket是一种用于实现双向通信的网络协议&#xff0c;它非常适合构建实时应用程序&#xff0c;如在线聊天、实时通知和多人协作工具。Spring Boot提供了对WebSocket的支持&#xff0c;使得在应用程序中集成WebSocket变得非常容易…

KWin、libdrm、DRM从上到下全过程 —— drmModeAddFBxxx(2)

接前一篇文章&#xff1a;KWin、libdrm、DRM从上到下全过程 —— drmModeAddFBxxx&#xff08;1&#xff09; 上回书说到drmModeAddFB、drmModeAddFB2和drmModeAddFB2WithModifiers函数最终“三分归一统”&#xff0c;在内核层统一调用到drm_mode_addfb2函数。 这里我们先不急…

手撕各种排序

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大一&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;掌握每种排序的方法&#xff0c;理解每种排序利弊…

视频剪辑利器,批量随机抽帧轻松保存,让精彩片段永不丢失!

您是否经常遇到这样的情况&#xff1a;在一段长视频中&#xff0c;有一些精彩的瞬间&#xff0c;但却不知道如何将其提取并保存下来&#xff1f;现在&#xff0c;我们为您推出了一款强大的视频剪辑利器&#xff0c;能够帮助您批量从视频中指定的区间内随机抽帧&#xff0c;并轻…

NPDP考什么?难度大不大?

之前给大家分享了NPDP考试时间以及报名条件&#xff0c;最近有宝子问我&#xff0c;这个考试难度咋样&#xff1f;都考察什么内容啊&#xff1f;今天给大家详细回答一下~ 1&#xff09;NPDP考试及报名时间 2023年下半年NPDP考试将于12月2日进行 预报名时间&#xff1a;10月8日…