Merge remote-tracking branch 'origin/main' into gitee

This commit is contained in:
xiaoxian521 2024-01-02 19:52:43 +08:00
commit 4fb186424c
162 changed files with 4345 additions and 4437 deletions

View File

@ -3,11 +3,11 @@ dist
*.d.ts *.d.ts
/src/assets /src/assets
package.json package.json
.eslintrc.js eslint.config.js
.prettierrc.js .prettierrc.js
commitlint.config.js commitlint.config.js
postcss.config.js postcss.config.js
tailwind.config.js tailwind.config.ts
stylelint.config.js stylelint.config.js
src/components/ReSplitPane/iconfont src/components/ReSplitPane/iconfont
src/components/ReFlowChart/src/assets/iconfont src/components/ReFlowChart/src/assets/iconfont

View File

@ -1,120 +0,0 @@
module.exports = {
root: true,
env: {
node: true
},
globals: {
// Ref sugar (take 2)
$: "readonly",
$$: "readonly",
$ref: "readonly",
$shallowRef: "readonly",
$computed: "readonly",
// index.d.ts
// global.d.ts
Fn: "readonly",
PromiseFn: "readonly",
RefType: "readonly",
LabelValueOptions: "readonly",
EmitType: "readonly",
TargetContext: "readonly",
ComponentElRef: "readonly",
ComponentRef: "readonly",
ElRef: "readonly",
global: "readonly",
ForDataType: "readonly",
ComponentRoutes: "readonly",
// script setup
defineProps: "readonly",
defineEmits: "readonly",
defineExpose: "readonly",
withDefaults: "readonly"
},
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/eslint-config-typescript"
],
parser: "vue-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
ecmaVersion: 2020,
sourceType: "module",
jsxPragma: "React",
ecmaFeatures: {
jsx: true
}
},
overrides: [
{
files: ["*.ts", "*.vue"],
rules: {
"no-undef": "off"
}
},
{
files: ["*.vue"],
parser: "vue-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
extraFileExtensions: [".vue"],
ecmaVersion: "latest",
ecmaFeatures: {
jsx: true
}
},
rules: {
"no-undef": "off"
}
}
],
rules: {
"vue/no-v-html": "off",
"vue/require-default-prop": "off",
"vue/require-explicit-emits": "off",
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-explicit-any": "off", // any
"no-debugger": "off",
"@typescript-eslint/explicit-module-boundary-types": "off", // setup()
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"vue/html-self-closing": [
"error",
{
html: {
void: "always",
normal: "always",
component: "always"
},
svg: "always",
math: "always"
}
],
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
],
"no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
],
"prettier/prettier": [
"error",
{
endOfLine: "auto"
}
]
}
}

1
.gitignore vendored
View File

@ -3,6 +3,7 @@ node_modules
*.local *.local
.eslintcache .eslintcache
report.html report.html
vite.config.*.timestamp*
yarn.lock yarn.lock
npm-debug.log* npm-debug.log*

View File

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

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v20.10.0

View File

@ -1,4 +1,7 @@
module.exports = { // @ts-check
/** @type {import("prettier").Config} */
export default {
bracketSpacing: true, bracketSpacing: true,
singleQuote: false, singleQuote: false,
arrowParens: "avoid", arrowParens: "avoid",

View File

@ -25,7 +25,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": "explicit"
}, },
"i18n-ally.localesPaths": "locales", "i18n-ally.localesPaths": "locales",
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",

View File

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

View File

@ -8,7 +8,7 @@
## Introduction ## Introduction
`vue-pure-admin` is an open source free and out-of-the-box middle and background management system template. Developed using the latest mainstream technologies such as `Vue3`, `Vite`, `Element-Plus`, `TypeScript`, `Pinia`, `Tailwindcss` `vue-pure-admin` is an open source, free and out-of-the-box middle and backend management system template. Completely adopts `ECMAScript` module (`ESM`) specifications to write and organize code, using the latest `Vue3`, `Vite`, `Element-Plus`, `TypeScript`, `Pinia`, `Tailwindcss` and other mainstream technologies develop
## Thin version (offering non-internationalized and internationalized versions) ## Thin version (offering non-internationalized and internationalized versions)
@ -109,7 +109,7 @@ After operating the above two commands, open `http://localhost:8080` in the brow
Of course, you can also operate the `docker` project through the [Docker Desktop](https://www.docker.com/products/docker-desktop/) visual interface, as shown below Of course, you can also operate the `docker` project through the [Docker Desktop](https://www.docker.com/products/docker-desktop/) visual interface, as shown below
<p align="center"> <p align="center">
<img alt="docker" width="100%" src="https://yiming_chang.gitee.io/pure-admin-doc/img/docker/1.jpg"> <img alt="docker" width="100%" src="https://xiaoxian521.github.io/hyperlink/img/docker-desktop.jpg">
</p> </p>
## Change Log ## Change Log
@ -162,7 +162,7 @@ Support modern browsers, not IE
## License ## License
In principle, no fees and copyrights are charged, and it is commercially available, but if you need secondary open source (such as using this platform for secondary development and open source, the front-end code must be open source and free), please contact the author for permission! (Free, just take a record) Completely free and open source
[MIT © 2020-present, pure-admin](./LICENSE) [MIT © 2020-present, pure-admin](./LICENSE)
@ -170,10 +170,10 @@ In principle, no fees and copyrights are charged, and it is commercially availab
Many thanks to the kind individuals who leave a star. Your support is much appreciated :heart: Many thanks to the kind individuals who leave a star. Your support is much appreciated :heart:
[![Stargazers for vue-pure-admin](https://reporoster.com/stars/pure-admin/vue-pure-admin)](https://github.com/pure-admin/vue-pure-admin/stargazers) [![Stargazers repo roster for @pure-admin/vue-pure-admin](https://bytecrank.com/nastyox/reporoster/php/stargazersSVG.php?user=pure-admin&repo=vue-pure-admin)](https://github.com/pure-admin/vue-pure-admin/stargazers)
## `Fork` ## `Fork`
It's so cool that you study hard :heart: It's so cool that you study hard :heart:
[![Forkers repo roster for vue-pure-admin](https://reporoster.com/forks/pure-admin/vue-pure-admin)](https://github.com/pure-admin/vue-pure-admin/network/members) [![Forkers repo roster for @pure-admin/vue-pure-admin](https://bytecrank.com/nastyox/reporoster/php/forkersSVG.php?user=pure-admin&repo=vue-pure-admin)](https://github.com/pure-admin/vue-pure-admin/network/members)

View File

@ -8,7 +8,7 @@
## 简介 ## 简介
`vue-pure-admin` 是一款开源免费且开箱即用的中后台管理系统模版。使用了最新的 `Vue3``Vite``Element-Plus``TypeScript``Pinia``Tailwindcss` 等主流技术开发 `vue-pure-admin` 是一款开源免费且开箱即用的中后台管理系统模版。完全采用 `ECMAScript` 模块(`ESM`)规范来编写和组织代码,使用了最新的 `Vue3``Vite``Element-Plus``TypeScript``Pinia``Tailwindcss` 等主流技术开发
## 精简版(实际项目开发请用精简版,提供 `非国际化``国际化` 两个版本选择) ## 精简版(实际项目开发请用精简版,提供 `非国际化``国际化` 两个版本选择)
@ -109,7 +109,7 @@ docker run -dp 8080:80 --name pure-admin vue-pure-admin
当然也可以通过 [Docker Desktop](https://www.docker.com/products/docker-desktop/) 可视化界面去操作 `docker` 项目,如下图 当然也可以通过 [Docker Desktop](https://www.docker.com/products/docker-desktop/) 可视化界面去操作 `docker` 项目,如下图
<p align="center"> <p align="center">
<img alt="docker" width="100%" src="https://yiming_chang.gitee.io/pure-admin-doc/img/docker/1.jpg"> <img alt="docker" width="100%" src="https://xiaoxian521.github.io/hyperlink/img/docker-desktop.jpg">
</p> </p>
## 更新日志 ## 更新日志
@ -162,7 +162,7 @@ docker run -dp 8080:80 --name pure-admin vue-pure-admin
## 许可证 ## 许可证
原则上不收取任何费用及版权,可商用,不过如需二次开源(比如用此平台二次开发并开源,要求前端代码必须开源免费)请联系作者获取许可!(免费,走个记录而已) 完全免费开源
[MIT © 2020-present, pure-admin](./LICENSE) [MIT © 2020-present, pure-admin](./LICENSE)
@ -170,10 +170,10 @@ docker run -dp 8080:80 --name pure-admin vue-pure-admin
非常感谢留下星星的好心人,感谢您的支持 :heart: 非常感谢留下星星的好心人,感谢您的支持 :heart:
[![Stargazers for vue-pure-admin](https://reporoster.com/stars/pure-admin/vue-pure-admin)](https://github.com/pure-admin/vue-pure-admin/stargazers) [![Stargazers repo roster for @pure-admin/vue-pure-admin](https://bytecrank.com/nastyox/reporoster/php/stargazersSVG.php?user=pure-admin&repo=vue-pure-admin)](https://github.com/pure-admin/vue-pure-admin/stargazers)
## `Fork` ## `Fork`
瞧,那些 `小哥哥``小姐姐` 认真 `学习` 的样子真滴是 `哎呦不错哦` :heart: 瞧,那些 `小哥哥``小姐姐` 认真 `学习` 的样子真滴是 `哎呦不错哦` :heart:
[![Forkers repo roster for vue-pure-admin](https://reporoster.com/forks/pure-admin/vue-pure-admin)](https://github.com/pure-admin/vue-pure-admin/network/members) [![Forkers repo roster for @pure-admin/vue-pure-admin](https://bytecrank.com/nastyox/reporoster/php/forkersSVG.php?user=pure-admin&repo=vue-pure-admin)](https://github.com/pure-admin/vue-pure-admin/network/members)

View File

@ -3,7 +3,6 @@ import { Plugin as importToCDN } from "vite-plugin-cdn-import";
/** /**
* @description `cdn`使cdn模式 .env.production VITE_CDN true * @description `cdn`使cdn模式 .env.production VITE_CDN true
* cdnhttps://www.bootcdn.cn当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com * cdnhttps://www.bootcdn.cn当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com
* mockjs不能用cdn模式引入mockjs使
* 使jscss文件cdn * 使jscss文件cdn
*/ */
export const cdn = importToCDN({ export const cdn = importToCDN({

View File

@ -1,7 +1,7 @@
import type { Plugin } from "vite"; import type { Plugin } from "vite";
import picocolors from "picocolors"; import picocolors from "picocolors";
import dayjs, { Dayjs } from "dayjs";
import { getPackageSize } from "./utils"; import { getPackageSize } from "./utils";
import dayjs, { type Dayjs } from "dayjs";
import duration from "dayjs/plugin/duration"; import duration from "dayjs/plugin/duration";
dayjs.extend(duration); dayjs.extend(duration);

View File

@ -3,36 +3,45 @@ import { resolve } from "path";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import { viteBuildInfo } from "./info"; import { viteBuildInfo } from "./info";
import svgLoader from "vite-svg-loader"; import svgLoader from "vite-svg-loader";
import type { PluginOption } from "vite";
import vueJsx from "@vitejs/plugin-vue-jsx"; import vueJsx from "@vitejs/plugin-vue-jsx";
import { viteMockServe } from "vite-plugin-mock";
import { configCompressPlugin } from "./compress"; import { configCompressPlugin } from "./compress";
import removeNoMatch from "vite-plugin-router-warn";
import { visualizer } from "rollup-plugin-visualizer"; import { visualizer } from "rollup-plugin-visualizer";
import removeConsole from "vite-plugin-remove-console"; import removeConsole from "vite-plugin-remove-console";
import { themePreprocessorPlugin } from "@pureadmin/theme"; import { themePreprocessorPlugin } from "@pureadmin/theme";
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"; import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
import { genScssMultipleScopeVars } from "../src/layout/theme"; import { genScssMultipleScopeVars } from "../src/layout/theme";
import { vitePluginFakeServer } from "vite-plugin-fake-server";
export function getPluginsList( export function getPluginsList(
command: string,
VITE_CDN: boolean, VITE_CDN: boolean,
VITE_COMPRESSION: ViteCompression VITE_COMPRESSION: ViteCompression
) { ): PluginOption[] {
const prodMock = true;
const lifecycle = process.env.npm_lifecycle_event; const lifecycle = process.env.npm_lifecycle_event;
return [ return [
vue(), vue(),
// jsx、tsx语法支持
vueJsx(),
VueI18nPlugin({ VueI18nPlugin({
runtimeOnly: true, runtimeOnly: true,
compositionOnly: true, compositionOnly: true,
include: [resolve("locales/**")] include: [resolve("locales/**")]
}), }),
// jsx、tsx语法支持
vueJsx(),
VITE_CDN ? cdn : null,
configCompressPlugin(VITE_COMPRESSION),
// 线上环境删除console
removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }),
viteBuildInfo(), viteBuildInfo(),
/**
* vue-router动态路由警告No match found for location with path
* https://github.com/vuejs/router/issues/521 和 https://github.com/vuejs/router/issues/359
* vite-plugin-router-warn只在开发环境下启用vue-router文件并且只在服务启动或重启时运行一次
*/
removeNoMatch(),
// mock支持
vitePluginFakeServer({
logger: false,
include: "mock",
infixName: false,
enableProd: true
}),
// 自定义主题 // 自定义主题
themePreprocessorPlugin({ themePreprocessorPlugin({
scss: { scss: {
@ -42,20 +51,13 @@ export function getPluginsList(
}), }),
// svg组件化支持 // svg组件化支持
svgLoader(), svgLoader(),
// mock支持 VITE_CDN ? cdn : null,
viteMockServe({ configCompressPlugin(VITE_COMPRESSION),
mockPath: "mock", // 线上环境删除console
localEnabled: command === "serve", removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }),
prodEnabled: command !== "serve" && prodMock,
injectCode: `
import { setupProdMockServer } from './mockProdServer';
setupProdMockServer();
`,
logger: false
}),
// 打包分析 // 打包分析
lifecycle === "report" lifecycle === "report"
? visualizer({ open: true, brotliSize: true, filename: "report.html" }) ? visualizer({ open: true, brotliSize: true, filename: "report.html" })
: null : (null as any)
]; ];
} }

View File

@ -1,4 +1,7 @@
module.exports = { // @ts-check
/** @type {import("@commitlint/types").UserConfig} */
export default {
ignores: [commit => commit.includes("init")], ignores: [commit => commit.includes("init")],
extends: ["@commitlint/config-conventional"], extends: ["@commitlint/config-conventional"],
rules: { rules: {

174
eslint.config.js Normal file
View File

@ -0,0 +1,174 @@
import js from "@eslint/js";
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 { defineFlatConfig } from "eslint-define-config";
import * as parserTypeScript from "@typescript-eslint/parser";
import pluginTypeScript from "@typescript-eslint/eslint-plugin";
export default defineFlatConfig([
{
...js.configs.recommended,
ignores: ["src/assets/**", "src/**/iconfont/**"],
languageOptions: {
globals: {
// index.d.ts
RefType: "readonly",
EmitType: "readonly",
TargetContext: "readonly",
ComponentRef: "readonly",
ElRef: "readonly",
ForDataType: "readonly",
AnyFunction: "readonly",
PropType: "readonly",
Writable: "readonly",
Nullable: "readonly",
NonNullable: "readonly",
Recordable: "readonly",
ReadonlyRecordable: "readonly",
Indexable: "readonly",
DeepPartial: "readonly",
Without: "readonly",
Exclusive: "readonly",
TimeoutHandle: "readonly",
IntervalHandle: "readonly",
Effect: "readonly",
ChangeEvent: "readonly",
WheelEvent: "readonly",
ImportMetaEnv: "readonly",
Fn: "readonly",
PromiseFn: "readonly",
ComponentElRef: "readonly",
parseInt: "readonly",
parseFloat: "readonly"
}
},
plugins: {
prettier: pluginPrettier
},
rules: {
...configPrettier.rules,
...pluginPrettier.configs.recommended.rules,
"no-debugger": "off",
"no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
],
"prettier/prettier": [
"error",
{
endOfLine: "auto"
}
]
}
},
{
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-import-type-side-effects": "error",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/consistent-type-imports": [
"error",
{ disallowTypeAnnotations: false, fixStyle: "inline-type-imports" }
],
"@typescript-eslint/prefer-literal-enum-member": [
"error",
{ allowBitwiseExpressions: true }
],
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
]
}
},
{
files: ["**/*.d.ts"],
rules: {
"eslint-comments/no-unlimited-disable": "off",
"import/no-duplicates": "off",
"unused-imports/no-unused-vars": "off"
}
},
{
files: ["**/*.?([cm])js"],
rules: {
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-var-requires": "off"
}
},
{
files: ["**/*.vue"],
languageOptions: {
globals: {
$: "readonly",
$$: "readonly",
$computed: "readonly",
$customRef: "readonly",
$ref: "readonly",
$shallowRef: "readonly",
$toRef: "readonly"
},
parser: parserVue,
parserOptions: {
ecmaFeatures: {
jsx: true
},
extraFileExtensions: [".vue"],
parser: "@typescript-eslint/parser",
sourceType: "module"
}
},
plugins: {
vue: pluginVue
},
processor: pluginVue.processors[".vue"],
rules: {
...pluginVue.configs.base.rules,
...pluginVue.configs["vue3-essential"].rules,
...pluginVue.configs["vue3-recommended"].rules,
"no-undef": "off",
"no-unused-vars": "off",
"vue/no-v-html": "off",
"vue/require-default-prop": "off",
"vue/require-explicit-emits": "off",
"vue/multi-word-component-names": "off",
"vue/no-setup-props-reactivity-loss": "off",
"vue/html-self-closing": [
"error",
{
html: {
void: "always",
normal: "always",
component: "always"
},
svg: "always",
math: "always"
}
]
}
}
]);

View File

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>

View File

@ -1,5 +1,5 @@
// 模拟后端动态生成路由 // 模拟后端动态生成路由
import { MockMethod } from "vite-plugin-mock"; import { defineFakeRoute } from "vite-plugin-fake-server/client";
import { system, permission, frame, tabs } from "@/router/enums"; import { system, permission, frame, tabs } from "@/router/enums";
/** /**
@ -198,7 +198,7 @@ const tabsRouter = {
] ]
}; };
export default [ export default defineFakeRoute([
{ {
url: "/get-async-routes", url: "/get-async-routes",
method: "get", method: "get",
@ -209,4 +209,4 @@ export default [
}; };
} }
} }
] as MockMethod[]; ]);

View File

@ -1,6 +1,6 @@
import { MockMethod } from "vite-plugin-mock"; import { defineFakeRoute } from "vite-plugin-fake-server/client";
export default [ export default defineFakeRoute([
{ {
url: "/get-card-list", url: "/get-card-list",
method: "post", method: "post",
@ -676,4 +676,4 @@ export default [
}; };
} }
} }
] as MockMethod[]; ]);

View File

@ -1,7 +1,7 @@
// 根据角色动态生成路由 // 根据角色动态生成路由
import { MockMethod } from "vite-plugin-mock"; import { defineFakeRoute } from "vite-plugin-fake-server/client";
export default [ export default defineFakeRoute([
{ {
url: "/login", url: "/login",
method: "post", method: "post",
@ -33,4 +33,4 @@ export default [
} }
} }
} }
] as MockMethod[]; ]);

View File

@ -1,29 +1,33 @@
import { MockMethod } from "vite-plugin-mock"; import { defineFakeRoute } from "vite-plugin-fake-server/client";
import { faker } from "@faker-js/faker/locale/zh_CN";
type mapType = { type mapType = {
plateNumber: string; plateNumber: string;
driver: string; driver: string;
"orientation|1-360": number; orientation: number;
"lng|113-114.1-10": number; lng: number;
"lat|34-35.1-10": number; lat: number;
}; };
// http://mockjs.com/examples.html#Object
const mapList = (): Array<mapType> => { const mapList = (): Array<mapType> => {
const result: Array<mapType> = []; const result: Array<mapType> = [];
for (let index = 0; index < 200; index++) { for (let index = 0; index < 200; index++) {
result.push({ result.push({
plateNumber: "豫A@natural(11111, 99999)@character('upper')", plateNumber: `豫A${faker.string.numeric({
driver: "@cname()", length: 5
"orientation|1-360": 100, })}${faker.string.alphanumeric({
"lng|113-114.1-10": 1, casing: "upper"
"lat|34-35.1-10": 1 })}`,
driver: faker.person.firstName(),
orientation: faker.number.int({ min: 1, max: 360 }),
lng: faker.location.latitude({ max: 114.1, min: 113 }),
lat: faker.location.latitude({ max: 35.1, min: 34 })
}); });
} }
return result; return result;
}; };
export default [ export default defineFakeRoute([
{ {
url: "/get-map-info", url: "/get-map-info",
method: "get", method: "get",
@ -34,4 +38,4 @@ export default [
}; };
} }
} }
] as MockMethod[]; ]);

View File

@ -1,7 +1,7 @@
import { MockMethod } from "vite-plugin-mock"; import { defineFakeRoute } from "vite-plugin-fake-server/client";
// 模拟刷新token接口 // 模拟刷新token接口
export default [ export default defineFakeRoute([
{ {
url: "/refresh-token", url: "/refresh-token",
method: "post", method: "post",
@ -24,4 +24,4 @@ export default [
} }
} }
} }
] as MockMethod[]; ]);

View File

@ -1,6 +1,7 @@
import { MockMethod } from "vite-plugin-mock"; import { defineFakeRoute } from "vite-plugin-fake-server/client";
import { faker } from "@faker-js/faker/locale/zh_CN";
export default [ export default defineFakeRoute([
// 用户管理 // 用户管理
{ {
url: "/user", url: "/user",
@ -12,7 +13,7 @@ export default [
nickname: "admin", nickname: "admin",
avatar: "https://avatars.githubusercontent.com/u/44761321", avatar: "https://avatars.githubusercontent.com/u/44761321",
phone: "15888886789", phone: "15888886789",
email: "@email", email: faker.internet.email(),
sex: 0, sex: 0,
id: 1, id: 1,
status: 1, status: 1,
@ -30,7 +31,7 @@ export default [
nickname: "common", nickname: "common",
avatar: "https://avatars.githubusercontent.com/u/52823142", avatar: "https://avatars.githubusercontent.com/u/52823142",
phone: "18288882345", phone: "18288882345",
email: "@email", email: faker.internet.email(),
sex: 1, sex: 1,
id: 2, id: 2,
status: 1, status: 1,
@ -153,12 +154,12 @@ export default [
id: 100, id: 100,
sort: 0, sort: 0,
phone: "15888888888", phone: "15888888888",
principal: "@cname()", principal: faker.person.firstName(),
email: "@email", email: faker.internet.email(),
status: 1, // 状态 1 启用 0 停用 status: 1, // 状态 1 启用 0 停用
type: 1, // 1 公司 2 分公司 3 部门 type: 1, // 1 公司 2 分公司 3 部门
createTime: 1605456000000, createTime: 1605456000000,
remark: "@cparagraph(1, 3)" remark: "这里是备注信息这里是备注信息这里是备注信息这里是备注信息"
}, },
{ {
name: "郑州分公司", name: "郑州分公司",
@ -166,12 +167,12 @@ export default [
id: 101, id: 101,
sort: 1, sort: 1,
phone: "15888888888", phone: "15888888888",
principal: "@cname()", principal: faker.person.firstName(),
email: "@email", email: faker.internet.email(),
status: 1, status: 1,
type: 2, type: 2,
createTime: 1605456000000, createTime: 1605456000000,
remark: "@cparagraph(1, 3)" remark: "这里是备注信息这里是备注信息这里是备注信息这里是备注信息"
}, },
{ {
name: "研发部门", name: "研发部门",
@ -179,12 +180,12 @@ export default [
id: 103, id: 103,
sort: 1, sort: 1,
phone: "15888888888", phone: "15888888888",
principal: "@cname()", principal: faker.person.firstName(),
email: "@email", email: faker.internet.email(),
status: 1, status: 1,
type: 3, type: 3,
createTime: 1605456000000, createTime: 1605456000000,
remark: "@cparagraph(1, 3)" remark: "这里是备注信息这里是备注信息这里是备注信息这里是备注信息"
}, },
{ {
name: "市场部门", name: "市场部门",
@ -192,12 +193,12 @@ export default [
id: 108, id: 108,
sort: 1, sort: 1,
phone: "15888888888", phone: "15888888888",
principal: "@cname()", principal: faker.person.firstName(),
email: "@email", email: faker.internet.email(),
status: 1, status: 1,
type: 3, type: 3,
createTime: 1605456000000, createTime: 1605456000000,
remark: "@cparagraph(1, 3)" remark: "这里是备注信息这里是备注信息这里是备注信息这里是备注信息"
}, },
{ {
name: "深圳分公司", name: "深圳分公司",
@ -205,12 +206,12 @@ export default [
id: 102, id: 102,
sort: 2, sort: 2,
phone: "15888888888", phone: "15888888888",
principal: "@cname()", principal: faker.person.firstName(),
email: "@email", email: faker.internet.email(),
status: 1, status: 1,
type: 2, type: 2,
createTime: 1605456000000, createTime: 1605456000000,
remark: "@cparagraph(1, 3)" remark: "这里是备注信息这里是备注信息这里是备注信息这里是备注信息"
}, },
{ {
name: "市场部门", name: "市场部门",
@ -218,12 +219,12 @@ export default [
id: 104, id: 104,
sort: 2, sort: 2,
phone: "15888888888", phone: "15888888888",
principal: "@cname()", principal: faker.person.firstName(),
email: "@email", email: faker.internet.email(),
status: 1, status: 1,
type: 3, type: 3,
createTime: 1605456000000, createTime: 1605456000000,
remark: "@cparagraph(1, 3)" remark: "这里是备注信息这里是备注信息这里是备注信息这里是备注信息"
}, },
{ {
name: "财务部门", name: "财务部门",
@ -231,12 +232,12 @@ export default [
id: 109, id: 109,
sort: 2, sort: 2,
phone: "15888888888", phone: "15888888888",
principal: "@cname()", principal: faker.person.firstName(),
email: "@email", email: faker.internet.email(),
status: 1, status: 1,
type: 3, type: 3,
createTime: 1605456000000, createTime: 1605456000000,
remark: "@cparagraph(1, 3)" remark: "这里是备注信息这里是备注信息这里是备注信息这里是备注信息"
}, },
{ {
name: "测试部门", name: "测试部门",
@ -244,12 +245,12 @@ export default [
id: 105, id: 105,
sort: 3, sort: 3,
phone: "15888888888", phone: "15888888888",
principal: "@cname()", principal: faker.person.firstName(),
email: "@email", email: faker.internet.email(),
status: 0, status: 0,
type: 3, type: 3,
createTime: 1605456000000, createTime: 1605456000000,
remark: "@cparagraph(1, 3)" remark: "这里是备注信息这里是备注信息这里是备注信息这里是备注信息"
}, },
{ {
name: "财务部门", name: "财务部门",
@ -257,12 +258,12 @@ export default [
id: 106, id: 106,
sort: 4, sort: 4,
phone: "15888888888", phone: "15888888888",
principal: "@cname()", principal: faker.person.firstName(),
email: "@email", email: faker.internet.email(),
status: 1, status: 1,
type: 3, type: 3,
createTime: 1605456000000, createTime: 1605456000000,
remark: "@cparagraph(1, 3)" remark: "这里是备注信息这里是备注信息这里是备注信息这里是备注信息"
}, },
{ {
name: "运维部门", name: "运维部门",
@ -270,15 +271,15 @@ export default [
id: 107, id: 107,
sort: 5, sort: 5,
phone: "15888888888", phone: "15888888888",
principal: "@cname()", principal: faker.person.firstName(),
email: "@email", email: faker.internet.email(),
status: 0, status: 0,
type: 3, type: 3,
createTime: 1605456000000, createTime: 1605456000000,
remark: "@cparagraph(1, 3)" remark: "这里是备注信息这里是备注信息这里是备注信息这里是备注信息"
} }
] ]
}; };
} }
} }
] as MockMethod[]; ]);

View File

@ -2,30 +2,7 @@
"name": "vue-pure-admin", "name": "vue-pure-admin",
"version": "4.5.0", "version": "4.5.0",
"private": true, "private": true,
"keywords": [ "type": "module",
"vue-pure-admin",
"element-plus",
"tailwindcss",
"pure-admin",
"typescript",
"pinia",
"vue3",
"vite"
],
"homepage": "https://github.com/pure-admin/vue-pure-admin",
"bugs": {
"url": "https://github.com/pure-admin/vue-pure-admin/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pure-admin/vue-pure-admin.git"
},
"license": "MIT",
"author": {
"name": "xiaoxian521",
"email": "pureadmin@163.com",
"url": "https://github.com/xiaoxian521"
},
"scripts": { "scripts": {
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite", "dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
"serve": "pnpm dev", "serve": "pnpm dev",
@ -37,146 +14,166 @@
"typecheck": "tsc --noEmit && vue-tsc --noEmit --skipLibCheck", "typecheck": "tsc --noEmit && vue-tsc --noEmit --skipLibCheck",
"svgo": "svgo -f src/assets/svg -o src/assets/svg", "svgo": "svgo -f src/assets/svg -o src/assets/svg",
"cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML", "cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML",
"clean:cache": "rimraf .eslintcache && rimraf node_modules && pnpm install", "clean:cache": "rimraf .eslintcache && rimraf pnpm-lock.yaml && rimraf node_modules && pnpm store prune && pnpm install",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix", "lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"", "lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", "lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint", "lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
"prepare": "husky install",
"preinstall": "npx only-allow pnpm" "preinstall": "npx only-allow pnpm"
}, },
"keywords": [
"vue-pure-admin",
"element-plus",
"tailwindcss",
"pure-admin",
"typescript",
"pinia",
"vue3",
"vite",
"esm"
],
"homepage": "https://github.com/pure-admin/vue-pure-admin",
"repository": {
"type": "git",
"url": "git+https://github.com/pure-admin/vue-pure-admin.git"
},
"bugs": {
"url": "https://github.com/pure-admin/vue-pure-admin/issues"
},
"license": "MIT",
"author": {
"name": "xiaoxian521",
"email": "pureadmin@163.com",
"url": "https://github.com/xiaoxian521"
},
"dependencies": { "dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1", "@amap/amap-jsapi-loader": "^1.0.1",
"@howdyjs/mouse-menu": "2.0.9", "@howdyjs/mouse-menu": "2.0.9",
"@logicflow/core": "^1.2.17", "@logicflow/core": "^1.2.18",
"@logicflow/extension": "^1.2.18", "@logicflow/extension": "^1.2.19",
"@pureadmin/descriptions": "^1.2.0", "@pureadmin/descriptions": "^1.2.0",
"@pureadmin/table": "^2.3.4", "@pureadmin/table": "^3.0.0",
"@pureadmin/utils": "^1.9.10", "@pureadmin/utils": "^2.1.1",
"@vueuse/core": "^10.5.0", "@vueuse/core": "^10.7.1",
"@vueuse/motion": "^2.0.0", "@vueuse/motion": "^2.0.0",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.6.0", "axios": "^1.6.3",
"china-area-data": "^5.0.1", "china-area-data": "^5.0.1",
"cropperjs": "^1.6.1", "cropperjs": "^1.6.1",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"echarts": "^5.4.3", "echarts": "^5.4.3",
"el-table-infinite-scroll": "^3.0.3", "el-table-infinite-scroll": "^3.0.3",
"element-plus": "^2.4.1", "element-plus": "^2.4.4",
"intro.js": "^7.2.0", "intro.js": "^7.2.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsbarcode": "^3.11.5", "jsbarcode": "^3.11.6",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"md-editor-v3": "2.7.2", "md-editor-v3": "2.7.2",
"mint-filter": "^4.0.3", "mint-filter": "^4.0.3",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path": "^0.12.7", "path": "^0.12.7",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinyin-pro": "^3.17.0", "pinyin-pro": "^3.18.6",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"qs": "^6.11.2", "qs": "^6.11.2",
"responsive-storage": "^2.2.0", "responsive-storage": "^2.2.0",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.1",
"swiper": "^11.0.3", "swiper": "^11.0.5",
"typeit": "^8.7.1", "typeit": "^8.8.0",
"v-contextmenu": "3.0.0", "v-contextmenu": "3.0.0",
"v3-infinite-loading": "^1.3.1", "v3-infinite-loading": "^1.3.1",
"version-rocket": "^1.7.0", "version-rocket": "^1.7.1",
"vue": "^3.3.7", "vue": "^3.4.3",
"vue-i18n": "^9.6.2", "vue-i18n": "^9.8.0",
"vue-json-pretty": "^2.2.4", "vue-json-pretty": "^2.3.0",
"vue-pdf-embed": "^1.2.1", "vue-pdf-embed": "^1.2.1",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"vue-tippy": "^6.3.1", "vue-tippy": "^6.4.1",
"vue-types": "^5.1.1", "vue-types": "^5.1.1",
"vue-virtual-scroller": "2.0.0-beta.8", "vue-virtual-scroller": "2.0.0-beta.8",
"vue-waterfall-plugin-next": "^2.2.4", "vue-waterfall-plugin-next": "^2.3.1",
"vue3-danmaku": "^1.6.0", "vue3-danmaku": "^1.6.0",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"wavesurfer.js": "^7.4.2", "wavesurfer.js": "^7.6.0",
"xgplayer": "^3.0.9", "xgplayer": "^3.0.11",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.7.2", "@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^17.7.0", "@commitlint/config-conventional": "^18.4.3",
"@commitlint/types": "^18.4.3",
"@eslint/js": "^8.56.0",
"@faker-js/faker": "^8.3.1",
"@iconify-icons/ep": "^1.2.12", "@iconify-icons/ep": "^1.2.12",
"@iconify-icons/ri": "^1.2.10", "@iconify-icons/ri": "^1.2.10",
"@iconify/vue": "^4.1.1", "@iconify/vue": "^4.1.1",
"@intlify/unplugin-vue-i18n": "^1.4.0", "@intlify/unplugin-vue-i18n": "^2.0.0",
"@pureadmin/theme": "^3.2.0", "@pureadmin/theme": "^3.2.0",
"@types/intro.js": "^5.1.2", "@types/intro.js": "^5.1.5",
"@types/js-cookie": "^3.0.4", "@types/js-cookie": "^3.0.6",
"@types/mockjs": "^1.0.8", "@types/node": "^20.10.6",
"@types/node": "^20.8.2", "@types/nprogress": "^0.2.3",
"@types/nprogress": "0.2.0", "@types/qrcode": "^1.5.5",
"@types/qrcode": "^1.5.2", "@types/qs": "^6.9.11",
"@types/qs": "^6.9.8", "@types/sortablejs": "^1.15.7",
"@types/sortablejs": "^1.15.3", "@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.17.0",
"@typescript-eslint/parser": "^6.7.4", "@vitejs/plugin-vue": "^5.0.2",
"@vitejs/plugin-vue": "^4.4.0", "@vitejs/plugin-vue-jsx": "^3.1.0",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"cloc": "^2.11.0", "cloc": "^2.11.0",
"cssnano": "^6.0.1", "cssnano": "^6.0.2",
"eslint": "^8.50.0", "eslint": "^8.56.0",
"eslint-plugin-prettier": "^5.0.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.18.1", "eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.1.2",
"eslint-plugin-vue": "^9.19.2",
"husky": "^8.0.3", "husky": "^8.0.3",
"lint-staged": "^14.0.1", "lint-staged": "^15.2.0",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"postcss": "^8.4.31", "postcss": "^8.4.32",
"postcss-html": "^1.5.0", "postcss-html": "^1.5.0",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"postcss-scss": "^4.0.9", "postcss-scss": "^4.0.9",
"prettier": "^3.0.3", "prettier": "^3.1.1",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup-plugin-visualizer": "^5.9.2", "rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.69.0", "sass": "^1.69.6",
"sass-loader": "^13.3.2", "stylelint": "^16.1.0",
"stylelint": "^15.10.3", "stylelint-config-recess-order": "^4.4.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recess-order": "^4.3.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-config-recommended-scss": "^13.0.0",
"stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard": "^34.0.0", "stylelint-config-standard-scss": "^12.0.0",
"stylelint-config-standard-scss": "^11.0.0", "stylelint-prettier": "^5.0.0",
"stylelint-order": "^6.0.3", "svgo": "^3.2.0",
"stylelint-prettier": "^4.0.2", "tailwindcss": "^3.4.0",
"stylelint-scss": "^5.2.1", "typescript": "^5.3.3",
"svgo": "^3.0.2", "vite": "^5.0.10",
"tailwindcss": "^3.3.5",
"terser": "^5.24.0",
"typescript": "^5.2.2",
"vite": "^4.5.0",
"vite-plugin-cdn-import": "^0.3.5", "vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-mock": "2.9.6", "vite-plugin-fake-server": "^2.0.1",
"vite-plugin-remove-console": "^2.1.1", "vite-plugin-remove-console": "^2.2.0",
"vite-svg-loader": "^4.0.0", "vite-plugin-router-warn": "^1.0.0",
"vite-svg-loader": "^5.1.0",
"vue-eslint-parser": "^9.3.2", "vue-eslint-parser": "^9.3.2",
"vue-tsc": "^1.8.22" "vue-tsc": "^1.8.27"
}, },
"engines": {
"node": "^18.12.0 || >=20.0.0",
"pnpm": ">=8.6.10"
},
"packageManager": "pnpm@8.6.10",
"pnpm": { "pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"rollup",
"webpack",
"core-js"
]
},
"allowedDeprecatedVersions": { "allowedDeprecatedVersions": {
"sourcemap-codec": "*", "sourcemap-codec": "*",
"domexception": "*",
"w3c-hr-time": "*", "w3c-hr-time": "*",
"stable": "*" "stable": "*",
"abab": "*"
} }
} }
} }

6402
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
module.exports = { // @ts-check
/** @type {import('postcss-load-config').Config} */
export default {
plugins: { plugins: {
"postcss-import": {}, "postcss-import": {},
"tailwindcss/nesting": {}, "tailwindcss/nesting": {},

View File

@ -7,7 +7,7 @@
"KeepAlive": true, "KeepAlive": true,
"Locale": "zh", "Locale": "zh",
"Layout": "vertical", "Layout": "vertical",
"Theme": "default", "Theme": "light",
"DarkMode": false, "DarkMode": false,
"Grey": false, "Grey": false,
"Weak": false, "Weak": false,

View File

@ -7,15 +7,8 @@ defineOptions({
name: "ReAnimateSelector" name: "ReAnimateSelector"
}); });
const props = defineProps({ const inputValue = defineModel({ type: String });
modelValue: {
require: false,
type: String
}
});
const emit = defineEmits<{ (e: "update:modelValue", v: string) }>();
const inputValue = toRef(props, "modelValue");
const animatesList = ref(animates); const animatesList = ref(animates);
const copyAnimatesList = cloneDeep(animatesList); const copyAnimatesList = cloneDeep(animatesList);
@ -47,11 +40,11 @@ const animateStyle = computed(
); );
function onChangeIcon(animate: string) { function onChangeIcon(animate: string) {
emit("update:modelValue", animate); inputValue.value = animate;
} }
function onClear() { function onClear() {
emit("update:modelValue", ""); inputValue.value = "";
} }
function filterMethod(value: any) { function filterMethod(value: any) {
@ -81,8 +74,8 @@ function onMouseleave() {
placeholder="请选择动画" placeholder="请选择动画"
clearable clearable
filterable filterable
@clear="onClear"
:filter-method="filterMethod" :filter-method="filterMethod"
@clear="onClear"
> >
<template #empty> <template #empty>
<div class="w-[280px]"> <div class="w-[280px]">

View File

@ -1,4 +1,4 @@
import { PropType } from "vue"; import type { PropType } from "vue";
import propTypes from "@/utils/propTypes"; import propTypes from "@/utils/propTypes";
export const countToProps = { export const countToProps = {
startVal: propTypes.number.def(0), startVal: propTypes.number.def(0),

View File

@ -1,4 +1,4 @@
import { PropType } from "vue"; import type { PropType } from "vue";
import propTypes from "@/utils/propTypes"; import propTypes from "@/utils/propTypes";
export const reboundProps = { export const reboundProps = {
delay: propTypes.number.def(1), delay: propTypes.number.def(1),

View File

@ -1,7 +1,5 @@
@import "cropperjs/dist/cropper.css";
@import "tippy.js/dist/tippy.css";
@import "tippy.js/themes/light.css"; @import "tippy.js/themes/light.css";
@import "tippy.js/animations/perspective.css"; @import "cropperjs/dist/cropper.css";
.re-circled { .re-circled {
.cropper-view-box, .cropper-view-box,

View File

@ -1,16 +1,16 @@
import "./circled.css"; import "./circled.css";
import Cropper from "cropperjs"; import Cropper from "cropperjs";
import { useTippy } from "vue-tippy";
import { ElUpload } from "element-plus"; import { ElUpload } from "element-plus";
import type { CSSProperties } from "vue"; import type { CSSProperties } from "vue";
import { useResizeObserver } from "@vueuse/core"; import { useResizeObserver } from "@vueuse/core";
import { longpress } from "@/directives/longpress"; import { longpress } from "@/directives/longpress";
import { useTippy, directive as tippy } from "vue-tippy";
import { delay, debounce, isArray, downloadByBase64 } from "@pureadmin/utils"; import { delay, debounce, isArray, downloadByBase64 } from "@pureadmin/utils";
import { import {
ref, ref,
unref, unref,
computed, computed,
PropType, type PropType,
onMounted, onMounted,
onUnmounted, onUnmounted,
defineComponent defineComponent
@ -233,7 +233,6 @@ export default defineComponent({
const menuContent = defineComponent({ const menuContent = defineComponent({
directives: { directives: {
tippy,
longpress longpress
}, },
setup() { setup() {

View File

@ -27,8 +27,11 @@ const addDialog = (options: DialogOptions) => {
/** 关闭弹框 */ /** 关闭弹框 */
const closeDialog = (options: DialogOptions, index: number, args?: any) => { const closeDialog = (options: DialogOptions, index: number, args?: any) => {
dialogStore.value.splice(index, 1); dialogStore.value[index].visible = false;
options.closeCallBack && options.closeCallBack({ options, index, args }); options.closeCallBack && options.closeCallBack({ options, index, args });
useTimeoutFn(() => {
dialogStore.value.splice(index, 1);
}, 200);
}; };
/** /**

View File

@ -84,11 +84,11 @@ function handleClose(
<template> <template>
<el-dialog <el-dialog
class="pure-dialog"
v-for="(options, index) in dialogStore" v-for="(options, index) in dialogStore"
:key="index" :key="index"
v-bind="options" v-bind="options"
v-model="options.visible" v-model="options.visible"
class="pure-dialog"
:fullscreen="fullscreen ? true : options?.fullscreen ? true : false" :fullscreen="fullscreen ? true : options?.fullscreen ? true : false"
@close="handleClose(options, index)" @close="handleClose(options, index)"
@opened="eventsCallBack('open', options, index)" @opened="eventsCallBack('open', options, index)"
@ -123,8 +123,8 @@ function handleClose(
</i> </i>
</div> </div>
<component <component
v-else
:is="options?.headerRenderer({ close, titleId, titleClass })" :is="options?.headerRenderer({ close, titleId, titleClass })"
v-else
/> />
</template> </template>
<component <component

View File

@ -1,5 +1,5 @@
import "./index.css"; import "./index.css";
import { h, defineComponent, Component } from "vue"; import { h, defineComponent, type Component } from "vue";
export interface attrsType { export interface attrsType {
width?: string; width?: string;

View File

@ -18,7 +18,6 @@ export default defineComponent({
name: "ReFlop", name: "ReFlop",
props, props,
setup(props) { setup(props) {
// eslint-disable-next-line vue/no-setup-props-destructure
const { frontText, backText, duration } = props; const { frontText, backText, duration } = props;
const isFlipping = ref(false); const isFlipping = ref(false);
const flipType = ref("down"); const flipType = ref("down");

View File

@ -35,9 +35,9 @@ const nodeDragNode = item => {
<!-- 左侧bpmn元素选择器 --> <!-- 左侧bpmn元素选择器 -->
<div class="node-panel"> <div class="node-panel">
<div <div
class="node-item dark:text-bg_color"
v-for="item in props.nodeList" v-for="item in props.nodeList"
:key="item.text" :key="item.text"
class="node-item dark:text-bg_color"
@mousedown="nodeDragNode(item)" @mousedown="nodeDragNode(item)"
> >
<div class="node-item-icon" :class="item.class"> <div class="node-item-icon" :class="item.class">

View File

@ -10,15 +10,8 @@ defineOptions({
name: "IconSelect" name: "IconSelect"
}); });
const props = defineProps({ const inputValue = defineModel({ type: String });
modelValue: {
require: false,
type: String
}
});
const emit = defineEmits<{ (e: "update:modelValue", v: string) }>();
const inputValue = toRef(props, "modelValue");
const iconList = ref(IconJson); const iconList = ref(IconJson);
const icon = ref(); const icon = ref();
const currentActiveType = ref("ep:"); const currentActiveType = ref("ep:");
@ -68,11 +61,11 @@ const iconItemStyle = computed((): ParameterCSSProperties => {
}); });
function setVal() { function setVal() {
currentActiveType.value = props.modelValue.substring( currentActiveType.value = inputValue.value.substring(
0, 0,
props.modelValue.indexOf(":") + 1 inputValue.value.indexOf(":") + 1
); );
icon.value = props.modelValue.substring(props.modelValue.indexOf(":") + 1); icon.value = inputValue.value.substring(inputValue.value.indexOf(":") + 1);
} }
function onBeforeEnter() { function onBeforeEnter() {
@ -96,7 +89,7 @@ function handleClick({ props }) {
function onChangeIcon(item) { function onChangeIcon(item) {
icon.value = item; icon.value = item;
emit("update:modelValue", currentActiveType.value + item); inputValue.value = currentActiveType.value + item;
} }
function onCurrentChange(page) { function onCurrentChange(page) {
@ -105,7 +98,7 @@ function onCurrentChange(page) {
function onClear() { function onClear() {
icon.value = ""; icon.value = "";
emit("update:modelValue", ""); inputValue.value = "";
} }
watch( watch(
@ -117,7 +110,7 @@ watch(
{ immediate: true } { immediate: true }
); );
watch( watch(
() => props.modelValue, () => inputValue.value,
val => val && setVal(), val => val && setVal(),
{ immediate: true } { immediate: true }
); );
@ -151,8 +144,8 @@ watch(
</template> </template>
<el-input <el-input
class="px-2 pt-2"
v-model="filterValue" v-model="filterValue"
class="px-2 pt-2"
placeholder="搜索图标" placeholder="搜索图标"
clearable clearable
/> />

View File

@ -1,5 +1,5 @@
import { iconType } from "./types"; import type { iconType } from "./types";
import { h, defineComponent, Component } from "vue"; import { h, defineComponent, type Component } from "vue";
import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index"; import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
/** /**
@ -33,7 +33,7 @@ export function useRenderIcon(icon: any, attrs?: iconType): Component {
}); });
} else if (typeof icon === "function" || typeof icon?.render === "function") { } else if (typeof icon === "function" || typeof icon?.render === "function") {
// svg // svg
return icon; return attrs ? h(icon, { ...attrs }) : icon;
} else if (typeof icon === "object") { } else if (typeof icon === "object") {
return defineComponent({ return defineComponent({
name: "OfflineIcon", name: "OfflineIcon",

View File

@ -13,7 +13,8 @@ export interface iconType {
align?: string; align?: string;
onLoad?: Function; onLoad?: Function;
includes?: Function; includes?: Function;
// svg 需要什么SVG属性自行添加
fill?: string;
// all icon // all icon
style?: object; style?: object;
} }

View File

@ -4,13 +4,13 @@ import {
watch, watch,
nextTick, nextTick,
computed, computed,
PropType, type PropType,
defineComponent defineComponent
} from "vue"; } from "vue";
import "./index.scss"; import "./index.scss";
import propTypes from "@/utils/propTypes"; import propTypes from "@/utils/propTypes";
import { isString, cloneDeep } from "@pureadmin/utils"; import { isString, cloneDeep } from "@pureadmin/utils";
import QRCode, { QRCodeRenderersOptions } from "qrcode"; import QRCode, { type QRCodeRenderersOptions } from "qrcode";
import RefreshRight from "@iconify-icons/ep/refresh-right"; import RefreshRight from "@iconify-icons/ep/refresh-right";
interface QrcodeLogo { interface QrcodeLogo {

View File

@ -498,16 +498,16 @@ defineExpose({
<template> <template>
<div :ref="'wrap' + props.classOption['key']"> <div :ref="'wrap' + props.classOption['key']">
<div <div
:style="leftSwitch"
v-if="navigation" v-if="navigation"
:style="leftSwitch"
:class="leftSwitchClass" :class="leftSwitchClass"
@click="leftSwitchClick" @click="leftSwitchClick"
> >
<slot name="left-switch" /> <slot name="left-switch" />
</div> </div>
<div <div
:style="rightSwitch"
v-if="navigation" v-if="navigation"
:style="rightSwitch"
:class="rightSwitchClass" :class="rightSwitchClass"
@click="rightSwitchClick" @click="rightSwitchClick"
> >
@ -526,7 +526,7 @@ defineExpose({
<div :ref="'slotList' + props.classOption['key']" :style="float"> <div :ref="'slotList' + props.classOption['key']" :style="float">
<slot /> <slot />
</div> </div>
<div v-html="copyHtml" :style="float" /> <div :style="float" v-html="copyHtml" />
</div> </div>
</div> </div>
</template> </template>

View File

@ -6,7 +6,6 @@
color: rgba(0, 0, 0, 0.65); color: rgba(0, 0, 0, 0.65);
background-color: rgb(0 0 0 / 4%); background-color: rgb(0 0 0 / 4%);
border-radius: 2px; border-radius: 2px;
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
} }
.pure-segmented-group { .pure-segmented-group {
@ -43,7 +42,7 @@
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
} }
.pure-segmented-item > div { .pure-segmented-item > div {

View File

@ -121,7 +121,11 @@ export default defineComponent({
class="pure-segmented-item-icon" class="pure-segmented-item-icon"
style={{ marginRight: option.label ? "6px" : 0 }} style={{ marginRight: option.label ? "6px" : 0 }}
> >
{h(useRenderIcon(option.icon))} {h(
useRenderIcon(option.icon, {
...option?.iconAttrs
})
)}
</span> </span>
) : null} ) : null}
{option.label ? ( {option.label ? (

View File

@ -1,4 +1,5 @@
import type { VNode, Component } from "vue"; import type { VNode, Component } from "vue";
import type { iconType } from "@/components/ReIcon/src/types.ts";
export interface OptionsType { export interface OptionsType {
/** 文字 */ /** 文字 */
@ -8,6 +9,8 @@ export interface OptionsType {
* @see {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks } * @see {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
*/ */
icon?: string | Component; icon?: string | Component;
/** 图标属性、样式配置 */
iconAttrs?: iconType;
/** 值 */ /** 值 */
value?: string | number; value?: string | number;
/** 是否禁用 */ /** 是否禁用 */

View File

@ -55,7 +55,6 @@ export default defineComponent({
emits: ["selectedVal"], emits: ["selectedVal"],
setup(props, { emit }) { setup(props, { emit }) {
const instance = getCurrentInstance(); const instance = getCurrentInstance();
// eslint-disable-next-line vue/no-setup-props-destructure
const currentValue = props.value; const currentValue = props.value;
const rateDisabled = computed(() => { const rateDisabled = computed(() => {

View File

@ -1,4 +1,4 @@
import { defineComponent, ref, unref, computed, PropType } from "vue"; import { defineComponent, ref, unref, computed, type PropType } from "vue";
import resizer from "./resizer"; import resizer from "./resizer";
import "./index.css"; import "./index.css";

View File

@ -1,4 +1,4 @@
import { App } from "vue"; import type { App } from "vue";
import axios from "axios"; import axios from "axios";
let config: object = {}; let config: object = {};

View File

@ -50,6 +50,12 @@ const getSectionStyle = computed(() => {
}); });
const transitionMain = defineComponent({ const transitionMain = defineComponent({
props: {
route: {
type: undefined,
required: true
}
},
render() { render() {
const transitionName = const transitionName =
transitions.value(this.route)?.name || "fade-transform"; transitions.value(this.route)?.name || "fade-transform";
@ -72,12 +78,6 @@ const transitionMain = defineComponent({
default: () => [this.$slots.default()] default: () => [this.$slots.default()]
} }
); );
},
props: {
route: {
type: undefined,
required: true
}
} }
}); });
</script> </script>
@ -92,7 +92,8 @@ const transitionMain = defineComponent({
<el-scrollbar <el-scrollbar
v-if="props.fixedHeader" v-if="props.fixedHeader"
:wrap-style="{ :wrap-style="{
display: 'flex' display: 'flex',
'flex-wrap': 'wrap'
}" }"
:view-style="{ :view-style="{
display: 'flex', display: 'flex',
@ -117,8 +118,8 @@ const transitionMain = defineComponent({
/> />
</keep-alive> </keep-alive>
<component <component
v-else
:is="Component" :is="Component"
v-else
:key="route.fullPath" :key="route.fullPath"
class="main-content" class="main-content"
/> />
@ -139,8 +140,8 @@ const transitionMain = defineComponent({
/> />
</keep-alive> </keep-alive>
<component <component
v-else
:is="Component" :is="Component"
v-else
:key="route.fullPath" :key="route.fullPath"
class="main-content" class="main-content"
/> />

View File

@ -62,8 +62,8 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
@click="translationCh" @click="translationCh"
> >
<IconifyIconOffline <IconifyIconOffline
class="check-zh"
v-show="locale === 'zh'" v-show="locale === 'zh'"
class="check-zh"
:icon="Check" :icon="Check"
/> />
简体中文 简体中文
@ -73,7 +73,7 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
:class="['dark:!text-white', getDropdownItemClass(locale, 'en')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
@click="translationEn" @click="translationEn"
> >
<span class="check-en" v-show="locale === 'en'"> <span v-show="locale === 'en'" class="check-en">
<IconifyIconOffline :icon="Check" /> <IconifyIconOffline :icon="Check" />
</span> </span>
English English

View File

@ -23,8 +23,8 @@ notices.value.map(v => (noticesNum.value += v.list.length));
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-tabs <el-tabs
:stretch="true"
v-model="activeKey" v-model="activeKey"
:stretch="true"
class="dropdown-tabs" class="dropdown-tabs"
:style="{ width: notices.length === 0 ? '200px' : '330px' }" :style="{ width: notices.length === 0 ? '200px' : '330px' }"
> >

View File

@ -15,8 +15,8 @@ const props = defineProps({
<div v-if="props.list.length"> <div v-if="props.list.length">
<NoticeItem <NoticeItem
v-for="(item, index) in props.list" v-for="(item, index) in props.list"
:noticeItem="item"
:key="index" :key="index"
:noticeItem="item"
/> />
</div> </div>
<el-empty v-else description="暂无数据" /> <el-empty v-else description="暂无数据" />

View File

@ -2,6 +2,7 @@
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
import { onClickOutside } from "@vueuse/core"; import { onClickOutside } from "@vueuse/core";
import { ref, computed, onMounted, onBeforeUnmount } from "vue"; import { ref, computed, onMounted, onBeforeUnmount } from "vue";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import Close from "@iconify-icons/ep/close"; import Close from "@iconify-icons/ep/close";
const target = ref(null); const target = ref(null);
@ -9,7 +10,6 @@ const show = ref<Boolean>(false);
const iconClass = computed(() => { const iconClass = computed(() => {
return [ return [
"mr-[20px]",
"outline-none", "outline-none",
"width-[20px]", "width-[20px]",
"height-[20px]", "height-[20px]",
@ -22,6 +22,8 @@ const iconClass = computed(() => {
]; ];
}); });
const { onReset } = useDataThemeChange();
onClickOutside(target, (event: any) => { onClickOutside(target, (event: any) => {
if (event.clientX > target.value.offsetLeft) return; if (event.clientX > target.value.offsetLeft) return;
show.value = false; show.value = false;
@ -43,10 +45,18 @@ onBeforeUnmount(() => {
<div :class="{ show: show }" class="right-panel-container"> <div :class="{ show: show }" class="right-panel-container">
<div class="right-panel-background" /> <div class="right-panel-background" />
<div ref="target" class="right-panel bg-bg_color"> <div ref="target" class="right-panel bg-bg_color">
<div class="right-panel-items"> <div
<div class="project-configuration"> class="project-configuration border-b-[1px] border-solid border-[var(--pure-border-color)]"
>
<h4 class="dark:text-white">项目配置</h4> <h4 class="dark:text-white">项目配置</h4>
<span title="关闭配置" :class="iconClass"> <span
v-tippy="{
content: '关闭配置',
placement: 'bottom-start',
zIndex: 41000
}"
:class="iconClass"
>
<IconifyIconOffline <IconifyIconOffline
class="dark:text-white" class="dark:text-white"
width="20px" width="20px"
@ -56,10 +66,26 @@ onBeforeUnmount(() => {
/> />
</span> </span>
</div> </div>
<div <el-scrollbar>
class="border-b-[1px] border-solid border-[#dcdfe6] dark:border-[#303030]"
/>
<slot /> <slot />
</el-scrollbar>
<div
class="flex justify-end p-3 border-t-[1px] border-solid border-[var(--pure-border-color)]"
>
<el-button
v-tippy="{
content: '清空缓存并返回登录页',
placement: 'left-start',
zIndex: 41000
}"
type="danger"
text
bg
@click="onReset"
>
清空缓存
</el-button>
</div> </div>
</div> </div>
</div> </div>
@ -74,6 +100,10 @@ onBeforeUnmount(() => {
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-scrollbar) {
height: calc(100vh - 110px);
}
.right-panel-background { .right-panel-background {
position: fixed; position: fixed;
top: 0; top: 0;
@ -90,7 +120,7 @@ onBeforeUnmount(() => {
right: 0; right: 0;
z-index: 40000; z-index: 40000;
width: 100%; width: 100%;
max-width: 315px; max-width: 280px;
height: 100vh; height: 100vh;
box-shadow: 0 0 15px 0 rgb(0 0 0 / 5%); box-shadow: 0 0 15px 0 rgb(0 0 0 / 5%);
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1); transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
@ -112,47 +142,10 @@ onBeforeUnmount(() => {
} }
} }
.handle-button {
position: absolute;
top: 45%;
left: -48px;
z-index: 0;
width: 48px;
height: 48px;
font-size: 24px;
line-height: 48px;
color: #fff;
text-align: center;
pointer-events: auto;
cursor: pointer;
background: rgb(24 144 255);
border-radius: 6px 0 0 6px !important;
i {
font-size: 24px;
line-height: 48px;
}
}
.right-panel-items {
height: calc(100vh - 60px);
margin-top: 60px;
overflow-y: auto;
}
.project-configuration { .project-configuration {
position: fixed;
top: 15px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
width: 100%; padding: 14px 20px;
height: 30px;
margin-left: 10px;
}
:deep(.el-divider--horizontal) {
width: 90%;
margin: 20px auto 0;
} }
</style> </style>

View File

@ -146,9 +146,9 @@ onKeyStroke("ArrowDown", handleDown);
<template> <template>
<el-dialog <el-dialog
v-model="show"
top="5vh" top="5vh"
class="pure-search-dialog" class="pure-search-dialog"
v-model="show"
:show-close="false" :show-close="false"
:width="device === 'mobile' ? '80vw' : '40vw'" :width="device === 'mobile' ? '80vw' : '40vw'"
:before-close="handleClose" :before-close="handleClose"
@ -161,8 +161,8 @@ onKeyStroke("ArrowDown", handleDown);
> >
<el-input <el-input
ref="inputRef" ref="inputRef"
size="large"
v-model="keyword" v-model="keyword"
size="large"
clearable clearable
placeholder="搜索菜单(中文模式下支持拼音搜索)" placeholder="搜索菜单(中文模式下支持拼音搜索)"
@input="handleSearch" @input="handleSearch"

View File

@ -8,28 +8,22 @@ import {
nextTick, nextTick,
onBeforeMount onBeforeMount
} from "vue"; } from "vue";
import { getConfig } from "@/config";
import { useRouter } from "vue-router";
import panel from "../panel/index.vue"; import panel from "../panel/index.vue";
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
import { resetRouter } from "@/router";
import { removeToken } from "@/utils/auth";
import { routerArrays } from "@/layout/types";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import { useAppStoreHook } from "@/store/modules/app"; import { useAppStoreHook } from "@/store/modules/app";
import { useDark, debounce, useGlobal } from "@pureadmin/utils";
import { toggleTheme } from "@pureadmin/theme/dist/browser-utils"; import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import Segmented, { type OptionsType } from "@/components/ReSegmented";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange"; import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import { useDark, debounce, useGlobal, storageLocal } from "@pureadmin/utils";
import Check from "@iconify-icons/ep/check";
import dayIcon from "@/assets/svg/day.svg?component"; import dayIcon from "@/assets/svg/day.svg?component";
import darkIcon from "@/assets/svg/dark.svg?component"; import darkIcon from "@/assets/svg/dark.svg?component";
import Check from "@iconify-icons/ep/check";
import Logout from "@iconify-icons/ri/logout-circle-r-line";
const router = useRouter(); const { device } = useNav();
const { isDark } = useDark(); const { isDark } = useDark();
const { device, tooltipEffect } = useNav();
const { $storage } = useGlobal<GlobalPropertiesApi>(); const { $storage } = useGlobal<GlobalPropertiesApi>();
const mixRef = ref(); const mixRef = ref();
@ -40,8 +34,8 @@ const {
dataTheme, dataTheme,
layoutTheme, layoutTheme,
themeColors, themeColors,
toggleClass,
dataThemeChange, dataThemeChange,
setEpThemeColor,
setLayoutThemeColor setLayoutThemeColor
} = useDataThemeChange(); } = useDataThemeChange();
@ -89,13 +83,6 @@ function storageConfigureChange<T>(key: string, val: T): void {
$storage.configure = storageConfigure; $storage.configure = storageConfigure;
} }
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
const targetEl = target || document.body;
let { className } = targetEl;
className = className.replace(clsName, "").trim();
targetEl.className = flag ? `${className} ${clsName} ` : className;
}
/** 灰色模式设置 */ /** 灰色模式设置 */
const greyChange = (value): void => { const greyChange = (value): void => {
toggleClass(settings.greyVal, "html-grey", document.querySelector("html")); toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
@ -132,24 +119,11 @@ const multiTagsCacheChange = () => {
useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache); useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
}; };
/** 清空缓存并返回登录页 */ function onChange({ option }) {
function onReset() { const { value } = option;
removeToken(); markValue.value = value;
storageLocal().clear(); storageConfigureChange("showModel", value);
const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig(); emitter.emit("tagViewsShowModel", value);
useAppStoreHook().setLayout(Layout);
setEpThemeColor(EpThemeColor);
useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
toggleClass(Grey, "html-grey", document.querySelector("html"));
toggleClass(Weak, "html-weakness", document.querySelector("html"));
router.push("/login");
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter();
}
function onChange(label) {
storageConfigureChange("showModel", label);
emitter.emit("tagViewsShowModel", label);
} }
/** 侧边栏Logo */ /** 侧边栏Logo */
@ -185,6 +159,32 @@ const getThemeColor = computed(() => {
}; };
}); });
const themeOptions = computed<Array<OptionsType>>(() => {
return [
{
label: "亮色",
icon: dayIcon,
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
},
{
label: "暗色",
icon: darkIcon,
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
}
];
});
const markOptions: Array<OptionsType> = [
{
label: "灵动",
value: "smart"
},
{
label: "卡片",
value: "card"
}
];
/** 设置导航模式 */ /** 设置导航模式 */
function setLayoutModel(layout: string) { function setLayoutModel(layout: string) {
layoutTheme.value.layout = layout; layoutTheme.value.layout = layout;
@ -194,7 +194,8 @@ function setLayoutModel(layout: string) {
theme: layoutTheme.value.theme, theme: layoutTheme.value.theme,
darkMode: $storage.layout?.darkMode, darkMode: $storage.layout?.darkMode,
sidebarStatus: $storage.layout?.sidebarStatus, sidebarStatus: $storage.layout?.sidebarStatus,
epThemeColor: $storage.layout?.epThemeColor epThemeColor: $storage.layout?.epThemeColor,
themeColor: layoutTheme.value.theme
}; };
useAppStoreHook().setLayout(layout); useAppStoreHook().setLayout(layout);
} }
@ -234,78 +235,25 @@ onBeforeMount(() => {
<template> <template>
<panel> <panel>
<el-divider>主题</el-divider> <div class="p-6">
<el-switch <p class="mb-3 font-medium text-sm dark:text-white">整体风格</p>
v-model="dataTheme" <Segmented
inline-prompt :modelValue="dataTheme ? 1 : 0"
class="pure-datatheme" :options="themeOptions"
:active-icon="dayIcon" @change="
:inactive-icon="darkIcon" {
@change="dataThemeChange" dataTheme = !dataTheme;
dataThemeChange();
}
"
/> />
<el-divider>导航栏模式</el-divider> <p class="mt-5 mb-3 font-medium text-sm dark:text-white">主题色</p>
<ul class="pure-theme">
<el-tooltip
:effect="tooltipEffect"
class="item"
content="左侧模式"
placement="bottom"
popper-class="pure-tooltip"
>
<li
:class="layoutTheme.layout === 'vertical' ? 'is-select' : ''"
ref="verticalRef"
@click="setLayoutModel('vertical')"
>
<div />
<div />
</li>
</el-tooltip>
<el-tooltip
v-if="device !== 'mobile'"
:effect="tooltipEffect"
class="item"
content="顶部模式"
placement="bottom"
popper-class="pure-tooltip"
>
<li
:class="layoutTheme.layout === 'horizontal' ? 'is-select' : ''"
ref="horizontalRef"
@click="setLayoutModel('horizontal')"
>
<div />
<div />
</li>
</el-tooltip>
<el-tooltip
v-if="device !== 'mobile'"
:effect="tooltipEffect"
class="item"
content="混合模式"
placement="bottom"
popper-class="pure-tooltip"
>
<li
:class="layoutTheme.layout === 'mix' ? 'is-select' : ''"
ref="mixRef"
@click="setLayoutModel('mix')"
>
<div />
<div />
</li>
</el-tooltip>
</ul>
<el-divider>主题色</el-divider>
<ul class="theme-color"> <ul class="theme-color">
<li <li
v-for="(item, index) in themeColors" v-for="(item, index) in themeColors"
:key="index"
v-show="showThemeColors(item.themeColor)" v-show="showThemeColors(item.themeColor)"
:key="index"
:style="getThemeColorStyle(item.color)" :style="getThemeColorStyle(item.color)"
@click="setLayoutThemeColor(item.themeColor)" @click="setLayoutThemeColor(item.themeColor)"
> >
@ -319,14 +267,62 @@ onBeforeMount(() => {
</li> </li>
</ul> </ul>
<el-divider>界面显示</el-divider> <p class="mt-5 mb-3 font-medium text-sm dark:text-white">导航模式</p>
<ul class="pure-theme">
<li
ref="verticalRef"
v-tippy="{
content: '左侧菜单',
zIndex: 41000
}"
:class="layoutTheme.layout === 'vertical' ? 'is-select' : ''"
@click="setLayoutModel('vertical')"
>
<div />
<div />
</li>
<li
v-if="device !== 'mobile'"
ref="horizontalRef"
v-tippy="{
content: '顶部菜单',
zIndex: 41000
}"
:class="layoutTheme.layout === 'horizontal' ? 'is-select' : ''"
@click="setLayoutModel('horizontal')"
>
<div />
<div />
</li>
<li
v-if="device !== 'mobile'"
ref="mixRef"
v-tippy="{
content: '混合菜单',
zIndex: 41000
}"
:class="layoutTheme.layout === 'mix' ? 'is-select' : ''"
@click="setLayoutModel('mix')"
>
<div />
<div />
</li>
</ul>
<p class="mt-5 mb-3 font-medium text-base dark:text-white">页签风格</p>
<Segmented
:modelValue="markValue === 'smart' ? 0 : 1"
:options="markOptions"
@change="onChange"
/>
<p class="mt-5 mb-1 font-medium text-sm dark:text-white">界面显示</p>
<ul class="setting"> <ul class="setting">
<li> <li>
<span class="dark:text-white">灰色模式</span> <span class="dark:text-white">灰色模式</span>
<el-switch <el-switch
v-model="settings.greyVal" v-model="settings.greyVal"
inline-prompt inline-prompt
inactive-color="#a6a6a6"
active-text="开" active-text="开"
inactive-text="关" inactive-text="关"
@change="greyChange" @change="greyChange"
@ -337,7 +333,6 @@ onBeforeMount(() => {
<el-switch <el-switch
v-model="settings.weakVal" v-model="settings.weakVal"
inline-prompt inline-prompt
inactive-color="#a6a6a6"
active-text="开" active-text="开"
inactive-text="关" inactive-text="关"
@change="weekChange" @change="weekChange"
@ -348,7 +343,6 @@ onBeforeMount(() => {
<el-switch <el-switch
v-model="settings.tabsVal" v-model="settings.tabsVal"
inline-prompt inline-prompt
inactive-color="#a6a6a6"
active-text="开" active-text="开"
inactive-text="关" inactive-text="关"
@change="tagsChange" @change="tagsChange"
@ -359,60 +353,35 @@ onBeforeMount(() => {
<el-switch <el-switch
v-model="settings.hideFooter" v-model="settings.hideFooter"
inline-prompt inline-prompt
inactive-color="#a6a6a6"
active-text="开" active-text="开"
inactive-text="关" inactive-text="关"
@change="hideFooterChange" @change="hideFooterChange"
/> />
</li> </li>
<li> <li>
<span class="dark:text-white">侧边栏Logo</span> <span class="dark:text-white">Logo</span>
<el-switch <el-switch
v-model="logoVal" v-model="logoVal"
inline-prompt inline-prompt
:active-value="true" :active-value="true"
:inactive-value="false" :inactive-value="false"
inactive-color="#a6a6a6"
active-text="开" active-text="开"
inactive-text="关" inactive-text="关"
@change="logoChange" @change="logoChange"
/> />
</li> </li>
<li> <li>
<span class="dark:text-white">标签页持久化</span> <span class="dark:text-white">页签持久化</span>
<el-switch <el-switch
v-model="settings.multiTagsCache" v-model="settings.multiTagsCache"
inline-prompt inline-prompt
inactive-color="#a6a6a6"
active-text="开" active-text="开"
inactive-text="关" inactive-text="关"
@change="multiTagsCacheChange" @change="multiTagsCacheChange"
/> />
</li> </li>
<li>
<span class="dark:text-white">标签风格</span>
<el-radio-group v-model="markValue" size="small" @change="onChange">
<el-radio label="card">卡片</el-radio>
<el-radio label="smart">灵动</el-radio>
</el-radio-group>
</li>
</ul> </ul>
</div>
<el-divider />
<el-button
type="danger"
style="width: 90%; margin: 24px 15px"
@click="onReset"
>
<IconifyIconOffline
:icon="Logout"
width="15"
height="15"
style="margin-right: 4px"
/>
清空缓存并返回登录页
</el-button>
</panel> </panel>
</template> </template>
@ -422,41 +391,41 @@ onBeforeMount(() => {
font-weight: 700; font-weight: 700;
} }
.is-select { :deep(.el-switch__core) {
border: 2px solid var(--el-color-primary); --el-switch-off-color: var(--pure-switch-off-color);
min-width: 36px;
height: 18px;
} }
.setting { :deep(.el-switch__core .el-switch__action) {
width: 100%; height: 14px;
}
.theme-color {
height: 20px;
li { li {
display: flex; float: left;
align-items: center; height: 20px;
justify-content: space-between; margin-right: 8px;
margin: 25px; cursor: pointer;
} border-radius: 4px;
}
.pure-datatheme { &:nth-child(1) {
display: block; border: 1px solid #ddd;
width: 100%; }
height: 50px; }
padding-top: 25px;
text-align: center;
} }
.pure-theme { .pure-theme {
display: flex; display: flex;
flex-wrap: wrap; gap: 12px;
justify-content: space-around;
width: 100%;
height: 50px;
margin-top: 25px;
li { li {
position: relative; position: relative;
width: 18%; width: 46px;
height: 45px; height: 36px;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
background: #f0f2f5; background: #f0f2f5;
@ -517,27 +486,17 @@ onBeforeMount(() => {
} }
} }
.theme-color { .is-select {
display: flex; border: 2px solid var(--el-color-primary);
justify-content: center; }
width: 100%;
height: 40px;
margin-top: 20px;
.setting {
li { li {
float: left; display: flex;
width: 20px; align-items: center;
height: 20px; justify-content: space-between;
margin-top: 8px; padding: 4px 0;
margin-right: 8px; font-size: 14px;
font-weight: 700;
text-align: center;
cursor: pointer;
border-radius: 2px;
&:nth-child(2) {
border: 1px solid #ddd;
}
} }
} }
</style> </style>

View File

@ -108,9 +108,9 @@ watch(
<el-breadcrumb class="!leading-[50px] select-none" separator="/"> <el-breadcrumb class="!leading-[50px] select-none" separator="/">
<transition-group name="breadcrumb"> <transition-group name="breadcrumb">
<el-breadcrumb-item <el-breadcrumb-item
class="!inline !items-stretch"
v-for="item in levelList" v-for="item in levelList"
:key="item.path" :key="item.path"
class="!inline !items-stretch"
> >
<a @click.prevent="handleLink(item)"> <a @click.prevent="handleLink(item)">
{{ transformI18n(item.meta.title) }} {{ transformI18n(item.meta.title) }}

View File

@ -48,8 +48,8 @@ nextTick(() => {
<span>{{ title }}</span> <span>{{ title }}</span>
</div> </div>
<el-menu <el-menu
router
ref="menuRef" ref="menuRef"
router
mode="horizontal" mode="horizontal"
class="horizontal-header-menu" class="horizontal-header-menu"
:default-active="defaultActive" :default-active="defaultActive"
@ -78,7 +78,7 @@ nextTick(() => {
:class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
@click="translationCh" @click="translationCh"
> >
<span class="check-zh" v-show="locale === 'zh'"> <span v-show="locale === 'zh'" class="check-zh">
<IconifyIconOffline :icon="Check" /> <IconifyIconOffline :icon="Check" />
</span> </span>
简体中文 简体中文
@ -88,7 +88,7 @@ nextTick(() => {
:class="['dark:!text-white', getDropdownItemClass(locale, 'en')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
@click="translationEn" @click="translationEn"
> >
<span class="check-en" v-show="locale === 'en'"> <span v-show="locale === 'en'" class="check-en">
<IconifyIconOffline :icon="Check" /> <IconifyIconOffline :icon="Check" />
</span> </span>
English English

View File

@ -66,6 +66,6 @@ const toggleClick = () => {
width: 100%; width: 100%;
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
box-shadow: 0 0 6px -2px var(--el-color-primary); box-shadow: 0 0 6px -3px var(--el-color-primary);
} }
</style> </style>

View File

@ -61,12 +61,12 @@ watch(
<template> <template>
<div <div
v-if="device !== 'mobile'" v-if="device !== 'mobile'"
class="horizontal-header"
v-loading="usePermissionStoreHook().wholeMenus.length === 0" v-loading="usePermissionStoreHook().wholeMenus.length === 0"
class="horizontal-header"
> >
<el-menu <el-menu
router
ref="menuRef" ref="menuRef"
router
mode="horizontal" mode="horizontal"
class="horizontal-header-menu" class="horizontal-header-menu"
:default-active="defaultActive" :default-active="defaultActive"
@ -111,7 +111,7 @@ watch(
:class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
@click="translationCh" @click="translationCh"
> >
<span class="check-zh" v-show="locale === 'zh'"> <span v-show="locale === 'zh'" class="check-zh">
<IconifyIconOffline :icon="Check" /> <IconifyIconOffline :icon="Check" />
</span> </span>
简体中文 简体中文
@ -121,7 +121,7 @@ watch(
:class="['dark:!text-white', getDropdownItemClass(locale, 'en')]" :class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
@click="translationEn" @click="translationEn"
> >
<span class="check-en" v-show="locale === 'en'"> <span v-show="locale === 'en'" class="check-en">
<IconifyIconOffline :icon="Check" /> <IconifyIconOffline :icon="Check" />
</span> </span>
English English

View File

@ -275,7 +275,6 @@ function resolvePath(routePath) {
{{ transformI18n(props.item.meta.title) }} {{ transformI18n(props.item.meta.title) }}
</span> </span>
<div <div
:style="getSubMenuDivStyle(props.item)"
v-if=" v-if="
!( !(
isCollapse && isCollapse &&
@ -283,6 +282,7 @@ function resolvePath(routePath) {
props.item.parentId === null props.item.parentId === null
) )
" "
:style="getSubMenuDivStyle(props.item)"
> >
<el-tooltip <el-tooltip
v-if="layout !== 'horizontal'" v-if="layout !== 'horizontal'"

View File

@ -518,15 +518,15 @@ onBeforeUnmount(() => {
</script> </script>
<template> <template>
<div ref="containerDom" class="tags-view" v-if="!showTags"> <div v-if="!showTags" ref="containerDom" class="tags-view">
<span v-show="isShowArrow" class="arrow-left"> <span v-show="isShowArrow" class="arrow-left">
<IconifyIconOffline :icon="ArrowLeftSLine" @click="handleScroll(200)" /> <IconifyIconOffline :icon="ArrowLeftSLine" @click="handleScroll(200)" />
</span> </span>
<div ref="scrollbarDom" class="scroll-container"> <div ref="scrollbarDom" class="scroll-container">
<div class="tab select-none" ref="tabDom" :style="getTabStyle"> <div ref="tabDom" class="tab select-none" :style="getTabStyle">
<div <div
:ref="'dynamic' + index"
v-for="(item, index) in multiTags" v-for="(item, index) in multiTags"
:ref="'dynamic' + index"
:key="index" :key="index"
:class="['scroll-item is-closable', linkIsActive(item)]" :class="['scroll-item is-closable', linkIsActive(item)]"
@contextmenu.prevent="openMenu(item, $event)" @contextmenu.prevent="openMenu(item, $event)"
@ -550,8 +550,8 @@ onBeforeUnmount(() => {
<IconifyIconOffline :icon="CloseBold" /> <IconifyIconOffline :icon="CloseBold" />
</span> </span>
<div <div
:ref="'schedule' + index"
v-if="showModel !== 'card'" v-if="showModel !== 'card'"
:ref="'schedule' + index"
:class="[scheduleIsActive(item)]" :class="[scheduleIsActive(item)]"
/> />
</div> </div>

View File

@ -46,11 +46,11 @@ onMounted(() => {
<template> <template>
<div <div
class="frame"
v-loading="loading" v-loading="loading"
class="frame"
:element-loading-text="t('status.hsLoad')" :element-loading-text="t('status.hsLoad')"
> >
<iframe :src="frameSrc" class="frame-iframe" ref="frameRef" /> <iframe ref="frameRef" :src="frameSrc" class="frame-iframe" />
</div> </div>
</template> </template>

View File

@ -1,9 +1,14 @@
import { ref } from "vue"; import { ref } from "vue";
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import { useLayout } from "./useLayout"; import { useLayout } from "./useLayout";
import { themeColorsType } from "../types"; import { removeToken } from "@/utils/auth";
import { useGlobal } from "@pureadmin/utils"; 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 { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { import {
darken, darken,
lighten, lighten,
@ -13,42 +18,53 @@ import {
export function useDataThemeChange() { export function useDataThemeChange() {
const { layoutTheme, layout } = useLayout(); const { layoutTheme, layout } = useLayout();
const themeColors = ref<Array<themeColorsType>>([ const themeColors = ref<Array<themeColorsType>>([
/* 道奇蓝(默认) */
{ color: "#1b2a47", themeColor: "default" },
/* 亮白色 */ /* 亮白色 */
{ color: "#ffffff", themeColor: "light" }, { color: "#ffffff", themeColor: "light" },
/* 道奇蓝 */
{ color: "#1b2a47", themeColor: "default" },
/* 深紫罗兰色 */
{ color: "#722ed1", themeColor: "saucePurple" },
/* 深粉色 */
{ color: "#eb2f96", themeColor: "pink" },
/* 猩红色 */ /* 猩红色 */
{ color: "#f5222d", themeColor: "dusk" }, { color: "#f5222d", themeColor: "dusk" },
/* 橙红色 */ /* 橙红色 */
{ color: "#fa541c", themeColor: "volcano" }, { color: "#fa541c", themeColor: "volcano" },
/* 金色 */
{ color: "#fadb14", themeColor: "yellow" },
/* 绿宝石 */ /* 绿宝石 */
{ color: "#13c2c2", themeColor: "mingQing" }, { color: "#13c2c2", themeColor: "mingQing" },
/* 酸橙绿 */ /* 酸橙绿 */
{ color: "#52c41a", themeColor: "auroraGreen" }, { color: "#52c41a", themeColor: "auroraGreen" }
/* 深粉色 */
{ color: "#eb2f96", themeColor: "pink" },
/* 深紫罗兰色 */
{ color: "#722ed1", themeColor: "saucePurple" }
]); ]);
const { $storage } = useGlobal<GlobalPropertiesApi>(); const { $storage } = useGlobal<GlobalPropertiesApi>();
const dataTheme = ref<boolean>($storage?.layout?.darkMode); const dataTheme = ref<boolean>($storage?.layout?.darkMode);
const body = document.documentElement as HTMLElement; const body = document.documentElement as HTMLElement;
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
const targetEl = target || document.body;
let { className } = targetEl;
className = className.replace(clsName, "").trim();
targetEl.className = flag ? `${className} ${clsName} ` : className;
}
/** 设置导航主题色 */ /** 设置导航主题色 */
function setLayoutThemeColor(theme = getConfig().Theme ?? "default") { function setLayoutThemeColor(
theme = getConfig().Theme ?? "light",
isClick = true
) {
layoutTheme.value.theme = theme; layoutTheme.value.theme = theme;
toggleTheme({ toggleTheme({
scopeName: `layout-theme-${theme}` scopeName: `layout-theme-${theme}`
}); });
// 如果非isClick保留之前的themeColor
const storageThemeColor = $storage.layout.themeColor;
$storage.layout = { $storage.layout = {
layout: layout.value, layout: layout.value,
theme, theme,
darkMode: dataTheme.value, darkMode: dataTheme.value,
sidebarStatus: $storage.layout?.sidebarStatus, sidebarStatus: $storage.layout?.sidebarStatus,
epThemeColor: $storage.layout?.epThemeColor epThemeColor: $storage.layout?.epThemeColor,
themeColor: isClick ? theme : storageThemeColor
}; };
if (theme === "default" || theme === "light") { if (theme === "default" || theme === "light") {
@ -78,27 +94,46 @@ export function useDataThemeChange() {
} }
}; };
/** 日间、夜间主题切换 */ /** 亮色、暗色整体风格切换 */
function dataThemeChange() { function dataThemeChange() {
/* 如果当前是light夜间主题默认切换到default主题 */
if (useEpThemeStoreHook().epTheme === "light" && dataTheme.value) { if (useEpThemeStoreHook().epTheme === "light" && dataTheme.value) {
setLayoutThemeColor("default"); setLayoutThemeColor("default", false);
} else { } else {
setLayoutThemeColor(useEpThemeStoreHook().epTheme); setLayoutThemeColor(useEpThemeStoreHook().epTheme, false);
} }
if (dataTheme.value) { if (dataTheme.value) {
document.documentElement.classList.add("dark"); document.documentElement.classList.add("dark");
} else { } else {
if ($storage.layout.themeColor === "light") {
setLayoutThemeColor("light", false);
}
document.documentElement.classList.remove("dark"); document.documentElement.classList.remove("dark");
} }
} }
/** 清空缓存并返回登录页 */
function onReset() {
removeToken();
storageLocal().clear();
const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
useAppStoreHook().setLayout(Layout);
setEpThemeColor(EpThemeColor);
useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
toggleClass(Grey, "html-grey", document.querySelector("html"));
toggleClass(Weak, "html-weakness", document.querySelector("html"));
router.push("/login");
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter();
}
return { return {
body, body,
dataTheme, dataTheme,
layoutTheme, layoutTheme,
themeColors, themeColors,
onReset,
toggleClass,
dataThemeChange, dataThemeChange,
setEpThemeColor, setEpThemeColor,
setLayoutThemeColor setLayoutThemeColor

View File

@ -24,10 +24,11 @@ export function useLayout() {
if (!$storage.layout) { if (!$storage.layout) {
$storage.layout = { $storage.layout = {
layout: $config?.Layout ?? "vertical", layout: $config?.Layout ?? "vertical",
theme: $config?.Theme ?? "default", theme: $config?.Theme ?? "light",
darkMode: $config?.DarkMode ?? false, darkMode: $config?.DarkMode ?? false,
sidebarStatus: $config?.SidebarStatus ?? true, sidebarStatus: $config?.SidebarStatus ?? true,
epThemeColor: $config?.EpThemeColor ?? "#409EFF" epThemeColor: $config?.EpThemeColor ?? "#409EFF",
themeColor: $config?.Theme ?? "light"
}; };
} }
/** 灰色模式、色弱模式、隐藏标签页 */ /** 灰色模式、色弱模式、隐藏标签页 */

View File

@ -2,10 +2,10 @@ import { storeToRefs } from "pinia";
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
import { routeMetaType } from "../types";
import userAvatar from "@/assets/user.jpg"; import userAvatar from "@/assets/user.jpg";
import { getTopMenu } from "@/router/utils"; import { getTopMenu } from "@/router/utils";
import { useGlobal } from "@pureadmin/utils"; import { useGlobal } from "@pureadmin/utils";
import type { routeMetaType } from "../types";
import { transformI18n } from "@/plugins/i18n"; import { transformI18n } from "@/plugins/i18n";
import { router, remainingPaths } from "@/router"; import { router, remainingPaths } from "@/router";
import { computed, type CSSProperties } from "vue"; import { computed, type CSSProperties } from "vue";

View File

@ -4,10 +4,10 @@ import {
computed, computed,
reactive, reactive,
onMounted, onMounted,
CSSProperties, type CSSProperties,
getCurrentInstance getCurrentInstance
} from "vue"; } from "vue";
import { tagsViewsType } from "../types"; import type { tagsViewsType } from "../types";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { transformI18n, $t } from "@/plugins/i18n"; import { transformI18n, $t } from "@/plugins/i18n";
import { responsiveStorageNameSpace } from "@/config"; import { responsiveStorageNameSpace } from "@/config";

View File

@ -68,7 +68,8 @@ function setTheme(layoutModel: string) {
theme: $storage.layout?.theme, theme: $storage.layout?.theme,
darkMode: $storage.layout?.darkMode, darkMode: $storage.layout?.darkMode,
sidebarStatus: $storage.layout?.sidebarStatus, sidebarStatus: $storage.layout?.sidebarStatus,
epThemeColor: $storage.layout?.epThemeColor epThemeColor: $storage.layout?.epThemeColor,
themeColor: $storage.layout?.themeColor
}; };
} }

View File

@ -2,10 +2,23 @@
* @description 使 * @description 使
*/ */
import { type multipleScopeVarsOptions } from "@pureadmin/theme"; import type { multipleScopeVarsOptions } from "@pureadmin/theme";
/** 预设主题色 */ /** 预设主题色 */
const themeColors = { const themeColors = {
/* 亮白色 */
light: {
subMenuActiveText: "#000000d9",
menuBg: "#fff",
menuHover: "#e0ebf6",
subMenuBg: "#fff",
subMenuActiveBg: "#e0ebf6",
menuText: "rgb(0 0 0 / 60%)",
sidebarLogo: "#fff",
menuTitleHover: "#000",
menuActiveBefore: "#4091f7"
},
/* 道奇蓝 */
default: { default: {
subMenuActiveText: "#fff", subMenuActiveText: "#fff",
menuBg: "#001529", menuBg: "#001529",
@ -17,72 +30,19 @@ const themeColors = {
menuTitleHover: "#fff", menuTitleHover: "#fff",
menuActiveBefore: "#4091f7" menuActiveBefore: "#4091f7"
}, },
light: { /* 深紫罗兰色 */
subMenuActiveText: "#409eff", saucePurple: {
menuBg: "#fff",
menuHover: "#e0ebf6",
subMenuBg: "#fff",
subMenuActiveBg: "#e0ebf6",
menuText: "#7a80b4",
sidebarLogo: "#fff",
menuTitleHover: "#000",
menuActiveBefore: "#4091f7"
},
dusk: {
subMenuActiveText: "#fff", subMenuActiveText: "#fff",
menuBg: "#2a0608", menuBg: "#130824",
menuHover: "#e13c39", menuHover: "#693ac9",
subMenuBg: "#000", subMenuBg: "#000",
subMenuActiveBg: "#e13c39", subMenuActiveBg: "#693ac9",
menuText: "rgb(254 254 254 / 65.1%)",
sidebarLogo: "#42090c",
menuTitleHover: "#fff",
menuActiveBefore: "#e13c39"
},
volcano: {
subMenuActiveText: "#fff",
menuBg: "#2b0e05",
menuHover: "#e85f33",
subMenuBg: "#0f0603",
subMenuActiveBg: "#e85f33",
menuText: "rgb(254 254 254 / 65%)",
sidebarLogo: "#441708",
menuTitleHover: "#fff",
menuActiveBefore: "#e85f33"
},
yellow: {
subMenuActiveText: "#d25f00",
menuBg: "#2b2503",
menuHover: "#f6da4d",
subMenuBg: "#0f0603",
subMenuActiveBg: "#f6da4d",
menuText: "rgb(254 254 254 / 65%)",
sidebarLogo: "#443b05",
menuTitleHover: "#fff",
menuActiveBefore: "#f6da4d"
},
mingQing: {
subMenuActiveText: "#fff",
menuBg: "#032121",
menuHover: "#59bfc1",
subMenuBg: "#000",
subMenuActiveBg: "#59bfc1",
menuText: "#7a80b4", menuText: "#7a80b4",
sidebarLogo: "#053434", sidebarLogo: "#1f0c38",
menuTitleHover: "#fff", menuTitleHover: "#fff",
menuActiveBefore: "#59bfc1" menuActiveBefore: "#693ac9"
},
auroraGreen: {
subMenuActiveText: "#fff",
menuBg: "#0b1e15",
menuHover: "#60ac80",
subMenuBg: "#000",
subMenuActiveBg: "#60ac80",
menuText: "#7a80b4",
sidebarLogo: "#112f21",
menuTitleHover: "#fff",
menuActiveBefore: "#60ac80"
}, },
/* 深粉色 */
pink: { pink: {
subMenuActiveText: "#fff", subMenuActiveText: "#fff",
menuBg: "#28081a", menuBg: "#28081a",
@ -94,16 +54,53 @@ const themeColors = {
menuTitleHover: "#fff", menuTitleHover: "#fff",
menuActiveBefore: "#d84493" menuActiveBefore: "#d84493"
}, },
saucePurple: { /* 猩红色 */
dusk: {
subMenuActiveText: "#fff", subMenuActiveText: "#fff",
menuBg: "#130824", menuBg: "#2a0608",
menuHover: "#693ac9", menuHover: "#e13c39",
subMenuBg: "#000", subMenuBg: "#000",
subMenuActiveBg: "#693ac9", subMenuActiveBg: "#e13c39",
menuText: "#7a80b4", menuText: "rgb(254 254 254 / 65.1%)",
sidebarLogo: "#1f0c38", sidebarLogo: "#42090c",
menuTitleHover: "#fff", menuTitleHover: "#fff",
menuActiveBefore: "#693ac9" menuActiveBefore: "#e13c39"
},
/* 橙红色 */
volcano: {
subMenuActiveText: "#fff",
menuBg: "#2b0e05",
menuHover: "#e85f33",
subMenuBg: "#0f0603",
subMenuActiveBg: "#e85f33",
menuText: "rgb(254 254 254 / 65%)",
sidebarLogo: "#441708",
menuTitleHover: "#fff",
menuActiveBefore: "#e85f33"
},
/* 绿宝石 */
mingQing: {
subMenuActiveText: "#fff",
menuBg: "#032121",
menuHover: "#59bfc1",
subMenuBg: "#000",
subMenuActiveBg: "#59bfc1",
menuText: "#7a80b4",
sidebarLogo: "#053434",
menuTitleHover: "#fff",
menuActiveBefore: "#59bfc1"
},
/* 酸橙绿 */
auroraGreen: {
subMenuActiveText: "#fff",
menuBg: "#0b1e15",
menuHover: "#60ac80",
subMenuBg: "#000",
subMenuActiveBg: "#60ac80",
menuText: "#7a80b4",
sidebarLogo: "#112f21",
menuTitleHover: "#fff",
menuActiveBefore: "#60ac80"
} }
}; };

View File

@ -4,9 +4,9 @@ import { setupStore } from "@/store";
import ElementPlus from "element-plus"; import ElementPlus from "element-plus";
import { useI18n } from "@/plugins/i18n"; import { useI18n } from "@/plugins/i18n";
import { getPlatformConfig } from "./config"; import { getPlatformConfig } from "./config";
import { createApp, Directive } from "vue";
import { MotionPlugin } from "@vueuse/motion"; import { MotionPlugin } from "@vueuse/motion";
import { useEcharts } from "@/plugins/echarts"; import { useEcharts } from "@/plugins/echarts";
import { createApp, type Directive } from "vue";
import { injectResponsiveStorage } from "@/utils/responsive"; import { injectResponsiveStorage } from "@/utils/responsive";
import Table from "@pureadmin/table"; import Table from "@pureadmin/table";
@ -45,6 +45,14 @@ app.component("FontIcon", FontIcon);
import { Auth } from "@/components/ReAuth"; import { Auth } from "@/components/ReAuth";
app.component("Auth", Auth); app.component("Auth", Auth);
// 全局注册`vue-tippy`
import "tippy.js/dist/tippy.css";
import "tippy.js/animations/perspective.css";
import VueTippy from "vue-tippy";
app.use(VueTippy, {
defaultProps: { animation: "perspective" }
});
getPlatformConfig(app).then(async config => { getPlatformConfig(app).then(async config => {
setupStore(app); setupStore(app);
app.use(router); app.use(router);

View File

@ -1,14 +0,0 @@
import { createProdMockServer } from "vite-plugin-mock/es/createProdMockServer";
const modules: Record<string, any> = import.meta.glob("../mock/*.ts", {
eager: true
});
const mockModules = [];
Object.keys(modules).forEach(key => {
mockModules.push(...modules[key].default);
});
export function setupProdMockServer() {
createProdMockServer(mockModules);
}

View File

@ -1,4 +1,4 @@
import { App, Component } from "vue"; import type { App, Component } from "vue";
import { import {
ElTag, ElTag,
ElAffix, ElAffix,

View File

@ -1,6 +1,6 @@
// 多组件库的国际化和本地项目国际化兼容 // 多组件库的国际化和本地项目国际化兼容
import { App, WritableComputedRef } from "vue";
import { type I18n, createI18n } from "vue-i18n"; import { type I18n, createI18n } from "vue-i18n";
import type { App, WritableComputedRef } from "vue";
import { responsiveStorageNameSpace } from "@/config"; import { responsiveStorageNameSpace } from "@/config";
import { storageLocal, isObject } from "@pureadmin/utils"; import { storageLocal, isObject } from "@pureadmin/utils";
@ -8,16 +8,20 @@ import { storageLocal, isObject } from "@pureadmin/utils";
import enLocale from "element-plus/dist/locale/en.mjs"; import enLocale from "element-plus/dist/locale/en.mjs";
import zhLocale from "element-plus/dist/locale/zh-cn.mjs"; import zhLocale from "element-plus/dist/locale/zh-cn.mjs";
function siphonI18n(prefix = "zh-CN") { const siphonI18n = (function () {
return Object.fromEntries( // 仅初始化一次国际化配置
let cache = Object.fromEntries(
Object.entries( Object.entries(
import.meta.glob("../../locales/*.y(a)?ml", { eager: true }) import.meta.glob("../../locales/*.y(a)?ml", { eager: true })
).map(([key, value]: any) => { ).map(([key, value]: any) => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)[1]; const matched = key.match(/([A-Za-z0-9-_]+)\./i)[1];
return [matched, value.default]; return [matched, value.default];
}) })
)[prefix]; );
} return (prefix = "zh-CN") => {
return cache[prefix];
};
})();
export const localesConfigs = { export const localesConfigs = {
zh: { zh: {
@ -33,7 +37,7 @@ export const localesConfigs = {
/** 获取对象中所有嵌套对象的key键并将它们用点号分割组成字符串 */ /** 获取对象中所有嵌套对象的key键并将它们用点号分割组成字符串 */
function getObjectKeys(obj) { function getObjectKeys(obj) {
const stack = []; const stack = [];
const keys = []; const keys: Set<string> = new Set();
stack.push({ obj, key: "" }); stack.push({ obj, key: "" });
@ -46,7 +50,7 @@ function getObjectKeys(obj) {
if (obj[k] && isObject(obj[k])) { if (obj[k] && isObject(obj[k])) {
stack.push({ obj: obj[k], key: newKey }); stack.push({ obj: obj[k], key: newKey });
} else { } else {
keys.push(newKey); keys.add(newKey);
} }
} }
} }
@ -54,6 +58,17 @@ function getObjectKeys(obj) {
return keys; return keys;
} }
/** 将展开的key缓存 */
const keysCache: Map<string, Set<string>> = new Map();
const flatI18n = (prefix = "zh-CN") => {
let cache = keysCache.get(prefix);
if (!cache) {
cache = getObjectKeys(siphonI18n(prefix));
keysCache.set(prefix, cache);
}
return cache;
};
/** /**
* locales文件夹下文件进行国际化匹配 * locales文件夹下文件进行国际化匹配
* @param message message * @param message message
@ -73,9 +88,9 @@ export function transformI18n(message: any = "") {
const key = message.match(/(\S*)\./)?.input; const key = message.match(/(\S*)\./)?.input;
if (key && getObjectKeys(siphonI18n("zh-CN")).find(item => item === key)) { if (key && flatI18n("zh-CN").has(key)) {
return i18n.global.t.call(i18n.global.locale, message); return i18n.global.t.call(i18n.global.locale, message);
} else if (!key && Object.keys(siphonI18n("zh-CN")).includes(message)) { } else if (!key && Object.hasOwn(siphonI18n("zh-CN"), message)) {
// 兼容非嵌套形式的国际化写法 // 兼容非嵌套形式的国际化写法
return i18n.global.t.call(i18n.global.locale, message); return i18n.global.t.call(i18n.global.locale, message);
} else { } else {

View File

@ -20,10 +20,10 @@ import {
formatFlatteningRoutes formatFlatteningRoutes
} from "./utils"; } from "./utils";
import { import {
Router, type Router,
createRouter, createRouter,
RouteRecordRaw, type RouteRecordRaw,
RouteComponent type RouteComponent
} from "vue-router"; } from "vue-router";
import { import {
type DataInfo, type DataInfo,

View File

@ -183,4 +183,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -18,4 +18,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -171,4 +171,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -20,4 +20,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -36,4 +36,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -19,4 +19,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -22,4 +22,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -19,4 +19,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -24,4 +24,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -21,4 +21,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -19,4 +19,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -79,4 +79,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -21,4 +21,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -21,4 +21,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -21,4 +21,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -39,4 +39,4 @@ export default [
rank: 103 rank: 103
} }
} }
] as Array<RouteConfigsTable>; ] satisfies Array<RouteConfigsTable>;

View File

@ -27,4 +27,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -28,4 +28,4 @@ export default {
} }
} }
] ]
} as RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -1,7 +1,7 @@
import { import {
RouterHistory, type RouterHistory,
RouteRecordRaw, type RouteRecordRaw,
RouteComponent, type RouteComponent,
createWebHistory, createWebHistory,
createWebHashHistory createWebHashHistory
} from "vue-router"; } from "vue-router";
@ -17,7 +17,7 @@ import {
isIncludeAllChildren isIncludeAllChildren
} from "@pureadmin/utils"; } from "@pureadmin/utils";
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import { menuType } from "@/layout/types"; import type { menuType } from "@/layout/types";
import { buildHierarchyTree } from "@/utils/tree"; import { buildHierarchyTree } from "@/utils/tree";
import { userKey, type DataInfo } from "@/utils/auth"; import { userKey, type DataInfo } from "@/utils/auth";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";

View File

@ -1,6 +1,6 @@
import { store } from "@/store"; import { store } from "@/store";
import { appType } from "./types";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import type { appType } from "./types";
import { getConfig, responsiveStorageNameSpace } from "@/config"; import { getConfig, responsiveStorageNameSpace } from "@/config";
import { deviceDetection, storageLocal } from "@pureadmin/utils"; import { deviceDetection, storageLocal } from "@pureadmin/utils";

View File

@ -23,8 +23,6 @@ export const useEpThemeStore = defineStore({
fill(state) { fill(state) {
if (state.epTheme === "light") { if (state.epTheme === "light") {
return "#409eff"; return "#409eff";
} else if (state.epTheme === "yellow") {
return "#d25f00";
} else { } else {
return "#fff"; return "#fff";
} }

View File

@ -1,8 +1,8 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { store } from "@/store"; import { store } from "@/store";
import { routerArrays } from "@/layout/types"; import { routerArrays } from "@/layout/types";
import { multiType, positionType } from "./types";
import { responsiveStorageNameSpace } from "@/config"; import { responsiveStorageNameSpace } from "@/config";
import type { multiType, positionType } from "./types";
import { isEqual, isBoolean, isUrl, storageLocal } from "@pureadmin/utils"; import { isEqual, isBoolean, isUrl, storageLocal } from "@pureadmin/utils";
export const useMultiTagsStore = defineStore({ export const useMultiTagsStore = defineStore({

View File

@ -1,6 +1,6 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { store } from "@/store"; import { store } from "@/store";
import { cacheType } from "./types"; import type { cacheType } from "./types";
import { constantMenus } from "@/router"; import { constantMenus } from "@/router";
import { useMultiTagsStoreHook } from "./multiTags"; import { useMultiTagsStoreHook } from "./multiTags";
import { debounce, getKeyList } from "@pureadmin/utils"; import { debounce, getKeyList } from "@pureadmin/utils";

View File

@ -1,7 +1,7 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { store } from "@/store"; import { store } from "@/store";
import { setType } from "./types";
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import type { setType } from "./types";
export const useSettingStore = defineStore({ export const useSettingStore = defineStore({
id: "pure-setting", id: "pure-setting",

View File

@ -1,4 +1,4 @@
import { RouteRecordName } from "vue-router"; import type { RouteRecordName } from "vue-router";
export type cacheType = { export type cacheType = {
mode: string; mode: string;

View File

@ -1,11 +1,11 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { store } from "@/store"; import { store } from "@/store";
import { userType } from "./types"; import type { userType } from "./types";
import { routerArrays } from "@/layout/types"; import { routerArrays } from "@/layout/types";
import { router, resetRouter } from "@/router"; import { router, resetRouter } from "@/router";
import { storageLocal } from "@pureadmin/utils"; import { storageLocal } from "@pureadmin/utils";
import { getLogin, refreshTokenApi } from "@/api/user"; import { getLogin, refreshTokenApi } from "@/api/user";
import { UserResult, RefreshTokenResult } from "@/api/user"; import type { UserResult, RefreshTokenResult } from "@/api/user";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth"; import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth";

View File

@ -2,11 +2,18 @@
/* 暗黑模式适配 */ /* 暗黑模式适配 */
html.dark { html.dark {
/* 自定义深色背景颜色 */
// --el-bg-color: #020409;
$border-style: #303030; $border-style: #303030;
$color-white: #fff; $color-white: #fff;
/* 自定义深色背景颜色 */
// --el-bg-color: #020409;
/* 常用border-color 需要时可取用 */
--pure-border-color: rgb(253 253 253 / 12%);
/* switch关闭状态下的color 需要时可取用 */
--pure-switch-off-color: #ffffff3f;
.navbar, .navbar,
.tags-view, .tags-view,
.contextmenu, .contextmenu,

View File

@ -50,12 +50,6 @@
padding: 0 !important; padding: 0 !important;
} }
/* 自定义 tooltip 的类名 */
.pure-tooltip {
// 右侧操作面板right-panel类名的z-index为40000tooltip需要大于它才能显示
z-index: 41000 !important;
}
/* nprogress 适配 element-plus 的主题色 */ /* nprogress 适配 element-plus 的主题色 */
#nprogress { #nprogress {
& .bar { & .bar {

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