zhouhaibin
5 months ago
commit
ee92fb5d6c
1108 changed files with 199645 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||||
|
> 1% |
||||
|
last 2 versions |
||||
|
not dead |
||||
|
not ie 11 |
@ -0,0 +1,107 @@ |
|||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
const { execSync } = require('child_process'); |
||||
|
|
||||
|
const scopes = fs |
||||
|
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true }) |
||||
|
.filter((dirent) => dirent.isDirectory()) |
||||
|
.map((dirent) => dirent.name.replace(/s$/, '')); |
||||
|
|
||||
|
// precomputed scope |
||||
|
const scopeComplete = execSync('git status --porcelain || true') |
||||
|
.toString() |
||||
|
.trim() |
||||
|
.split('\n') |
||||
|
.find((r) => ~r.indexOf('M src')) |
||||
|
?.replace(/(\/)/g, '%%') |
||||
|
?.match(/src%%((\w|-)*)/)?.[1] |
||||
|
?.replace(/s$/, ''); |
||||
|
|
||||
|
/** @type {import('cz-git').UserConfig} */ |
||||
|
module.exports = { |
||||
|
ignores: [(commit) => commit.includes('init')], |
||||
|
extends: ['@commitlint/config-conventional'], |
||||
|
rules: { |
||||
|
'body-leading-blank': [2, 'always'], |
||||
|
'footer-leading-blank': [1, 'always'], |
||||
|
'header-max-length': [2, 'always', 108], |
||||
|
'subject-empty': [2, 'never'], |
||||
|
'type-empty': [2, 'never'], |
||||
|
'subject-case': [0], |
||||
|
'type-enum': [ |
||||
|
2, |
||||
|
'always', |
||||
|
[ |
||||
|
'feat', |
||||
|
'fix', |
||||
|
'perf', |
||||
|
'style', |
||||
|
'docs', |
||||
|
'test', |
||||
|
'refactor', |
||||
|
'build', |
||||
|
'ci', |
||||
|
'chore', |
||||
|
'revert', |
||||
|
'wip', |
||||
|
'workflow', |
||||
|
'types', |
||||
|
'release', |
||||
|
], |
||||
|
], |
||||
|
}, |
||||
|
prompt: { |
||||
|
/** @use `pnpm commit :f` */ |
||||
|
alias: { |
||||
|
f: 'docs: fix typos', |
||||
|
r: 'docs: update README', |
||||
|
s: 'style: update code format', |
||||
|
b: 'build: bump dependencies', |
||||
|
c: 'chore: update config', |
||||
|
}, |
||||
|
customScopesAlign: !scopeComplete ? 'top' : 'bottom', |
||||
|
defaultScope: scopeComplete, |
||||
|
scopes: [...scopes, 'mock'], |
||||
|
allowEmptyIssuePrefixs: false, |
||||
|
allowCustomIssuePrefixs: false, |
||||
|
|
||||
|
// English |
||||
|
typesAppend: [ |
||||
|
{ value: 'wip', name: 'wip: work in process' }, |
||||
|
{ value: 'workflow', name: 'workflow: workflow improvements' }, |
||||
|
{ value: 'types', name: 'types: type definition file changes' }, |
||||
|
], |
||||
|
|
||||
|
// 中英文对照版 |
||||
|
// messages: { |
||||
|
// type: '选择你要提交的类型 :', |
||||
|
// scope: '选择一个提交范围 (可选):', |
||||
|
// customScope: '请输入自定义的提交范围 :', |
||||
|
// subject: '填写简短精炼的变更描述 :\n', |
||||
|
// body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n', |
||||
|
// breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n', |
||||
|
// footerPrefixsSelect: '选择关联issue前缀 (可选):', |
||||
|
// customFooterPrefixs: '输入自定义issue前缀 :', |
||||
|
// footer: '列举关联issue (可选) 例如: #31, #I3244 :\n', |
||||
|
// confirmCommit: '是否提交或修改commit ?', |
||||
|
// }, |
||||
|
// types: [ |
||||
|
// { value: 'feat', name: 'feat: 新增功能' }, |
||||
|
// { value: 'fix', name: 'fix: 修复缺陷' }, |
||||
|
// { value: 'docs', name: 'docs: 文档变更' }, |
||||
|
// { value: 'style', name: 'style: 代码格式' }, |
||||
|
// { value: 'refactor', name: 'refactor: 代码重构' }, |
||||
|
// { value: 'perf', name: 'perf: 性能优化' }, |
||||
|
// { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' }, |
||||
|
// { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' }, |
||||
|
// { value: 'ci', name: 'ci: 修改 CI 配置、脚本' }, |
||||
|
// { value: 'revert', name: 'revert: 回滚 commit' }, |
||||
|
// { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' }, |
||||
|
// { value: 'wip', name: 'wip: 正在开发中' }, |
||||
|
// { value: 'workflow', name: 'workflow: 工作流程改进' }, |
||||
|
// { value: 'types', name: 'types: 类型定义文件修改' }, |
||||
|
// ], |
||||
|
// emptyScopesAlias: 'empty: 不填写', |
||||
|
// customScopesAlias: 'custom: 自定义', |
||||
|
}, |
||||
|
}; |
@ -0,0 +1,19 @@ |
|||||
|
root = true |
||||
|
|
||||
|
[*] |
||||
|
charset=utf-8 |
||||
|
end_of_line=lf |
||||
|
insert_final_newline=true |
||||
|
indent_style=space |
||||
|
indent_size=2 |
||||
|
max_line_length = 100 |
||||
|
|
||||
|
[*.{yml,yaml,json}] |
||||
|
indent_style = space |
||||
|
indent_size = 2 |
||||
|
|
||||
|
[*.md] |
||||
|
trim_trailing_whitespace = false |
||||
|
|
||||
|
[Makefile] |
||||
|
indent_style = tab |
@ -0,0 +1,2 @@ |
|||||
|
# spa-title |
||||
|
VITE_GLOB_APP_TITLE = Plus Admin |
@ -0,0 +1,17 @@ |
|||||
|
# public path |
||||
|
VITE_PUBLIC_PATH = / |
||||
|
|
||||
|
# 后台请求路径 具体在vite.config.ts配置代理 |
||||
|
VITE_GLOB_API_URL=/basic-api |
||||
|
|
||||
|
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) |
||||
|
VITE_GLOB_ENABLE_ENCRYPT=true |
||||
|
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 |
||||
|
VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== |
||||
|
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 |
||||
|
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= |
||||
|
# 客户端id |
||||
|
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e |
||||
|
|
||||
|
# 开启WEBSOCKET |
||||
|
VITE_GLOB_WEBSOCKET_ENABLE=true |
@ -0,0 +1,27 @@ |
|||||
|
# public path |
||||
|
VITE_PUBLIC_PATH = / |
||||
|
|
||||
|
# 是否开启压缩 需要nginx支持 |
||||
|
# 可选gzip brotli 也可共存 'brotli,gzip' |
||||
|
VITE_BUILD_COMPRESS = 'gzip' |
||||
|
|
||||
|
# 是否删除所有的console.xx 和 debugger production模式才会生效 |
||||
|
VITE_DROP_CONSOLE = true |
||||
|
|
||||
|
# 后端路径 |
||||
|
VITE_GLOB_API_URL=/prod-api |
||||
|
|
||||
|
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) |
||||
|
VITE_GLOB_ENABLE_ENCRYPT=true |
||||
|
|
||||
|
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 |
||||
|
VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== |
||||
|
|
||||
|
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 |
||||
|
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= |
||||
|
|
||||
|
# 客户端id |
||||
|
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e |
||||
|
|
||||
|
# 开启WEBSOCKET |
||||
|
VITE_GLOB_WEBSOCKET_ENABLE=true |
@ -0,0 +1,17 @@ |
|||||
|
# public path |
||||
|
VITE_PUBLIC_PATH = / |
||||
|
|
||||
|
# 后台请求路径 具体在vite.config.ts配置代理 |
||||
|
VITE_GLOB_API_URL=/basic-api |
||||
|
|
||||
|
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) |
||||
|
VITE_GLOB_ENABLE_ENCRYPT=true |
||||
|
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 |
||||
|
VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== |
||||
|
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 |
||||
|
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= |
||||
|
# 客户端id |
||||
|
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e |
||||
|
|
||||
|
# 开启WEBSOCKET |
||||
|
VITE_GLOB_WEBSOCKET_ENABLE=true |
@ -0,0 +1,16 @@ |
|||||
|
|
||||
|
*.sh |
||||
|
node_modules |
||||
|
*.md |
||||
|
*.woff |
||||
|
*.ttf |
||||
|
.vscode |
||||
|
.idea |
||||
|
dist |
||||
|
/public |
||||
|
/docs |
||||
|
.husky |
||||
|
.local |
||||
|
/bin |
||||
|
Dockerfile |
||||
|
package.json |
@ -0,0 +1,7 @@ |
|||||
|
module.exports = { |
||||
|
root: true, |
||||
|
extends: ['@vben'], |
||||
|
rules: { |
||||
|
'no-undef': 'off', |
||||
|
}, |
||||
|
}; |
@ -0,0 +1,11 @@ |
|||||
|
# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings |
||||
|
|
||||
|
# Automatically normalize line endings (to LF) for all text-based files. |
||||
|
* text=auto eol=lf |
||||
|
|
||||
|
# Declare files that will always have CRLF line endings on checkout. |
||||
|
*.{cmd,[cC][mM][dD]} text eol=crlf |
||||
|
*.{bat,[bB][aA][tT]} text eol=crlf |
||||
|
|
||||
|
# Denote all files that are truly binary and should not be modified. |
||||
|
*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary |
@ -0,0 +1,38 @@ |
|||||
|
name: Bug 反馈 |
||||
|
description: 当你在代码中发现了一个 Bug,导致应用崩溃或抛出异常,或者有一个组件存在问题,或者某些地方看起来不对劲。 |
||||
|
title: '[Bug]: ' |
||||
|
labels: ['bug'] |
||||
|
body: |
||||
|
- type: markdown |
||||
|
attributes: |
||||
|
value: | |
||||
|
感谢对项目的支持与关注。在提出问题之前,请确保你已查看相关开发或使用文档: |
||||
|
- https://... |
||||
|
- type: checkboxes |
||||
|
attributes: |
||||
|
label: 这个问题是否已经存在? |
||||
|
options: |
||||
|
- label: 我已经搜索过现有的问题 (https://gitee.com/../../issues) |
||||
|
required: true |
||||
|
- type: textarea |
||||
|
attributes: |
||||
|
label: 如何复现 |
||||
|
description: 请详细告诉我们如何复现你遇到的问题,如涉及代码,可提供一个最小代码示例,并使用反引号```附上它 |
||||
|
placeholder: | |
||||
|
1. ... |
||||
|
2. ... |
||||
|
3. ... |
||||
|
validations: |
||||
|
required: true |
||||
|
- type: textarea |
||||
|
attributes: |
||||
|
label: 预期结果 |
||||
|
description: 请告诉我们你预期会发生什么。 |
||||
|
validations: |
||||
|
required: true |
||||
|
- type: textarea |
||||
|
attributes: |
||||
|
label: 截图或视频 |
||||
|
description: 如果可以的话,上传任何关于 bug 的截图。 |
||||
|
value: | |
||||
|
[在这里上传图片] |
@ -0,0 +1 @@ |
|||||
|
blank_issues_enabled: true |
@ -0,0 +1,43 @@ |
|||||
|
name: 功能建议 |
||||
|
description: 对本项目提出一个功能建议 |
||||
|
title: '[功能建议]: ' |
||||
|
labels: ['enhancement'] |
||||
|
body: |
||||
|
- type: markdown |
||||
|
attributes: |
||||
|
value: | |
||||
|
感谢提出功能建议,我们将仔细考虑! |
||||
|
- type: textarea |
||||
|
id: related-problem |
||||
|
attributes: |
||||
|
label: 你的功能建议是否和某个问题相关? |
||||
|
description: 清晰并简洁地描述问题是什么,例如,当我...时,我总是感到困扰。 |
||||
|
validations: |
||||
|
required: false |
||||
|
- type: textarea |
||||
|
id: desired-solution |
||||
|
attributes: |
||||
|
label: 你希望看到什么解决方案? |
||||
|
description: 清晰并简洁地描述你希望发生的事情。 |
||||
|
validations: |
||||
|
required: true |
||||
|
- type: textarea |
||||
|
id: alternatives |
||||
|
attributes: |
||||
|
label: 你考虑过哪些替代方案? |
||||
|
description: 清晰并简洁地描述你考虑过的任何替代解决方案或功能。 |
||||
|
validations: |
||||
|
required: false |
||||
|
- type: textarea |
||||
|
id: additional-context |
||||
|
attributes: |
||||
|
label: 你有其他上下文或截图吗? |
||||
|
description: 在此处添加有关功能请求的任何其他上下文或截图。 |
||||
|
validations: |
||||
|
required: false |
||||
|
- type: checkboxes |
||||
|
attributes: |
||||
|
label: 意向参与贡献 |
||||
|
options: |
||||
|
- label: 我有意向参与具体功能的开发实现并将代码贡献回到上游社区 |
||||
|
required: false |
@ -0,0 +1,34 @@ |
|||||
|
node_modules |
||||
|
.DS_Store |
||||
|
dist |
||||
|
.cache |
||||
|
.turbo |
||||
|
|
||||
|
tests/server/static |
||||
|
tests/server/static/upload |
||||
|
|
||||
|
.local |
||||
|
# local env files |
||||
|
.env.local |
||||
|
.env.*.local |
||||
|
.eslintcache |
||||
|
|
||||
|
# Log files |
||||
|
npm-debug.log* |
||||
|
yarn-debug.log* |
||||
|
yarn-error.log* |
||||
|
pnpm-debug.log* |
||||
|
|
||||
|
# Editor directories and files |
||||
|
.idea |
||||
|
# .vscode |
||||
|
*.suo |
||||
|
*.ntvs* |
||||
|
*.njsproj |
||||
|
*.sln |
||||
|
*.sw? |
||||
|
|
||||
|
package-lock.json |
||||
|
pnpm-lock.yaml |
||||
|
|
||||
|
.history |
@ -0,0 +1,8 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
# shellcheck source=./_/husky.sh |
||||
|
. "$(dirname "$0")/_/husky.sh" |
||||
|
|
||||
|
PATH="/usr/local/bin:$PATH" |
||||
|
|
||||
|
npx --no-install commitlint --edit "$1" |
@ -0,0 +1,9 @@ |
|||||
|
#!/bin/sh |
||||
|
command_exists () { |
||||
|
command -v "$1" >/dev/null 2>&1 |
||||
|
} |
||||
|
|
||||
|
# Workaround for Windows 10, Git Bash and Yarn |
||||
|
if command_exists winpty && test -t 1; then |
||||
|
exec < /dev/tty |
||||
|
fi |
@ -0,0 +1,10 @@ |
|||||
|
#!/bin/sh |
||||
|
. "$(dirname "$0")/_/husky.sh" |
||||
|
. "$(dirname "$0")/common.sh" |
||||
|
|
||||
|
[ -n "$CI" ] && exit 0 |
||||
|
|
||||
|
PATH="/usr/local/bin:$PATH" |
||||
|
|
||||
|
# Format and submit code according to lintstagedrc.js configuration |
||||
|
pnpm exec lint-staged |
@ -0,0 +1,8 @@ |
|||||
|
public-hoist-pattern[]=husky |
||||
|
public-hoist-pattern[]=*eslint* |
||||
|
public-hoist-pattern[]=*prettier* |
||||
|
public-hoist-pattern[]=lint-staged |
||||
|
public-hoist-pattern[]=*stylelint* |
||||
|
public-hoist-pattern[]=@commitlint/cli |
||||
|
public-hoist-pattern[]=@vben/eslint-config |
||||
|
package-manager-strict=false |
@ -0,0 +1,12 @@ |
|||||
|
dist |
||||
|
.local |
||||
|
.output.js |
||||
|
node_modules |
||||
|
|
||||
|
**/*.svg |
||||
|
**/*.sh |
||||
|
|
||||
|
public |
||||
|
.npmrc |
||||
|
|
||||
|
*-lock.yaml |
@ -0,0 +1,19 @@ |
|||||
|
module.exports = { |
||||
|
printWidth: 100, |
||||
|
semi: true, |
||||
|
vueIndentScriptAndStyle: true, |
||||
|
singleQuote: true, |
||||
|
trailingComma: 'all', |
||||
|
proseWrap: 'never', |
||||
|
htmlWhitespaceSensitivity: 'strict', |
||||
|
endOfLine: 'auto', |
||||
|
plugins: ['prettier-plugin-packagejson'], |
||||
|
overrides: [ |
||||
|
{ |
||||
|
files: '.*rc', |
||||
|
options: { |
||||
|
parser: 'json', |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
}; |
@ -0,0 +1,2 @@ |
|||||
|
dist |
||||
|
public |
@ -0,0 +1,4 @@ |
|||||
|
module.exports = { |
||||
|
root: true, |
||||
|
extends: ['@vben/stylelint-config'], |
||||
|
}; |
@ -0,0 +1,14 @@ |
|||||
|
{ |
||||
|
"recommendations": [ |
||||
|
"vue.volar", |
||||
|
"dbaeumer.vscode-eslint", |
||||
|
"stylelint.vscode-stylelint", |
||||
|
"esbenp.prettier-vscode", |
||||
|
"mrmlnc.vscode-less", |
||||
|
"lokalise.i18n-ally", |
||||
|
"antfu.iconify", |
||||
|
"antfu.unocss", |
||||
|
"mikestead.dotenv", |
||||
|
"warmthsea.vscode-custom-code-color" |
||||
|
] |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
{ |
||||
|
"version": "0.2.0", |
||||
|
"configurations": [ |
||||
|
{ |
||||
|
"type": "chrome", |
||||
|
"request": "launch", |
||||
|
"name": "Launch Chrome", |
||||
|
"url": "http://localhost:3100", |
||||
|
"webRoot": "${workspaceFolder}/src", |
||||
|
"sourceMaps": true |
||||
|
} |
||||
|
] |
||||
|
} |
@ -0,0 +1,192 @@ |
|||||
|
{ |
||||
|
"typescript.tsdk": "./node_modules/typescript/lib", |
||||
|
"volar.tsPlugin": true, |
||||
|
"volar.tsPluginStatus": false, |
||||
|
"npm.packageManager": "pnpm", |
||||
|
"editor.tabSize": 2, |
||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode", |
||||
|
"files.eol": "\n", |
||||
|
"search.exclude": { |
||||
|
"**/node_modules": true, |
||||
|
"**/*.log": true, |
||||
|
"**/*.log*": true, |
||||
|
"**/bower_components": true, |
||||
|
"**/dist": true, |
||||
|
"**/elehukouben": true, |
||||
|
"**/.git": true, |
||||
|
"**/.gitignore": true, |
||||
|
"**/.svn": true, |
||||
|
"**/.DS_Store": true, |
||||
|
"**/.idea": true, |
||||
|
"**/.vscode": false, |
||||
|
"**/yarn.lock": true, |
||||
|
"**/tmp": true, |
||||
|
"out": true, |
||||
|
"dist": true, |
||||
|
"node_modules": true, |
||||
|
"CHANGELOG.md": true, |
||||
|
"examples": true, |
||||
|
"res": true, |
||||
|
"screenshots": true, |
||||
|
"yarn-error.log": true, |
||||
|
"**/.yarn": true |
||||
|
}, |
||||
|
"files.exclude": { |
||||
|
"**/.cache": true, |
||||
|
"**/.editorconfig": true, |
||||
|
"**/.eslintcache": true, |
||||
|
"**/bower_components": true, |
||||
|
"**/.idea": true, |
||||
|
"**/tmp": true, |
||||
|
"**/.git": true, |
||||
|
"**/.svn": true, |
||||
|
"**/.hg": true, |
||||
|
"**/CVS": true, |
||||
|
"**/.DS_Store": true |
||||
|
}, |
||||
|
"files.watcherExclude": { |
||||
|
"**/.git/objects/**": true, |
||||
|
"**/.git/subtree-cache/**": true, |
||||
|
"**/.vscode/**": true, |
||||
|
"**/node_modules/**": true, |
||||
|
"**/tmp/**": true, |
||||
|
"**/bower_components/**": true, |
||||
|
"**/dist/**": true, |
||||
|
"**/yarn.lock": true |
||||
|
}, |
||||
|
"stylelint.enable": true, |
||||
|
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"], |
||||
|
"path-intellisense.mappings": { |
||||
|
"@/": "${workspaceRoot}/src" |
||||
|
}, |
||||
|
"[javascriptreact]": { |
||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode" |
||||
|
}, |
||||
|
"[typescript]": { |
||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode" |
||||
|
}, |
||||
|
"[typescriptreact]": { |
||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode" |
||||
|
}, |
||||
|
"[html]": { |
||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode" |
||||
|
}, |
||||
|
"[css]": { |
||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode" |
||||
|
}, |
||||
|
"[less]": { |
||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode" |
||||
|
}, |
||||
|
"[scss]": { |
||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode" |
||||
|
}, |
||||
|
"[markdown]": { |
||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode" |
||||
|
}, |
||||
|
"editor.codeActionsOnSave": { |
||||
|
"source.fixAll.eslint": "explicit", |
||||
|
"source.fixAll.stylelint": "explicit" |
||||
|
}, |
||||
|
"[vue]": { |
||||
|
"editor.codeActionsOnSave": { |
||||
|
"source.fixAll.eslint": "explicit", |
||||
|
"source.fixAll.stylelint": "explicit" |
||||
|
}, |
||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode" |
||||
|
}, |
||||
|
"i18n-ally.localesPaths": ["src/locales/lang"], |
||||
|
"i18n-ally.keystyle": "nested", |
||||
|
"i18n-ally.sortKeys": true, |
||||
|
"i18n-ally.namespace": true, |
||||
|
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}", |
||||
|
"i18n-ally.enabledParsers": ["json", "ts", "js"], |
||||
|
"i18n-ally.sourceLanguage": "en", |
||||
|
"i18n-ally.displayLanguage": "zh-CN", |
||||
|
"i18n-ally.enabledFrameworks": ["vue", "react"], |
||||
|
"cSpell.words": [ |
||||
|
"antd", |
||||
|
"antv", |
||||
|
"brotli", |
||||
|
"browserslist", |
||||
|
"Cascader", |
||||
|
"clientid", |
||||
|
"codemirror", |
||||
|
"colorpicker", |
||||
|
"commitlint", |
||||
|
"cropperjs", |
||||
|
"Datas", |
||||
|
"didi", |
||||
|
"echarts", |
||||
|
"esnext", |
||||
|
"esno", |
||||
|
"exceljs", |
||||
|
"iconify", |
||||
|
"INNERLINK", |
||||
|
"INTLIFY", |
||||
|
"jsencrypt", |
||||
|
"lintstagedrc", |
||||
|
"logicflow", |
||||
|
"mockjs", |
||||
|
"nprogress", |
||||
|
"packagejson", |
||||
|
"PARENTVIEW", |
||||
|
"persistedstate", |
||||
|
"phonenumber", |
||||
|
"pinia", |
||||
|
"pnpm", |
||||
|
"Popconfirm", |
||||
|
"preinstall", |
||||
|
"qrcode", |
||||
|
"ruoyi", |
||||
|
"sider", |
||||
|
"sortablejs", |
||||
|
"stylelint", |
||||
|
"tailwindcss", |
||||
|
"tinymce", |
||||
|
"unocss", |
||||
|
"unref", |
||||
|
"vben", |
||||
|
"vditor", |
||||
|
"Vite", |
||||
|
"vitejs", |
||||
|
"vuedraggable", |
||||
|
"vueuse", |
||||
|
"zxcvbn" |
||||
|
], |
||||
|
"vetur.format.scriptInitialIndent": true, |
||||
|
"vetur.format.styleInitialIndent": true, |
||||
|
"vetur.validation.script": false, |
||||
|
"MicroPython.executeButton": [ |
||||
|
{ |
||||
|
"text": "▶", |
||||
|
"tooltip": "运行", |
||||
|
"alignment": "left", |
||||
|
"command": "extension.executeFile", |
||||
|
"priority": 3.5 |
||||
|
} |
||||
|
], |
||||
|
"MicroPython.syncButton": [ |
||||
|
{ |
||||
|
"text": "$(sync)", |
||||
|
"tooltip": "同步", |
||||
|
"alignment": "left", |
||||
|
"command": "extension.execute", |
||||
|
"priority": 4 |
||||
|
} |
||||
|
], |
||||
|
// 控制相关文件嵌套展示 |
||||
|
"explorer.fileNesting.enabled": true, |
||||
|
"explorer.fileNesting.expand": false, |
||||
|
"explorer.fileNesting.patterns": { |
||||
|
"*.ts": "$(capture).test.ts, $(capture).test.tsx", |
||||
|
"*.tsx": "$(capture).test.ts, $(capture).test.tsx", |
||||
|
"*.env": "$(capture).env.*", |
||||
|
"CHANGELOG.md": "CHANGELOG*", |
||||
|
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,README*,.npmrc,.browserslistrc", |
||||
|
".eslintrc.cjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,.stylelintrc.*" |
||||
|
}, |
||||
|
"terminal.integrated.scrollback": 10000, |
||||
|
"nuxt.isNuxtApp": false, |
||||
|
"vscodeCustomCodeColor.highlightValue": "v-auth", |
||||
|
"vscodeCustomCodeColor.highlightValueColor": "#6366f1" |
||||
|
} |
@ -0,0 +1,479 @@ |
|||||
|
# 1.3.5 |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
新增导出功能(带查询参数) |
||||
|
|
||||
|
duplicateRoutesChecker 路由name重复检查器 |
||||
|
|
||||
|
**Refactor** |
||||
|
|
||||
|
重构用户选择`UserSelect`组件, 不用在父页面多次引入, 增加对应type的emit即可 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
修改全局名称为`Ruoyi Plus` -> `Plus Admin` |
||||
|
|
||||
|
eslint/typescript扫描 修复全局报错 现在不会有任何报错(vscode) |
||||
|
|
||||
|
因Vxe-Table 4.7版本类型定义与4.6不同 回滚为4.6版本 |
||||
|
|
||||
|
导出取消`PopConfirmButton`二次确认 改为`Button` |
||||
|
|
||||
|
更新`update_icon.sql`文件 |
||||
|
|
||||
|
# 1.3.4 |
||||
|
|
||||
|
**Refactor** |
||||
|
|
||||
|
合并最新vben Upload组件 添加demo |
||||
|
|
||||
|
根据后端重构oauth相关功能 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
通知提醒的分页样式 |
||||
|
|
||||
|
修复 [vue-router 4.3.2 -> 4.3.3](https://github.com/vuejs/router/compare/v4.3.2...v4.3.3#diff-bd14e588139087e2c8f184c61b6978ffb473a2d63261ce07b19c8ffd9882d955R551) warn `permissionGuard.ts:82 [Vue Router warn]: Finding ancestor route "/:path(.*)*" failed for "/:path(.*)*"` |
||||
|
|
||||
|
Vxe-Table 升级 4.7.25 需要重新`pnpm install` |
||||
|
|
||||
|
# 1.3.3 |
||||
|
|
||||
|
**Refactor** |
||||
|
|
||||
|
`TableSwitch`组件去除二次确认Modal, 可直接切换 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
流程设计器 单选 在id为undefined下打开窗口会错误赋值id-0 |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
锁定vxe-table版本号 "vxe-table": "~4.6.3", 防止版本冲突 |
||||
|
|
||||
|
# 1.3.2 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
强制指定vite-config模块的postcss版本 解决高版本unocss报错问题(unocss版本暂时锁死0.60.4) |
||||
|
|
||||
|
修复消息通知点击通知一直弹窗不会关闭的问题 |
||||
|
|
||||
|
# 1.3.1 |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
系统主题切换功能(重磅登场!) |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
暂时降级@Unocss到0.58.9版本 更高版本报错无法运行 |
||||
|
|
||||
|
# 1.3.0 |
||||
|
|
||||
|
合并最新vben依赖 |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
加签/减签功能 |
||||
|
|
||||
|
# 1.2.0(大版本更新) |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
当存在`菜单根目录为菜单`形式(比如演示站的微信群) & login?redirect=%252F(即/) 会错误调转到`对应的第一个根目录菜单的页面`(空页面) 解决: 判断为/跳转到首页 |
||||
|
|
||||
|
官方的element使用ParentView来处理二级菜单 使用ParentView的菜单不应被添加到路由(添加到侧边菜单就够了) |
||||
|
|
||||
|
ps: 相同name的路由 后一个会覆盖前一个(https://www.cnblogs.com/mochenxiya/p/16732793.html) |
||||
|
|
||||
|
OSS 图片拓展名大写无法预览 -> toLowerCase()比较 |
||||
|
|
||||
|
代码生成 编辑 下拉框组件在展开状态下滚动不会跟随 -> 官方方案: [注意,如果发现下拉菜单跟随页面滚动,或者需要在其他弹层中触发 Select,请尝试使用 getPopupContainer={triggerNode => triggerNode.parentNode} 将下拉弹层渲染节点固定在触发器的父元素中。](https://www.antdv.com/components/select-cn#api) |
||||
|
|
||||
|
**Refactor** |
||||
|
|
||||
|
主题色(primaryColor)由`#0960bd`修改为`#1677FF`[AntDesign品牌色](https://ant-design.antgroup.com/docs/spec/colors-cn#%E4%BA%A7%E5%93%81%E7%BA%A7%E8%89%B2%E5%BD%A9%E4%BD%93%E7%B3%BB) |
||||
|
|
||||
|
使用Ant Design推荐的设计规范 将表格按钮从title栏(左边)移动到toolbar(右边) |
||||
|
|
||||
|
详见: |
||||
|
|
||||
|
- [有很多按钮组,如何确定顺序](https://ant-design.antgroup.com/docs/spec/buttons-cn#%E6%9C%89%E5%BE%88%E5%A4%9A%E6%8C%89%E9%92%AE%E7%BB%84%E5%A6%82%E4%BD%95%E7%A1%AE%E5%AE%9A%E9%A1%BA%E5%BA%8F) |
||||
|
- [按钮区](https://ant-design.antgroup.com/docs/spec/buttons-cn#%E6%8C%89%E9%92%AE%E5%8C%BA) |
||||
|
|
||||
|
修改部分按钮颜色(比如导出)改为次要按钮 |
||||
|
|
||||
|
部分需要二次确认的操作(删除) 由modal改为popConfirm |
||||
|
|
||||
|
- [覆盖层](https://ant-design.antgroup.com/docs/spec/stay-cn#%E8%A6%86%E7%9B%96%E5%B1%82) |
||||
|
|
||||
|
代码生成-编辑 Table编辑组件改为`VxeTable` 性能对比原来的`AntTable`性能大幅提升 |
||||
|
|
||||
|
BasicTable 使用`reload`代替`reloadWithCallback` 官方已经修复modal会有关闭两次动画问题 |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
`VITE_GLOB_ENABLE_ENCRYPT`全局请求加解密开关 |
||||
|
|
||||
|
适配官方workflow版本(预览版) |
||||
|
|
||||
|
TableAction的dropdown按钮支持绑定按钮样式 |
||||
|
|
||||
|
代码生成-代码预览 支持根据文件的不同类型切换不同的预览显示 |
||||
|
|
||||
|
BasicTable reload添加`doNotClearSelectRows`参数 默认reload会清空当前表格所有选中行 |
||||
|
|
||||
|
租户管理 `未添加任何租户套餐时`不允许打开`新增租户抽屉`并弹出提示(所有东西填完了发现没法选择租户套餐所以也没办法提交😅) |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
客户端管理 id为1(默认PC客户端)不可进行禁用 |
||||
|
|
||||
|
Oss配置 新增添加提示信息 |
||||
|
|
||||
|
默认登录页 登录中disabled第三方登录 |
||||
|
|
||||
|
上传文件/头像上传 超时时间设置为30s |
||||
|
|
||||
|
**依赖更新** |
||||
|
|
||||
|
npm依赖升级至目前最新 |
||||
|
|
||||
|
新增依赖 bpmn-js bpmn-js-token-simulation diagram-js diagram-js-minimap didi tiny-svg |
||||
|
|
||||
|
| 依赖 | 功能 | |
||||
|
| ------------------------ | --------------------- | |
||||
|
| bpmn-js | bpmn核心依赖 | |
||||
|
| diagram-js | 创建/管理bpmn图形界面 | |
||||
|
| bpmn-js-token-simulation | 流程图执行模拟 | |
||||
|
| diagram-js-minimap | 流程图小地图 | |
||||
|
| didi | 依赖注入 | |
||||
|
| tiny-svg | svg依赖 | |
||||
|
|
||||
|
# 1.1.10 |
||||
|
|
||||
|
租户套餐 增加预览菜单功能 |
||||
|
|
||||
|
去除表格上部表单 重置/搜索的前置图标 |
||||
|
|
||||
|
默认DictTag的primary色由`blue`改为`primary` |
||||
|
|
||||
|
字典标签重构 支持`自定义颜色`+`css属性(前置/后置小点效果)` |
||||
|
|
||||
|
unocss支持iconify图标 不用再导入Icon组件 |
||||
|
|
||||
|
- 即`<div class="i-material-symbols-light:13mp-outline-sharp"></div>`写法 |
||||
|
|
||||
|
~~用户管理 新增/修改 部门选择改为`级联选择器`组件~~ (由于修改了岗位和部门联动逻辑, 恢复成下拉) |
||||
|
|
||||
|
# 1.1.9 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
当存在`菜单根目录为菜单`形式(比如演示站的微信群) & login?redirect=%252F(即/) 会错误调转到`对应的第一个根目录菜单的页面`(空页面) 解决: 判断为/跳转到首页 |
||||
|
|
||||
|
官方的element使用ParentView来处理二级菜单 使用ParentView的菜单不应被添加到路由(添加到侧边菜单就够了) |
||||
|
|
||||
|
ps: 相同name的路由 后一个会覆盖前一个(https://www.cnblogs.com/mochenxiya/p/16732793.html) |
||||
|
|
||||
|
**Refactor** |
||||
|
|
||||
|
主题色(primaryColor)由`#0960bd`修改为`#1677FF`[AntDesign品牌色](https://ant-design.antgroup.com/docs/spec/colors-cn#%E4%BA%A7%E5%93%81%E7%BA%A7%E8%89%B2%E5%BD%A9%E4%BD%93%E7%B3%BB) |
||||
|
|
||||
|
使用Ant Design推荐的设计规范 将表格按钮从title栏(左边)移动到toolbar(右边) |
||||
|
|
||||
|
详见: |
||||
|
|
||||
|
- [有很多按钮组,如何确定顺序](https://ant-design.antgroup.com/docs/spec/buttons-cn#%E6%9C%89%E5%BE%88%E5%A4%9A%E6%8C%89%E9%92%AE%E7%BB%84%E5%A6%82%E4%BD%95%E7%A1%AE%E5%AE%9A%E9%A1%BA%E5%BA%8F) |
||||
|
- [按钮区](https://ant-design.antgroup.com/docs/spec/buttons-cn#%E6%8C%89%E9%92%AE%E5%8C%BA) |
||||
|
|
||||
|
修改部分按钮颜色(比如导出)改为次要按钮 |
||||
|
|
||||
|
部分需要二次确认的操作(删除) 由modal改为popConfirm |
||||
|
|
||||
|
- [覆盖层](https://ant-design.antgroup.com/docs/spec/stay-cn#%E8%A6%86%E7%9B%96%E5%B1%82) |
||||
|
|
||||
|
代码生成-编辑 Table编辑组件改为`VxeTable` 性能对比原来的`AntTable`性能大幅提升 |
||||
|
|
||||
|
BasicTable 使用`reload`代替`reloadWithCallback` 官方已经修复modal会有关闭两次动画问题 |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
`VITE_GLOB_ENABLE_ENCRYPT`全局请求加解密开关 |
||||
|
|
||||
|
TableAction的dropdown按钮支持绑定按钮样式 |
||||
|
|
||||
|
代码生成-代码预览 支持根据文件的不同类型切换不同的预览显示 |
||||
|
|
||||
|
BasicTable reload添加`doNotClearSelectRows`参数 默认reload会清空当前表格所有选中行 |
||||
|
|
||||
|
租户管理 `未添加任何租户套餐时`不允许打开`新增租户抽屉`并弹出提示(所有东西填完了发现没法选择租户套餐所以也没办法提交) |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
客户端管理 id为1(默认PC客户端)不可进行禁用 |
||||
|
|
||||
|
Oss配置 新增添加提示信息 |
||||
|
|
||||
|
默认登录页 登录中disabled第三方登录 |
||||
|
|
||||
|
**依赖更新** |
||||
|
|
||||
|
npm依赖升级至目前最新 |
||||
|
|
||||
|
# 1.1.8 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
使用filter方法替代findNodeAll(用于排除节点) (findNodeAll由于children拼写错误导致运行成功--!) |
||||
|
|
||||
|
用户管理 用户导入 下载模板modal添加z-index(设置过小会有bug) 否则下载模板时会遮挡loading效果 |
||||
|
|
||||
|
角色管理 分配角色 导入由modal改为抽屉 修复表格翻页会重置勾选状态 |
||||
|
|
||||
|
**Refactor** |
||||
|
|
||||
|
逻辑更新 去除websocket相关**VITE_GLOB_WEBSOCKET_URL** 兼容apiUrl为http链接/非链接形式 即使用http://aaa.com/bbb或/bbb都行 |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
登录重定向 即登录页login?redirect=重定向地址(即1.1.6有bug被移除 vben官方已经修复) |
||||
|
|
||||
|
- 在登录超时/踢下线/修改角色下线(即403)等一些会携带redirect /login?redirect=xxx地址 |
||||
|
- 如果是正常登出则不带redirect /login |
||||
|
- redirect如果是不存在的地址(手动地址栏输入/更改角色权限导致菜单不存在)则跳转到默认首页 |
||||
|
|
||||
|
增加`手机号登录`的支持 |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
oss的图片预览组件TableImg改为异步导入 解决table加载时间过长(loading会在图片加载完成结束 改为异步则不会有限制) |
||||
|
|
||||
|
大量的拼写错误(还是建议安装一个拼写检查工具 vscode没有自带 --!) |
||||
|
|
||||
|
去除接口前缀相关**VITE_GLOB_API_URL_PREFIX** 直接拼接在**VITE_GLOB_API_URL**即可 |
||||
|
|
||||
|
# 1.1.7 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
租户套餐 在未操作菜单情况下(比如直接点确定/改备注后点确定) transformIdStr函数转number导致丢失精度 -> id直接用string |
||||
|
|
||||
|
src/router/guard/permissionGuard.ts 外链不能被添加到路由(漏了) |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
租户套餐 隐藏租户相关菜单 只有superadmin可以操作 分配了也没法用 |
||||
|
|
||||
|
角色管理 隐藏租户相关菜单 只有superadmin可以操作 分配了也没法用 |
||||
|
|
||||
|
角色管理 小管理员(admin)不可操作(防止误操作把自己权限玩没) |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
登录 有验证码和无验证码时input宽度(404-400px)统一 原版本在有验证码情况下input宽度太长了 |
||||
|
|
||||
|
用户管理 部门树选择 ->改为/ |
||||
|
|
||||
|
菜单管理 菜单树选择 ->改为/ |
||||
|
|
||||
|
部门管理 部门树选择 ->改为/ |
||||
|
|
||||
|
用户管理 左侧部门树增加图标 |
||||
|
|
||||
|
角色管理 仅超级管理员可修改小管理员菜单 |
||||
|
|
||||
|
# 1.1.6-fix |
||||
|
|
||||
|
~~登录重定向 即登录页login?redirect=重定向地址~~ **功能有严重BUG 回滚版本** |
||||
|
|
||||
|
# 1.1.6 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
修复同一个字典多次请求api(页面/modal/drawer会加载三次), 现在只会请求一次 |
||||
|
|
||||
|
角色管理 分配用户 Number(roleId)导致精度丢失 -> 改为string |
||||
|
|
||||
|
/@/ => @/(新版vben已不支持/@/路径) |
||||
|
|
||||
|
pinia-plugin-persistedstate插件无法持久化(key的问题) |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
登录页面 租户和验证码都加载完成登录按钮才可用(enable) |
||||
|
|
||||
|
**有严重BUG 于1.1.6-fix版本删除** ~~登录重定向 即登录页login?redirect=重定向地址~~ |
||||
|
|
||||
|
- ~~在登录超时/踢下线/修改角色下线(即403)等一些会携带redirect~~ |
||||
|
- ~~如果是正常登出则不带redirect~~ |
||||
|
- ~~redirect如果是不存在的地址则跳转到默认首页~~ |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
控制台warning: [DOM] Found 2 elements with non-unique id #form_item_configKey: (More info: https://goo.gl/9p2vKq) 主要是由于id冲突(即搜索和更新用的同一个id) 表单添加上name参数即可 |
||||
|
|
||||
|
改了一些代码(oxlint warning) 主要是代码风格 |
||||
|
|
||||
|
# 1.1.5 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
无 |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
无 |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
用户管理 默认头像 |
||||
|
|
||||
|
userStore 默认头像 |
||||
|
|
||||
|
操作日志 添加id |
||||
|
|
||||
|
客户端管理 pc不允许禁用(编辑里仍然可以修改) |
||||
|
|
||||
|
消息通知 style |
||||
|
|
||||
|
# 1.1.4 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
夜间模式通过刷新加载会有短暂白屏问题 |
||||
|
|
||||
|
回滚部门管理代码 逻辑有问题 --! |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
用户管理 部门树skeleton加载 |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
用户管理 用户信息modal使用skeleton加载 |
||||
|
|
||||
|
oss配置 v-auth |
||||
|
|
||||
|
代码生成 v-auth |
||||
|
|
||||
|
# 1.1.3 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
无 |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
用户管理 用户信息预览 |
||||
|
|
||||
|
添加使用modal/drawer页面打开时的loading |
||||
|
|
||||
|
代码生成 - 代码预览样式优化 |
||||
|
|
||||
|
代码生成 - 树表 |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
个人中心 绑定item间距 |
||||
|
|
||||
|
# 1.1.2 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
通知公告 - 删除(变量写错了导致无法删除) |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
通知公告 - 预览 |
||||
|
|
||||
|
富文本编辑器(TinyMce)支持图片大小修改(右键修改) |
||||
|
|
||||
|
富文本编辑器(TinyMce)支持图片粘贴 (base64格式非上传) |
||||
|
|
||||
|
**大部分组件新增抽屉 极少数表单只有几行的没加** |
||||
|
|
||||
|
部门管理 切换上级部门时 对应的负责人列表也会变化 |
||||
|
|
||||
|
登录日志 根据浏览器/系统名称显示对应图标 |
||||
|
|
||||
|
在线用户 根据浏览器/系统名称显示对应图标 |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
手机端不显示租户切换(有遮挡) |
||||
|
|
||||
|
租户下拉框样式优化 |
||||
|
|
||||
|
- 用户管理-用户导入 样式更新 |
||||
|
- 用户管理-密码修改 样式更新 |
||||
|
- 操作日志 添加'部门' |
||||
|
|
||||
|
**大部分页面支持移动端(聊胜于无)** |
||||
|
|
||||
|
# 1.1.1 |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
(样式)按钮点击后任然有焦点 |
||||
|
|
||||
|
(样式)TableSetting居中 |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
操作日志支持模态框/抽屉打开 可自行选择 |
||||
|
|
||||
|
操作日志清空添加等待时间5S 等待完成才能点击 防止误操作 |
||||
|
|
||||
|
登录日志清空添加等待时间5S 等待完成才能点击 防止误操作 |
||||
|
|
||||
|
个人中心添加loading效果 |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
菜单管理 为按钮时不再显示"新增按钮"(不合逻辑) |
||||
|
|
||||
|
树表(如菜单管理/部门管理)去除空children 这样前面就不会有展开/关闭图标了 |
||||
|
|
||||
|
代码生成-多选删除 按钮样式 |
||||
|
|
||||
|
CollapseContainer border-radius 2px -> 6px |
||||
|
|
||||
|
缓存监控 添加图标 |
||||
|
|
||||
|
表格圆角 2px -> 6px |
||||
|
|
||||
|
# 1.1.0 (2024年1月16日) |
||||
|
|
||||
|
**依赖更新** |
||||
|
|
||||
|
- 升级目前最新最新vben |
||||
|
|
||||
|
**Bug Fixed** |
||||
|
|
||||
|
- 修复表格在开启border情况下 左边有一条竖线的样式问题 |
||||
|
- 修复tab的关闭按钮"X"没有居中样式问题 |
||||
|
|
||||
|
**Feature** |
||||
|
|
||||
|
- 树表支持双击展开/折叠 -菜单管理/部门管理 |
||||
|
|
||||
|
**其他** |
||||
|
|
||||
|
- 登录后右上角昵称默认加粗显示 |
||||
|
|
||||
|
# 1.0.0 (2023-11-1) |
||||
|
|
||||
|
**依赖更新** |
||||
|
|
||||
|
- 升级目前最新最新vben 使用antv4版本 |
||||
|
|
||||
|
# 0.1.0 |
||||
|
|
||||
|
没写 初始完成版本 使用antv3版本 |
@ -0,0 +1,21 @@ |
|||||
|
MIT License |
||||
|
|
||||
|
Copyright (c) 2020-present, Vben |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
of this software and associated documentation files (the "Software"), to deal |
||||
|
in the Software without restriction, including without limitation the rights |
||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
copies of the Software, and to permit persons to whom the Software is |
||||
|
furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be included in all |
||||
|
copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
|
SOFTWARE. |
@ -0,0 +1,177 @@ |
|||||
|
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="200" height="200" src="https://anncwb.github.io/anncwb/images/logo.png"> </a> <br> <br> |
||||
|
|
||||
|
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) |
||||
|
|
||||
|
<h1>RuoYi Plus Vben</h1> |
||||
|
</div> |
||||
|
|
||||
|
## 提示 |
||||
|
|
||||
|
已经将 antv4 版本的代码提交到该仓库 即默认 main 分支 |
||||
|
|
||||
|
关于运行警告: `The CJS build of Vite's Node API is deprecated. See https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.` 是由于升级vite5 官方还未解决但是不影响使用 详见[vben issue](https://github.com/vbenjs/vue-vben-admin/pull/3508) |
||||
|
|
||||
|
## 简介 |
||||
|
|
||||
|
基于 [vben(ant-design-vue)](https://github.com/vbenjs/vue-vben-admin) 的 RuoYi-Vue-Plus 前端项目 |
||||
|
|
||||
|
| 组件/框架 | 版本 | |
||||
|
| :------------- | :----- | |
||||
|
| vben | 2.11.4 | |
||||
|
| ant-design-vue | 4.2.1 | |
||||
|
| vue | 3.4.25 | |
||||
|
|
||||
|
对应后端项目: **(分布式 5.X 分支 微服务 2.分支)** |
||||
|
|
||||
|
分布式 [RuoYi-Vue-Plus](https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/) |
||||
|
|
||||
|
微服务 [RuoYi-Cloud-Plus](https://gitee.com/dromara/RuoYi-Cloud-Plus/tree/2.X/) |
||||
|
|
||||
|
## 预览 |
||||
|
|
||||
|
admin 账号: admin admin123 |
||||
|
|
||||
|
[预览地址点这里](http://117.72.10.31) |
||||
|
|
||||
|
## WX Group |
||||
|
|
||||
|
广告佬g远点, 正常人从演示站加 |
||||
|
|
||||
|
## 文档 |
||||
|
|
||||
|
[vben 文档地址](https://doc.vvbin.cn/) |
||||
|
|
||||
|
[RuoYi-Plus 文档地址](https://plus-doc.dromara.org/#/) |
||||
|
|
||||
|
[本框架文档(必看)](https://117.72.10.31:6060/) |
||||
|
|
||||
|
## 预览图 |
||||
|
|
||||
|
![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/1.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/2.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/3.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/4.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/5.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/6.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/7.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/8.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/9.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/10.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/11.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/12.png) |
||||
|
|
||||
|
## 安装使用 |
||||
|
|
||||
|
前置准备环境(只能用pnpm) |
||||
|
|
||||
|
```json |
||||
|
"packageManager": "pnpm@9.0.4", |
||||
|
"engines": { |
||||
|
"node": ">=18.12.0", |
||||
|
"pnpm": ">=9.0.4" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
- 获取项目代码 |
||||
|
|
||||
|
```bash |
||||
|
git clone https://gitee.com/dapppp/ruoyi-plus-vben.git |
||||
|
``` |
||||
|
|
||||
|
- 安装依赖 |
||||
|
|
||||
|
```bash |
||||
|
cd ruoyi-plus-vben |
||||
|
|
||||
|
pnpm install |
||||
|
``` |
||||
|
|
||||
|
- 关于代码生成(非必选) |
||||
|
|
||||
|
**系统工具-代码生成 表格右上角有详细操作怎么改后端!** |
||||
|
|
||||
|
**系统工具-代码生成 表格右上角有详细操作怎么改后端!** |
||||
|
|
||||
|
**系统工具-代码生成 表格右上角有详细操作怎么改后端!** |
||||
|
|
||||
|
- 关于一些监控的地址配置(微服务版本可以跳过这一小节) |
||||
|
|
||||
|
使用[RuoYi-Vue-Plus](https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/)注意 `已经去除 admin/powerjob/easyretry 的.env 配置` 可自行修改 有两种方式 |
||||
|
|
||||
|
1. 修改源码`/views/monitor/powerjob` `/views/monitor/admin` `views/monitor/easyretry` |
||||
|
|
||||
|
```ts |
||||
|
// 修改地址 |
||||
|
const url = ref<string>('http://127.0.0.1:7700/#/oms/home'); |
||||
|
``` |
||||
|
|
||||
|
2. **推荐** 使用菜单自行配置 (跟 cloud 版本打开方式一致) |
||||
|
|
||||
|
![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/菜单修改.png) |
||||
|
|
||||
|
使用内嵌 iframe 方式需要解决跨域问题 可参考[nginx.conf](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/script/docker/nginx/conf/nginx.conf#LC87)配置 |
||||
|
|
||||
|
- 修改.env.development 配置文件 |
||||
|
- **注意 RSA 公私钥一定要修改和后端匹配** |
||||
|
- RSA 公私钥为两对 `前端请求加密-后端解密是一对` `后端响应加密 前端解密是一对` |
||||
|
|
||||
|
```properties |
||||
|
# 后台请求路径 具体在vite.config.ts配置代理 |
||||
|
VITE_GLOB_API_URL=/basic-api |
||||
|
|
||||
|
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) |
||||
|
VITE_GLOB_ENABLE_ENCRYPT=true |
||||
|
|
||||
|
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 |
||||
|
VITE_GLOB_RSA_PUBLIC_KEY=xxx |
||||
|
|
||||
|
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 |
||||
|
VITE_GLOB_RSA_PRIVATE_KEY=xx |
||||
|
|
||||
|
# 客户端id 必填 |
||||
|
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e |
||||
|
|
||||
|
# 开启WEBSOCKET |
||||
|
VITE_GLOB_WEBSOCKET_ENABLE=true |
||||
|
``` |
||||
|
|
||||
|
- 运行 |
||||
|
|
||||
|
```bash |
||||
|
pnpm serve |
||||
|
``` |
||||
|
|
||||
|
- 打包 |
||||
|
|
||||
|
```bash |
||||
|
pnpm build |
||||
|
``` |
||||
|
|
||||
|
## 项目地址 |
||||
|
|
||||
|
- [vue-vben-admin](https://github.com/anncwb/vue-vben-admin) - vben |
||||
|
- [ruoyi-plus-vben](https://gitee.com/dapppp/ruoyi-plus-vben/tree/master) |
||||
|
- ~~[ruoyi-plus-vben-antv4](https://gitee.com/dapppp/ruoyi-plus-vben-antv4)~~ |
||||
|
|
||||
|
## Git 贡献提交规范 |
||||
|
|
||||
|
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) |
||||
|
|
||||
|
- `feat` 增加新功能 |
||||
|
- `fix` 修复问题/BUG |
||||
|
- `style` 代码风格相关无影响运行结果的 |
||||
|
- `perf` 优化/性能提升 |
||||
|
- `refactor` 重构 |
||||
|
- `revert` 撤销修改 |
||||
|
- `test` 测试相关 |
||||
|
- `docs` 文档/注释 |
||||
|
- `chore` 依赖更新/脚手架配置修改等 |
||||
|
- `workflow` 工作流改进 |
||||
|
- `ci` 持续集成 |
||||
|
- `types` 类型定义文件更改 |
||||
|
- `wip` 开发中 |
||||
|
|
||||
|
## 浏览器支持 |
||||
|
|
||||
|
本地开发推荐使用`Chrome 85+` 浏览器 |
||||
|
|
||||
|
支持现代浏览器, 不支持 IE |
||||
|
|
||||
|
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | |
||||
|
| :-: | :-: | :-: | :-: | :-: | |
||||
|
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | |
||||
|
|
||||
|
## 捐赠 |
||||
|
|
||||
|
如果项目帮助到您 可以考虑请作者喝杯咖啡 万分感谢您对开源的支持! |
||||
|
|
||||
|
<img src=https://117.72.10.31/minio-server/plus/2024/03/16/98a9d704eb0c4c04b721bf7799217571.jpg height=360px /> |
@ -0,0 +1,157 @@ |
|||||
|
<!doctype html> |
||||
|
<html lang="zh" id="htmlRoot"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8" /> |
||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> |
||||
|
<meta name="renderer" content="webkit" /> |
||||
|
<meta |
||||
|
name="viewport" |
||||
|
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" |
||||
|
/> |
||||
|
<title><%= VITE_GLOB_APP_TITLE %></title> |
||||
|
<link rel="icon" href="/favicon.ico" /> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="app"> |
||||
|
<style> |
||||
|
html { |
||||
|
/* same as ant-design-vue/dist/reset.css setting, avoid the title line-height changed */ |
||||
|
line-height: 1.15; |
||||
|
} |
||||
|
|
||||
|
html[data-theme='dark'] .app-loading { |
||||
|
background-color: #2c344a; |
||||
|
} |
||||
|
|
||||
|
html[data-theme='dark'] .app-loading .app-loading-title { |
||||
|
color: rgb(255 255 255 / 85%); |
||||
|
} |
||||
|
|
||||
|
.app-loading { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background-color: #f4f7f9; |
||||
|
} |
||||
|
|
||||
|
.app-loading .app-loading-wrap { |
||||
|
display: flex; |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
transform: translate3d(-50%, -50%, 0); |
||||
|
} |
||||
|
|
||||
|
.app-loading .dots { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 98px; |
||||
|
} |
||||
|
|
||||
|
.app-loading .app-loading-title { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin-top: 30px; |
||||
|
color: rgb(0 0 0 / 85%); |
||||
|
font-size: 30px; |
||||
|
} |
||||
|
|
||||
|
.app-loading .app-loading-logo { |
||||
|
display: block; |
||||
|
width: 90px; |
||||
|
margin: 0 auto; |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.dot { |
||||
|
display: inline-block; |
||||
|
position: relative; |
||||
|
box-sizing: border-box; |
||||
|
width: 48px; |
||||
|
height: 48px; |
||||
|
margin-top: 30px; |
||||
|
transform: rotate(45deg); |
||||
|
animation: ant-rotate 1.2s infinite linear; |
||||
|
font-size: 32px; |
||||
|
} |
||||
|
|
||||
|
.dot i { |
||||
|
display: block; |
||||
|
position: absolute; |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
transform: scale(0.75); |
||||
|
transform-origin: 50% 50%; |
||||
|
animation: ant-spin-move 1s infinite linear alternate; |
||||
|
border-radius: 100%; |
||||
|
opacity: 0.3; |
||||
|
background-color: #0065cc; |
||||
|
} |
||||
|
|
||||
|
.dot i:nth-child(1) { |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
.dot i:nth-child(2) { |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
animation-delay: 0.4s; |
||||
|
} |
||||
|
|
||||
|
.dot i:nth-child(3) { |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
animation-delay: 0.8s; |
||||
|
} |
||||
|
|
||||
|
.dot i:nth-child(4) { |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
animation-delay: 1.2s; |
||||
|
} |
||||
|
|
||||
|
@keyframes ant-rotate { |
||||
|
to { |
||||
|
transform: rotate(405deg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes ant-spin-move { |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
|
<script> |
||||
|
// 从localStorage中获取主题 第一次打开页面未null 也就是默认的是light |
||||
|
const mode = localStorage.getItem('__APP__DARK__MODE__'); |
||||
|
// data-theme设置之后 html[data-theme='dark'] 才会生效 |
||||
|
// 主要解决夜间模式加载会白屏的问题 |
||||
|
if (mode === 'dark') { |
||||
|
htmlRoot.setAttribute('data-theme', 'dark'); |
||||
|
} else { |
||||
|
htmlRoot.setAttribute('data-theme', 'light'); |
||||
|
} |
||||
|
</script> |
||||
|
<div class="app-loading"> |
||||
|
<div class="app-loading-wrap"> |
||||
|
<img src="/logo.png" class="app-loading-logo" alt="Logo" /> |
||||
|
<div class="app-loading-dots"> |
||||
|
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span> |
||||
|
</div> |
||||
|
<div class="app-loading-title"><%= VITE_GLOB_APP_TITLE %></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<script type="module" src="/src/main.ts"></script> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,9 @@ |
|||||
|
|
||||
|
*.sh |
||||
|
node_modules |
||||
|
*.md |
||||
|
*.woff |
||||
|
*.ttf |
||||
|
.turbo |
||||
|
dist |
||||
|
package.json |
@ -0,0 +1,4 @@ |
|||||
|
module.exports = { |
||||
|
root: true, |
||||
|
extends: ['@vben/eslint-config/strict'], |
||||
|
}; |
@ -0,0 +1,10 @@ |
|||||
|
import { defineBuildConfig } from 'unbuild'; |
||||
|
|
||||
|
export default defineBuildConfig({ |
||||
|
clean: true, |
||||
|
entries: ['src/index', 'src/strict'], |
||||
|
declaration: true, |
||||
|
rollup: { |
||||
|
emitCJS: true, |
||||
|
}, |
||||
|
}); |
@ -0,0 +1,50 @@ |
|||||
|
{ |
||||
|
"name": "@vben/eslint-config", |
||||
|
"version": "1.0.0", |
||||
|
"private": true, |
||||
|
"homepage": "https://github.com/vbenjs/vue-vben-admin", |
||||
|
"bugs": { |
||||
|
"url": "https://github.com/vbenjs/vue-vben-admin/issues" |
||||
|
}, |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "git+https://github.com/vbenjs/vue-vben-admin.git", |
||||
|
"directory": "internal/eslint-config" |
||||
|
}, |
||||
|
"license": "MIT", |
||||
|
"type": "module", |
||||
|
"exports": { |
||||
|
".": { |
||||
|
"types": "./dist/index.d.ts", |
||||
|
"import": "./dist/index.mjs", |
||||
|
"require": "./dist/index.cjs" |
||||
|
}, |
||||
|
"./strict": { |
||||
|
"types": "./dist/strict.d.ts", |
||||
|
"import": "./dist/strict.mjs", |
||||
|
"require": "./dist/strict.cjs" |
||||
|
} |
||||
|
}, |
||||
|
"main": "./dist/index.cjs", |
||||
|
"module": "./dist/index.mjs", |
||||
|
"types": "./dist/index.d.ts", |
||||
|
"files": [ |
||||
|
"dist" |
||||
|
], |
||||
|
"scripts": { |
||||
|
"clean": "pnpm rimraf .turbo node_modules dist", |
||||
|
"lint": "pnpm eslint .", |
||||
|
"stub": "pnpm unbuild --stub" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@typescript-eslint/eslint-plugin": "^7.0.1", |
||||
|
"@typescript-eslint/parser": "^7.0.1", |
||||
|
"eslint": "^8.56.0", |
||||
|
"eslint-config-prettier": "^9.1.0", |
||||
|
"eslint-plugin-import": "^2.29.1", |
||||
|
"eslint-plugin-prettier": "^5.1.3", |
||||
|
"eslint-plugin-simple-import-sort": "^12.0.0", |
||||
|
"eslint-plugin-vue": "^9.21.1", |
||||
|
"vue-eslint-parser": "^9.4.2" |
||||
|
} |
||||
|
} |
@ -0,0 +1,91 @@ |
|||||
|
export default { |
||||
|
env: { |
||||
|
browser: true, |
||||
|
node: true, |
||||
|
es6: true, |
||||
|
}, |
||||
|
parser: 'vue-eslint-parser', |
||||
|
parserOptions: { |
||||
|
parser: '@typescript-eslint/parser', |
||||
|
ecmaVersion: 2020, |
||||
|
sourceType: 'module', |
||||
|
jsxPragma: 'React', |
||||
|
ecmaFeatures: { |
||||
|
jsx: true, |
||||
|
}, |
||||
|
project: './tsconfig.*?.json', |
||||
|
createDefaultProgram: false, |
||||
|
extraFileExtensions: ['.vue'], |
||||
|
}, |
||||
|
plugins: ['vue', '@typescript-eslint', 'import'], |
||||
|
extends: [ |
||||
|
'eslint:recommended', |
||||
|
'plugin:vue/vue3-recommended', |
||||
|
'plugin:@typescript-eslint/recommended', |
||||
|
'plugin:prettier/recommended', |
||||
|
], |
||||
|
rules: { |
||||
|
'no-unused-vars': 'off', |
||||
|
'no-case-declarations': 'off', |
||||
|
'no-use-before-define': 'off', |
||||
|
'space-before-function-paren': 'off', |
||||
|
|
||||
|
'import/first': 'error', |
||||
|
'import/newline-after-import': 'error', |
||||
|
'import/no-duplicates': 'error', |
||||
|
|
||||
|
'@typescript-eslint/no-unused-vars': [ |
||||
|
'error', |
||||
|
{ |
||||
|
argsIgnorePattern: '^_', |
||||
|
varsIgnorePattern: '^_', |
||||
|
}, |
||||
|
], |
||||
|
'@typescript-eslint/ban-ts-ignore': 'off', |
||||
|
'@typescript-eslint/ban-ts-comment': 'off', |
||||
|
'@typescript-eslint/ban-types': 'off', |
||||
|
'@typescript-eslint/explicit-function-return-type': 'off', |
||||
|
'@typescript-eslint/no-explicit-any': 'off', |
||||
|
'@typescript-eslint/no-var-requires': 'off', |
||||
|
'@typescript-eslint/no-empty-function': 'off', |
||||
|
'@typescript-eslint/no-use-before-define': 'off', |
||||
|
'@typescript-eslint/no-non-null-assertion': 'off', |
||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off', |
||||
|
'vue/script-setup-uses-vars': 'error', |
||||
|
'vue/no-reserved-component-names': 'off', |
||||
|
'vue/custom-event-name-casing': 'off', |
||||
|
'vue/attributes-order': 'off', |
||||
|
'vue/one-component-per-file': 'off', |
||||
|
'vue/html-closing-bracket-newline': 'off', |
||||
|
'vue/max-attributes-per-line': 'off', |
||||
|
'vue/multiline-html-element-content-newline': 'off', |
||||
|
'vue/singleline-html-element-content-newline': 'off', |
||||
|
'vue/attribute-hyphenation': 'off', |
||||
|
'vue/require-default-prop': 'off', |
||||
|
'vue/require-explicit-emits': 'off', |
||||
|
'vue/html-self-closing': [ |
||||
|
'error', |
||||
|
{ |
||||
|
html: { |
||||
|
void: 'always', |
||||
|
normal: 'never', |
||||
|
component: 'always', |
||||
|
}, |
||||
|
svg: 'always', |
||||
|
math: 'always', |
||||
|
}, |
||||
|
], |
||||
|
'vue/multi-word-component-names': 'off', |
||||
|
// 'sort-imports': [
|
||||
|
// 'error',
|
||||
|
// {
|
||||
|
// ignoreCase: true,
|
||||
|
// ignoreDeclarationSort: false,
|
||||
|
// ignoreMemberSort: false,
|
||||
|
// memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
|
||||
|
// allowSeparatedGroups: false,
|
||||
|
// },
|
||||
|
// ],
|
||||
|
}, |
||||
|
globals: { defineOptions: 'readonly' }, |
||||
|
}; |
@ -0,0 +1,57 @@ |
|||||
|
export default { |
||||
|
extends: ['@vben'], |
||||
|
plugins: ['simple-import-sort'], |
||||
|
rules: { |
||||
|
'simple-import-sort/imports': 'error', |
||||
|
'simple-import-sort/exports': 'error', |
||||
|
|
||||
|
'@typescript-eslint/ban-ts-comment': [ |
||||
|
'error', |
||||
|
{ |
||||
|
'ts-expect-error': 'allow-with-description', |
||||
|
'ts-ignore': 'allow-with-description', |
||||
|
'ts-nocheck': 'allow-with-description', |
||||
|
'ts-check': false, |
||||
|
}, |
||||
|
], |
||||
|
|
||||
|
/** |
||||
|
* 【强制】关键字前后有一个空格 |
||||
|
* @link https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/keyword-spacing.md
|
||||
|
*/ |
||||
|
'keyword-spacing': 'off', |
||||
|
'@typescript-eslint/keyword-spacing': [ |
||||
|
'error', |
||||
|
{ |
||||
|
before: true, |
||||
|
after: true, |
||||
|
overrides: { |
||||
|
return: { after: true }, |
||||
|
throw: { after: true }, |
||||
|
case: { after: true }, |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
|
||||
|
/** |
||||
|
* 禁止出现空函数,普通函数(非 async/await/generator)、箭头函数、类上的方法除外 |
||||
|
* @link https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-function.md
|
||||
|
*/ |
||||
|
'no-empty-function': 'off', |
||||
|
'@typescript-eslint/no-empty-function': [ |
||||
|
'error', |
||||
|
{ |
||||
|
allow: ['arrowFunctions', 'functions', 'methods'], |
||||
|
}, |
||||
|
], |
||||
|
|
||||
|
/** |
||||
|
* 优先使用 interface 而不是 type 定义对象类型 |
||||
|
* @link https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-definitions.md
|
||||
|
*/ |
||||
|
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], |
||||
|
|
||||
|
'vue/attributes-order': 'error', |
||||
|
'vue/require-default-prop': 'error', |
||||
|
}, |
||||
|
}; |
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/tsconfig", |
||||
|
"extends": "@vben/ts-config/node.json", |
||||
|
"include": ["src"] |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
|
||||
|
*.sh |
||||
|
node_modules |
||||
|
*.md |
||||
|
*.woff |
||||
|
*.ttf |
||||
|
.turbo |
||||
|
dist |
||||
|
package.json |
@ -0,0 +1,4 @@ |
|||||
|
module.exports = { |
||||
|
root: true, |
||||
|
extends: ['@vben/eslint-config/strict'], |
||||
|
}; |
@ -0,0 +1,10 @@ |
|||||
|
import { defineBuildConfig } from 'unbuild'; |
||||
|
|
||||
|
export default defineBuildConfig({ |
||||
|
clean: true, |
||||
|
entries: ['src/index'], |
||||
|
declaration: true, |
||||
|
rollup: { |
||||
|
emitCJS: true, |
||||
|
}, |
||||
|
}); |
@ -0,0 +1,49 @@ |
|||||
|
{ |
||||
|
"name": "@vben/stylelint-config", |
||||
|
"version": "1.0.0", |
||||
|
"private": true, |
||||
|
"homepage": "https://github.com/vbenjs/vue-vben-admin", |
||||
|
"bugs": { |
||||
|
"url": "https://github.com/vbenjs/vue-vben-admin/issues" |
||||
|
}, |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "git+https://github.com/vbenjs/vue-vben-admin.git", |
||||
|
"directory": "internal/stylelint-config" |
||||
|
}, |
||||
|
"license": "MIT", |
||||
|
"type": "module", |
||||
|
"exports": { |
||||
|
".": { |
||||
|
"types": "./dist/index.d.ts", |
||||
|
"import": "./dist/index.mjs", |
||||
|
"require": "./dist/index.cjs" |
||||
|
} |
||||
|
}, |
||||
|
"main": "./dist/index.cjs", |
||||
|
"module": "./dist/index.mjs", |
||||
|
"types": "./dist/index.d.ts", |
||||
|
"files": [ |
||||
|
"dist" |
||||
|
], |
||||
|
"scripts": { |
||||
|
"clean": "pnpm rimraf .turbo node_modules dist", |
||||
|
"lint": "pnpm eslint .", |
||||
|
"stub": "pnpm unbuild --stub" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"postcss": "^8.4.38", |
||||
|
"postcss-html": "^1.6.0", |
||||
|
"postcss-less": "^6.0.0", |
||||
|
"postcss-scss": "^4.0.9", |
||||
|
"prettier": "^3.2.5", |
||||
|
"stylelint": "^16.4.0", |
||||
|
"stylelint-config-property-sort-order-smacss": "^10.0.0", |
||||
|
"stylelint-config-recommended-scss": "^14.0.0", |
||||
|
"stylelint-config-recommended-vue": "^1.5.0", |
||||
|
"stylelint-config-standard": "^36.0.0", |
||||
|
"stylelint-config-standard-scss": "^13.1.0", |
||||
|
"stylelint-order": "^6.0.4", |
||||
|
"stylelint-prettier": "^5.0.0" |
||||
|
} |
||||
|
} |
@ -0,0 +1,92 @@ |
|||||
|
export default { |
||||
|
extends: ['stylelint-config-standard', 'stylelint-config-property-sort-order-smacss'], |
||||
|
plugins: ['stylelint-order', 'stylelint-prettier'], |
||||
|
// customSyntax: 'postcss-html',
|
||||
|
overrides: [ |
||||
|
{ |
||||
|
files: ['**/*.(css|html|vue)'], |
||||
|
customSyntax: 'postcss-html', |
||||
|
}, |
||||
|
{ |
||||
|
files: ['*.less', '**/*.less'], |
||||
|
customSyntax: 'postcss-less', |
||||
|
extends: ['stylelint-config-standard', 'stylelint-config-recommended-vue'], |
||||
|
}, |
||||
|
{ |
||||
|
files: ['*.scss', '**/*.scss'], |
||||
|
customSyntax: 'postcss-scss', |
||||
|
extends: ['stylelint-config-standard-scss', 'stylelint-config-recommended-vue/scss'], |
||||
|
rule: { |
||||
|
'scss/percent-placeholder-pattern': null, |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
rules: { |
||||
|
'prettier/prettier': true, |
||||
|
'media-feature-range-notation': null, |
||||
|
'selector-not-notation': null, |
||||
|
'import-notation': null, |
||||
|
'function-no-unknown': null, |
||||
|
'selector-class-pattern': null, |
||||
|
'selector-pseudo-class-no-unknown': [ |
||||
|
true, |
||||
|
{ |
||||
|
ignorePseudoClasses: ['global', 'deep'], |
||||
|
}, |
||||
|
], |
||||
|
'selector-pseudo-element-no-unknown': [ |
||||
|
true, |
||||
|
{ |
||||
|
ignorePseudoElements: ['v-deep'], |
||||
|
}, |
||||
|
], |
||||
|
'at-rule-no-unknown': [ |
||||
|
true, |
||||
|
{ |
||||
|
ignoreAtRules: [ |
||||
|
'tailwind', |
||||
|
'apply', |
||||
|
'variants', |
||||
|
'responsive', |
||||
|
'screen', |
||||
|
'function', |
||||
|
'if', |
||||
|
'each', |
||||
|
'include', |
||||
|
'mixin', |
||||
|
'extend', |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
'no-empty-source': null, |
||||
|
'named-grid-areas-no-invalid': null, |
||||
|
'no-descending-specificity': null, |
||||
|
'font-family-no-missing-generic-family-keyword': null, |
||||
|
'rule-empty-line-before': [ |
||||
|
'always', |
||||
|
{ |
||||
|
ignore: ['after-comment', 'first-nested'], |
||||
|
}, |
||||
|
], |
||||
|
'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }], |
||||
|
'order/order': [ |
||||
|
[ |
||||
|
'dollar-variables', |
||||
|
'custom-properties', |
||||
|
'at-rules', |
||||
|
'declarations', |
||||
|
{ |
||||
|
type: 'at-rule', |
||||
|
name: 'supports', |
||||
|
}, |
||||
|
{ |
||||
|
type: 'at-rule', |
||||
|
name: 'media', |
||||
|
}, |
||||
|
'rules', |
||||
|
], |
||||
|
{ severity: 'error' }, |
||||
|
], |
||||
|
}, |
||||
|
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'], |
||||
|
}; |
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/tsconfig", |
||||
|
"extends": "@vben/ts-config/node.json", |
||||
|
"include": ["src"] |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/tsconfig", |
||||
|
"display": "Base", |
||||
|
"compilerOptions": { |
||||
|
"target": "ESNext", |
||||
|
"module": "ESNext", |
||||
|
"moduleResolution": "bundler", |
||||
|
"strict": true, |
||||
|
"declaration": true, |
||||
|
"noImplicitOverride": true, |
||||
|
"noUnusedLocals": true, |
||||
|
"esModuleInterop": true, |
||||
|
"useUnknownInCatchVariables": false, |
||||
|
"composite": false, |
||||
|
"declarationMap": false, |
||||
|
"forceConsistentCasingInFileNames": true, |
||||
|
"inlineSources": false, |
||||
|
"isolatedModules": true, |
||||
|
"skipLibCheck": true, |
||||
|
"noUnusedParameters": false, |
||||
|
"preserveWatchOutput": true, |
||||
|
"experimentalDecorators": true, |
||||
|
"resolveJsonModule": true, |
||||
|
"removeComments": true |
||||
|
}, |
||||
|
"exclude": ["**/node_modules/**", "**/dist/**"] |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/tsconfig", |
||||
|
"display": "Node Server Config", |
||||
|
"extends": "./base.json", |
||||
|
"compilerOptions": { |
||||
|
"module": "commonjs", |
||||
|
"declaration": false, |
||||
|
"removeComments": true, |
||||
|
"emitDecoratorMetadata": true, |
||||
|
"experimentalDecorators": true, |
||||
|
"target": "es6", |
||||
|
"sourceMap": false, |
||||
|
"esModuleInterop": true, |
||||
|
"outDir": "./dist", |
||||
|
"baseUrl": "./" |
||||
|
}, |
||||
|
"exclude": ["node_modules"] |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/tsconfig", |
||||
|
"display": "Node Config", |
||||
|
"extends": "./base.json", |
||||
|
"compilerOptions": { |
||||
|
"lib": ["ESNext"], |
||||
|
"noImplicitAny": true, |
||||
|
"sourceMap": true, |
||||
|
"noEmit": true, |
||||
|
"baseUrl": "./" |
||||
|
} |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
{ |
||||
|
"name": "@vben/ts-config", |
||||
|
"version": "1.0.0", |
||||
|
"private": true, |
||||
|
"homepage": "https://github.com/vbenjs/vue-vben-admin", |
||||
|
"bugs": { |
||||
|
"url": "https://github.com/vbenjs/vue-vben-admin/issues" |
||||
|
}, |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "git+https://github.com/vbenjs/vue-vben-admin.git", |
||||
|
"directory": "internal/ts-config" |
||||
|
}, |
||||
|
"license": "MIT", |
||||
|
"type": "module", |
||||
|
"files": [ |
||||
|
"base.json", |
||||
|
"node.json", |
||||
|
"vue-app.json", |
||||
|
"node-server.json" |
||||
|
], |
||||
|
"dependencies": { |
||||
|
"@types/node": "^20.12.7", |
||||
|
"vite": "^5.2.10" |
||||
|
} |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/tsconfig", |
||||
|
"display": "Vue Application", |
||||
|
"extends": "./base.json", |
||||
|
"compilerOptions": { |
||||
|
"jsx": "preserve", |
||||
|
"lib": ["ESNext", "DOM"], |
||||
|
"noImplicitAny": false |
||||
|
} |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
|
||||
|
*.sh |
||||
|
node_modules |
||||
|
*.md |
||||
|
*.woff |
||||
|
*.ttf |
||||
|
.turbo |
||||
|
dist |
||||
|
package.json |
@ -0,0 +1,4 @@ |
|||||
|
module.exports = { |
||||
|
root: true, |
||||
|
extends: ['@vben/eslint-config/strict'], |
||||
|
}; |
@ -0,0 +1,10 @@ |
|||||
|
import { defineBuildConfig } from 'unbuild'; |
||||
|
|
||||
|
export default defineBuildConfig({ |
||||
|
clean: true, |
||||
|
entries: ['src/index'], |
||||
|
declaration: true, |
||||
|
rollup: { |
||||
|
emitCJS: true, |
||||
|
}, |
||||
|
}); |
@ -0,0 +1,59 @@ |
|||||
|
{ |
||||
|
"name": "@vben/vite-config", |
||||
|
"version": "1.0.0", |
||||
|
"private": true, |
||||
|
"homepage": "https://github.com/vbenjs/vue-vben-admin", |
||||
|
"bugs": { |
||||
|
"url": "https://github.com/vbenjs/vue-vben-admin/issues" |
||||
|
}, |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "git+https://github.com/vbenjs/vue-vben-admin.git", |
||||
|
"directory": "internal/vite-config" |
||||
|
}, |
||||
|
"license": "MIT", |
||||
|
"type": "module", |
||||
|
"exports": { |
||||
|
".": { |
||||
|
"types": "./dist/index.d.ts", |
||||
|
"import": "./dist/index.mjs", |
||||
|
"require": "./dist/index.cjs" |
||||
|
} |
||||
|
}, |
||||
|
"main": "./dist/index.cjs", |
||||
|
"module": "./dist/index.mjs", |
||||
|
"types": "./dist/index.d.ts", |
||||
|
"files": [ |
||||
|
"dist" |
||||
|
], |
||||
|
"scripts": { |
||||
|
"clean": "pnpm rimraf .turbo node_modules dist", |
||||
|
"lint": "pnpm eslint .", |
||||
|
"stub": "pnpm unbuild --stub" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@ant-design/colors": "^7.0.2", |
||||
|
"vite": "^5.2.10" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@types/fs-extra": "^11.0.4", |
||||
|
"@vitejs/plugin-vue": "^5.0.4", |
||||
|
"@vitejs/plugin-vue-jsx": "^3.1.0", |
||||
|
"ant-design-vue": "^4.2.1", |
||||
|
"dayjs": "^1.11.10", |
||||
|
"dotenv": "^16.4.5", |
||||
|
"fs-extra": "^11.2.0", |
||||
|
"less": "^4.2.0", |
||||
|
"picocolors": "^1.0.0", |
||||
|
"pkg-types": "^1.1.0", |
||||
|
"postcss": "^8.4.38", |
||||
|
"rollup-plugin-visualizer": "^5.12.0", |
||||
|
"sass": "^1.75.0", |
||||
|
"unocss": "0.60.4", |
||||
|
"vite-plugin-compression": "^0.5.1", |
||||
|
"vite-plugin-dts": "^3.9.0", |
||||
|
"vite-plugin-html": "^3.2.2", |
||||
|
"vite-plugin-purge-icons": "^0.10.0", |
||||
|
"vite-plugin-svg-icons": "^2.0.1" |
||||
|
} |
||||
|
} |
@ -0,0 +1,105 @@ |
|||||
|
import { resolve } from 'node:path'; |
||||
|
|
||||
|
import dayjs from 'dayjs'; |
||||
|
import { readPackageJSON } from 'pkg-types'; |
||||
|
import { defineConfig, loadEnv, mergeConfig, type UserConfig } from 'vite'; |
||||
|
|
||||
|
import { createPlugins } from '../plugins'; |
||||
|
import { generateModifyVars } from '../utils/modifyVars'; |
||||
|
import { commonConfig } from './common'; |
||||
|
|
||||
|
interface DefineOptions { |
||||
|
overrides?: UserConfig; |
||||
|
options?: { |
||||
|
//
|
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function defineApplicationConfig(defineOptions: DefineOptions = {}) { |
||||
|
const { overrides = {} } = defineOptions; |
||||
|
|
||||
|
return defineConfig(async ({ command, mode }) => { |
||||
|
const root = process.cwd(); |
||||
|
const isBuild = command === 'build'; |
||||
|
const { VITE_PUBLIC_PATH, VITE_BUILD_COMPRESS, VITE_ENABLE_ANALYZE } = loadEnv(mode, root); |
||||
|
|
||||
|
const defineData = await createDefineData(root); |
||||
|
const plugins = await createPlugins({ |
||||
|
isBuild, |
||||
|
root, |
||||
|
enableAnalyze: VITE_ENABLE_ANALYZE === 'true', |
||||
|
compress: VITE_BUILD_COMPRESS, |
||||
|
}); |
||||
|
|
||||
|
const pathResolve = (pathname: string) => resolve(root, '.', pathname); |
||||
|
const timestamp = new Date().getTime(); |
||||
|
const applicationConfig: UserConfig = { |
||||
|
base: VITE_PUBLIC_PATH, |
||||
|
resolve: { |
||||
|
alias: [ |
||||
|
{ |
||||
|
find: 'vue-i18n', |
||||
|
replacement: 'vue-i18n/dist/vue-i18n.cjs.js', |
||||
|
}, |
||||
|
// @/xxxx => src/xxxx
|
||||
|
{ |
||||
|
find: /@\//, |
||||
|
replacement: pathResolve('src') + '/', |
||||
|
}, |
||||
|
// #/xxxx => types/xxxx
|
||||
|
{ |
||||
|
find: /#\//, |
||||
|
replacement: pathResolve('types') + '/', |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
define: defineData, |
||||
|
build: { |
||||
|
target: 'es2015', |
||||
|
cssTarget: 'chrome80', |
||||
|
rollupOptions: { |
||||
|
output: { |
||||
|
// 入口文件名
|
||||
|
entryFileNames: `assets/entry/[name]-[hash]-${timestamp}.js`, |
||||
|
manualChunks: { |
||||
|
vue: ['vue', 'pinia', 'vue-router'], |
||||
|
antd: ['ant-design-vue', '@ant-design/icons-vue'], |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
css: { |
||||
|
preprocessorOptions: { |
||||
|
less: { |
||||
|
modifyVars: generateModifyVars(), |
||||
|
javascriptEnabled: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
plugins, |
||||
|
}; |
||||
|
|
||||
|
const mergedConfig = mergeConfig(commonConfig(mode), applicationConfig); |
||||
|
|
||||
|
return mergeConfig(mergedConfig, overrides); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async function createDefineData(root: string) { |
||||
|
try { |
||||
|
const pkgJson = await readPackageJSON(root); |
||||
|
const { dependencies, devDependencies, name, version } = pkgJson; |
||||
|
|
||||
|
const __APP_INFO__ = { |
||||
|
pkg: { dependencies, devDependencies, name, version }, |
||||
|
lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), |
||||
|
}; |
||||
|
return { |
||||
|
__APP_INFO__: JSON.stringify(__APP_INFO__), |
||||
|
}; |
||||
|
} catch (error) { |
||||
|
return {}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export { defineApplicationConfig }; |
@ -0,0 +1,26 @@ |
|||||
|
import UnoCSS from 'unocss/vite'; |
||||
|
import { loadEnv, type UserConfig } from 'vite'; |
||||
|
|
||||
|
const commonConfig: (mode: string) => UserConfig = (mode) => { |
||||
|
const { VITE_DROP_CONSOLE } = loadEnv(mode, process.cwd()); |
||||
|
const dropConsole = VITE_DROP_CONSOLE === 'true'; |
||||
|
return { |
||||
|
server: { |
||||
|
host: true, |
||||
|
}, |
||||
|
esbuild: { |
||||
|
drop: mode === 'production' ? (dropConsole ? ['console', 'debugger'] : []) : [], |
||||
|
}, |
||||
|
build: { |
||||
|
reportCompressedSize: false, |
||||
|
chunkSizeWarningLimit: 1500, |
||||
|
rollupOptions: { |
||||
|
// TODO: Prevent memory overflow
|
||||
|
maxParallelFileOps: 3, |
||||
|
}, |
||||
|
}, |
||||
|
plugins: [UnoCSS()], |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export { commonConfig }; |
@ -0,0 +1,42 @@ |
|||||
|
import { readPackageJSON } from 'pkg-types'; |
||||
|
import { defineConfig, mergeConfig, type UserConfig } from 'vite'; |
||||
|
import dts from 'vite-plugin-dts'; |
||||
|
|
||||
|
import { commonConfig } from './common'; |
||||
|
|
||||
|
interface DefineOptions { |
||||
|
overrides?: UserConfig; |
||||
|
options?: { |
||||
|
//
|
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function definePackageConfig(defineOptions: DefineOptions = {}) { |
||||
|
const { overrides = {} } = defineOptions; |
||||
|
const root = process.cwd(); |
||||
|
return defineConfig(async ({ mode }) => { |
||||
|
const { dependencies = {}, peerDependencies = {} } = await readPackageJSON(root); |
||||
|
const packageConfig: UserConfig = { |
||||
|
build: { |
||||
|
lib: { |
||||
|
entry: 'src/index.ts', |
||||
|
formats: ['es'], |
||||
|
fileName: () => 'index.mjs', |
||||
|
}, |
||||
|
rollupOptions: { |
||||
|
external: [...Object.keys(dependencies), ...Object.keys(peerDependencies)], |
||||
|
}, |
||||
|
}, |
||||
|
plugins: [ |
||||
|
dts({ |
||||
|
logLevel: 'error', |
||||
|
}), |
||||
|
], |
||||
|
}; |
||||
|
const mergedConfig = mergeConfig(commonConfig(mode), packageConfig); |
||||
|
|
||||
|
return mergeConfig(mergedConfig, overrides); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export { definePackageConfig }; |
@ -0,0 +1,2 @@ |
|||||
|
export * from './config/application'; |
||||
|
export * from './config/package'; |
@ -0,0 +1,104 @@ |
|||||
|
import colors from 'picocolors'; |
||||
|
import { readPackageJSON } from 'pkg-types'; |
||||
|
import { type PluginOption } from 'vite'; |
||||
|
|
||||
|
import { getEnvConfig } from '../utils/env'; |
||||
|
import { createContentHash } from '../utils/hash'; |
||||
|
|
||||
|
const GLOBAL_CONFIG_FILE_NAME = '_app.config.js'; |
||||
|
const PLUGIN_NAME = 'app-config'; |
||||
|
|
||||
|
async function createAppConfigPlugin({ |
||||
|
root, |
||||
|
isBuild, |
||||
|
}: { |
||||
|
root: string; |
||||
|
isBuild: boolean; |
||||
|
}): Promise<PluginOption> { |
||||
|
let publicPath: string; |
||||
|
let source: string; |
||||
|
if (!isBuild) { |
||||
|
return { |
||||
|
name: PLUGIN_NAME, |
||||
|
}; |
||||
|
} |
||||
|
const { version = '' } = await readPackageJSON(root); |
||||
|
|
||||
|
return { |
||||
|
name: PLUGIN_NAME, |
||||
|
async configResolved(_config) { |
||||
|
const appTitle = _config?.env?.VITE_GLOB_APP_TITLE ?? ''; |
||||
|
// appTitle = appTitle.replace(/\s/g, '_').replace(/-/g, '_');
|
||||
|
publicPath = _config.base; |
||||
|
source = await getConfigSource(appTitle); |
||||
|
}, |
||||
|
async transformIndexHtml(html) { |
||||
|
publicPath = publicPath.endsWith('/') ? publicPath : `${publicPath}/`; |
||||
|
|
||||
|
const appConfigSrc = `${ |
||||
|
publicPath || '/' |
||||
|
}${GLOBAL_CONFIG_FILE_NAME}?v=${version}-${createContentHash(source)}`;
|
||||
|
|
||||
|
return { |
||||
|
html, |
||||
|
tags: [ |
||||
|
{ |
||||
|
tag: 'script', |
||||
|
attrs: { |
||||
|
src: appConfigSrc, |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
}; |
||||
|
}, |
||||
|
async generateBundle() { |
||||
|
try { |
||||
|
this.emitFile({ |
||||
|
type: 'asset', |
||||
|
fileName: GLOBAL_CONFIG_FILE_NAME, |
||||
|
source, |
||||
|
}); |
||||
|
|
||||
|
console.log(colors.cyan(`✨configuration file is build successfully!`)); |
||||
|
} catch (error) { |
||||
|
console.log( |
||||
|
colors.red('configuration file configuration file failed to package:\n' + error), |
||||
|
); |
||||
|
} |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the configuration file variable name |
||||
|
* @param env |
||||
|
*/ |
||||
|
const getVariableName = (title: string) => { |
||||
|
function strToHex(str: string) { |
||||
|
const result: string[] = []; |
||||
|
for (let i = 0; i < str.length; ++i) { |
||||
|
const hex = str.charCodeAt(i).toString(16); |
||||
|
result.push(('000' + hex).slice(-4)); |
||||
|
} |
||||
|
return result.join('').toUpperCase(); |
||||
|
} |
||||
|
return `__PRODUCTION__${strToHex(title) || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, ''); |
||||
|
}; |
||||
|
|
||||
|
async function getConfigSource(appTitle: string) { |
||||
|
const config = await getEnvConfig(); |
||||
|
const variableName = getVariableName(appTitle); |
||||
|
const windowVariable = `window.${variableName}`; |
||||
|
// Ensure that the variable will not be modified
|
||||
|
let source = `${windowVariable}=${JSON.stringify(config)};`; |
||||
|
source += ` |
||||
|
Object.freeze(${windowVariable}); |
||||
|
Object.defineProperty(window, "${variableName}", { |
||||
|
configurable: false, |
||||
|
writable: false, |
||||
|
}); |
||||
|
`.replace(/\s/g, '');
|
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
export { createAppConfigPlugin }; |
@ -0,0 +1,38 @@ |
|||||
|
/** |
||||
|
* Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated |
||||
|
* https://github.com/anncwb/vite-plugin-compression
|
||||
|
*/ |
||||
|
import type { PluginOption } from 'vite'; |
||||
|
import compressPlugin from 'vite-plugin-compression'; |
||||
|
|
||||
|
export function configCompressPlugin({ |
||||
|
compress, |
||||
|
deleteOriginFile = false, |
||||
|
}: { |
||||
|
compress: string; |
||||
|
deleteOriginFile?: boolean; |
||||
|
}): PluginOption[] { |
||||
|
const compressList = compress.split(','); |
||||
|
|
||||
|
const plugins: PluginOption[] = []; |
||||
|
|
||||
|
if (compressList.includes('gzip')) { |
||||
|
plugins.push( |
||||
|
compressPlugin({ |
||||
|
ext: '.gz', |
||||
|
deleteOriginFile, |
||||
|
}), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if (compressList.includes('brotli')) { |
||||
|
plugins.push( |
||||
|
compressPlugin({ |
||||
|
ext: '.br', |
||||
|
algorithm: 'brotliCompress', |
||||
|
deleteOriginFile, |
||||
|
}), |
||||
|
); |
||||
|
} |
||||
|
return plugins; |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
/** |
||||
|
* Plugin to minimize and use ejs template syntax in index.html. |
||||
|
* https://github.com/anncwb/vite-plugin-html
|
||||
|
*/ |
||||
|
import type { PluginOption } from 'vite'; |
||||
|
import { createHtmlPlugin } from 'vite-plugin-html'; |
||||
|
|
||||
|
export function configHtmlPlugin({ isBuild }: { isBuild: boolean }) { |
||||
|
const htmlPlugin: PluginOption[] = createHtmlPlugin({ |
||||
|
minify: isBuild, |
||||
|
}); |
||||
|
return htmlPlugin; |
||||
|
} |
@ -0,0 +1,55 @@ |
|||||
|
import vue from '@vitejs/plugin-vue'; |
||||
|
import vueJsx from '@vitejs/plugin-vue-jsx'; |
||||
|
import { type PluginOption } from 'vite'; |
||||
|
import purgeIcons from 'vite-plugin-purge-icons'; |
||||
|
|
||||
|
import { createAppConfigPlugin } from './appConfig'; |
||||
|
import { configCompressPlugin } from './compress'; |
||||
|
import { configHtmlPlugin } from './html'; |
||||
|
import { configSvgIconsPlugin } from './svgSprite'; |
||||
|
import { configVisualizerConfig } from './visualizer'; |
||||
|
// import DevTools from 'vite-plugin-vue-devtools';
|
||||
|
|
||||
|
interface Options { |
||||
|
isBuild: boolean; |
||||
|
root: string; |
||||
|
compress: string; |
||||
|
enableAnalyze?: boolean; |
||||
|
} |
||||
|
|
||||
|
async function createPlugins({ isBuild, root, compress, enableAnalyze }: Options) { |
||||
|
const vitePlugins: (PluginOption | PluginOption[])[] = [vue(), vueJsx()]; |
||||
|
|
||||
|
const appConfigPlugin = await createAppConfigPlugin({ root, isBuild }); |
||||
|
vitePlugins.push(appConfigPlugin); |
||||
|
|
||||
|
// vitePlugins.push(DevTools());
|
||||
|
|
||||
|
// vite-plugin-html
|
||||
|
vitePlugins.push(configHtmlPlugin({ isBuild })); |
||||
|
|
||||
|
// vite-plugin-svg-icons
|
||||
|
vitePlugins.push(configSvgIconsPlugin({ isBuild })); |
||||
|
|
||||
|
// vite-plugin-purge-icons
|
||||
|
vitePlugins.push(purgeIcons()); |
||||
|
|
||||
|
// The following plugins only work in the production environment
|
||||
|
if (isBuild) { |
||||
|
// rollup-plugin-gzip
|
||||
|
vitePlugins.push( |
||||
|
configCompressPlugin({ |
||||
|
compress, |
||||
|
}), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// rollup-plugin-visualizer
|
||||
|
if (enableAnalyze) { |
||||
|
vitePlugins.push(configVisualizerConfig()); |
||||
|
} |
||||
|
|
||||
|
return vitePlugins; |
||||
|
} |
||||
|
|
||||
|
export { createPlugins }; |
@ -0,0 +1,17 @@ |
|||||
|
/** |
||||
|
* Vite Plugin for fast creating SVG sprites. |
||||
|
* https://github.com/anncwb/vite-plugin-svg-icons
|
||||
|
*/ |
||||
|
|
||||
|
import { resolve } from 'node:path'; |
||||
|
|
||||
|
import type { PluginOption } from 'vite'; |
||||
|
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; |
||||
|
|
||||
|
export function configSvgIconsPlugin({ isBuild }: { isBuild: boolean }) { |
||||
|
const svgIconsPlugin = createSvgIconsPlugin({ |
||||
|
iconDirs: [resolve(process.cwd(), 'src/assets/icons')], |
||||
|
svgoOptions: isBuild, |
||||
|
}); |
||||
|
return svgIconsPlugin as PluginOption; |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
/** |
||||
|
* Package file volume analysis |
||||
|
*/ |
||||
|
import visualizer from 'rollup-plugin-visualizer'; |
||||
|
import { type PluginOption } from 'vite'; |
||||
|
|
||||
|
export function configVisualizerConfig() { |
||||
|
return visualizer({ |
||||
|
filename: './node_modules/.cache/visualizer/stats.html', |
||||
|
open: true, |
||||
|
gzipSize: true, |
||||
|
brotliSize: true, |
||||
|
}) as PluginOption; |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
/** |
||||
|
* 解决vite-plugin-purge-icons报错没有类型定义 |
||||
|
*/ |
||||
|
declare module 'vite-plugin-purge-icons' { |
||||
|
const type = any; |
||||
|
export default type; |
||||
|
} |
@ -0,0 +1,49 @@ |
|||||
|
import { join } from 'node:path'; |
||||
|
|
||||
|
import dotenv from 'dotenv'; |
||||
|
import { readFile } from 'fs-extra'; |
||||
|
|
||||
|
/** |
||||
|
* 获取当前环境下生效的配置文件名 |
||||
|
*/ |
||||
|
function getConfFiles() { |
||||
|
const script = process.env.npm_lifecycle_script as string; |
||||
|
const reg = new RegExp('--mode ([a-z_\\d]+)'); |
||||
|
const result = reg.exec(script); |
||||
|
if (result) { |
||||
|
const mode = result[1]; |
||||
|
return ['.env', `.env.${mode}`]; |
||||
|
} |
||||
|
return ['.env', '.env.production']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the environment variables starting with the specified prefix |
||||
|
* @param match prefix |
||||
|
* @param confFiles ext |
||||
|
*/ |
||||
|
export async function getEnvConfig( |
||||
|
match = 'VITE_GLOB_', |
||||
|
confFiles = getConfFiles(), |
||||
|
): Promise<{ |
||||
|
[key: string]: string; |
||||
|
}> { |
||||
|
let envConfig = {}; |
||||
|
|
||||
|
for (const confFile of confFiles) { |
||||
|
try { |
||||
|
const envPath = await readFile(join(process.cwd(), confFile), { encoding: 'utf8' }); |
||||
|
const env = dotenv.parse(envPath); |
||||
|
envConfig = { ...envConfig, ...env }; |
||||
|
} catch (e) { |
||||
|
console.error(`Error in parsing ${confFile}`, e); |
||||
|
} |
||||
|
} |
||||
|
const reg = new RegExp(`^(${match})`); |
||||
|
Object.keys(envConfig).forEach((key) => { |
||||
|
if (!reg.test(key)) { |
||||
|
Reflect.deleteProperty(envConfig, key); |
||||
|
} |
||||
|
}); |
||||
|
return envConfig; |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
import { createHash } from 'node:crypto'; |
||||
|
|
||||
|
function createContentHash(content: string, hashLSize = 12) { |
||||
|
const hash = createHash('sha256').update(content); |
||||
|
return hash.digest('hex').slice(0, hashLSize); |
||||
|
} |
||||
|
function strToHex(str: string) { |
||||
|
const result: string[] = []; |
||||
|
for (let i = 0; i < str.length; ++i) { |
||||
|
const hex = str.charCodeAt(i).toString(16); |
||||
|
result.push(('000' + hex).slice(-4)); |
||||
|
} |
||||
|
return result.join('').toUpperCase(); |
||||
|
} |
||||
|
|
||||
|
export { createContentHash, strToHex }; |
@ -0,0 +1,48 @@ |
|||||
|
import { resolve } from 'node:path'; |
||||
|
|
||||
|
import { generate } from '@ant-design/colors'; |
||||
|
// @ts-ignore: typo
|
||||
|
/* import { getThemeVariables } from 'ant-design-vue/dist/theme'; */ |
||||
|
import { theme } from 'ant-design-vue/lib'; |
||||
|
import convertLegacyToken from 'ant-design-vue/lib/theme/convertLegacyToken'; |
||||
|
|
||||
|
const { defaultAlgorithm, defaultSeed } = theme; |
||||
|
// const primaryColor = '#0960bd';
|
||||
|
const primaryColor = '#1677ff'; |
||||
|
|
||||
|
function generateAntColors(color: string, theme: 'default' | 'dark' = 'default') { |
||||
|
return generate(color, { |
||||
|
theme, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* less global variable |
||||
|
*/ |
||||
|
export function generateModifyVars() { |
||||
|
const palettes = generateAntColors(primaryColor); |
||||
|
const primary = palettes[5]; |
||||
|
const primaryColorObj: Record<string, string> = {}; |
||||
|
|
||||
|
for (let index = 0; index < 10; index++) { |
||||
|
primaryColorObj[`primary-${index + 1}`] = palettes[index]; |
||||
|
} |
||||
|
// const modifyVars = getThemeVariables();
|
||||
|
const mapToken = defaultAlgorithm(defaultSeed); |
||||
|
const v3Token = convertLegacyToken(mapToken); |
||||
|
return { |
||||
|
...v3Token, |
||||
|
// reference: Avoid repeated references
|
||||
|
hack: `true; @import (reference) "${resolve('src/design/config.less')}";`, |
||||
|
'primary-color': primary, |
||||
|
...primaryColorObj, |
||||
|
'info-color': primary, |
||||
|
'processing-color': primary, |
||||
|
'success-color': '#55D187', // Success color
|
||||
|
'error-color': '#ED6F6F', // False color
|
||||
|
'warning-color': '#EFBD47', // Warning color
|
||||
|
'font-size-base': '14px', // Main font size
|
||||
|
'border-radius-base': '2px', // Component/float fillet
|
||||
|
'link-color': primary, // Link color
|
||||
|
}; |
||||
|
} |
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/tsconfig", |
||||
|
"extends": "@vben/ts-config/node.json", |
||||
|
"include": ["src"] |
||||
|
} |
@ -0,0 +1,161 @@ |
|||||
|
{ |
||||
|
"name": "ruoyi-plus-vben", |
||||
|
"version": "1.3.5", |
||||
|
"homepage": "https://gitee.com/dapppp/ruoyi-plus-vben.git", |
||||
|
"bugs": { |
||||
|
"url": "https://gitee.com/dapppp/ruoyi-plus-vben/issues" |
||||
|
}, |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "git+https://gitee.com/dapppp/ruoyi-plus-vben.git" |
||||
|
}, |
||||
|
"license": "MIT", |
||||
|
"author": { |
||||
|
"name": "vben", |
||||
|
"email": "anncwb@126.com", |
||||
|
"url": "https://github.com/anncwb" |
||||
|
}, |
||||
|
"type": "module", |
||||
|
"scripts": { |
||||
|
"bootstrap": "pnpm install", |
||||
|
"build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build", |
||||
|
"build:no-cache": "pnpm store prune && npm run build", |
||||
|
"build:test": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build --mode test", |
||||
|
"commit": "czg", |
||||
|
"dev": "pnpm vite", |
||||
|
"preinstall": "npx only-allow pnpm", |
||||
|
"postinstall": "turbo run stub", |
||||
|
"lint": "turbo run lint", |
||||
|
"lint:eslint": "eslint --cache --max-warnings 0 \"./src/**/*.{vue,ts,tsx}\" --fix", |
||||
|
"lint:prettier": "prettier --write .", |
||||
|
"lint:stylelint": "stylelint \"**/*.{vue,css,less,scss}\" --fix --cache --cache-location node_modules/.cache/stylelint/", |
||||
|
"log": "conventional-changelog -p angular -i CHANGELOG.md -s", |
||||
|
"prepare": "husky install", |
||||
|
"preview": "vite preview", |
||||
|
"reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap", |
||||
|
"serve": "npm run dev", |
||||
|
"test:gzip": "npx http-server dist --cors --gzip -c-1", |
||||
|
"type:check": "vue-tsc --noEmit --skipLibCheck" |
||||
|
}, |
||||
|
"lint-staged": { |
||||
|
"*.{js,jsx,ts,tsx}": [ |
||||
|
"prettier --write", |
||||
|
"eslint --fix" |
||||
|
], |
||||
|
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [ |
||||
|
"prettier --write--parser json" |
||||
|
], |
||||
|
"package.json": [ |
||||
|
"prettier --write" |
||||
|
], |
||||
|
"*.vue": [ |
||||
|
"prettier --write", |
||||
|
"eslint --fix", |
||||
|
"stylelint --fix" |
||||
|
], |
||||
|
"*.{scss,less,styl,html}": [ |
||||
|
"prettier --write", |
||||
|
"stylelint --fix" |
||||
|
], |
||||
|
"*.md": [ |
||||
|
"prettier --write" |
||||
|
] |
||||
|
}, |
||||
|
"config": { |
||||
|
"commitizen": { |
||||
|
"path": "node_modules/cz-git" |
||||
|
} |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@ant-design/icons-vue": "^7.0.1", |
||||
|
"@iconify/iconify": "^3.1.1", |
||||
|
"@logicflow/core": "^1.2.26", |
||||
|
"@logicflow/extension": "^1.2.26", |
||||
|
"@vben/hooks": "workspace:*", |
||||
|
"@vue/shared": "^3.4.25", |
||||
|
"@vueuse/core": "^10.9.0", |
||||
|
"@zxcvbn-ts/core": "^3.0.4", |
||||
|
"ant-design-vue": "^4.2.3", |
||||
|
"axios": "^1.6.8", |
||||
|
"bpmn-js": "17.5.0", |
||||
|
"bpmn-js-token-simulation": "^0.34.1", |
||||
|
"codemirror": "^5.65.16", |
||||
|
"cropperjs": "^1.6.2", |
||||
|
"crypto-js": "^4.2.0", |
||||
|
"dayjs": "^1.11.10", |
||||
|
"diagram-js": "^14.6.0", |
||||
|
"diagram-js-minimap": "^4.1.0", |
||||
|
"didi": "^10.2.2", |
||||
|
"driver.js": "^1.3.1", |
||||
|
"echarts": "^5.5.0", |
||||
|
"exceljs": "^4.4.0", |
||||
|
"html2canvas": "^1.4.1", |
||||
|
"jsencrypt": "^3.3.2", |
||||
|
"lodash-es": "^4.17.21", |
||||
|
"nprogress": "^0.2.0", |
||||
|
"path-to-regexp": "^6.2.2", |
||||
|
"pinia": "2.1.7", |
||||
|
"pinia-plugin-persistedstate": "^3.2.1", |
||||
|
"print-js": "^1.6.0", |
||||
|
"qrcode": "^1.5.3", |
||||
|
"qs": "^6.12.1", |
||||
|
"resize-observer-polyfill": "^1.5.1", |
||||
|
"showdown": "^2.1.0", |
||||
|
"sortablejs": "^1.15.2", |
||||
|
"tiny-svg": "^4.0.0", |
||||
|
"tinymce": "^5.10.9", |
||||
|
"unocss": "0.60.4", |
||||
|
"vditor": "^3.10.4", |
||||
|
"vue": "^3.4.25", |
||||
|
"vue-i18n": "^9.13.1", |
||||
|
"vue-json-pretty": "^2.4.0", |
||||
|
"vue-router": "^4.3.3", |
||||
|
"vue-types": "^5.1.1", |
||||
|
"vue3-colorpicker": "^2.3.0", |
||||
|
"vuedraggable": "^4.1.0", |
||||
|
"vxe-table": "4.6.17", |
||||
|
"vxe-table-plugin-export-xlsx": "^4.0.1", |
||||
|
"xe-utils": "^3.5.25", |
||||
|
"xlsx": "^0.18.5" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@commitlint/cli": "^19.3.0", |
||||
|
"@commitlint/config-conventional": "^19.2.2", |
||||
|
"@iconify/json": "^2.2.203", |
||||
|
"@purge-icons/generated": "^0.10.0", |
||||
|
"@types/codemirror": "^5.60.15", |
||||
|
"@types/crypto-js": "^4.2.2", |
||||
|
"@types/lodash-es": "^4.17.12", |
||||
|
"@types/nprogress": "^0.2.3", |
||||
|
"@types/qrcode": "^1.5.5", |
||||
|
"@types/qs": "^6.9.15", |
||||
|
"@types/showdown": "^2.0.6", |
||||
|
"@types/sortablejs": "^1.15.8", |
||||
|
"@vben/eslint-config": "workspace:*", |
||||
|
"@vben/stylelint-config": "workspace:*", |
||||
|
"@vben/ts-config": "workspace:*", |
||||
|
"@vben/types": "workspace:*", |
||||
|
"@vben/vite-config": "workspace:*", |
||||
|
"@vue/compiler-sfc": "^3.4.25", |
||||
|
"@vue/test-utils": "^2.4.5", |
||||
|
"conventional-changelog-cli": "^4.1.0", |
||||
|
"cross-env": "^7.0.3", |
||||
|
"cz-git": "^1.9.1", |
||||
|
"czg": "^1.9.1", |
||||
|
"husky": "^9.0.11", |
||||
|
"lint-staged": "15.2.2", |
||||
|
"prettier": "^3.2.5", |
||||
|
"prettier-plugin-packagejson": "^2.5.0", |
||||
|
"rimraf": "^5.0.5", |
||||
|
"turbo": "^1.13.2", |
||||
|
"typescript": "^5.4.5", |
||||
|
"unbuild": "^2.0.0", |
||||
|
"vite": "^5.2.10", |
||||
|
"vite-plugin-vue-devtools": "^7.2.0", |
||||
|
"vue-tsc": "^2.0.14" |
||||
|
}, |
||||
|
"engines": { |
||||
|
"node": ">=18.12.0", |
||||
|
"pnpm": ">=9.0.4" |
||||
|
} |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
module.exports = { |
||||
|
root: true, |
||||
|
extends: ['@vben/eslint-config/strict'], |
||||
|
}; |
@ -0,0 +1,10 @@ |
|||||
|
import { defineBuildConfig } from 'unbuild'; |
||||
|
|
||||
|
export default defineBuildConfig({ |
||||
|
clean: true, |
||||
|
entries: ['src/index'], |
||||
|
declaration: true, |
||||
|
rollup: { |
||||
|
emitCJS: true, |
||||
|
}, |
||||
|
}); |
@ -0,0 +1,40 @@ |
|||||
|
{ |
||||
|
"name": "@vben/hooks", |
||||
|
"version": "1.0.0", |
||||
|
"homepage": "https://github.com/vbenjs/vue-vben-admin", |
||||
|
"bugs": { |
||||
|
"url": "https://github.com/vbenjs/vue-vben-admin/issues" |
||||
|
}, |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "git+https://github.com/vbenjs/vue-vben-admin.git", |
||||
|
"directory": "packages/hooks" |
||||
|
}, |
||||
|
"license": "MIT", |
||||
|
"sideEffects": false, |
||||
|
"type": "module", |
||||
|
"exports": { |
||||
|
".": { |
||||
|
"default": "./src/index.ts" |
||||
|
} |
||||
|
}, |
||||
|
"main": "./src/index.ts", |
||||
|
"module": "./src/index.ts", |
||||
|
"files": [ |
||||
|
"dist" |
||||
|
], |
||||
|
"scripts": { |
||||
|
"//build": "pnpm unbuild", |
||||
|
"//stub": "pnpm unbuild --stub", |
||||
|
"clean": "pnpm rimraf .turbo node_modules dist", |
||||
|
"lint": "pnpm eslint ." |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@vueuse/core": "^10.9.0", |
||||
|
"lodash-es": "^4.17.21", |
||||
|
"vue": "^3.4.25" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@vben/types": "workspace:*" |
||||
|
} |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
export * from './onMountedOrActivated'; |
||||
|
export * from './useAttrs'; |
||||
|
export * from './useRefs'; |
||||
|
export * from './useRequest'; |
||||
|
export * from './useScrollTo'; |
||||
|
export * from './useWindowSizeFn'; |
||||
|
export { useTimeoutFn } from '@vueuse/core'; |
@ -0,0 +1,25 @@ |
|||||
|
import { type AnyFunction } from '@vben/types'; |
||||
|
import { nextTick, onActivated, onMounted } from 'vue'; |
||||
|
|
||||
|
/** |
||||
|
* 在 OnMounted 或者 OnActivated 时触发 |
||||
|
* @param hook 任何函数(包括异步函数) |
||||
|
*/ |
||||
|
function onMountedOrActivated(hook: AnyFunction) { |
||||
|
let mounted: boolean; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
hook(); |
||||
|
nextTick(() => { |
||||
|
mounted = true; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
onActivated(() => { |
||||
|
if (mounted) { |
||||
|
hook(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export { onMountedOrActivated }; |
@ -0,0 +1,43 @@ |
|||||
|
import { type Recordable } from '@vben/types'; |
||||
|
import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue'; |
||||
|
|
||||
|
interface UseAttrsOptions { |
||||
|
excludeListeners?: boolean; |
||||
|
excludeKeys?: string[]; |
||||
|
excludeDefaultKeys?: boolean; |
||||
|
} |
||||
|
|
||||
|
const DEFAULT_EXCLUDE_KEYS = ['class', 'style']; |
||||
|
const LISTENER_PREFIX = /^on[A-Z]/; |
||||
|
|
||||
|
function entries<T>(obj: Recordable<T>): [string, T][] { |
||||
|
return Object.keys(obj).map((key: string) => [key, obj[key]]); |
||||
|
} |
||||
|
|
||||
|
function useAttrs(options: UseAttrsOptions = {}): Recordable<any> { |
||||
|
const instance = getCurrentInstance(); |
||||
|
if (!instance) return {}; |
||||
|
|
||||
|
const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = options; |
||||
|
const attrs = shallowRef({}); |
||||
|
const allExcludeKeys = excludeKeys.concat(excludeDefaultKeys ? DEFAULT_EXCLUDE_KEYS : []); |
||||
|
|
||||
|
// Since attrs are not reactive, make it reactive instead of doing in `onUpdated` hook for better performance
|
||||
|
instance.attrs = reactive(instance.attrs); |
||||
|
|
||||
|
watchEffect(() => { |
||||
|
const res = entries(instance.attrs).reduce((acm, [key, val]) => { |
||||
|
if (!allExcludeKeys.includes(key) && !(excludeListeners && LISTENER_PREFIX.test(key))) { |
||||
|
acm[key] = val; |
||||
|
} |
||||
|
|
||||
|
return acm; |
||||
|
}, {} as Recordable<any>); |
||||
|
|
||||
|
attrs.value = res; |
||||
|
}); |
||||
|
|
||||
|
return attrs; |
||||
|
} |
||||
|
|
||||
|
export { useAttrs, type UseAttrsOptions }; |
@ -0,0 +1,24 @@ |
|||||
|
import type { ComponentPublicInstance, Ref } from 'vue'; |
||||
|
import { onBeforeUpdate, shallowRef } from 'vue'; |
||||
|
|
||||
|
function useRefs<T = HTMLElement>(): { |
||||
|
refs: Ref<T[]>; |
||||
|
setRefs: (index: number) => (el: Element | ComponentPublicInstance | null) => void; |
||||
|
} { |
||||
|
const refs = shallowRef([]) as Ref<T[]>; |
||||
|
|
||||
|
onBeforeUpdate(() => { |
||||
|
refs.value = []; |
||||
|
}); |
||||
|
|
||||
|
const setRefs = (index: number) => (el: Element | ComponentPublicInstance | null) => { |
||||
|
refs.value[index] = el as T; |
||||
|
}; |
||||
|
|
||||
|
return { |
||||
|
refs, |
||||
|
setRefs, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export { useRefs }; |
@ -0,0 +1,147 @@ |
|||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */ |
||||
|
import { reactive } from 'vue'; |
||||
|
|
||||
|
import type { FetchState, PluginReturn, Service, Subscribe, UseRequestOptions } from './types'; |
||||
|
import { isFunction } from './utils/isFunction'; |
||||
|
|
||||
|
export default class Fetch<TData, TParams extends any[]> { |
||||
|
pluginImpls: PluginReturn<TData, TParams>[] = []; |
||||
|
|
||||
|
count: number = 0; |
||||
|
|
||||
|
state: FetchState<TData, TParams> = reactive({ |
||||
|
loading: false, |
||||
|
params: undefined, |
||||
|
data: undefined, |
||||
|
error: undefined, |
||||
|
}); |
||||
|
|
||||
|
constructor( |
||||
|
public serviceRef: Service<TData, TParams>, |
||||
|
public options: UseRequestOptions<TData, TParams>, |
||||
|
public subscribe: Subscribe, |
||||
|
public initState: Partial<FetchState<TData, TParams>> = {}, |
||||
|
) { |
||||
|
this.setState({ loading: !options.manual, ...initState }); |
||||
|
} |
||||
|
|
||||
|
setState(s: Partial<FetchState<TData, TParams>> = {}) { |
||||
|
Object.assign(this.state, s); |
||||
|
this.subscribe(); |
||||
|
} |
||||
|
|
||||
|
runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: any[]) { |
||||
|
// @ts-ignore
|
||||
|
const r = this.pluginImpls.map((i) => i[event]?.(...rest)).filter(Boolean); |
||||
|
return Object.assign({}, ...r); |
||||
|
} |
||||
|
|
||||
|
async runAsync(...params: TParams): Promise<TData> { |
||||
|
this.count += 1; |
||||
|
const currentCount = this.count; |
||||
|
|
||||
|
const { |
||||
|
stopNow = false, |
||||
|
returnNow = false, |
||||
|
...state |
||||
|
} = this.runPluginHandler('onBefore', params); |
||||
|
|
||||
|
// stop request
|
||||
|
if (stopNow) { |
||||
|
return new Promise(() => {}); |
||||
|
} |
||||
|
|
||||
|
this.setState({ |
||||
|
loading: true, |
||||
|
params, |
||||
|
...state, |
||||
|
}); |
||||
|
|
||||
|
// return now
|
||||
|
if (returnNow) { |
||||
|
return Promise.resolve(state.data); |
||||
|
} |
||||
|
|
||||
|
this.options.onBefore?.(params); |
||||
|
|
||||
|
try { |
||||
|
// replace service
|
||||
|
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef, params); |
||||
|
|
||||
|
if (!servicePromise) { |
||||
|
servicePromise = this.serviceRef(...params); |
||||
|
} |
||||
|
|
||||
|
const res = await servicePromise; |
||||
|
|
||||
|
if (currentCount !== this.count) { |
||||
|
// prevent run.then when request is canceled
|
||||
|
return new Promise(() => {}); |
||||
|
} |
||||
|
|
||||
|
// const formattedResult = this.options.formatResultRef.current ? this.options.formatResultRef.current(res) : res;
|
||||
|
|
||||
|
this.setState({ data: res, error: undefined, loading: false }); |
||||
|
|
||||
|
this.options.onSuccess?.(res, params); |
||||
|
this.runPluginHandler('onSuccess', res, params); |
||||
|
|
||||
|
this.options.onFinally?.(params, res, undefined); |
||||
|
|
||||
|
if (currentCount === this.count) { |
||||
|
this.runPluginHandler('onFinally', params, res, undefined); |
||||
|
} |
||||
|
|
||||
|
return res; |
||||
|
} catch (error) { |
||||
|
if (currentCount !== this.count) { |
||||
|
// prevent run.then when request is canceled
|
||||
|
return new Promise(() => {}); |
||||
|
} |
||||
|
|
||||
|
this.setState({ error, loading: false }); |
||||
|
|
||||
|
this.options.onError?.(error, params); |
||||
|
this.runPluginHandler('onError', error, params); |
||||
|
|
||||
|
this.options.onFinally?.(params, undefined, error); |
||||
|
|
||||
|
if (currentCount === this.count) { |
||||
|
this.runPluginHandler('onFinally', params, undefined, error); |
||||
|
} |
||||
|
|
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
run(...params: TParams) { |
||||
|
this.runAsync(...params).catch((error) => { |
||||
|
if (!this.options.onError) { |
||||
|
console.error(error); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
cancel() { |
||||
|
this.count += 1; |
||||
|
this.setState({ loading: false }); |
||||
|
|
||||
|
this.runPluginHandler('onCancel'); |
||||
|
} |
||||
|
|
||||
|
refresh() { |
||||
|
// @ts-ignore
|
||||
|
this.run(...(this.state.params || [])); |
||||
|
} |
||||
|
|
||||
|
refreshAsync() { |
||||
|
// @ts-ignore
|
||||
|
return this.runAsync(...(this.state.params || [])); |
||||
|
} |
||||
|
|
||||
|
mutate(data?: TData | ((oldData?: TData) => TData | undefined)) { |
||||
|
const targetData = isFunction(data) ? data(this.state.data) : data; |
||||
|
this.runPluginHandler('onMutate', targetData); |
||||
|
this.setState({ data: targetData }); |
||||
|
} |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
import useAutoRunPlugin from './plugins/useAutoRunPlugin'; |
||||
|
import useCachePlugin from './plugins/useCachePlugin'; |
||||
|
import useDebouncePlugin from './plugins/useDebouncePlugin'; |
||||
|
import useLoadingDelayPlugin from './plugins/useLoadingDelayPlugin'; |
||||
|
import usePollingPlugin from './plugins/usePollingPlugin'; |
||||
|
import useRefreshOnWindowFocusPlugin from './plugins/useRefreshOnWindowFocusPlugin'; |
||||
|
import useRetryPlugin from './plugins/useRetryPlugin'; |
||||
|
import useThrottlePlugin from './plugins/useThrottlePlugin'; |
||||
|
import type { Service, UseRequestOptions, UseRequestPlugin } from './types'; |
||||
|
import { useRequestImplement } from './useRequestImplement'; |
||||
|
|
||||
|
export { clearCache } from './utils/cache'; |
||||
|
|
||||
|
export function useRequest<TData, TParams extends any[]>( |
||||
|
service: Service<TData, TParams>, |
||||
|
options?: UseRequestOptions<TData, TParams>, |
||||
|
plugins?: UseRequestPlugin<TData, TParams>[], |
||||
|
) { |
||||
|
return useRequestImplement<TData, TParams>(service, options, [ |
||||
|
...(plugins || []), |
||||
|
useDebouncePlugin, |
||||
|
useLoadingDelayPlugin, |
||||
|
usePollingPlugin, |
||||
|
useRefreshOnWindowFocusPlugin, |
||||
|
useThrottlePlugin, |
||||
|
useAutoRunPlugin, |
||||
|
useCachePlugin, |
||||
|
useRetryPlugin, |
||||
|
] as UseRequestPlugin<TData, TParams>[]); |
||||
|
} |
@ -0,0 +1,52 @@ |
|||||
|
import { ref, unref, watch } from 'vue'; |
||||
|
|
||||
|
import type { UseRequestPlugin } from '../types'; |
||||
|
|
||||
|
// support refreshDeps & ready
|
||||
|
const useAutoRunPlugin: UseRequestPlugin<any, any[]> = ( |
||||
|
fetchInstance, |
||||
|
{ manual, ready = true, defaultParams = [], refreshDeps = [], refreshDepsAction }, |
||||
|
) => { |
||||
|
const hasAutoRun = ref(false); |
||||
|
|
||||
|
watch( |
||||
|
() => unref(ready), |
||||
|
(readyVal) => { |
||||
|
if (!unref(manual) && readyVal) { |
||||
|
hasAutoRun.value = true; |
||||
|
fetchInstance.run(...defaultParams); |
||||
|
} |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
if (refreshDeps.length) { |
||||
|
watch(refreshDeps, () => { |
||||
|
if (hasAutoRun.value) { |
||||
|
return; |
||||
|
} |
||||
|
if (!manual) { |
||||
|
if (refreshDepsAction) { |
||||
|
refreshDepsAction(); |
||||
|
} else { |
||||
|
fetchInstance.refresh(); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
onBefore: () => { |
||||
|
if (!unref(ready)) { |
||||
|
return { stopNow: true }; |
||||
|
} |
||||
|
}, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
useAutoRunPlugin.onInit = ({ ready = true, manual }) => { |
||||
|
return { |
||||
|
loading: !unref(manual) && unref(ready), |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export default useAutoRunPlugin; |
@ -0,0 +1,127 @@ |
|||||
|
import { onUnmounted, ref, watchEffect } from 'vue'; |
||||
|
|
||||
|
import type { UseRequestPlugin } from '../types'; |
||||
|
import type { CachedData } from '../utils/cache'; |
||||
|
import { getCache, setCache } from '../utils/cache'; |
||||
|
import { getCachePromise, setCachePromise } from '../utils/cachePromise'; |
||||
|
import { subscribe, trigger } from '../utils/cacheSubscribe'; |
||||
|
|
||||
|
const useCachePlugin: UseRequestPlugin<any, any[]> = ( |
||||
|
fetchInstance, |
||||
|
{ |
||||
|
cacheKey, |
||||
|
cacheTime = 5 * 60 * 1000, |
||||
|
staleTime = 0, |
||||
|
setCache: customSetCache, |
||||
|
getCache: customGetCache, |
||||
|
}, |
||||
|
) => { |
||||
|
const unSubscribeRef = ref<() => void>(); |
||||
|
const currentPromiseRef = ref<Promise<any>>(); |
||||
|
|
||||
|
const _setCache = (key: string, cachedData: CachedData) => { |
||||
|
customSetCache ? customSetCache(cachedData) : setCache(key, cacheTime, cachedData); |
||||
|
trigger(key, cachedData.data); |
||||
|
}; |
||||
|
|
||||
|
const _getCache = (key: string, params: any[] = []) => { |
||||
|
return customGetCache ? customGetCache(params) : getCache(key); |
||||
|
}; |
||||
|
|
||||
|
watchEffect(() => { |
||||
|
if (!cacheKey) return; |
||||
|
|
||||
|
// get data from cache when init
|
||||
|
const cacheData = _getCache(cacheKey); |
||||
|
if (cacheData && Object.hasOwnProperty.call(cacheData, 'data')) { |
||||
|
fetchInstance.state.data = cacheData.data; |
||||
|
fetchInstance.state.params = cacheData.params; |
||||
|
|
||||
|
if (staleTime === -1 || new Date().getTime() - cacheData.time <= staleTime) { |
||||
|
fetchInstance.state.loading = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// subscribe same cachekey update, trigger update
|
||||
|
unSubscribeRef.value = subscribe(cacheKey, (data) => { |
||||
|
fetchInstance.setState({ data }); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
onUnmounted(() => { |
||||
|
unSubscribeRef.value?.(); |
||||
|
}); |
||||
|
|
||||
|
if (!cacheKey) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
onBefore: (params) => { |
||||
|
const cacheData = _getCache(cacheKey, params); |
||||
|
|
||||
|
if (!cacheData || !Object.hasOwnProperty.call(cacheData, 'data')) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
// If the data is fresh, stop request
|
||||
|
if (staleTime === -1 || new Date().getTime() - cacheData.time <= staleTime) { |
||||
|
return { |
||||
|
loading: false, |
||||
|
data: cacheData?.data, |
||||
|
error: undefined, |
||||
|
returnNow: true, |
||||
|
}; |
||||
|
} else { |
||||
|
// If the data is stale, return data, and request continue
|
||||
|
return { data: cacheData?.data, error: undefined }; |
||||
|
} |
||||
|
}, |
||||
|
onRequest: (service, args) => { |
||||
|
let servicePromise = getCachePromise(cacheKey); |
||||
|
|
||||
|
// If has servicePromise, and is not trigger by self, then use it
|
||||
|
if (servicePromise && servicePromise !== currentPromiseRef.value) { |
||||
|
return { servicePromise }; |
||||
|
} |
||||
|
|
||||
|
servicePromise = service(...args); |
||||
|
currentPromiseRef.value = servicePromise; |
||||
|
setCachePromise(cacheKey, servicePromise); |
||||
|
|
||||
|
return { servicePromise }; |
||||
|
}, |
||||
|
onSuccess: (data, params) => { |
||||
|
if (cacheKey) { |
||||
|
// cancel subscribe, avoid trgger self
|
||||
|
unSubscribeRef.value?.(); |
||||
|
|
||||
|
_setCache(cacheKey, { data, params, time: new Date().getTime() }); |
||||
|
|
||||
|
// resubscribe
|
||||
|
unSubscribeRef.value = subscribe(cacheKey, (d) => { |
||||
|
fetchInstance.setState({ data: d }); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
onMutate: (data) => { |
||||
|
if (cacheKey) { |
||||
|
// cancel subscribe, avoid trigger self
|
||||
|
unSubscribeRef.value?.(); |
||||
|
|
||||
|
_setCache(cacheKey, { |
||||
|
data, |
||||
|
params: fetchInstance.state.params, |
||||
|
time: new Date().getTime(), |
||||
|
}); |
||||
|
|
||||
|
// resubscribe
|
||||
|
unSubscribeRef.value = subscribe(cacheKey, (d) => { |
||||
|
fetchInstance.setState({ data: d }); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export default useCachePlugin; |
@ -0,0 +1,71 @@ |
|||||
|
import type { DebouncedFunc, DebounceSettings } from 'lodash-es'; |
||||
|
import { debounce } from 'lodash-es'; |
||||
|
import { computed, ref, watchEffect } from 'vue'; |
||||
|
|
||||
|
import type { UseRequestPlugin } from '../types'; |
||||
|
|
||||
|
const useDebouncePlugin: UseRequestPlugin<any, any[]> = ( |
||||
|
fetchInstance, |
||||
|
{ debounceWait, debounceLeading, debounceTrailing, debounceMaxWait }, |
||||
|
) => { |
||||
|
const debouncedRef = ref<DebouncedFunc<any>>(); |
||||
|
|
||||
|
const options = computed(() => { |
||||
|
const ret: DebounceSettings = {}; |
||||
|
|
||||
|
if (debounceLeading !== undefined) { |
||||
|
ret.leading = debounceLeading; |
||||
|
} |
||||
|
if (debounceTrailing !== undefined) { |
||||
|
ret.trailing = debounceTrailing; |
||||
|
} |
||||
|
if (debounceMaxWait !== undefined) { |
||||
|
ret.maxWait = debounceMaxWait; |
||||
|
} |
||||
|
|
||||
|
return ret; |
||||
|
}); |
||||
|
|
||||
|
watchEffect(() => { |
||||
|
if (debounceWait) { |
||||
|
const _originRunAsync = fetchInstance.runAsync.bind(fetchInstance); |
||||
|
|
||||
|
debouncedRef.value = debounce( |
||||
|
(callback) => { |
||||
|
callback(); |
||||
|
}, |
||||
|
debounceWait, |
||||
|
options.value, |
||||
|
); |
||||
|
|
||||
|
// debounce runAsync should be promise
|
||||
|
// https://github.com/lodash/lodash/issues/4400#issuecomment-834800398
|
||||
|
fetchInstance.runAsync = (...args) => { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
debouncedRef.value?.(() => { |
||||
|
_originRunAsync(...args) |
||||
|
.then(resolve) |
||||
|
.catch(reject); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
return () => { |
||||
|
debouncedRef.value?.cancel(); |
||||
|
fetchInstance.runAsync = _originRunAsync; |
||||
|
}; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (!debounceWait) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
onCancel: () => { |
||||
|
debouncedRef.value?.cancel(); |
||||
|
}, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export default useDebouncePlugin; |
@ -0,0 +1,45 @@ |
|||||
|
import { ref, unref } from 'vue'; |
||||
|
|
||||
|
import type { UseRequestPlugin, UseRequestTimeout } from '../types'; |
||||
|
|
||||
|
const useLoadingDelayPlugin: UseRequestPlugin<any, any[]> = ( |
||||
|
fetchInstance, |
||||
|
{ loadingDelay, ready }, |
||||
|
) => { |
||||
|
const timerRef = ref<UseRequestTimeout>(); |
||||
|
|
||||
|
if (!loadingDelay) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
const cancelTimeout = () => { |
||||
|
if (timerRef.value) { |
||||
|
clearTimeout(timerRef.value); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return { |
||||
|
onBefore: () => { |
||||
|
cancelTimeout(); |
||||
|
|
||||
|
// Two cases:
|
||||
|
// 1. ready === undefined
|
||||
|
// 2. ready === true
|
||||
|
if (unref(ready) !== false) { |
||||
|
timerRef.value = setTimeout(() => { |
||||
|
fetchInstance.setState({ loading: true }); |
||||
|
}, loadingDelay); |
||||
|
} |
||||
|
|
||||
|
return { loading: false }; |
||||
|
}, |
||||
|
onFinally: () => { |
||||
|
cancelTimeout(); |
||||
|
}, |
||||
|
onCancel: () => { |
||||
|
cancelTimeout(); |
||||
|
}, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export default useLoadingDelayPlugin; |
@ -0,0 +1,71 @@ |
|||||
|
import { ref, watch } from 'vue'; |
||||
|
|
||||
|
import type { UseRequestPlugin, UseRequestTimeout } from '../types'; |
||||
|
import { isDocumentVisible } from '../utils/isDocumentVisible'; |
||||
|
import subscribeReVisible from '../utils/subscribeReVisible'; |
||||
|
|
||||
|
const usePollingPlugin: UseRequestPlugin<any, any[]> = ( |
||||
|
fetchInstance, |
||||
|
{ pollingInterval, pollingWhenHidden = true, pollingErrorRetryCount = -1 }, |
||||
|
) => { |
||||
|
const timerRef = ref<UseRequestTimeout>(); |
||||
|
const unsubscribeRef = ref<() => void>(); |
||||
|
const countRef = ref<number>(0); |
||||
|
|
||||
|
const stopPolling = () => { |
||||
|
if (timerRef.value) { |
||||
|
clearTimeout(timerRef.value); |
||||
|
} |
||||
|
unsubscribeRef.value?.(); |
||||
|
}; |
||||
|
|
||||
|
watch( |
||||
|
() => pollingInterval, |
||||
|
() => { |
||||
|
if (!pollingInterval) { |
||||
|
stopPolling(); |
||||
|
} |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
if (!pollingInterval) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
onBefore: () => { |
||||
|
stopPolling(); |
||||
|
}, |
||||
|
onError: () => { |
||||
|
countRef.value += 1; |
||||
|
}, |
||||
|
onSuccess: () => { |
||||
|
countRef.value = 0; |
||||
|
}, |
||||
|
onFinally: () => { |
||||
|
if ( |
||||
|
pollingErrorRetryCount === -1 || |
||||
|
// When an error occurs, the request is not repeated after pollingErrorRetryCount retries
|
||||
|
(pollingErrorRetryCount !== -1 && countRef.value <= pollingErrorRetryCount) |
||||
|
) { |
||||
|
timerRef.value = setTimeout(() => { |
||||
|
// if pollingWhenHidden = false && document is hidden, then stop polling and subscribe revisible
|
||||
|
if (!pollingWhenHidden && !isDocumentVisible()) { |
||||
|
unsubscribeRef.value = subscribeReVisible(() => { |
||||
|
fetchInstance.refresh(); |
||||
|
}); |
||||
|
} else { |
||||
|
fetchInstance.refresh(); |
||||
|
} |
||||
|
}, pollingInterval); |
||||
|
} else { |
||||
|
countRef.value = 0; |
||||
|
} |
||||
|
}, |
||||
|
onCancel: () => { |
||||
|
stopPolling(); |
||||
|
}, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export default usePollingPlugin; |
@ -0,0 +1,37 @@ |
|||||
|
import { onUnmounted, ref, watchEffect } from 'vue'; |
||||
|
|
||||
|
import type { UseRequestPlugin } from '../types'; |
||||
|
import { limit } from '../utils/limit'; |
||||
|
import subscribeFocus from '../utils/subscribeFocus'; |
||||
|
|
||||
|
const useRefreshOnWindowFocusPlugin: UseRequestPlugin<any, any[]> = ( |
||||
|
fetchInstance, |
||||
|
{ refreshOnWindowFocus, focusTimespan = 5000 }, |
||||
|
) => { |
||||
|
const unsubscribeRef = ref<() => void>(); |
||||
|
|
||||
|
const stopSubscribe = () => { |
||||
|
unsubscribeRef.value?.(); |
||||
|
}; |
||||
|
|
||||
|
watchEffect(() => { |
||||
|
if (refreshOnWindowFocus) { |
||||
|
const limitRefresh = limit(fetchInstance.refresh.bind(fetchInstance), focusTimespan); |
||||
|
unsubscribeRef.value = subscribeFocus(() => { |
||||
|
limitRefresh(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return () => { |
||||
|
stopSubscribe(); |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
onUnmounted(() => { |
||||
|
stopSubscribe(); |
||||
|
}); |
||||
|
|
||||
|
return {}; |
||||
|
}; |
||||
|
|
||||
|
export default useRefreshOnWindowFocusPlugin; |
@ -0,0 +1,54 @@ |
|||||
|
import { ref } from 'vue'; |
||||
|
|
||||
|
import type { UseRequestPlugin, UseRequestTimeout } from '../types'; |
||||
|
|
||||
|
const useRetryPlugin: UseRequestPlugin<any, any[]> = ( |
||||
|
fetchInstance, |
||||
|
{ retryInterval, retryCount }, |
||||
|
) => { |
||||
|
const timerRef = ref<UseRequestTimeout>(); |
||||
|
const countRef = ref(0); |
||||
|
|
||||
|
const triggerByRetry = ref(false); |
||||
|
|
||||
|
if (!retryCount) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
onBefore: () => { |
||||
|
if (!triggerByRetry.value) { |
||||
|
countRef.value = 0; |
||||
|
} |
||||
|
triggerByRetry.value = false; |
||||
|
|
||||
|
if (timerRef.value) { |
||||
|
clearTimeout(timerRef.value); |
||||
|
} |
||||
|
}, |
||||
|
onSuccess: () => { |
||||
|
countRef.value = 0; |
||||
|
}, |
||||
|
onError: () => { |
||||
|
countRef.value += 1; |
||||
|
if (retryCount === -1 || countRef.value <= retryCount) { |
||||
|
// Exponential backoff
|
||||
|
const timeout = retryInterval ?? Math.min(1000 * 2 ** countRef.value, 30000); |
||||
|
timerRef.value = setTimeout(() => { |
||||
|
triggerByRetry.value = true; |
||||
|
fetchInstance.refresh(); |
||||
|
}, timeout); |
||||
|
} else { |
||||
|
countRef.value = 0; |
||||
|
} |
||||
|
}, |
||||
|
onCancel: () => { |
||||
|
countRef.value = 0; |
||||
|
if (timerRef.value) { |
||||
|
clearTimeout(timerRef.value); |
||||
|
} |
||||
|
}, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export default useRetryPlugin; |
@ -0,0 +1,63 @@ |
|||||
|
import type { DebouncedFunc, ThrottleSettings } from 'lodash-es'; |
||||
|
import { throttle } from 'lodash-es'; |
||||
|
import { ref, watchEffect } from 'vue'; |
||||
|
|
||||
|
import type { UseRequestPlugin } from '../types'; |
||||
|
|
||||
|
const useThrottlePlugin: UseRequestPlugin<any, any[]> = ( |
||||
|
fetchInstance, |
||||
|
{ throttleWait, throttleLeading, throttleTrailing }, |
||||
|
) => { |
||||
|
const throttledRef = ref<DebouncedFunc<any>>(); |
||||
|
|
||||
|
const options: ThrottleSettings = {}; |
||||
|
if (throttleLeading !== undefined) { |
||||
|
options.leading = throttleLeading; |
||||
|
} |
||||
|
if (throttleTrailing !== undefined) { |
||||
|
options.trailing = throttleTrailing; |
||||
|
} |
||||
|
|
||||
|
watchEffect(() => { |
||||
|
if (throttleWait) { |
||||
|
const _originRunAsync = fetchInstance.runAsync.bind(fetchInstance); |
||||
|
|
||||
|
throttledRef.value = throttle( |
||||
|
(callback) => { |
||||
|
callback(); |
||||
|
}, |
||||
|
throttleWait, |
||||
|
options, |
||||
|
); |
||||
|
|
||||
|
// throttle runAsync should be promise
|
||||
|
// https://github.com/lodash/lodash/issues/4400#issuecomment-834800398
|
||||
|
fetchInstance.runAsync = (...args) => { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
throttledRef.value?.(() => { |
||||
|
_originRunAsync(...args) |
||||
|
.then(resolve) |
||||
|
.catch(reject); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
return () => { |
||||
|
fetchInstance.runAsync = _originRunAsync; |
||||
|
throttledRef.value?.cancel(); |
||||
|
}; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (!throttleWait) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
onCancel: () => { |
||||
|
throttledRef.value?.cancel(); |
||||
|
}, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export default useThrottlePlugin; |
@ -0,0 +1,124 @@ |
|||||
|
import type { MaybeRef, Ref, WatchSource } from 'vue'; |
||||
|
|
||||
|
import type Fetch from './Fetch'; |
||||
|
import type { CachedData } from './utils/cache'; |
||||
|
|
||||
|
export type Service<TData, TParams extends any[]> = (...args: TParams) => Promise<TData>; |
||||
|
export type Subscribe = () => void; |
||||
|
|
||||
|
// for Fetch
|
||||
|
export interface FetchState<TData, TParams extends any[]> { |
||||
|
loading: boolean; |
||||
|
params?: TParams; |
||||
|
data?: TData; |
||||
|
error?: Error; |
||||
|
} |
||||
|
|
||||
|
export interface PluginReturn<TData, TParams extends any[]> { |
||||
|
onBefore?: (params: TParams) => |
||||
|
| ({ |
||||
|
stopNow?: boolean; |
||||
|
returnNow?: boolean; |
||||
|
} & Partial<FetchState<TData, TParams>>) |
||||
|
| void; |
||||
|
|
||||
|
onRequest?: ( |
||||
|
service: Service<TData, TParams>, |
||||
|
params: TParams, |
||||
|
) => { |
||||
|
servicePromise?: Promise<TData>; |
||||
|
}; |
||||
|
|
||||
|
onSuccess?: (data: TData, params: TParams) => void; |
||||
|
onError?: (e: Error, params: TParams) => void; |
||||
|
onFinally?: (params: TParams, data?: TData, e?: Error) => void; |
||||
|
onCancel?: () => void; |
||||
|
onMutate?: (data: TData) => void; |
||||
|
} |
||||
|
|
||||
|
// for useRequestImplement
|
||||
|
export interface UseRequestOptions<TData, TParams extends any[]> { |
||||
|
manual?: MaybeRef<boolean>; |
||||
|
|
||||
|
onBefore?: (params: TParams) => void; |
||||
|
onSuccess?: (data: TData, params: TParams) => void; |
||||
|
onError?: (e: Error, params: TParams) => void; |
||||
|
// formatResult?: (res: any) => TData;
|
||||
|
onFinally?: (params: TParams, data?: TData, e?: Error) => void; |
||||
|
|
||||
|
defaultParams?: TParams; |
||||
|
|
||||
|
// refreshDeps
|
||||
|
refreshDeps?: WatchSource<any>[]; |
||||
|
refreshDepsAction?: () => void; |
||||
|
|
||||
|
// loading delay
|
||||
|
loadingDelay?: number; |
||||
|
|
||||
|
// polling
|
||||
|
pollingInterval?: number; |
||||
|
pollingWhenHidden?: boolean; |
||||
|
pollingErrorRetryCount?: number; |
||||
|
|
||||
|
// refresh on window focus
|
||||
|
refreshOnWindowFocus?: boolean; |
||||
|
focusTimespan?: number; |
||||
|
|
||||
|
// debounce
|
||||
|
debounceWait?: number; |
||||
|
debounceLeading?: boolean; |
||||
|
debounceTrailing?: boolean; |
||||
|
debounceMaxWait?: number; |
||||
|
|
||||
|
// throttle
|
||||
|
throttleWait?: number; |
||||
|
throttleLeading?: boolean; |
||||
|
throttleTrailing?: boolean; |
||||
|
|
||||
|
// cache
|
||||
|
cacheKey?: string; |
||||
|
cacheTime?: number; |
||||
|
staleTime?: number; |
||||
|
setCache?: (data: CachedData<TData, TParams>) => void; |
||||
|
getCache?: (params: TParams) => CachedData<TData, TParams> | undefined; |
||||
|
|
||||
|
// retry
|
||||
|
retryCount?: number; |
||||
|
retryInterval?: number; |
||||
|
|
||||
|
// ready
|
||||
|
ready?: MaybeRef<boolean>; |
||||
|
|
||||
|
// [key: string]: any;
|
||||
|
} |
||||
|
|
||||
|
export interface UseRequestPlugin<TData, TParams extends any[]> { |
||||
|
// eslint-disable-next-line prettier/prettier
|
||||
|
( |
||||
|
fetchInstance: Fetch<TData, TParams>, |
||||
|
options: UseRequestOptions<TData, TParams>, |
||||
|
): PluginReturn<TData, TParams>; |
||||
|
onInit?: (options: UseRequestOptions<TData, TParams>) => Partial<FetchState<TData, TParams>>; |
||||
|
} |
||||
|
|
||||
|
// for index
|
||||
|
// export type OptionsWithoutFormat<TData, TParams extends any[]> = Omit<Options<TData, TParams>, 'formatResult'>;
|
||||
|
|
||||
|
// export interface OptionsWithFormat<TData, TParams extends any[], TFormated, TTFormated extends TFormated = any> extends Omit<Options<TTFormated, TParams>, 'formatResult'> {
|
||||
|
// formatResult: (res: TData) => TFormated;
|
||||
|
// };
|
||||
|
|
||||
|
export interface UseRequestResult<TData, TParams extends any[]> { |
||||
|
loading: Ref<boolean>; |
||||
|
data: Ref<TData>; |
||||
|
error: Ref<Error>; |
||||
|
params: Ref<TParams | []>; |
||||
|
cancel: Fetch<TData, TParams>['cancel']; |
||||
|
refresh: Fetch<TData, TParams>['refresh']; |
||||
|
refreshAsync: Fetch<TData, TParams>['refreshAsync']; |
||||
|
run: Fetch<TData, TParams>['run']; |
||||
|
runAsync: Fetch<TData, TParams>['runAsync']; |
||||
|
mutate: Fetch<TData, TParams>['mutate']; |
||||
|
} |
||||
|
|
||||
|
export type UseRequestTimeout = ReturnType<typeof setTimeout>; |
@ -0,0 +1,49 @@ |
|||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */ |
||||
|
import { onMounted, onUnmounted, toRefs } from 'vue'; |
||||
|
|
||||
|
import Fetch from './Fetch'; |
||||
|
import type { Service, UseRequestOptions, UseRequestPlugin, UseRequestResult } from './types'; |
||||
|
|
||||
|
export function useRequestImplement<TData, TParams extends any[]>( |
||||
|
service: Service<TData, TParams>, |
||||
|
options: UseRequestOptions<TData, TParams> = {}, |
||||
|
plugins: UseRequestPlugin<TData, TParams>[] = [], |
||||
|
) { |
||||
|
const { manual = false, ...rest } = options; |
||||
|
const fetchOptions = { manual, ...rest }; |
||||
|
|
||||
|
const initState = plugins.map((p) => p?.onInit?.(fetchOptions)).filter(Boolean); |
||||
|
|
||||
|
const fetchInstance = new Fetch<TData, TParams>( |
||||
|
service, |
||||
|
fetchOptions, |
||||
|
() => {}, |
||||
|
Object.assign({}, ...initState), |
||||
|
); |
||||
|
|
||||
|
fetchInstance.options = fetchOptions; |
||||
|
// run all plugins hooks
|
||||
|
fetchInstance.pluginImpls = plugins.map((p) => p(fetchInstance, fetchOptions)); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
if (!manual) { |
||||
|
const params = fetchInstance.state.params || options.defaultParams || []; |
||||
|
// @ts-ignore
|
||||
|
fetchInstance.run(...params); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
onUnmounted(() => { |
||||
|
fetchInstance.cancel(); |
||||
|
}); |
||||
|
|
||||
|
return { |
||||
|
...toRefs(fetchInstance.state), |
||||
|
cancel: fetchInstance.cancel.bind(fetchInstance), |
||||
|
mutate: fetchInstance.mutate.bind(fetchInstance), |
||||
|
refresh: fetchInstance.refresh.bind(fetchInstance), |
||||
|
refreshAsync: fetchInstance.refreshAsync.bind(fetchInstance), |
||||
|
run: fetchInstance.run.bind(fetchInstance), |
||||
|
runAsync: fetchInstance.runAsync.bind(fetchInstance), |
||||
|
} as UseRequestResult<TData, TParams>; |
||||
|
} |
@ -0,0 +1,48 @@ |
|||||
|
type Timer = ReturnType<typeof setTimeout>; |
||||
|
type CachedKey = string | number; |
||||
|
|
||||
|
export interface CachedData<TData = any, TParams = any> { |
||||
|
data: TData; |
||||
|
params: TParams; |
||||
|
time: number; |
||||
|
} |
||||
|
|
||||
|
interface RecordData extends CachedData { |
||||
|
timer: Timer | undefined; |
||||
|
} |
||||
|
|
||||
|
const cache = new Map<CachedKey, RecordData>(); |
||||
|
|
||||
|
export const setCache = (key: CachedKey, cacheTime: number, cachedData: CachedData) => { |
||||
|
const currentCache = cache.get(key); |
||||
|
if (currentCache?.timer) { |
||||
|
clearTimeout(currentCache.timer); |
||||
|
} |
||||
|
|
||||
|
let timer: Timer | undefined = undefined; |
||||
|
|
||||
|
if (cacheTime > -1) { |
||||
|
// if cache out, clear it
|
||||
|
timer = setTimeout(() => { |
||||
|
cache.delete(key); |
||||
|
}, cacheTime); |
||||
|
} |
||||
|
|
||||
|
cache.set(key, { |
||||
|
...cachedData, |
||||
|
timer, |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
export const getCache = (key: CachedKey) => { |
||||
|
return cache.get(key); |
||||
|
}; |
||||
|
|
||||
|
export const clearCache = (key?: string | string[]) => { |
||||
|
if (key) { |
||||
|
const cacheKeys = Array.isArray(key) ? key : [key]; |
||||
|
cacheKeys.forEach((cacheKey) => cache.delete(cacheKey)); |
||||
|
} else { |
||||
|
cache.clear(); |
||||
|
} |
||||
|
}; |
@ -0,0 +1,23 @@ |
|||||
|
type CachedKey = string | number; |
||||
|
|
||||
|
const cachePromise = new Map<CachedKey, Promise<any>>(); |
||||
|
|
||||
|
export const getCachePromise = (cacheKey: CachedKey) => { |
||||
|
return cachePromise.get(cacheKey); |
||||
|
}; |
||||
|
|
||||
|
export const setCachePromise = (cacheKey: CachedKey, promise: Promise<any>) => { |
||||
|
// Should cache the same promise, cannot be promise.finally
|
||||
|
// Because the promise.finally will change the reference of the promise
|
||||
|
cachePromise.set(cacheKey, promise); |
||||
|
|
||||
|
// no use promise.finally for compatibility
|
||||
|
promise |
||||
|
.then((res) => { |
||||
|
cachePromise.delete(cacheKey); |
||||
|
return res; |
||||
|
}) |
||||
|
.catch(() => { |
||||
|
cachePromise.delete(cacheKey); |
||||
|
}); |
||||
|
}; |
@ -0,0 +1,22 @@ |
|||||
|
type Listener = (data: any) => void; |
||||
|
|
||||
|
const listeners: Record<string, Listener[]> = {}; |
||||
|
|
||||
|
export const trigger = (key: string, data: any) => { |
||||
|
if (listeners[key]) { |
||||
|
listeners[key].forEach((item) => item(data)); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export const subscribe = (key: string, listener: Listener) => { |
||||
|
if (!listeners[key]) { |
||||
|
listeners[key] = []; |
||||
|
} |
||||
|
|
||||
|
listeners[key].push(listener); |
||||
|
|
||||
|
return function unsubscribe() { |
||||
|
const index = listeners[key].indexOf(listener); |
||||
|
listeners[key].splice(index, 1); |
||||
|
}; |
||||
|
}; |
@ -0,0 +1,5 @@ |
|||||
|
export const isBrowser = !!( |
||||
|
typeof window !== 'undefined' && |
||||
|
window.document && |
||||
|
window.document.createElement |
||||
|
); |
@ -0,0 +1,8 @@ |
|||||
|
import { isBrowser } from './isBrowser'; |
||||
|
|
||||
|
export function isDocumentVisible(): boolean { |
||||
|
if (isBrowser) { |
||||
|
return document.visibilityState !== 'hidden'; |
||||
|
} |
||||
|
return true; |
||||
|
} |
@ -0,0 +1,2 @@ |
|||||
|
export const isFunction = (value: unknown): value is (...args: any) => any => |
||||
|
typeof value === 'function'; |
@ -0,0 +1,8 @@ |
|||||
|
import { isBrowser } from './isBrowser'; |
||||
|
|
||||
|
export function isOnline(): boolean { |
||||
|
if (isBrowser && typeof navigator.onLine !== 'undefined') { |
||||
|
return navigator.onLine; |
||||
|
} |
||||
|
return true; |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
export function limit(fn: any, timespan: number) { |
||||
|
let pending = false; |
||||
|
|
||||
|
return (...args: any[]) => { |
||||
|
if (pending) return; |
||||
|
pending = true; |
||||
|
fn(...args); |
||||
|
setTimeout(() => { |
||||
|
pending = false; |
||||
|
}, timespan); |
||||
|
}; |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
import { isBrowser } from './isBrowser'; |
||||
|
import { isDocumentVisible } from './isDocumentVisible'; |
||||
|
import { isOnline } from './isOnline'; |
||||
|
|
||||
|
type Listener = () => void; |
||||
|
|
||||
|
const listeners: Listener[] = []; |
||||
|
|
||||
|
if (isBrowser) { |
||||
|
const revalidate = () => { |
||||
|
if (!isDocumentVisible() || !isOnline()) return; |
||||
|
for (let i = 0; i < listeners.length; i++) { |
||||
|
const listener = listeners[i]; |
||||
|
listener(); |
||||
|
} |
||||
|
}; |
||||
|
window.addEventListener('visibilitychange', revalidate, false); |
||||
|
window.addEventListener('focus', revalidate, false); |
||||
|
} |
||||
|
|
||||
|
export default function subscribe(listener: Listener) { |
||||
|
listeners.push(listener); |
||||
|
|
||||
|
return function unsubscribe() { |
||||
|
const index = listeners.indexOf(listener); |
||||
|
if (index > -1) { |
||||
|
listeners.splice(index, 1); |
||||
|
} |
||||
|
}; |
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
import { isBrowser } from './isBrowser'; |
||||
|
import { isDocumentVisible } from './isDocumentVisible'; |
||||
|
|
||||
|
type Listener = () => void; |
||||
|
|
||||
|
const listeners: Listener[] = []; |
||||
|
|
||||
|
if (isBrowser) { |
||||
|
const revalidate = () => { |
||||
|
if (!isDocumentVisible()) return; |
||||
|
for (let i = 0; i < listeners.length; i++) { |
||||
|
const listener = listeners[i]; |
||||
|
listener(); |
||||
|
} |
||||
|
}; |
||||
|
window.addEventListener('visibilitychange', revalidate, false); |
||||
|
} |
||||
|
|
||||
|
export default function subscribe(listener: Listener) { |
||||
|
listeners.push(listener); |
||||
|
return function unsubscribe() { |
||||
|
const index = listeners.indexOf(listener); |
||||
|
listeners.splice(index, 1); |
||||
|
}; |
||||
|
} |
@ -0,0 +1,60 @@ |
|||||
|
import { shallowRef, unref } from 'vue'; |
||||
|
|
||||
|
interface UseScrollToOptions { |
||||
|
el: any; |
||||
|
to: number; |
||||
|
duration?: number; |
||||
|
callback?: () => any; |
||||
|
} |
||||
|
|
||||
|
function easeInOutQuad(t: number, b: number, c: number, d: number) { |
||||
|
t /= d / 2; |
||||
|
if (t < 1) { |
||||
|
return (c / 2) * t * t + b; |
||||
|
} |
||||
|
t--; |
||||
|
return (-c / 2) * (t * (t - 2) - 1) + b; |
||||
|
} |
||||
|
|
||||
|
function move(el: HTMLElement, amount: number) { |
||||
|
el.scrollTop = amount; |
||||
|
} |
||||
|
|
||||
|
const position = (el: HTMLElement) => { |
||||
|
return el.scrollTop; |
||||
|
}; |
||||
|
function useScrollTo({ el, to, duration = 500, callback }: UseScrollToOptions) { |
||||
|
const isActiveRef = shallowRef(false); |
||||
|
const start = position(el); |
||||
|
const change = to - start; |
||||
|
const increment = 20; |
||||
|
let currentTime = 0; |
||||
|
|
||||
|
const animateScroll = function () { |
||||
|
if (!unref(isActiveRef)) { |
||||
|
return; |
||||
|
} |
||||
|
currentTime += increment; |
||||
|
const val = easeInOutQuad(currentTime, start, change, duration); |
||||
|
move(el, val); |
||||
|
if (currentTime < duration && unref(isActiveRef)) { |
||||
|
requestAnimationFrame(animateScroll); |
||||
|
} else { |
||||
|
if (callback && typeof callback === 'function') { |
||||
|
callback(); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
const run = () => { |
||||
|
isActiveRef.value = true; |
||||
|
animateScroll(); |
||||
|
}; |
||||
|
|
||||
|
const stop = () => { |
||||
|
isActiveRef.value = false; |
||||
|
}; |
||||
|
|
||||
|
return { start: run, stop }; |
||||
|
} |
||||
|
|
||||
|
export { useScrollTo, type UseScrollToOptions }; |
@ -0,0 +1,40 @@ |
|||||
|
import { type AnyFunction } from '@vben/types'; |
||||
|
import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'; |
||||
|
|
||||
|
interface UseWindowSizeOptions { |
||||
|
wait?: number; |
||||
|
once?: boolean; |
||||
|
immediate?: boolean; |
||||
|
listenerOptions?: AddEventListenerOptions | boolean; |
||||
|
} |
||||
|
|
||||
|
function useWindowSizeFn(fn: AnyFunction, options: UseWindowSizeOptions = {}) { |
||||
|
const { wait = 150, immediate } = options; |
||||
|
let handler = () => { |
||||
|
fn(); |
||||
|
}; |
||||
|
const handleSize = useDebounceFn(handler, wait); |
||||
|
handler = handleSize; |
||||
|
|
||||
|
const start = () => { |
||||
|
if (immediate) { |
||||
|
handler(); |
||||
|
} |
||||
|
window.addEventListener('resize', handler); |
||||
|
}; |
||||
|
|
||||
|
const stop = () => { |
||||
|
window.removeEventListener('resize', handler); |
||||
|
}; |
||||
|
|
||||
|
tryOnMounted(() => { |
||||
|
start(); |
||||
|
}); |
||||
|
|
||||
|
tryOnUnmounted(() => { |
||||
|
stop(); |
||||
|
}); |
||||
|
return { start, stop }; |
||||
|
} |
||||
|
|
||||
|
export { useWindowSizeFn, type UseWindowSizeOptions }; |
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/tsconfig", |
||||
|
"extends": "@vben/ts-config/vue-app.json", |
||||
|
"include": ["src"] |
||||
|
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue