Compare commits

..

65 Commits
i18n ... v5.7.0

Author SHA1 Message Date
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
125 changed files with 4691 additions and 5803 deletions

2
.nvmrc
View File

@@ -1 +1 @@
v22.20.0
v20.13.1

View File

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

15
.vscode/settings.json vendored
View File

@@ -1,5 +1,4 @@
{
"tailwindCSS.experimental.configFile": "src/style/tailwind.css",
"editor.formatOnType": true,
"editor.formatOnSave": true,
"[vue]": {
@@ -28,19 +27,6 @@
"editor.codeActionsOnSave": {
"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": [
"el"
],
@@ -50,7 +36,6 @@
"v-copy",
"v-longpress",
"v-optimize",
"v-perms",
"v-ripple"
],
"vscodeCustomCodeColor.highlightValueColor": "#b392f0",

View File

@@ -1,8 +1,8 @@
FROM node:20-alpine as build-stage
FROM node:18-alpine as build-stage
WORKDIR /app
RUN corepack enable
RUN corepack prepare pnpm@latest --activate
RUN corepack prepare pnpm@8.6.10 --activate
RUN npm config set registry https://registry.npmmirror.com

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)
@@ -15,12 +15,12 @@ The simplified version is based on the shelf extracted from [vue-pure-admin](htt
## Nanny-level documents
[Click me to view vue-pure-admin documentation](https://pure-admin.cn/)
[Click me to view vue-pure-admin documentation](https://pure-admin.github.io/pure-admin-doc)
[Click me to view @pureadmin/utils documentation](https://pure-admin-utils.netlify.app)
## Premium service
## Quality service, software outsourcing, sponsorship support
[Click me to view details](https://pure-admin.cn/pages/service/)
[Click me to view details](https://pure-admin.github.io/pure-admin-doc/pages/service/)
## Preview

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)
@@ -10,7 +10,7 @@
## 版本选择
当前是国际化版本,如果您需要国际化版本 [请点击](https://github.com/pure-admin/pure-admin-thin)
当前是国际化版本,如果您需要国际化版本 [请点击](https://github.com/pure-admin/pure-admin-thin/tree/i18n)
## 配套视频
@@ -19,12 +19,12 @@
## 配套保姆级文档
[点我查看 vue-pure-admin 文档](https://pure-admin.cn/)
[点我查看 vue-pure-admin 文档](https://pure-admin.github.io/pure-admin-doc)
[点我查看 @pureadmin/utils 文档](https://pure-admin-utils.netlify.app)
## 高级服务
## 优质服务、软件外包、赞助支持
[点我查看详情](https://pure-admin.cn/pages/service/)
[点我查看详情](https://pure-admin.github.io/pure-admin-doc/pages/service/)
## 预览

View File

@@ -19,11 +19,6 @@ export const cdn = importToCDN({
var: "VueRouter",
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
{
name: "vue-demi",

View File

@@ -1,13 +1,13 @@
import type { Plugin } from "vite";
import gradient from "gradient-string";
import { getPackageSize } from "./utils";
import dayjs, { type Dayjs } from "dayjs";
import duration from "dayjs/plugin/duration";
import gradientString from "gradient-string";
import boxen, { type Options as BoxenOptions } from "boxen";
dayjs.extend(duration);
const welcomeMessage = gradient(["cyan", "magenta"]).multiline(
`您好! 欢迎使用 pure-admin 开源项目\n我们为您精心准备了下面两个贴心的保姆级文档\nhttps://pure-admin.cn\nhttps://pure-admin-utils.netlify.app`
const welcomeMessage = gradientString("cyan", "magenta").multiline(
`您好! 欢迎使用 pure-admin 开源项目\n我们为您精心准备了下面两个贴心的保姆级文档\nhttps://pure-admin.github.io/pure-admin-doc\nhttps://pure-admin-utils.netlify.app`
);
const boxenOptions: BoxenOptions = {
@@ -41,7 +41,7 @@ export function viteBuildInfo(): Plugin {
callback: (size: string) => {
console.log(
boxen(
gradient(["cyan", "magenta"]).multiline(
gradientString("cyan", "magenta").multiline(
`🎉 恭喜打包完成(总用时${dayjs
.duration(endTime.diff(startTime))
.format("mm分ss秒")},打包后的大小为${size}`

View File

@@ -10,7 +10,6 @@ const include = [
"dayjs",
"axios",
"pinia",
"vue-i18n",
"vue-types",
"js-cookie",
"vue-tippy",
@@ -23,8 +22,12 @@ const include = [
/**
* 在预构建中强制排除的依赖项
* 温馨提示:平台推荐的使用方式是哪里需要哪里引入而且都是单个的引入,不需要预构建,直接让浏览器加载就好
* 温馨提示:所有以 `@iconify-icons/` 开头引入的的本地图标模块,都应该加入到下面的 `exclude` 里,因为平台推荐的使用方式是哪里需要哪里引入而且都是单个的引入,不需要预构建,直接让浏览器加载就好
*/
const exclude = ["@iconify/json"];
const exclude = [
"@iconify-icons/ep",
"@iconify-icons/ri",
"@pureadmin/theme/dist/browser-utils"
];
export { include, exclude };

View File

@@ -1,18 +1,16 @@
import { cdn } from "./cdn";
import vue from "@vitejs/plugin-vue";
import { pathResolve } from "./utils";
import { viteBuildInfo } from "./info";
import svgLoader from "vite-svg-loader";
import Icons from "unplugin-icons/vite";
import type { PluginOption } from "vite";
import vueJsx from "@vitejs/plugin-vue-jsx";
import tailwindcss from "@tailwindcss/vite";
import Inspector from "vite-plugin-vue-inspector";
import { configCompressPlugin } from "./compress";
import removeNoMatch from "vite-plugin-router-warn";
import { visualizer } from "rollup-plugin-visualizer";
import removeConsole from "vite-plugin-remove-console";
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
import { codeInspectorPlugin } from "code-inspector-plugin";
import { themePreprocessorPlugin } from "@pureadmin/theme";
import { genScssMultipleScopeVars } from "../src/layout/theme";
import { vitePluginFakeServer } from "vite-plugin-fake-server";
export function getPluginsList(
@@ -21,23 +19,11 @@ export function getPluginsList(
): PluginOption[] {
const lifecycle = process.env.npm_lifecycle_event;
return [
tailwindcss(),
vue(),
// jsx、tsx语法支持
vueJsx(),
VueI18nPlugin({
include: [pathResolve("../locales/**")]
}),
/**
* 在页面上按住组合键时,鼠标在页面移动即会在 DOM 上出现遮罩层并显示相关信息,点击一下将自动打开 IDE 并将光标定位到元素对应的代码位置
* Mac 默认组合键 Option + Shift
* Windows 默认组合键 Alt + Shift
* 更多用法看 https://inspector.fe-dev.cn/guide/start.html
*/
codeInspectorPlugin({
bundler: "vite",
hideConsole: true
}),
// 按下Command(⌘)+Shift(⇧)然后点击页面元素会自动打开本地IDE并跳转到对应的代码位置
Inspector(),
viteBuildInfo(),
/**
* 开发环境下移除非必要的vue-router动态路由警告No match found for location with path
@@ -52,13 +38,15 @@ export function getPluginsList(
infixName: false,
enableProd: true
}),
// 自定义主题
themePreprocessorPlugin({
scss: {
multipleScopeVars: genScssMultipleScopeVars(),
extract: true
}
}),
// svg组件化支持
svgLoader(),
// 自动按需加载图标
Icons({
compiler: "vue3",
scale: 1
}),
VITE_CDN ? cdn : null,
configCompressPlugin(VITE_COMPRESSION),
// 线上环境删除console

View File

@@ -1,25 +1,26 @@
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginVue from "eslint-plugin-vue";
import * as parserVue from "vue-eslint-parser";
import configPrettier from "eslint-config-prettier";
import pluginPrettier from "eslint-plugin-prettier";
import { defineConfig, globalIgnores } from "eslint/config";
import { defineFlatConfig } from "eslint-define-config";
import * as parserTypeScript from "@typescript-eslint/parser";
import pluginTypeScript from "@typescript-eslint/eslint-plugin";
export default defineConfig([
globalIgnores([
export default defineFlatConfig([
{
...js.configs.recommended,
ignores: [
"**/.*",
"dist/*",
"*.d.ts",
"public/*",
"src/assets/**",
"src/**/iconfont/**"
]),
{
...js.configs.recommended,
],
languageOptions: {
globals: {
// types/index.d.ts
// index.d.ts
RefType: "readonly",
EmitType: "readonly",
TargetContext: "readonly",
@@ -72,18 +73,26 @@ export default defineConfig([
]
}
},
...tseslint.config({
extends: [...tseslint.configs.recommended],
{
files: ["**/*.?([cm])ts", "**/*.?([cm])tsx"],
languageOptions: {
parser: parserTypeScript,
parserOptions: {
sourceType: "module"
}
},
plugins: {
"@typescript-eslint": pluginTypeScript
},
rules: {
...pluginTypeScript.configs.strict.rules,
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-redeclare": "error",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/prefer-as-const": "warn",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/no-unsafe-function-type": "off",
"@typescript-eslint/no-import-type-side-effects": "error",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/consistent-type-imports": [
@@ -102,20 +111,20 @@ export default defineConfig([
}
]
}
}),
},
{
files: ["**/*.d.ts"],
rules: {
"eslint-comments/no-unlimited-disable": "off",
"import/no-duplicates": "off",
"no-restricted-syntax": "off",
"unused-imports/no-unused-vars": "off"
}
},
{
files: ["**/*.?([cm])js"],
rules: {
"@typescript-eslint/no-require-imports": "off"
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-var-requires": "off"
}
},
{
@@ -136,19 +145,18 @@ export default defineConfig([
jsx: true
},
extraFileExtensions: [".vue"],
parser: tseslint.parser,
parser: "@typescript-eslint/parser",
sourceType: "module"
}
},
plugins: {
"@typescript-eslint": tseslint.plugin,
vue: pluginVue
},
processor: pluginVue.processors[".vue"],
rules: {
...pluginVue.configs.base.rules,
...pluginVue.configs.essential.rules,
...pluginVue.configs.recommended.rules,
...pluginVue.configs["vue3-essential"].rules,
...pluginVue.configs["vue3-recommended"].rules,
"no-undef": "off",
"no-unused-vars": "off",
"vue/no-v-html": "off",

View File

@@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@@ -10,6 +10,9 @@
/>
<title>pure-admin-thin</title>
<link rel="icon" href="/favicon.ico" />
<script>
window.process = {};
</script>
</head>
<body>

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 = {
path: "/permission",
meta: {
title: "menus.purePermission",
title: "权限管理",
icon: "ep:lollipop",
rank: 10
},
@@ -18,39 +18,22 @@ const permissionRouter = {
path: "/permission/page/index",
name: "PermissionPage",
meta: {
title: "menus.purePermissionPage",
title: "页面权限",
roles: ["admin", "common"]
}
},
{
path: "/permission/button",
path: "/permission/button/index",
name: "PermissionButton",
meta: {
title: "menus.purePermissionButton",
roles: ["admin", "common"]
},
children: [
{
path: "/permission/button/router",
component: "permission/button/index",
name: "PermissionButtonRouter",
meta: {
title: "menus.purePermissionButtonRouter",
title: "按钮权限",
roles: ["admin", "common"],
auths: [
"permission:btn:add",
"permission:btn:edit",
"permission:btn:delete"
]
}
},
{
path: "/permission/button/login",
component: "permission/button/perms",
name: "PermissionButtonLogin",
meta: {
title: "menus.purePermissionButtonLogin"
}
}
]
}
]
};

View File

@@ -15,8 +15,6 @@ export default defineFakeRoute([
nickname: "小铭",
// 一个用户可能有多个角色
roles: ["admin"],
// 按钮级别权限
permissions: ["*:*:*"],
accessToken: "eyJhbGciOiJIUzUxMiJ9.admin",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
expires: "2030/10/30 00:00:00"
@@ -30,7 +28,6 @@ export default defineFakeRoute([
username: "common",
nickname: "小林",
roles: ["common"],
permissions: ["permission:btn:add", "permission:btn:edit"],
accessToken: "eyJhbGciOiJIUzUxMiJ9.common",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh",
expires: "2030/10/30 00:00:00"

View File

@@ -1,6 +1,6 @@
{
"name": "pure-admin-thin",
"version": "6.2.0",
"version": "5.7.0",
"private": true,
"type": "module",
"scripts": {
@@ -33,7 +33,7 @@
"vite",
"esm"
],
"homepage": "https://github.com/pure-admin/pure-admin-thin/tree/i18n",
"homepage": "https://github.com/pure-admin/pure-admin-thin",
"repository": {
"type": "git",
"url": "git+https://github.com/pure-admin/pure-admin-thin.git"
@@ -49,96 +49,96 @@
},
"dependencies": {
"@pureadmin/descriptions": "^1.2.1",
"@pureadmin/table": "^3.3.0",
"@pureadmin/utils": "^2.6.2",
"@vueuse/core": "^14.0.0",
"@vueuse/motion": "^3.0.3",
"@pureadmin/table": "^3.1.2",
"@pureadmin/utils": "^2.4.7",
"@vueuse/core": "^10.10.0",
"@vueuse/motion": "^2.2.3",
"animate.css": "^4.1.1",
"axios": "^1.12.2",
"dayjs": "^1.11.18",
"echarts": "^6.0.0",
"element-plus": "^2.11.5",
"axios": "^1.7.2",
"dayjs": "^1.11.11",
"echarts": "^5.5.0",
"element-plus": "^2.7.4",
"js-cookie": "^3.0.5",
"localforage": "^1.10.0",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"pinia": "^3.0.3",
"pinyin-pro": "^3.27.0",
"qs": "^6.14.0",
"path": "^0.12.7",
"pinia": "^2.1.7",
"pinyin-pro": "^3.21.1",
"qs": "^6.12.1",
"responsive-storage": "^2.2.0",
"sortablejs": "^1.15.6",
"vue": "^3.5.22",
"vue-i18n": "^11.1.12",
"vue-router": "^4.6.3",
"vue-tippy": "^6.7.1",
"vue-types": "^6.0.0"
"sortablejs": "^1.15.2",
"vue": "^3.4.27",
"vue-router": "^4.3.2",
"vue-tippy": "^6.4.1",
"vue-types": "^5.1.2"
},
"devDependencies": {
"@commitlint/cli": "^20.1.0",
"@commitlint/config-conventional": "^20.0.0",
"@commitlint/types": "^20.0.0",
"@eslint/js": "^9.38.0",
"@faker-js/faker": "^10.1.0",
"@iconify/json": "^2.2.400",
"@iconify/vue": "4.2.0",
"@intlify/unplugin-vue-i18n": "^11.0.1",
"@tailwindcss/vite": "^4.1.16",
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@commitlint/types": "^19.0.3",
"@eslint/js": "^9.4.0",
"@faker-js/faker": "^8.4.1",
"@iconify-icons/ep": "^1.2.12",
"@iconify-icons/ri": "^1.2.10",
"@iconify/vue": "^4.1.2",
"@pureadmin/theme": "^3.2.0",
"@types/gradient-string": "^1.1.6",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20.19.23",
"@types/node": "^20.14.2",
"@types/nprogress": "^0.2.3",
"@types/path-browserify": "^1.0.3",
"@types/qs": "^6.14.0",
"@types/sortablejs": "^1.15.9",
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^5.1.1",
"boxen": "^8.0.1",
"code-inspector-plugin": "^1.2.10",
"cssnano": "^7.1.1",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-vue": "^10.5.1",
"gradient-string": "^3.0.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.6",
"postcss": "^8.5.6",
"postcss-html": "^1.8.0",
"postcss-load-config": "^6.0.1",
"@types/qs": "^6.9.15",
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^7.12.0",
"@typescript-eslint/parser": "^7.12.0",
"@vitejs/plugin-vue": "^5.0.5",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"autoprefixer": "^10.4.19",
"boxen": "^7.1.1",
"cssnano": "^7.0.2",
"eslint": "^9.4.0",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.26.0",
"gradient-string": "^2.0.2",
"husky": "^9.0.11",
"lint-staged": "^15.2.5",
"postcss": "^8.4.38",
"postcss-html": "^1.7.0",
"postcss-import": "^16.1.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.6.2",
"rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^6.0.5",
"sass": "^1.93.2",
"stylelint": "^16.25.0",
"stylelint-config-recess-order": "^7.4.0",
"stylelint-config-recommended-vue": "^1.6.1",
"stylelint-config-standard-scss": "^14.0.0",
"stylelint-prettier": "^5.0.3",
"svgo": "^4.0.0",
"tailwindcss": "^4.1.16",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.2",
"unplugin-icons": "^22.5.0",
"vite": "^7.1.12",
"prettier": "^3.3.1",
"rimraf": "^5.0.7",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.77.4",
"stylelint": "^16.6.1",
"stylelint-config-recess-order": "^5.0.1",
"stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard-scss": "^13.1.0",
"stylelint-prettier": "^5.0.0",
"svgo": "^3.3.2",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5",
"vite": "^5.2.12",
"vite-plugin-cdn-import": "^1.0.1",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-fake-server": "^2.2.0",
"vite-plugin-fake-server": "^2.1.1",
"vite-plugin-remove-console": "^2.2.0",
"vite-plugin-router-warn": "^1.0.0",
"vite-plugin-vue-inspector": "^5.1.2",
"vite-svg-loader": "^5.1.0",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.1.2"
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^1.8.27"
},
"engines": {
"node": "^20.19.0 || >=22.13.0",
"node": "^18.18.0 || ^20.9.0 || >=21.1.0",
"pnpm": ">=9"
},
"pnpm": {
"allowedDeprecatedVersions": {
"are-we-there-yet": "*",
"sourcemap-codec": "*",
"lodash.isequal": "*",
"domexception": "*",
"w3c-hr-time": "*",
"inflight": "*",
@@ -149,16 +149,10 @@
"abab": "*",
"glob": "*"
},
"onlyBuiltDependencies": [
"@parcel/watcher",
"core-js",
"es5-ext",
"esbuild",
"typeit",
"vue-demi"
],
"ignoredBuiltDependencies": [
"@tailwindcss/oxide"
]
"peerDependencyRules": {
"allowedVersions": {
"eslint": "9"
}
}
}
}

7634
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,10 @@
/** @type {import('postcss-load-config').Config} */
export default {
plugins: {
"postcss-import": {},
"tailwindcss/nesting": {},
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {})
}
};

View File

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

View File

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

View File

@@ -11,8 +11,6 @@ export type UserResult = {
nickname: string;
/** 当前登录用户的角色 */
roles: Array<string>;
/** 按钮级别权限 */
permissions: Array<string>;
/** `token` */
accessToken: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" class="iconify iconify--ant-design" viewBox="0 0 1024 1024"><path fill="currentColor" d="M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="iconify iconify--ant-design" viewBox="0 0 1024 1024"><path fill="currentColor" d="M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8"/></svg>

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 351 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3.5 4H1V3h2V1h1v2.5zM13 3V1h-1v2.5l.5.5H15V3zm-1 9.5V15h1v-2h2v-1h-2.5zM1 12v1h2v2h1v-2.5l-.5-.5zm11-1.5-.5.5h-7l-.5-.5v-5l.5-.5h7l.5.5zM10 7H6v2h4z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3.5 4H1V3h2V1h1v2.5zM13 3V1h-1v2.5l.5.5H15V3zm-1 9.5V15h1v-2h2v-1h-2.5zM1 12v1h2v2h1v-2.5l-.5-.5zm11-1.5-.5.5h-7l-.5-.5v-5l.5-.5h7l.5.5zM10 7H6v2h4z"/></svg>

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 327 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3 12h10V4H3zm2-6h6v4H5zM2 6H1V2.5l.5-.5H5v1H2zm13-3.5V6h-1V3h-3V2h3.5zM14 10h1v3.5l-.5.5H11v-1h3zM2 13h3v1H1.5l-.5-.5V10h1z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3 12h10V4H3zm2-6h6v4H5zM2 6H1V2.5l.5-.5H5v1H2zm13-3.5V6h-1V3h-3V2h3.5zM14 10h1v3.5l-.5.5H11v-1h3zM2 13h3v1H1.5l-.5-.5V10h1z"/></svg>

Before

Width:  |  Height:  |  Size: 283 B

After

Width:  |  Height:  |  Size: 302 B

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 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" class="iconify iconify--mdi" viewBox="0 0 24 24"><path fill="currentColor" d="M1 7h6v2H3v2h4v2H3v2h4v2H1zm10 0h4v2h-4v2h2a2 2 0 0 1 2 2v2c0 1.11-.89 2-2 2H9v-2h4v-2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2m8 0h2a2 2 0 0 1 2 2v1h-2V9h-2v6h2v-1h2v1c0 1.11-.89 2-2 2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="iconify iconify--mdi" viewBox="0 0 24 24"><path fill="currentColor" d="M1 7h6v2H3v2h4v2H3v2h4v2H1zm10 0h4v2h-4v2h2a2 2 0 0 1 2 2v2c0 1.11-.89 2-2 2H9v-2h4v-2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2m8 0h2a2 2 0 0 1 2 2v1h-2V9h-2v6h2v-1h2v1c0 1.11-.89 2-2 2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2"/></svg>

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 379 B

View File

@@ -1 +1 @@
<svg width="32" height="32" fill="currentColor" data-icon="holder" viewBox="64 64 896 896"><path d="M300 276.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97m0 284a56 56 0 1 0 56-97 56 56 0 0 0-56 97M640 228a56 56 0 1 0 112 0 56 56 0 0 0-112 0m0 284a56 56 0 1 0 112 0 56 56 0 0 0-112 0M300 844.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97M640 796a56 56 0 1 0 112 0 56 56 0 0 0-112 0"/></svg>
<svg width="32" height="32" fill="currentColor" aria-hidden="true" data-icon="holder" viewBox="64 64 896 896"><path d="M300 276.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97m0 284a56 56 0 1 0 56-97 56 56 0 0 0-56 97M640 228a56 56 0 1 0 112 0 56 56 0 0 0-112 0m0 284a56 56 0 1 0 112 0 56 56 0 0 0-112 0M300 844.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97M640 796a56 56 0 1 0 112 0 56 56 0 0 0-112 0"/></svg>

Before

Width:  |  Height:  |  Size: 373 B

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -8,14 +8,13 @@ import {
} from "./index";
import { ref, computed } from "vue";
import { isFunction } from "@pureadmin/utils";
import Fullscreen from "~icons/ri/fullscreen-fill";
import ExitFullscreen from "~icons/ri/fullscreen-exit-fill";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
defineOptions({
name: "ReDialog"
});
const sureBtnMap = ref({});
const fullscreen = ref(false);
const footerButtons = computed(() => {
@@ -44,26 +43,10 @@ const footerButtons = computed(() => {
bg: true,
popconfirm: options?.popconfirm,
btnClick: ({ dialog: { options, index } }) => {
if (options?.sureBtnLoading) {
sureBtnMap.value[index] = Object.assign(
{},
sureBtnMap.value[index],
{
loading: true
}
);
}
const closeLoading = () => {
if (options?.sureBtnLoading) {
sureBtnMap.value[index].loading = false;
}
};
const done = () => {
closeLoading();
const done = () =>
closeDialog(options, index, { command: "sure" });
};
if (options?.beforeSure && isFunction(options?.beforeSure)) {
options.beforeSure(done, { options, index, closeLoading });
options.beforeSure(done, { options, index });
} else {
done();
}
@@ -79,7 +62,7 @@ const fullscreenClass = computed(() => {
"el-dialog__close",
"-translate-x-2",
"cursor-pointer",
"hover:text-[red]!"
"hover:!text-[red]"
];
});
@@ -189,7 +172,6 @@ function handleClose(
<el-button
v-else
v-bind="btn"
:loading="key === 1 && sureBtnMap[index]?.loading"
@click="
btn.btnClick({
dialog: { options, index },

View File

@@ -69,11 +69,11 @@ type DialogProps = {
type Popconfirm = {
/** 标题 */
title?: string;
/** 确按钮文字 */
/** 确按钮文字 */
confirmButtonText?: string;
/** 取消按钮文字 */
cancelButtonText?: string;
/** 确按钮类型,默认 `primary` */
/** 确按钮类型,默认 `primary` */
confirmButtonType?: ButtonType;
/** 取消按钮类型,默认 `text` */
cancelButtonType?: ButtonType;
@@ -121,7 +121,7 @@ type ButtonProps = {
round?: boolean;
/** 是否为圆形按钮,默认 `false` */
circle?: boolean;
/** 确按钮的 `Popconfirm` 气泡确认框相关配置 */
/** 确按钮的 `Popconfirm` 气泡确认框相关配置 */
popconfirm?: Popconfirm;
/** 是否为加载中状态,默认 `false` */
loading?: boolean;
@@ -160,10 +160,8 @@ interface DialogOptions extends DialogProps {
props?: any;
/** 是否隐藏 `Dialog` 按钮操作区的内容 */
hideFooter?: boolean;
/** 确按钮的 `Popconfirm` 气泡确认框相关配置 */
/** 确按钮的 `Popconfirm` 气泡确认框相关配置 */
popconfirm?: Popconfirm;
/** 点击确定按钮后是否开启 `loading` 加载动画 */
sureBtnLoading?: boolean;
/**
* @description 自定义对话框标题的内容渲染器
* @see {@link https://element-plus.org/zh-CN/component/dialog.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A4%B4%E9%83%A8}
@@ -261,13 +259,10 @@ interface DialogOptions extends DialogProps {
done: Function,
{
options,
index,
closeLoading
index
}: {
options: DialogOptions;
index: number;
/** 关闭确定按钮的 `loading` 加载动画 */
closeLoading: Function;
}
) => void;
}

View File

@@ -1,10 +1,10 @@
import type { iconType } from "./types";
import { h, defineComponent, type Component } from "vue";
import { FontIcon, IconifyIconOnline, IconifyIconOffline } from "../index";
import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
/**
* 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标
* @see 点击查看文档图标篇 {@link https://pure-admin.cn/pages/icon/}
* @see 点击查看文档图标篇 {@link https://pure-admin.github.io/pure-admin-doc/pages/icon/}
* @param icon 必传 图标
* @param attrs 可选 iconType 属性
* @returns Component
@@ -49,12 +49,10 @@ export function useRenderIcon(icon: any, attrs?: iconType): Component {
return defineComponent({
name: "Icon",
render() {
if (!icon) return;
const IconifyIcon = icon.includes(":")
? IconifyIconOnline
: IconifyIconOffline;
const IconifyIcon =
icon && icon.includes(":") ? IconifyIconOnline : IconifyIconOffline;
return h(IconifyIcon, {
icon,
icon: icon,
...attrs
});
}

View File

@@ -27,7 +27,8 @@ export default defineComponent({
return h(
"svg",
{
class: "icon-svg"
class: "icon-svg",
"aria-hidden": true
},
{
default: () => [

View File

@@ -13,12 +13,10 @@ export default defineComponent({
render() {
if (typeof this.icon === "object") addIcon(this.icon, this.icon);
const attrs = this.$attrs;
if (typeof this.icon === "string") {
return h(
IconifyIcon,
{
icon: this.icon,
"aria-hidden": false,
style: attrs?.style
? Object.assign(attrs.style, { outline: "none" })
: { outline: "none" },
@@ -28,20 +26,5 @@ export default defineComponent({
default: () => []
}
);
} else {
return h(
this.icon,
{
"aria-hidden": false,
style: attrs?.style
? Object.assign(attrs.style, { outline: "none" })
: { outline: "none" },
...attrs
},
{
default: () => []
}
);
}
}
});

View File

@@ -17,7 +17,6 @@ export default defineComponent({
IconifyIcon,
{
icon: `${this.icon}`,
"aria-hidden": false,
style: attrs?.style
? Object.assign(attrs.style, { outline: "none" })
: { outline: "none" },

View File

@@ -1,23 +1,14 @@
// 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载
import { getSvgInfo } from "@pureadmin/utils";
import { addIcon } from "@iconify/vue/dist/offline";
// https://icon-sets.iconify.design/ep/?keyword=ep
import EpHomeFilled from "~icons/ep/home-filled?raw";
// https://icon-sets.iconify.design/ri/?keyword=ri
import RiSearchLine from "~icons/ri/search-line?raw";
import RiInformationLine from "~icons/ri/information-line?raw";
const icons = [
// Element Plus Icon: https://github.com/element-plus/element-plus-icons
["ep/home-filled", EpHomeFilled],
// Remix Icon: https://github.com/Remix-Design/RemixIcon
["ri/search-line", RiSearchLine],
["ri/information-line", RiInformationLine]
];
// 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标
icons.forEach(([name, icon]) => {
addIcon(name as string, getSvgInfo(icon as string));
});
// @iconify-icons/ep
import Lollipop from "@iconify-icons/ep/lollipop";
import HomeFilled from "@iconify-icons/ep/home-filled";
addIcon("ep:lollipop", Lollipop);
addIcon("ep:home-filled", HomeFilled);
// @iconify-icons/ri
import Search from "@iconify-icons/ri/search-line";
import InformationLine from "@iconify-icons/ri/information-line";
addIcon("ri:search-line", Search);
addIcon("ri:information-line", InformationLine);

View File

@@ -1,5 +0,0 @@
import perms from "./src/perms";
const Perms = perms;
export { Perms };

View File

@@ -1,20 +0,0 @@
import { defineComponent, Fragment } from "vue";
import { hasPerms } from "@/utils/auth";
export default defineComponent({
name: "Perms",
props: {
value: {
type: undefined,
default: []
}
},
setup(props, { slots }) {
return () => {
if (!slots) return null;
return hasPerms(props.value) ? (
<Fragment>{slots.default?.()}</Fragment>
) : null;
};
}
});

View File

@@ -1,5 +1,4 @@
import Sortable from "sortablejs";
import { transformI18n } from "@/plugins/i18n";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import {
type PropType,
@@ -18,8 +17,6 @@ import {
getKeyList
} from "@pureadmin/utils";
import Fullscreen from "~icons/ri/fullscreen-fill";
import ExitFullscreen from "~icons/ri/fullscreen-exit-fill";
import DragIcon from "@/assets/table-bar/drag.svg?component";
import ExpandIcon from "@/assets/table-bar/expand.svg?component";
import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
@@ -54,12 +51,11 @@ const props = {
export default defineComponent({
name: "PureTableBar",
props,
emits: ["refresh", "fullscreen"],
emits: ["refresh"],
setup(props, { emit, slots, attrs }) {
const size = ref("default");
const loading = ref(false);
const checkAll = ref(true);
const isFullscreen = ref(false);
const isIndeterminate = ref(false);
const instance = getCurrentInstance()!;
const isExpandAll = ref(props.isExpandAll);
@@ -87,9 +83,9 @@ export default defineComponent({
"text-black",
"dark:text-white",
"duration-100",
"hover:text-primary!",
"hover:!text-primary",
"cursor-pointer",
"outline-hidden"
"outline-none"
];
});
@@ -117,11 +113,6 @@ export default defineComponent({
toggleRowExpansionAll(props.tableRef.data, isExpandAll.value);
}
function onFullscreen() {
isFullscreen.value = !isFullscreen.value;
emit("fullscreen", isFullscreen.value);
}
function toggleRowExpansionAll(data, isExpansion) {
data.forEach(item => {
props.tableRef.toggleRowExpansion(item, isExpansion);
@@ -148,9 +139,7 @@ export default defineComponent({
}
function handleCheckColumnListChange(val: boolean, label: string) {
dynamicColumns.value.filter(
item => transformI18n(item.label) === transformI18n(label)
)[0].hide = !val;
dynamicColumns.value.filter(item => item.label === label)[0].hide = !val;
}
async function onReset() {
@@ -223,9 +212,7 @@ export default defineComponent({
};
const isFixedColumn = (label: string) => {
return dynamicColumns.value.filter(
item => transformI18n(item.label) === transformI18n(label)
)[0].fixed
return dynamicColumns.value.filter(item => item.label === label)[0].fixed
? true
: false;
};
@@ -252,18 +239,7 @@ export default defineComponent({
return () => (
<>
<div
{...attrs}
class={[
"w-full",
"px-2",
"pb-2",
"bg-bg_color",
isFullscreen.value
? ["h-full!", "z-2002", "fixed", "inset-0"]
: "mt-2"
]}
>
<div {...attrs} class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
<div class="flex justify-between w-full h-[60px] p-4">
{slots?.title ? (
slots.title()
@@ -317,7 +293,7 @@ export default defineComponent({
>
<div class={[topClass.value]}>
<el-checkbox
class="-mr-1!"
class="!-mr-1"
label="列展示"
v-model={checkAll.value}
indeterminate={isIndeterminate.value}
@@ -347,8 +323,8 @@ export default defineComponent({
class={[
"drag-btn w-[16px] mr-2",
isFixedColumn(item)
? "cursor-no-drop!"
: "cursor-grab!"
? "!cursor-no-drop"
: "!cursor-grab"
]}
onMouseenter={(event: {
preventDefault: () => void;
@@ -363,10 +339,10 @@ export default defineComponent({
}
>
<span
title={transformI18n(item)}
title={item}
class="inline-block w-[120px] truncate hover:text-text_color_primary"
>
{transformI18n(item)}
{item}
</span>
</el-checkbox>
</div>
@@ -377,14 +353,6 @@ export default defineComponent({
</el-scrollbar>
</div>
</el-popover>
<el-divider direction="vertical" />
<iconifyIconOffline
class={["w-[16px]", iconClass.value]}
icon={isFullscreen.value ? ExitFullscreen : Fullscreen}
v-tippy={isFullscreen.value ? "退出全屏" : "全屏"}
onClick={() => onFullscreen()}
/>
</div>
</div>
{slots.default({

View File

@@ -98,6 +98,7 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
transition: 0.1s;
}
.pure-segmented-group {

View File

@@ -6,7 +6,7 @@ export interface OptionsType {
label?: string | (() => VNode | Component);
/**
* @description 图标,采用平台内置的 `useRenderIcon` 函数渲染
* @see {@link 用法参考 https://pure-admin.cn/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
* @see {@link 用法参考 https://pure-admin.github.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
*/
icon?: string | Component;
/** 图标属性、样式配置 */

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { h, onMounted, ref } from "vue";
import { type TippyOptions, type TippyContent, useTippy } from "vue-tippy";
<script lang="ts" setup>
import { h, onMounted, ref, useSlots } from "vue";
import { type TippyOptions, useTippy } from "vue-tippy";
defineOptions({
name: "ReText"
@@ -17,10 +17,7 @@ const props = defineProps({
}
});
const slots = defineSlots<{
content: () => TippyContent;
default: () => any;
}>();
const $slots = useSlots();
const textRef = ref();
const tippyFunc = ref();
@@ -36,7 +33,7 @@ const isTextEllipsis = (el: HTMLElement) => {
};
const getTippyProps = () => ({
content: h(slots.content || slots.default),
content: h($slots.content || $slots.default),
...props.tippyProps
});

View File

@@ -2,5 +2,4 @@ export * from "./auth";
export * from "./copy";
export * from "./longpress";
export * from "./optimize";
export * from "./perms";
export * from "./ripple";

View File

@@ -1,15 +0,0 @@
import { hasPerms } from "@/utils/auth";
import type { Directive, DirectiveBinding } from "vue";
export const perms: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding<string | Array<string>>) {
const { value } = binding;
if (value) {
!hasPerms(value) && el.parentNode?.removeChild(el);
} else {
throw new Error(
"[Directive: perms]: need perms! Like v-perms=\"['btn.add','btn.edit']\""
);
}
}
};

View File

@@ -13,10 +13,10 @@ $ripple-animation-visible-opacity: 0.25 !default;
z-index: 0;
width: 100%;
height: 100%;
contain: strict;
overflow: hidden;
pointer-events: none;
border-radius: inherit;
contain: strict;
}
&__animation {

View File

@@ -32,8 +32,8 @@ const calculate = (
const offset = el.getBoundingClientRect();
// 获取点击位置距离 el 的垂直和水平距离
const localX = e.clientX - offset.left;
const localY = e.clientY - offset.top;
let localX = e.clientX - offset.left;
let localY = e.clientY - offset.top;
let radius = 0;
let scale = 0.3;

View File

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

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { getConfig } from "@/config";
const TITLE = getConfig("Title");
@@ -10,7 +10,7 @@ const TITLE = getConfig("Title");
>
Copyright © 2020-present
<a
class="hover:text-primary!"
class="hover:text-primary"
href="https://github.com/pure-admin"
target="_blank"
>

View File

@@ -3,15 +3,12 @@ import { useNav } from "@/layout/hooks/useNav";
import LaySearch from "../lay-search/index.vue";
import LayNotice from "../lay-notice/index.vue";
import LayNavMix from "../lay-sidebar/NavMix.vue";
import { useTranslationLang } from "@/layout/hooks/useTranslationLang";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
import LaySidebarBreadCrumb from "../lay-sidebar/components/SidebarBreadCrumb.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 Setting from "~icons/ri/settings-3-line";
import Check from "~icons/ep/check";
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
import Setting from "@iconify-icons/ri/settings-3-line";
const {
layout,
@@ -22,16 +19,12 @@ const {
username,
userAvatar,
avatarsStyle,
toggleSideBar,
getDropdownItemStyle,
getDropdownItemClass
toggleSideBar
} = useNav();
const { t, locale, translationCh, translationEn } = useTranslationLang();
</script>
<template>
<div class="navbar bg-[#fff] shadow-xs shadow-[rgba(0,21,41,0.08)]">
<div class="navbar bg-[#fff] shadow-sm shadow-[rgba(0,21,41,0.08)]">
<LaySidebarTopCollapse
v-if="device === 'mobile'"
class="hamburger-container"
@@ -49,38 +42,6 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<div v-if="layout === 'vertical'" class="vertical-header-right">
<!-- 菜单搜索 -->
<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" />
<!-- 消息通知 -->
@@ -98,14 +59,14 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
:icon="LogoutCircleRLine"
style="margin: 5px"
/>
{{ t("buttons.pureLoginOut") }}
退出系统
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span
class="set-icon navbar-bg-hover"
:title="t('buttons.pureOpenSystemSet')"
title="打开系统配置"
@click="onPanel"
>
<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 {
width: 120px;

View File

@@ -49,7 +49,7 @@ function hoverDescription(event, description) {
<template>
<div
class="notice-container border-0 border-b-[1px] border-solid border-[#f0f0f0] dark:border-[#303030]"
class="notice-container border-b-[1px] border-solid border-[#f0f0f0] dark:border-[#303030]"
>
<el-avatar
v-if="noticeItem.avatar"
@@ -112,7 +112,7 @@ function hoverDescription(event, description) {
max-width: 238px;
}
</style>
<style lang="scss" scoped>
<style scoped lang="scss">
.notice-container {
display: flex;
align-items: flex-start;

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,9 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { emitter } from "@/utils/mitt";
import { onClickOutside } from "@vueuse/core";
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import CloseIcon from "~icons/ep/close";
import CloseIcon from "@iconify-icons/ep/close";
const target = ref(null);
const show = ref<Boolean>(false);
@@ -16,7 +15,7 @@ const iconClass = computed(() => {
"flex",
"justify-center",
"items-center",
"outline-hidden",
"outline-none",
"rounded-[4px]",
"cursor-pointer",
"transition-colors",
@@ -26,7 +25,6 @@ const iconClass = computed(() => {
];
});
const { t } = useI18n();
const { onReset } = useDataThemeChange();
onClickOutside(target, (event: any) => {
@@ -51,14 +49,12 @@ onBeforeUnmount(() => {
<div class="right-panel-background" />
<div ref="target" class="right-panel bg-bg_color">
<div
class="project-configuration border-0 border-b-[1px] border-solid border-[var(--pure-border-color)]"
class="project-configuration border-b-[1px] border-solid border-[var(--pure-border-color)]"
>
<h4 class="dark:text-white">
{{ t("panel.pureSystemSet") }}
</h4>
<h4 class="dark:text-white">系统配置</h4>
<span
v-tippy="{
content: t('panel.pureCloseSystemSet'),
content: '关闭配置',
placement: 'bottom-start',
zIndex: 41000
}"
@@ -78,11 +74,11 @@ onBeforeUnmount(() => {
</el-scrollbar>
<div
class="flex justify-end p-3 border-0 border-t-[1px] border-solid border-[var(--pure-border-color)]"
class="flex justify-end p-3 border-t-[1px] border-solid border-[var(--pure-border-color)]"
>
<el-button
v-tippy="{
content: t('panel.pureClearCacheAndToLogin'),
content: '清空缓存并返回登录页',
placement: 'left-start',
zIndex: 41000
}"
@@ -91,7 +87,7 @@ onBeforeUnmount(() => {
bg
@click="onReset"
>
{{ t("panel.pureClearCache") }}
清空缓存
</el-button>
</div>
</div>
@@ -121,8 +117,8 @@ onBeforeUnmount(() => {
width: 100%;
max-width: 280px;
box-shadow: 0 0 15px 0 rgb(0 0 0 / 5%);
transform: translate(100%);
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
transform: translate(100%);
}
.show {

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,17 @@
<script setup lang="ts">
import { match } from "pinyin-pro";
import { useI18n } from "vue-i18n";
import { getConfig } from "@/config";
import { useRouter } from "vue-router";
import SearchResult from "./SearchResult.vue";
import SearchFooter from "./SearchFooter.vue";
import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import SearchHistory from "./SearchHistory.vue";
import type { optionsItem, dragItem } from "../types";
import { ref, computed, shallowRef, watch } from "vue";
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
import { usePermissionStoreHook } from "@/store/modules/permission";
import { cloneDeep, isAllEmpty, storageLocal } from "@pureadmin/utils";
import SearchIcon from "~icons/ri/search-line";
import SearchIcon from "@iconify-icons/ri/search-line";
interface Props {
/** 弹窗显隐 */
@@ -29,7 +27,6 @@ const emit = defineEmits<Emits>();
const props = withDefaults(defineProps<Props>(), {});
const router = useRouter();
const { t, locale } = useI18n();
const HISTORY_TYPE = "history";
const COLLECT_TYPE = "collect";
@@ -110,16 +107,15 @@ function search() {
const flatMenusData = flatTree(menusData.value);
resultOptions.value = flatMenusData.filter(menu =>
keyword.value
? transformI18n(menu.meta?.title)
? menu.meta?.title
.toLocaleLowerCase()
.includes(keyword.value.toLocaleLowerCase().trim()) ||
(locale.value === "zh" &&
!isAllEmpty(
match(
transformI18n(menu.meta?.title).toLocaleLowerCase(),
menu.meta?.title.toLocaleLowerCase(),
keyword.value.toLocaleLowerCase().trim()
)
))
)
: false
);
activePath.value =
@@ -293,7 +289,7 @@ onKeyStroke("ArrowDown", handleDown);
v-model="keyword"
size="large"
clearable
:placeholder="t('search.purePlaceholder')"
placeholder="搜索菜单(支持拼音搜索)"
@input="handleSearch"
>
<template #prefix>
@@ -305,7 +301,7 @@ onKeyStroke("ArrowDown", handleDown);
</el-input>
<div class="search-content">
<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
v-if="showSearchHistory"
ref="historyRef"

View File

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

View File

@@ -14,7 +14,7 @@ function handleSearch() {
class="search-container w-[40px] h-[48px] flex-c cursor-pointer navbar-bg-hover"
@click="handleSearch"
>
<IconifyIconOffline icon="ri/search-line" />
<IconifyIconOffline icon="ri:search-line" />
</div>
<SearchModal v-model:value="show" />
</div>

View File

@@ -9,24 +9,23 @@ import {
onUnmounted,
onBeforeMount
} from "vue";
import { useI18n } from "vue-i18n";
import { emitter } from "@/utils/mitt";
import LayPanel from "../lay-panel/index.vue";
import { useNav } from "@/layout/hooks/useNav";
import { useAppStoreHook } from "@/store/modules/app";
import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import Segmented, { type OptionsType } from "@/components/ReSegmented";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import { useDark, useGlobal, debounce, isNumber } from "@pureadmin/utils";
import Check from "~icons/ep/check";
import LeftArrow from "~icons/ri/arrow-left-s-line?width=20&height=20";
import RightArrow from "~icons/ri/arrow-right-s-line?width=20&height=20";
import Check from "@iconify-icons/ep/check";
import LeftArrow from "@iconify-icons/ri/arrow-left-s-line";
import RightArrow from "@iconify-icons/ri/arrow-right-s-line";
import DayIcon from "@/assets/svg/day.svg?component";
import DarkIcon from "@/assets/svg/dark.svg?component";
import SystemIcon from "@/assets/svg/system.svg?component";
const { t } = useI18n();
const { device } = useNav();
const { isDark } = useDark();
const { $storage } = useGlobal<GlobalPropertiesApi>();
@@ -49,7 +48,9 @@ const {
if (unref(layoutTheme)) {
const layout = unref(layoutTheme).layout;
const theme = unref(layoutTheme).theme;
document.documentElement.setAttribute("data-theme", theme);
toggleTheme({
scopeName: `layout-theme-${theme}`
});
setLayoutModel(layout);
}
@@ -147,13 +148,13 @@ function setFalse(Doms): any {
const stretchTypeOptions = computed<Array<OptionsType>>(() => {
return [
{
label: t("panel.pureStretchFixed"),
tip: t("panel.pureStretchFixedTip"),
label: "固定",
tip: "紧凑页面,轻松找到所需信息",
value: "fixed"
},
{
label: t("panel.pureStretchCustom"),
tip: t("panel.pureStretchCustomTip"),
label: "自定义",
tip: "最小1280、最大1600",
value: "custom"
}
];
@@ -189,30 +190,30 @@ const getThemeColor = computed(() => {
});
const pClass = computed(() => {
return ["mb-[12px]!", "font-medium", "text-sm", "dark:text-white"];
return ["mb-[12px]", "font-medium", "text-sm", "dark:text-white"];
});
const themeOptions = computed<Array<OptionsType>>(() => {
return [
{
label: t("panel.pureOverallStyleLight"),
label: "浅色",
icon: DayIcon,
theme: "light",
tip: t("panel.pureOverallStyleLightTip"),
tip: "清新启航,点亮舒适的工作界面",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
},
{
label: t("panel.pureOverallStyleDark"),
label: "深色",
icon: DarkIcon,
theme: "dark",
tip: t("panel.pureOverallStyleDarkTip"),
tip: "月光序曲,沉醉于夜的静谧雅致",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
},
{
label: t("panel.pureOverallStyleSystem"),
label: "自动",
icon: SystemIcon,
theme: "system",
tip: t("panel.pureOverallStyleSystemTip"),
tip: "同步时光,界面随晨昏自然呼应",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
}
];
@@ -221,18 +222,18 @@ const themeOptions = computed<Array<OptionsType>>(() => {
const markOptions = computed<Array<OptionsType>>(() => {
return [
{
label: t("panel.pureTagsStyleSmart"),
tip: t("panel.pureTagsStyleSmartTip"),
label: "灵动",
tip: "灵动标签,添趣生辉",
value: "smart"
},
{
label: t("panel.pureTagsStyleCard"),
tip: t("panel.pureTagsStyleCardTip"),
label: "卡片",
tip: "卡片标签,高效浏览",
value: "card"
},
{
label: t("panel.pureTagsStyleChrome"),
tip: t("panel.pureTagsStyleChromeTip"),
label: "谷歌",
tip: "谷歌风格,经典美观",
value: "chrome"
}
];
@@ -317,7 +318,7 @@ onUnmounted(() => removeMatchMedia);
<template>
<LayPanel>
<div class="p-5">
<p :class="pClass">{{ t("panel.pureOverallStyle") }}</p>
<p :class="pClass">整体风格</p>
<Segmented
resize
class="select-none"
@@ -335,7 +336,7 @@ onUnmounted(() => removeMatchMedia);
"
/>
<p :class="['mt-5!', pClass]">{{ t("panel.pureThemeColor") }}</p>
<p :class="['mt-5', pClass]">主题色</p>
<ul class="theme-color">
<li
v-for="(item, index) in themeColors"
@@ -354,12 +355,12 @@ onUnmounted(() => removeMatchMedia);
</li>
</ul>
<p :class="['mt-5!', pClass]">{{ t("panel.pureLayoutModel") }}</p>
<p :class="['mt-5', pClass]">导航模式</p>
<ul class="pure-theme">
<li
ref="verticalRef"
v-tippy="{
content: t('panel.pureVerticalTip'),
content: '左侧菜单,亲切熟悉',
zIndex: 41000
}"
:class="layoutTheme.layout === 'vertical' ? 'is-select' : ''"
@@ -372,7 +373,7 @@ onUnmounted(() => removeMatchMedia);
v-if="device !== 'mobile'"
ref="horizontalRef"
v-tippy="{
content: t('panel.pureHorizontalTip'),
content: '顶部菜单,简洁概览',
zIndex: 41000
}"
:class="layoutTheme.layout === 'horizontal' ? 'is-select' : ''"
@@ -385,7 +386,7 @@ onUnmounted(() => removeMatchMedia);
v-if="device !== 'mobile'"
ref="mixRef"
v-tippy="{
content: t('panel.pureMixTip'),
content: '混合菜单,灵活多变',
zIndex: 41000
}"
:class="layoutTheme.layout === 'mix' ? 'is-select' : ''"
@@ -397,7 +398,7 @@ onUnmounted(() => removeMatchMedia);
</ul>
<span v-if="useAppStoreHook().getViewportWidth > 1280">
<p :class="['mt-5!', pClass]">{{ t("panel.pureStretch") }}</p>
<p :class="['mt-5', pClass]">页宽</p>
<Segmented
resize
class="mb-2 select-none"
@@ -426,19 +427,21 @@ onUnmounted(() => removeMatchMedia);
>
<IconifyIconOffline
:icon="settings.stretch ? RightArrow : LeftArrow"
height="20"
/>
<div
class="grow border-0 border-b border-dashed"
class="flex-grow border-b border-dashed"
style="border-color: var(--el-color-primary)"
/>
<IconifyIconOffline
:icon="settings.stretch ? LeftArrow : RightArrow"
height="20"
/>
</div>
</button>
</span>
<p :class="['mt-4!', pClass]">{{ t("panel.pureTagsStyle") }}</p>
<p :class="['mt-4', pClass]">页签风格</p>
<Segmented
resize
class="select-none"
@@ -447,47 +450,45 @@ onUnmounted(() => removeMatchMedia);
@change="onChange"
/>
<p class="mt-5! font-medium text-sm dark:text-white">
{{ t("panel.pureInterfaceDisplay") }}
</p>
<p class="mt-5 font-medium text-sm dark:text-white">界面显示</p>
<ul class="setting">
<li>
<span class="dark:text-white">{{ t("panel.pureGreyModel") }}</span>
<span class="dark:text-white">灰色模式</span>
<el-switch
v-model="settings.greyVal"
inline-prompt
:active-text="t('buttons.pureOpenText')"
:inactive-text="t('buttons.pureCloseText')"
active-text=""
inactive-text=""
@change="greyChange"
/>
</li>
<li>
<span class="dark:text-white">{{ t("panel.pureWeakModel") }}</span>
<span class="dark:text-white">色弱模式</span>
<el-switch
v-model="settings.weakVal"
inline-prompt
:active-text="t('buttons.pureOpenText')"
:inactive-text="t('buttons.pureCloseText')"
active-text=""
inactive-text=""
@change="weekChange"
/>
</li>
<li>
<span class="dark:text-white">{{ t("panel.pureHiddenTags") }}</span>
<span class="dark:text-white">隐藏标签页</span>
<el-switch
v-model="settings.tabsVal"
inline-prompt
:active-text="t('buttons.pureOpenText')"
:inactive-text="t('buttons.pureCloseText')"
active-text=""
inactive-text=""
@change="tagsChange"
/>
</li>
<li>
<span class="dark:text-white">{{ t("panel.pureHiddenFooter") }}</span>
<span class="dark:text-white">隐藏页脚</span>
<el-switch
v-model="settings.hideFooter"
inline-prompt
:active-text="t('buttons.pureOpenText')"
:inactive-text="t('buttons.pureCloseText')"
active-text=""
inactive-text=""
@change="hideFooterChange"
/>
</li>
@@ -498,20 +499,18 @@ onUnmounted(() => removeMatchMedia);
inline-prompt
:active-value="true"
:inactive-value="false"
:active-text="t('buttons.pureOpenText')"
:inactive-text="t('buttons.pureCloseText')"
active-text=""
inactive-text=""
@change="logoChange"
/>
</li>
<li>
<span class="dark:text-white">
{{ t("panel.pureMultiTagsCache") }}
</span>
<span class="dark:text-white">页签持久化</span>
<el-switch
v-model="settings.multiTagsCache"
inline-prompt
:active-text="t('buttons.pureOpenText')"
:inactive-text="t('buttons.pureCloseText')"
active-text=""
inactive-text=""
@change="multiTagsCacheChange"
/>
</li>

View File

@@ -1,31 +1,20 @@
<script setup lang="ts">
import { emitter } from "@/utils/mitt";
import { isAllEmpty } from "@pureadmin/utils";
import { ref, nextTick, computed } from "vue";
import { useNav } from "@/layout/hooks/useNav";
import LaySearch from "../lay-search/index.vue";
import LayNotice from "../lay-notice/index.vue";
import { responsiveStorageNameSpace } from "@/config";
import { ref, nextTick, computed, onMounted } from "vue";
import { storageLocal, isAllEmpty } from "@pureadmin/utils";
import { useTranslationLang } from "../../hooks/useTranslationLang";
import { usePermissionStoreHook } from "@/store/modules/permission";
import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
import GlobalizationIcon from "@/assets/svg/globalization.svg?component";
import LogoutCircleRLine from "~icons/ri/logout-circle-r-line";
import Setting from "~icons/ri/settings-3-line";
import Check from "~icons/ep/check";
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
import Setting from "@iconify-icons/ri/settings-3-line";
const menuRef = ref();
const showLogo = ref(
storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}configure`
)?.showLogo ?? true
);
const { t, route, locale, translationCh, translationEn } =
useTranslationLang(menuRef);
const {
route,
title,
logout,
onPanel,
@@ -33,9 +22,7 @@ const {
username,
userAvatar,
backTopMenu,
avatarsStyle,
getDropdownItemStyle,
getDropdownItemClass
avatarsStyle
} = useNav();
const defaultActive = computed(() =>
@@ -45,12 +32,6 @@ const defaultActive = computed(() =>
nextTick(() => {
menuRef.value?.handleResize();
});
onMounted(() => {
emitter.on("logoChange", key => {
showLogo.value = key;
});
});
</script>
<template>
@@ -58,7 +39,7 @@ onMounted(() => {
v-loading="usePermissionStoreHook().wholeMenus.length === 0"
class="horizontal-header"
>
<div v-if="showLogo" class="horizontal-header-left" @click="backTopMenu">
<div class="horizontal-header-left" @click="backTopMenu">
<img :src="getLogo()" alt="logo" />
<span>{{ title }}</span>
</div>
@@ -79,36 +60,6 @@ onMounted(() => {
<div class="horizontal-header-right">
<!-- 菜单搜索 -->
<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" />
<!-- 消息通知 -->
@@ -126,14 +77,14 @@ onMounted(() => {
:icon="LogoutCircleRLine"
style="margin: 5px"
/>
{{ t("buttons.pureLoginOut") }}
退出系统
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span
class="set-icon navbar-bg-hover"
:title="t('buttons.pureOpenSystemSet')"
title="打开系统配置"
@click="onPanel"
>
<IconifyIconOffline :icon="Setting" />
@@ -147,22 +98,6 @@ onMounted(() => {
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 {
width: 120px;

View File

@@ -1,28 +1,23 @@
<script setup lang="ts">
import { isAllEmpty } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import LaySearch from "../lay-search/index.vue";
import LayNotice from "../lay-notice/index.vue";
import { ref, toRaw, watch, onMounted, nextTick } from "vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { getParentPaths, findRouteByPath } from "@/router/utils";
import { useTranslationLang } from "../../hooks/useTranslationLang";
import { usePermissionStoreHook } from "@/store/modules/permission";
import LaySidebarExtraIcon from "../lay-sidebar/components/SidebarExtraIcon.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 Setting from "~icons/ri/settings-3-line";
import Check from "~icons/ep/check";
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
import Setting from "@iconify-icons/ri/settings-3-line";
const menuRef = ref();
const defaultActive = ref(null);
const { t, route, locale, translationCh, translationEn } =
useTranslationLang(menuRef);
const {
route,
device,
logout,
onPanel,
@@ -30,9 +25,7 @@ const {
username,
userAvatar,
getDivStyle,
avatarsStyle,
getDropdownItemStyle,
getDropdownItemClass
avatarsStyle
} = useNav();
function getDefaultActive(routePath) {
@@ -90,7 +83,7 @@ watch(
</div>
<div :style="getDivStyle">
<span class="select-none">
{{ transformI18n(route.meta.title) }}
{{ route.meta.title }}
</span>
<LaySidebarExtraIcon :extraIcon="route.meta.extraIcon" />
</div>
@@ -100,36 +93,6 @@ watch(
<div class="horizontal-header-right">
<!-- 菜单搜索 -->
<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" />
<!-- 消息通知 -->
@@ -147,14 +110,14 @@ watch(
:icon="LogoutCircleRLine"
style="margin: 5px"
/>
{{ t("buttons.pureLoginOut") }}
退出系统
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span
class="set-icon navbar-bg-hover"
:title="t('buttons.pureOpenSystemSet')"
title="打开系统配置"
@click="onPanel"
>
<IconifyIconOffline :icon="Setting" />
@@ -168,22 +131,6 @@ watch(
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 {
width: 120px;

View File

@@ -1,6 +1,5 @@
<script setup lang="ts">
import { isEqual } from "@pureadmin/utils";
import { transformI18n } from "@/plugins/i18n";
import { useRoute, useRouter } from "vue-router";
import { ref, watch, onMounted, toRaw } from "vue";
import { getParentPaths, findRouteByPath } from "@/router/utils";
@@ -105,15 +104,15 @@ watch(
</script>
<template>
<el-breadcrumb class="leading-[50px]! select-none" separator="/">
<el-breadcrumb class="!leading-[50px] select-none" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item
v-for="item in levelList"
:key="item.path"
class="inline! items-stretch!"
class="!inline !items-stretch"
>
<a @click.prevent="handleLink(item)">
{{ transformI18n(item.meta.title) }}
{{ item.meta.title }}
</a>
</el-breadcrumb-item>
</transition-group>

View File

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

View File

@@ -1,10 +1,9 @@
<script setup lang="ts">
import path from "path";
import { getConfig } from "@/config";
import { posix } from "path-browserify";
import { menuType } from "@/layout/types";
import { ReText } from "@/components/ReText";
import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import SidebarLinkItem from "./SidebarLinkItem.vue";
import SidebarExtraIcon from "./SidebarExtraIcon.vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
@@ -17,10 +16,10 @@ import {
useAttrs
} from "vue";
import ArrowUp from "~icons/ep/arrow-up-bold";
import EpArrowDown from "~icons/ep/arrow-down-bold";
import ArrowLeft from "~icons/ep/arrow-left-bold";
import ArrowRight from "~icons/ep/arrow-right-bold";
import ArrowUp from "@iconify-icons/ep/arrow-up-bold";
import EpArrowDown from "@iconify-icons/ep/arrow-down-bold";
import ArrowLeft from "@iconify-icons/ep/arrow-left-bold";
import ArrowRight from "@iconify-icons/ep/arrow-right-bold";
const attrs = useAttrs();
const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav();
@@ -61,21 +60,6 @@ const getSubMenuIconStyle = computed((): CSSProperties => {
};
});
const textClass = computed(() => {
const item = props.item;
const baseClass = "w-full! text-inherit!";
if (
layout.value !== "horizontal" &&
isCollapse.value &&
!toRaw(item.meta.icon) &&
((layout.value === "vertical" && item.parentId === null) ||
(layout.value === "mix" && item.pathList.length === 2))
) {
return `${baseClass} min-w-[54px]! text-center! px-3!`;
}
return baseClass;
});
const expandCloseIcon = computed(() => {
if (!getConfig()?.MenuArrowIconNoTransition) return "";
return {
@@ -114,7 +98,8 @@ function resolvePath(routePath) {
if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
return routePath || props.basePath;
} else {
return posix.resolve(props.basePath, routePath);
// 使用path.posix.resolve替代path.resolve 避免windows环境下使用electron出现盘符问题
return path.posix.resolve(props.basePath, routePath);
}
}
</script>
@@ -159,9 +144,9 @@ function resolvePath(routePath) {
item?.pathList?.length === 2)
"
truncated
class="w-full! px-3! min-w-[54px]! text-center! text-inherit!"
class="!w-full !pl-4 !text-inherit"
>
{{ transformI18n(onlyOneChild.meta.title) }}
{{ onlyOneChild.meta.title }}
</el-text>
<template #title>
@@ -171,9 +156,9 @@ function resolvePath(routePath) {
offset: [0, -10],
theme: tooltipEffect
}"
class="w-full! text-inherit!"
class="!w-full !text-inherit"
>
{{ transformI18n(onlyOneChild.meta.title) }}
{{ onlyOneChild.meta.title }}
</ReText>
<SidebarExtraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
</div>
@@ -210,9 +195,17 @@ function resolvePath(routePath) {
offset: [0, -10],
theme: tooltipEffect
}"
:class="textClass"
:class="{
'!w-full': true,
'!text-inherit': true,
'!pl-4':
layout !== 'horizontal' &&
isCollapse &&
!toRaw(item.meta.icon) &&
item.parentId === null
}"
>
{{ transformI18n(item.meta.title) }}
{{ item.meta.title }}
</ReText>
<SidebarExtraIcon v-if="!isCollapse" :extraIcon="item.meta.extraIcon" />
</template>

View File

@@ -1,20 +1,18 @@
<script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { useGlobal } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav";
import MenuFold from "~icons/ri/menu-fold-fill";
import MenuFold from "@iconify-icons/ri/menu-fold-fill";
interface Props {
isActive?: boolean;
isActive: boolean;
}
withDefaults(defineProps<Props>(), {
isActive: false
});
const { t } = useI18n();
const { tooltipEffect } = useNav();
const iconClass = computed(() => {
@@ -23,7 +21,7 @@ const iconClass = computed(() => {
"mb-1",
"w-[16px]",
"h-[16px]",
"inline-block!",
"inline-block",
"align-middle",
"cursor-pointer",
"duration-[100ms]"
@@ -46,9 +44,7 @@ const toggleClick = () => {
<div class="left-collapse">
<IconifyIconOffline
v-tippy="{
content: isActive
? t('buttons.pureClickCollapse')
: t('buttons.pureClickExpand'),
content: isActive ? '点击折叠' : '点击展开',
theme: tooltipEffect,
hideOnClick: 'toggle',
placement: 'right'

View File

@@ -60,11 +60,11 @@ const { title, getLogo } = useNav();
height: 32px;
margin: 2px 0 0 12px;
overflow: hidden;
text-overflow: ellipsis;
font-size: 18px;
font-weight: 600;
line-height: 32px;
color: var(--pure-theme-sub-menu-active-text);
color: $subMenuActiveText;
text-overflow: ellipsis;
white-space: nowrap;
}
}

View File

@@ -1,18 +1,15 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import MenuFold from "~icons/ri/menu-fold-fill";
import MenuUnfold from "~icons/ri/menu-unfold-fill";
import MenuFold from "@iconify-icons/ri/menu-fold-fill";
import MenuUnfold from "@iconify-icons/ri/menu-unfold-fill";
interface Props {
isActive?: boolean;
isActive: boolean;
}
withDefaults(defineProps<Props>(), {
isActive: false
});
const { t } = useI18n();
const emit = defineEmits<{
(e: "toggleClick"): void;
}>();
@@ -25,14 +22,12 @@ const toggleClick = () => {
<template>
<div
class="px-3 mr-1 navbar-bg-hover"
:title="
isActive ? t('buttons.pureClickCollapse') : t('buttons.pureClickExpand')
"
:title="isActive ? '点击折叠' : '点击展开'"
@click="toggleClick"
>
<IconifyIconOffline
:icon="isActive ? MenuFold : MenuUnfold"
class="inline-block! align-middle hover:text-primary dark:hover:text-white!"
class="inline-block align-middle hover:text-primary dark:hover:!text-white"
/>
</div>
</template>

View File

@@ -59,10 +59,10 @@
color: var(--el-color-primary);
cursor: pointer;
border-radius: 4px;
transform: translate(0, -50%);
transition:
background-color 0.12s,
color 0.12s;
transform: translate(0, -50%);
&:hover {
color: rgb(0 0 0 / 88%) !important;
@@ -127,10 +127,10 @@
font-weight: normal;
color: var(--el-text-color-primary);
white-space: nowrap;
outline: 0;
list-style-type: none;
background: #fff;
border-radius: 4px;
outline: 0;
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
li {

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import { $t } from "@/plugins/i18n";
import { emitter } from "@/utils/mitt";
import NProgress from "@/utils/progress";
import { RouteConfigs } from "../../types";
import { useTags } from "../../hooks/useTag";
import { routerArrays } from "@/layout/types";
@@ -19,11 +17,11 @@ import {
useResizeObserver
} from "@pureadmin/utils";
import ExitFullscreen from "~icons/ri/fullscreen-exit-fill";
import Fullscreen from "~icons/ri/fullscreen-fill";
import ArrowDown from "~icons/ri/arrow-down-s-line";
import ArrowRightSLine from "~icons/ri/arrow-right-s-line";
import ArrowLeftSLine from "~icons/ri/arrow-left-s-line";
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
import ArrowDown from "@iconify-icons/ri/arrow-down-s-line";
import ArrowRightSLine from "@iconify-icons/ri/arrow-right-s-line";
import ArrowLeftSLine from "@iconify-icons/ri/arrow-left-s-line";
const {
Close,
@@ -38,7 +36,6 @@ const {
buttonLeft,
showModel,
translateX,
isFixedTag,
pureSetting,
activeIndex,
getTabStyle,
@@ -52,7 +49,6 @@ const {
onMounted,
onMouseenter,
onMouseleave,
transformI18n,
onContentFullScreen
} = useTags();
@@ -207,14 +203,12 @@ function dynamicRouteTag(value: string): void {
/** 刷新路由 */
function onFresh() {
NProgress.start();
const { fullPath, query } = unref(route);
router.replace({
path: "/redirect" + fullPath,
query
});
handleAliveRoute(route as ToRouteType, "refresh");
NProgress.done();
}
function deleteDynamicTag(obj: any, current: any, tag?: string) {
@@ -257,7 +251,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
if (tag === "other") {
spliceRoute(1, 1, true);
} else if (tag === "left") {
spliceRoute(fixedTags.length, valueIndex - fixedTags.length);
spliceRoute(fixedTags.length, valueIndex - 1, true);
} else if (tag === "right") {
spliceRoute(valueIndex + 1, multiTags.value.length);
} else {
@@ -347,16 +341,16 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
setTimeout(() => {
if (pureSetting.hiddenSideBar) {
tagsViews[6].icon = ExitFullscreen;
tagsViews[6].text = $t("buttons.pureContentExitFullScreen");
tagsViews[6].text = "内容区退出全屏";
} else {
tagsViews[6].icon = Fullscreen;
tagsViews[6].text = $t("buttons.pureContentFullScreen");
tagsViews[6].text = "内容区全屏";
}
}, 100);
break;
}
setTimeout(() => {
showMenuModel(route.fullPath, route.query, route.params);
showMenuModel(route.fullPath, route.query);
});
}
@@ -391,18 +385,15 @@ function disabledMenus(value: boolean, fixedTag = false) {
function showMenuModel(
currentPath: string,
query: object = {},
params: object = {},
refresh = false
) {
const allRoute = multiTags.value;
const routeLength = multiTags.value.length;
let currentIndex = -1;
if (!isAllEmpty(params)) {
currentIndex = allRoute.findIndex(v => isEqual(v.params, params));
} else if (!isAllEmpty(query)) {
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
} else {
if (isAllEmpty(query)) {
currentIndex = allRoute.findIndex(v => v.path === currentPath);
} else {
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
}
function fixedTagDisabled() {
if (allRoute[currentIndex]?.meta?.fixedTag) {
@@ -468,14 +459,14 @@ function openMenu(tag, e) {
} else if (route.path !== tag.path && route.name !== tag.name) {
// 右键菜单不匹配当前路由,隐藏刷新
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) {
showMenus(true);
// 只有两个标签时不显示关闭其他标签页
tagsViews[4].show = false;
showMenuModel(tag.path, tag.query, tag.params);
} else {
showMenuModel(tag.path, tag.query, tag.params, true);
} else if (route.path === tag.path) {
// 右键当前激活的菜单
showMenuModel(tag.path, tag.query, true);
}
currentSelect.value = tag;
@@ -517,7 +508,6 @@ function tagOnClick(item) {
} else {
router.push({ path });
}
emitter.emit("tagOnClick", item);
}
onClickOutside(contextmenuRef, closeMenu, {
@@ -586,7 +576,7 @@ onBeforeUnmount(() => {
'scroll-item is-closable',
linkIsActive(item),
showModel === 'chrome' && 'chrome-item',
isFixedTag(item) && 'fixed-tag'
!isAllEmpty(item?.meta?.fixedTag) && 'fixed-tag'
]"
@contextmenu.prevent="openMenu(item, $event)"
@mouseenter.prevent="onMouseenter(index)"
@@ -595,16 +585,16 @@ onBeforeUnmount(() => {
>
<template v-if="showModel !== 'chrome'">
<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
v-if="
isFixedTag(item)
? false
: iconIsActive(item, index) ||
isAllEmpty(item?.meta?.fixedTag)
? iconIsActive(item, index) ||
(index === activeIndex && index !== 0)
: false
"
class="el-icon-close"
@click.stop="deleteMenu(item)"
@@ -622,10 +612,10 @@ onBeforeUnmount(() => {
<TagChrome />
</div>
<span class="tag-title">
{{ transformI18n(item.meta.title) }}
{{ item.meta.title }}
</span>
<span
v-if="isFixedTag(item) ? false : index !== 0"
v-if="isAllEmpty(item?.meta?.fixedTag) ? index !== 0 : false"
class="chrome-close-btn"
@click.stop="deleteMenu(item)"
>
@@ -655,7 +645,7 @@ onBeforeUnmount(() => {
>
<li v-if="item.show" @click="selectTag(key, item)">
<IconifyIconOffline :icon="item.icon" />
{{ transformI18n(item.text) }}
{{ item.text }}
</li>
</div>
</ul>
@@ -679,7 +669,7 @@ onBeforeUnmount(() => {
:disabled="item.disabled"
>
<IconifyIconOffline :icon="item.icon" />
{{ transformI18n(item.text) }}
{{ item.text }}
</el-dropdown-item>
</el-dropdown-menu>
</template>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import { ref, unref, watch, onMounted, nextTick } from "vue";
@@ -14,27 +13,17 @@ const props = defineProps<{
};
}>();
const { t } = useI18n();
const loading = ref(true);
const currentRoute = useRoute();
const frameSrc = ref<string>("");
const frameRef = ref<HTMLElement | null>(null);
const fallbackTimer = ref<number | null>(null);
if (unref(currentRoute.meta)?.frameSrc) {
frameSrc.value = unref(currentRoute.meta)?.frameSrc as string;
}
function clearFallbackTimer() {
if (fallbackTimer.value !== null) {
clearTimeout(fallbackTimer.value);
fallbackTimer.value = null;
}
}
unref(currentRoute.meta)?.frameLoading === false && hideLoading();
function hideLoading() {
loading.value = false;
clearFallbackTimer();
}
function init() {
@@ -43,42 +32,32 @@ function init() {
if (!iframe) return;
const _frame = iframe as any;
if (_frame.attachEvent) {
_frame.attachEvent("onload", hideLoading);
_frame.attachEvent("onload", () => {
hideLoading();
});
} else {
iframe.onload = hideLoading;
iframe.onload = () => {
hideLoading();
};
}
});
}
let isRedirect = false;
watch(
() => currentRoute.fullPath,
path => {
if (
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;
return;
}
if (props.frameInfo?.fullPath === path && isRedirect) {
loading.value = true;
clearFallbackTimer();
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();
// 重新赋值
if (props.frameInfo?.fullPath === path) {
frameSrc.value = props.frameInfo?.frameSrc;
}
}, 1500);
isRedirect = false;
}
},
{ immediate: true }
);
onMounted(() => {
@@ -87,11 +66,7 @@ onMounted(() => {
</script>
<template>
<div
v-loading="loading"
class="frame"
:element-loading-text="t('status.pureLoad')"
>
<div v-loading="loading" class="frame" element-loading-text="加载中...">
<iframe ref="frameRef" :src="frameSrc" class="frame-iframe" />
</div>
</template>

View File

@@ -6,9 +6,14 @@ import { routerArrays } from "@/layout/types";
import { router, resetRouter } from "@/router";
import type { themeColorsType } from "../types";
import { useAppStoreHook } from "@/store/modules/app";
import { useGlobal, storageLocal } from "@pureadmin/utils";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { darken, lighten, useGlobal, storageLocal } from "@pureadmin/utils";
import {
darken,
lighten,
toggleTheme
} from "@pureadmin/theme/dist/browser-utils";
export function useDataThemeChange() {
const { layoutTheme, layout } = useLayout();
@@ -49,7 +54,9 @@ export function useDataThemeChange() {
isClick = true
) {
layoutTheme.value.theme = theme;
document.documentElement.setAttribute("data-theme", theme);
toggleTheme({
scopeName: `layout-theme-${theme}`
});
// 如果非isClick保留之前的themeColor
const storageThemeColor = $storage.layout.themeColor;
$storage.layout = {

View File

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

View File

@@ -1,26 +1,25 @@
import { storeToRefs } from "pinia";
import { getConfig } from "@/config";
import { useRouter } from "vue-router";
import { emitter } from "@/utils/mitt";
import Avatar from "@/assets/user.jpg";
import { getTopMenu } from "@/router/utils";
import { useFullscreen } from "@vueuse/core";
import type { routeMetaType } from "../types";
import { transformI18n } from "@/plugins/i18n";
import { useRouter, useRoute } from "vue-router";
import { router, remainingPaths } from "@/router";
import { computed, type CSSProperties } from "vue";
import { useAppStoreHook } from "@/store/modules/app";
import { useUserStoreHook } from "@/store/modules/user";
import { useGlobal, isAllEmpty } from "@pureadmin/utils";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { usePermissionStoreHook } from "@/store/modules/permission";
import ExitFullscreen from "~icons/ri/fullscreen-exit-fill";
import Fullscreen from "~icons/ri/fullscreen-fill";
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
const errorInfo =
"The current routing configuration is incorrect, please check the configuration";
export function useNav() {
const route = useRoute();
const pureApp = useAppStoreHook();
const routers = useRouter().options.routes;
const { isFullscreen, toggle } = useFullscreen();
@@ -52,22 +51,6 @@ export function useNav() {
: 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(() => {
return username.value ? { marginRight: "10px" } : "";
});
@@ -92,8 +75,8 @@ export function useNav() {
/** 动态title */
function changeTitle(meta: routeMetaType) {
const Title = getConfig().Title;
if (Title) document.title = `${transformI18n(meta.title)} | ${Title}`;
else document.title = transformI18n(meta.title);
if (Title) document.title = `${meta.title} | ${Title}`;
else document.title = meta.title;
}
/** 退出登录 */
@@ -144,6 +127,7 @@ export function useNav() {
}
return {
route,
title,
device,
layout,
@@ -168,8 +152,6 @@ export function useNav() {
username,
userAvatar,
avatarsStyle,
tooltipEffect,
getDropdownItemStyle,
getDropdownItemClass
tooltipEffect
};
}

View File

@@ -9,7 +9,6 @@ import {
} from "vue";
import type { tagsViewsType } from "../types";
import { useRoute, useRouter } from "vue-router";
import { transformI18n, $t } from "@/plugins/i18n";
import { responsiveStorageNameSpace } from "@/config";
import { useSettingStoreHook } from "@/store/modules/settings";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
@@ -21,13 +20,13 @@ import {
hasClass
} from "@pureadmin/utils";
import Fullscreen from "~icons/ri/fullscreen-fill";
import CloseAllTags from "~icons/ri/subtract-line";
import CloseOtherTags from "~icons/ri/text-spacing";
import CloseRightTags from "~icons/ri/text-direction-l";
import CloseLeftTags from "~icons/ri/text-direction-r";
import RefreshRight from "~icons/ep/refresh-right";
import Close from "~icons/ep/close";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
import CloseAllTags from "@iconify-icons/ri/subtract-line";
import CloseOtherTags from "@iconify-icons/ri/text-spacing";
import CloseRightTags from "@iconify-icons/ri/text-direction-l";
import CloseLeftTags from "@iconify-icons/ri/text-direction-r";
import RefreshRight from "@iconify-icons/ep/refresh-right";
import Close from "@iconify-icons/ep/close";
export function useTags() {
const route = useRoute();
@@ -64,49 +63,49 @@ export function useTags() {
const tagsViews = reactive<Array<tagsViewsType>>([
{
icon: RefreshRight,
text: $t("buttons.pureReload"),
text: "重新加载",
divided: false,
disabled: false,
show: true
},
{
icon: Close,
text: $t("buttons.pureCloseCurrentTab"),
text: "关闭当前标签页",
divided: false,
disabled: multiTags.value.length > 1 ? false : true,
show: true
},
{
icon: CloseLeftTags,
text: $t("buttons.pureCloseLeftTabs"),
text: "关闭左侧标签页",
divided: true,
disabled: multiTags.value.length > 1 ? false : true,
show: true
},
{
icon: CloseRightTags,
text: $t("buttons.pureCloseRightTabs"),
text: "关闭右侧标签页",
divided: false,
disabled: multiTags.value.length > 1 ? false : true,
show: true
},
{
icon: CloseOtherTags,
text: $t("buttons.pureCloseOtherTabs"),
text: "关闭其他标签页",
divided: true,
disabled: multiTags.value.length > 2 ? false : true,
show: true
},
{
icon: CloseAllTags,
text: $t("buttons.pureCloseAllTabs"),
text: "关闭全部标签页",
divided: false,
disabled: multiTags.value.length > 1 ? false : true,
show: true
},
{
icon: Fullscreen,
text: $t("buttons.pureContentFullScreen"),
text: "内容区全屏",
divided: true,
disabled: false,
show: true
@@ -114,30 +113,17 @@ export function useTags() {
]);
function conditionHandle(item, previous, next) {
const currentName = route.name || "";
const itemName = item.name || "";
if (isBoolean(route?.meta?.showLink) && route?.meta?.showLink === false) {
if (Object.keys(route.query).length > 0) {
return currentName === itemName && isEqual(route.query, item.query)
? previous
: next;
return isEqual(route.query, item.query) ? previous : next;
} else {
return currentName === itemName && isEqual(route.params, item.params)
? previous
: next;
return isEqual(route.params, item.params) ? previous : next;
}
} else {
return currentName === itemName ? previous : next;
return route.path === item.path ? previous : next;
}
}
const isFixedTag = computed(() => {
return item => {
return isBoolean(item?.meta?.fixedTag) && item?.meta?.fixedTag === true;
};
});
const iconIsActive = computed(() => {
return (item, index) => {
if (index === 0) return;
@@ -234,7 +220,6 @@ export function useTags() {
buttonTop,
buttonLeft,
translateX,
isFixedTag,
pureSetting,
activeIndex,
getTabStyle,
@@ -244,12 +229,10 @@ export function useTags() {
currentSelect,
scheduleIsActive,
getContextMenuStyle,
$t,
closeMenu,
onMounted,
onMouseenter,
onMouseleave,
transformI18n,
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添加过的本地图标
import "@/components/ReIcon/src/offlineIcon";
import { setType } from "./types";
import { useI18n } from "vue-i18n";
import { useLayout } from "./hooks/useLayout";
import { useAppStoreHook } from "@/store/modules/app";
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 BackTopIcon from "@/assets/svg/back_top.svg?component";
const { t } = useI18n();
const appWrapperRef = ref();
const { isDark } = useDark();
const { layout } = useLayout();
@@ -188,7 +186,7 @@ const LayHeader = defineComponent({
</div>
<el-scrollbar v-else>
<el-backtop
:title="t('buttons.pureBackTop')"
title="回到顶部"
target=".main-container .el-scrollbar__wrap"
>
<BackTopIcon />
@@ -210,8 +208,8 @@ const LayHeader = defineComponent({
height: 100%;
&::after {
clear: both;
display: table;
clear: both;
content: "";
}
@@ -224,7 +222,7 @@ const LayHeader = defineComponent({
.app-mask {
position: absolute;
top: 0;
z-index: 2001;
z-index: 999;
width: 100%;
height: 100%;
background: #000;

129
src/layout/theme/index.ts Normal file
View File

@@ -0,0 +1,129 @@
/**
* @description ⚠️:此文件仅供主题插件使用,请不要在此文件中导出别的工具函数(仅在页面加载前运行)
*/
import type { multipleScopeVarsOptions } from "@pureadmin/theme";
/** 预设主题色 */
const themeColors = {
/* 亮白色 */
light: {
subMenuActiveText: "#000000d9",
menuBg: "#fff",
menuHover: "#f6f6f6",
subMenuBg: "#fff",
subMenuActiveBg: "#e0ebf6",
menuText: "rgb(0 0 0 / 60%)",
sidebarLogo: "#fff",
menuTitleHover: "#000",
menuActiveBefore: "#4091f7"
},
/* 道奇蓝 */
default: {
subMenuActiveText: "#fff",
menuBg: "#001529",
menuHover: "rgb(64 145 247 / 15%)",
subMenuBg: "#0f0303",
subMenuActiveBg: "#4091f7",
menuText: "rgb(254 254 254 / 65%)",
sidebarLogo: "#002140",
menuTitleHover: "#fff",
menuActiveBefore: "#4091f7"
},
/* 深紫罗兰色 */
saucePurple: {
subMenuActiveText: "#fff",
menuBg: "#130824",
menuHover: "rgb(105 58 201 / 15%)",
subMenuBg: "#000",
subMenuActiveBg: "#693ac9",
menuText: "#7a80b4",
sidebarLogo: "#1f0c38",
menuTitleHover: "#fff",
menuActiveBefore: "#693ac9"
},
/* 深粉色 */
pink: {
subMenuActiveText: "#fff",
menuBg: "#28081a",
menuHover: "rgb(216 68 147 / 15%)",
subMenuBg: "#000",
subMenuActiveBg: "#d84493",
menuText: "#7a80b4",
sidebarLogo: "#3f0d29",
menuTitleHover: "#fff",
menuActiveBefore: "#d84493"
},
/* 猩红色 */
dusk: {
subMenuActiveText: "#fff",
menuBg: "#2a0608",
menuHover: "rgb(225 60 57 / 15%)",
subMenuBg: "#000",
subMenuActiveBg: "#e13c39",
menuText: "rgb(254 254 254 / 65.1%)",
sidebarLogo: "#42090c",
menuTitleHover: "#fff",
menuActiveBefore: "#e13c39"
},
/* 橙红色 */
volcano: {
subMenuActiveText: "#fff",
menuBg: "#2b0e05",
menuHover: "rgb(232 95 51 / 15%)",
subMenuBg: "#0f0603",
subMenuActiveBg: "#e85f33",
menuText: "rgb(254 254 254 / 65%)",
sidebarLogo: "#441708",
menuTitleHover: "#fff",
menuActiveBefore: "#e85f33"
},
/* 绿宝石 */
mingQing: {
subMenuActiveText: "#fff",
menuBg: "#032121",
menuHover: "rgb(89 191 193 / 15%)",
subMenuBg: "#000",
subMenuActiveBg: "#59bfc1",
menuText: "#7a80b4",
sidebarLogo: "#053434",
menuTitleHover: "#fff",
menuActiveBefore: "#59bfc1"
},
/* 酸橙绿 */
auroraGreen: {
subMenuActiveText: "#fff",
menuBg: "#0b1e15",
menuHover: "rgb(96 172 128 / 15%)",
subMenuBg: "#000",
subMenuActiveBg: "#60ac80",
menuText: "#7a80b4",
sidebarLogo: "#112f21",
menuTitleHover: "#fff",
menuActiveBefore: "#60ac80"
}
};
/**
* @description 将预设主题色处理成主题插件所需格式
*/
export const genScssMultipleScopeVars = (): multipleScopeVarsOptions[] => {
const result = [] as multipleScopeVarsOptions[];
Object.keys(themeColors).forEach(key => {
result.push({
scopeName: `layout-theme-${key}`,
varsContent: `
$subMenuActiveText: ${themeColors[key].subMenuActiveText} !default;
$menuBg: ${themeColors[key].menuBg} !default;
$menuHover: ${themeColors[key].menuHover} !default;
$subMenuBg: ${themeColors[key].subMenuBg} !default;
$subMenuActiveBg: ${themeColors[key].subMenuActiveBg} !default;
$menuText: ${themeColors[key].menuText} !default;
$sidebarLogo: ${themeColors[key].sidebarLogo} !default;
$menuTitleHover: ${themeColors[key].menuTitleHover} !default;
$menuActiveBefore: ${themeColors[key].menuActiveBefore} !default;
`
} as multipleScopeVarsOptions);
});
return result;
};

View File

@@ -1,4 +1,4 @@
import type { FunctionalComponent } from "vue";
import type { IconifyIcon } from "@iconify/vue";
const { VITE_HIDE_HOME } = import.meta.env;
export const routerArrays: Array<RouteConfigs> =
@@ -6,10 +6,9 @@ export const routerArrays: Array<RouteConfigs> =
? [
{
path: "/welcome",
name: "Welcome",
meta: {
title: "menus.pureHome",
icon: "ep/home-filled"
title: "首页",
icon: "ep:home-filled"
}
}
]
@@ -17,7 +16,7 @@ export const routerArrays: Array<RouteConfigs> =
export type routeMetaType = {
title?: string;
icon?: string | FunctionalComponent;
icon?: string | IconifyIcon;
showLink?: boolean;
savedPosition?: boolean;
auths?: Array<string>;
@@ -37,7 +36,7 @@ export type multiTagsType = {
};
export type tagsViewsType = {
icon: string | FunctionalComponent;
icon: string | IconifyIcon;
text: string;
divided: boolean;
disabled: boolean;

View File

@@ -1,7 +1,6 @@
import App from "./App.vue";
import router from "./router";
import { setupStore } from "@/store";
import { useI18n } from "@/plugins/i18n";
import { getPlatformConfig } from "./config";
import { MotionPlugin } from "@vueuse/motion";
// import { useEcharts } from "@/plugins/echarts";
@@ -43,9 +42,7 @@ app.component("FontIcon", FontIcon);
// 全局注册按钮级别权限组件
import { Auth } from "@/components/ReAuth";
import { Perms } from "@/components/RePerms";
app.component("Auth", Auth);
app.component("Perms", Perms);
// 全局注册vue-tippy
import "tippy.js/dist/tippy.css";
@@ -58,7 +55,7 @@ getPlatformConfig(app).then(async config => {
app.use(router);
await router.isReady();
injectResponsiveStorage(app, config);
app.use(MotionPlugin).use(useI18n).use(useElementPlus).use(Table);
app.use(MotionPlugin).use(useElementPlus).use(Table);
// .use(PureDescriptions)
// .use(useEcharts);
app.mount("#app");

View File

@@ -107,7 +107,6 @@ import {
ElWatermark,
ElTour,
ElTourStep,
ElSegmented,
/**
* 为了方便演示平台将 element-plus 导出的所有插件引入,实际使用中如果你没用到哪个插件,将其注释掉就行
* 导出来源https://github.com/element-plus/element-plus/blob/dev/packages/element-plus/plugin.ts#L11-L16
@@ -222,8 +221,7 @@ const components = [
ElUpload,
ElWatermark,
ElTour,
ElTourStep,
ElSegmented
ElTourStep
];
const plugins = [

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,18 +2,11 @@
import Cookies from "js-cookie";
import { getConfig } from "@/config";
import NProgress from "@/utils/progress";
import { transformI18n } from "@/plugins/i18n";
import { buildHierarchyTree } from "@/utils/tree";
import remainingRouter from "./modules/remaining";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { usePermissionStoreHook } from "@/store/modules/permission";
import {
isUrl,
openLink,
cloneDeep,
isAllEmpty,
storageLocal
} from "@pureadmin/utils";
import { isUrl, openLink, storageLocal, isAllEmpty } from "@pureadmin/utils";
import {
ascending,
getTopMenu,
@@ -27,9 +20,9 @@ import {
} from "./utils";
import {
type Router,
createRouter,
type RouteRecordRaw,
type RouteComponent,
createRouter
type RouteComponent
} from "vue-router";
import {
type DataInfo,
@@ -61,9 +54,6 @@ export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
formatFlatteningRoutes(buildHierarchyTree(ascending(routes.flat(Infinity))))
);
/** 初始的静态路由,用于退出登录时重置路由 */
const initConstantRoutes: Array<RouteRecordRaw> = cloneDeep(constantRoutes);
/** 用于渲染菜单,保持原始层级 */
export const constantMenus: Array<RouteComponent> = ascending(
routes.flat(Infinity)
@@ -94,25 +84,20 @@ export const router: Router = createRouter({
}
});
/** 记录已经加载的页面路径 */
const loadedPaths = new Set<string>();
/** 重置已加载页面记录 */
export function resetLoadedPaths() {
loadedPaths.clear();
}
/** 重置路由 */
export function resetRouter() {
router.clearRoutes();
for (const route of initConstantRoutes.concat(...(remainingRouter as any))) {
router.addRoute(route);
}
router.getRoutes().forEach(route => {
const { name, meta } = route;
if (name && router.hasRoute(name) && meta?.backstage) {
router.removeRoute(name);
router.options.routes = formatTwoStageRoutes(
formatFlatteningRoutes(buildHierarchyTree(ascending(routes.flat(Infinity))))
formatFlatteningRoutes(
buildHierarchyTree(ascending(routes.flat(Infinity)))
)
);
}
});
usePermissionStoreHook().clearAllCachePage();
resetLoadedPaths();
}
/** 路由白名单 */
@@ -121,12 +106,6 @@ const whiteList = ["/login"];
const { VITE_HIDE_HOME } = import.meta.env;
router.beforeEach((to: ToRouteType, _from, next) => {
to.meta.loaded = loadedPaths.has(to.path);
if (!to.meta.loaded) {
NProgress.start();
}
if (to.meta?.keepAlive) {
handleAliveRoute(to, "add");
// 页面整体刷新和点击标签页刷新
@@ -135,14 +114,14 @@ router.beforeEach((to: ToRouteType, _from, next) => {
}
}
const userInfo = storageLocal().getItem<DataInfo<number>>(userKey);
NProgress.start();
const externalLink = isUrl(to?.name as string);
if (!externalLink) {
to.matched.some(item => {
if (!item.meta.title) return "";
const Title = getConfig().Title;
if (Title)
document.title = `${transformI18n(item.meta.title)} | ${Title}`;
else document.title = transformI18n(item.meta.title);
if (Title) document.title = `${item.meta.title} | ${Title}`;
else document.title = item.meta.title as string;
});
}
/** 如果已经登录并存在登录信息后不能跳转到路由白名单,而是继续保持在当前页面 */
@@ -220,8 +199,7 @@ router.beforeEach((to: ToRouteType, _from, next) => {
}
});
router.afterEach(to => {
loadedPaths.add(to.path);
router.afterEach(() => {
NProgress.done();
});

View File

@@ -1,12 +1,10 @@
import { $t } from "@/plugins/i18n";
export default {
path: "/error",
redirect: "/error/403",
meta: {
icon: "ri/information-line",
icon: "ri:information-line",
// showLink: false,
title: $t("menus.pureAbnormal"),
title: "异常页面",
rank: 9
},
children: [
@@ -15,7 +13,7 @@ export default {
name: "403",
component: () => import("@/views/error/403.vue"),
meta: {
title: $t("menus.pureAccessDenied")
title: "403"
}
},
{
@@ -23,7 +21,7 @@ export default {
name: "404",
component: () => import("@/views/error/404.vue"),
meta: {
title: $t("menus.purePageNotFound")
title: "404"
}
},
{
@@ -31,7 +29,7 @@ export default {
name: "500",
component: () => import("@/views/error/500.vue"),
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 Layout = () => import("@/layout/index.vue");
@@ -8,8 +7,8 @@ export default {
component: Layout,
redirect: "/welcome",
meta: {
icon: "ep/home-filled",
title: $t("menus.pureHome"),
icon: "ep:home-filled",
title: "首页",
rank: 0
},
children: [
@@ -18,7 +17,7 @@ export default {
name: "Welcome",
component: () => import("@/views/welcome/index.vue"),
meta: {
title: $t("menus.pureHome"),
title: "首页",
showLink: VITE_HIDE_HOME === "true" ? false : true
}
}

View File

@@ -1,4 +1,3 @@
import { $t } from "@/plugins/i18n";
const Layout = () => import("@/layout/index.vue");
export default [
@@ -7,36 +6,18 @@ export default [
name: "Login",
component: () => import("@/views/login/index.vue"),
meta: {
title: $t("menus.pureLogin"),
showLink: false
}
},
// 全屏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
title: "登录",
showLink: false,
rank: 101
}
},
{
path: "/redirect",
component: Layout,
meta: {
title: $t("status.pureLoad"),
showLink: false
title: "加载中...",
showLink: false,
rank: 102
},
children: [
{

View File

@@ -139,17 +139,12 @@ function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
}
}
/** 动态路由注册完成后再添加全屏404页面不存在页面避免刷新动态路由页面时误跳转到404页面 */
function addPathMatch() {
if (!router.hasRoute("pathMatch")) {
router.addRoute({
path: "/:pathMatch(.*)*",
name: "PageNotFound",
component: () => import("@/views/error/404.vue"),
meta: {
title: "menus.purePageNotFound",
showLink: false
}
path: "/:pathMatch(.*)",
name: "pathMatch",
redirect: "/error/404"
});
}
}
@@ -177,8 +172,6 @@ function handleAsyncRoutes(routeList) {
const flattenRouters: any = router
.getRoutes()
.find(n => n.path === "/");
// 保持router.options.routes[0].children与path为"/"的children一致防止数据不一致导致异常
flattenRouters.children = router.options.routes[0].children;
router.addRoute(flattenRouters);
}
}
@@ -362,7 +355,7 @@ function getAuths(): Array<string> {
return router.currentRoute.value.meta.auths as Array<string>;
}
/** 是否有按钮级别的权限(根据路由`meta`中的`auths`字段进行判断)*/
/** 是否有按钮级别的权限 */
function hasAuth(value: string | Array<string>): boolean {
if (!value) return false;
/** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */

View File

@@ -8,7 +8,8 @@ import {
responsiveStorageNameSpace
} from "../utils";
export const useAppStore = defineStore("pure-app", {
export const useAppStore = defineStore({
id: "pure-app",
state: (): appType => ({
sidebar: {
opened:
@@ -76,6 +77,9 @@ export const useAppStore = defineStore("pure-app", {
},
setViewportSize(size) {
this.viewportSize = size;
},
setSortSwap(val) {
this.sortSwap = val;
}
}
});

View File

@@ -6,7 +6,8 @@ import {
responsiveStorageNameSpace
} from "../utils";
export const useEpThemeStore = defineStore("pure-epTheme", {
export const useEpThemeStore = defineStore({
id: "pure-epTheme",
state: () => ({
epThemeColor:
storageLocal().getItem<StorageConfigs>(

View File

@@ -14,7 +14,8 @@ import {
} from "../utils";
import { usePermissionStoreHook } from "./permission";
export const useMultiTagsStore = defineStore("pure-multiTags", {
export const useMultiTagsStore = defineStore({
id: "pure-multiTags",
state: () => ({
// 存储标签页信息(路由信息)
multiTags: storageLocal().getItem<StorageConfigs>(
@@ -23,12 +24,12 @@ export const useMultiTagsStore = defineStore("pure-multiTags", {
? storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}tags`
)
: ([
: [
...routerArrays,
...usePermissionStoreHook().flatteningRoutes.filter(
v => v?.meta?.fixedTag
)
] as any),
],
multiTagsCache: storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}configure`
)?.multiTagsCache
@@ -80,15 +81,22 @@ export const useMultiTagsStore = defineStore("pure-multiTags", {
if (isBoolean(tagVal?.meta?.showLink) && !tagVal?.meta?.showLink)
return;
const tagPath = tagVal.path;
// 判断tag是否已存在
const tagHasExits = this.multiTags.some(tag => {
return (
tag.path === tagPath &&
isEqual(tag?.query, tagVal?.query) &&
isEqual(tag?.params, tagVal?.params)
);
return tag.path === tagPath;
});
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;

View File

@@ -2,6 +2,7 @@ import { defineStore } from "pinia";
import {
type cacheType,
store,
debounce,
ascending,
getKeyList,
filterTree,
@@ -11,7 +12,8 @@ import {
} from "../utils";
import { useMultiTagsStoreHook } from "./multiTags";
export const usePermissionStore = defineStore("pure-permission", {
export const usePermissionStore = defineStore({
id: "pure-permission",
state: () => ({
// 静态路由生成的菜单
constantMenus,
@@ -29,11 +31,24 @@ export const usePermissionStore = defineStore("pure-permission", {
filterTree(ascending(this.constantMenus.concat(routes)))
);
this.flatteningRoutes = formatFlatteningRoutes(
this.constantMenus.concat(routes) as any
this.constantMenus.concat(routes)
);
},
cacheOperate({ mode, name }: cacheType) {
const delIndex = this.cachePageList.findIndex(v => v === name);
switch (mode) {
case "refresh":
this.cachePageList = this.cachePageList.filter(v => v !== name);
break;
case "add":
this.cachePageList.push(name);
break;
case "delete":
delIndex !== -1 && this.cachePageList.splice(delIndex, 1);
break;
}
/** 监听缓存页面是否存在于标签页,不存在则删除 */
clearCache() {
debounce(() => {
let cacheLength = this.cachePageList.length;
const nameList = getKeyList(useMultiTagsStoreHook().multiTags, "name");
while (cacheLength > 0) {
@@ -45,22 +60,7 @@ export const usePermissionStore = defineStore("pure-permission", {
);
cacheLength--;
}
},
cacheOperate({ mode, name }: cacheType) {
const delIndex = this.cachePageList.findIndex(v => v === name);
switch (mode) {
case "refresh":
this.cachePageList = this.cachePageList.filter(v => v !== name);
this.clearCache();
break;
case "add":
this.cachePageList.push(name);
break;
case "delete":
delIndex !== -1 && this.cachePageList.splice(delIndex, 1);
this.clearCache();
break;
}
})();
},
/** 清空缓存页面 */
clearAllCachePage() {

View File

@@ -1,7 +1,8 @@
import { defineStore } from "pinia";
import { type setType, store, getConfig } from "../utils";
export const useSettingStore = defineStore("pure-setting", {
export const useSettingStore = defineStore({
id: "pure-setting",
state: (): setType => ({
title: getConfig().Title,
fixedHeader: getConfig().FixedHeader,

View File

@@ -16,7 +16,8 @@ import {
import { useMultiTagsStoreHook } from "./multiTags";
import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth";
export const useUserStore = defineStore("pure-user", {
export const useUserStore = defineStore({
id: "pure-user",
state: (): userType => ({
// 头像
avatar: storageLocal().getItem<DataInfo<number>>(userKey)?.avatar ?? "",
@@ -26,9 +27,6 @@ export const useUserStore = defineStore("pure-user", {
nickname: storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "",
// 页面级别权限
roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [],
// 按钮级别权限
permissions:
storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? [],
// 是否勾选了登录页的免登录
isRemembered: false,
// 登录页的免登录存储几天默认7天
@@ -51,10 +49,6 @@ export const useUserStore = defineStore("pure-user", {
SET_ROLES(roles: Array<string>) {
this.roles = roles;
},
/** 存储按钮级别权限 */
SET_PERMS(permissions: Array<string>) {
this.permissions = permissions;
},
/** 存储是否勾选了登录页的免登录 */
SET_ISREMEMBERED(bool: boolean) {
this.isRemembered = bool;
@@ -80,7 +74,6 @@ export const useUserStore = defineStore("pure-user", {
logOut() {
this.username = "";
this.roles = [];
this.permissions = [];
removeToken();
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter();

View File

@@ -41,7 +41,6 @@ export type userType = {
username?: string;
nickname?: string;
roles?: Array<string>;
permissions?: Array<string>;
isRemembered?: boolean;
loginDay?: number;
};

View File

@@ -83,8 +83,8 @@
.el-upload-list__item.is-ready &.el-icon--close {
width: 24px;
height: 24px;
outline: none;
border-radius: 4px;
outline: none;
transition:
background-color 0.2s,
color 0.2s;
@@ -117,8 +117,8 @@
}
& .el-message__closeBtn {
outline: none;
border-radius: 4px;
outline: none;
transition:
background-color 0.2s,
color 0.2s;
@@ -163,10 +163,9 @@
/* 仿 el-scrollbar 滚动条样式支持大多数浏览器如Chrome、Edge、Firefox、Safari等。整体暗色风格在 src/style/dark.scss 文件进行了适配 */
.pure-scrollbar {
scrollbar-color: rgb(221 222 224) transparent; /* 滑块颜色、轨道颜色 */
/* Firefox */
scrollbar-width: thin; /* 可选值为 'auto', 'thin', 'none' */
scrollbar-color: rgb(221 222 224) transparent; /* 滑块颜色、轨道颜色 */
::-webkit-scrollbar {
width: 6px; /* 滚动条宽度 */
}

View File

@@ -1,8 +1,7 @@
@use "theme";
@use "transition";
@use "element-plus";
@use "sidebar";
@use "dark";
@import "./transition";
@import "./element-plus";
@import "./sidebar";
@import "./dark";
/* 自定义全局 CssVar */
:root {
@@ -14,16 +13,6 @@
/* switch关闭状态下的color 需要时可取用 */
--pure-switch-off-color: #a6a6a6;
/** 主题色 */
--pure-theme-sub-menu-active-text: initial;
--pure-theme-menu-bg: none;
--pure-theme-menu-hover: none;
--pure-theme-sub-menu-bg: transparent;
--pure-theme-menu-text: initial;
--pure-theme-sidebar-logo: none;
--pure-theme-menu-title-hover: initial;
--pure-theme-menu-active-before: transparent;
}
/* 灰色模式 */

View File

@@ -1,3 +1,12 @@
*,
::before,
::after {
box-sizing: border-box;
border-color: currentColor;
border-style: solid;
border-width: 0;
}
#app {
width: 100%;
height: 100%;
@@ -16,8 +25,7 @@ body {
width: 100%;
height: 100%;
margin: 0;
font-family:
"Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
line-height: inherit;
-moz-osx-font-smoothing: grayscale;
@@ -49,9 +57,8 @@ code,
kbd,
samp,
pre {
font-family:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
font-size: 1em;
}

View File

@@ -1,5 +1,7 @@
/* $sideBarWidth: vertical 模式下主体内容距离网页文档左侧的距离 */
@mixin merge-style($sideBarWidth) {
$menuActiveText: #7a80b4;
@media screen and (width >= 150px) and (width <= 420px) {
.app-main-nofixed-header {
overflow-y: hidden;
@@ -92,7 +94,7 @@
height: 100%;
overflow: visible;
font-size: 0;
background: var(--pure-theme-menu-bg) !important;
background: $menuBg;
border-right: 1px solid var(--pure-border-color);
/* 展开动画 */
@@ -148,11 +150,11 @@
.el-menu-item,
.el-sub-menu__title {
height: 50px;
color: var(--pure-theme-menu-text);
color: $menuText;
background-color: transparent !important;
&:hover {
color: var(--pure-theme-menu-title-hover) !important;
color: $menuTitleHover !important;
}
div,
@@ -171,11 +173,15 @@
.is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown {
color: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
i {
color: $subMenuActiveText !important;
}
}
.is-active {
color: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
transition: color 0.3s;
}
@@ -187,8 +193,8 @@
.el-menu-item.is-active.nest-menu::before {
position: absolute;
inset: 0 8px;
clear: both;
margin: 4px 0;
clear: both;
content: "";
background: var(--el-color-primary) !important;
border-radius: 3px;
@@ -198,7 +204,7 @@
& .el-sub-menu .el-menu-item {
min-width: $sideBarWidth !important;
font-size: 14px;
background-color: var(--pure-theme-sub-menu-bg) !important;
background-color: $subMenuBg !important;
}
/* 有子集的激活菜单左侧小竖条 */
@@ -208,13 +214,13 @@
position: absolute;
top: 0;
left: 0;
clear: both;
width: 2px;
height: 100%;
clear: both;
content: "";
background-color: var(--pure-theme-menu-active-before);
transform: translateY(0);
background-color: $menuActiveBefore;
transition: all var(--pure-transition-duration) ease-in-out;
transform: translateY(0);
}
.el-menu--collapse .outer-most.el-sub-menu > .el-sub-menu__title::before {
@@ -236,8 +242,8 @@
.is-active.submenu-title-noDropdown.outer-most::before {
position: absolute;
inset: 0 8px;
clear: both;
margin: 4px 0;
clear: both;
content: "";
background: var(--el-color-primary) !important;
border-radius: 3px;
@@ -247,7 +253,7 @@
/* vertical 菜单折叠 */
.el-menu--vertical {
.el-menu--popup {
background-color: var(--pure-theme-sub-menu-bg) !important;
background-color: $subMenuBg !important;
.el-menu-item {
span {
@@ -265,30 +271,34 @@
.is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown {
color: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
i {
color: $subMenuActiveText !important;
}
}
/* 子菜单中还有子菜单 */
.el-menu .el-sub-menu__title {
min-width: $sideBarWidth !important;
font-size: 14px;
background-color: var(--pure-theme-sub-menu-bg) !important;
background-color: $subMenuBg !important;
}
.el-menu-item,
.el-sub-menu__title {
height: 50px;
line-height: 50px;
color: var(--pure-theme-menu-text);
background-color: var(--pure-theme-sub-menu-bg);
color: $menuText;
background-color: $subMenuBg;
&:hover {
color: var(--pure-theme-menu-title-hover) !important;
color: $menuTitleHover !important;
}
}
.is-active {
color: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
transition: color 0.3s;
}
@@ -332,15 +342,15 @@
}
.el-menu--popup {
background-color: var(--pure-theme-sub-menu-bg) !important;
background-color: $subMenuBg !important;
a > .is-active.submenu-title-noDropdown {
border-bottom: none;
}
.el-menu-item {
color: var(--pure-theme-menu-text);
background-color: var(--pure-theme-sub-menu-bg);
color: $menuText;
background-color: $subMenuBg;
span {
font-size: 14px;
@@ -348,7 +358,7 @@
}
.el-sub-menu__title {
color: var(--pure-theme-menu-text);
color: $menuText;
}
}
@@ -356,27 +366,31 @@
.el-menu .el-sub-menu__title {
min-width: $sideBarWidth !important;
font-size: 14px;
background-color: var(--pure-theme-sub-menu-bg) !important;
background-color: $subMenuBg !important;
&:hover {
color: var(--pure-theme-menu-title-hover) !important;
color: $menuTitleHover !important;
}
}
.is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown {
color: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
i {
color: $subMenuActiveText !important;
}
}
.nest-menu .el-sub-menu > .el-sub-menu__title,
.el-menu-item {
&:hover {
color: var(--pure-theme-menu-title-hover) !important;
color: $menuTitleHover !important;
}
}
.el-menu-item.is-active {
color: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
transition: color 0.3s;
}
@@ -401,7 +415,7 @@
justify-content: space-around;
width: 100%;
height: 48px;
background: var(--pure-theme-menu-bg) !important;
background: $menuBg;
.horizontal-header-left {
display: flex;
@@ -423,11 +437,11 @@
height: 32px;
margin: 2px 0 0 12px;
overflow: hidden;
text-overflow: ellipsis;
font-size: 18px;
font-weight: 600;
line-height: 32px;
color: var(--pure-theme-sub-menu-active-text);
color: $subMenuActiveText;
text-overflow: ellipsis;
white-space: nowrap;
}
}
@@ -444,12 +458,10 @@
align-items: center;
justify-content: flex-end;
min-width: 340px;
color: var(--pure-theme-sub-menu-active-text);
color: $subMenuActiveText;
/* 搜索 */
.search-container,
/* 国际化 */
.globalization,
/* 全屏 */
.fullscreen-icon,
/* 消息通知 */
@@ -459,22 +471,13 @@
/* 设置 */
.set-icon {
&:hover {
background: var(--pure-theme-menu-hover);
background: $menuHover;
}
}
.dropdown-badge {
height: 48px;
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;
color: $subMenuActiveText;
}
.el-dropdown-link {
@@ -483,7 +486,7 @@
justify-content: space-around;
height: 48px;
padding: 10px;
color: var(--pure-theme-sub-menu-active-text);
color: $subMenuActiveText;
cursor: pointer;
p {
@@ -508,10 +511,10 @@
.el-menu-item,
.el-sub-menu__title {
padding-right: var(--el-menu-base-level-padding);
color: var(--pure-theme-menu-text);
color: $menuText;
&:hover {
color: var(--pure-theme-menu-title-hover) !important;
color: $menuTitleHover !important;
}
}
@@ -519,7 +522,7 @@
.el-sub-menu__title {
height: 48px;
line-height: 48px;
background: var(--pure-theme-menu-bg) !important;
background: $menuBg;
svg {
position: static !important;
@@ -528,11 +531,15 @@
.is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown {
color: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
i {
color: $subMenuActiveText !important;
}
}
.is-active {
color: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
transition: color 0.3s;
}
}
@@ -553,7 +560,6 @@
}
.sidebar-container {
z-index: 2001;
width: $sideBarWidth;
transition: transform var(--pure-transition-duration);
}
@@ -561,8 +567,8 @@
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transform: translate3d(-$sideBarWidth, 0, 0);
transition-duration: 0.3s;
transform: translate3d(-$sideBarWidth, 0, 0);
}
}
}
@@ -578,7 +584,7 @@ body[layout="vertical"] {
}
.sidebar-logo-container {
background: var(--pure-theme-sidebar-logo);
background: $sidebarLogo;
}
.hideSidebar {
@@ -605,10 +611,10 @@ body[layout="vertical"] {
.el-sub-menu {
& > .el-sub-menu__title {
& > span {
visibility: visible;
width: 100%;
height: 100%;
text-align: center;
visibility: visible;
}
}
}
@@ -629,8 +635,6 @@ body[layout="vertical"] {
/* 搜索 */
.search-container,
/* 国际化 */
.globalization,
/* 全屏 */
.fullscreen-icon,
/* 消息通知 */
@@ -650,10 +654,6 @@ body[layout="horizontal"] {
@include merge-style($sideBarWidth);
.el-menu {
--el-menu-hover-text-color: var(--pure-theme-menu-text) !important;
}
.fixed-header,
.main-container {
transition: none !important;
@@ -675,7 +675,6 @@ body[layout="mix"] {
.el-menu {
--el-menu-hover-bg-color: transparent !important;
--el-menu-hover-text-color: var(--pure-theme-menu-text) !important;
}
.hideSidebar {
@@ -704,10 +703,10 @@ body[layout="mix"] {
padding: 0;
& > span {
visibility: visible;
width: 100%;
height: 100%;
text-align: center;
visibility: visible;
}
}
}

View File

@@ -1,46 +1,21 @@
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities);
@tailwind base;
@tailwind components;
@tailwind utilities;
@custom-variant dark (&:is(.dark *));
@theme {
--color-bg_color: var(--el-bg-color);
--color-primary: var(--el-color-primary);
--color-text_color_primary: var(--el-text-color-primary);
--color-text_color_regular: var(--el-text-color-regular);
}
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
@utility flex-c {
@layer components {
.flex-c {
@apply flex justify-center items-center;
}
@utility flex-ac {
.flex-ac {
@apply flex justify-around items-center;
}
@utility flex-bc {
.flex-bc {
@apply flex justify-between items-center;
}
@utility navbar-bg-hover {
@apply dark:text-white dark:hover:bg-[#242424]!;
.navbar-bg-hover {
@apply dark:text-white dark:hover:!bg-[#242424];
}
}

Some files were not shown because too many files have changed in this diff Show More