Compare commits

..

75 Commits
i18n ... v6.1.0

Author SHA1 Message Date
xiaoxian521
6106ebf2cc release: update 6.1.0 2025-08-07 15:50:34 +08:00
xiaoxian521
df315bb7d2 docs: update README 2025-05-27 18:51:15 +08:00
xiaoxian521
d2543cf9d3 release: update 6.0.0 2025-04-25 09:50:52 +08:00
xiaoxian521
13a36c0acb release: update 5.9.0 2024-12-15 10:45:15 +08:00
xiaoxian521
85e5074665 chore: update 2024-08-20 16:04:50 +08:00
xiaoxian521
a8a1f8d900 release: update 5.8.0 2024-08-20 09:33:07 +08:00
xiaoxian521
c0f3dcbbb7 chore: 同步完整版代码 2024-07-03 11:52:17 +08:00
xiaoxian521
c6b0aa2467 chore(deps): update 2024-06-14 14:20:29 +08:00
xiaoxian521
057fef9147 chore: 同步完整版代码 2024-06-13 12:17:24 +08:00
xiaoxian521
036459cb02 docs: update 2024-06-12 07:45:07 +08:00
xiaoxian521
7640f97a36 release: update 5.7.0 2024-06-07 09:31:37 +08:00
xiaoxian521
0a57ad7920 chore: 同步完整版代码 2024-05-28 14:05:39 +08:00
xiaoxian521
a4f857124f release: update 5.6.0 2024-05-14 12:09:08 +08:00
xiaoxian521
0d8f6b314b release: update 5.5.0 2024-05-10 12:09:23 +08:00
xiaoxian521
923fdd9ff1 chore: 同步完整版代码 2024-04-25 12:11:21 +08:00
xiaoxian521
e29340422a chore: 同步完整版代码 2024-04-23 11:05:13 +08:00
xiaoxian521
e25f4bcf39 release: update 5.4.0 2024-04-22 14:15:05 +08:00
xiaoxian521
270df1b17a release: update 5.3.0 2024-03-29 00:11:15 +08:00
xiaoxian521
fda66ee37c chore: 同步完整版代码 2024-03-23 08:58:13 +08:00
xiaoming
dcd687fe86 release: update 5.2.0
release: update `5.2.0`
2024-03-22 21:20:24 +08:00
xiaoxian521
1f2116c6b9 chore: update 2024-03-22 21:18:24 +08:00
xiaoxian521
23db7512d0 chore: update 2024-03-22 21:15:33 +08:00
xiaoxian521
03d68a24d9 release: update 5.2.0 2024-03-22 20:47:17 +08:00
xiaoming
51a4a6d246 release: update 5.1.0
release: update `5.1.0`
2024-03-09 19:33:07 +08:00
xiaoxian521
f64cd466eb chore: update 2024-03-09 19:29:05 +08:00
xiaoxian521
e87134a635 chore: 测试完毕 2024-03-09 18:45:22 +08:00
xiaoxian521
ef184a8636 Files changed完全检查 2024-03-09 18:03:02 +08:00
xiaoxian521
afc9ad0273 chore: 第二次同步 2024-03-09 17:07:18 +08:00
xiaoxian521
1121c5a2b5 release: update 5.1.0 2024-03-09 16:27:52 +08:00
xiaoxian521
ff27074ebd chore: 同步完整版代码 2023-06-26 18:12:39 +08:00
xiaoxian521
1f27d6cd9e release: update 4.5.0 2023-06-26 12:32:11 +08:00
xiaoxian521
4b435d0e0f chore: 同步完整版代码 2023-06-19 12:04:37 +08:00
xiaoxian521
872e0bbd5b chore: 同步完整版代码 2023-06-15 18:38:39 +08:00
xiaoxian521
b6859d7920 release: update 4.4.0 2023-06-14 16:29:45 +08:00
xiaoxian521
6e02ae14a0 chore: 同步完整版代码 2023-06-07 11:40:26 +08:00
xiaoxian521
c28066fb1f feat: 添加vscode-docker插件 2023-06-05 23:02:54 +08:00
xiaoxian521
d9ab1b1198 release: update 4.3.0 2023-06-05 15:44:49 +08:00
xiaoxian521
4e95672cb4 release: update 4.1.0 2023-05-12 13:27:40 +08:00
xiaoxian521
9c0872fa6a perf: export addPathMatch utils 2023-03-07 21:02:36 +08:00
xiaoxian521
7678aa64e2 chore: update propTypes 2023-03-01 20:03:47 +08:00
xiaoxian521
1e30b31be2 chore: 同步完整版代码 2023-02-28 22:42:12 +08:00
xiaoxian521
2c887ee1d9 fix: 调整登录页 initRouter方法顺序,防止登录页热更新报错 2023-02-12 18:58:39 +08:00
xiaoxian521
635da1dc52 chore: update link 2023-02-08 20:10:08 +08:00
xiaoxian521
6f65e67872 release: update 3.9.7 2022-12-26 14:51:38 +08:00
xiaoxian521
c79862a6c8 chore: update 2022-12-23 18:41:27 +08:00
xiaoxian521
799643a283 docs: update README.md 2022-12-21 11:53:54 +08:00
xiaoxian521
707200c71d release: update 3.9.6 2022-12-19 13:45:28 +08:00
xiaoxian521
bc548d500c chore: update @pureadmin/theme latest 2022-12-15 12:41:02 +08:00
xiaoxian521
4d57c9e3d7 release: update 3.9.5 2022-12-13 14:37:56 +08:00
xiaoxian521
1bcf391513 fix: 修复暗黑主题样式问题 2022-12-09 20:35:50 +08:00
xiaoxian521
30af2b78fe chore: update 2022-12-09 12:45:47 +08:00
xiaoxian521
0b1bd19179 fix: 修复动态路由 rank 问题 2022-12-07 17:22:07 +08:00
xiaoxian521
4ccf200059 perf: 优化路由 rank,当rank 不存在时,根据顺序自动创建,首页路由永远在第一位 2022-12-06 21:18:12 +08:00
xiaoxian521
27056e7560 release: update 3.9.4 2022-12-05 14:28:46 +08:00
xiaoxian521
132fbbade3 release: update 3.9.3 2022-12-04 18:00:16 +08:00
xiaoxian521
c6e25d6933 release: update 3.9.2 2022-12-03 15:20:30 +08:00
xiaoxian521
cabf1f85ef release: update 3.9.1 2022-12-02 20:20:50 +08:00
xiaoxian521
cb3d7cd552 perf: 优化 initRouter ,兼容 sso 场景 2022-12-01 16:05:06 +08:00
xiaoxian521
80453ec4b1 feat: 添加 CachingAsyncRoutes 是否开启动态路由缓存本地,默认 true 2022-12-01 11:42:10 +08:00
xiaoxian521
8ca8bbcee0 chore: update 2022-11-30 18:20:56 +08:00
xiaoxian521
2b67efe771 release: update 3.9.0 2022-11-30 16:33:46 +08:00
xiaoxian521
bbe23ce0a2 release: update 3.8.7 2022-11-28 01:49:35 +08:00
xiaoxian521
68492ec362 docs: update 2022-11-27 18:05:55 +08:00
xiaoxian521
bc3b199860 release: update 3.8.6 2022-11-27 17:40:35 +08:00
xiaoxian521
4407dd5d10 perf: 无需安装 @vue/runtime-core,兼容 element-plus 的组件 volar 提示 2022-11-27 09:56:19 +08:00
xiaoxian521
b3cbdd6e87 release: update 3.8.5 2022-11-27 00:33:24 +08:00
xiaoxian521
b5f0ca52ce perf: 移除 @pureadmin/components , 打包大小未启用压缩前减少 0.48 MB , 首屏请求减少 2.3 MB 的资源,请务必升级哦 2022-11-26 21:23:43 +08:00
xiaoxian521
6110be29a1 release: update 3.8.0 2022-11-26 19:14:08 +08:00
xiaoxian521
d53496e495 chore: 同步完整版代码 2022-11-11 11:53:54 +08:00
xiaoxian521
1bafbeaab7 release: update 3.6.4 2022-11-10 12:28:10 +08:00
xiaoxian521
9a802296c7 perf: 优化路由守卫 2022-11-08 18:17:33 +08:00
xiaoxian521
e183ea75a0 feat: 菜单图标 icon 支持使用在线图标 2022-11-08 12:10:54 +08:00
xiaoxian521
969775c7cf perf: 优化代码 2022-11-08 01:25:44 +08:00
xiaoxian521
841c5bd53a release: update 3.6.3 2022-11-01 16:29:32 +08:00
xiaoxian521
761b0e5ec2 feat: 提交非国际化版本代码 2022-10-28 15:32:31 +08:00
63 changed files with 1807 additions and 2797 deletions

2
.nvmrc
View File

@@ -1 +1 @@
v22.20.0 v22.17.1

View File

@@ -9,7 +9,6 @@
"bradlc.vscode-tailwindcss", "bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"lokalise.i18n-ally",
"redhat.vscode-yaml", "redhat.vscode-yaml",
"csstools.postcss", "csstools.postcss",
"mikestead.dotenv", "mikestead.dotenv",

14
.vscode/settings.json vendored
View File

