xiaoxian521
9 months ago
154 changed files with 7994 additions and 8587 deletions
-
4.browserslistrc
-
2.env.development
-
2.env.production
-
2.env.staging
-
2.eslintignore
-
1.gitignore
-
4.husky/commit-msg
-
8.husky/lintstagedrc.js
-
6.husky/pre-commit
-
16.lintstagedrc
-
1.nvmrc
-
5.prettierrc.js
-
2.vscode/settings.json
-
4Dockerfile
-
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
-
16mock/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
-
79src/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
-
35src/layout/frameView.vue
-
75src/layout/hooks/useDataThemeChange.ts
@ -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", |
|||
"version": "4.5.0", |
|||
"version": "5.1.0", |
|||
"private": true, |
|||
"type": "module", |
|||
"scripts": { |
|||
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite", |
|||
"serve": "pnpm dev", |
|||
@ -11,127 +12,139 @@ |
|||
"preview": "vite preview", |
|||
"preview:build": "pnpm build && vite preview", |
|||
"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", |
|||
"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: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", |
|||
"prepare": "husky install", |
|||
"prepare": "husky", |
|||
"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": { |
|||
"@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", |
|||
"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", |
|||
"mitt": "^3.0.0", |
|||
"mockjs": "^1.1.0", |
|||
"localforage": "^1.10.0", |
|||
"mitt": "^3.0.1", |
|||
"nprogress": "^0.2.0", |
|||
"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", |
|||
"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": { |
|||
"@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/ri": "^1.2.9", |
|||
"@iconify-icons/ri": "^1.2.10", |
|||
"@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", |
|||
"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-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": { |
|||
"peerDependencyRules": { |
|||
"ignoreMissing": [ |
|||
"rollup", |
|||
"webpack", |
|||
"core-js" |
|||
] |
|||
}, |
|||
"allowedDeprecatedVersions": { |
|||
"sourcemap-codec": "*", |
|||
"domexception": "*", |
|||
"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"; |
|||
|
|||
/** |
|||
* 这里存放本地图标,在 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"; |
|||
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 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,79 @@ |
|||
<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