Merge pull request 'optimize: ♻️ 将多语言单独拆分出来' (#7) from dev into master

Reviewed-on: Bunny-Cli/bunny-admin-element-thin#7
This commit is contained in:
bunny 2024-09-02 23:43:23 +08:00
commit bdea37649d
52 changed files with 858 additions and 487 deletions

View File

@ -1,91 +0,0 @@
buttons:
pureLoginOut: LoginOut
pureLogin: Login
pureOpenSystemSet: Open System Configs
pureReload: Reload
pureCloseCurrentTab: Close CurrentTab
pureCloseLeftTabs: Close LeftTabs
pureCloseRightTabs: Close RightTabs
pureCloseOtherTabs: Close OtherTabs
pureCloseAllTabs: Close AllTabs
pureContentFullScreen: Content FullScreen
pureContentExitFullScreen: Content ExitFullScreen
pureClickCollapse: Collapse
pureClickExpand: Expand
pureConfirm: Confirm
pureSwitch: Switch
pureClose: Close
pureBackTop: BackTop
pureOpenText: Open
pureCloseText: Close
search:
pureTotal: Total
pureHistory: History
pureCollect: Collect
pureDragSort: Drag Sort
pureEmpty: Empty
purePlaceholder: Search Menu
panel:
pureSystemSet: System Configs
pureCloseSystemSet: Close System Configs
pureClearCacheAndToLogin: Clear cache and return to login page
pureClearCache: Clear Cache
pureOverallStyle: Overall Style
pureOverallStyleLight: Light
pureOverallStyleLightTip: Set sail freshly and light up the comfortable work interface
pureOverallStyleDark: Dark
pureOverallStyleDarkTip: Moonlight Overture, indulge in the tranquility and elegance of the night
pureOverallStyleSystem: Auto
pureOverallStyleSystemTip: Synchronize time, the interface naturally responds to morning and dusk
pureThemeColor: Theme Color
pureLayoutModel: Layout Model
pureVerticalTip: The menu on the left is familiar and friendly
pureHorizontalTip: Top menu, concise overview
pureMixTip: Mixed menu, flexible
pureStretch: Stretch Page
pureStretchFixed: Fixed
pureStretchFixedTip: Compact pages make it easy to find the information you need
pureStretchCustom: Custom
pureStretchCustomTip: Minimum 1280, maximum 1600
pureTagsStyle: Tags Style
pureTagsStyleSmart: Smart
pureTagsStyleSmartTip: Smart tags add fun and brilliance
pureTagsStyleCard: Card
pureTagsStyleCardTip: Card tags for efficient browsing
pureTagsStyleChrome: Chrome
pureTagsStyleChromeTip: Chrome style is classic and elegant
pureInterfaceDisplay: Interface Display
pureGreyModel: Grey Model
pureWeakModel: Weak Model
pureHiddenTags: Hidden Tags
pureHiddenFooter: Hidden Footer
pureMultiTagsCache: MultiTags Cache
menus:
pureHome: Home
pureLogin: Login
pureAbnormal: Abnormal Page
pureFourZeroFour: "404"
pureFourZeroOne: "403"
pureFive: "500"
purePermission: Permission Manage
purePermissionPage: Page Permission
purePermissionButton: Button Permission
purePermissionButtonRouter: Route return button permission
purePermissionButtonLogin: Login interface return button permission
status:
pureLoad: Loading...
pureMessage: Message
pureNotify: Notify
pureTodo: Todo
pureNoMessage: No Message
pureNoNotify: No Notify
pureNoTodo: No Todo
login:
pureUsername: Username
purePassword: Password
pureLogin: Login
pureLoginSuccess: Login Success
pureLoginFail: Login Fail
pureUsernameReg: Please enter username
purePassWordReg: Please enter password
purePassWordRuleReg: The password format should be any combination of 8-18 digits

View File

@ -1,91 +0,0 @@
buttons:
pureLoginOut: 退出系统
pureLogin: 登录
pureOpenSystemSet: 打开系统配置
pureReload: 重新加载
pureCloseCurrentTab: 关闭当前标签页
pureCloseLeftTabs: 关闭左侧标签页
pureCloseRightTabs: 关闭右侧标签页
pureCloseOtherTabs: 关闭其他标签页
pureCloseAllTabs: 关闭全部标签页
pureContentFullScreen: 内容区全屏
pureContentExitFullScreen: 内容区退出全屏
pureClickCollapse: 点击折叠
pureClickExpand: 点击展开
pureConfirm: 确认
pureSwitch: 切换
pureClose: 关闭
pureBackTop: 回到顶部
pureOpenText:
pureCloseText:
search:
pureTotal:
pureHistory: 搜索历史
pureCollect: 收藏
pureDragSort: (可拖拽排序)
pureEmpty: 暂无搜索结果
purePlaceholder: 搜索菜单(支持拼音搜索)
panel:
pureSystemSet: 系统配置
pureCloseSystemSet: 关闭配置
pureClearCacheAndToLogin: 清空缓存并返回登录页
pureClearCache: 清空缓存
pureOverallStyle: 整体风格
pureOverallStyleLight: 浅色
pureOverallStyleLightTip: 清新启航,点亮舒适的工作界面
pureOverallStyleDark: 深色
pureOverallStyleDarkTip: 月光序曲,沉醉于夜的静谧雅致
pureOverallStyleSystem: 自动
pureOverallStyleSystemTip: 同步时光,界面随晨昏自然呼应
pureThemeColor: 主题色
pureLayoutModel: 导航模式
pureVerticalTip: 左侧菜单,亲切熟悉
pureHorizontalTip: 顶部菜单,简洁概览
pureMixTip: 混合菜单,灵活多变
pureStretch: 页宽
pureStretchFixed: 固定
pureStretchFixedTip: 紧凑页面,轻松找到所需信息
pureStretchCustom: 自定义
pureStretchCustomTip: 最小1280、最大1600
pureTagsStyle: 页签风格
pureTagsStyleSmart: 灵动
pureTagsStyleSmartTip: 灵动标签,添趣生辉
pureTagsStyleCard: 卡片
pureTagsStyleCardTip: 卡片标签,高效浏览
pureTagsStyleChrome: 谷歌
pureTagsStyleChromeTip: 谷歌风格,经典美观
pureInterfaceDisplay: 界面显示
pureGreyModel: 灰色模式
pureWeakModel: 色弱模式
pureHiddenTags: 隐藏标签页
pureHiddenFooter: 隐藏页脚
pureMultiTagsCache: 页签持久化
menus:
pureHome: 首页
pureLogin: 登录
pureAbnormal: 异常页面
pureFourZeroFour: "404"
pureFourZeroOne: "403"
pureFive: "500"
purePermission: 权限管理
purePermissionPage: 页面权限
purePermissionButton: 按钮权限
purePermissionButtonRouter: 路由返回按钮权限
purePermissionButtonLogin: 登录接口返回按钮权限
status:
pureLoad: 加载中...
pureMessage: 消息
pureNotify: 通知
pureTodo: 待办
pureNoMessage: 暂无消息
pureNoNotify: 暂无通知
pureNoTodo: 暂无待办
login:
pureUsername: 账号
purePassword: 密码
pureLogin: 登录
pureLoginSuccess: 登录成功
pureLoginFail: 登录失败
pureUsernameReg: 请输入账号
purePassWordReg: 请输入密码
purePassWordRuleReg: 密码格式应为8-18位数字、字母、符号的任意两种组合

View File

@ -23,34 +23,25 @@ const permissionRouter = {
}
},
{
path: "/permission/button",
path: "/permission/button/router",
component: "permission/button/index",
name: "PermissionButtonRouter",
meta: {
title: "menus.purePermissionButton",
roles: ["admin", "common"]
},
children: [
{
path: "/permission/button/router",
component: "permission/button/index",
name: "PermissionButtonRouter",
meta: {
title: "menus.purePermissionButtonRouter",
auths: [
"permission:btn:add",
"permission:btn:edit",
"permission:btn:delete"
]
}
},
{
path: "/permission/button/login",
component: "permission/button/perms",
name: "PermissionButtonLogin",
meta: {
title: "menus.purePermissionButtonLogin"
}
}
]
title: "menus.purePermissionButtonRouter",
auths: [
"permission:btn:add",
"permission:btn:edit",
"permission:btn:delete"
]
}
},
{
path: "/permission/button/login",
component: "permission/button/perms",
name: "PermissionButtonLogin",
meta: {
title: "menus.purePermissionButtonLogin"
}
}
]
};

16
mock/i18n.ts Normal file
View File

@ -0,0 +1,16 @@
import { defineFakeRoute } from "vite-plugin-fake-server/client";
import en from "./i18n/en";
import zh from "./i18n/zh";
export default defineFakeRoute([
{
url: "/mock/getI18n",
method: "get",
response: () => {
return {
code: 200,
data: { zh, en, local: "zh" }
};
}
}
]);

20
mock/i18n/en.ts Normal file
View File

@ -0,0 +1,20 @@
import { buttons } from "./en/buttons";
import { search } from "./en/search";
import { panel } from "./en/panel";
import { menus } from "./en/menus";
import { status } from "./en/status";
import { login } from "./en/login";
import { style } from "./en/style";
import { system } from "./en/system";
export default {
name: "en",
buttons,
search,
panel,
menus,
status,
login,
style,
system
};

25
mock/i18n/en/buttons.ts Normal file
View File

@ -0,0 +1,25 @@
export const buttons = {
openSystemSet: "Open System Configs",
pureOpenSystemSet: "pureOpenSystemSet",
pureAccountSettings: "Account",
pureLoginOut: "LoginOut",
pureLogin: "Login",
pureReload: "Reload",
pureCloseCurrentTab: "Close CurrentTab",
pureCloseLeftTabs: "Close LeftTabs",
pureCloseRightTabs: "Close RightTabs",
pureCloseOtherTabs: "Close OtherTabs",
pureCloseAllTabs: "Close AllTabs",
pureContentFullScreen: "Content FullScreen",
pureContentExitFullScreen: "Content ExitFullScreen",
pureClickCollapse: "Collapse",
pureClickExpand: "Expand",
confirm: "Confirm",
pureSwitch: "Switch",
close: "Close",
pureBackTop: "BackTop",
pureOpenText: "Open",
pureCloseText: "Close",
rest: "Rest"
};

40
mock/i18n/en/login.ts Normal file
View File

@ -0,0 +1,40 @@
export const login = {
loginSuccess: "Login Success",
loginFail: "Login Fail",
usernameRegex: "please input username",
username: "input username",
password: "input password",
login: "Login",
email: "email",
repeatPassword: "Sure Password",
emailCode: "input email code",
verifyCode: "verify code",
emailRegex: "please input email",
passwordRegex: "please input password",
passwordSureRegex: "Please entr confirm password",
passwordDifferentRegex: "The two passwords do not match!",
emailCodeRegex: "please input email code",
getEmailCode: "get email code",
rememberMe: "days no need to login",
rememberInfo:
"After checking and logging in, will automatically log in to the system without entering your username and password within the specified number of days.",
forgetPassword: "Forget Password?",
getCodeInfo: "Seconds",
getVerifyCode: "Get VerifyCode",
definite: "definite",
back: "back",
passWordUpdateReg: "Password has been updated",
pureTip: 'After scanning the code, click "Confirm" to complete the login',
pureRegisterSuccess: "Regist Success",
pureTickPrivacy: "Please tick Privacy Policy",
pureReadAccept: "I have read it carefully and accept",
purePrivacyPolicy: "Privacy Policy",
pureVerifyCodeReg: "Please enter verify code",
pureVerifyCodeCorrectReg: "Please enter correct verify code",
pureVerifyCodeSixReg: "Please enter a 6-digit verify code",
purePhoneReg: "Please enter the phone",
purePhoneCorrectReg: "Please enter the correct phone number format",
purePassWordRuleReg:
"The password format should be any combination of 8-18 digits"
};

120
mock/i18n/en/menus.ts Normal file
View File

@ -0,0 +1,120 @@
export const menus = {
home: "Home",
purePermissionButtonRouter: "PermissionButtonRouter",
purePermissionButtonLogin: "purePermissionButtonLogin",
pureLogin: "Login",
pureEmpty: "Empty Page",
pureTable: "Table",
pureSysManagement: "System Manage",
pureUser: "User Manage",
pureRole: "Role Manage",
pureSystemMenu: "Menu Manage",
pureDept: "Dept Manage",
pureSysMonitor: "System Monitor",
pureOnlineUser: "Online User",
pureLoginLog: "Login Log",
pureOperationLog: "Operation Log",
pureSystemLog: "System Log",
pureEditor: "Editor",
pureAbnormal: "Abnormal Page",
pureFourZeroFour: "404",
pureFourZeroOne: "403",
pureFive: "500",
pureComponents: "Components",
pureDialog: "Dialog",
pureMessage: "Message Tips",
pureVideo: "Video",
pureSegmented: "Segmented",
pureWaterfall: "Waterfall",
pureMap: "Map",
pureDraggable: "Draggable",
pureSplitPane: "Split Pane",
pureText: "Text Ellipsis",
pureElButton: "Button",
pureButton: "Button Animation",
pureCheckButton: "Check Button",
pureCropping: "Picture Cropping",
pureAnimatecss: "AnimateCss Selector",
pureCountTo: "Digital Animation",
pureSelector: "Scope Selector",
pureFlowChart: "Flow Chart",
pureSeamless: "Seamless Scroll",
pureContextmenu: "Context Menu",
pureTypeit: "Typeit",
pureJsonEditor: "JSON Editor",
pureColorPicker: "Color Picker",
pureDatePicker: "Date Picker",
pureDateTimePicker: "DateTimePicker",
pureTimePicker: "TimePicker",
pureTag: "Tag",
pureStatistic: "Statistic",
pureCollapse: "Collapse",
pureGanttastic: "Gantt Chart",
pureProgress: "Progress",
pureUpload: "File Upload",
pureCheckCard: "CheckCard",
pureMenus: "MultiLevel Menu",
pureMenu1: "Menu1",
pureMenu2: "Menu2",
purePermission: "Permission Manage",
purePermissionPage: "Page Permission",
purePermissionButton: "Button Permission",
pureTabs: "Tabs Operate",
pureGuide: "Guide",
pureAble: "Able",
pureMenuTree: "Menu Tree",
pureVideoFrame: "Video Frame Capture",
pureWavesurfer: "Audio Visualization",
pureRipple: "Ripple",
pureMqtt: "Mqtt Client",
pureOptimize: "Debounce、Throttle、Copy、Longpress Directives",
pureVerify: "Captcha",
pureWatermark: "Water Mark",
purePrint: "Print",
pureDownload: "Download",
pureExternalPage: "External Page",
pureExternalDoc: "Docs External",
pureEmbeddedDoc: "Docs Embedded",
pureExternalLink: "Vue-Pure-Admin",
pureUtilsLink: "Pure-Admin-Utils",
pureColorHuntDoc: "ColorHunt",
pureUiGradients: "UiGradients",
pureEpDoc: "Element-Plus",
pureTailwindcssDoc: "Tailwindcss",
pureVueDoc: "Vue3",
pureViteDoc: "Vite",
purePiniaDoc: "Pinia",
pureRouterDoc: "Vue-Router",
pureAbout: "About",
pureResult: "Result Page",
pureSuccess: "Success Page",
pureFail: "Fail Page",
pureIconSelect: "Icon Select",
pureTimeline: "Time Line",
pureLineTree: "LineTree",
pureList: "List Page",
pureCardList: "Card List Page",
pureDebounce: "Debounce & Throttle",
pureFormDesign: "Form Design",
pureBarcode: "Barcode",
pureQrcode: "Qrcode",
pureCascader: "Area Cascader",
pureSwiper: "Swiper Plugin",
pureVirtualList: "Virtual List",
purePdf: "PDF Preview",
pureExcel: "Export Excel",
pureInfiniteScroll: "Table Infinite Scroll",
pureSensitive: "Sensitive Filter",
purePinyin: "PinYin",
pureDanmaku: "Danmaku",
pureSchemaForm: "Form",
pureTableBase: "Base Usage",
pureTableHigh: "High Usage",
pureTableEdit: "Edit Usage",
pureVxeTable: "Virtual Usage",
pureBoard: "Paint Board",
pureMindMap: "Mind Map",
pureMenuOverflow: "Menu Overflow Show Tooltip Text",
pureChildMenuOverflow: "Child Menu Overflow Show Tooltip Text",
systemctlTest: "Systemctl lTest"
};

38
mock/i18n/en/panel.ts Normal file
View File

@ -0,0 +1,38 @@
export const panel = {
pureSystemSet: "System Configs",
pureCloseSystemSet: "Close System Configs",
pureClearCacheAndToLogin: "Clear cache and return to login page",
pureClearCache: "Clear Cache",
pureOverallStyle: "Overall Style",
pureOverallStyleLight: "Light",
pureOverallStyleLightTip:
"Set sail freshly and light up the comfortable work interface",
pureOverallStyleDark: "Dark",
pureOverallStyleDarkTip:
"Moonlight Overture, indulge in the tranquility and elegance of the night",
pureOverallStyleSystem: "Auto",
pureOverallStyleSystemTip:
"Synchronize time, the interface naturally responds to morning and dusk",
pureThemeColor: "Theme Color",
pureLayoutModel: "Layout Model",
pureVerticalTip: "The menu on the left is familiar and friendly",
pureHorizontalTip: "Top menu, concise overview",
pureMixTip: "Mixed menu, flexible",
pureStretch: "Stretch Page",
pureStretchFixed: "Fixed",
pureStretchFixedTip:
"Compact pages make it easy to find the information you need",
pureStretchCustom: "Custom",
pureStretchCustomTip: "Minimum 1280, maximum 1600",
pureTagsStyle: "Tags Style",
pureTagsStyleSmart: "Smart",
pureTagsStyleSmartTip: "Smart tags add fun and brilliance",
pureTagsStyleCard: "Card",
pureTagsStyleCardTip: "Card tags for efficient browsing",
pureInterfaceDisplay: "Interface Display",
pureGreyModel: "Grey Model",
pureWeakModel: "Weak Model",
pureHiddenTags: "Hidden Tags",
pureHiddenFooter: "Hidden Footer",
pureMultiTagsCache: "MultiTags Cache"
};

8
mock/i18n/en/search.ts Normal file
View File

@ -0,0 +1,8 @@
export const search = {
pureTotal: "Total",
pureHistory: "History",
pureCollect: "Collect",
pureDragSort: "Drag Sort",
pureEmpty: "Empty",
purePlaceholder: "Search Menu"
};

11
mock/i18n/en/status.ts Normal file
View File

@ -0,0 +1,11 @@
export const status = {
pureLoad: "Loading...",
pureMessage: "Message",
pureNotify: "Notify",
pureTodo: "Todo",
pureNoMessage: "No Message",
pureNoNotify: "No Notify",
pureNoTodo: "No Todo",
enable: "enable",
disable: "disable"
};

5
mock/i18n/en/style.ts Normal file
View File

@ -0,0 +1,5 @@
export const style = {
larger: "Larger",
default: "Default",
small: "Small"
};

8
mock/i18n/en/system.ts Normal file
View File

@ -0,0 +1,8 @@
export const system = {
carousel: "carousel setting",
config: "system config",
favicon: "system favicon",
feedback: "system feedback",
emailUsers: "email users",
log: "system log"
};

20
mock/i18n/zh.ts Normal file
View File

@ -0,0 +1,20 @@
import { buttons } from "./zh/buttons";
import { search } from "./zh/search";
import { panel } from "./zh/panel";
import { menus } from "./zh/menus";
import { status } from "./zh/status";
import { login } from "./zh/login";
import { style } from "./zh/style";
import { system } from "./zh/system";
export default {
name: "zh",
buttons,
search,
panel,
menus,
status,
login,
style,
system
};

26
mock/i18n/zh/buttons.ts Normal file
View File

@ -0,0 +1,26 @@
export const buttons = {
openSystemSet: "打开系统配置",
pureOpenSystemSet: "权限设定",
pureAccountSettings: "账户设置",
pureLoginOut: "退出系统",
pureLogin: "登录",
pureReload: "重新加载",
pureCloseCurrentTab: "关闭当前标签页",
pureCloseLeftTabs: "关闭左侧标签页",
pureCloseRightTabs: "关闭右侧标签页",
pureCloseOtherTabs: "关闭其他标签页",
pureCloseAllTabs: "关闭全部标签页",
pureContentFullScreen: "内容区全屏",
pureContentExitFullScreen: "内容区退出全屏",
pureClickCollapse: "点击折叠",
pureClickExpand: "点击展开",
confirm: "确认",
pureSwitch: "切换",
close: "关闭",
pureBackTop: "回到顶部",
pureOpenText: "开",
pureCloseText: "关",
rest: "重置",
search: "搜索"
};

38
mock/i18n/zh/login.ts Normal file
View File

@ -0,0 +1,38 @@
export const login = {
loginSuccess: "登录成功",
loginFail: "登录失败",
usernameRegex: "请输入账号",
username: "输入用户名",
password: "输入密码",
login: "登录",
email: "输入邮箱",
repeatPassword: "确认密码",
emailCode: "邮箱验证码",
verifyCode: "图形验证码",
emailRegex: "输入邮箱",
passwordRegex: "请输入密码",
passwordSureRegex: "请输入确认密码",
repeatPasswordRegex: "请输入确认密码",
passwordDifferentRegex: "两次密码不一致!",
emailCodeRegex: "请输入邮箱验证码",
verifyCodeRegex: "输入验证码",
getEmailCode: "获取邮箱验证码",
rememberMe: "天内免登录",
rememberInfo: "勾选并登录后,规定天数内无需输入用户名和密码会自动登入系统",
forgetPassword: "忘记密码?",
getVerifyCode: "获取验证码",
definite: "确定",
back: "返回",
getCodeInfo: "秒后重新获取",
passWordUpdateReg: "修改密码成功",
pureRegisterSuccess: "注册成功",
pureTickPrivacy: "请勾选隐私政策",
pureReadAccept: "我已仔细阅读并接受",
purePrivacyPolicy: "《隐私政策》",
pureVerifyCodeCorrectReg: "请输入正确的验证码",
pureVerifyCodeSixReg: "请输入6位数字验证码",
purePhoneReg: "请输入手机号码",
purePhoneCorrectReg: "请输入正确的手机号码格式",
purePassWordRuleReg: "密码格式应为8-18位数字、字母、符号的任意两种组合"
};

120
mock/i18n/zh/menus.ts Normal file
View File

@ -0,0 +1,120 @@
export const menus = {
home: "首页",
purePermissionButtonRouter: "权限1",
purePermissionButtonLogin: "权限2",
pureLogin: "登录",
pureEmpty: "无Layout页",
pureTable: "表格",
pureSysManagement: "系统管理",
pureUser: "用户管理",
pureRole: "角色管理",
pureSystemMenu: "菜单管理",
pureDept: "部门管理",
pureSysMonitor: "系统监控",
pureOnlineUser: "在线用户",
pureLoginLog: "登录日志",
pureOperationLog: "操作日志",
pureSystemLog: "系统日志",
pureEditor: "编辑器",
pureAbnormal: "异常页面",
pureFourZeroFour: "404",
pureFourZeroOne: "403",
pureFive: "500",
pureComponents: "组件",
pureDialog: "函数式弹框",
pureMessage: "消息提示",
pureVideo: "视频",
pureSegmented: "分段控制器",
pureWaterfall: "瀑布流无限滚动",
pureMap: "地图",
pureDraggable: "拖拽",
pureSplitPane: "切割面板",
pureText: "文本省略",
pureElButton: "按钮",
pureCheckButton: "可选按钮",
pureButton: "按钮动效",
pureCropping: "图片裁剪",
pureAnimatecss: "animate.css选择器",
pureCountTo: "数字动画",
pureSelector: "范围选择器",
pureFlowChart: "流程图",
pureSeamless: "无缝滚动",
pureContextmenu: "右键菜单",
pureTypeit: "打字机",
pureJsonEditor: "JSON编辑器",
pureColorPicker: "颜色选择器",
pureDatePicker: "日期选择器",
pureDateTimePicker: "日期时间选择器",
pureTimePicker: "时间选择器",
pureTag: "标签",
pureStatistic: "统计组件",
pureCollapse: "折叠面板",
pureGanttastic: "甘特图",
pureProgress: "进度条",
pureUpload: "文件上传",
pureCheckCard: "多选卡片",
pureMenus: "多级菜单",
pureMenu1: "菜单1",
pureMenu2: "菜单2",
purePermission: "权限管理",
purePermissionPage: "页面权限",
purePermissionButton: "按钮权限",
pureTabs: "标签页操作",
pureGuide: "引导页",
pureAble: "功能",
pureMenuTree: "菜单树结构",
pureVideoFrame: "视频帧截取-wasm版",
pureWavesurfer: "音频可视化",
pureRipple: "波纹(Ripple)",
pureMqtt: "MQTT客户端(mqtt)",
pureOptimize: "防抖、截流、复制、长按指令",
pureVerify: "图形验证码",
pureWatermark: "水印",
purePrint: "打印",
pureDownload: "下载",
pureExternalPage: "外部页面",
pureExternalDoc: "文档外链",
pureEmbeddedDoc: "文档内嵌",
pureExternalLink: "vue-pure-admin",
pureUtilsLink: "pure-admin-utils",
pureColorHuntDoc: "调色板",
pureUiGradients: "渐变色",
pureEpDoc: "element-plus",
pureTailwindcssDoc: "tailwindcss",
pureVueDoc: "vue3",
pureViteDoc: "vite",
purePiniaDoc: "pinia",
pureRouterDoc: "vue-router",
pureAbout: "关于",
pureResult: "结果页面",
pureSuccess: "成功页面",
pureFail: "失败页面",
pureIconSelect: "图标选择器",
pureTimeline: "时间线",
pureLineTree: "树形连接线",
pureList: "列表页面",
pureCardList: "卡片列表页",
pureDebounce: "防抖节流",
pureFormDesign: "表单设计器",
pureBarcode: "条形码",
pureQrcode: "二维码",
pureCascader: "区域级联选择器",
pureSwiper: "Swiper插件",
pureVirtualList: "虚拟列表",
purePdf: "PDF预览",
pureExcel: "导出Excel",
pureInfiniteScroll: "表格无限滚动",
pureSensitive: "敏感词过滤",
purePinyin: "汉语拼音",
pureDanmaku: "弹幕",
pureSchemaForm: "表单",
pureTableBase: "基础用法",
pureTableHigh: "高级用法",
pureTableEdit: "可编辑用法",
pureVxeTable: "虚拟滚动",
pureBoard: "艺术画板",
pureMindMap: "思维导图",
pureMenuOverflow: "目录超出显示 Tooltip 文字提示",
pureChildMenuOverflow: "菜单超出显示 Tooltip 文字提示",
systemctlTest: "系统测试"
};

