xiaoming
9 months ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
156 changed files with 7995 additions and 8592 deletions
-
4.browserslistrc
-
2.env.development
-
2.env.production
-
2.env.staging
-
2.eslintignore
-
1.gitignore
-
2.husky/commit-msg
-
8.husky/lintstagedrc.js
-
6.husky/pre-commit
-
16.lintstagedrc
-
1.nvmrc
-
5.prettierrc.js
-
2.vscode/settings.json
-
4Dockerfile
-
2README.en-US.md
-
2README.md
-
1build/cdn.ts
-
31build/index.ts
-
38build/info.ts
-
4build/optimize.ts
-
54build/plugins.ts
-
110build/utils.ts
-
5commitlint.config.js
-
174eslint.config.js
-
2locales/en.yaml
-
2locales/zh-CN.yaml
-
17mock/asyncRoutes.ts
-
10mock/login.ts
-
10mock/refreshToken.ts
-
207package.json
-
10647pnpm-lock.yaml
-
5postcss.config.js
-
2public/logo.svg
-
11public/platform-config.json
-
4src/App.vue
-
2src/api/routes.ts
-
2src/api/user.ts
-
3src/assets/iconfont/iconfont.css
-
2src/assets/login/avatar.svg
-
2src/assets/login/illustration.svg
-
2src/assets/status/403.svg
-
2src/assets/status/404.svg
-
2src/assets/status/500.svg
-
2src/assets/svg/back_top.svg
-
2src/assets/svg/dark.svg
-
2src/assets/svg/day.svg
-
2src/assets/svg/enter_outlined.svg
-
2src/assets/svg/exit_screen.svg
-
2src/assets/svg/full_screen.svg
-
2src/assets/svg/keyboard_esc.svg
-
1src/assets/svg/system.svg
-
7src/components/ReDialog/index.ts
-
10src/components/ReDialog/index.vue
-
6src/components/ReDialog/type.ts
-
6src/components/ReIcon/src/hooks.ts
-
2src/components/ReIcon/src/iconifyIconOffline.ts
-
20src/components/ReIcon/src/offlineIcon.ts
-
5src/components/ReIcon/src/types.ts
-
177src/components/RePureTableBar/src/bar.tsx
-
2src/components/RePureTableBar/src/svg/collapse.svg
-
2src/components/RePureTableBar/src/svg/drag.svg
-
2src/components/RePureTableBar/src/svg/expand.svg
-
2src/components/RePureTableBar/src/svg/settings.svg
-
8src/components/ReSegmented/index.ts
-
80src/components/ReSegmented/src/index.css
-
166src/components/ReSegmented/src/index.tsx
-
20src/components/ReSegmented/src/type.ts
-
7src/components/ReText/index.ts
-
62src/components/ReText/src/index.vue
-
10src/config/index.ts
-
1src/directives/index.ts
-
48src/directives/ripple/index.scss
-
234src/directives/ripple/index.ts
-
170src/layout/components/appMain.vue
-
31src/layout/components/footer/index.vue
-
80src/layout/components/keepAliveFrame/index.vue
-
25src/layout/components/keepAliveFrame/useMultiFrame.ts
-
10src/layout/components/navbar.vue
-
4src/layout/components/notice/data.ts
-
2src/layout/components/notice/index.vue
-
4src/layout/components/notice/noticeList.vue
-
121src/layout/components/panel/index.vue
-
8src/layout/components/search/components/SearchFooter.vue
-
198src/layout/components/search/components/SearchHistory.vue
-
53src/layout/components/search/components/SearchHistoryItem.vue
-
226src/layout/components/search/components/SearchModal.vue
-
27src/layout/components/search/components/SearchResult.vue
-
15src/layout/components/search/index.vue
-
20src/layout/components/search/types.ts
-
503src/layout/components/setting/index.vue
-
26src/layout/components/sidebar/breadCrumb.vue
-
12src/layout/components/sidebar/horizontal.vue
-
46src/layout/components/sidebar/leftCollapse.vue
-
36src/layout/components/sidebar/linkItem.vue
-
7src/layout/components/sidebar/logo.vue
-
11src/layout/components/sidebar/mixNav.vue
-
271src/layout/components/sidebar/sidebarItem.vue
-
15src/layout/components/sidebar/vertical.vue
-
93src/layout/components/tag/index.scss
-
94src/layout/components/tag/index.vue
@ -0,0 +1,4 @@ |
|||||
|
> 1% |
||||
|
last 2 versions |
||||
|
not dead |
||||
|
not ie 11 |
@ -1,8 +0,0 @@ |
|||||
module.exports = { |
|
||||
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"], |
|
||||
"{!(package)*.json}": ["prettier --write--parser json"], |
|
||||
"package.json": ["prettier --write"], |
|
||||
"*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"], |
|
||||
"*.{vue,css,scss,postcss,less}": ["stylelint --fix", "prettier --write"], |
|
||||
"*.md": ["prettier --write"] |
|
||||
}; |
|
@ -0,0 +1,16 @@ |
|||||
|
{ |
||||
|
"*.{js,jsx,ts,tsx}": [ |
||||
|
"prettier --cache --ignore-unknown --write", |
||||
|
"eslint --cache --fix" |
||||
|
], |
||||
|
"{!(package)*.json,*.code-snippets,.!({browserslist,nvm})*rc}": [ |
||||
|
"prettier --cache --write--parser json" |
||||
|
], |
||||
|
"package.json": ["prettier --cache --write"], |
||||
|
"*.vue": ["prettier --write", "eslint --cache --fix", "stylelint --fix"], |
||||
|
"*.{css,scss,html}": [ |
||||
|
"prettier --cache --ignore-unknown --write", |
||||
|
"stylelint --fix" |
||||
|
], |
||||
|
"*.md": ["prettier --cache --ignore-unknown --write"] |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
v20.11.1 |
@ -1,31 +0,0 @@ |
|||||
/** 处理环境变量 */ |
|
||||
const warpperEnv = (envConf: Recordable): ViteEnv => { |
|
||||
/** 此处为默认值 */ |
|
||||
const ret: ViteEnv = { |
|
||||
VITE_PORT: 8848, |
|
||||
VITE_PUBLIC_PATH: "", |
|
||||
VITE_ROUTER_HISTORY: "", |
|
||||
VITE_CDN: false, |
|
||||
VITE_HIDE_HOME: "false", |
|
||||
VITE_COMPRESSION: "none" |
|
||||
}; |
|
||||
|
|
||||
for (const envName of Object.keys(envConf)) { |
|
||||
let realName = envConf[envName].replace(/\\n/g, "\n"); |
|
||||
realName = |
|
||||
realName === "true" ? true : realName === "false" ? false : realName; |
|
||||
|
|
||||
if (envName === "VITE_PORT") { |
|
||||
realName = Number(realName); |
|
||||
} |
|
||||
ret[envName] = realName; |
|
||||
if (typeof realName === "string") { |
|
||||
process.env[envName] = realName; |
|
||||
} else if (typeof realName === "object") { |
|
||||
process.env[envName] = JSON.stringify(realName); |
|
||||
} |
|
||||
} |
|
||||
return ret; |
|
||||
}; |
|
||||
|
|
||||
export { warpperEnv }; |
|
@ -0,0 +1,110 @@ |
|||||
|
import dayjs from "dayjs"; |
||||
|
import { readdir, stat } from "node:fs"; |
||||
|
import { fileURLToPath } from "node:url"; |
||||
|
import { dirname, resolve } from "node:path"; |
||||
|
import { sum, formatBytes } from "@pureadmin/utils"; |
||||
|
import { |
||||
|
name, |
||||
|
version, |
||||
|
engines, |
||||
|
dependencies, |
||||
|
devDependencies |
||||
|
} from "../package.json"; |
||||
|
|
||||
|
/** 启动`node`进程时所在工作目录的绝对路径 */ |
||||
|
const root: string = process.cwd(); |
||||
|
|
||||
|
/** |
||||
|
* @description 根据可选的路径片段生成一个新的绝对路径 |
||||
|
* @param dir 路径片段,默认`build` |
||||
|
* @param metaUrl 模块的完整`url`,如果在`build`目录外调用必传`import.meta.url` |
||||
|
*/ |
||||
|
const pathResolve = (dir = ".", metaUrl = import.meta.url) => { |
||||
|
// 当前文件目录的绝对路径
|
||||
|
const currentFileDir = dirname(fileURLToPath(metaUrl)); |
||||
|
// build 目录的绝对路径
|
||||
|
const buildDir = resolve(currentFileDir, "build"); |
||||
|
// 解析的绝对路径
|
||||
|
const resolvedPath = resolve(currentFileDir, dir); |
||||
|
// 检查解析的绝对路径是否在 build 目录内
|
||||
|
if (resolvedPath.startsWith(buildDir)) { |
||||
|
// 在 build 目录内,返回当前文件路径
|
||||
|
return fileURLToPath(metaUrl); |
||||
|
} |
||||
|
// 不在 build 目录内,返回解析后的绝对路径
|
||||
|
return resolvedPath; |
||||
|
}; |
||||
|
|
||||
|
/** 设置别名 */ |
||||
|
const alias: Record<string, string> = { |
||||
|
"@": pathResolve("../src"), |
||||
|
"@build": pathResolve() |
||||
|
}; |
||||
|
|
||||
|
/** 平台的名称、版本、运行所需的`node`和`pnpm`版本、依赖、最后构建时间的类型提示 */ |
||||
|
const __APP_INFO__ = { |
||||
|
pkg: { name, version, engines, dependencies, devDependencies }, |
||||
|
lastBuildTime: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss") |
||||
|
}; |
||||
|
|
||||
|
/** 处理环境变量 */ |
||||
|
const warpperEnv = (envConf: Recordable): ViteEnv => { |
||||
|
// 默认值
|
||||
|
const ret: ViteEnv = { |
||||
|
VITE_PORT: 8848, |
||||
|
VITE_PUBLIC_PATH: "", |
||||
|
VITE_ROUTER_HISTORY: "", |
||||
|
VITE_CDN: false, |
||||
|
VITE_HIDE_HOME: "false", |
||||
|
VITE_COMPRESSION: "none" |
||||
|
}; |
||||
|
|
||||
|
for (const envName of Object.keys(envConf)) { |
||||
|
let realName = envConf[envName].replace(/\\n/g, "\n"); |
||||
|
realName = |
||||
|
realName === "true" ? true : realName === "false" ? false : realName; |
||||
|
|
||||
|
if (envName === "VITE_PORT") { |
||||
|
realName = Number(realName); |
||||
|
} |
||||
|
ret[envName] = realName; |
||||
|
if (typeof realName === "string") { |
||||
|
process.env[envName] = realName; |
||||
|
} else if (typeof realName === "object") { |
||||
|
process.env[envName] = JSON.stringify(realName); |
||||
|
} |
||||
|
} |
||||
|
return ret; |
||||
|
}; |
||||
|
|
||||
|
const fileListTotal: number[] = []; |
||||
|
|
||||
|
/** 获取指定文件夹中所有文件的总大小 */ |
||||
|
const getPackageSize = options => { |
||||
|
const { folder = "dist", callback, format = true } = options; |
||||
|
readdir(folder, (err, files: string[]) => { |
||||
|
if (err) throw err; |
||||
|
let count = 0; |
||||
|
const checkEnd = () => { |
||||
|
++count == files.length && |
||||
|
callback(format ? formatBytes(sum(fileListTotal)) : sum(fileListTotal)); |
||||
|
}; |
||||
|
files.forEach((item: string) => { |
||||
|
stat(`${folder}/${item}`, async (err, stats) => { |
||||
|
if (err) throw err; |
||||
|
if (stats.isFile()) { |
||||
|
fileListTotal.push(stats.size); |
||||
|
checkEnd(); |
||||
|
} else if (stats.isDirectory()) { |
||||
|
getPackageSize({ |
||||
|
folder: `${folder}/${item}/`, |
||||
|
callback: checkEnd |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
files.length === 0 && callback(0); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
export { root, pathResolve, alias, __APP_INFO__, warpperEnv, getPackageSize }; |
@ -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" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
]); |
@ -1,7 +1,8 @@ |
|||||
{ |
{ |
||||
"name": "pure-admin-thin", |
"name": "pure-admin-thin", |
||||
"version": "4.5.0", |
|
||||
|
"version": "5.1.0", |
||||
"private": true, |
"private": true, |
||||
|
"type": "module", |
||||
"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", |
||||
@ -11,127 +12,139 @@ |
|||||
"preview": "vite preview", |
"preview": "vite preview", |
||||
"preview:build": "pnpm build && vite preview", |
"preview:build": "pnpm build && vite preview", |
||||
"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 . -r", |
||||
"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 node_modules && rimraf .eslintcache && 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 \"**/*.{html,vue,css,scss}\" --fix --cache --cache-location node_modules/.cache/stylelint/", |
|
||||
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", |
|
||||
"lint:pretty": "pretty-quick --staged", |
|
||||
|
"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", |
|
||||
|
"prepare": "husky", |
||||
"preinstall": "npx only-allow pnpm" |
"preinstall": "npx only-allow pnpm" |
||||
}, |
}, |
||||
"browserslist": [ |
|
||||
"> 1%", |
|
||||
"not ie 11", |
|
||||
"not op_mini all" |
|
||||
|
"keywords": [ |
||||
|
"vue-pure-admin", |
||||
|
"element-plus", |
||||
|
"tailwindcss", |
||||
|
"pure-admin", |
||||
|
"typescript", |
||||
|
"pinia", |
||||
|
"vue3", |
||||
|
"vite", |
||||
|
"esm" |
||||
], |
], |
||||
|
"homepage": "https://github.com/pure-admin/pure-admin-thin/tree/i18n", |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "git+https://github.com/pure-admin/pure-admin-thin.git" |
||||
|
}, |
||||
|
"bugs": { |
||||
|
"url": "https://github.com/pure-admin/vue-pure-admin/issues" |
||||
|
}, |
||||
|
"license": "MIT", |
||||
|
"author": { |
||||
|
"name": "xiaoxian521", |
||||
|
"email": "[email protected]", |
||||
|
"url": "https://github.com/xiaoxian521" |
||||
|
}, |
||||
"dependencies": { |
"dependencies": { |
||||
"@pureadmin/descriptions": "^1.1.1", |
|
||||
"@pureadmin/table": "^2.3.2", |
|
||||
"@pureadmin/utils": "^1.9.6", |
|
||||
"@vueuse/core": "^10.2.0", |
|
||||
"@vueuse/motion": "^2.0.0", |
|
||||
|
"@pureadmin/descriptions": "^1.2.0", |
||||
|
"@pureadmin/table": "^3.1.2", |
||||
|
"@pureadmin/utils": "^2.4.5", |
||||
|
"@vueuse/core": "^10.9.0", |
||||
|
"@vueuse/motion": "^2.1.0", |
||||
"animate.css": "^4.1.1", |
"animate.css": "^4.1.1", |
||||
"axios": "^1.4.0", |
|
||||
"dayjs": "^1.11.8", |
|
||||
"echarts": "^5.4.2", |
|
||||
"element-plus": "2.3.6", |
|
||||
|
"axios": "^1.6.7", |
||||
|
"dayjs": "^1.11.10", |
||||
|
"echarts": "^5.5.0", |
||||
|
"element-plus": "^2.6.0", |
||||
"js-cookie": "^3.0.5", |
"js-cookie": "^3.0.5", |
||||
"mitt": "^3.0.0", |
|
||||
"mockjs": "^1.1.0", |
|
||||
|
"localforage": "^1.10.0", |
||||
|
"mitt": "^3.0.1", |
||||
"nprogress": "^0.2.0", |
"nprogress": "^0.2.0", |
||||
"path": "^0.12.7", |
"path": "^0.12.7", |
||||
"pinia": "^2.1.4", |
|
||||
"pinyin-pro": "^3.15.2", |
|
||||
|
"pinia": "^2.1.7", |
||||
|
"pinyin-pro": "^3.19.6", |
||||
"qs": "^6.11.2", |
"qs": "^6.11.2", |
||||
"responsive-storage": "^2.2.0", |
"responsive-storage": "^2.2.0", |
||||
"sortablejs": "^1.15.0", |
|
||||
"vue": "^3.3.4", |
|
||||
"vue-i18n": "^9.2.2", |
|
||||
"vue-router": "^4.2.2", |
|
||||
"vue-types": "^5.1.0" |
|
||||
|
"sortablejs": "^1.15.2", |
||||
|
"vue": "^3.4.21", |
||||
|
"vue-i18n": "^9.10.1", |
||||
|
"vue-router": "^4.3.0", |
||||
|
"vue-tippy": "^6.4.1", |
||||
|
"vue-types": "^5.1.1" |
||||
}, |
}, |
||||
"devDependencies": { |
"devDependencies": { |
||||
"@commitlint/cli": "^17.6.6", |
|
||||
"@commitlint/config-conventional": "^17.6.6", |
|
||||
|
"@commitlint/cli": "^18.6.1", |
||||
|
"@commitlint/config-conventional": "^18.6.2", |
||||
|
"@commitlint/types": "^18.6.1", |
||||
|
"@eslint/js": "^8.57.0", |
||||
|
"@faker-js/faker": "^8.4.1", |
||||
"@iconify-icons/ep": "^1.2.12", |
"@iconify-icons/ep": "^1.2.12", |
||||
"@iconify-icons/ri": "^1.2.9", |
|
||||
|
"@iconify-icons/ri": "^1.2.10", |
||||
"@iconify/vue": "^4.1.1", |
"@iconify/vue": "^4.1.1", |
||||
"@intlify/unplugin-vue-i18n": "^0.11.0", |
|
||||
"@pureadmin/theme": "^3.1.0", |
|
||||
"@types/js-cookie": "^3.0.3", |
|
||||
"@types/mockjs": "^1.0.7", |
|
||||
"@types/node": "^20.3.1", |
|
||||
"@types/nprogress": "0.2.0", |
|
||||
"@types/qs": "^6.9.7", |
|
||||
"@types/sortablejs": "^1.15.1", |
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.0", |
|
||||
"@typescript-eslint/parser": "^5.60.0", |
|
||||
"@vitejs/plugin-vue": "^4.2.3", |
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1", |
|
||||
"@vue/eslint-config-prettier": "^7.1.0", |
|
||||
"@vue/eslint-config-typescript": "^11.0.3", |
|
||||
"autoprefixer": "^10.4.14", |
|
||||
|
"@intlify/unplugin-vue-i18n": "^2.0.0", |
||||
|
"@pureadmin/theme": "^3.2.0", |
||||
|
"@types/gradient-string": "^1.1.5", |
||||
|
"@types/js-cookie": "^3.0.6", |
||||
|
"@types/node": "^20.11.24", |
||||
|
"@types/nprogress": "^0.2.3", |
||||
|
"@types/qs": "^6.9.12", |
||||
|
"@types/sortablejs": "^1.15.8", |
||||
|
"@typescript-eslint/eslint-plugin": "^7.1.1", |
||||
|
"@typescript-eslint/parser": "^7.1.1", |
||||
|
"@vitejs/plugin-vue": "^5.0.4", |
||||
|
"@vitejs/plugin-vue-jsx": "^3.1.0", |
||||
|
"autoprefixer": "^10.4.18", |
||||
|
"boxen": "^7.1.1", |
||||
"cloc": "^2.11.0", |
"cloc": "^2.11.0", |
||||
"cssnano": "^6.0.1", |
|
||||
"eslint": "^8.43.0", |
|
||||
"eslint-plugin-prettier": "^4.2.1", |
|
||||
"eslint-plugin-vue": "^9.15.1", |
|
||||
"husky": "^8.0.3", |
|
||||
"lint-staged": "^13.2.2", |
|
||||
"picocolors": "^1.0.0", |
|
||||
"postcss": "^8.4.24", |
|
||||
"postcss-html": "^1.5.0", |
|
||||
"postcss-import": "^15.1.0", |
|
||||
"postcss-scss": "^4.0.6", |
|
||||
"prettier": "^2.8.8", |
|
||||
"pretty-quick": "^3.1.3", |
|
||||
"rimraf": "^5.0.1", |
|
||||
"rollup-plugin-visualizer": "^5.9.2", |
|
||||
"sass": "^1.63.6", |
|
||||
"sass-loader": "^13.3.2", |
|
||||
"stylelint": "^15.9.0", |
|
||||
"stylelint-config-html": "^1.1.0", |
|
||||
"stylelint-config-recess-order": "^4.2.0", |
|
||||
"stylelint-config-recommended": "^12.0.0", |
|
||||
"stylelint-config-recommended-scss": "^12.0.0", |
|
||||
"stylelint-config-recommended-vue": "^1.4.0", |
|
||||
"stylelint-config-standard": "^33.0.0", |
|
||||
"stylelint-config-standard-scss": "^9.0.0", |
|
||||
"stylelint-order": "^6.0.3", |
|
||||
"stylelint-prettier": "^3.0.0", |
|
||||
"stylelint-scss": "^5.0.1", |
|
||||
"svgo": "^3.0.2", |
|
||||
"tailwindcss": "^3.3.2", |
|
||||
"terser": "^5.18.1", |
|
||||
"typescript": "5.0.4", |
|
||||
"vite": "^4.3.9", |
|
||||
|
"cssnano": "^6.0.5", |
||||
|
"eslint": "^8.57.0", |
||||
|
"eslint-config-prettier": "^9.1.0", |
||||
|
"eslint-define-config": "^2.1.0", |
||||
|
"eslint-plugin-prettier": "^5.1.3", |
||||
|
"eslint-plugin-vue": "^9.22.0", |
||||
|
"gradient-string": "^2.0.2", |
||||
|
"husky": "^9.0.11", |
||||
|
"lint-staged": "^15.2.2", |
||||
|
"postcss": "^8.4.35", |
||||
|
"postcss-html": "^1.6.0", |
||||
|
"postcss-import": "^16.0.1", |
||||
|
"postcss-scss": "^4.0.9", |
||||
|
"prettier": "^3.2.5", |
||||
|
"rimraf": "^5.0.5", |
||||
|
"rollup-plugin-visualizer": "^5.12.0", |
||||
|
"sass": "^1.71.1", |
||||
|
"stylelint": "^16.2.1", |
||||
|
"stylelint-config-recess-order": "^5.0.0", |
||||
|
"stylelint-config-recommended-vue": "^1.5.0", |
||||
|
"stylelint-config-standard-scss": "^13.0.0", |
||||
|
"stylelint-prettier": "^5.0.0", |
||||
|
"svgo": "^3.2.0", |
||||
|
"tailwindcss": "^3.4.1", |
||||
|
"typescript": "^5.3.3", |
||||
|
"vite": "^5.1.5", |
||||
"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-remove-console": "^2.1.1", |
|
||||
"vite-svg-loader": "^4.0.0", |
|
||||
"vue-eslint-parser": "^9.3.1", |
|
||||
"vue-tsc": "^1.8.1" |
|
||||
|
"vite-plugin-fake-server": "^2.1.1", |
||||
|
"vite-plugin-remove-console": "^2.2.0", |
||||
|
"vite-plugin-router-warn": "^1.0.0", |
||||
|
"vite-svg-loader": "^5.1.0", |
||||
|
"vue-eslint-parser": "^9.4.2", |
||||
|
"vue-tsc": "^1.8.27" |
||||
}, |
}, |
||||
|
"engines": { |
||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0", |
||||
|
"pnpm": ">=8.6.10" |
||||
|
}, |
||||
|
"packageManager": "[email protected]", |
||||
"pnpm": { |
"pnpm": { |
||||
"peerDependencyRules": { |
|
||||
"ignoreMissing": [ |
|
||||
"rollup", |
|
||||
"webpack", |
|
||||
"core-js" |
|
||||
] |
|
||||
}, |
|
||||
"allowedDeprecatedVersions": { |
"allowedDeprecatedVersions": { |
||||
"sourcemap-codec": "*", |
"sourcemap-codec": "*", |
||||
|
"domexception": "*", |
||||
"w3c-hr-time": "*", |
"w3c-hr-time": "*", |
||||
"stable": "*" |
|
||||
|
"stable": "*", |
||||
|
"abab": "*" |
||||
} |
} |
||||
}, |
|
||||
"repository": "[email protected]:pure-admin/pure-admin-thin.git", |
|
||||
"author": "xiaoxian521", |
|
||||
"license": "MIT" |
|
||||
|
} |
||||
} |
} |
10647
pnpm-lock.yaml
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1 +1 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109z"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665z"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.114 323.114 0 0 1-107.769-242.852z"/></svg> |
|
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.1 323.1 0 0 1-107.769-242.852z"/></svg> |
@ -1 +1 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109z"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665z"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.114 323.114 0 0 1-107.769-242.852z"/></svg> |
|
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.1 323.1 0 0 1-107.769-242.852z"/></svg> |
2
src/assets/login/illustration.svg
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
src/assets/status/403.svg
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
src/assets/status/404.svg
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
src/assets/status/500.svg
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1 +1 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M2.88 18.054a35.897 35.897 0 0 1 8.531-16.32.8.8 0 0 1 1.178 0c.166.18.304.332.413.455a35.897 35.897 0 0 1 8.118 15.865c-2.141.451-4.34.747-6.584.874l-2.089 4.178a.5.5 0 0 1-.894 0l-2.089-4.178a44.019 44.019 0 0 1-6.584-.874zm6.698-1.123 1.157.066L12 19.527l1.265-2.53 1.157-.066a42.137 42.137 0 0 0 4.227-.454A33.913 33.913 0 0 0 12 4.09a33.913 33.913 0 0 0-6.649 12.387c1.395.222 2.805.374 4.227.454zM12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></svg> |
|
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M2.88 18.054a35.9 35.9 0 0 1 8.531-16.32.8.8 0 0 1 1.178 0q.25.27.413.455a35.9 35.9 0 0 1 8.118 15.865c-2.141.451-4.34.747-6.584.874l-2.089 4.178a.5.5 0 0 1-.894 0l-2.089-4.178a44 44 0 0 1-6.584-.874m6.698-1.123 1.157.066L12 19.527l1.265-2.53 1.157-.066a42 42 0 0 0 4.227-.454A33.9 33.9 0 0 0 12 4.09a33.9 33.9 0 0 0-6.649 12.387q2.093.334 4.227.454M12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6m0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg> |
@ -1 +1 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.38 2.019a7.5 7.5 0 1 0 10.6 10.6C21.662 17.854 17.316 22 12.001 22 6.477 22 2 17.523 2 12c0-5.315 4.146-9.661 9.38-9.981z"/></svg> |
|
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.38 2.019a7.5 7.5 0 1 0 10.6 10.6C21.662 17.854 17.316 22 12.001 22 6.477 22 2 17.523 2 12c0-5.315 4.146-9.661 9.38-9.981"/></svg> |
@ -1 +1 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85 1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></svg> |
|
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12M11 1h2v3h-2zm0 19h2v3h-2zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414zm2.121-14.85 1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414zM23 11v2h-3v-2zM4 11v2H1v-2z"/></svg> |
@ -1 +1 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="iconify iconify--ant-design" viewBox="0 0 1024 1024"><path fill="currentColor" d="M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8z"/></svg> |
|
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="iconify iconify--ant-design" viewBox="0 0 1024 1024"><path fill="currentColor" d="M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8"/></svg> |
@ -1 +1 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3.5 4H1V3h2V1h1v2.5l-.5.5zM13 3V1h-1v2.5l.5.5H15V3h-2zm-1 9.5V15h1v-2h2v-1h-2.5l-.5.5zM1 12v1h2v2h1v-2.5l-.5-.5H1zm11-1.5-.5.5h-7l-.5-.5v-5l.5-.5h7l.5.5v5zM10 7H6v2h4V7z"/></svg> |
|
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3.5 4H1V3h2V1h1v2.5zM13 3V1h-1v2.5l.5.5H15V3zm-1 9.5V15h1v-2h2v-1h-2.5zM1 12v1h2v2h1v-2.5l-.5-.5zm11-1.5-.5.5h-7l-.5-.5v-5l.5-.5h7l.5.5zM10 7H6v2h4z"/></svg> |
@ -1 +1 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3 12h10V4H3v8zm2-6h6v4H5V6zM2 6H1V2.5l.5-.5H5v1H2v3zm13-3.5V6h-1V3h-3V2h3.5l.5.5zM14 10h1v3.5l-.5.5H11v-1h3v-3zM2 13h3v1H1.5l-.5-.5V10h1v3z"/></svg> |
|
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3 12h10V4H3zm2-6h6v4H5zM2 6H1V2.5l.5-.5H5v1H2zm13-3.5V6h-1V3h-3V2h3.5zM14 10h1v3.5l-.5.5H11v-1h3zM2 13h3v1H1.5l-.5-.5V10h1z"/></svg> |
@ -1 +1 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="iconify iconify--mdi" viewBox="0 0 24 24"><path fill="currentColor" d="M1 7h6v2H3v2h4v2H3v2h4v2H1V7m10 0h4v2h-4v2h2a2 2 0 0 1 2 2v2c0 1.11-.89 2-2 2H9v-2h4v-2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2m8 0h2a2 2 0 0 1 2 2v1h-2V9h-2v6h2v-1h2v1c0 1.11-.89 2-2 2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2Z"/></svg> |
|
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="iconify iconify--mdi" viewBox="0 0 24 24"><path fill="currentColor" d="M1 7h6v2H3v2h4v2H3v2h4v2H1zm10 0h4v2h-4v2h2a2 2 0 0 1 2 2v2c0 1.11-.89 2-2 2H9v-2h4v-2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2m8 0h2a2 2 0 0 1 2 2v1h-2V9h-2v6h2v-1h2v1c0 1.11-.89 2-2 2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2"/></svg> |
@ -0,0 +1 @@ |
|||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="icon" viewBox="0 0 1024 1024"><path d="M554 849.574c0 23.365-18.635 42.307-42 42.307s-42-18.941-42-42.307V662.719c0-23.365 18.635-42.307 42-42.307v-7.051c23.365 0 42 25.993 42 49.358z"/><path d="M893 888.5c0 17.397-14.103 31.5-31.5 31.5h-700c-17.397 0-31.5-14.103-31.5-31.5s14.103-31.5 31.5-31.5h700c17.397 0 31.5 14.103 31.5 31.5m33-714.074C926 135.484 894.686 105 855.744 105H168.256C129.314 105 98 135.484 98 174.426V533h828zM98 630.988C98 669.931 129.314 702 168.256 702h687.488C894.686 702 926 669.931 926 630.988V596H98z"/></svg> |
@ -1,14 +1,14 @@ |
|||||
|
// 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载
|
||||
import { addIcon } from "@iconify/vue/dist/offline"; |
import { addIcon } from "@iconify/vue/dist/offline"; |
||||
|
|
||||
/** |
|
||||
* 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载 |
|
||||
*/ |
|
||||
|
|
||||
// 本地菜单图标,后端在路由的icon中返回对应的图标字符串并且前端在此处使用addIcon添加即可渲染菜单图标
|
|
||||
|
// 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标
|
||||
|
// @iconify-icons/ep
|
||||
|
import Lollipop from "@iconify-icons/ep/lollipop"; |
||||
import HomeFilled from "@iconify-icons/ep/home-filled"; |
import HomeFilled from "@iconify-icons/ep/home-filled"; |
||||
|
addIcon("ep:lollipop", Lollipop); |
||||
|
addIcon("ep:home-filled", HomeFilled); |
||||
|
// @iconify-icons/ri
|
||||
|
import Search from "@iconify-icons/ri/search-line"; |
||||
import InformationLine from "@iconify-icons/ri/information-line"; |
import InformationLine from "@iconify-icons/ri/information-line"; |
||||
import Lollipop from "@iconify-icons/ep/lollipop"; |
|
||||
|
|
||||
addIcon("homeFilled", HomeFilled); |
|
||||
addIcon("informationLine", InformationLine); |
|
||||
addIcon("lollipop", Lollipop); |
|
||||
|
addIcon("ri:search-line", Search); |
||||
|
addIcon("ri:information-line", InformationLine); |
@ -1 +1 @@ |
|||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.79 10.21a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42l-2.5-2.5a1 1 0 0 0-.33-.21 1 1 0 0 0-.76 0 1 1 0 0 0-.33.21l-2.5 2.5a1 1 0 0 0 1.42 1.42l.79-.8v5.18l-.79-.8a1 1 0 0 0-1.42 1.42l2.5 2.5a1 1 0 0 0 .33.21.94.94 0 0 0 .76 0 1 1 0 0 0 .33-.21l2.5-2.5a1 1 0 0 0-1.42-1.42l-.79.8V9.41ZM7 4h10a1 1 0 0 0 0-2H7a1 1 0 0 0 0 2Zm10 16H7a1 1 0 0 0 0 2h10a1 1 0 0 0 0-2Z"/></svg> |
|
||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.79 10.21a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42l-2.5-2.5a1 1 0 0 0-.33-.21 1 1 0 0 0-.76 0 1 1 0 0 0-.33.21l-2.5 2.5a1 1 0 0 0 1.42 1.42l.79-.8v5.18l-.79-.8a1 1 0 0 0-1.42 1.42l2.5 2.5a1 1 0 0 0 .33.21.94.94 0 0 0 .76 0 1 1 0 0 0 .33-.21l2.5-2.5a1 1 0 0 0-1.42-1.42l-.79.8V9.41ZM7 4h10a1 1 0 0 0 0-2H7a1 1 0 0 0 0 2m10 16H7a1 1 0 0 0 0 2h10a1 1 0 0 0 0-2"/></svg> |
@ -1 +1 @@ |
|||||
<svg width="32" height="32" fill="currentColor" aria-hidden="true" data-icon="holder" viewBox="64 64 896 896"><path d="M300 276.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zm0 284a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 228a56 56 0 1 0 112 0 56 56 0 0 0-112 0zm0 284a56 56 0 1 0 112 0 56 56 0 0 0-112 0zM300 844.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 796a56 56 0 1 0 112 0 56 56 0 0 0-112 0z"/></svg> |
|
||||
|
<svg width="32" height="32" fill="currentColor" aria-hidden="true" data-icon="holder" viewBox="64 64 896 896"><path d="M300 276.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97m0 284a56 56 0 1 0 56-97 56 56 0 0 0-56 97M640 228a56 56 0 1 0 112 0 56 56 0 0 0-112 0m0 284a56 56 0 1 0 112 0 56 56 0 0 0-112 0M300 844.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97M640 796a56 56 0 1 0 112 0 56 56 0 0 0-112 0"/></svg> |
@ -1 +1 @@ |
|||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4h9Z"/></svg> |
|
||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4z"/></svg> |
@ -1 +1 @@ |
|||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M3.34 17a10.018 10.018 0 0 1-.978-2.326 3 3 0 0 0 .002-5.347A9.99 9.99 0 0 1 4.865 4.99a3 3 0 0 0 4.631-2.674 9.99 9.99 0 0 1 5.007.002 3 3 0 0 0 4.632 2.672A9.99 9.99 0 0 1 20.66 7c.433.749.757 1.53.978 2.326a3 3 0 0 0-.002 5.347 9.99 9.99 0 0 1-2.501 4.337 3 3 0 0 0-4.631 2.674 9.99 9.99 0 0 1-5.007-.002 3 3 0 0 0-4.632-2.672A10.018 10.018 0 0 1 3.34 17zm5.66.196a4.993 4.993 0 0 1 2.25 2.77c.499.047 1 .048 1.499.001A4.993 4.993 0 0 1 15 17.197a4.993 4.993 0 0 1 3.525-.565c.29-.408.54-.843.748-1.298A4.993 4.993 0 0 1 18 12c0-1.26.47-2.437 1.273-3.334a8.126 8.126 0 0 0-.75-1.298A4.993 4.993 0 0 1 15 6.804a4.993 4.993 0 0 1-2.25-2.77c-.499-.047-1-.048-1.499-.001A4.993 4.993 0 0 1 9 6.803a4.993 4.993 0 0 1-3.525.565 7.99 7.99 0 0 0-.748 1.298A4.993 4.993 0 0 1 6 12a4.99 4.99 0 0 1-1.273 3.334 8.126 8.126 0 0 0 .75 1.298A4.993 4.993 0 0 1 9 17.196zM12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></svg> |
|
||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M3.34 17a10 10 0 0 1-.978-2.326 3 3 0 0 0 .002-5.347A10 10 0 0 1 4.865 4.99a3 3 0 0 0 4.631-2.674 10 10 0 0 1 5.007.002 3 3 0 0 0 4.632 2.672A10 10 0 0 1 20.66 7c.433.749.757 1.53.978 2.326a3 3 0 0 0-.002 5.347 10 10 0 0 1-2.501 4.337 3 3 0 0 0-4.631 2.674 10 10 0 0 1-5.007-.002 3 3 0 0 0-4.632-2.672A10 10 0 0 1 3.34 17m5.66.196a5 5 0 0 1 2.25 2.77q.75.071 1.499.001A5 5 0 0 1 15 17.197a5 5 0 0 1 3.525-.565q.435-.614.748-1.298A5 5 0 0 1 18 12c0-1.26.47-2.437 1.273-3.334a8 8 0 0 0-.75-1.298A5 5 0 0 1 15 6.804a5 5 0 0 1-2.25-2.77q-.75-.071-1.499-.001A5 5 0 0 1 9 6.803a5 5 0 0 1-3.525.565 8 8 0 0 0-.748 1.298A5 5 0 0 1 6 12a5 5 0 0 1-1.273 3.334 8 8 0 0 0 .75 1.298A5 5 0 0 1 9 17.196M12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6m0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg> |
@ -0,0 +1,8 @@ |
|||||
|
import reSegmented from "./src/index"; |
||||
|
import { withInstall } from "@pureadmin/utils"; |
||||
|
|
||||
|
/** 分段控制器组件 */ |
||||
|
export const ReSegmented = withInstall(reSegmented); |
||||
|
|
||||
|
export default ReSegmented; |
||||
|
export type { OptionsType } from "./src/type"; |
@ -0,0 +1,80 @@ |
|||||
|
.pure-segmented { |
||||
|
box-sizing: border-box; |
||||
|
display: inline-block; |
||||
|
padding: 2px; |
||||
|
font-size: 14px; |
||||
|
color: rgba(0, 0, 0, 0.65); |
||||
|
background-color: rgb(0 0 0 / 4%); |
||||
|
border-radius: 2px; |
||||
|
} |
||||
|
|
||||
|
.pure-segmented-group { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
align-items: stretch; |
||||
|
justify-items: flex-start; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.pure-segmented-item-selected { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
box-sizing: border-box; |
||||
|
display: none; |
||||
|
width: 0; |
||||
|
height: 100%; |
||||
|
padding: 4px 0; |
||||
|
background-color: #fff; |
||||
|
border-radius: 4px; |
||||
|
box-shadow: |
||||
|
0 2px 8px -2px rgb(0 0 0 / 5%), |
||||
|
0 1px 4px -1px rgb(0 0 0 / 7%), |
||||
|
0 0 1px rgb(0 0 0 / 7%); |
||||
|
transition: |
||||
|
transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1), |
||||
|
width 0.5s cubic-bezier(0.645, 0.045, 0.355, 1); |
||||
|
will-change: transform, width; |
||||
|
} |
||||
|
|
||||
|
.pure-segmented-item { |
||||
|
position: relative; |
||||
|
text-align: center; |
||||
|
cursor: pointer; |
||||
|
border-radius: 4px; |
||||
|
transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1); |
||||
|
} |
||||
|
|
||||
|
.pure-segmented-item > div { |
||||
|
min-height: 28px; |
||||
|
line-height: 28px; |
||||
|
padding: 0 11px; |
||||
|
overflow: hidden; |
||||
|
white-space: nowrap; |
||||
|
text-overflow: ellipsis; |
||||
|
} |
||||
|
|
||||
|
.pure-segmented-item > input { |
||||
|
position: absolute; |
||||
|
inset-block-start: 0; |
||||
|
inset-inline-start: 0; |
||||
|
width: 0; |
||||
|
height: 0; |
||||
|
opacity: 0; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.pure-segmented-item-label { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.pure-segmented-item-icon svg { |
||||
|
width: 16px; |
||||
|
height: 16px; |
||||
|
} |
||||
|
|
||||
|
.pure-segmented-item-disabled { |
||||
|
color: rgba(0, 0, 0, 0.25); |
||||
|
cursor: not-allowed; |
||||
|
} |
@ -0,0 +1,166 @@ |
|||||
|
import "./index.css"; |
||||
|
import { |
||||
|
h, |
||||
|
ref, |
||||
|
toRef, |
||||
|
watch, |
||||
|
nextTick, |
||||
|
defineComponent, |
||||
|
getCurrentInstance |
||||
|
} from "vue"; |
||||
|
import type { OptionsType } from "./type"; |
||||
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; |
||||
|
import { isFunction, isNumber, useDark } from "@pureadmin/utils"; |
||||
|
|
||||
|
const props = { |
||||
|
options: { |
||||
|
type: Array<OptionsType>, |
||||
|
default: () => [] |
||||
|
}, |
||||
|
/** 默认选中,按照第一个索引为 `0` 的模式,可选(`modelValue`只有传`number`类型时才为响应式) */ |
||||
|
modelValue: { |
||||
|
type: undefined, |
||||
|
require: false, |
||||
|
default: "0" |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default defineComponent({ |
||||
|
name: "ReSegmented", |
||||
|
props, |
||||
|
emits: ["change", "update:modelValue"], |
||||
|
setup(props, { emit }) { |
||||
|
const width = ref(0); |
||||
|
const translateX = ref(0); |
||||
|
const { isDark } = useDark(); |
||||
|
const initStatus = ref(false); |
||||
|
const curMouseActive = ref(-1); |
||||
|
const segmentedItembg = ref(""); |
||||
|
const instance = getCurrentInstance()!; |
||||
|
const curIndex = isNumber(props.modelValue) |
||||
|
? toRef(props, "modelValue") |
||||
|
: ref(0); |
||||
|
|
||||
|
function handleChange({ option, index }, event: Event) { |
||||
|
if (option.disabled) return; |
||||
|
event.preventDefault(); |
||||
|
isNumber(props.modelValue) |
||||
|
? emit("update:modelValue", index) |
||||
|
: (curIndex.value = index); |
||||
|
segmentedItembg.value = ""; |
||||
|
emit("change", { index, option }); |
||||
|
} |
||||
|
|
||||
|
function handleMouseenter({ option, index }, event: Event) { |
||||
|
event.preventDefault(); |
||||
|
curMouseActive.value = index; |
||||
|
if (option.disabled || curIndex.value === index) { |
||||
|
segmentedItembg.value = ""; |
||||
|
} else { |
||||
|
segmentedItembg.value = isDark.value |
||||
|
? "#1f1f1f" |
||||
|
: "rgba(0, 0, 0, 0.06)"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleMouseleave(_, event: Event) { |
||||
|
event.preventDefault(); |
||||
|
curMouseActive.value = -1; |
||||
|
} |
||||
|
|
||||
|
function handleInit(index = curIndex.value) { |
||||
|
nextTick(() => { |
||||
|
const curLabelRef = instance?.proxy?.$refs[`labelRef${index}`] as ElRef; |
||||
|
width.value = curLabelRef.clientWidth; |
||||
|
translateX.value = curLabelRef.offsetLeft; |
||||
|
initStatus.value = true; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
watch( |
||||
|
() => curIndex.value, |
||||
|
index => { |
||||
|
nextTick(() => { |
||||
|
handleInit(index); |
||||
|
}); |
||||
|
}, |
||||
|
{ |
||||
|
deep: true, |
||||
|
immediate: true |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
const rendLabel = () => { |
||||
|
return props.options.map((option, index) => { |
||||
|
return ( |
||||
|
<label |
||||
|
ref={`labelRef${index}`} |
||||
|
class={[ |
||||
|
"pure-segmented-item", |
||||
|
option?.disabled && "pure-segmented-item-disabled" |
||||
|
]} |
||||
|
style={{ |
||||
|
background: |
||||
|
curMouseActive.value === index ? segmentedItembg.value : "", |
||||
|
color: |
||||
|
!option.disabled && |
||||
|
(curIndex.value === index || curMouseActive.value === index) |
||||
|
? isDark.value |
||||
|
? "rgba(255, 255, 255, 0.85)" |
||||
|
: "rgba(0,0,0,.88)" |
||||
|
: "" |
||||
|
}} |
||||
|
onMouseenter={event => handleMouseenter({ option, index }, event)} |
||||
|
onMouseleave={event => handleMouseleave({ option, index }, event)} |
||||
|
onClick={event => handleChange({ option, index }, event)} |
||||
|
> |
||||
|
<input type="radio" name="segmented" /> |
||||
|
<div |
||||
|
class="pure-segmented-item-label" |
||||
|
v-tippy={{ |
||||
|
content: option?.tip, |
||||
|
zIndex: 41000 |
||||
|
}} |
||||
|
> |
||||
|
{option.icon && !isFunction(option.label) ? ( |
||||
|
<span |
||||
|
class="pure-segmented-item-icon" |
||||
|
style={{ marginRight: option.label ? "6px" : 0 }} |
||||
|
> |
||||
|
{h( |
||||
|
useRenderIcon(option.icon, { |
||||
|
...option?.iconAttrs |
||||
|
}) |
||||
|
)} |
||||
|
</span> |
||||
|
) : null} |
||||
|
{option.label ? ( |
||||
|
isFunction(option.label) ? ( |
||||
|
h(option.label) |
||||
|
) : ( |
||||
|
<span>{option.label}</span> |
||||
|
) |
||||
|
) : null} |
||||
|
</div> |
||||
|
</label> |
||||
|
); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
return () => ( |
||||
|
<div class="pure-segmented"> |
||||
|
<div class="pure-segmented-group"> |
||||
|
<div |
||||
|
class="pure-segmented-item-selected" |
||||
|
style={{ |
||||
|
width: `${width.value}px`, |
||||
|
transform: `translateX(${translateX.value}px)`, |
||||
|
display: initStatus.value ? "block" : "none" |
||||
|
}} |
||||
|
></div> |
||||
|
{rendLabel()} |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
}); |
@ -0,0 +1,20 @@ |
|||||
|
import type { VNode, Component } from "vue"; |
||||
|
import type { iconType } from "@/components/ReIcon/src/types.ts"; |
||||
|
|
||||
|
export interface OptionsType { |
||||
|
/** 文字 */ |
||||
|
label?: string | (() => VNode | Component); |
||||
|
/** |
||||
|
* @description 图标,采用平台内置的 `useRenderIcon` 函数渲染 |
||||
|
* @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; |
||||
|
/** 图标属性、样式配置 */ |
||||
|
iconAttrs?: iconType; |
||||
|
/** 值 */ |
||||
|
value?: any; |
||||
|
/** 是否禁用 */ |
||||
|
disabled?: boolean; |
||||
|
/** `tooltip` 提示 */ |
||||
|
tip?: string; |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
import reText from "./src/index.vue"; |
||||
|
import { withInstall } from "@pureadmin/utils"; |
||||
|
|
||||
|
/** 支持`Tooltip`提示的文本省略组件 */ |
||||
|
export const ReText = withInstall(reText); |
||||
|
|
||||
|
export default ReText; |
@ -0,0 +1,62 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { h, onMounted, ref, useSlots } from "vue"; |
||||
|
import { useTippy, type TippyOptions } from "vue-tippy"; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
// 行数 |
||||
|
lineClamp: { |
||||
|
type: [String, Number] |
||||
|
}, |
||||
|
tippyProps: { |
||||
|
type: Object as PropType<TippyOptions>, |
||||
|
default: () => ({}) |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const $slots = useSlots(); |
||||
|
|
||||
|
const textRef = ref(); |
||||
|
const tippyFunc = ref(); |
||||
|
|
||||
|
const isTextEllipsis = (el: HTMLElement) => { |
||||
|
if (!props.lineClamp) { |
||||
|
// 单行省略判断 |
||||
|
return el.scrollWidth > el.clientWidth; |
||||
|
} else { |
||||
|
// 多行省略判断 |
||||
|
return el.scrollHeight > el.clientHeight; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const getTippyProps = () => ({ |
||||
|
content: h($slots.content || $slots.default), |
||||
|
...props.tippyProps |
||||
|
}); |
||||
|
|
||||
|
function handleHover(event: MouseEvent) { |
||||
|
if (isTextEllipsis(event.target as HTMLElement)) { |
||||
|
tippyFunc.value.setProps(getTippyProps()); |
||||
|
tippyFunc.value.enable(); |
||||
|
} else { |
||||
|
tippyFunc.value.disable(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMounted(() => { |
||||
|
tippyFunc.value = useTippy(textRef.value?.$el, getTippyProps()); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-text |
||||
|
v-bind="{ |
||||
|
truncated: !lineClamp, |
||||
|
lineClamp, |
||||
|
...$attrs |
||||
|
}" |
||||
|
ref="textRef" |
||||
|
@mouseover.self="handleHover" |
||||
|
> |
||||
|
<slot /> |
||||
|
</el-text> |
||||
|
</template> |
@ -0,0 +1,48 @@ |
|||||
|
/* stylelint-disable-next-line scss/dollar-variable-colon-space-after */ |
||||
|
$ripple-animation-transition-in: |
||||
|
transform 0.4s cubic-bezier(0, 0, 0.2, 1), |
||||
|
opacity 0.2s cubic-bezier(0, 0, 0.2, 1) !default; |
||||
|
$ripple-animation-transition-out: opacity 0.5s cubic-bezier(0, 0, 0.2, 1) !default; |
||||
|
$ripple-animation-visible-opacity: 0.25 !default; |
||||
|
|
||||
|
.v-ripple { |
||||
|
&__container { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
z-index: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
pointer-events: none; |
||||
|
border-radius: inherit; |
||||
|
contain: strict; |
||||
|
} |
||||
|
|
||||
|
&__animation { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
overflow: hidden; |
||||
|
pointer-events: none; |
||||
|
background: currentcolor; |
||||
|
border-radius: 50%; |
||||
|
opacity: 0; |
||||
|
will-change: transform, opacity; |
||||
|
|
||||
|
&--enter { |
||||
|
opacity: 0; |
||||
|
transition: none; |
||||
|
} |
||||
|
|
||||
|
&--in { |
||||
|
opacity: $ripple-animation-visible-opacity; |
||||
|
transition: $ripple-animation-transition-in; |
||||
|
} |
||||
|
|
||||
|
&--out { |
||||
|
opacity: 0; |
||||
|
transition: $ripple-animation-transition-out; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,234 @@ |
|||||
|
import "./index.scss"; |
||||
|
import { isObject } from "@pureadmin/utils"; |
||||
|
import type { Directive, DirectiveBinding } from "vue"; |
||||
|
|
||||
|
interface RippleOptions { |
||||
|
class?: string; |
||||
|
center?: boolean; |
||||
|
circle?: boolean; |
||||
|
} |
||||
|
|
||||
|
export interface RippleDirectiveBinding |
||||
|
extends Omit<DirectiveBinding, "modifiers" | "value"> { |
||||
|
value?: boolean | { class: string }; |
||||
|
modifiers: { |
||||
|
center?: boolean; |
||||
|
circle?: boolean; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function transform(el: HTMLElement, value: string) { |
||||
|
el.style.transform = value; |
||||
|
el.style.webkitTransform = value; |
||||
|
} |
||||
|
|
||||
|
const calculate = ( |
||||
|
e: PointerEvent, |
||||
|
el: HTMLElement, |
||||
|
value: RippleOptions = {} |
||||
|
) => { |
||||
|
const offset = el.getBoundingClientRect(); |
||||
|
|
||||
|
// 获取点击位置距离 el 的垂直和水平距离
|
||||
|
let localX = e.clientX - offset.left; |
||||
|
let localY = e.clientY - offset.top; |
||||
|
|
||||
|
let radius = 0; |
||||
|
let scale = 0.3; |
||||
|
// 计算点击位置到 el 顶点最远距离,即为圆的最大半径(勾股定理)
|
||||
|
if (el._ripple?.circle) { |
||||
|
scale = 0.15; |
||||
|
radius = el.clientWidth / 2; |
||||
|
radius = value.center |
||||
|
? radius |
||||
|
: radius + Math.sqrt((localX - radius) ** 2 + (localY - radius) ** 2) / 4; |
||||
|
} else { |
||||
|
radius = Math.sqrt(el.clientWidth ** 2 + el.clientHeight ** 2) / 2; |
||||
|
} |
||||
|
|
||||
|
// 中心点坐标
|
||||
|
const centerX = `${(el.clientWidth - radius * 2) / 2}px`; |
||||
|
const centerY = `${(el.clientHeight - radius * 2) / 2}px`; |
||||
|
|
||||
|
// 点击位置坐标
|
||||
|
const x = value.center ? centerX : `${localX - radius}px`; |
||||
|
const y = value.center ? centerY : `${localY - radius}px`; |
||||
|
|
||||
|
return { radius, scale, x, y, centerX, centerY }; |
||||
|
}; |
||||
|
|
||||
|
const ripples = { |
||||
|
show(e: PointerEvent, el: HTMLElement, value: RippleOptions = {}) { |
||||
|
if (!el?._ripple?.enabled) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 创建 ripple 元素和 ripple 父元素
|
||||
|
const container = document.createElement("span"); |
||||
|
const animation = document.createElement("span"); |
||||
|
|
||||
|
container.appendChild(animation); |
||||
|
container.className = "v-ripple__container"; |
||||
|
|
||||
|
if (value.class) { |
||||
|
container.className += ` ${value.class}`; |
||||
|
} |
||||
|
|
||||
|
const { radius, scale, x, y, centerX, centerY } = calculate(e, el, value); |
||||
|
|
||||
|
// ripple 圆大小
|
||||
|
const size = `${radius * 2}px`; |
||||
|
|
||||
|
animation.className = "v-ripple__animation"; |
||||
|
animation.style.width = size; |
||||
|
animation.style.height = size; |
||||
|
|
||||
|
el.appendChild(container); |
||||
|
|
||||
|
// 获取目标元素样式表
|
||||
|
const computed = window.getComputedStyle(el); |
||||
|
// 防止 position 被覆盖导致 ripple 位置有问题
|
||||
|
if (computed && computed.position === "static") { |
||||
|
el.style.position = "relative"; |
||||
|
el.dataset.previousPosition = "static"; |
||||
|
} |
||||
|
|
||||
|
animation.classList.add("v-ripple__animation--enter"); |
||||
|
animation.classList.add("v-ripple__animation--visible"); |
||||
|
transform( |
||||
|
animation, |
||||
|
`translate(${x}, ${y}) scale3d(${scale},${scale},${scale})` |
||||
|
); |
||||
|
animation.dataset.activated = String(performance.now()); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
animation.classList.remove("v-ripple__animation--enter"); |
||||
|
animation.classList.add("v-ripple__animation--in"); |
||||
|
transform(animation, `translate(${centerX}, ${centerY}) scale3d(1,1,1)`); |
||||
|
}, 0); |
||||
|
}, |
||||
|
|
||||
|
hide(el: HTMLElement | null) { |
||||
|
if (!el?._ripple?.enabled) return; |
||||
|
|
||||
|
const ripples = el.getElementsByClassName("v-ripple__animation"); |
||||
|
|
||||
|
if (ripples.length === 0) return; |
||||
|
const animation = ripples[ripples.length - 1] as HTMLElement; |
||||
|
|
||||
|
if (animation.dataset.isHiding) return; |
||||
|
else animation.dataset.isHiding = "true"; |
||||
|
|
||||
|
const diff = performance.now() - Number(animation.dataset.activated); |
||||
|
const delay = Math.max(250 - diff, 0); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
animation.classList.remove("v-ripple__animation--in"); |
||||
|
animation.classList.add("v-ripple__animation--out"); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
const ripples = el.getElementsByClassName("v-ripple__animation"); |
||||
|
if (ripples.length === 1 && el.dataset.previousPosition) { |
||||
|
el.style.position = el.dataset.previousPosition; |
||||
|
delete el.dataset.previousPosition; |
||||
|
} |
||||
|
|
||||
|
if (animation.parentNode?.parentNode === el) |
||||
|
el.removeChild(animation.parentNode); |
||||
|
}, 300); |
||||
|
}, delay); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
function isRippleEnabled(value: any): value is true { |
||||
|
return typeof value === "undefined" || !!value; |
||||
|
} |
||||
|
|
||||
|
function rippleShow(e: PointerEvent) { |
||||
|
const value: RippleOptions = {}; |
||||
|
const element = e.currentTarget as HTMLElement | undefined; |
||||
|
|
||||
|
if (!element?._ripple || element._ripple.touched) return; |
||||
|
|
||||
|
value.center = element._ripple.centered; |
||||
|
if (element._ripple.class) { |
||||
|
value.class = element._ripple.class; |
||||
|
} |
||||
|
|
||||
|
ripples.show(e, element, value); |
||||
|
} |
||||
|
|
||||
|
function rippleHide(e: Event) { |
||||
|
const element = e.currentTarget as HTMLElement | null; |
||||
|
if (!element?._ripple) return; |
||||
|
|
||||
|
window.setTimeout(() => { |
||||
|
if (element._ripple) { |
||||
|
element._ripple.touched = false; |
||||
|
} |
||||
|
}); |
||||
|
ripples.hide(element); |
||||
|
} |
||||
|
|
||||
|
function updateRipple( |
||||
|
el: HTMLElement, |
||||
|
binding: RippleDirectiveBinding, |
||||
|
wasEnabled: boolean |
||||
|
) { |
||||
|
const { value, modifiers } = binding; |
||||
|
const enabled = isRippleEnabled(value); |
||||
|
if (!enabled) { |
||||
|
ripples.hide(el); |
||||
|
} |
||||
|
|
||||
|
el._ripple = el._ripple ?? {}; |
||||
|
el._ripple.enabled = enabled; |
||||
|
el._ripple.centered = modifiers.center; |
||||
|
el._ripple.circle = modifiers.circle; |
||||
|
if (isObject(value) && value.class) { |
||||
|
el._ripple.class = value.class; |
||||
|
} |
||||
|
|
||||
|
if (enabled && !wasEnabled) { |
||||
|
el.addEventListener("pointerdown", rippleShow); |
||||
|
el.addEventListener("pointerup", rippleHide); |
||||
|
} else if (!enabled && wasEnabled) { |
||||
|
removeListeners(el); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function removeListeners(el: HTMLElement) { |
||||
|
el.removeEventListener("pointerdown", rippleShow); |
||||
|
el.removeEventListener("pointerup", rippleHide); |
||||
|
} |
||||
|
|
||||
|
function mounted(el: HTMLElement, binding: RippleDirectiveBinding) { |
||||
|
updateRipple(el, binding, false); |
||||
|
} |
||||
|
|
||||
|
function unmounted(el: HTMLElement) { |
||||
|
delete el._ripple; |
||||
|
removeListeners(el); |
||||
|
} |
||||
|
|
||||
|
function updated(el: HTMLElement, binding: RippleDirectiveBinding) { |
||||
|
if (binding.value === binding.oldValue) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const wasEnabled = isRippleEnabled(binding.oldValue); |
||||
|
updateRipple(el, binding, wasEnabled); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @description 指令 v-ripple |
||||
|
* @use 用法如下 |
||||
|
* 1. v-ripple 代表启用基本的 ripple 功能 |
||||
|
* 2. v-ripple="{ class: 'text-red' }" 代表自定义 ripple 颜色,支持 tailwindcss,生效样式是 color |
||||
|
* 3. v-ripple.center 代表从中心扩散 |
||||
|
*/ |
||||
|
export const Ripple: Directive = { |
||||
|
mounted, |
||||
|
unmounted, |
||||
|
updated |
||||
|
}; |
@ -0,0 +1,31 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { getConfig } from "@/config"; |
||||
|
|
||||
|
const TITLE = getConfig("Title"); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<footer |
||||
|
class="layout-footer text-[rgba(0,0,0,0.6)] dark:text-[rgba(220,220,242,0.8)]" |
||||
|
> |
||||
|
Copyright © 2020-present |
||||
|
<a |
||||
|
class="hover:text-primary" |
||||
|
href="https://github.com/pure-admin" |
||||
|
target="_blank" |
||||
|
> |
||||
|
{{ TITLE }} |
||||
|
</a> |
||||
|
</footer> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.layout-footer { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
width: 100%; |
||||
|
padding: 0 0 8px; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,80 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { getConfig } from "@/config"; |
||||
|
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; |
||||
|
import { type Component, shallowRef, watch, computed } from "vue"; |
||||
|
import { type RouteRecordRaw, RouteLocationNormalizedLoaded } from "vue-router"; |
||||
|
import { useMultiFrame } from "@/layout/components/keepAliveFrame/useMultiFrame"; |
||||
|
|
||||
|
const props = defineProps<{ |
||||
|
currRoute: RouteLocationNormalizedLoaded; |
||||
|
currComp: Component; |
||||
|
}>(); |
||||
|
|
||||
|
const compList = shallowRef([]); |
||||
|
const { setMap, getMap, MAP, delMap } = useMultiFrame(); |
||||
|
|
||||
|
const keep = computed(() => { |
||||
|
return ( |
||||
|
getConfig().KeepAlive && |
||||
|
props.currRoute.meta?.keepAlive && |
||||
|
!!props.currRoute.meta?.frameSrc |
||||
|
); |
||||
|
}); |
||||
|
// 避免重新渲染 frameView |
||||
|
const normalComp = computed(() => !keep.value && props.currComp); |
||||
|
|
||||
|
watch(useMultiTagsStoreHook().multiTags, (tags: any) => { |
||||
|
if (!Array.isArray(tags) || !keep.value) { |
||||
|
return; |
||||
|
} |
||||
|
const iframeTags = tags.filter(i => i.meta?.frameSrc); |
||||
|
// tags必须是小于MAP,才是做了关闭动作,因为MAP插入的顺序在tags变化后发生 |
||||
|
if (iframeTags.length < MAP.size) { |
||||
|
for (const i of MAP.keys()) { |
||||
|
if (!tags.some(s => s.path === i)) { |
||||
|
delMap(i); |
||||
|
compList.value = getMap(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
watch( |
||||
|
() => props.currRoute.fullPath, |
||||
|
path => { |
||||
|
const multiTags = useMultiTagsStoreHook().multiTags as RouteRecordRaw[]; |
||||
|
const iframeTags = multiTags.filter(i => i.meta?.frameSrc); |
||||
|
if (keep.value) { |
||||
|
if (iframeTags.length !== MAP.size) { |
||||
|
const sameKey = [...MAP.keys()].find(i => path === i); |
||||
|
if (!sameKey) { |
||||
|
// 添加缓存 |
||||
|
setMap(path, props.currComp); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (MAP.size > 0) { |
||||
|
compList.value = getMap(); |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
immediate: true |
||||
|
} |
||||
|
); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<template v-for="[fullPath, Comp] in compList" :key="fullPath"> |
||||
|
<div v-show="fullPath === props.currRoute.fullPath" class="w-full h-full"> |
||||
|
<slot |
||||
|
:fullPath="fullPath" |
||||
|
:Comp="Comp" |
||||
|
:frameInfo="{ frameSrc: currRoute.meta?.frameSrc, fullPath }" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
|
<div v-show="!keep" class="w-full h-full"> |
||||
|
<slot :Comp="normalComp" :fullPath="props.currRoute.fullPath" frameInfo /> |
||||
|
</div> |
||||
|
</template> |
@ -0,0 +1,25 @@ |
|||||
|
const MAP = new Map(); |
||||
|
|
||||
|
export const useMultiFrame = () => { |
||||
|
function setMap(path, Comp) { |
||||
|
MAP.set(path, Comp); |
||||
|
} |
||||
|
|
||||
|
function getMap(path?) { |
||||
|
if (path) { |
||||
|
return MAP.get(path); |
||||
|
} |
||||
|
return [...MAP.entries()]; |
||||
|
} |
||||
|
|
||||
|
function delMap(path) { |
||||
|
MAP.delete(path); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
setMap, |
||||
|
getMap, |
||||
|
delMap, |
||||
|
MAP |
||||
|
}; |
||||
|
}; |
@ -0,0 +1,198 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import Sortable from "sortablejs"; |
||||
|
import SearchHistoryItem from "./SearchHistoryItem.vue"; |
||||
|
import type { optionsItem, dragItem, Props } from "../types"; |
||||
|
import { useEpThemeStoreHook } from "@/store/modules/epTheme"; |
||||
|
import { useResizeObserver, isArray, delay } from "@pureadmin/utils"; |
||||
|
import { ref, watch, nextTick, computed, getCurrentInstance } from "vue"; |
||||
|
|
||||
|
interface Emits { |
||||
|
(e: "update:value", val: string): void; |
||||
|
(e: "enter"): void; |
||||
|
(e: "collect", val: optionsItem): void; |
||||
|
(e: "delete", val: optionsItem): void; |
||||
|
(e: "drag", val: dragItem): void; |
||||
|
} |
||||
|
|
||||
|
const historyRef = ref(); |
||||
|
const innerHeight = ref(); |
||||
|
/** 判断是否停止鼠标移入事件处理 */ |
||||
|
const stopMouseEvent = ref(false); |
||||
|
|
||||
|
const emit = defineEmits<Emits>(); |
||||
|
const instance = getCurrentInstance()!; |
||||
|
const props = withDefaults(defineProps<Props>(), {}); |
||||
|
|
||||
|
const itemStyle = computed(() => { |
||||
|
return item => { |
||||
|
return { |
||||
|
background: |
||||
|
item?.path === active.value ? useEpThemeStoreHook().epThemeColor : "", |
||||
|
color: item.path === active.value ? "#fff" : "", |
||||
|
fontSize: item.path === active.value ? "16px" : "14px" |
||||
|
}; |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
const titleStyle = computed(() => { |
||||
|
return { |
||||
|
color: useEpThemeStoreHook().epThemeColor, |
||||
|
fontWeight: 500 |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
const active = computed({ |
||||
|
get() { |
||||
|
return props.value; |
||||
|
}, |
||||
|
set(val: string) { |
||||
|
emit("update:value", val); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
watch( |
||||
|
() => props.value, |
||||
|
newValue => { |
||||
|
if (newValue) { |
||||
|
if (stopMouseEvent.value) { |
||||
|
delay(100).then(() => (stopMouseEvent.value = false)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
const historyList = computed(() => { |
||||
|
return props.options.filter(item => item.type === "history"); |
||||
|
}); |
||||
|
|
||||
|
const collectList = computed(() => { |
||||
|
return props.options.filter(item => item.type === "collect"); |
||||
|
}); |
||||
|
|
||||
|
function handleCollect(item) { |
||||
|
emit("collect", item); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(item) { |
||||
|
stopMouseEvent.value = true; |
||||
|
emit("delete", item); |
||||
|
} |
||||
|
|
||||
|
/** 鼠标移入 */ |
||||
|
async function handleMouse(item) { |
||||
|
if (!stopMouseEvent.value) active.value = item.path; |
||||
|
} |
||||
|
|
||||
|
function handleTo() { |
||||
|
emit("enter"); |
||||
|
} |
||||
|
|
||||
|
function resizeResult() { |
||||
|
// el-scrollbar max-height="calc(90vh - 140px)" |
||||
|
innerHeight.value = window.innerHeight - window.innerHeight / 10 - 140; |
||||
|
} |
||||
|
|
||||
|
useResizeObserver(historyRef, resizeResult); |
||||
|
|
||||
|
function handleScroll(index: number) { |
||||
|
const curInstance = instance?.proxy?.$refs[`historyItemRef${index}`]; |
||||
|
if (!curInstance) return 0; |
||||
|
const curRef = isArray(curInstance) |
||||
|
? (curInstance[0] as ElRef) |
||||
|
: (curInstance as ElRef); |
||||
|
const scrollTop = curRef.offsetTop + 128; // 128 两个history-item(56px+56px=112px)高度加上下margin(8px+8px=16px) |
||||
|
return scrollTop > innerHeight.value ? scrollTop - innerHeight.value : 0; |
||||
|
} |
||||
|
|
||||
|
const handleChangeIndex = (evt): void => { |
||||
|
emit("drag", { oldIndex: evt.oldIndex, newIndex: evt.newIndex }); |
||||
|
}; |
||||
|
|
||||
|
let sortableInstance = null; |
||||
|
|
||||
|
watch( |
||||
|
collectList, |
||||
|
val => { |
||||
|
if (val.length > 1) { |
||||
|
nextTick(() => { |
||||
|
const wrapper: HTMLElement = |
||||
|
document.querySelector(".collect-container"); |
||||
|
if (!wrapper || sortableInstance) return; |
||||
|
sortableInstance = Sortable.create(wrapper, { |
||||
|
animation: 160, |
||||
|
onStart: event => { |
||||
|
event.item.style.cursor = "move"; |
||||
|
}, |
||||
|
onEnd: event => { |
||||
|
event.item.style.cursor = "pointer"; |
||||
|
}, |
||||
|
onUpdate: handleChangeIndex |
||||
|
}); |
||||
|
resizeResult(); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
{ deep: true, immediate: true } |
||||
|
); |
||||
|
|
||||
|
defineExpose({ handleScroll }); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div ref="historyRef" class="history"> |
||||
|
<template v-if="historyList.length"> |
||||
|
<div :style="titleStyle">搜索历史</div> |
||||
|
<div |
||||
|
v-for="(item, index) in historyList" |
||||
|
:key="item.path" |
||||
|
:ref="'historyItemRef' + index" |
||||
|
class="history-item dark:bg-[#1d1d1d]" |
||||
|
:style="itemStyle(item)" |
||||
|
@click="handleTo" |
||||
|
@mouseenter="handleMouse(item)" |
||||
|
> |
||||
|
<SearchHistoryItem |
||||
|
:item="item" |
||||
|
@delete-item="handleDelete" |
||||
|
@collect-item="handleCollect" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template v-if="collectList.length"> |
||||
|
<div :style="titleStyle"> |
||||
|
收藏{{ collectList.length > 1 ? "(可拖拽排序)" : "" }} |
||||
|
</div> |
||||
|
<div class="collect-container"> |
||||
|
<div |
||||
|
v-for="(item, index) in collectList" |
||||
|
:key="item.path" |
||||
|
:ref="'historyItemRef' + (index + historyList.length)" |
||||
|
class="history-item dark:bg-[#1d1d1d]" |
||||
|
:style="itemStyle(item)" |
||||
|
@click="handleTo" |
||||
|
@mouseenter="handleMouse(item)" |
||||
|
> |
||||
|
<SearchHistoryItem :item="item" @delete-item="handleDelete" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.history { |
||||
|
padding-bottom: 12px; |
||||
|
|
||||
|
&-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
height: 56px; |
||||
|
padding: 14px; |
||||
|
margin: 8px auto 10px; |
||||
|
cursor: pointer; |
||||
|
border: 0.1px solid #ccc; |
||||
|
border-radius: 4px; |
||||
|
transition: font-size 0.16s; |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,53 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { optionsItem } from "../types"; |
||||
|
import { transformI18n } from "@/plugins/i18n"; |
||||
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; |
||||
|
import Star from "@iconify-icons/ep/star"; |
||||
|
import Close from "@iconify-icons/ep/close"; |
||||
|
|
||||
|
interface Props { |
||||
|
item: optionsItem; |
||||
|
} |
||||
|
|
||||
|
interface Emits { |
||||
|
(e: "collectItem", val: optionsItem): void; |
||||
|
(e: "deleteItem", val: optionsItem): void; |
||||
|
} |
||||
|
|
||||
|
const emit = defineEmits<Emits>(); |
||||
|
withDefaults(defineProps<Props>(), {}); |
||||
|
|
||||
|
function handleCollect(item) { |
||||
|
emit("collectItem", item); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(item) { |
||||
|
emit("deleteItem", item); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<component :is="useRenderIcon(item.meta?.icon)" /> |
||||
|
<span class="history-item-title"> |
||||
|
{{ transformI18n(item.meta?.title) }} |
||||
|
</span> |
||||
|
<IconifyIconOffline |
||||
|
v-show="item.type === 'history'" |
||||
|
:icon="Star" |
||||
|
class="w-[18px] h-[18px] mr-2 hover:text-[#d7d5d4]" |
||||
|
@click.stop="handleCollect(item)" |
||||
|
/> |
||||
|
<IconifyIconOffline |
||||
|
:icon="Close" |
||||
|
class="w-[18px] h-[18px] hover:text-[#d7d5d4] cursor-pointer" |
||||
|
@click.stop="handleDelete(item)" |
||||
|
/> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.history-item-title { |
||||
|
display: flex; |
||||
|
flex: 1; |
||||
|
margin-left: 5px; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,20 @@ |
|||||
|
interface optionsItem { |
||||
|
path: string; |
||||
|
type: "history" | "collect"; |
||||
|
meta: { |
||||
|
icon?: string; |
||||
|
title?: string; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
interface dragItem { |
||||
|
oldIndex: number; |
||||
|
newIndex: number; |
||||
|
} |
||||
|
|
||||
|
interface Props { |
||||
|
value: string; |
||||
|
options: Array<optionsItem>; |
||||
|
} |
||||
|
|
||||
|
export type { optionsItem, dragItem, Props }; |
@ -0,0 +1,36 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { computed } from "vue"; |
||||
|
import { isUrl } from "@pureadmin/utils"; |
||||
|
import { menuType } from "@/layout/types"; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: "LinkItem" |
||||
|
}); |
||||
|
|
||||
|
const props = defineProps<{ |
||||
|
to: menuType; |
||||
|
}>(); |
||||
|
|
||||
|
const isExternalLink = computed(() => isUrl(props.to.name)); |
||||
|
const getLinkProps = (item: menuType) => { |
||||
|
if (isExternalLink.value) { |
||||
|
return { |
||||
|
href: item.name, |
||||
|
target: "_blank", |
||||
|
rel: "noopener" |
||||
|
}; |
||||
|
} |
||||
|
return { |
||||
|
to: item |
||||
|
}; |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<component |
||||
|
:is="isExternalLink ? 'a' : 'router-link'" |
||||
|
v-bind="getLinkProps(to)" |
||||
|
> |
||||
|
<slot /> |
||||
|
</component> |
||||
|
</template> |
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue