Compare commits

..

No commits in common. "main" and "v5.5.0" have entirely different histories.
main ... v5.5.0

115 changed files with 5805 additions and 7089 deletions

View File

@ -3,7 +3,7 @@
"prettier --cache --ignore-unknown --write",
"eslint --cache --fix"
],
"{!(package)*.json,*.code-snippets,.!({browserslist,npm,nvm})*rc}": [
"{!(package)*.json,*.code-snippets,.!({browserslist,nvm})*rc}": [
"prettier --cache --write--parser json"
],
"package.json": ["prettier --cache --write"],

3
.npmrc
View File

@ -1,4 +1,3 @@
shell-emulator=true
shamefully-hoist=true
enable-pre-post-scripts=false
strict-peer-dependencies=false
shell-emulator=true

2
.nvmrc
View File

@ -1 +1 @@
v22.14.0
v20.12.2

View File

@ -1,7 +1,6 @@
{
"recommendations": [
"christian-kohler.path-intellisense",
"warmthsea.vscode-custom-code-color",
"vscode-icons-team.vscode-icons",
"davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker",

14
.vscode/settings.json vendored
View File

@ -27,17 +27,5 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"iconify.excludes": [
"el"
],
"vscodeCustomCodeColor.highlightValue": [
"v-loading",
"v-auth",
"v-copy",
"v-longpress",
"v-optimize",
"v-perms",
"v-ripple"
],
"vscodeCustomCodeColor.highlightValueColor": "#b392f0",
"iconify.excludes": ["el"]
}

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

@ -8,14 +8,6 @@
The simplified version is based on the shelf extracted from [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin), which contains main functions and is more suitable for actual project development. The packaged size is introduced globally [element-plus](https://element-plus.org) is still below `2.3MB`, and the full version of the code will be permanently synchronized. After enabling `brotli` compression and `cdn` to replace the local library mode, the package size is less than `350kb`
## `js` version
[Click me to view js version](https://pure-admin.cn/pages/js/)
## `max` version
[Click me to view the max version](https://pure-admin.cn/pages/max/)
## Supporting video
[Click me to view UI design](https://www.bilibili.com/video/BV17g411T7rq)
@ -23,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)
## 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

@ -12,14 +12,6 @@
当前是非国际化版本,如果您需要国际化版本 [请点击](https://github.com/pure-admin/pure-admin-thin/tree/i18n)
## `js` 版本
[点我查看 js 版本](https://pure-admin.cn/pages/js/)
## `max` 版本
[点我查看 max 版本](https://pure-admin.cn/pages/max/)
## 配套视频
[点我查看 UI 设计](https://www.bilibili.com/video/BV17g411T7rq)
@ -27,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

@ -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

@ -22,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

@ -2,15 +2,14 @@ import { cdn } from "./cdn";
import vue from "@vitejs/plugin-vue";
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 { configCompressPlugin } from "./compress";
import removeNoMatch from "vite-plugin-router-warn";
import { visualizer } from "rollup-plugin-visualizer";
import removeConsole from "vite-plugin-remove-console";
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(
@ -19,20 +18,9 @@ export function getPluginsList(
): PluginOption[] {
const lifecycle = process.env.npm_lifecycle_event;
return [
tailwindcss(),
vue(),
// jsx、tsx语法支持
vueJsx(),
/**
* DOM IDE
* Mac Option + Shift
* Windows Alt + Shift
* https://inspector.fe-dev.cn/guide/start.html
*/
codeInspectorPlugin({
bundler: "vite",
hideConsole: true
}),
viteBuildInfo(),
/**
* vue-router动态路由警告No match found for location with path
@ -47,13 +35,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

@ -23,34 +23,17 @@ const permissionRouter = {
}
},
{
path: "/permission/button",
path: "/permission/button/index",
name: "PermissionButton",
meta: {
title: "按钮权限",
roles: ["admin", "common"]
},
children: [
{
path: "/permission/button/router",
component: "permission/button/index",
name: "PermissionButtonRouter",
meta: {
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: "登录接口返回按钮权限"
}
}
]
}
]
};

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.0.0",
"version": "5.5.0",
"private": true,
"type": "module",
"scripts": {
@ -22,7 +22,6 @@
"preinstall": "npx only-allow pnpm"
},
"keywords": [
"pure-admin-thin",
"vue-pure-admin",
"element-plus",
"tailwindcss",
@ -49,111 +48,104 @@
},
"dependencies": {
"@pureadmin/descriptions": "^1.2.1",
"@pureadmin/table": "^3.2.1",
"@pureadmin/utils": "^2.6.0",
"@vueuse/core": "^13.1.0",
"@vueuse/motion": "^3.0.3",
"@pureadmin/table": "^3.1.2",
"@pureadmin/utils": "^2.4.7",
"@vueuse/core": "^10.9.0",
"@vueuse/motion": "^2.1.0",
"animate.css": "^4.1.1",
"axios": "^1.9.0",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"element-plus": "^2.9.8",
"axios": "^1.6.8",
"dayjs": "^1.11.11",
"echarts": "^5.5.0",
"element-plus": "2.7.1",
"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.2",
"pinyin-pro": "^3.26.0",
"qs": "^6.14.0",
"path": "^0.12.7",
"pinia": "^2.1.7",
"pinyin-pro": "^3.20.4",
"qs": "^6.12.1",
"responsive-storage": "^2.2.0",
"sortablejs": "^1.15.6",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"vue-tippy": "^6.7.0",
"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": "^19.8.0",
"@commitlint/config-conventional": "^19.8.0",
"@commitlint/types": "^19.8.0",
"@eslint/js": "^9.25.1",
"@faker-js/faker": "^9.7.0",
"@iconify/json": "^2.2.331",
"@iconify/vue": "4.2.0",
"@tailwindcss/vite": "^4.1.4",
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@commitlint/types": "^19.0.3",
"@eslint/js": "^9.2.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.17.30",
"@types/node": "^20.12.11",
"@types/nprogress": "^0.2.3",
"@types/path-browserify": "^1.0.3",
"@types/qs": "^6.9.18",
"@types/qs": "^6.9.15",
"@types/sortablejs": "^1.15.8",
"@vitejs/plugin-vue": "^5.2.3",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"boxen": "^8.0.1",
"code-inspector-plugin": "^0.20.10",
"cssnano": "^7.0.6",
"eslint": "^9.25.1",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-vue": "^10.0.0",
"gradient-string": "^3.0.0",
"husky": "^9.1.7",
"lint-staged": "^15.5.1",
"postcss": "^8.5.3",
"postcss-html": "^1.8.0",
"postcss-load-config": "^6.0.1",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"autoprefixer": "^10.4.19",
"boxen": "^7.1.1",
"cssnano": "^7.0.1",
"eslint": "^9.2.0",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.25.0",
"gradient-string": "^2.0.2",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"postcss": "^8.4.38",
"postcss-html": "^1.7.0",
"postcss-import": "^16.1.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.5.3",
"rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "^1.87.0",
"stylelint": "^16.19.0",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended-vue": "^1.6.0",
"stylelint-config-standard-scss": "^14.0.0",
"stylelint-prettier": "^5.0.3",
"svgo": "^3.3.2",
"tailwindcss": "^4.1.4",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.0",
"unplugin-icons": "^22.1.0",
"vite": "^6.3.3",
"vite-plugin-cdn-import": "^1.0.1",
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.77.0",
"stylelint": "^16.5.0",
"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.0",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.5",
"vite": "^5.2.11",
"vite-plugin-cdn-import": "^0.3.5",
"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-svg-loader": "^5.1.0",
"vue-eslint-parser": "^10.1.3",
"vue-tsc": "^2.2.10"
"vue-eslint-parser": "^9.4.2",
"vue-tsc": "^1.8.27"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=22.0.0",
"pnpm": ">=9"
"node": "^18.18.0 || ^20.9.0 || >=21.1.0",
"pnpm": ">=8.6.10"
},
"packageManager": "pnpm@8.6.10",
"pnpm": {
"allowedDeprecatedVersions": {
"are-we-there-yet": "*",
"sourcemap-codec": "*",
"lodash.isequal": "*",
"domexception": "*",
"w3c-hr-time": "*",
"inflight": "*",
"npmlog": "*",
"rimraf": "*",
"stable": "*",
"gauge": "*",
"abab": "*",
"glob": "*"
"abab": "*"
},
"onlyBuiltDependencies": [
"@parcel/watcher",
"core-js",
"es5-ext",
"esbuild",
"typeit",
"vue-demi"
]
"peerDependencyRules": {
"allowedVersions": {
"eslint": "9"
}
}
}
}

10837
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,5 +1,5 @@
{
"Version": "6.0.0",
"Version": "5.5.0",
"Title": "PureAdmin",
"FixedHeader": true,
"HiddenSideBar": false,

View File

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

View File

@ -28,7 +28,8 @@
c = document.createElement("div");
(c.innerHTML = e._iconfont_svg_string_2208059),
(c = c.getElementsByTagName("svg")[0]) &&
((c.style.position = "absolute"),
(c.setAttribute("aria-hidden", "true"),
(c.style.position = "absolute"),
(c.style.width = 0),
(c.style.height = 0),
(c.style.overflow = "hidden"),

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 +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

@ -17,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";
@ -53,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);
@ -86,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"
];
});
@ -116,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);
@ -247,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()
@ -312,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}
@ -342,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;
@ -372,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,7 +2,7 @@ import { hasAuth } from "@/router/utils";
import type { Directive, DirectiveBinding } from "vue";
export const auth: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding<string | Array<string>>) {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
if (value) {
!hasAuth(value) && el.parentNode?.removeChild(el);

View File

@ -3,13 +3,13 @@ import { useEventListener } from "@vueuse/core";
import { copyTextToClipboard } from "@pureadmin/utils";
import type { Directive, DirectiveBinding } from "vue";
export interface CopyEl extends HTMLElement {
interface CopyEl extends HTMLElement {
copyValue: string;
}
/** 文本复制指令(默认双击复制) */
export const copy: Directive = {
mounted(el: CopyEl, binding: DirectiveBinding<string>) {
mounted(el: CopyEl, binding: DirectiveBinding) {
const { value } = binding;
if (value) {
el.copyValue = value;

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

@ -3,7 +3,7 @@ import type { Directive, DirectiveBinding } from "vue";
import { subBefore, subAfter, isFunction } from "@pureadmin/utils";
export const longpress: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding<Function>) {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const cb = binding.value;
if (cb && isFunction(cb)) {
let timer = null;

View File

@ -8,22 +8,9 @@ import {
import { useEventListener } from "@vueuse/core";
import type { Directive, DirectiveBinding } from "vue";
export interface OptimizeOptions {
/** 事件名 */
event: string;
/** 事件触发的方法 */
fn: (...params: any) => any;
/** 是否立即执行 */
immediate?: boolean;
/** 防抖或节流的延迟时间(防抖默认:`200`毫秒、节流默认:`1000`毫秒) */
timeout?: number;
/** 传递的参数 */
params?: any;
}
/** 防抖v-optimize或v-optimize:debounce、节流v-optimize:throttle指令 */
export const optimize: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding<OptimizeOptions>) {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
const optimizeType = binding.arg ?? "debounce";
const type = ["debounce", "throttle"].find(t => t === optimizeType);

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

@ -2,10 +2,8 @@ import "./index.scss";
import { isObject } from "@pureadmin/utils";
import type { Directive, DirectiveBinding } from "vue";
export interface RippleOptions {
/** 自定义`ripple`颜色,支持`tailwindcss` */
interface RippleOptions {
class?: string;
/** 是否从中心扩散 */
center?: boolean;
circle?: boolean;
}
@ -32,8 +30,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;
@ -222,6 +220,13 @@ function updated(el: HTMLElement, binding: RippleDirectiveBinding) {
updateRipple(el, binding, wasEnabled);
}
/**
* @description v-ripple
* @use
* 1. v-ripple ripple
* 2. v-ripple="{ class: 'text-red' }" ripple tailwindcss color
* 3. v-ripple.center
*/
export const Ripple: Directive = {
mounted,
unmounted,

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import LayFrame from "../lay-frame/index.vue";
import LayFooter from "../lay-footer/index.vue";
import { useTags } from "@/layout/hooks/useTag";
import { useGlobal, isNumber } from "@pureadmin/utils";
import BackTopIcon from "@/assets/svg/back_top.svg?component";
import { h, computed, Transition, defineComponent } from "vue";
@ -11,7 +10,6 @@ const props = defineProps({
fixedHeader: Boolean
});
const { showModel } = useTags();
const { $storage, $config } = useGlobal<GlobalPropertiesApi>();
const isKeepAlive = computed(() => {
@ -51,17 +49,9 @@ const getMainWidth = computed(() => {
const getSectionStyle = computed(() => {
return [
hideTabs.value && layout ? "padding-top: 48px;" : "",
!hideTabs.value && layout
? showModel.value == "chrome"
? "padding-top: 85px;"
: "padding-top: 81px;"
: "",
!hideTabs.value && layout ? "padding-top: 81px;" : "",
hideTabs.value && !layout.value ? "padding-top: 48px;" : "",
!hideTabs.value && !layout.value
? showModel.value == "chrome"
? "padding-top: 85px;"
: "padding-top: 81px;"
: "",
!hideTabs.value && !layout.value ? "padding-top: 81px;" : "",
props.fixedHeader
? ""
: `padding-top: 0;${

View File

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { getConfig } from "@/config";
const TITLE = getConfig("Title");

View File

@ -7,8 +7,8 @@ import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vu
import LaySidebarBreadCrumb from "../lay-sidebar/components/SidebarBreadCrumb.vue";
import LaySidebarTopCollapse from "../lay-sidebar/components/SidebarTopCollapse.vue";
import LogoutCircleRLine from "~icons/ri/logout-circle-r-line";
import Setting from "~icons/ri/settings-3-line";
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
import Setting from "@iconify-icons/ri/settings-3-line";
const {
layout,
@ -24,7 +24,7 @@ const {
</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"

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,7 @@
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 noticesNum = ref(0);
const notices = ref(noticesData);

View File

@ -3,7 +3,7 @@ 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);
@ -15,7 +15,7 @@ const iconClass = computed(() => {
"flex",
"justify-center",
"items-center",
"outline-hidden",
"outline-none",
"rounded-[4px]",
"cursor-pointer",
"transition-colors",
@ -49,7 +49,7 @@ 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">系统配置</h4>
<span
@ -74,7 +74,7 @@ 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="{
@ -117,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

@ -2,10 +2,10 @@
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
});

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { optionsItem } from "../types";
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;

View File

@ -11,7 +11,7 @@ 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 {
/** 弹窗显隐 */

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

@ -13,14 +13,15 @@ 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";
@ -47,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);
}
@ -187,7 +190,7 @@ 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>>(() => {
@ -227,11 +230,6 @@ const markOptions = computed<Array<OptionsType>>(() => {
label: "卡片",
tip: "卡片标签,高效浏览",
value: "card"
},
{
label: "谷歌",
tip: "谷歌风格,经典美观",
value: "chrome"
}
];
});
@ -333,7 +331,7 @@ onUnmounted(() => removeMatchMedia);
"
/>
<p :class="['mt-5!', pClass]">主题色</p>
<p :class="['mt-5', pClass]">主题色</p>
<ul class="theme-color">
<li
v-for="(item, index) in themeColors"
@ -352,7 +350,7 @@ onUnmounted(() => removeMatchMedia);
</li>
</ul>
<p :class="['mt-5!', pClass]">导航模式</p>
<p :class="['mt-5', pClass]">导航模式</p>
<ul class="pure-theme">
<li
ref="verticalRef"
@ -395,7 +393,7 @@ onUnmounted(() => removeMatchMedia);
</ul>
<span v-if="useAppStoreHook().getViewportWidth > 1280">
<p :class="['mt-5!', pClass]">页宽</p>
<p :class="['mt-5', pClass]">页宽</p>
<Segmented
resize
class="mb-2 select-none"
@ -424,28 +422,30 @@ 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]">页签风格</p>
<p :class="['mt-4', pClass]">页签风格</p>
<Segmented
resize
class="select-none"
:modelValue="markValue === 'smart' ? 0 : markValue === 'card' ? 1 : 2"
:modelValue="markValue === 'smart' ? 0 : 1"
:options="markOptions"
@change="onChange"
/>
<p class="mt-5! font-medium text-sm dark:text-white">界面显示</p>
<p class="mt-5 font-medium text-sm dark:text-white">界面显示</p>
<ul class="setting">
<li>
<span class="dark:text-white">灰色模式</span>

View File

@ -1,24 +1,17 @@
<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 { usePermissionStoreHook } from "@/store/modules/permission";
import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
import LogoutCircleRLine from "~icons/ri/logout-circle-r-line";
import Setting from "~icons/ri/settings-3-line";
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 {
route,
@ -39,12 +32,6 @@ const defaultActive = computed(() =>
nextTick(() => {
menuRef.value?.handleResize();
});
onMounted(() => {
emitter.on("logoChange", key => {
showLogo.value = key;
});
});
</script>
<template>
@ -52,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>

View File

@ -10,8 +10,8 @@ import { usePermissionStoreHook } from "@/store/modules/permission";
import LaySidebarExtraIcon from "../lay-sidebar/components/SidebarExtraIcon.vue";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
import LogoutCircleRLine from "~icons/ri/logout-circle-r-line";
import Setting from "~icons/ri/settings-3-line";
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);

View File

@ -104,12 +104,12 @@ 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)">
{{ item.meta.title }}

View File

@ -3,10 +3,10 @@ import { computed } from "vue";
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>(), {

View File

@ -1,6 +1,6 @@
<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";
@ -16,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();
@ -60,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 {
@ -113,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.resolvepath.resolve windows使electron
return path.posix.resolve(props.basePath, routePath);
}
}
</script>
@ -158,7 +144,7 @@ function resolvePath(routePath) {
item?.pathList?.length === 2)
"
truncated
class="w-full! px-3! min-w-[54px]! text-center! text-inherit!"
class="!w-full !px-4 !text-inherit"
>
{{ onlyOneChild.meta.title }}
</el-text>
@ -170,7 +156,7 @@ function resolvePath(routePath) {
offset: [0, -10],
theme: tooltipEffect
}"
class="w-full! text-inherit!"
class="!w-full !text-inherit"
>
{{ onlyOneChild.meta.title }}
</ReText>
@ -209,7 +195,15 @@ function resolvePath(routePath) {
offset: [0, -10],
theme: tooltipEffect
}"
:class="textClass"
:class="{
'!w-full': true,
'!text-inherit': true,
'!px-4':
layout !== 'horizontal' &&
isCollapse &&
!toRaw(item.meta.icon) &&
item.parentId === null
}"
>
{{ item.meta.title }}
</ReText>

View File

@ -3,10 +3,10 @@ import { computed } from "vue";
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>(), {
@ -21,7 +21,7 @@ const iconClass = computed(() => {
"mb-1",
"w-[16px]",
"h-[16px]",
"inline-block!",
"inline-block",
"align-middle",
"cursor-pointer",
"duration-[100ms]"

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,9 +1,9 @@
<script setup lang="ts">
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>(), {
@ -27,7 +27,7 @@ const 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

@ -1,33 +0,0 @@
<template>
<svg class="w-full h-full">
<defs>
<symbol id="geometry-left" viewBox="0 0 214 36">
<path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z" />
</symbol>
<symbol id="geometry-right" viewBox="0 0 214 36">
<use xlink:href="#geometry-left" />
</symbol>
<clipPath>
<rect width="100%" height="100%" x="0" />
</clipPath>
</defs>
<svg width="51%" height="100%">
<use
xlink:href="#geometry-left"
width="214"
height="36"
fill="currentColor"
/>
</svg>
<g transform="scale(-1, 1)">
<svg width="51%" height="100%" x="-100%" y="0">
<use
xlink:href="#geometry-right"
width="214"
height="36"
fill="currentColor"
/>
</svg>
</g>
</svg>
</template>

View File

@ -41,13 +41,6 @@
padding-right: 24px;
}
&.chrome-item {
padding-right: 0;
padding-left: 0;
margin-right: -18px;
box-shadow: none;
}
.el-icon-close {
position: absolute;
top: 50%;
@ -59,10 +52,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;
@ -83,14 +76,6 @@
overflow: hidden;
white-space: nowrap;
&.chrome-scroll-container {
padding-top: 4px;
.fixed-tag {
padding: 0 !important;
}
}
.tab {
position: relative;
float: left;
@ -104,12 +89,6 @@
&:nth-child(1) {
padding: 0 12px;
}
&.chrome-item {
&:nth-child(1) {
padding: 0;
}
}
}
.fixed-tag {
@ -127,10 +106,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 {
@ -194,29 +173,9 @@
color: #fff;
box-shadow: 0 0 0.7px #888;
.chrome-tab {
z-index: 10;
}
.chrome-tab__bg {
color: var(--el-color-primary-light-9) !important;
}
.tag-title {
color: var(--el-color-primary) !important;
}
.chrome-close-btn {
color: var(--el-color-primary);
&:hover {
background-color: var(--el-color-primary);
}
}
.chrome-tab-divider {
opacity: 0;
}
}
.arrow-left,
@ -303,69 +262,3 @@
background: var(--el-color-primary);
animation: schedule-out-width 200ms ease-in;
}
/* 谷歌风格的页签 */
.chrome-tab {
position: relative;
display: inline-flex;
gap: 16px;
align-items: center;
justify-content: center;
padding: 0 24px;
white-space: nowrap;
cursor: pointer;
.tag-title {
padding: 0;
}
.chrome-tab-divider {
position: absolute;
right: 7px;
width: 1px;
height: 14px;
background-color: #2b2d2f;
}
&:hover {
z-index: 10;
.chrome-tab__bg {
color: #dee1e6;
}
.tag-title {
color: #1f1f1f;
}
.chrome-tab-divider {
opacity: 0;
}
}
.chrome-tab__bg {
position: absolute;
top: 0;
left: 0;
z-index: -10;
width: 100%;
height: 100%;
color: transparent;
pointer-events: none;
}
.chrome-close-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
color: #666;
border-radius: 50%;
&:hover {
color: white;
background-color: #b1b3b8;
}
}
}

View File

@ -4,7 +4,6 @@ import { RouteConfigs } from "../../types";
import { useTags } from "../../hooks/useTag";
import { routerArrays } from "@/layout/types";
import { onClickOutside } from "@vueuse/core";
import TagChrome from "./components/TagChrome.vue";
import { handleAliveRoute, getTopMenu } from "@/router/utils";
import { useSettingStoreHook } from "@/store/modules/settings";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
@ -17,11 +16,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,
@ -36,7 +35,6 @@ const {
buttonLeft,
showModel,
translateX,
isFixedTag,
pureSetting,
activeIndex,
getTabStyle,
@ -509,7 +507,6 @@ function tagOnClick(item) {
} else {
router.push({ path });
}
emitter.emit("tagOnClick", item);
}
onClickOutside(contextmenuRef, closeMenu, {
@ -566,7 +563,6 @@ onBeforeUnmount(() => {
<div
ref="scrollbarDom"
class="scroll-container"
:class="showModel === 'chrome' && 'chrome-scroll-container'"
@wheel.prevent="handleWheel"
>
<div ref="tabDom" class="tab select-none" :style="getTabStyle">
@ -577,26 +573,24 @@ onBeforeUnmount(() => {
:class="[
'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)"
@mouseleave.prevent="onMouseleave(index)"
@click="tagOnClick(item)"
>
<template v-if="showModel !== 'chrome'">
<span
class="tag-title dark:text-text_color_primary! dark:hover:text-primary!"
class="tag-title dark:!text-text_color_primary dark:hover:!text-primary"
>
{{ 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)"
@ -608,23 +602,6 @@ onBeforeUnmount(() => {
:ref="'schedule' + index"
:class="[scheduleIsActive(item)]"
/>
</template>
<div v-else class="chrome-tab">
<div class="chrome-tab__bg">
<TagChrome />
</div>
<span class="tag-title">
{{ item.meta.title }}
</span>
<span
v-if="isFixedTag(item) ? false : index !== 0"
class="chrome-close-btn"
@click.stop="deleteMenu(item)"
>
<IconifyIconOffline :icon="Close" />
</span>
<span class="chrome-tab-divider" />
</div>
</div>
</div>
</div>

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

@ -12,8 +12,8 @@ import { useAppStoreHook } from "@/store/modules/app";
import { useUserStoreHook } from "@/store/modules/user";
import { useGlobal, isAllEmpty } from "@pureadmin/utils";
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";

View File

@ -20,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();
@ -124,12 +124,6 @@ export function useTags() {
}
}
const isFixedTag = computed(() => {
return item => {
return isBoolean(item?.meta?.fixedTag) && item?.meta?.fixedTag === true;
};
});
const iconIsActive = computed(() => {
return (item, index) => {
if (index === 0) return;
@ -226,7 +220,6 @@ export function useTags() {
buttonTop,
buttonLeft,
translateX,
isFixedTag,
pureSetting,
activeIndex,
getTabStyle,

View File

@ -208,8 +208,8 @@ const LayHeader = defineComponent({
height: 100%;
&::after {
clear: both;
display: table;
clear: both;
content: "";
}
@ -222,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> =
@ -8,7 +8,7 @@ export const routerArrays: Array<RouteConfigs> =
path: "/welcome",
meta: {
title: "首页",
icon: "ep/home-filled"
icon: "ep:home-filled"
}
}
]
@ -16,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>;
@ -36,7 +36,7 @@ export type multiTagsType = {
};
export type tagsViewsType = {
icon: string | FunctionalComponent;
icon: string | IconifyIcon;
text: string;
divided: boolean;
disabled: boolean;

View File

@ -42,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";

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

@ -6,13 +6,7 @@ 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,
@ -26,9 +20,9 @@ import {
} from "./utils";
import {
type Router,
createRouter,
type RouteRecordRaw,
type RouteComponent,
createRouter
type RouteComponent
} from "vue-router";
import {
type DataInfo,
@ -60,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)
@ -95,13 +86,17 @@ export const router: Router = createRouter({
/** 重置路由 */
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();
}

View File

@ -2,7 +2,7 @@ export default {
path: "/error",
redirect: "/error/403",
meta: {
icon: "ri/information-line",
icon: "ri:information-line",
// showLink: false,
title: "异常页面",
rank: 9

View File

@ -7,7 +7,7 @@ export default {
component: Layout,
redirect: "/welcome",
meta: {
icon: "ep/home-filled",
icon: "ep:home-filled",
title: "首页",
rank: 0
},

View File

@ -172,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);
}
}
@ -357,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

View File

@ -12,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,
@ -30,7 +31,7 @@ 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) {

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

@ -49,22 +49,6 @@ html.dark {
background-color: rgb(255 255 255 / 12%);
}
}
.chrome-tab {
.tag-title {
color: #666;
}
&:hover {
.chrome-tab__bg {
color: #333;
}
.tag-title {
color: #adadad;
}
}
}
}
}

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,15 +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: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
}
}
.is-active {
color: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
transition: color 0.3s;
}
@ -191,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;
@ -202,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;
}
/* 有子集的激活菜单左侧小竖条 */
@ -212,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 {
@ -240,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;
@ -251,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 {
@ -269,10 +271,10 @@
.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: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
}
}
@ -280,23 +282,23 @@
.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;
}
@ -340,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;
@ -356,7 +358,7 @@
}
.el-sub-menu__title {
color: var(--pure-theme-menu-text);
color: $menuText;
}
}
@ -364,31 +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: var(--pure-theme-sub-menu-active-text) !important;
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;
}
@ -413,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;
@ -435,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;
}
}
@ -456,7 +458,7 @@
align-items: center;
justify-content: flex-end;
min-width: 340px;
color: var(--pure-theme-sub-menu-active-text);
color: $subMenuActiveText;
/* 搜索 */
.search-container,
@ -469,13 +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);
color: $subMenuActiveText;
}
.el-dropdown-link {
@ -484,7 +486,7 @@
justify-content: space-around;
height: 48px;
padding: 10px;
color: var(--pure-theme-sub-menu-active-text);
color: $subMenuActiveText;
cursor: pointer;
p {
@ -509,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;
}
}
@ -520,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;
@ -529,15 +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: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
}
}
.is-active {
color: var(--pure-theme-sub-menu-active-text) !important;
color: $subMenuActiveText !important;
transition: color 0.3s;
}
}
@ -558,7 +560,6 @@
}
.sidebar-container {
z-index: 2001;
width: $sideBarWidth;
transition: transform var(--pure-transition-duration);
}
@ -566,8 +567,8 @@
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transform: translate3d(-$sideBarWidth, 0, 0);
transition-duration: 0.3s;
transform: translate3d(-$sideBarWidth, 0, 0);
}
}
}
@ -583,7 +584,7 @@ body[layout="vertical"] {
}
.sidebar-logo-container {
background: var(--pure-theme-sidebar-logo);
background: $sidebarLogo;
}
.hideSidebar {
@ -610,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;
}
}
}
@ -653,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;
@ -678,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 {
@ -707,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];
}
}

View File

@ -1,95 +0,0 @@
/* 亮白色 */
html[data-theme="light"] {
--pure-theme-sub-menu-active-text: #000000d9;
--pure-theme-menu-bg: #fff;
--pure-theme-menu-hover: #f6f6f6;
--pure-theme-sub-menu-bg: #fff;
--pure-theme-menu-text: rgb(0 0 0 / 60%);
--pure-theme-sidebar-logo: #fff;
--pure-theme-menu-title-hover: #000;
--pure-theme-menu-active-before: #4091f7;
}
/* 道奇蓝 */
html[data-theme="default"] {
--pure-theme-sub-menu-active-text: #fff;
--pure-theme-menu-bg: #001529;
--pure-theme-menu-hover: rgb(64 145 247 / 15%);
--pure-theme-sub-menu-bg: #0f0303;
--pure-theme-menu-text: rgb(254 254 254 / 65%);
--pure-theme-sidebar-logo: #002140;
--pure-theme-menu-title-hover: #fff;
--pure-theme-menu-active-before: #4091f7;
}
/* 深紫罗兰色 */
html[data-theme="saucePurple"] {
--pure-theme-sub-menu-active-text: #fff;
--pure-theme-menu-bg: #130824;
--pure-theme-menu-hover: rgb(105 58 201 / 15%);
--pure-theme-sub-menu-bg: #000;
--pure-theme-menu-text: #7a80b4;
--pure-theme-sidebar-logo: #1f0c38;
--pure-theme-menu-title-hover: #fff;
--pure-theme-menu-active-before: #693ac9;
}
/* 深粉色 */
html[data-theme="pink"] {
--pure-theme-sub-menu-active-text: #fff;
--pure-theme-menu-bg: #28081a;
--pure-theme-menu-hover: rgb(216 68 147 / 15%);
--pure-theme-sub-menu-bg: #000;
--pure-theme-menu-text: #7a80b4;
--pure-theme-sidebar-logo: #3f0d29;
--pure-theme-menu-title-hover: #fff;
--pure-theme-menu-active-before: #d84493;
}
/* 猩红色 */
html[data-theme="dusk"] {
--pure-theme-sub-menu-active-text: #fff;
--pure-theme-menu-bg: #2a0608;
--pure-theme-menu-hover: rgb(225 60 57 / 15%);
--pure-theme-sub-menu-bg: #000;
--pure-theme-menu-text: rgb(254 254 254 / 65.1%);
--pure-theme-sidebar-logo: #42090c;
--pure-theme-menu-title-hover: #fff;
--pure-theme-menu-active-before: #e13c39;
}
/* 橙红色 */
html[data-theme="volcano"] {
--pure-theme-sub-menu-active-text: #fff;
--pure-theme-menu-bg: #2b0e05;
--pure-theme-menu-hover: rgb(232 95 51 / 15%);
--pure-theme-sub-menu-bg: #0f0603;
--pure-theme-menu-text: rgb(254 254 254 / 65%);
--pure-theme-sidebar-logo: #441708;
--pure-theme-menu-title-hover: #fff;
--pure-theme-menu-active-before: #e85f33;
}
/* 绿宝石 */
html[data-theme="mingQing"] {
--pure-theme-sub-menu-active-text: #fff;
--pure-theme-menu-bg: #032121;
--pure-theme-menu-hover: rgb(89 191 193 / 15%);
--pure-theme-sub-menu-bg: #000;
--pure-theme-menu-text: #7a80b4;
--pure-theme-sidebar-logo: #053434;
--pure-theme-menu-title-hover: #fff;
--pure-theme-menu-active-before: #59bfc1;
}
/* 酸橙绿 */
html[data-theme="auroraGreen"] {
--pure-theme-sub-menu-active-text: #fff;
--pure-theme-menu-bg: #0b1e15;
--pure-theme-menu-hover: rgb(96 172 128 / 15%);
--pure-theme-sub-menu-bg: #000;
--pure-theme-menu-text: #7a80b4;
--pure-theme-sidebar-logo: #112f21;
--pure-theme-menu-title-hover: #fff;
--pure-theme-menu-active-before: #60ac80;
}

View File

@ -1,6 +1,6 @@
import Cookies from "js-cookie";
import { storageLocal } from "@pureadmin/utils";
import { useUserStoreHook } from "@/store/modules/user";
import { storageLocal, isString, isIncludeAllChildren } from "@pureadmin/utils";
export interface DataInfo<T> {
/** token */
@ -17,8 +17,6 @@ export interface DataInfo<T> {
nickname?: string;
/** 当前登录用户的角色 */
roles?: Array<string>;
/** 当前登录用户的按钮级别权限 */
permissions?: Array<string>;
}
export const userKey = "user-info";
@ -43,7 +41,7 @@ export function getToken(): DataInfo<number> {
* @description `token``token`
* `accessToken`访使`token``refreshToken``accessToken``token``refreshToken`30`accessToken`2`expires``accessToken`
* `accessToken``expires``refreshToken`key值为authorized-token的cookie里
* `avatar``username``nickname``roles``permissions``refreshToken``expires`key值为`user-info`localStorage里`multipleTabsKey`
* `avatar``username``nickname``roles``refreshToken``expires`key值为`user-info`localStorage里`multipleTabsKey`
*/
export function setToken(data: DataInfo<Date>) {
let expires = 0;
@ -68,20 +66,18 @@ export function setToken(data: DataInfo<Date>) {
: {}
);
function setUserKey({ avatar, username, nickname, roles, permissions }) {
function setUserKey({ avatar, username, nickname, roles }) {
useUserStoreHook().SET_AVATAR(avatar);
useUserStoreHook().SET_USERNAME(username);
useUserStoreHook().SET_NICKNAME(nickname);
useUserStoreHook().SET_ROLES(roles);
useUserStoreHook().SET_PERMS(permissions);
storageLocal().setItem(userKey, {
refreshToken,
expires,
avatar,
username,
nickname,
roles,
permissions
roles
});
}
@ -91,8 +87,7 @@ export function setToken(data: DataInfo<Date>) {
avatar: data?.avatar ?? "",
username,
nickname: data?.nickname ?? "",
roles,
permissions: data?.permissions ?? []
roles
});
} else {
const avatar =
@ -103,14 +98,11 @@ export function setToken(data: DataInfo<Date>) {
storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "";
const roles =
storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [];
const permissions =
storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? [];
setUserKey({
avatar,
username,
nickname,
roles,
permissions
roles
});
}
}
@ -126,16 +118,3 @@ export function removeToken() {
export const formatToken = (token: string): string => {
return "Bearer " + token;
};
/** 是否有按钮级别的权限(根据登录接口返回的`permissions`字段进行判断)*/
export const hasPerms = (value: string | Array<string>): boolean => {
if (!value) return false;
const allPerms = "*:*:*";
const { permissions } = useUserStoreHook();
if (!permissions) return false;
if (permissions.length === 1 && permissions[0] === allPerms) return true;
const isAuths = isString(value)
? permissions.includes(value)
: isIncludeAllChildren(value, permissions);
return isAuths ? true : false;
};

View File

@ -36,12 +36,12 @@ export default class PureHttp {
): Promise<T>;
post<T, P>(
url: string,
params?: P,
params?: T,
config?: PureHttpRequestConfig
): Promise<T>;
): Promise<P>;
get<T, P>(
url: string,
params?: P,
params?: T,
config?: PureHttpRequestConfig
): Promise<T>;
): Promise<P>;
}

View File

@ -8,8 +8,6 @@ type messageTypes = "info" | "success" | "warning" | "error";
interface MessageParams {
/** 消息类型,可选 `info` 、`success` 、`warning` 、`error` ,默认 `info` */
type?: messageTypes;
/** 是否纯色,默认 `false` */
plain?: boolean;
/** 自定义图标,该属性会覆盖 `type` 的图标 */
icon?: any;
/** 是否将 `message` 属性作为 `HTML` 片段处理,默认 `false` */
@ -20,14 +18,14 @@ interface MessageParams {
duration?: number;
/** 是否显示关闭按钮,默认值 `false` */
showClose?: boolean;
/** `Message` 距离窗口顶部的偏移量,默认 `16` */
/** 文字是否居中,默认值 `false` */
center?: boolean;
/** `Message` 距离窗口顶部的偏移量,默认 `20` */
offset?: number;
/** 设置组件的根元素,默认 `document.body` */
appendTo?: string | HTMLElement;
/** 合并内容相同的消息,不支持 `VNode` 类型的消息,默认值 `false` */
grouping?: boolean;
/** 重复次数,类似于 `Badge` 。当和 `grouping` 属性一起使用时作为初始数量使用,默认值 `1` */
repeatNum?: number;
/** 关闭时的回调函数, 参数为被关闭的 `message` 实例 */
onClose?: Function | null;
}
@ -50,30 +48,28 @@ const message = (
const {
icon,
type = "info",
plain = false,
dangerouslyUseHTMLString = false,
customClass = "antd",
duration = 2000,
showClose = false,
offset = 16,
center = false,
offset = 20,
appendTo = document.body,
grouping = false,
repeatNum = 1,
onClose
} = params;
return ElMessage({
message,
icon,
type,
plain,
icon,
dangerouslyUseHTMLString,
duration,
showClose,
center,
offset,
appendTo,
grouping,
repeatNum,
// 全局搜 pure-message 即可知道该类的样式位置
customClass: customClass === "antd" ? "pure-message" : "",
onClose: () => (isFunction(onClose) ? onClose() : null)

View File

@ -4,11 +4,10 @@ import mitt from "mitt";
/** 全局公共事件需要在此处添加类型 */
type Events = {
openPanel: string;
tagOnClick: string;
logoChange: boolean;
tagViewsChange: string;
changLayoutRoute: string;
tagViewsShowModel: string;
logoChange: boolean;
changLayoutRoute: string;
};
export const emitter: Emitter<Events> = mitt<Events>();

View File

@ -9,7 +9,6 @@ const Print = function (dom, options?: object): PrintFunction {
options = options || {};
// @ts-expect-error
if (!(this instanceof Print)) return new Print(dom, options);
// @ts-expect-error
this.conf = {
styleStr: "",
// Elements that need to dynamically get and set the height
@ -19,26 +18,19 @@ const Print = function (dom, options?: object): PrintFunction {
// Callback after printing
printDoneCallBack: null
};
// @ts-expect-error
for (const key in this.conf) {
if (key && options.hasOwnProperty(key)) {
// @ts-expect-error
this.conf[key] = options[key];
}
}
if (typeof dom === "string") {
// @ts-expect-error
this.dom = document.querySelector(dom);
} else {
// @ts-expect-error
this.dom = this.isDOM(dom) ? dom : dom.$el;
}
// @ts-expect-error
if (this.conf.setDomHeightArr && this.conf.setDomHeightArr.length) {
// @ts-expect-error
this.setDomHeight(this.conf.setDomHeightArr);
}
// @ts-expect-error
this.init();
};
@ -140,10 +132,8 @@ Print.prototype = {
"position:absolute;width:0;height:0;top:-10px;left:-10px;"
);
// eslint-disable-next-line prefer-const
w = f.contentWindow || f.contentDocument;
// eslint-disable-next-line prefer-const
doc = f.contentDocument || f.contentWindow.document;
doc.open();
doc.write(content);
@ -182,7 +172,7 @@ Print.prototype = {
if (!frameWindow.document.execCommand("print", false, null)) {
frameWindow.print();
}
} catch {
} catch (e) {
frameWindow.print();
}
frameWindow.close();

View File

@ -15,7 +15,7 @@ const router = useRouter();
<div class="ml-12">
<p
v-motion
class="font-medium text-4xl mb-4! dark:text-white"
class="font-medium text-4xl mb-4 dark:text-white"
:initial="{
opacity: 0,
y: 100
@ -32,7 +32,7 @@ const router = useRouter();
</p>
<p
v-motion
class="mb-4! text-gray-500"
class="mb-4 text-gray-500"
:initial="{
opacity: 0,
y: 100

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