34
mock/i18n/zh/panel.ts Normal file
View File

@ -0,0 +1,34 @@
export const panel = {
pureSystemSet: "系统配置",
pureCloseSystemSet: "关闭配置",
pureClearCacheAndToLogin: "清空缓存并返回登录页",
pureClearCache: "清空缓存",
pureOverallStyle: "整体风格",
pureOverallStyleLight: "浅色",
pureOverallStyleLightTip: "清新启航,点亮舒适的工作界面",
pureOverallStyleDark: "深色",
pureOverallStyleDarkTip: "月光序曲,沉醉于夜的静谧雅致",
pureOverallStyleSystem: "自动",
pureOverallStyleSystemTip: "同步时光,界面随晨昏自然呼应",
pureThemeColor: "主题色",
pureLayoutModel: "导航模式",
pureVerticalTip: "左侧菜单,亲切熟悉",
pureHorizontalTip: "顶部菜单,简洁概览",
pureMixTip: "混合菜单,灵活多变",
pureStretch: "页宽",
pureStretchFixed: "固定",
pureStretchFixedTip: "紧凑页面,轻松找到所需信息",
pureStretchCustom: "自定义",
pureStretchCustomTip: "最小1280、最大1600",
pureTagsStyle: "页签风格",
pureTagsStyleSmart: "灵动",
pureTagsStyleSmartTip: "灵动标签,添趣生辉",
pureTagsStyleCard: "卡片",
pureTagsStyleCardTip: "卡片标签,高效浏览",
pureInterfaceDisplay: "界面显示",
pureGreyModel: "灰色模式",
pureWeakModel: "色弱模式",
pureHiddenTags: "隐藏标签页",
pureHiddenFooter: "隐藏页脚",
pureMultiTagsCache: "页签持久化"
};

10
mock/i18n/zh/search.ts Normal file
View File

@ -0,0 +1,10 @@
export const search = {
search: {
pureTotal: "共",
pureHistory: "搜索历史",
pureCollect: "收藏",
pureDragSort: "(可拖拽排序)",
pureEmpty: "暂无搜索结果",
purePlaceholder: "搜索菜单(支持拼音搜索)"
}
};

11
mock/i18n/zh/status.ts Normal file
View File

@ -0,0 +1,11 @@
export const status = {
pureLoad: "加载中...",
pureMessage: "消息",
pureNotify: "通知",
pureTodo: "待办",
pureNoMessage: "暂无消息",
pureNoNotify: "暂无通知",
pureNoTodo: "暂无待办",
enable: "启用",
disable: "不启用"
};

5
mock/i18n/zh/style.ts Normal file
View File

@ -0,0 +1,5 @@
export const style = {
larger: "宽松",
default: "默认",
small: "紧凑"
};

8
mock/i18n/zh/system.ts Normal file
View File

@ -0,0 +1,8 @@
export const system = {
config: "系统设置",
carousel: "轮播图设置",
favicon: "图标设置",
feedback: "用户反馈",
emailUsers: "邮件用户",
log: "系统日志"
};

View File

@ -5,23 +5,56 @@
</el-config-provider>
</template>
<script lang="ts">
import { defineComponent } from "vue";
<script lang="ts" setup>
import { ElConfigProvider } from "element-plus";
import { computed, onMounted } from "vue";
import { ReDialog } from "@/components/BaseDialog";
import en from "element-plus/es/locale/lang/en";
import zhCn from "element-plus/es/locale/lang/zh-cn";
import plusEn from "plus-pro-components/es/locale/lang/en";
import plusZhCn from "plus-pro-components/es/locale/lang/zh-cn";
import { useNav } from "@/layout/hooks/useNav";
import { useI18n } from "vue-i18n";
import { userI18nStore } from "@/store/i18n/i18n";
export default defineComponent({
name: "app",
components: {
[ElConfigProvider.name]: ElConfigProvider,
ReDialog
},
computed: {
currentLocale() {
return this.$storage.locale?.locale === "zh" ? zhCn : en;
}
const i18nStore = userI18nStore();
const i18n = useI18n();
const { $storage } = useNav();
/**
* * 设置多语言内容
*/
const setI18n = async () => {
await i18nStore.fetchI18n();
const languageData = JSON.parse(localStorage.getItem("i18nStore") as any);
//
const locale = $storage.locale.locale;
//
if (locale == "" || locale == null || !locale) {
const local = languageData.i18n.local;
i18n.locale.value = local;
$storage.locale = { locale: local };
i18n.mergeLocaleMessage(local, languageData.i18n[local]);
return;
}
i18n.locale.value = locale;
$storage.locale = { locale };
i18nStore.i18n.local = locale;
i18n.mergeLocaleMessage(locale, languageData.i18n[locale]);
};
/**
* * 当前语言类别
*/
const currentLocale = computed(() => {
const languageData = JSON.parse(localStorage.getItem("i18nStore") as any);
const local = languageData ? languageData.i18n.local : {};
return local === "zh" ? { ...zhCn, ...plusZhCn } : { ...plusEn, ...en };
});
onMounted(() => {
//
setI18n();
});
</script>

9
src/api/v1/i18n/i18n.ts Normal file
View File

@ -0,0 +1,9 @@
import { http } from "@/api/service/mockRequest";
import type { Result } from "../../../../types/store/baseStoreState";
/**
* *
*/
export const fetchGetI18n = () => {
return http.request<Result<object>>("get", "getI18n");
};

View File

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { useNav } from "@/layout/hooks/useNav";
import LaySearch from "../lay-search/index.vue";
import LayNotice from "../lay-notice/index.vue";
@ -9,10 +9,10 @@ import LaySidebarBreadCrumb from "../lay-sidebar/components/SidebarBreadCrumb.vu
import LaySidebarTopCollapse from "../lay-sidebar/components/SidebarTopCollapse.vue";
import GlobalizationIcon from "@/assets/svg/globalization.svg?component";
import AccountSettingsIcon from "@iconify-icons/ri/user-settings-line";
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
import Setting from "@iconify-icons/ri/settings-3-line";
import Check from "@iconify-icons/ep/check";
import { $t } from "@/plugins/i18n";
const {
layout,
@ -35,8 +35,8 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<div class="navbar bg-[#fff] shadow-sm shadow-[rgba(0,21,41,0.08)]">
<LaySidebarTopCollapse
v-if="device === 'mobile'"
class="hamburger-container"
:is-active="pureApp.sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
@ -58,20 +58,20 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')"
:class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
:style="getDropdownItemStyle(locale, 'zh')"
@click="translationCh"
>
<IconifyIconOffline
v-show="locale === 'zh'"
class="check-zh"
:icon="Check"
class="check-zh"
/>
简体中文
</el-dropdown-item>
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')"
:class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
:style="getDropdownItemStyle(locale, 'en')"
@click="translationEn"
>
<span v-show="locale === 'en'" class="check-en">
@ -89,7 +89,7 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<!-- 退出登录 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover select-none">
<img :src="userAvatar" :style="avatarsStyle" />
<img :src="userAvatar" :style="avatarsStyle" alt="" />
<p v-if="username" class="dark:text-white">{{ username }}</p>
</span>
<template #dropdown>
@ -99,14 +99,14 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
:icon="LogoutCircleRLine"
style="margin: 5px"
/>
{{ t("buttons.pureLoginOut") }}
{{ $t("buttons.pureLoginOut") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span
:title="$t('buttons.pureOpenSystemSet')"
class="set-icon navbar-bg-hover"
:title="t('buttons.pureOpenSystemSet')"
@click="onPanel"
>
<IconifyIconOffline :icon="Setting" />

View File

@ -1,8 +1,7 @@
<script setup lang="ts">
<script lang="ts" setup>
import { PropType } from "vue";
import { ListItem } from "../data";
import NoticeItem from "./NoticeItem.vue";
import { transformI18n } from "@/plugins/i18n";
defineProps({
list: {
@ -20,5 +19,5 @@ defineProps({
<div v-if="list.length">
<NoticeItem v-for="(item, index) in list" :key="index" :noticeItem="item" />
</div>
<el-empty v-else :description="transformI18n(emptyText)" />
<el-empty v-else :description="emptyText" />
</template>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref, computed } from "vue";
import { computed, ref } from "vue";
import { noticesData } from "./data";
import NoticeList from "./components/NoticeList.vue";
import BellIcon from "@iconify-icons/ep/bell";
@ -14,12 +14,12 @@ notices.value.map(v => (noticesNum.value += v.list.length));
const getLabel = computed(
() => item =>
t(item.name) + (item.list.length > 0 ? `(${item.list.length})` : "")
item.name + (item.list.length > 0 ? `(${item.list.length})` : "")
);
</script>
<template>
<el-dropdown trigger="click" placement="bottom-end">
<el-dropdown placement="bottom-end" trigger="click">
<span
:class="[
'dropdown-badge',
@ -28,7 +28,7 @@ const getLabel = computed(
Number(noticesNum) !== 0 && 'mr-[10px]'
]"
>
<el-badge :value="Number(noticesNum) === 0 ? '' : noticesNum" :max="99">
<el-badge :max="99" :value="Number(noticesNum) === 0 ? '' : noticesNum">
<span class="header-notice-icon">
<IconifyIconOffline :icon="BellIcon" />
</span>
@ -39,8 +39,8 @@ const getLabel = computed(
<el-tabs
v-model="activeKey"
:stretch="true"
class="dropdown-tabs"
:style="{ width: notices.length === 0 ? '200px' : '330px' }"
class="dropdown-tabs"
>
<el-empty
v-if="notices.length === 0"
@ -52,7 +52,7 @@ const getLabel = computed(
<el-tab-pane :label="getLabel(item)" :name="`${item.key}`">
<el-scrollbar max-height="330px">
<div class="noticeList-container">
<NoticeList :list="item.list" :emptyText="item.emptyText" />
<NoticeList :emptyText="item.emptyText" :list="item.list" />
</div>
</el-scrollbar>
</el-tab-pane>

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
<script lang="ts" setup>
import { useRenderIcon } from "@/components/CommonIcon/src/hooks";
import { transformI18n } from "@/plugins/i18n";
import CloseIcon from "@iconify-icons/ep/close";
import StarIcon from "@iconify-icons/ep/star";
import type { optionsItem } from "../types";
import { $t } from "../../../../plugins/i18n";
interface Props {
item: optionsItem;
@ -11,6 +11,7 @@ interface Props {
interface Emits {
(e: "collectItem", val: optionsItem): void;
(e: "deleteItem", val: optionsItem): void;
}
@ -29,7 +30,7 @@ function handleDelete(item) {
<template>
<component :is="useRenderIcon(item.meta?.icon)" />
<span class="history-item-title">
{{ transformI18n(item.meta?.title) }}
{{ $t(item.meta?.title) }}
</span>
<IconifyIconOffline
v-show="item.type === 'history'"

View File

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { match } from "pinyin-pro";
import { useI18n } from "vue-i18n";
import { getConfig } from "@/config";
@ -6,14 +6,14 @@ import { useRouter } from "vue-router";
import SearchResult from "./SearchResult.vue";
import SearchFooter from "./SearchFooter.vue";
import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import SearchHistory from "./SearchHistory.vue";
import type { optionsItem, dragItem } from "../types";
import { ref, computed, shallowRef, watch } from "vue";
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
import type { dragItem, optionsItem } from "../types";
import { computed, ref, shallowRef, watch } from "vue";
import { onKeyStroke, useDebounceFn } from "@vueuse/core";
import { usePermissionStoreHook } from "@/store/modules/permission";
import { cloneDeep, isAllEmpty, storageLocal } from "@pureadmin/utils";
import SearchIcon from "@iconify-icons/ri/search-line";
import { $t } from "@/plugins/i18n";
interface Props {
/** 弹窗显隐 */
@ -95,12 +95,14 @@ function setStorageItem(key, value) {
/** 将菜单树形结构扁平化为一维数组,用于菜单查询 */
function flatTree(arr) {
const res = [];
function deep(arr) {
arr.forEach(item => {
res.push(item);
item.children && deep(item.children);
});
}
deep(arr);
return res;
}
@ -110,13 +112,13 @@ function search() {
const flatMenusData = flatTree(menusData.value);
resultOptions.value = flatMenusData.filter(menu =>
keyword.value
? transformI18n(menu.meta?.title)
? $t(menu.meta?.title)
.toLocaleLowerCase()
.includes(keyword.value.toLocaleLowerCase().trim()) ||
(locale.value === "zh" &&
!isAllEmpty(
match(
transformI18n(menu.meta?.title).toLocaleLowerCase(),
$t(menu.meta?.title).toLocaleLowerCase(),
keyword.value.toLocaleLowerCase().trim()
)
))
@ -276,24 +278,24 @@ onKeyStroke("ArrowDown", handleDown);
<template>
<el-dialog
v-model="show"
top="5vh"
class="pure-search-dialog"
:show-close="false"
:width="device === 'mobile' ? '80vw' : '40vw'"
:before-close="handleClose"
:show-close="false"
:style="{
borderRadius: '6px'
}"
:width="device === 'mobile' ? '80vw' : '40vw'"
append-to-body
@opened="inputRef.focus()"
class="pure-search-dialog"
top="5vh"
@closed="inputRef.blur()"
@opened="inputRef.focus()"
>
<el-input
ref="inputRef"
v-model="keyword"
size="large"
clearable
:placeholder="t('search.purePlaceholder')"
clearable
size="large"
@input="handleSearch"
>
<template #prefix>
@ -312,8 +314,8 @@ onKeyStroke("ArrowDown", handleDown);
v-model:value="historyPath"
:options="historyOptions"
@click="handleEnter"
@delete="handleDelete"
@collect="handleCollect"
@delete="handleDelete"
@drag="handleDrag"
/>
<SearchResult

View File

@ -1,14 +1,15 @@
<script setup lang="ts">
<script lang="ts" setup>
import EnterOutlined from "@/assets/svg/enter_outlined.svg?component";
import { useRenderIcon } from "@/components/CommonIcon/src/hooks";
import { transformI18n } from "@/plugins/i18n";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { useResizeObserver } from "@pureadmin/utils";
import { computed, getCurrentInstance, onMounted, ref } from "vue";
import type { Props } from "../types";
import { $t } from "../../../../plugins/i18n";
interface Emits {
(e: "update:value", val: string): void;
(e: "enter"): void;
}
@ -75,14 +76,14 @@ defineExpose({ handleScroll });
v-for="(item, index) in options"
:key="item.path"
:ref="'resultItemRef' + index"
class="result-item dark:bg-[#1d1d1d]"
:style="itemStyle(item)"
class="result-item dark:bg-[#1d1d1d]"
@click="handleTo"
@mouseenter="handleMouse(item)"
>
<component :is="useRenderIcon(item.meta?.icon)" />
<span class="result-item-title">
{{ transformI18n(item.meta?.title) }}
{{ $t(item.meta?.title) }}
</span>
<EnterOutlined />
</div>

View File

@ -234,8 +234,8 @@ const markOptions = computed<Array<OptionsType>>(() => {
value: "card"
},
{
label: t("panel.pureTagsStyleChrome"),
tip: t("panel.pureTagsStyleChromeTip"),
label: "panel.pureTagsStyleChrome",
tip: "panel.pureTagsStyleChromeTip",
value: "chrome"
}
];

View File

@ -1,21 +1,21 @@
<script setup lang="ts">
<script lang="ts" setup>
import { emitter } from "@/utils/mitt";
import { useNav } from "@/layout/hooks/useNav";
import LaySearch from "../lay-search/index.vue";
import LayNotice from "../lay-notice/index.vue";
import { responsiveStorageNameSpace } from "@/config";
import { ref, nextTick, computed, onMounted } from "vue";
import { storageLocal, isAllEmpty } from "@pureadmin/utils";
import { computed, nextTick, onMounted, ref } from "vue";
import { isAllEmpty, storageLocal } from "@pureadmin/utils";
import { useTranslationLang } from "../../hooks/useTranslationLang";
import { usePermissionStoreHook } from "@/store/modules/permission";
import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
import GlobalizationIcon from "@/assets/svg/globalization.svg?component";
import AccountSettingsIcon from "@iconify-icons/ri/user-settings-line";
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
import Setting from "@iconify-icons/ri/settings-3-line";
import Check from "@iconify-icons/ep/check";
import { $t } from "@/plugins/i18n";
const menuRef = ref();
const showLogo = ref(
@ -65,16 +65,16 @@ onMounted(() => {
</div>
<el-menu
ref="menuRef"
:default-active="defaultActive"
class="horizontal-header-menu"
mode="horizontal"
popper-class="pure-scrollbar"
class="horizontal-header-menu"
:default-active="defaultActive"
>
<LaySidebarItem
v-for="route in usePermissionStoreHook().wholeMenus"
:key="route.path"
:item="route"
:base-path="route.path"
:item="route"
/>
</el-menu>
<div class="horizontal-header-right">
@ -88,8 +88,8 @@ onMounted(() => {
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')"
:class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
:style="getDropdownItemStyle(locale, 'zh')"
@click="translationCh"
>
<span v-show="locale === 'zh'" class="check-zh">
@ -98,8 +98,8 @@ onMounted(() => {
简体中文
</el-dropdown-item>
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')"
:class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
:style="getDropdownItemStyle(locale, 'en')"
@click="translationEn"
>
<span v-show="locale === 'en'" class="check-en">
@ -127,14 +127,14 @@ onMounted(() => {
:icon="LogoutCircleRLine"
style="margin: 5px"
/>
{{ t("buttons.pureLoginOut") }}
{{ $t("buttons.pureLoginOut") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span
:title="$t('buttons.pureOpenSystemSet')"
class="set-icon navbar-bg-hover"
:title="t('buttons.pureOpenSystemSet')"
@click="onPanel"
>
<IconifyIconOffline :icon="Setting" />

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
<script lang="ts" setup>
import { useRenderIcon } from "@/components/CommonIcon/src/hooks";
import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import { findRouteByPath, getParentPaths } from "@/router/utils";
import { usePermissionStoreHook } from "@/store/modules/permission";
import { isAllEmpty } from "@pureadmin/utils";
@ -16,11 +15,13 @@ import GlobalizationIcon from "@/assets/svg/globalization.svg?component";
import Check from "@iconify-icons/ep/check";
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
import Setting from "@iconify-icons/ri/settings-3-line";
import { useI18n } from "vue-i18n";
import { $t } from "@/plugins/i18n";
const menuRef = ref();
const defaultActive = ref(null);
const { t, route, locale, translationCh, translationEn } =
const { t } = useI18n();
const { route, locale, translationCh, translationEn } =
useTranslationLang(menuRef);
const {
device,
@ -68,11 +69,11 @@ watch(
>
<el-menu
ref="menuRef"
router
:default-active="defaultActive"
class="horizontal-header-menu"
mode="horizontal"
popper-class="pure-scrollbar"
class="horizontal-header-menu"
:default-active="defaultActive"
router
>
<el-menu-item
v-for="route in usePermissionStoreHook().wholeMenus"
@ -90,7 +91,7 @@ watch(
</div>
<div :style="getDivStyle">
<span class="select-none">
{{ transformI18n(route.meta.title) }}
{{ t(route.meta.title) }}
</span>
<LaySidebarExtraIcon :extraIcon="route.meta.extraIcon" />
</div>
@ -108,8 +109,8 @@ watch(
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')"
:class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
:style="getDropdownItemStyle(locale, 'zh')"
@click="translationCh"
>
<span v-show="locale === 'zh'" class="check-zh">
@ -118,8 +119,8 @@ watch(
简体中文
</el-dropdown-item>
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')"
:class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
:style="getDropdownItemStyle(locale, 'en')"
@click="translationEn"
>
<span v-show="locale === 'en'" class="check-en">
@ -147,14 +148,14 @@ watch(
:icon="LogoutCircleRLine"
style="margin: 5px"
/>
{{ t("buttons.pureLoginOut") }}
{{ $t("buttons.pureLoginOut") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span
:title="$t('buttons.pureOpenSystemSet')"
class="set-icon navbar-bg-hover"
:title="t('buttons.pureOpenSystemSet')"
@click="onPanel"
>
<IconifyIconOffline :icon="Setting" />

View File

@ -1,10 +1,10 @@
<script setup lang="ts">
<script lang="ts" setup>
import { isEqual } from "@pureadmin/utils";
import { transformI18n } from "@/plugins/i18n";
import { useRoute, useRouter } from "vue-router";
import { ref, watch, onMounted, toRaw } from "vue";
import { getParentPaths, findRouteByPath } from "@/router/utils";
import { onMounted, ref, toRaw, watch } from "vue";
import { findRouteByPath, getParentPaths } from "@/router/utils";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { $t } from "@/plugins/i18n";
const route = useRoute();
const levelList = ref([]);
@ -113,7 +113,7 @@ watch(
class="!inline !items-stretch"
>
<a @click.prevent="handleLink(item)">
{{ transformI18n(item.meta.title) }}
{{ $t(item.meta.title) }}
</a>
</el-breadcrumb-item>
</transition-group>

View File

@ -4,7 +4,6 @@ import { ReText } from "@/components/Text";
import { getConfig } from "@/config";
import { useNav } from "@/layout/hooks/useNav";
import { menuType } from "@/layout/types";
import { transformI18n } from "@/plugins/i18n";
import path from "path";
import {
computed,
@ -17,6 +16,7 @@ import {
import SidebarExtraIcon from "./SidebarExtraIcon.vue";
import SidebarLinkItem from "./SidebarLinkItem.vue";
import { $t } from "@/plugins/i18n";
import EpArrowDown from "@iconify-icons/ep/arrow-down-bold";
import ArrowLeft from "@iconify-icons/ep/arrow-left-bold";
import ArrowRight from "@iconify-icons/ep/arrow-right-bold";
@ -147,7 +147,7 @@ function resolvePath(routePath) {
class="!w-full !pl-4 !text-inherit"
truncated
>
{{ transformI18n(onlyOneChild.meta.title) }}
{{ $t(onlyOneChild.meta.title) }}
</el-text>
<template #title>
@ -159,7 +159,7 @@ function resolvePath(routePath) {
}"
class="!w-full !text-inherit"
>
{{ transformI18n(onlyOneChild.meta.title) }}
{{ $t(onlyOneChild.meta.title) }}
</ReText>
<SidebarExtraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
</div>
@ -206,7 +206,7 @@ function resolvePath(routePath) {
theme: tooltipEffect
}"
>
{{ transformI18n(item.meta.title) }}
{{ $t(item.meta.title) }}
</ReText>
<SidebarExtraIcon v-if="!isCollapse" :extraIcon="item.meta.extraIcon" />
</template>

View File

@ -1,28 +1,28 @@
<script setup lang="ts">
import { $t } from "@/plugins/i18n";
import { emitter } from "@/utils/mitt";
import { RouteConfigs } from "../../types";
import { useTags } from "../../hooks/useTag";
<script lang="ts" setup>
import { routerArrays } from "@/layout/types";
import { onClickOutside } from "@vueuse/core";
import TagChrome from "./components/TagChrome.vue";
import { handleAliveRoute, getTopMenu } from "@/router/utils";
import { useSettingStoreHook } from "@/store/modules/settings";
import { $t } from "@/plugins/i18n";
import { getTopMenu, handleAliveRoute } from "@/router/utils";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { usePermissionStoreHook } from "@/store/modules/permission";
import { ref, watch, unref, toRaw, nextTick, onBeforeUnmount } from "vue";
import { useSettingStoreHook } from "@/store/modules/settings";
import { emitter } from "@/utils/mitt";
import {
delay,
isEqual,
isAllEmpty,
isEqual,
useResizeObserver
} from "@pureadmin/utils";
import { onClickOutside } from "@vueuse/core";
import { nextTick, onBeforeUnmount, ref, toRaw, unref, watch } from "vue";
import { useTags } from "../../hooks/useTag";
import { RouteConfigs } from "../../types";
import TagChrome from "./components/TagChrome.vue";
import ArrowDown from "@iconify-icons/ri/arrow-down-s-line";
import ArrowLeftSLine from "@iconify-icons/ri/arrow-left-s-line";
import ArrowRightSLine from "@iconify-icons/ri/arrow-right-s-line";
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
import ArrowDown from "@iconify-icons/ri/arrow-down-s-line";
import ArrowRightSLine from "@iconify-icons/ri/arrow-right-s-line";
import ArrowLeftSLine from "@iconify-icons/ri/arrow-left-s-line";
const {
Close,
@ -51,7 +51,6 @@ const {
onMounted,
onMouseenter,
onMouseleave,
transformI18n,
onContentFullScreen
} = useTags();
@ -201,6 +200,7 @@ function dynamicRouteTag(value: string): void {
});
}
}
concatPath(router.options.routes as any, value);
}
@ -398,6 +398,7 @@ function showMenuModel(
} else {
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
}
function fixedTagDisabled() {
if (allRoute[currentIndex]?.meta?.fixedTag) {
Array.of(1, 2, 3, 4, 5).forEach(v => {
@ -566,31 +567,31 @@ onBeforeUnmount(() => {
</span>
<div
ref="scrollbarDom"
class="scroll-container"
:class="showModel === 'chrome' && 'chrome-scroll-container'"
class="scroll-container"
@wheel.prevent="handleWheel"
>
<div ref="tabDom" class="tab select-none" :style="getTabStyle">
<div ref="tabDom" :style="getTabStyle" class="tab select-none">
<div
v-for="(item, index) in multiTags"
:ref="'dynamic' + index"
:key="index"
:ref="'dynamic' + index"
:class="[
'scroll-item is-closable',
linkIsActive(item),
showModel === 'chrome' && 'chrome-item',
isFixedTag(item) && 'fixed-tag'
]"
@click="tagOnClick(item)"
@contextmenu.prevent="openMenu(item, $event)"
@mouseenter.prevent="onMouseenter(index)"
@mouseleave.prevent="onMouseleave(index)"
@click="tagOnClick(item)"
>
<template v-if="showModel !== 'chrome'">
<span
class="tag-title dark:!text-text_color_primary dark:hover:!text-primary"
>
{{ transformI18n(item.meta.title) }}
{{ $t(item.meta.title) }}
</span>
<span
v-if="
@ -636,8 +637,8 @@ onBeforeUnmount(() => {
<transition name="el-zoom-in-top">
<ul
v-show="visible"
ref="contextmenuRef"
:key="Math.random()"
ref="contextmenuRef"
:style="getContextMenuStyle"
class="contextmenu"
>
@ -648,15 +649,15 @@ onBeforeUnmount(() => {
>
<li v-if="item.show" @click="selectTag(key, item)">
<IconifyIconOffline :icon="item.icon" />
{{ transformI18n(item.text) }}
{{ item.text }}
</li>
</div>
</ul>
</transition>
<!-- 右侧功能按钮 -->
<el-dropdown
trigger="click"
placement="bottom-end"
trigger="click"
@command="handleCommand"
>
<span class="arrow-down">
@ -668,11 +669,11 @@ onBeforeUnmount(() => {
v-for="(item, key) in tagsViews"
:key="key"
:command="{ key, item }"
:divided="item.divided"
:disabled="item.disabled"
:divided="item.divided"
>
<IconifyIconOffline :icon="item.icon" />
{{ transformI18n(item.text) }}
{{ item.text }}
</el-dropdown-item>
</el-dropdown-menu>
</template>

View File

@ -6,7 +6,6 @@ import Avatar from "@/assets/user.jpg";
import { getTopMenu } from "@/router/utils";
import { useFullscreen } from "@vueuse/core";
import type { routeMetaType } from "../types";
import { transformI18n } from "@/plugins/i18n";
import { remainingPaths, router } from "@/router";
import { computed, type CSSProperties } from "vue";
import { useAppStoreHook } from "@/store/modules/app";
@ -16,6 +15,7 @@ import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { usePermissionStoreHook } from "@/store/modules/permission";
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
import { $t } from "@/plugins/i18n";
const errorInfo =
"The current routing configuration is incorrect, please check the configuration";
@ -81,9 +81,7 @@ export function useNav() {
});
const { $storage, $config } = useGlobal<GlobalPropertiesApi>();
const layout = computed(() => {
return $storage?.layout?.layout;
});
const layout = computed(() => $storage?.layout?.layout);
const title = computed(() => {
return $config.Title;
@ -92,8 +90,8 @@ export function useNav() {
/** 动态title */
function changeTitle(meta: routeMetaType) {
const Title = getConfig().Title;
if (Title) document.title = `${transformI18n(meta.title)} | ${Title}`;
else document.title = transformI18n(meta.title);
if (Title) document.title = `${meta.title} | ${Title}`;
else document.title = $t(meta.title);
}
/** 退出登录 */

View File

@ -1,24 +1,24 @@
import {
ref,
unref,
computed,
reactive,
onMounted,
type CSSProperties,
getCurrentInstance
getCurrentInstance,
onMounted,
reactive,
ref,
unref
} from "vue";
import type { tagsViewsType } from "../types";
import { useRoute, useRouter } from "vue-router";
import { transformI18n, $t } from "@/plugins/i18n";
import { $t } from "@/plugins/i18n";
import { responsiveStorageNameSpace } from "@/config";
import { useSettingStoreHook } from "@/store/modules/settings";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import {
isEqual,
hasClass,
isBoolean,
isEqual,
storageLocal,
toggleClass,
hasClass
toggleClass
} from "@pureadmin/utils";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
@ -73,35 +73,35 @@ export function useTags() {
icon: Close,
text: $t("buttons.pureCloseCurrentTab"),
divided: false,
disabled: multiTags.value.length > 1 ? false : true,
disabled: multiTags.value.length <= 1,
show: true
},
{
icon: CloseLeftTags,
text: $t("buttons.pureCloseLeftTabs"),
divided: true,
disabled: multiTags.value.length > 1 ? false : true,
disabled: multiTags.value.length <= 1,
show: true
},
{
icon: CloseRightTags,
text: $t("buttons.pureCloseRightTabs"),
divided: false,
disabled: multiTags.value.length > 1 ? false : true,
disabled: multiTags.value.length <= 1,
show: true
},
{
icon: CloseOtherTags,
text: $t("buttons.pureCloseOtherTabs"),
divided: true,
disabled: multiTags.value.length > 2 ? false : true,
disabled: multiTags.value.length <= 2,
show: true
},
{
icon: CloseAllTags,
text: $t("buttons.pureCloseAllTabs"),
divided: false,
disabled: multiTags.value.length > 1 ? false : true,
disabled: multiTags.value.length <= 1,
show: true
},
{
@ -242,7 +242,6 @@ export function useTags() {
onMounted,
onMouseenter,
onMouseleave,
transformI18n,
onContentFullScreen
};
}

View File

@ -1,11 +1,11 @@
import { useNav } from "./useNav";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import { watch, onBeforeMount, type Ref } from "vue";
import { onBeforeMount, type Ref, watch } from "vue";
export function useTranslationLang(ref?: Ref) {
const { $storage, changeTitle, handleResize } = useNav();
const { locale, t } = useI18n();
const { locale } = useI18n();
const route = useRoute();
function translationCh() {
@ -32,7 +32,6 @@ export function useTranslationLang(ref?: Ref) {
});
return {
t,
route,
locale,
translationCh,

View File

@ -1,4 +1,5 @@
import type { IconifyIcon } from "@iconify/vue";
const { VITE_HIDE_HOME } = import.meta.env;
export const routerArrays: Array<RouteConfigs> =
@ -7,7 +8,7 @@ export const routerArrays: Array<RouteConfigs> =
{
path: "/welcome",
meta: {
title: "menus.pureHome",
title: "menus.home",
icon: "ep:home-filled"
}
}

View File

@ -47,7 +47,6 @@ Object.keys(directives).forEach(key => {
app.component("IconifyIconOffline", IconifyIconOffline);
app.component("IconifyIconOnline", IconifyIconOnline);
app.component("FontIcon", FontIcon);
app.component("Auth", Auth);
app.component("Perms", Perms);

View File

@ -1,116 +1,24 @@
// 多组件库的国际化和本地项目国际化兼容
import { type I18n, createI18n } from "vue-i18n";
import type { App, WritableComputedRef } from "vue";
import { responsiveStorageNameSpace } from "@/config";
import { storageLocal, isObject } from "@pureadmin/utils";
import { createI18n } from "vue-i18n";
import type { App } from "vue";
// element-plus国际化
import enLocale from "element-plus/es/locale/lang/en";
import zhLocale from "element-plus/es/locale/lang/zh-cn";
// ? 从本地存储中获取数据
const languageData = localStorage.getItem("i18nStore");
const siphonI18n = (function () {
// 仅初始化一次国际化配置
let cache = Object.fromEntries(
Object.entries(
import.meta.glob("../../locales/*.y(a)?ml", { eager: true })
).map(([key, value]: any) => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)[1];
return [matched, value.default];
})
);
return (prefix = "zh-CN") => {
return cache[prefix];
};
})();
export const localesConfigs = {
zh: {
...siphonI18n("zh-CN"),
...zhLocale
},
en: {
...siphonI18n("en"),
...enLocale
}
};
/** 获取对象中所有嵌套对象的key键并将它们用点号分割组成字符串 */
function getObjectKeys(obj) {
const stack = [];
const keys: Set<string> = new Set();
stack.push({ obj, key: "" });
while (stack.length > 0) {
const { obj, key } = stack.pop();
for (const k in obj) {
const newKey = key ? `${key}.${k}` : k;
if (obj[k] && isObject(obj[k])) {
stack.push({ obj: obj[k], key: newKey });
} else {
keys.add(newKey);
}
}
}
return keys;
}
/** 将展开的key缓存 */
const keysCache: Map<string, Set<string>> = new Map();
const flatI18n = (prefix = "zh-CN") => {
let cache = keysCache.get(prefix);
if (!cache) {
cache = getObjectKeys(siphonI18n(prefix));
keysCache.set(prefix, cache);
}
return cache;
};
/**
* locales文件夹下文件进行国际化匹配
* @param message message
* @returns message
*/
export function transformI18n(message: any = "") {
if (!message) {
return "";
}
// 处理存储动态路由的title,格式 {zh:"",en:""}
if (typeof message === "object") {
const locale: string | WritableComputedRef<string> | any =
i18n.global.locale;
return message[locale?.value];
}
const key = message.match(/(\S*)\./)?.input;
if (key && flatI18n("zh-CN").has(key)) {
return i18n.global.t.call(i18n.global.locale, message);
} else if (!key && Object.hasOwn(siphonI18n("zh-CN"), message)) {
// 兼容非嵌套形式的国际化写法
return i18n.global.t.call(i18n.global.locale, message);
} else {
return message;
}
}
/** 此函数只是配合i18n Ally插件来进行国际化智能提示并无实际意义只对提示起作用如果不需要国际化可删除 */
export const $t = (key: string) => key;
export const i18n: I18n = createI18n({
// 配置多语言
export const i18n = createI18n({
// 如果要支持 compositionAPI此项必须设置为 false
legacy: false,
locale:
storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}locale`
)?.locale ?? "zh",
// locale: 'zh',
fallbackLocale: "en",
messages: localesConfigs
// ? 全局注册$t方法
globalInjection: true,
// 本地内容存在时,首次加载如果本地存储没有多语言需要再刷新
messages: languageData ? JSON.parse(languageData).i18n : {}
});
export const $t: any = (i18n.global as any).t as any;
export function useI18n(app: App) {
app.use(i18n);
}

View File

@ -2,34 +2,34 @@
import Cookies from "js-cookie";
import { getConfig } from "@/config";
import NProgress from "@/utils/progress";
import { transformI18n } from "@/plugins/i18n";
import { $t } from "@/plugins/i18n";
import { buildHierarchyTree } from "@/utils/tree";
import remainingRouter from "./modules/remaining";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { usePermissionStoreHook } from "@/store/modules/permission";
import { isUrl, openLink, storageLocal, isAllEmpty } from "@pureadmin/utils";
import { isAllEmpty, isUrl, openLink, storageLocal } from "@pureadmin/utils";
import {
ascending,
getTopMenu,
initRouter,
isOneOfArray,
getHistoryMode,
findRouteByPath,
handleAliveRoute,
formatFlatteningRoutes,
formatTwoStageRoutes,
formatFlatteningRoutes
getHistoryMode,
getTopMenu,
handleAliveRoute,
initRouter,
isOneOfArray
} from "./utils";
import {
type Router,
createRouter,
type RouteRecordRaw,
type RouteComponent
type RouteComponent,
type Router,
type RouteRecordRaw
} from "vue-router";
import {
type DataInfo,
userKey,
multipleTabsKey,
removeToken,
multipleTabsKey
userKey
} from "@/utils/auth";
/** src/router/modules .ts remaining.ts
@ -121,15 +121,16 @@ router.beforeEach((to: ToRouteType, _from, next) => {
to.matched.some(item => {
if (!item.meta.title) return "";
const Title = getConfig().Title;
if (Title)
document.title = `${transformI18n(item.meta.title)} | ${Title}`;
else document.title = transformI18n(item.meta.title);
if (Title) document.title = `${$t(item.meta.title)} | ${Title}`;
else document.title = $t(item.meta.title);
});
}
/** 如果已经登录并存在登录信息后不能跳转到路由白名单,而是继续保持在当前页面 */
function toCorrectRoute() {
whiteList.includes(to.fullPath) ? next(_from.fullPath) : next();
}
if (Cookies.get(multipleTabsKey) && userInfo) {
// 无权限跳转403页面
if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) {

View File

@ -5,8 +5,8 @@ export default {
redirect: "/error/403",
meta: {
icon: "ri:information-line",
// showLink: false,
title: $t("menus.pureAbnormal"),
showLink: false,
title: "menus.pureAbnormal",
rank: 9
},
children: [

View File

@ -1,4 +1,3 @@
import { $t } from "@/plugins/i18n";
const { VITE_HIDE_HOME } = import.meta.env;
const Layout = () => import("@/layout/index.vue");
@ -9,7 +8,7 @@ export default {
redirect: "/welcome",
meta: {
icon: "ep:home-filled",
title: $t("menus.pureHome"),
title: "menus.home",
rank: 0
},
children: [
@ -18,8 +17,8 @@ export default {
name: "Welcome",
component: () => import("@/views/welcome/index.vue"),
meta: {
title: $t("menus.pureHome"),
showLink: VITE_HIDE_HOME === "true" ? false : true
title: "menus.home",
showLink: VITE_HIDE_HOME !== "true"
}
}
]

View File

@ -1,4 +1,5 @@
import { $t } from "@/plugins/i18n";
const Layout = () => import("@/layout/index.vue");
export default [
@ -7,7 +8,7 @@ export default [
name: "Login",
component: () => import("@/views/login/index.vue"),
meta: {
title: $t("menus.pureLogin"),
title: "menus.pureLogin",
showLink: false,
rank: 101
}

34
src/store/i18n/i18n.ts Normal file
View File

@ -0,0 +1,34 @@
// import { fetchGetI18n } from '@/api/mock/i18n';
import { defineStore } from "pinia";
import { fetchGetI18n } from "@/api/v1/i18n/i18n";
import type { I18nState } from "../../../types/store/i18n";
export const userI18nStore = defineStore("i18nStore", {
persist: true,
state(): I18nState {
return {
// ? 多语言内容
i18n: {}
};
},
getters: {},
actions: {
/**
* *
*/
async fetchI18n() {
const result = await fetchGetI18n();
if (result.code === 200) {
localStorage.removeItem("i18nStore");
// 当前的返回参数
const data = result.data;
// 将返回对象中key设置name后端不好设置
for (let key in data) if (key !== "local") data[key].name = key;
// 赋值返回参数
this.i18n = data;
}
}
}
});

View File

@ -1,8 +1,11 @@
import type { App } from "vue";
import { createPinia } from "pinia";
import piniaPluginPersistedState from "pinia-plugin-persistedstate";
const store = createPinia();
export function setupStore(app: App<Element>) {
store.use(piniaPluginPersistedState);
app.use(store);
}

View File

@ -6,7 +6,7 @@ import { message } from "@/utils/message";
import { loginRules } from "./utils/rule";
import { useNav } from "@/layout/hooks/useNav";
import type { FormInstance } from "element-plus";
import { $t, transformI18n } from "@/plugins/i18n";
import { $t } from "@/plugins/i18n";
import { useLayout } from "@/layout/hooks/useLayout";
import { useUserStoreHook } from "@/store/modules/user";
import { getTopMenu, initRouter } from "@/router/utils";
@ -56,11 +56,11 @@ const onLogin = async (formEl: FormInstance | undefined) => {
//
return initRouter().then(() => {
router.push(getTopMenu(true).path).then(() => {
message(t("login.pureLoginSuccess"), { type: "success" });
message(t("login.loginSuccess"), { type: "success" });
});
});
} else {
message(t("login.pureLoginFail"), { type: "error" });
message(t("login.loginFail"), { type: "error" });
}
})
.finally(() => (loading.value = false));
@ -86,7 +86,7 @@ onBeforeUnmount(() => {
<template>
<div class="select-none">
<img :src="bg" class="wave" />
<img :src="bg" alt="" class="wave" />
<div class="flex-c absolute right-5 top-3">
<!-- 主题 -->
<el-switch
@ -151,7 +151,7 @@ onBeforeUnmount(() => {
:rules="[
{
required: true,
message: transformI18n($t('login.pureUsernameReg')),
message: $t('login.usernameRegex'),
trigger: 'blur'
}
]"
@ -159,7 +159,7 @@ onBeforeUnmount(() => {
>
<el-input
v-model="ruleForm.username"
:placeholder="t('login.pureUsername')"
:placeholder="t('login.username')"
:prefix-icon="useRenderIcon(User)"
clearable
/>
@ -170,7 +170,7 @@ onBeforeUnmount(() => {
<el-form-item prop="password">
<el-input
v-model="ruleForm.password"
:placeholder="t('login.purePassword')"
:placeholder="t('login.password')"
:prefix-icon="useRenderIcon(Lock)"
clearable
show-password
@ -186,7 +186,7 @@ onBeforeUnmount(() => {
type="primary"
@click="onLogin(ruleFormRef)"
>
{{ t("login.pureLogin") }}
{{ t("login.login") }}
</el-button>
</Motion>
</el-form>

View File

@ -1,6 +1,6 @@
import { reactive } from "vue";
import type { FormRules } from "element-plus";
import { $t, transformI18n } from "@/plugins/i18n";
import { $t } from "@/plugins/i18n";
/** 密码正则密码格式应为8-18位数字、字母、符号的任意两种组合 */
export const REGEXP_PWD =
@ -12,9 +12,9 @@ const loginRules = reactive(<FormRules>{
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.purePassWordReg"))));
callback(new Error($t("login.purePassWordReg")));
} else if (!REGEXP_PWD.test(value)) {
callback(new Error(transformI18n($t("login.purePassWordRuleReg"))));
callback(new Error($t("login.purePassWordRuleReg")));
} else {
callback();
}

View File

@ -0,0 +1,6 @@
// 返回响应内容
export interface Result<T> {
code: number;
data: T;
message: string;
}

4
types/store/i18n.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
// i18n 仓库state
export interface I18nState {
i18n: any;
}