@@ -1,5 +1,4 @@
{ {
"tailwindCSS.experimental.configFile": "src/style/tailwind.css",
"editor.formatOnType": true, "editor.formatOnType": true,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"[vue]": { "[vue]": {
@@ -28,19 +27,6 @@
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit" "source.fixAll.eslint": "explicit"
}, },
"i18n-ally.localesPaths": "locales",
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
"i18n-ally.enabledParsers": [
"yaml",
"js"
],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": [
"vue"
],
"iconify.excludes": [ "iconify.excludes": [
"el" "el"
], ],

View File

@@ -1,4 +1,4 @@
<h1>vue-pure-admin Lite Editioni18n version</h1> <h1>vue-pure-admin Lite Editionno i18n version</h1>
[![license](https://img.shields.io/github/license/pure-admin/vue-pure-admin.svg)](LICENSE) [![license](https://img.shields.io/github/license/pure-admin/vue-pure-admin.svg)](LICENSE)

View File

@@ -1,4 +1,4 @@
<h1>vue-pure-admin精简版国际化版本</h1> <h1>vue-pure-admin精简版国际化版本)</h1>
[![license](https://img.shields.io/github/license/pure-admin/vue-pure-admin.svg)](LICENSE) [![license](https://img.shields.io/github/license/pure-admin/vue-pure-admin.svg)](LICENSE)
@@ -10,7 +10,7 @@
## 版本选择 ## 版本选择
当前是国际化版本,如果您需要国际化版本 [请点击](https://github.com/pure-admin/pure-admin-thin) 当前是国际化版本,如果您需要国际化版本 [请点击](https://github.com/pure-admin/pure-admin-thin/tree/i18n)
## 配套视频 ## 配套视频

View File

@@ -19,11 +19,6 @@ export const cdn = importToCDN({
var: "VueRouter", var: "VueRouter",
path: "vue-router.global.min.js" path: "vue-router.global.min.js"
}, },
{
name: "vue-i18n",
var: "VueI18n",
path: "vue-i18n.runtime.global.prod.min.js"
},
// 项目中没有直接安装vue-demi但是pinia用到了所以需要在引入pinia前引入vue-demihttps://github.com/vuejs/pinia/blob/v2/packages/pinia/package.json#L77 // 项目中没有直接安装vue-demi但是pinia用到了所以需要在引入pinia前引入vue-demihttps://github.com/vuejs/pinia/blob/v2/packages/pinia/package.json#L77
{ {
name: "vue-demi", name: "vue-demi",

View File

@@ -10,7 +10,6 @@ const include = [
"dayjs", "dayjs",
"axios", "axios",
"pinia", "pinia",
"vue-i18n",
"vue-types", "vue-types",
"js-cookie", "js-cookie",
"vue-tippy", "vue-tippy",

View File

@@ -1,6 +1,5 @@
import { cdn } from "./cdn"; import { cdn } from "./cdn";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import { pathResolve } from "./utils";
import { viteBuildInfo } from "./info"; import { viteBuildInfo } from "./info";
import svgLoader from "vite-svg-loader"; import svgLoader from "vite-svg-loader";
import Icons from "unplugin-icons/vite"; import Icons from "unplugin-icons/vite";
@@ -11,7 +10,6 @@ import { configCompressPlugin } from "./compress";
import removeNoMatch from "vite-plugin-router-warn"; import removeNoMatch from "vite-plugin-router-warn";
import { visualizer } from "rollup-plugin-visualizer"; import { visualizer } from "rollup-plugin-visualizer";
import removeConsole from "vite-plugin-remove-console"; import removeConsole from "vite-plugin-remove-console";
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
import { codeInspectorPlugin } from "code-inspector-plugin"; import { codeInspectorPlugin } from "code-inspector-plugin";
import { vitePluginFakeServer } from "vite-plugin-fake-server"; import { vitePluginFakeServer } from "vite-plugin-fake-server";
@@ -25,9 +23,6 @@ export function getPluginsList(
vue(), vue(),
// jsx、tsx语法支持 // jsx、tsx语法支持
vueJsx(), vueJsx(),
VueI18nPlugin({
include: [pathResolve("../locales/**")]
}),
/** /**
* 在页面上按住组合键时,鼠标在页面移动即会在 DOM 上出现遮罩层并显示相关信息,点击一下将自动打开 IDE 并将光标定位到元素对应的代码位置 * 在页面上按住组合键时,鼠标在页面移动即会在 DOM 上出现遮罩层并显示相关信息,点击一下将自动打开 IDE 并将光标定位到元素对应的代码位置
* Mac 默认组合键 Option + Shift * Mac 默认组合键 Option + Shift

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
purePageNotFound: "404"
pureAccessDenied: "403"
pureServerError: "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: 异常页面
purePageNotFound: "404"
pureAccessDenied: "403"
pureServerError: "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

@@ -9,7 +9,7 @@ import { defineFakeRoute } from "vite-plugin-fake-server/client";
const permissionRouter = { const permissionRouter = {
path: "/permission", path: "/permission",
meta: { meta: {
title: "menus.purePermission", title: "权限管理",
icon: "ep:lollipop", icon: "ep:lollipop",
rank: 10 rank: 10
}, },
@@ -18,14 +18,14 @@ const permissionRouter = {
path: "/permission/page/index", path: "/permission/page/index",
name: "PermissionPage", name: "PermissionPage",
meta: { meta: {
title: "menus.purePermissionPage", title: "页面权限",
roles: ["admin", "common"] roles: ["admin", "common"]
} }
}, },
{ {
path: "/permission/button", path: "/permission/button",
meta: { meta: {
title: "menus.purePermissionButton", title: "按钮权限",
roles: ["admin", "common"] roles: ["admin", "common"]
}, },
children: [ children: [
@@ -34,7 +34,7 @@ const permissionRouter = {
component: "permission/button/index", component: "permission/button/index",
name: "PermissionButtonRouter", name: "PermissionButtonRouter",
meta: { meta: {
title: "menus.purePermissionButtonRouter", title: "路由返回按钮权限",
auths: [ auths: [
"permission:btn:add", "permission:btn:add",
"permission:btn:edit", "permission:btn:edit",
@@ -47,7 +47,7 @@ const permissionRouter = {
component: "permission/button/perms", component: "permission/button/perms",
name: "PermissionButtonLogin", name: "PermissionButtonLogin",
meta: { meta: {
title: "menus.purePermissionButtonLogin" title: "登录接口返回按钮权限"
} }
} }
] ]

View File

@@ -1,6 +1,6 @@
{ {
"name": "pure-admin-thin", "name": "pure-admin-thin",
"version": "6.2.0", "version": "6.1.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -33,7 +33,7 @@
"vite", "vite",
"esm" "esm"
], ],
"homepage": "https://github.com/pure-admin/pure-admin-thin/tree/i18n", "homepage": "https://github.com/pure-admin/pure-admin-thin",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/pure-admin/pure-admin-thin.git" "url": "git+https://github.com/pure-admin/pure-admin-thin.git"
@@ -51,76 +51,74 @@
"@pureadmin/descriptions": "^1.2.1", "@pureadmin/descriptions": "^1.2.1",
"@pureadmin/table": "^3.3.0", "@pureadmin/table": "^3.3.0",
"@pureadmin/utils": "^2.6.2", "@pureadmin/utils": "^2.6.2",
"@vueuse/core": "^14.0.0", "@vueuse/core": "^13.6.0",
"@vueuse/motion": "^3.0.3", "@vueuse/motion": "^3.0.3",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.12.2", "axios": "^1.11.0",
"dayjs": "^1.11.18", "dayjs": "^1.11.13",
"echarts": "^6.0.0", "echarts": "^5.6.0",
"element-plus": "^2.11.5", "element-plus": "^2.10.4",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"pinyin-pro": "^3.27.0", "pinyin-pro": "^3.26.0",
"qs": "^6.14.0", "qs": "^6.14.0",
"responsive-storage": "^2.2.0", "responsive-storage": "^2.2.0",
"sortablejs": "^1.15.6", "sortablejs": "^1.15.6",
"vue": "^3.5.22", "vue": "^3.5.18",
"vue-i18n": "^11.1.12", "vue-router": "^4.5.1",
"vue-router": "^4.6.3",
"vue-tippy": "^6.7.1", "vue-tippy": "^6.7.1",
"vue-types": "^6.0.0" "vue-types": "^6.0.0"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^20.1.0", "@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^20.0.0", "@commitlint/config-conventional": "^19.8.1",
"@commitlint/types": "^20.0.0", "@commitlint/types": "^19.8.1",
"@eslint/js": "^9.38.0", "@eslint/js": "^9.32.0",
"@faker-js/faker": "^10.1.0", "@faker-js/faker": "^9.9.0",
"@iconify/json": "^2.2.400", "@iconify/json": "^2.2.364",
"@iconify/vue": "4.2.0", "@iconify/vue": "4.2.0",
"@intlify/unplugin-vue-i18n": "^11.0.1", "@tailwindcss/vite": "^4.1.11",
"@tailwindcss/vite": "^4.1.16",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/node": "^20.19.23", "@types/node": "^20.19.9",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/path-browserify": "^1.0.3", "@types/path-browserify": "^1.0.3",
"@types/qs": "^6.14.0", "@types/qs": "^6.14.0",
"@types/sortablejs": "^1.15.9", "@types/sortablejs": "^1.15.8",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^5.1.1", "@vitejs/plugin-vue-jsx": "^5.0.1",
"boxen": "^8.0.1", "boxen": "^8.0.1",
"code-inspector-plugin": "^1.2.10", "code-inspector-plugin": "^1.0.3",
"cssnano": "^7.1.1", "cssnano": "^7.1.0",
"eslint": "^9.38.0", "eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-vue": "^10.5.1", "eslint-plugin-vue": "^10.3.0",
"gradient-string": "^3.0.0", "gradient-string": "^3.0.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^16.2.6", "lint-staged": "^16.1.2",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"postcss-html": "^1.8.0", "postcss-html": "^1.8.0",
"postcss-load-config": "^6.0.1", "postcss-load-config": "^6.0.1",
"postcss-scss": "^4.0.9", "postcss-scss": "^4.0.9",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^6.0.5", "rollup-plugin-visualizer": "^6.0.3",
"sass": "^1.93.2", "sass": "^1.89.2",
"stylelint": "^16.25.0", "stylelint": "^16.23.0",
"stylelint-config-recess-order": "^7.4.0", "stylelint-config-recess-order": "^7.1.0",
"stylelint-config-recommended-vue": "^1.6.1", "stylelint-config-recommended-vue": "^1.6.1",
"stylelint-config-standard-scss": "^14.0.0", "stylelint-config-standard-scss": "^14.0.0",
"stylelint-prettier": "^5.0.3", "stylelint-prettier": "^5.0.3",
"svgo": "^4.0.0", "svgo": "^4.0.0",
"tailwindcss": "^4.1.16", "tailwindcss": "^4.1.11",
"typescript": "^5.9.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.46.2", "typescript-eslint": "^8.38.0",
"unplugin-icons": "^22.5.0", "unplugin-icons": "^22.2.0",
"vite": "^7.1.12", "vite": "^7.0.6",
"vite-plugin-cdn-import": "^1.0.1", "vite-plugin-cdn-import": "^1.0.1",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-fake-server": "^2.2.0", "vite-plugin-fake-server": "^2.2.0",
@@ -128,10 +126,10 @@
"vite-plugin-router-warn": "^1.0.0", "vite-plugin-router-warn": "^1.0.0",
"vite-svg-loader": "^5.1.0", "vite-svg-loader": "^5.1.0",
"vue-eslint-parser": "^10.2.0", "vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.1.2" "vue-tsc": "^3.0.4"
}, },
"engines": { "engines": {
"node": "^20.19.0 || >=22.13.0", "node": "^20.19.0 || >=22.12.0",
"pnpm": ">=9" "pnpm": ">=9"
}, },
"pnpm": { "pnpm": {

3273
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,10 @@
{ {
"Version": "6.2.0", "Version": "6.1.0",
"Title": "PureAdmin", "Title": "PureAdmin",
"FixedHeader": true, "FixedHeader": true,
"HiddenSideBar": false, "HiddenSideBar": false,
"MultiTagsCache": false, "MultiTagsCache": false,
"KeepAlive": true, "KeepAlive": true,
"Locale": "zh",
"Layout": "vertical", "Layout": "vertical",
"Theme": "light", "Theme": "light",
"DarkMode": false, "DarkMode": false,

View File

@@ -9,7 +9,6 @@
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { ElConfigProvider } from "element-plus"; import { ElConfigProvider } from "element-plus";
import { ReDialog } from "@/components/ReDialog"; import { ReDialog } from "@/components/ReDialog";
import en from "element-plus/es/locale/lang/en";
import zhCn from "element-plus/es/locale/lang/zh-cn"; import zhCn from "element-plus/es/locale/lang/zh-cn";
export default defineComponent({ export default defineComponent({
@@ -20,7 +19,7 @@ export default defineComponent({
}, },
computed: { computed: {
currentLocale() { currentLocale() {
return this.$storage.locale?.locale === "zh" ? zhCn : en; return zhCn;
} }
} }
}); });

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" class="globalization" viewBox="0 0 512 512"><path fill="currentColor" d="m478.33 433.6-90-218a22 22 0 0 0-40.67 0l-90 218a22 22 0 1 0 40.67 16.79L316.66 406h102.67l18.33 44.39A22 22 0 0 0 458 464a22 22 0 0 0 20.32-30.4zM334.83 362 368 281.65 401.17 362zm-66.99-19.08a22 22 0 0 0-4.89-30.7c-.2-.15-15-11.13-36.49-34.73 39.65-53.68 62.11-114.75 71.27-143.49H330a22 22 0 0 0 0-44H214V70a22 22 0 0 0-44 0v20H54a22 22 0 0 0 0 44h197.25c-9.52 26.95-27.05 69.5-53.79 108.36-31.41-41.68-43.08-68.65-43.17-68.87a22 22 0 0 0-40.58 17c.58 1.38 14.55 34.23 52.86 83.93.92 1.19 1.83 2.35 2.74 3.51-39.24 44.35-77.74 71.86-93.85 80.74a22 22 0 1 0 21.07 38.63c2.16-1.18 48.6-26.89 101.63-85.59 22.52 24.08 38 35.44 38.93 36.1a22 22 0 0 0 30.75-4.9z"/></svg>

Before

Width:  |  Height:  |  Size: 807 B

View File

@@ -1,5 +1,4 @@
import Sortable from "sortablejs"; import Sortable from "sortablejs";
import { transformI18n } from "@/plugins/i18n";
import { useEpThemeStoreHook } from "@/store/modules/epTheme"; import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { import {
type PropType, type PropType,
@@ -148,9 +147,7 @@ export default defineComponent({
} }
function handleCheckColumnListChange(val: boolean, label: string) { function handleCheckColumnListChange(val: boolean, label: string) {
dynamicColumns.value.filter( dynamicColumns.value.filter(item => item.label === label)[0].hide = !val;
item => transformI18n(item.label) === transformI18n(label)
)[0].hide = !val;
} }
async function onReset() { async function onReset() {
@@ -223,9 +220,7 @@ export default defineComponent({
}; };
const isFixedColumn = (label: string) => { const isFixedColumn = (label: string) => {
return dynamicColumns.value.filter( return dynamicColumns.value.filter(item => item.label === label)[0].fixed
item => transformI18n(item.label) === transformI18n(label)
)[0].fixed
? true ? true
: false; : false;
}; };
@@ -363,10 +358,10 @@ export default defineComponent({
} }
> >
<span <span
title={transformI18n(item)} title={item}
class="inline-block w-[120px] truncate hover:text-text_color_primary" class="inline-block w-[120px] truncate hover:text-text_color_primary"
> >
{transformI18n(item)} {item}
</span> </span>
</el-checkbox> </el-checkbox>
</div> </div>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from "vue-i18n";
import LayFrame from "../lay-frame/index.vue"; import LayFrame from "../lay-frame/index.vue";
import LayFooter from "../lay-footer/index.vue"; import LayFooter from "../lay-footer/index.vue";
import { useTags } from "@/layout/hooks/useTag"; import { useTags } from "@/layout/hooks/useTag";
@@ -12,7 +11,6 @@ const props = defineProps({
fixedHeader: Boolean fixedHeader: Boolean
}); });
const { t } = useI18n();
const { showModel } = useTags(); const { showModel } = useTags();
const { $storage, $config } = useGlobal<GlobalPropertiesApi>(); const { $storage, $config } = useGlobal<GlobalPropertiesApi>();
@@ -133,7 +131,7 @@ const transitionMain = defineComponent({
}" }"
> >
<el-backtop <el-backtop
:title="t('buttons.pureBackTop')" title="回到顶部"
target=".app-main .el-scrollbar__wrap" target=".app-main .el-scrollbar__wrap"
> >
<BackTopIcon /> <BackTopIcon />

View File

@@ -3,15 +3,12 @@ import { useNav } from "@/layout/hooks/useNav";
import LaySearch from "../lay-search/index.vue"; import LaySearch from "../lay-search/index.vue";
import LayNotice from "../lay-notice/index.vue"; import LayNotice from "../lay-notice/index.vue";
import LayNavMix from "../lay-sidebar/NavMix.vue"; import LayNavMix from "../lay-sidebar/NavMix.vue";
import { useTranslationLang } from "@/layout/hooks/useTranslationLang";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue"; import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
import LaySidebarBreadCrumb from "../lay-sidebar/components/SidebarBreadCrumb.vue"; import LaySidebarBreadCrumb from "../lay-sidebar/components/SidebarBreadCrumb.vue";
import LaySidebarTopCollapse from "../lay-sidebar/components/SidebarTopCollapse.vue"; import LaySidebarTopCollapse from "../lay-sidebar/components/SidebarTopCollapse.vue";
import GlobalizationIcon from "@/assets/svg/globalization.svg?component";
import LogoutCircleRLine from "~icons/ri/logout-circle-r-line"; import LogoutCircleRLine from "~icons/ri/logout-circle-r-line";
import Setting from "~icons/ri/settings-3-line"; import Setting from "~icons/ri/settings-3-line";
import Check from "~icons/ep/check";
const { const {
layout, layout,
@@ -22,12 +19,8 @@ const {
username, username,
userAvatar, userAvatar,
avatarsStyle, avatarsStyle,
toggleSideBar, toggleSideBar
getDropdownItemStyle,
getDropdownItemClass
} = useNav(); } = useNav();
const { t, locale, translationCh, translationEn } = useTranslationLang();
</script> </script>
<template> <template>
@@ -49,38 +42,6 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<div v-if="layout === 'vertical'" class="vertical-header-right"> <div v-if="layout === 'vertical'" class="vertical-header-right">
<!-- 菜单搜索 --> <!-- 菜单搜索 -->
<LaySearch id="header-search" /> <LaySearch id="header-search" />
<!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click">
<GlobalizationIcon
class="navbar-bg-hover w-[40px] h-[48px] p-[11px] cursor-pointer outline-hidden"
/>
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')"
:class="['dark:text-white!', getDropdownItemClass(locale, 'zh')]"
@click="translationCh"
>
<IconifyIconOffline
v-show="locale === 'zh'"
class="check-zh"
:icon="Check"
/>
简体中文
</el-dropdown-item>
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')"
:class="['dark:text-white!', getDropdownItemClass(locale, 'en')]"
@click="translationEn"
>
<span v-show="locale === 'en'" class="check-en">
<IconifyIconOffline :icon="Check" />
</span>
English
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 全屏 --> <!-- 全屏 -->
<LaySidebarFullScreen id="full-screen" /> <LaySidebarFullScreen id="full-screen" />
<!-- 消息通知 --> <!-- 消息通知 -->
@@ -98,14 +59,14 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
:icon="LogoutCircleRLine" :icon="LogoutCircleRLine"
style="margin: 5px" style="margin: 5px"
/> />
{{ t("buttons.pureLoginOut") }} 退出系统
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<span <span
class="set-icon navbar-bg-hover" class="set-icon navbar-bg-hover"
:title="t('buttons.pureOpenSystemSet')" title="打开系统配置"
@click="onPanel" @click="onPanel"
> >
<IconifyIconOffline :icon="Setting" /> <IconifyIconOffline :icon="Setting" />
@@ -162,22 +123,6 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
} }
} }
.translation {
::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px;
}
.check-zh {
position: absolute;
left: 20px;
}
.check-en {
position: absolute;
left: 20px;
}
}
.logout { .logout {
width: 120px; width: 120px;

View File

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

View File

@@ -1,5 +1,3 @@
import { $t } from "@/plugins/i18n";
export interface ListItem { export interface ListItem {
avatar: string; avatar: string;
title: string; title: string;
@@ -20,13 +18,13 @@ export interface TabItem {
export const noticesData: TabItem[] = [ export const noticesData: TabItem[] = [
{ {
key: "1", key: "1",
name: $t("status.pureNotify"), name: "通知",
list: [], list: [],
emptyText: $t("status.pureNoNotify") emptyText: "暂无通知"
}, },
{ {
key: "2", key: "2",
name: $t("status.pureMessage"), name: "消息",
list: [ list: [
{ {
avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile1.svg", avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile1.svg",
@@ -51,11 +49,11 @@ export const noticesData: TabItem[] = [
type: "2" type: "2"
} }
], ],
emptyText: $t("status.pureNoMessage") emptyText: "暂无消息"
}, },
{ {
key: "3", key: "3",
name: $t("status.pureTodo"), name: "待办",
list: [ list: [
{ {
avatar: "", avatar: "",
@@ -94,6 +92,6 @@ export const noticesData: TabItem[] = [
type: "3" type: "3"
} }
], ],
emptyText: $t("status.pureNoTodo") emptyText: "暂无待办"
} }
]; ];

View File

@@ -1,11 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from "vue-i18n";
import { ref, computed } from "vue"; import { ref, computed } from "vue";
import { noticesData } from "./data"; import { noticesData } from "./data";
import NoticeList from "./components/NoticeList.vue"; import NoticeList from "./components/NoticeList.vue";
import BellIcon from "~icons/ep/bell"; import BellIcon from "~icons/ep/bell";
const { t } = useI18n();
const noticesNum = ref(0); const noticesNum = ref(0);
const notices = ref(noticesData); const notices = ref(noticesData);
const activeKey = ref(noticesData[0]?.key); const activeKey = ref(noticesData[0]?.key);
@@ -14,7 +12,7 @@ notices.value.map(v => (noticesNum.value += v.list.length));
const getLabel = computed( const getLabel = computed(
() => item => () => item =>
t(item.name) + (item.list.length > 0 ? `(${item.list.length})` : "") item.name + (item.list.length > 0 ? `(${item.list.length})` : "")
); );
</script> </script>
@@ -44,7 +42,7 @@ const getLabel = computed(
> >
<el-empty <el-empty
v-if="notices.length === 0" v-if="notices.length === 0"
:description="t('status.pureNoMessage')" description="暂无消息"
:image-size="60" :image-size="60"
/> />
<span v-else> <span v-else>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from "vue-i18n";
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
import { onClickOutside } from "@vueuse/core"; import { onClickOutside } from "@vueuse/core";
import { ref, computed, onMounted, onBeforeUnmount } from "vue"; import { ref, computed, onMounted, onBeforeUnmount } from "vue";
@@ -26,7 +25,6 @@ const iconClass = computed(() => {
]; ];
}); });
const { t } = useI18n();
const { onReset } = useDataThemeChange(); const { onReset } = useDataThemeChange();
onClickOutside(target, (event: any) => { onClickOutside(target, (event: any) => {
@@ -53,12 +51,10 @@ onBeforeUnmount(() => {
<div <div
class="project-configuration border-0 border-b-[1px] border-solid border-[var(--pure-border-color)]" class="project-configuration border-0 border-b-[1px] border-solid border-[var(--pure-border-color)]"
> >
<h4 class="dark:text-white"> <h4 class="dark:text-white">系统配置</h4>
{{ t("panel.pureSystemSet") }}
</h4>
<span <span
v-tippy="{ v-tippy="{
content: t('panel.pureCloseSystemSet'), content: '关闭配置',
placement: 'bottom-start', placement: 'bottom-start',
zIndex: 41000 zIndex: 41000
}" }"
@@ -82,7 +78,7 @@ onBeforeUnmount(() => {
> >
<el-button <el-button
v-tippy="{ v-tippy="{
content: t('panel.pureClearCacheAndToLogin'), content: '清空缓存并返回登录页',
placement: 'left-start', placement: 'left-start',
zIndex: 41000 zIndex: 41000
}" }"
@@ -91,7 +87,7 @@ onBeforeUnmount(() => {
bg bg
@click="onReset" @click="onReset"
> >
{{ t("panel.pureClearCache") }} 清空缓存
</el-button> </el-button>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from "vue-i18n";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import MdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component"; import MdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
import EnterOutlined from "@/assets/svg/enter_outlined.svg?component"; import EnterOutlined from "@/assets/svg/enter_outlined.svg?component";
@@ -10,7 +9,6 @@ withDefaults(defineProps<{ total?: number }>(), {
total: 0 total: 0
}); });
const { t } = useI18n();
const { device } = useNav(); const { device } = useNav();
</script> </script>
@@ -18,19 +16,19 @@ const { device } = useNav();
<div class="search-footer text-[#333] dark:text-white"> <div class="search-footer text-[#333] dark:text-white">
<span class="search-footer-item"> <span class="search-footer-item">
<EnterOutlined class="icon" /> <EnterOutlined class="icon" />
{{ t("buttons.pureConfirm") }} 确认
</span> </span>
<span class="search-footer-item"> <span class="search-footer-item">
<IconifyIconOffline :icon="ArrowUpLine" class="icon" /> <IconifyIconOffline :icon="ArrowUpLine" class="icon" />
<IconifyIconOffline :icon="ArrowDownLine" class="icon" /> <IconifyIconOffline :icon="ArrowDownLine" class="icon" />
{{ t("buttons.pureSwitch") }} 切换
</span> </span>
<span class="search-footer-item"> <span class="search-footer-item">
<MdiKeyboardEsc class="icon" /> <MdiKeyboardEsc class="icon" />
{{ t("buttons.pureClose") }} 关闭
</span> </span>
<p v-if="device !== 'mobile' && total > 0" class="search-footer-total"> <p v-if="device !== 'mobile' && total > 0" class="search-footer-total">
{{ `${t("search.pureTotal")} ${total}` }} {{ ` ${total} ` }}
</p> </p>
</div> </div>
</template> </template>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import Sortable from "sortablejs"; import Sortable from "sortablejs";
import { useI18n } from "vue-i18n";
import SearchHistoryItem from "./SearchHistoryItem.vue"; import SearchHistoryItem from "./SearchHistoryItem.vue";
import type { optionsItem, dragItem, Props } from "../types"; import type { optionsItem, dragItem, Props } from "../types";
import { useEpThemeStoreHook } from "@/store/modules/epTheme"; import { useEpThemeStoreHook } from "@/store/modules/epTheme";
@@ -20,7 +19,6 @@ const innerHeight = ref();
/** 判断是否停止鼠标移入事件处理 */ /** 判断是否停止鼠标移入事件处理 */
const stopMouseEvent = ref(false); const stopMouseEvent = ref(false);
const { t } = useI18n();
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
const instance = getCurrentInstance()!; const instance = getCurrentInstance()!;
const props = withDefaults(defineProps<Props>(), {}); const props = withDefaults(defineProps<Props>(), {});
@@ -143,9 +141,7 @@ defineExpose({ handleScroll });
<template> <template>
<div ref="historyRef" class="history"> <div ref="historyRef" class="history">
<template v-if="historyList.length"> <template v-if="historyList.length">
<div :style="titleStyle"> <div :style="titleStyle">搜索历史</div>
{{ t("search.pureHistory") }}
</div>
<div <div
v-for="(item, index) in historyList" v-for="(item, index) in historyList"
:key="item.path" :key="item.path"
@@ -164,9 +160,7 @@ defineExpose({ handleScroll });
</template> </template>
<template v-if="collectList.length"> <template v-if="collectList.length">
<div :style="titleStyle"> <div :style="titleStyle">
{{ {{ `收藏${collectList.length > 1 ? "(可拖拽排序)" : ""}` }}
`${t("search.pureCollect")}${collectList.length > 1 ? t("search.pureDragSort") : ""}`
}}
</div> </div>
<div class="collect-container"> <div class="collect-container">
<div <div

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { optionsItem } from "../types"; import type { optionsItem } from "../types";
import { transformI18n } from "@/plugins/i18n";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import StarIcon from "~icons/ep/star"; import StarIcon from "~icons/ep/star";
import CloseIcon from "~icons/ep/close"; import CloseIcon from "~icons/ep/close";
@@ -29,7 +28,7 @@ function handleDelete(item) {
<template> <template>
<component :is="useRenderIcon(item.meta?.icon)" /> <component :is="useRenderIcon(item.meta?.icon)" />
<span class="history-item-title"> <span class="history-item-title">
{{ transformI18n(item.meta?.title) }} {{ item.meta?.title }}
</span> </span>
<IconifyIconOffline <IconifyIconOffline
v-show="item.type === 'history'" v-show="item.type === 'history'"

View File

@@ -1,12 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { match } from "pinyin-pro"; import { match } from "pinyin-pro";
import { useI18n } from "vue-i18n";
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import SearchResult from "./SearchResult.vue"; import SearchResult from "./SearchResult.vue";
import SearchFooter from "./SearchFooter.vue"; import SearchFooter from "./SearchFooter.vue";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import SearchHistory from "./SearchHistory.vue"; import SearchHistory from "./SearchHistory.vue";
import type { optionsItem, dragItem } from "../types"; import type { optionsItem, dragItem } from "../types";
import { ref, computed, shallowRef, watch } from "vue"; import { ref, computed, shallowRef, watch } from "vue";
@@ -29,7 +27,6 @@ const emit = defineEmits<Emits>();
const props = withDefaults(defineProps<Props>(), {}); const props = withDefaults(defineProps<Props>(), {});
const router = useRouter(); const router = useRouter();
const { t, locale } = useI18n();
const HISTORY_TYPE = "history"; const HISTORY_TYPE = "history";
const COLLECT_TYPE = "collect"; const COLLECT_TYPE = "collect";
@@ -110,16 +107,15 @@ function search() {
const flatMenusData = flatTree(menusData.value); const flatMenusData = flatTree(menusData.value);
resultOptions.value = flatMenusData.filter(menu => resultOptions.value = flatMenusData.filter(menu =>
keyword.value keyword.value
? transformI18n(menu.meta?.title) ? menu.meta?.title
.toLocaleLowerCase() .toLocaleLowerCase()
.includes(keyword.value.toLocaleLowerCase().trim()) || .includes(keyword.value.toLocaleLowerCase().trim()) ||
(locale.value === "zh" && !isAllEmpty(
!isAllEmpty( match(
match( menu.meta?.title.toLocaleLowerCase(),
transformI18n(menu.meta?.title).toLocaleLowerCase(), keyword.value.toLocaleLowerCase().trim()
keyword.value.toLocaleLowerCase().trim() )
) )
))
: false : false
); );
activePath.value = activePath.value =
@@ -293,7 +289,7 @@ onKeyStroke("ArrowDown", handleDown);
v-model="keyword" v-model="keyword"
size="large" size="large"
clearable clearable
:placeholder="t('search.purePlaceholder')" placeholder="搜索菜单(支持拼音搜索)"
@input="handleSearch" @input="handleSearch"
> >
<template #prefix> <template #prefix>
@@ -305,7 +301,7 @@ onKeyStroke("ArrowDown", handleDown);
</el-input> </el-input>
<div class="search-content"> <div class="search-content">
<el-scrollbar ref="scrollbarRef" max-height="calc(90vh - 140px)"> <el-scrollbar ref="scrollbarRef" max-height="calc(90vh - 140px)">
<el-empty v-if="showEmpty" :description="t('search.pureEmpty')" /> <el-empty v-if="showEmpty" description="暂无搜索结果" />
<SearchHistory <SearchHistory
v-if="showSearchHistory" v-if="showSearchHistory"
ref="historyRef" ref="historyRef"

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Props } from "../types"; import type { Props } from "../types";
import { transformI18n } from "@/plugins/i18n";
import { useResizeObserver } from "@pureadmin/utils"; import { useResizeObserver } from "@pureadmin/utils";
import { useEpThemeStoreHook } from "@/store/modules/epTheme"; import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
@@ -82,7 +81,7 @@ defineExpose({ handleScroll });
> >
<component :is="useRenderIcon(item.meta?.icon)" /> <component :is="useRenderIcon(item.meta?.icon)" />
<span class="result-item-title"> <span class="result-item-title">
{{ transformI18n(item.meta?.title) }} {{ item.meta?.title }}
</span> </span>
<EnterOutlined /> <EnterOutlined />
</div> </div>

View File

@@ -9,7 +9,6 @@ import {
onUnmounted, onUnmounted,
onBeforeMount onBeforeMount
} from "vue"; } from "vue";
import { useI18n } from "vue-i18n";
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
import LayPanel from "../lay-panel/index.vue"; import LayPanel from "../lay-panel/index.vue";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
@@ -26,7 +25,6 @@ import DayIcon from "@/assets/svg/day.svg?component";
import DarkIcon from "@/assets/svg/dark.svg?component"; import DarkIcon from "@/assets/svg/dark.svg?component";
import SystemIcon from "@/assets/svg/system.svg?component"; import SystemIcon from "@/assets/svg/system.svg?component";
const { t } = useI18n();
const { device } = useNav(); const { device } = useNav();
const { isDark } = useDark(); const { isDark } = useDark();
const { $storage } = useGlobal<GlobalPropertiesApi>(); const { $storage } = useGlobal<GlobalPropertiesApi>();
@@ -147,13 +145,13 @@ function setFalse(Doms): any {
const stretchTypeOptions = computed<Array<OptionsType>>(() => { const stretchTypeOptions = computed<Array<OptionsType>>(() => {
return [ return [
{ {
label: t("panel.pureStretchFixed"), label: "固定",
tip: t("panel.pureStretchFixedTip"), tip: "紧凑页面,轻松找到所需信息",
value: "fixed" value: "fixed"
}, },
{ {
label: t("panel.pureStretchCustom"), label: "自定义",
tip: t("panel.pureStretchCustomTip"), tip: "最小1280、最大1600",
value: "custom" value: "custom"
} }
]; ];
@@ -195,24 +193,24 @@ const pClass = computed(() => {
const themeOptions = computed<Array<OptionsType>>(() => { const themeOptions = computed<Array<OptionsType>>(() => {
return [ return [
{ {
label: t("panel.pureOverallStyleLight"), label: "浅色",
icon: DayIcon, icon: DayIcon,
theme: "light", theme: "light",
tip: t("panel.pureOverallStyleLightTip"), tip: "清新启航,点亮舒适的工作界面",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" } iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
}, },
{ {
label: t("panel.pureOverallStyleDark"), label: "深色",
icon: DarkIcon, icon: DarkIcon,
theme: "dark", theme: "dark",
tip: t("panel.pureOverallStyleDarkTip"), tip: "月光序曲,沉醉于夜的静谧雅致",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" } iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
}, },
{ {
label: t("panel.pureOverallStyleSystem"), label: "自动",
icon: SystemIcon, icon: SystemIcon,
theme: "system", theme: "system",
tip: t("panel.pureOverallStyleSystemTip"), tip: "同步时光,界面随晨昏自然呼应",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" } iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
} }
]; ];
@@ -221,18 +219,18 @@ const themeOptions = computed<Array<OptionsType>>(() => {
const markOptions = computed<Array<OptionsType>>(() => { const markOptions = computed<Array<OptionsType>>(() => {
return [ return [
{ {
label: t("panel.pureTagsStyleSmart"), label: "灵动",
tip: t("panel.pureTagsStyleSmartTip"), tip: "灵动标签,添趣生辉",
value: "smart" value: "smart"
}, },
{ {
label: t("panel.pureTagsStyleCard"), label: "卡片",
tip: t("panel.pureTagsStyleCardTip"), tip: "卡片标签,高效浏览",
value: "card" value: "card"
}, },
{ {
label: t("panel.pureTagsStyleChrome"), label: "谷歌",
tip: t("panel.pureTagsStyleChromeTip"), tip: "谷歌风格,经典美观",
value: "chrome" value: "chrome"
} }
]; ];
@@ -317,7 +315,7 @@ onUnmounted(() => removeMatchMedia);
<template> <template>
<LayPanel> <LayPanel>
<div class="p-5"> <div class="p-5">
<p :class="pClass">{{ t("panel.pureOverallStyle") }}</p> <p :class="pClass">整体风格</p>
<Segmented <Segmented
resize resize
class="select-none" class="select-none"
@@ -335,7 +333,7 @@ onUnmounted(() => removeMatchMedia);
" "
/> />
<p :class="['mt-5!', pClass]">{{ t("panel.pureThemeColor") }}</p> <p :class="['mt-5!', pClass]">主题色</p>
<ul class="theme-color"> <ul class="theme-color">
<li <li
v-for="(item, index) in themeColors" v-for="(item, index) in themeColors"
@@ -354,12 +352,12 @@ onUnmounted(() => removeMatchMedia);
</li> </li>
</ul> </ul>
<p :class="['mt-5!', pClass]">{{ t("panel.pureLayoutModel") }}</p> <p :class="['mt-5!', pClass]">导航模式</p>
<ul class="pure-theme"> <ul class="pure-theme">
<li <li
ref="verticalRef" ref="verticalRef"
v-tippy="{ v-tippy="{
content: t('panel.pureVerticalTip'), content: '左侧菜单,亲切熟悉',
zIndex: 41000 zIndex: 41000
}" }"
:class="layoutTheme.layout === 'vertical' ? 'is-select' : ''" :class="layoutTheme.layout === 'vertical' ? 'is-select' : ''"
@@ -372,7 +370,7 @@ onUnmounted(() => removeMatchMedia);
v-if="device !== 'mobile'" v-if="device !== 'mobile'"
ref="horizontalRef" ref="horizontalRef"
v-tippy="{ v-tippy="{
content: t('panel.pureHorizontalTip'), content: '顶部菜单,简洁概览',
zIndex: 41000 zIndex: 41000
}" }"
:class="layoutTheme.layout === 'horizontal' ? 'is-select' : ''" :class="layoutTheme.layout === 'horizontal' ? 'is-select' : ''"
@@ -385,7 +383,7 @@ onUnmounted(() => removeMatchMedia);
v-if="device !== 'mobile'" v-if="device !== 'mobile'"
ref="mixRef" ref="mixRef"
v-tippy="{ v-tippy="{
content: t('panel.pureMixTip'), content: '混合菜单,灵活多变',
zIndex: 41000 zIndex: 41000
}" }"
:class="layoutTheme.layout === 'mix' ? 'is-select' : ''" :class="layoutTheme.layout === 'mix' ? 'is-select' : ''"
@@ -397,7 +395,7 @@ onUnmounted(() => removeMatchMedia);
</ul> </ul>
<span v-if="useAppStoreHook().getViewportWidth > 1280"> <span v-if="useAppStoreHook().getViewportWidth > 1280">
<p :class="['mt-5!', pClass]">{{ t("panel.pureStretch") }}</p> <p :class="['mt-5!', pClass]">页宽</p>
<Segmented <Segmented
resize resize
class="mb-2 select-none" class="mb-2 select-none"
@@ -438,7 +436,7 @@ onUnmounted(() => removeMatchMedia);
</button> </button>
</span> </span>
<p :class="['mt-4!', pClass]">{{ t("panel.pureTagsStyle") }}</p> <p :class="['mt-4!', pClass]">页签风格</p>
<Segmented <Segmented
resize resize
class="select-none" class="select-none"
@@ -447,47 +445,45 @@ onUnmounted(() => removeMatchMedia);
@change="onChange" @change="onChange"
/> />
<p class="mt-5! font-medium text-sm dark:text-white"> <p class="mt-5! font-medium text-sm dark:text-white">界面显示</p>
{{ t("panel.pureInterfaceDisplay") }}
</p>
<ul class="setting"> <ul class="setting">
<li> <li>
<span class="dark:text-white">{{ t("panel.pureGreyModel") }}</span> <span class="dark:text-white">灰色模式</span>
<el-switch <el-switch
v-model="settings.greyVal" v-model="settings.greyVal"
inline-prompt inline-prompt
:active-text="t('buttons.pureOpenText')" active-text=""
:inactive-text="t('buttons.pureCloseText')" inactive-text=""
@change="greyChange" @change="greyChange"
/> />
</li> </li>
<li> <li>
<span class="dark:text-white">{{ t("panel.pureWeakModel") }}</span> <span class="dark:text-white">色弱模式</span>
<el-switch <el-switch
v-model="settings.weakVal" v-model="settings.weakVal"
inline-prompt inline-prompt
:active-text="t('buttons.pureOpenText')" active-text=""
:inactive-text="t('buttons.pureCloseText')" inactive-text=""
@change="weekChange" @change="weekChange"
/> />
</li> </li>
<li> <li>
<span class="dark:text-white">{{ t("panel.pureHiddenTags") }}</span> <span class="dark:text-white">隐藏标签页</span>
<el-switch <el-switch
v-model="settings.tabsVal" v-model="settings.tabsVal"
inline-prompt inline-prompt
:active-text="t('buttons.pureOpenText')" active-text=""
:inactive-text="t('buttons.pureCloseText')" inactive-text=""
@change="tagsChange" @change="tagsChange"
/> />
</li> </li>
<li> <li>
<span class="dark:text-white">{{ t("panel.pureHiddenFooter") }}</span> <span class="dark:text-white">隐藏页脚</span>
<el-switch <el-switch
v-model="settings.hideFooter" v-model="settings.hideFooter"
inline-prompt inline-prompt
:active-text="t('buttons.pureOpenText')" active-text=""
:inactive-text="t('buttons.pureCloseText')" inactive-text=""
@change="hideFooterChange" @change="hideFooterChange"
/> />
</li> </li>
@@ -498,20 +494,18 @@ onUnmounted(() => removeMatchMedia);
inline-prompt inline-prompt
:active-value="true" :active-value="true"
:inactive-value="false" :inactive-value="false"
:active-text="t('buttons.pureOpenText')" active-text=""
:inactive-text="t('buttons.pureCloseText')" inactive-text=""
@change="logoChange" @change="logoChange"
/> />
</li> </li>
<li> <li>
<span class="dark:text-white"> <span class="dark:text-white">页签持久化</span>
{{ t("panel.pureMultiTagsCache") }}
</span>
<el-switch <el-switch
v-model="settings.multiTagsCache" v-model="settings.multiTagsCache"
inline-prompt inline-prompt
:active-text="t('buttons.pureOpenText')" active-text=""
:inactive-text="t('buttons.pureCloseText')" inactive-text=""
@change="multiTagsCacheChange" @change="multiTagsCacheChange"
/> />
</li> </li>

View File

@@ -6,15 +6,12 @@ import LayNotice from "../lay-notice/index.vue";
import { responsiveStorageNameSpace } from "@/config"; import { responsiveStorageNameSpace } from "@/config";
import { ref, nextTick, computed, onMounted } from "vue"; import { ref, nextTick, computed, onMounted } from "vue";
import { storageLocal, isAllEmpty } from "@pureadmin/utils"; import { storageLocal, isAllEmpty } from "@pureadmin/utils";
import { useTranslationLang } from "../../hooks/useTranslationLang";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue"; import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue"; import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
import GlobalizationIcon from "@/assets/svg/globalization.svg?component";
import LogoutCircleRLine from "~icons/ri/logout-circle-r-line"; import LogoutCircleRLine from "~icons/ri/logout-circle-r-line";
import Setting from "~icons/ri/settings-3-line"; import Setting from "~icons/ri/settings-3-line";
import Check from "~icons/ep/check";
const menuRef = ref(); const menuRef = ref();
const showLogo = ref( const showLogo = ref(
@@ -23,9 +20,8 @@ const showLogo = ref(
)?.showLogo ?? true )?.showLogo ?? true
); );
const { t, route, locale, translationCh, translationEn } =
useTranslationLang(menuRef);
const { const {
route,
title, title,
logout, logout,
onPanel, onPanel,
@@ -33,9 +29,7 @@ const {
username, username,
userAvatar, userAvatar,
backTopMenu, backTopMenu,
avatarsStyle, avatarsStyle
getDropdownItemStyle,
getDropdownItemClass
} = useNav(); } = useNav();
const defaultActive = computed(() => const defaultActive = computed(() =>
@@ -79,36 +73,6 @@ onMounted(() => {
<div class="horizontal-header-right"> <div class="horizontal-header-right">
<!-- 菜单搜索 --> <!-- 菜单搜索 -->
<LaySearch id="header-search" /> <LaySearch id="header-search" />
<!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click">
<GlobalizationIcon
class="navbar-bg-hover w-[40px] h-[48px] p-[11px] cursor-pointer outline-hidden"
/>
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')"
:class="['dark:text-white!', getDropdownItemClass(locale, 'zh')]"
@click="translationCh"
>
<span v-show="locale === 'zh'" class="check-zh">
<IconifyIconOffline :icon="Check" />
</span>
简体中文
</el-dropdown-item>
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')"
:class="['dark:text-white!', getDropdownItemClass(locale, 'en')]"
@click="translationEn"
>
<span v-show="locale === 'en'" class="check-en">
<IconifyIconOffline :icon="Check" />
</span>
English
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 全屏 --> <!-- 全屏 -->
<LaySidebarFullScreen id="full-screen" /> <LaySidebarFullScreen id="full-screen" />
<!-- 消息通知 --> <!-- 消息通知 -->
@@ -126,14 +90,14 @@ onMounted(() => {
:icon="LogoutCircleRLine" :icon="LogoutCircleRLine"
style="margin: 5px" style="margin: 5px"
/> />
{{ t("buttons.pureLoginOut") }} 退出系统
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<span <span
class="set-icon navbar-bg-hover" class="set-icon navbar-bg-hover"
:title="t('buttons.pureOpenSystemSet')" title="打开系统配置"
@click="onPanel" @click="onPanel"
> >
<IconifyIconOffline :icon="Setting" /> <IconifyIconOffline :icon="Setting" />
@@ -147,22 +111,6 @@ onMounted(() => {
opacity: 0.45; opacity: 0.45;
} }
.translation {
::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px;
}
.check-zh {
position: absolute;
left: 20px;
}
.check-en {
position: absolute;
left: 20px;
}
}
.logout { .logout {
width: 120px; width: 120px;

View File

@@ -1,28 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { isAllEmpty } from "@pureadmin/utils"; import { isAllEmpty } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import LaySearch from "../lay-search/index.vue"; import LaySearch from "../lay-search/index.vue";
import LayNotice from "../lay-notice/index.vue"; import LayNotice from "../lay-notice/index.vue";
import { ref, toRaw, watch, onMounted, nextTick } from "vue"; import { ref, toRaw, watch, onMounted, nextTick } from "vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { getParentPaths, findRouteByPath } from "@/router/utils"; import { getParentPaths, findRouteByPath } from "@/router/utils";
import { useTranslationLang } from "../../hooks/useTranslationLang";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import LaySidebarExtraIcon from "../lay-sidebar/components/SidebarExtraIcon.vue"; import LaySidebarExtraIcon from "../lay-sidebar/components/SidebarExtraIcon.vue";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue"; import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
import GlobalizationIcon from "@/assets/svg/globalization.svg?component";
import LogoutCircleRLine from "~icons/ri/logout-circle-r-line"; import LogoutCircleRLine from "~icons/ri/logout-circle-r-line";
import Setting from "~icons/ri/settings-3-line"; import Setting from "~icons/ri/settings-3-line";
import Check from "~icons/ep/check";
const menuRef = ref(); const menuRef = ref();
const defaultActive = ref(null); const defaultActive = ref(null);
const { t, route, locale, translationCh, translationEn } =
useTranslationLang(menuRef);
const { const {
route,
device, device,
logout, logout,
onPanel, onPanel,
@@ -30,9 +25,7 @@ const {
username, username,
userAvatar, userAvatar,
getDivStyle, getDivStyle,
avatarsStyle, avatarsStyle
getDropdownItemStyle,
getDropdownItemClass
} = useNav(); } = useNav();
function getDefaultActive(routePath) { function getDefaultActive(routePath) {
@@ -90,7 +83,7 @@ watch(
</div> </div>
<div :style="getDivStyle"> <div :style="getDivStyle">
<span class="select-none"> <span class="select-none">
{{ transformI18n(route.meta.title) }} {{ route.meta.title }}
</span> </span>
<LaySidebarExtraIcon :extraIcon="route.meta.extraIcon" /> <LaySidebarExtraIcon :extraIcon="route.meta.extraIcon" />
</div> </div>
@@ -100,36 +93,6 @@ watch(
<div class="horizontal-header-right"> <div class="horizontal-header-right">
<!-- 菜单搜索 --> <!-- 菜单搜索 -->
<LaySearch id="header-search" /> <LaySearch id="header-search" />
<!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click">
<GlobalizationIcon
class="navbar-bg-hover w-[40px] h-[48px] p-[11px] cursor-pointer outline-hidden"
/>
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')"
:class="['dark:text-white!', getDropdownItemClass(locale, 'zh')]"
@click="translationCh"
>
<span v-show="locale === 'zh'" class="check-zh">
<IconifyIconOffline :icon="Check" />
</span>
简体中文
</el-dropdown-item>
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')"
:class="['dark:text-white!', getDropdownItemClass(locale, 'en')]"
@click="translationEn"
>
<span v-show="locale === 'en'" class="check-en">
<IconifyIconOffline :icon="Check" />
</span>
English
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 全屏 --> <!-- 全屏 -->
<LaySidebarFullScreen id="full-screen" /> <LaySidebarFullScreen id="full-screen" />
<!-- 消息通知 --> <!-- 消息通知 -->
@@ -147,14 +110,14 @@ watch(
:icon="LogoutCircleRLine" :icon="LogoutCircleRLine"
style="margin: 5px" style="margin: 5px"
/> />
{{ t("buttons.pureLoginOut") }} 退出系统
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<span <span
class="set-icon navbar-bg-hover" class="set-icon navbar-bg-hover"
:title="t('buttons.pureOpenSystemSet')" title="打开系统配置"
@click="onPanel" @click="onPanel"
> >
<IconifyIconOffline :icon="Setting" /> <IconifyIconOffline :icon="Setting" />
@@ -168,22 +131,6 @@ watch(
opacity: 0.45; opacity: 0.45;
} }
.translation {
::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px;
}
.check-zh {
position: absolute;
left: 20px;
}
.check-en {
position: absolute;
left: 20px;
}
}
.logout { .logout {
width: 120px; width: 120px;

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { isEqual } from "@pureadmin/utils"; import { isEqual } from "@pureadmin/utils";
import { transformI18n } from "@/plugins/i18n";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { ref, watch, onMounted, toRaw } from "vue"; import { ref, watch, onMounted, toRaw } from "vue";
import { getParentPaths, findRouteByPath } from "@/router/utils"; import { getParentPaths, findRouteByPath } from "@/router/utils";
@@ -113,7 +112,7 @@ watch(
class="inline! items-stretch!" class="inline! items-stretch!"
> >
<a @click.prevent="handleLink(item)"> <a @click.prevent="handleLink(item)">
{{ transformI18n(item.meta.title) }} {{ item.meta.title }}
</a> </a>
</el-breadcrumb-item> </el-breadcrumb-item>
</transition-group> </transition-group>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue"; import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { useGlobal } from "@pureadmin/utils"; import { useGlobal } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
@@ -14,7 +13,6 @@ withDefaults(defineProps<Props>(), {
isActive: false isActive: false
}); });
const { t } = useI18n();
const { tooltipEffect } = useNav(); const { tooltipEffect } = useNav();
const iconClass = computed(() => { const iconClass = computed(() => {
@@ -36,9 +34,7 @@ const toggleClick = () => {
<template> <template>
<div <div
v-tippy="{ v-tippy="{
content: isActive content: isActive ? '点击折叠' : '点击展开',
? t('buttons.pureClickCollapse')
: t('buttons.pureClickExpand'),
theme: tooltipEffect, theme: tooltipEffect,
hideOnClick: 'toggle', hideOnClick: 'toggle',
placement: 'right' placement: 'right'

View File

@@ -4,7 +4,6 @@ import { posix } from "path-browserify";
import { menuType } from "@/layout/types"; import { menuType } from "@/layout/types";
import { ReText } from "@/components/ReText"; import { ReText } from "@/components/ReText";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import SidebarLinkItem from "./SidebarLinkItem.vue"; import SidebarLinkItem from "./SidebarLinkItem.vue";
import SidebarExtraIcon from "./SidebarExtraIcon.vue"; import SidebarExtraIcon from "./SidebarExtraIcon.vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
@@ -161,7 +160,7 @@ function resolvePath(routePath) {
truncated truncated
class="w-full! px-3! min-w-[54px]! text-center! text-inherit!" class="w-full! px-3! min-w-[54px]! text-center! text-inherit!"
> >
{{ transformI18n(onlyOneChild.meta.title) }} {{ onlyOneChild.meta.title }}
</el-text> </el-text>
<template #title> <template #title>
@@ -173,7 +172,7 @@ function resolvePath(routePath) {
}" }"
class="w-full! text-inherit!" class="w-full! text-inherit!"
> >
{{ transformI18n(onlyOneChild.meta.title) }} {{ onlyOneChild.meta.title }}
</ReText> </ReText>
<SidebarExtraIcon :extraIcon="onlyOneChild.meta.extraIcon" /> <SidebarExtraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
</div> </div>
@@ -212,7 +211,7 @@ function resolvePath(routePath) {
}" }"
:class="textClass" :class="textClass"
> >
{{ transformI18n(item.meta.title) }} {{ item.meta.title }}
</ReText> </ReText>
<SidebarExtraIcon v-if="!isCollapse" :extraIcon="item.meta.extraIcon" /> <SidebarExtraIcon v-if="!isCollapse" :extraIcon="item.meta.extraIcon" />
</template> </template>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue"; import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { useGlobal } from "@pureadmin/utils"; import { useGlobal } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
@@ -14,7 +13,6 @@ withDefaults(defineProps<Props>(), {
isActive: false isActive: false
}); });
const { t } = useI18n();
const { tooltipEffect } = useNav(); const { tooltipEffect } = useNav();
const iconClass = computed(() => { const iconClass = computed(() => {
@@ -46,9 +44,7 @@ const toggleClick = () => {
<div class="left-collapse"> <div class="left-collapse">
<IconifyIconOffline <IconifyIconOffline
v-tippy="{ v-tippy="{
content: isActive content: isActive ? '点击折叠' : '点击展开',
? t('buttons.pureClickCollapse')
: t('buttons.pureClickExpand'),
theme: tooltipEffect, theme: tooltipEffect,
hideOnClick: 'toggle', hideOnClick: 'toggle',
placement: 'right' placement: 'right'

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from "vue-i18n";
import MenuFold from "~icons/ri/menu-fold-fill"; import MenuFold from "~icons/ri/menu-fold-fill";
import MenuUnfold from "~icons/ri/menu-unfold-fill"; import MenuUnfold from "~icons/ri/menu-unfold-fill";
@@ -11,8 +10,6 @@ withDefaults(defineProps<Props>(), {
isActive: false isActive: false
}); });
const { t } = useI18n();
const emit = defineEmits<{ const emit = defineEmits<{
(e: "toggleClick"): void; (e: "toggleClick"): void;
}>(); }>();
@@ -25,9 +22,7 @@ const toggleClick = () => {
<template> <template>
<div <div
class="px-3 mr-1 navbar-bg-hover" class="px-3 mr-1 navbar-bg-hover"
:title=" :title="isActive ? '点击折叠' : '点击展开'"
isActive ? t('buttons.pureClickCollapse') : t('buttons.pureClickExpand')
"
@click="toggleClick" @click="toggleClick"
> >
<IconifyIconOffline <IconifyIconOffline

View File

@@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { $t } from "@/plugins/i18n";
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
import NProgress from "@/utils/progress";
import { RouteConfigs } from "../../types"; import { RouteConfigs } from "../../types";
import { useTags } from "../../hooks/useTag"; import { useTags } from "../../hooks/useTag";
import { routerArrays } from "@/layout/types"; import { routerArrays } from "@/layout/types";
@@ -52,7 +50,6 @@ const {
onMounted, onMounted,
onMouseenter, onMouseenter,
onMouseleave, onMouseleave,
transformI18n,
onContentFullScreen onContentFullScreen
} = useTags(); } = useTags();
@@ -207,14 +204,12 @@ function dynamicRouteTag(value: string): void {
/** 刷新路由 */ /** 刷新路由 */
function onFresh() { function onFresh() {
NProgress.start();
const { fullPath, query } = unref(route); const { fullPath, query } = unref(route);
router.replace({ router.replace({
path: "/redirect" + fullPath, path: "/redirect" + fullPath,
query query
}); });
handleAliveRoute(route as ToRouteType, "refresh"); handleAliveRoute(route as ToRouteType, "refresh");
NProgress.done();
} }
function deleteDynamicTag(obj: any, current: any, tag?: string) { function deleteDynamicTag(obj: any, current: any, tag?: string) {
@@ -347,16 +342,16 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
setTimeout(() => { setTimeout(() => {
if (pureSetting.hiddenSideBar) { if (pureSetting.hiddenSideBar) {
tagsViews[6].icon = ExitFullscreen; tagsViews[6].icon = ExitFullscreen;
tagsViews[6].text = $t("buttons.pureContentExitFullScreen"); tagsViews[6].text = "内容区退出全屏";
} else { } else {
tagsViews[6].icon = Fullscreen; tagsViews[6].icon = Fullscreen;
tagsViews[6].text = $t("buttons.pureContentFullScreen"); tagsViews[6].text = "内容区全屏";
} }
}, 100); }, 100);
break; break;
} }
setTimeout(() => { setTimeout(() => {
showMenuModel(route.fullPath, route.query, route.params); showMenuModel(route.fullPath, route.query);
}); });
} }
@@ -391,18 +386,15 @@ function disabledMenus(value: boolean, fixedTag = false) {
function showMenuModel( function showMenuModel(
currentPath: string, currentPath: string,
query: object = {}, query: object = {},
params: object = {},
refresh = false refresh = false
) { ) {
const allRoute = multiTags.value; const allRoute = multiTags.value;
const routeLength = multiTags.value.length; const routeLength = multiTags.value.length;
let currentIndex = -1; let currentIndex = -1;
if (!isAllEmpty(params)) { if (isAllEmpty(query)) {
currentIndex = allRoute.findIndex(v => isEqual(v.params, params));
} else if (!isAllEmpty(query)) {
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
} else {
currentIndex = allRoute.findIndex(v => v.path === currentPath); currentIndex = allRoute.findIndex(v => v.path === currentPath);
} else {
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
} }
function fixedTagDisabled() { function fixedTagDisabled() {
if (allRoute[currentIndex]?.meta?.fixedTag) { if (allRoute[currentIndex]?.meta?.fixedTag) {
@@ -468,14 +460,14 @@ function openMenu(tag, e) {
} else if (route.path !== tag.path && route.name !== tag.name) { } else if (route.path !== tag.path && route.name !== tag.name) {
// 右键菜单不匹配当前路由,隐藏刷新 // 右键菜单不匹配当前路由,隐藏刷新
tagsViews[0].show = false; tagsViews[0].show = false;
showMenuModel(tag.path, tag.query, tag.params); showMenuModel(tag.path, tag.query);
} else if (multiTags.value.length === 2 && route.path !== tag.path) { } else if (multiTags.value.length === 2 && route.path !== tag.path) {
showMenus(true); showMenus(true);
// 只有两个标签时不显示关闭其他标签页 // 只有两个标签时不显示关闭其他标签页
tagsViews[4].show = false; tagsViews[4].show = false;
showMenuModel(tag.path, tag.query, tag.params); } else if (route.path === tag.path) {
} else { // 右键当前激活的菜单
showMenuModel(tag.path, tag.query, tag.params, true); showMenuModel(tag.path, tag.query, true);
} }
currentSelect.value = tag; currentSelect.value = tag;
@@ -597,7 +589,7 @@ onBeforeUnmount(() => {
<span <span
class="tag-title dark:text-text_color_primary! dark:hover:text-primary!" class="tag-title dark:text-text_color_primary! dark:hover:text-primary!"
> >
{{ transformI18n(item.meta.title) }} {{ item.meta.title }}
</span> </span>
<span <span
v-if=" v-if="
@@ -622,7 +614,7 @@ onBeforeUnmount(() => {
<TagChrome /> <TagChrome />
</div> </div>
<span class="tag-title"> <span class="tag-title">
{{ transformI18n(item.meta.title) }} {{ item.meta.title }}
</span> </span>
<span <span
v-if="isFixedTag(item) ? false : index !== 0" v-if="isFixedTag(item) ? false : index !== 0"
@@ -655,7 +647,7 @@ onBeforeUnmount(() => {
> >
<li v-if="item.show" @click="selectTag(key, item)"> <li v-if="item.show" @click="selectTag(key, item)">
<IconifyIconOffline :icon="item.icon" /> <IconifyIconOffline :icon="item.icon" />
{{ transformI18n(item.text) }} {{ item.text }}
</li> </li>
</div> </div>
</ul> </ul>
@@ -679,7 +671,7 @@ onBeforeUnmount(() => {
:disabled="item.disabled" :disabled="item.disabled"
> >
<IconifyIconOffline :icon="item.icon" /> <IconifyIconOffline :icon="item.icon" />
{{ transformI18n(item.text) }} {{ item.text }}
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { ref, unref, watch, onMounted, nextTick } from "vue"; import { ref, unref, watch, onMounted, nextTick } from "vue";
@@ -14,27 +13,17 @@ const props = defineProps<{
}; };
}>(); }>();
const { t } = useI18n();
const loading = ref(true); const loading = ref(true);
const currentRoute = useRoute(); const currentRoute = useRoute();
const frameSrc = ref<string>(""); const frameSrc = ref<string>("");
const frameRef = ref<HTMLElement | null>(null); const frameRef = ref<HTMLElement | null>(null);
const fallbackTimer = ref<number | null>(null);
if (unref(currentRoute.meta)?.frameSrc) { if (unref(currentRoute.meta)?.frameSrc) {
frameSrc.value = unref(currentRoute.meta)?.frameSrc as string; frameSrc.value = unref(currentRoute.meta)?.frameSrc as string;
} }
unref(currentRoute.meta)?.frameLoading === false && hideLoading();
function clearFallbackTimer() {
if (fallbackTimer.value !== null) {
clearTimeout(fallbackTimer.value);
fallbackTimer.value = null;
}
}
function hideLoading() { function hideLoading() {
loading.value = false; loading.value = false;
clearFallbackTimer();
} }
function init() { function init() {
@@ -43,42 +32,32 @@ function init() {
if (!iframe) return; if (!iframe) return;
const _frame = iframe as any; const _frame = iframe as any;
if (_frame.attachEvent) { if (_frame.attachEvent) {
_frame.attachEvent("onload", hideLoading); _frame.attachEvent("onload", () => {
hideLoading();
});
} else { } else {
iframe.onload = hideLoading; iframe.onload = () => {
hideLoading();
};
} }
}); });
} }
let isRedirect = false;
watch( watch(
() => currentRoute.fullPath, () => currentRoute.fullPath,
path => { path => {
if ( if (
currentRoute.name === "Redirect" && currentRoute.name === "Redirect" &&
props.frameInfo?.fullPath && path.includes(props.frameInfo?.fullPath)
path.includes(props.frameInfo.fullPath)
) { ) {
isRedirect = true; frameSrc.value = path; // redirect时置换成任意值待重定向后 重新赋值
loading.value = true; loading.value = true;
return;
} }
if (props.frameInfo?.fullPath === path && isRedirect) { // 重新赋值
loading.value = true; if (props.frameInfo?.fullPath === path) {
clearFallbackTimer(); frameSrc.value = props.frameInfo?.frameSrc;
const url = new URL(props.frameInfo.frameSrc, window.location.origin);
const joinChar = url.search ? "&" : "?";
frameSrc.value = `${props.frameInfo.frameSrc}${joinChar}t=${Date.now()}`;
fallbackTimer.value = window.setTimeout(() => {
if (loading.value) {
hideLoading();
}
}, 1500);
isRedirect = false;
} }
}, }
{ immediate: true }
); );
onMounted(() => { onMounted(() => {
@@ -87,11 +66,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<div <div v-loading="loading" class="frame" element-loading-text="加载中...">
v-loading="loading"
class="frame"
:element-loading-text="t('status.pureLoad')"
>
<iframe ref="frameRef" :src="frameSrc" class="frame-iframe" /> <iframe ref="frameRef" :src="frameSrc" class="frame-iframe" />
</div> </div>
</template> </template>

View File

@@ -1,5 +1,4 @@
import { computed } from "vue"; import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { routerArrays } from "../types"; import { routerArrays } from "../types";
import { useGlobal } from "@pureadmin/utils"; import { useGlobal } from "@pureadmin/utils";
import { useMultiTagsStore } from "@/store/modules/multiTags"; import { useMultiTagsStore } from "@/store/modules/multiTags";
@@ -15,11 +14,6 @@ export function useLayout() {
) { ) {
$storage.tags = routerArrays; $storage.tags = routerArrays;
} }
/** 国际化 */
if (!$storage.locale) {
$storage.locale = { locale: $config?.Locale ?? "zh" };
useI18n().locale.value = $config?.Locale ?? "zh";
}
/** 导航 */ /** 导航 */
if (!$storage.layout) { if (!$storage.layout) {
$storage.layout = { $storage.layout = {

View File

@@ -1,18 +1,16 @@
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import { useRouter } from "vue-router";
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
import Avatar from "@/assets/user.jpg"; import Avatar from "@/assets/user.jpg";
import { getTopMenu } from "@/router/utils"; import { getTopMenu } from "@/router/utils";
import { useFullscreen } from "@vueuse/core"; import { useFullscreen } from "@vueuse/core";
import type { routeMetaType } from "../types"; import type { routeMetaType } from "../types";
import { transformI18n } from "@/plugins/i18n"; import { useRouter, useRoute } from "vue-router";
import { router, remainingPaths } from "@/router"; import { router, remainingPaths } from "@/router";
import { computed, type CSSProperties } from "vue"; import { computed, type CSSProperties } from "vue";
import { useAppStoreHook } from "@/store/modules/app"; import { useAppStoreHook } from "@/store/modules/app";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
import { useGlobal, isAllEmpty } from "@pureadmin/utils"; import { useGlobal, isAllEmpty } from "@pureadmin/utils";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import ExitFullscreen from "~icons/ri/fullscreen-exit-fill"; import ExitFullscreen from "~icons/ri/fullscreen-exit-fill";
import Fullscreen from "~icons/ri/fullscreen-fill"; import Fullscreen from "~icons/ri/fullscreen-fill";
@@ -21,6 +19,7 @@ const errorInfo =
"The current routing configuration is incorrect, please check the configuration"; "The current routing configuration is incorrect, please check the configuration";
export function useNav() { export function useNav() {
const route = useRoute();
const pureApp = useAppStoreHook(); const pureApp = useAppStoreHook();
const routers = useRouter().options.routes; const routers = useRouter().options.routes;
const { isFullscreen, toggle } = useFullscreen(); const { isFullscreen, toggle } = useFullscreen();
@@ -52,22 +51,6 @@ export function useNav() {
: useUserStoreHook()?.nickname; : useUserStoreHook()?.nickname;
}); });
/** 设置国际化选中后的样式 */
const getDropdownItemStyle = computed(() => {
return (locale, t) => {
return {
background: locale === t ? useEpThemeStoreHook().epThemeColor : "",
color: locale === t ? "#f4f4f5" : "#000"
};
};
});
const getDropdownItemClass = computed(() => {
return (locale, t) => {
return locale === t ? "" : "dark:hover:text-primary!";
};
});
const avatarsStyle = computed(() => { const avatarsStyle = computed(() => {
return username.value ? { marginRight: "10px" } : ""; return username.value ? { marginRight: "10px" } : "";
}); });
@@ -92,8 +75,8 @@ export function useNav() {
/** 动态title */ /** 动态title */
function changeTitle(meta: routeMetaType) { function changeTitle(meta: routeMetaType) {
const Title = getConfig().Title; const Title = getConfig().Title;
if (Title) document.title = `${transformI18n(meta.title)} | ${Title}`; if (Title) document.title = `${meta.title} | ${Title}`;
else document.title = transformI18n(meta.title); else document.title = meta.title;
} }
/** 退出登录 */ /** 退出登录 */
@@ -144,6 +127,7 @@ export function useNav() {
} }
return { return {
route,
title, title,
device, device,
layout, layout,
@@ -168,8 +152,6 @@ export function useNav() {
username, username,
userAvatar, userAvatar,
avatarsStyle, avatarsStyle,
tooltipEffect, tooltipEffect
getDropdownItemStyle,
getDropdownItemClass
}; };
} }

View File

@@ -9,7 +9,6 @@ import {
} from "vue"; } from "vue";
import type { tagsViewsType } from "../types"; import type { tagsViewsType } from "../types";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { transformI18n, $t } from "@/plugins/i18n";
import { responsiveStorageNameSpace } from "@/config"; import { responsiveStorageNameSpace } from "@/config";
import { useSettingStoreHook } from "@/store/modules/settings"; import { useSettingStoreHook } from "@/store/modules/settings";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
@@ -64,49 +63,49 @@ export function useTags() {
const tagsViews = reactive<Array<tagsViewsType>>([ const tagsViews = reactive<Array<tagsViewsType>>([
{ {
icon: RefreshRight, icon: RefreshRight,
text: $t("buttons.pureReload"), text: "重新加载",
divided: false, divided: false,
disabled: false, disabled: false,
show: true show: true
}, },
{ {
icon: Close, icon: Close,
text: $t("buttons.pureCloseCurrentTab"), text: "关闭当前标签页",
divided: false, divided: false,
disabled: multiTags.value.length > 1 ? false : true, disabled: multiTags.value.length > 1 ? false : true,
show: true show: true
}, },
{ {
icon: CloseLeftTags, icon: CloseLeftTags,
text: $t("buttons.pureCloseLeftTabs"), text: "关闭左侧标签页",
divided: true, divided: true,
disabled: multiTags.value.length > 1 ? false : true, disabled: multiTags.value.length > 1 ? false : true,
show: true show: true
}, },
{ {
icon: CloseRightTags, icon: CloseRightTags,
text: $t("buttons.pureCloseRightTabs"), text: "关闭右侧标签页",
divided: false, divided: false,
disabled: multiTags.value.length > 1 ? false : true, disabled: multiTags.value.length > 1 ? false : true,
show: true show: true
}, },
{ {
icon: CloseOtherTags, icon: CloseOtherTags,
text: $t("buttons.pureCloseOtherTabs"), text: "关闭其他标签页",
divided: true, divided: true,
disabled: multiTags.value.length > 2 ? false : true, disabled: multiTags.value.length > 2 ? false : true,
show: true show: true
}, },
{ {
icon: CloseAllTags, icon: CloseAllTags,
text: $t("buttons.pureCloseAllTabs"), text: "关闭全部标签页",
divided: false, divided: false,
disabled: multiTags.value.length > 1 ? false : true, disabled: multiTags.value.length > 1 ? false : true,
show: true show: true
}, },
{ {
icon: Fullscreen, icon: Fullscreen,
text: $t("buttons.pureContentFullScreen"), text: "内容区全屏",
divided: true, divided: true,
disabled: false, disabled: false,
show: true show: true
@@ -114,21 +113,14 @@ export function useTags() {
]); ]);
function conditionHandle(item, previous, next) { function conditionHandle(item, previous, next) {
const currentName = route.name || "";
const itemName = item.name || "";
if (isBoolean(route?.meta?.showLink) && route?.meta?.showLink === false) { if (isBoolean(route?.meta?.showLink) && route?.meta?.showLink === false) {
if (Object.keys(route.query).length > 0) { if (Object.keys(route.query).length > 0) {
return currentName === itemName && isEqual(route.query, item.query) return isEqual(route.query, item.query) ? previous : next;
? previous
: next;
} else { } else {
return currentName === itemName && isEqual(route.params, item.params) return isEqual(route.params, item.params) ? previous : next;
? previous
: next;
} }
} else { } else {
return currentName === itemName ? previous : next; return route.path === item.path ? previous : next;
} }
} }
@@ -244,12 +236,10 @@ export function useTags() {
currentSelect, currentSelect,
scheduleIsActive, scheduleIsActive,
getContextMenuStyle, getContextMenuStyle,
$t,
closeMenu, closeMenu,
onMounted, onMounted,
onMouseenter, onMouseenter,
onMouseleave, onMouseleave,
transformI18n,
onContentFullScreen onContentFullScreen
}; };
} }

View File

@@ -1,41 +0,0 @@
import { useNav } from "./useNav";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import { watch, onBeforeMount, type Ref } from "vue";
export function useTranslationLang(ref?: Ref) {
const { $storage, changeTitle, handleResize } = useNav();
const { locale, t } = useI18n();
const route = useRoute();
function translationCh() {
$storage.locale = { locale: "zh" };
locale.value = "zh";
ref && handleResize(ref.value);
}
function translationEn() {
$storage.locale = { locale: "en" };
locale.value = "en";
ref && handleResize(ref.value);
}
watch(
() => locale.value,
() => {
changeTitle(route.meta);
}
);
onBeforeMount(() => {
locale.value = $storage.locale?.locale ?? "zh";
});
return {
t,
route,
locale,
translationCh,
translationEn
};
}

View File

@@ -3,7 +3,6 @@ import "animate.css";
// 引入 src/components/ReIcon/src/offlineIcon.ts 文件中所有使用addIcon添加过的本地图标 // 引入 src/components/ReIcon/src/offlineIcon.ts 文件中所有使用addIcon添加过的本地图标
import "@/components/ReIcon/src/offlineIcon"; import "@/components/ReIcon/src/offlineIcon";
import { setType } from "./types"; import { setType } from "./types";
import { useI18n } from "vue-i18n";
import { useLayout } from "./hooks/useLayout"; import { useLayout } from "./hooks/useLayout";
import { useAppStoreHook } from "@/store/modules/app"; import { useAppStoreHook } from "@/store/modules/app";
import { useSettingStoreHook } from "@/store/modules/settings"; import { useSettingStoreHook } from "@/store/modules/settings";
@@ -32,7 +31,6 @@ import NavVertical from "./components/lay-sidebar/NavVertical.vue";
import NavHorizontal from "./components/lay-sidebar/NavHorizontal.vue"; import NavHorizontal from "./components/lay-sidebar/NavHorizontal.vue";
import BackTopIcon from "@/assets/svg/back_top.svg?component"; import BackTopIcon from "@/assets/svg/back_top.svg?component";
const { t } = useI18n();
const appWrapperRef = ref(); const appWrapperRef = ref();
const { isDark } = useDark(); const { isDark } = useDark();
const { layout } = useLayout(); const { layout } = useLayout();
@@ -188,7 +186,7 @@ const LayHeader = defineComponent({
</div> </div>
<el-scrollbar v-else> <el-scrollbar v-else>
<el-backtop <el-backtop
:title="t('buttons.pureBackTop')" title="回到顶部"
target=".main-container .el-scrollbar__wrap" target=".main-container .el-scrollbar__wrap"
> >
<BackTopIcon /> <BackTopIcon />

View File

@@ -6,9 +6,8 @@ export const routerArrays: Array<RouteConfigs> =
? [ ? [
{ {
path: "/welcome", path: "/welcome",
name: "Welcome",
meta: { meta: {
title: "menus.pureHome", title: "首页",
icon: "ep/home-filled" icon: "ep/home-filled"
} }
} }

View File

@@ -1,7 +1,6 @@
import App from "./App.vue"; import App from "./App.vue";
import router from "./router"; import router from "./router";
import { setupStore } from "@/store"; import { setupStore } from "@/store";
import { useI18n } from "@/plugins/i18n";
import { getPlatformConfig } from "./config"; import { getPlatformConfig } from "./config";
import { MotionPlugin } from "@vueuse/motion"; import { MotionPlugin } from "@vueuse/motion";
// import { useEcharts } from "@/plugins/echarts"; // import { useEcharts } from "@/plugins/echarts";
@@ -58,7 +57,7 @@ getPlatformConfig(app).then(async config => {
app.use(router); app.use(router);
await router.isReady(); await router.isReady();
injectResponsiveStorage(app, config); injectResponsiveStorage(app, config);
app.use(MotionPlugin).use(useI18n).use(useElementPlus).use(Table); app.use(MotionPlugin).use(useElementPlus).use(Table);
// .use(PureDescriptions) // .use(PureDescriptions)
// .use(useEcharts); // .use(useEcharts);
app.mount("#app"); app.mount("#app");

View File

@@ -1,116 +0,0 @@
// 多组件库的国际化和本地项目国际化兼容
import { type I18n, createI18n } from "vue-i18n";
import type { App, WritableComputedRef } from "vue";
import { responsiveStorageNameSpace } from "@/config";
import { storageLocal, isObject } from "@pureadmin/utils";
// element-plus国际化
import enLocale from "element-plus/es/locale/lang/en";
import zhLocale from "element-plus/es/locale/lang/zh-cn";
const siphonI18n = (function () {
// 仅初始化一次国际化配置
const 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(key);
}
}
}
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({
legacy: false,
locale:
storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}locale`
)?.locale ?? "zh",
fallbackLocale: "en",
messages: localesConfigs
});
export function useI18n(app: App) {
app.use(i18n);
}

View File

@@ -2,7 +2,6 @@
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import NProgress from "@/utils/progress"; import NProgress from "@/utils/progress";
import { transformI18n } from "@/plugins/i18n";
import { buildHierarchyTree } from "@/utils/tree"; import { buildHierarchyTree } from "@/utils/tree";
import remainingRouter from "./modules/remaining"; import remainingRouter from "./modules/remaining";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
@@ -94,14 +93,6 @@ export const router: Router = createRouter({
} }
}); });
/** 记录已经加载的页面路径 */
const loadedPaths = new Set<string>();
/** 重置已加载页面记录 */
export function resetLoadedPaths() {
loadedPaths.clear();
}
/** 重置路由 */ /** 重置路由 */
export function resetRouter() { export function resetRouter() {
router.clearRoutes(); router.clearRoutes();
@@ -112,7 +103,6 @@ export function resetRouter() {
formatFlatteningRoutes(buildHierarchyTree(ascending(routes.flat(Infinity)))) formatFlatteningRoutes(buildHierarchyTree(ascending(routes.flat(Infinity))))
); );
usePermissionStoreHook().clearAllCachePage(); usePermissionStoreHook().clearAllCachePage();
resetLoadedPaths();
} }
/** 路由白名单 */ /** 路由白名单 */
@@ -121,12 +111,6 @@ const whiteList = ["/login"];
const { VITE_HIDE_HOME } = import.meta.env; const { VITE_HIDE_HOME } = import.meta.env;
router.beforeEach((to: ToRouteType, _from, next) => { router.beforeEach((to: ToRouteType, _from, next) => {
to.meta.loaded = loadedPaths.has(to.path);
if (!to.meta.loaded) {
NProgress.start();
}
if (to.meta?.keepAlive) { if (to.meta?.keepAlive) {
handleAliveRoute(to, "add"); handleAliveRoute(to, "add");
// 页面整体刷新和点击标签页刷新 // 页面整体刷新和点击标签页刷新
@@ -135,14 +119,14 @@ router.beforeEach((to: ToRouteType, _from, next) => {
} }
} }
const userInfo = storageLocal().getItem<DataInfo<number>>(userKey); const userInfo = storageLocal().getItem<DataInfo<number>>(userKey);
NProgress.start();
const externalLink = isUrl(to?.name as string); const externalLink = isUrl(to?.name as string);
if (!externalLink) { if (!externalLink) {
to.matched.some(item => { to.matched.some(item => {
if (!item.meta.title) return ""; if (!item.meta.title) return "";
const Title = getConfig().Title; const Title = getConfig().Title;
if (Title) if (Title) document.title = `${item.meta.title} | ${Title}`;
document.title = `${transformI18n(item.meta.title)} | ${Title}`; else document.title = item.meta.title as string;
else document.title = transformI18n(item.meta.title);
}); });
} }
/** 如果已经登录并存在登录信息后不能跳转到路由白名单,而是继续保持在当前页面 */ /** 如果已经登录并存在登录信息后不能跳转到路由白名单,而是继续保持在当前页面 */
@@ -220,8 +204,7 @@ router.beforeEach((to: ToRouteType, _from, next) => {
} }
}); });
router.afterEach(to => { router.afterEach(() => {
loadedPaths.add(to.path);
NProgress.done(); NProgress.done();
}); });

View File

@@ -1,12 +1,10 @@
import { $t } from "@/plugins/i18n";
export default { export default {
path: "/error", path: "/error",
redirect: "/error/403", redirect: "/error/403",
meta: { meta: {
icon: "ri/information-line", icon: "ri/information-line",
// showLink: false, // showLink: false,
title: $t("menus.pureAbnormal"), title: "异常页面",
rank: 9 rank: 9
}, },
children: [ children: [
@@ -15,7 +13,7 @@ export default {
name: "403", name: "403",
component: () => import("@/views/error/403.vue"), component: () => import("@/views/error/403.vue"),
meta: { meta: {
title: $t("menus.pureAccessDenied") title: "403"
} }
}, },
{ {
@@ -23,7 +21,7 @@ export default {
name: "404", name: "404",
component: () => import("@/views/error/404.vue"), component: () => import("@/views/error/404.vue"),
meta: { meta: {
title: $t("menus.purePageNotFound") title: "404"
} }
}, },
{ {
@@ -31,7 +29,7 @@ export default {
name: "500", name: "500",
component: () => import("@/views/error/500.vue"), component: () => import("@/views/error/500.vue"),
meta: { meta: {
title: $t("menus.pureServerError") title: "500"
} }
} }
] ]

View File

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

View File

@@ -1,4 +1,3 @@
import { $t } from "@/plugins/i18n";
const Layout = () => import("@/layout/index.vue"); const Layout = () => import("@/layout/index.vue");
export default [ export default [
@@ -7,36 +6,18 @@ export default [
name: "Login", name: "Login",
component: () => import("@/views/login/index.vue"), component: () => import("@/views/login/index.vue"),
meta: { meta: {
title: $t("menus.pureLogin"), title: "登录",
showLink: false showLink: false,
} rank: 101
},
// 全屏403无权访问页面
{
path: "/access-denied",
name: "AccessDenied",
component: () => import("@/views/error/403.vue"),
meta: {
title: $t("menus.pureAccessDenied"),
showLink: false
}
},
// 全屏500服务器出错页面
{
path: "/server-error",
name: "ServerError",
component: () => import("@/views/error/500.vue"),
meta: {
title: $t("menus.pureServerError"),
showLink: false
} }
}, },
{ {
path: "/redirect", path: "/redirect",
component: Layout, component: Layout,
meta: { meta: {
title: $t("status.pureLoad"), title: "加载中...",
showLink: false showLink: false,
rank: 102
}, },
children: [ children: [
{ {

View File

@@ -139,17 +139,12 @@ function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
} }
} }
/** 动态路由注册完成后再添加全屏404页面不存在页面避免刷新动态路由页面时误跳转到404页面 */
function addPathMatch() { function addPathMatch() {
if (!router.hasRoute("pathMatch")) { if (!router.hasRoute("pathMatch")) {
router.addRoute({ router.addRoute({
path: "/:pathMatch(.*)*", path: "/:pathMatch(.*)",
name: "PageNotFound", name: "pathMatch",
component: () => import("@/views/error/404.vue"), redirect: "/error/404"
meta: {
title: "menus.purePageNotFound",
showLink: false
}
}); });
} }
} }

View File

@@ -80,15 +80,22 @@ export const useMultiTagsStore = defineStore("pure-multiTags", {
if (isBoolean(tagVal?.meta?.showLink) && !tagVal?.meta?.showLink) if (isBoolean(tagVal?.meta?.showLink) && !tagVal?.meta?.showLink)
return; return;
const tagPath = tagVal.path; const tagPath = tagVal.path;
// 判断tag是否已存在
const tagHasExits = this.multiTags.some(tag => { const tagHasExits = this.multiTags.some(tag => {
return ( return tag.path === tagPath;
tag.path === tagPath &&
isEqual(tag?.query, tagVal?.query) &&
isEqual(tag?.params, tagVal?.params)
);
}); });
if (tagHasExits) return; // 判断tag中的query键值是否相等
const tagQueryHasExits = this.multiTags.some(tag => {
return isEqual(tag?.query, tagVal?.query);
});
// 判断tag中的params键值是否相等
const tagParamsHasExits = this.multiTags.some(tag => {
return isEqual(tag?.params, tagVal?.params);
});
if (tagHasExits && tagQueryHasExits && tagParamsHasExits) return;
// 动态路由可打开的最大数量 // 动态路由可打开的最大数量
const dynamicLevel = tagVal?.meta?.dynamicLevel ?? -1; const dynamicLevel = tagVal?.meta?.dynamicLevel ?? -1;

View File

@@ -2,6 +2,7 @@ import { defineStore } from "pinia";
import { import {
type cacheType, type cacheType,
store, store,
debounce,
ascending, ascending,
getKeyList, getKeyList,
filterTree, filterTree,
@@ -32,35 +33,33 @@ export const usePermissionStore = defineStore("pure-permission", {
this.constantMenus.concat(routes) as any this.constantMenus.concat(routes) as any
); );
}, },
/** 监听缓存页面是否存在于标签页,不存在则删除 */
clearCache() {
let cacheLength = this.cachePageList.length;
const nameList = getKeyList(useMultiTagsStoreHook().multiTags, "name");
while (cacheLength > 0) {
nameList.findIndex(v => v === this.cachePageList[cacheLength - 1]) ===
-1 &&
this.cachePageList.splice(
this.cachePageList.indexOf(this.cachePageList[cacheLength - 1]),
1
);
cacheLength--;
}
},
cacheOperate({ mode, name }: cacheType) { cacheOperate({ mode, name }: cacheType) {
const delIndex = this.cachePageList.findIndex(v => v === name); const delIndex = this.cachePageList.findIndex(v => v === name);
switch (mode) { switch (mode) {
case "refresh": case "refresh":
this.cachePageList = this.cachePageList.filter(v => v !== name); this.cachePageList = this.cachePageList.filter(v => v !== name);
this.clearCache();
break; break;
case "add": case "add":
this.cachePageList.push(name); this.cachePageList.push(name);
break; break;
case "delete": case "delete":
delIndex !== -1 && this.cachePageList.splice(delIndex, 1); delIndex !== -1 && this.cachePageList.splice(delIndex, 1);
this.clearCache();
break; break;
} }
/** 监听缓存页面是否存在于标签页,不存在则删除 */
debounce(() => {
let cacheLength = this.cachePageList.length;
const nameList = getKeyList(useMultiTagsStoreHook().multiTags, "name");
while (cacheLength > 0) {
nameList.findIndex(v => v === this.cachePageList[cacheLength - 1]) ===
-1 &&
this.cachePageList.splice(
this.cachePageList.indexOf(this.cachePageList[cacheLength - 1]),
1
);
cacheLength--;
}
})();
}, },
/** 清空缓存页面 */ /** 清空缓存页面 */
clearAllCachePage() { clearAllCachePage() {

View File

@@ -172,6 +172,10 @@
.is-active > .el-sub-menu__title, .is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown { .is-active.submenu-title-noDropdown {
color: var(--pure-theme-sub-menu-active-text) !important; color: var(--pure-theme-sub-menu-active-text) !important;
i {
color: var(--pure-theme-sub-menu-active-text) !important;
}
} }
.is-active { .is-active {
@@ -266,6 +270,10 @@
.is-active > .el-sub-menu__title, .is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown { .is-active.submenu-title-noDropdown {
color: var(--pure-theme-sub-menu-active-text) !important; color: var(--pure-theme-sub-menu-active-text) !important;
i {
color: var(--pure-theme-sub-menu-active-text) !important;
}
} }
/* 子菜单中还有子菜单 */ /* 子菜单中还有子菜单 */
@@ -366,6 +374,10 @@
.is-active > .el-sub-menu__title, .is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown { .is-active.submenu-title-noDropdown {
color: var(--pure-theme-sub-menu-active-text) !important; color: var(--pure-theme-sub-menu-active-text) !important;
i {
color: var(--pure-theme-sub-menu-active-text) !important;
}
} }
.nest-menu .el-sub-menu > .el-sub-menu__title, .nest-menu .el-sub-menu > .el-sub-menu__title,
@@ -448,8 +460,6 @@
/* 搜索 */ /* 搜索 */
.search-container, .search-container,
/* 国际化 */
.globalization,
/* 全屏 */ /* 全屏 */
.fullscreen-icon, .fullscreen-icon,
/* 消息通知 */ /* 消息通知 */
@@ -468,15 +478,6 @@
color: var(--pure-theme-sub-menu-active-text); color: var(--pure-theme-sub-menu-active-text);
} }
.globalization {
width: 40px;
height: 48px;
padding: 11px;
color: var(--pure-theme-sub-menu-active-text);
cursor: pointer;
outline: none;
}
.el-dropdown-link { .el-dropdown-link {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -529,6 +530,10 @@
.is-active > .el-sub-menu__title, .is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown { .is-active.submenu-title-noDropdown {
color: var(--pure-theme-sub-menu-active-text) !important; color: var(--pure-theme-sub-menu-active-text) !important;
i {
color: var(--pure-theme-sub-menu-active-text) !important;
}
} }
.is-active { .is-active {
@@ -629,8 +634,6 @@ body[layout="vertical"] {
/* 搜索 */ /* 搜索 */
.search-container, .search-container,
/* 国际化 */
.globalization,
/* 全屏 */ /* 全屏 */
.fullscreen-icon, .fullscreen-icon,
/* 消息通知 */ /* 消息通知 */

View File

@@ -10,6 +10,7 @@ import type {
PureHttpRequestConfig PureHttpRequestConfig
} from "./types.d"; } from "./types.d";
import { stringify } from "qs"; import { stringify } from "qs";
import NProgress from "../progress";
import { getToken, formatToken } from "@/utils/auth"; import { getToken, formatToken } from "@/utils/auth";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
@@ -60,6 +61,8 @@ class PureHttp {
private httpInterceptorsRequest(): void { private httpInterceptorsRequest(): void {
PureHttp.axiosInstance.interceptors.request.use( PureHttp.axiosInstance.interceptors.request.use(
async (config: PureHttpRequestConfig): Promise<any> => { async (config: PureHttpRequestConfig): Promise<any> => {
// 开启进度条动画
NProgress.start();
// 优先判断post/get等方法是否传入回调否则执行初始化设置等回调 // 优先判断post/get等方法是否传入回调否则执行初始化设置等回调
if (typeof config.beforeRequestCallback === "function") { if (typeof config.beforeRequestCallback === "function") {
config.beforeRequestCallback(config); config.beforeRequestCallback(config);
@@ -118,6 +121,8 @@ class PureHttp {
instance.interceptors.response.use( instance.interceptors.response.use(
(response: PureHttpResponse) => { (response: PureHttpResponse) => {
const $config = response.config; const $config = response.config;
// 关闭进度条动画
NProgress.done();
// 优先判断post/get等方法是否传入回调否则执行初始化设置等回调 // 优先判断post/get等方法是否传入回调否则执行初始化设置等回调
if (typeof $config.beforeResponseCallback === "function") { if (typeof $config.beforeResponseCallback === "function") {
$config.beforeResponseCallback(response); $config.beforeResponseCallback(response);
@@ -132,6 +137,8 @@ class PureHttp {
(error: PureHttpError) => { (error: PureHttpError) => {
const $error = error; const $error = error;
$error.isCancelRequest = Axios.isCancel($error); $error.isCancelRequest = Axios.isCancel($error);
// 关闭进度条动画
NProgress.done();
// 所有的响应异常 区分来源为取消请求/非取消请求 // 所有的响应异常 区分来源为取消请求/非取消请求
return Promise.reject($error); return Promise.reject($error);
} }

View File

@@ -8,10 +8,6 @@ export const injectResponsiveStorage = (app: App, config: PlatformConfigs) => {
const nameSpace = responsiveStorageNameSpace(); const nameSpace = responsiveStorageNameSpace();
const configObj = Object.assign( const configObj = Object.assign(
{ {
// 国际化 默认中文zh
locale: Storage.getData("locale", nameSpace) ?? {
locale: config.Locale ?? "zh"
},
// layout模式以及主题 // layout模式以及主题
layout: Storage.getData("layout", nameSpace) ?? { layout: Storage.getData("layout", nameSpace) ?? {
layout: config.Layout ?? "vertical", layout: config.Layout ?? "vertical",

View File

@@ -10,11 +10,9 @@ const router = useRouter();
</script> </script>
<template> <template>
<div <div class="flex justify-center items-center h-[640px]">
class="flex flex-col md:flex-row justify-center items-center min-h-full w-full p-4 md:p-0"
>
<noAccess /> <noAccess />
<div class="mt-8 md:ml-12 md:mt-0 text-center md:text-left"> <div class="ml-12">
<p <p
v-motion v-motion
class="font-medium text-4xl mb-4! dark:text-white" class="font-medium text-4xl mb-4! dark:text-white"
@@ -34,7 +32,7 @@ const router = useRouter();
</p> </p>
<p <p
v-motion v-motion
class="text-xl mb-4! text-gray-500" class="mb-4! text-gray-500"
:initial="{ :initial="{
opacity: 0, opacity: 0,
y: 100 y: 100
@@ -52,7 +50,6 @@ const router = useRouter();
<el-button <el-button
v-motion v-motion
type="primary" type="primary"
class="block mx-auto md:inline-block md:mx-0"
:initial="{ :initial="{
opacity: 0, opacity: 0,
y: 100 y: 100
@@ -71,9 +68,3 @@ const router = useRouter();
</div> </div>
</div> </div>
</template> </template>
<style scoped>
.main-content {
margin: 0 !important;
}
</style>

View File

@@ -10,11 +10,9 @@ const router = useRouter();
</script> </script>
<template> <template>
<div <div class="flex justify-center items-center h-[640px]">
class="flex flex-col md:flex-row justify-center items-center min-h-full w-full p-4 md:p-0"
>
<noExist /> <noExist />
<div class="mt-8 md:ml-12 md:mt-0 text-center md:text-left"> <div class="ml-12">
<p <p
v-motion v-motion
class="font-medium text-4xl mb-4! dark:text-white" class="font-medium text-4xl mb-4! dark:text-white"
@@ -34,7 +32,7 @@ const router = useRouter();
</p> </p>
<p <p
v-motion v-motion
class="text-xl mb-4! text-gray-500" class="mb-4! text-gray-500"
:initial="{ :initial="{
opacity: 0, opacity: 0,
y: 100 y: 100
@@ -52,7 +50,6 @@ const router = useRouter();
<el-button <el-button
v-motion v-motion
type="primary" type="primary"
class="block mx-auto md:inline-block md:mx-0"
:initial="{ :initial="{
opacity: 0, opacity: 0,
y: 100 y: 100
@@ -71,9 +68,3 @@ const router = useRouter();
</div> </div>
</div> </div>
</template> </template>
<style scoped>
.main-content {
margin: 0 !important;
}
</style>

View File

@@ -10,11 +10,9 @@ const router = useRouter();
</script> </script>
<template> <template>
<div <div class="flex justify-center items-center h-[640px]">
class="flex flex-col md:flex-row justify-center items-center min-h-full w-full p-4 md:p-0"
>
<noServer /> <noServer />
<div class="mt-8 md:ml-12 md:mt-0 text-center md:text-left"> <div class="ml-12">
<p <p
v-motion v-motion
class="font-medium text-4xl mb-4! dark:text-white" class="font-medium text-4xl mb-4! dark:text-white"
@@ -34,7 +32,7 @@ const router = useRouter();
</p> </p>
<p <p
v-motion v-motion
class="text-xl mb-4! text-gray-500" class="mb-4! text-gray-500"
:initial="{ :initial="{
opacity: 0, opacity: 0,
y: 100 y: 100
@@ -52,7 +50,6 @@ const router = useRouter();
<el-button <el-button
v-motion v-motion
type="primary" type="primary"
class="block mx-auto md:inline-block md:mx-0"
:initial="{ :initial="{
opacity: 0, opacity: 0,
y: 100 y: 100
@@ -71,9 +68,3 @@ const router = useRouter();
</div> </div>
</div> </div>
</template> </template>
<style scoped>
.main-content {
margin: 0 !important;
}
</style>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from "vue-i18n";
import Motion from "./utils/motion"; import Motion from "./utils/motion";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { message } from "@/utils/message"; import { message } from "@/utils/message";
@@ -9,20 +8,16 @@ import { debounce } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import { useEventListener } from "@vueuse/core"; import { useEventListener } from "@vueuse/core";
import type { FormInstance } from "element-plus"; import type { FormInstance } from "element-plus";
import { $t, transformI18n } from "@/plugins/i18n";
import { useLayout } from "@/layout/hooks/useLayout"; import { useLayout } from "@/layout/hooks/useLayout";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
import { initRouter, getTopMenu } from "@/router/utils"; import { initRouter, getTopMenu } from "@/router/utils";
import { bg, avatar, illustration } from "./utils/static"; import { bg, avatar, illustration } from "./utils/static";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { useTranslationLang } from "@/layout/hooks/useTranslationLang";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange"; import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import dayIcon from "@/assets/svg/day.svg?component"; import dayIcon from "@/assets/svg/day.svg?component";
import darkIcon from "@/assets/svg/dark.svg?component"; import darkIcon from "@/assets/svg/dark.svg?component";
import globalization from "@/assets/svg/globalization.svg?component";
import Lock from "~icons/ri/lock-fill"; import Lock from "~icons/ri/lock-fill";
import Check from "~icons/ep/check";
import User from "~icons/ri/user-3-fill"; import User from "~icons/ri/user-3-fill";
defineOptions({ defineOptions({
@@ -37,11 +32,9 @@ const ruleFormRef = ref<FormInstance>();
const { initStorage } = useLayout(); const { initStorage } = useLayout();
initStorage(); initStorage();
const { t } = useI18n();
const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange(); const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange();
dataThemeChange(overallStyle.value); dataThemeChange(overallStyle.value);
const { title, getDropdownItemStyle, getDropdownItemClass } = useNav(); const { title } = useNav();
const { locale, translationCh, translationEn } = useTranslationLang();
const ruleForm = reactive({ const ruleForm = reactive({
username: "admin", username: "admin",
@@ -66,12 +59,12 @@ const onLogin = async (formEl: FormInstance | undefined) => {
router router
.push(getTopMenu(true).path) .push(getTopMenu(true).path)
.then(() => { .then(() => {
message(t("login.pureLoginSuccess"), { type: "success" }); message("登录成功", { type: "success" });
}) })
.finally(() => (disabled.value = false)); .finally(() => (disabled.value = false));
}); });
} else { } else {
message(t("login.pureLoginFail"), { type: "error" }); message("登录失败", { type: "error" });
} }
}) })
.finally(() => (loading.value = false)); .finally(() => (loading.value = false));
@@ -107,38 +100,6 @@ useEventListener(document, "keydown", ({ code }) => {
:inactive-icon="darkIcon" :inactive-icon="darkIcon"
@change="dataThemeChange" @change="dataThemeChange"
/> />
<!-- 国际化 -->
<el-dropdown trigger="click">
<globalization
class="hover:text-primary hover:bg-[transparent]! w-[20px] h-[20px] ml-1.5 cursor-pointer outline-hidden duration-300"
/>
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')"
:class="['dark:text-white!', getDropdownItemClass(locale, 'zh')]"
@click="translationCh"
>
<IconifyIconOffline
v-show="locale === 'zh'"
class="check-zh"
:icon="Check"
/>
简体中文
</el-dropdown-item>
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')"
:class="['dark:text-white!', getDropdownItemClass(locale, 'en')]"
@click="translationEn"
>
<span v-show="locale === 'en'" class="check-en">
<IconifyIconOffline :icon="Check" />
</span>
English
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div> </div>
<div class="login-container"> <div class="login-container">
<div class="img"> <div class="img">
@@ -162,7 +123,7 @@ useEventListener(document, "keydown", ({ code }) => {
:rules="[ :rules="[
{ {
required: true, required: true,
message: transformI18n($t('login.pureUsernameReg')), message: '请输入账号',
trigger: 'blur' trigger: 'blur'
} }
]" ]"
@@ -171,7 +132,7 @@ useEventListener(document, "keydown", ({ code }) => {
<el-input <el-input
v-model="ruleForm.username" v-model="ruleForm.username"
clearable clearable
:placeholder="t('login.pureUsername')" placeholder="账号"
:prefix-icon="useRenderIcon(User)" :prefix-icon="useRenderIcon(User)"
/> />
</el-form-item> </el-form-item>
@@ -183,7 +144,7 @@ useEventListener(document, "keydown", ({ code }) => {
v-model="ruleForm.password" v-model="ruleForm.password"
clearable clearable
show-password show-password
:placeholder="t('login.purePassword')" placeholder="密码"
:prefix-icon="useRenderIcon(Lock)" :prefix-icon="useRenderIcon(Lock)"
/> />
</el-form-item> </el-form-item>
@@ -198,7 +159,7 @@ useEventListener(document, "keydown", ({ code }) => {
:disabled="disabled" :disabled="disabled"
@click="onLogin(ruleFormRef)" @click="onLogin(ruleFormRef)"
> >
{{ t("login.pureLogin") }} 登录
</el-button> </el-button>
</Motion> </Motion>
</el-form> </el-form>
@@ -216,20 +177,4 @@ useEventListener(document, "keydown", ({ code }) => {
:deep(.el-input-group__append, .el-input-group__prepend) { :deep(.el-input-group__append, .el-input-group__prepend) {
padding: 0; padding: 0;
} }
.translation {
::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px;
}
.check-zh {
position: absolute;
left: 20px;
}
.check-en {
position: absolute;
left: 20px;
}
}
</style> </style>

View File

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

View File

@@ -5,5 +5,5 @@ defineOptions({
</script> </script>
<template> <template>
<h1>Pure-Admin-Thin国际化版本</h1> <h1>Pure-Admin-Thin国际化版本</h1>
</template> </template>

2
types/router.d.ts vendored
View File

@@ -56,8 +56,6 @@ declare global {
* 而通过设置`activePath`指定激活菜单即可获得高亮,`activePath`为指定激活菜单的`path` * 而通过设置`activePath`指定激活菜单即可获得高亮,`activePath`为指定激活菜单的`path`
*/ */
activePath?: string; activePath?: string;
/** 当前页面是否已经加载过 */
loaded?: boolean;
} }
/** /**