first commit
commit
ccd0308958
|
|
@ -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 `yarn 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,3 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.vscode/
|
||||||
|
|
@ -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,23 @@
|
||||||
|
# Whether to open mock
|
||||||
|
VITE_USE_MOCK = true
|
||||||
|
|
||||||
|
# public path
|
||||||
|
VITE_PUBLIC_PATH = /
|
||||||
|
|
||||||
|
# Whether to enable gzip or brotli compression
|
||||||
|
# Optional: gzip | brotli | none
|
||||||
|
# If you need multiple forms, you can use `,` to separate
|
||||||
|
VITE_BUILD_COMPRESS = 'none'
|
||||||
|
|
||||||
|
|
||||||
|
# Basic interface address SPA
|
||||||
|
VITE_GLOB_API_URL=/basic-api
|
||||||
|
|
||||||
|
# File upload address, optional
|
||||||
|
# It can be forwarded by nginx or write the actual address directly
|
||||||
|
VITE_GLOB_UPLOAD_URL=/upload
|
||||||
|
|
||||||
|
# Interface prefix
|
||||||
|
VITE_GLOB_API_URL_PREFIX=
|
||||||
|
|
||||||
|
VITE_ENABLE_ANALYZE = true
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Whether to open mock
|
||||||
|
VITE_USE_MOCK = true
|
||||||
|
|
||||||
|
# public path
|
||||||
|
VITE_PUBLIC_PATH = /
|
||||||
|
|
||||||
|
# Basic interface address SPA
|
||||||
|
#VITE_GLOB_API_URL=/basic-api
|
||||||
|
VITE_GLOB_API_URL=http://192.168.10.102:9020
|
||||||
|
|
||||||
|
|
||||||
|
# File upload address, optional
|
||||||
|
# VITE_GLOB_UPLOAD_URL=http://192.168.10.104:9011
|
||||||
|
VITE_GLOB_UPLOAD_URL=http://60.213.14.14:6070
|
||||||
|
|
||||||
|
# Interface prefix
|
||||||
|
VITE_GLOB_API_URL_PREFIX=
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Whether to open mock
|
||||||
|
VITE_USE_MOCK = true
|
||||||
|
|
||||||
|
# public path
|
||||||
|
VITE_PUBLIC_PATH = /
|
||||||
|
|
||||||
|
# Whether to enable gzip or brotli compression
|
||||||
|
# Optional: gzip | brotli | none
|
||||||
|
# If you need multiple forms, you can use `,` to separate
|
||||||
|
VITE_BUILD_COMPRESS = 'none'
|
||||||
|
|
||||||
|
|
||||||
|
# Basic interface address SPA
|
||||||
|
VITE_GLOB_API_URL=http://192.168.10.102:9020
|
||||||
|
|
||||||
|
# File upload address, optional
|
||||||
|
# It can be forwarded by nginx or write the actual address directly
|
||||||
|
VITE_GLOB_UPLOAD_URL=http://60.213.14.14:6070
|
||||||
|
|
||||||
|
# Interface prefix
|
||||||
|
VITE_GLOB_API_URL_PREFIX=
|
||||||
|
|
@ -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,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,6 @@
|
||||||
|
ports:
|
||||||
|
- port: 3344
|
||||||
|
onOpen: open-preview
|
||||||
|
tasks:
|
||||||
|
- init: pnpm install
|
||||||
|
command: pnpm run dev
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
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
|
||||||
|
|
@ -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,162 @@
|
||||||
|
<!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" />
|
||||||
|
<link rel="stylesheet" href="learunui/learunui.css">
|
||||||
|
|
||||||
|
</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-rotate {
|
||||||
|
to {
|
||||||
|
transform: rotate(405deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ant-spin-move {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ant-spin-move {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<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": "^6.17.0",
|
||||||
|
"@typescript-eslint/parser": "^6.17.0",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-import": "^2.29.1",
|
||||||
|
"eslint-plugin-prettier": "^5.1.2",
|
||||||
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"eslint-plugin-vue": "^9.19.2",
|
||||||
|
"vue-eslint-parser": "^9.3.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.33",
|
||||||
|
"postcss-html": "^1.5.0",
|
||||||
|
"postcss-less": "^6.0.0",
|
||||||
|
"postcss-scss": "^4.0.9",
|
||||||
|
"prettier": "^3.1.1",
|
||||||
|
"stylelint": "^16.1.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.0.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.10.6",
|
||||||
|
"vite": "^5.0.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.0.10"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/fs-extra": "^11.0.4",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.2",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||||
|
"ant-design-vue": "^4.0.8",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"fs-extra": "^11.2.0",
|
||||||
|
"less": "^4.2.0",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"pkg-types": "^1.0.3",
|
||||||
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
|
"sass": "^1.69.7",
|
||||||
|
"unocss": "0.58.3",
|
||||||
|
"vite-plugin-compression": "^0.5.1",
|
||||||
|
"vite-plugin-dts": "^3.7.0",
|
||||||
|
"vite-plugin-html": "^3.2.1",
|
||||||
|
"vite-plugin-mock": "^2.9.6",
|
||||||
|
"vite-plugin-purge-icons": "^0.10.0",
|
||||||
|
"vite-plugin-svg-icons": "^2.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
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_USE_MOCK, 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',
|
||||||
|
enableMock: VITE_USE_MOCK === '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,22 @@
|
||||||
|
import UnoCSS from 'unocss/vite';
|
||||||
|
import { type UserConfig } from 'vite';
|
||||||
|
|
||||||
|
const commonConfig: (mode: string) => UserConfig = (mode) => ({
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
},
|
||||||
|
esbuild: {
|
||||||
|
drop: mode === 'production' ? ['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,14 @@
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
viteNext: true,
|
||||||
|
});
|
||||||
|
return htmlPlugin;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
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 { configMockPlugin } from './mock';
|
||||||
|
import { configSvgIconsPlugin } from './svgSprite';
|
||||||
|
import { configVisualizerConfig } from './visualizer';
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
isBuild: boolean;
|
||||||
|
root: string;
|
||||||
|
compress: string;
|
||||||
|
enableMock?: boolean;
|
||||||
|
enableAnalyze?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPlugins({ isBuild, root, enableMock, compress, enableAnalyze }: Options) {
|
||||||
|
const vitePlugins: (PluginOption | PluginOption[])[] = [vue(), vueJsx()];
|
||||||
|
|
||||||
|
const appConfigPlugin = await createAppConfigPlugin({ root, isBuild });
|
||||||
|
vitePlugins.push(appConfigPlugin);
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
// vite-plugin-mock
|
||||||
|
if (enableMock) {
|
||||||
|
vitePlugins.push(configMockPlugin({ isBuild }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return vitePlugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createPlugins };
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
/**
|
||||||
|
* Mock plugin for development and production.
|
||||||
|
* https://github.com/anncwb/vite-plugin-mock
|
||||||
|
*/
|
||||||
|
import { viteMockServe } from 'vite-plugin-mock';
|
||||||
|
|
||||||
|
export function configMockPlugin({ isBuild }: { isBuild: boolean }) {
|
||||||
|
return viteMockServe({
|
||||||
|
ignore: /^_/,
|
||||||
|
mockPath: 'mock',
|
||||||
|
localEnabled: !isBuild,
|
||||||
|
prodEnabled: isBuild,
|
||||||
|
injectCode: `
|
||||||
|
import { setupProdMockServer } from '../mock/_createProductionServer';
|
||||||
|
|
||||||
|
setupProdMockServer();
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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,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,47 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
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"]
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
|
||||||
|
|
||||||
|
// 问题描述
|
||||||
|
// 1. `import.meta.globEager` 已被弃用, 需要升级vite版本,有兼容问题
|
||||||
|
// 2. `vite-plugin-mock` 插件问题 https://github.com/vbenjs/vite-plugin-mock/issues/56
|
||||||
|
|
||||||
|
// const modules: Record<string, any> = import.meta.glob("./**/*.ts", {
|
||||||
|
// import: "default",
|
||||||
|
// eager: true,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const mockModules = Object.keys(modules).reduce((pre, key) => {
|
||||||
|
// if (!key.includes("/_")) {
|
||||||
|
// pre.push(...modules[key]);
|
||||||
|
// }
|
||||||
|
// return pre;
|
||||||
|
// }, [] as any[]);
|
||||||
|
|
||||||
|
const modules = import.meta.glob('./**/*.ts', { eager: true });
|
||||||
|
|
||||||
|
const mockModules: any[] = [];
|
||||||
|
Object.keys(modules).forEach((key) => {
|
||||||
|
if (key.includes('/_')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mockModules.push(...(modules as Recordable)[key].default);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in a production environment. Need to manually import all modules
|
||||||
|
*/
|
||||||
|
export function setupProdMockServer() {
|
||||||
|
createProdMockServer(mockModules);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Interface data format used to return a unified format
|
||||||
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
|
|
||||||
|
export function resultSuccess<T = Recordable>(result: T, { message = 'ok' } = {}) {
|
||||||
|
return {
|
||||||
|
code: ResultEnum.SUCCESS,
|
||||||
|
result,
|
||||||
|
message,
|
||||||
|
type: 'success',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resultPageSuccess<T = any>(
|
||||||
|
page: number,
|
||||||
|
pageSize: number,
|
||||||
|
list: T[],
|
||||||
|
{ message = 'ok' } = {},
|
||||||
|
) {
|
||||||
|
const pageData = pagination(page, pageSize, list);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...resultSuccess({
|
||||||
|
items: pageData,
|
||||||
|
total: list.length,
|
||||||
|
}),
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resultError(
|
||||||
|
message = 'Request failed',
|
||||||
|
{ code = ResultEnum.ERROR, result = null } = {},
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
result,
|
||||||
|
message,
|
||||||
|
type: 'error',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]): T[] {
|
||||||
|
const offset = (pageNo - 1) * Number(pageSize);
|
||||||
|
return offset + Number(pageSize) >= array.length
|
||||||
|
? array.slice(offset, array.length)
|
||||||
|
: array.slice(offset, offset + Number(pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface requestParams {
|
||||||
|
method: string;
|
||||||
|
body: any;
|
||||||
|
headers?: { authorization?: string };
|
||||||
|
query: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 本函数用于从request数据中获取token,请根据项目的实际情况修改
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function getRequestToken({ headers }: requestParams): string | undefined {
|
||||||
|
return headers?.authorization;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { MockMethod } from 'vite-plugin-mock';
|
||||||
|
import { resultSuccess, resultError } from '../_util';
|
||||||
|
import { ResultEnum } from '../../src/enums/httpEnum';
|
||||||
|
|
||||||
|
const userInfo = {
|
||||||
|
name: 'Vben',
|
||||||
|
userid: '00000001',
|
||||||
|
email: 'test@gmail.com',
|
||||||
|
signature: '海纳百川,有容乃大',
|
||||||
|
introduction: '微笑着,努力着,欣赏着',
|
||||||
|
title: '交互专家',
|
||||||
|
group: '某某某事业群-某某平台部-某某技术部-UED',
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
key: '0',
|
||||||
|
label: '很有想法的',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: '专注设计',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: '辣~',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
label: '大长腿',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
label: '川妹子',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5',
|
||||||
|
label: '海纳百川',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
notifyCount: 12,
|
||||||
|
unreadCount: 11,
|
||||||
|
country: 'China',
|
||||||
|
address: 'Xiamen City 77',
|
||||||
|
phone: '0592-268888888',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/basic-api/account/getAccountInfo',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'get',
|
||||||
|
response: () => {
|
||||||
|
return resultSuccess(userInfo);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/user/sessionTimeout',
|
||||||
|
method: 'post',
|
||||||
|
statusCode: 401,
|
||||||
|
response: () => {
|
||||||
|
return resultError();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/user/tokenExpired',
|
||||||
|
method: 'post',
|
||||||
|
statusCode: 200,
|
||||||
|
response: () => {
|
||||||
|
return resultError('Token Expired!', { code: ResultEnum.TIMEOUT as number });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as MockMethod[];
|
||||||
|
|
@ -0,0 +1,325 @@
|
||||||
|
import { MockMethod } from 'vite-plugin-mock';
|
||||||
|
import { resultSuccess } from '../_util';
|
||||||
|
|
||||||
|
const areaList: any[] = [
|
||||||
|
{
|
||||||
|
id: '530825900854620160',
|
||||||
|
code: '430000',
|
||||||
|
parentCode: '100000',
|
||||||
|
levelType: 1,
|
||||||
|
name: '湖南省',
|
||||||
|
province: '湖南省',
|
||||||
|
city: null,
|
||||||
|
district: null,
|
||||||
|
town: null,
|
||||||
|
village: null,
|
||||||
|
parentPath: '430000',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 16:33:42',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530825900883980288',
|
||||||
|
code: '430100',
|
||||||
|
parentCode: '430000',
|
||||||
|
levelType: 2,
|
||||||
|
name: '长沙市',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: null,
|
||||||
|
town: null,
|
||||||
|
village: null,
|
||||||
|
parentPath: '430000,430100',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 16:33:42',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530825900951089152',
|
||||||
|
code: '430102',
|
||||||
|
parentCode: '430100',
|
||||||
|
levelType: 3,
|
||||||
|
name: '芙蓉区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '芙蓉区',
|
||||||
|
town: null,
|
||||||
|
village: null,
|
||||||
|
parentPath: '430000,430100,430102',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 16:33:42',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530825901014003712',
|
||||||
|
code: '430104',
|
||||||
|
parentCode: '430100',
|
||||||
|
levelType: 3,
|
||||||
|
name: '岳麓区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '岳麓区',
|
||||||
|
town: null,
|
||||||
|
village: null,
|
||||||
|
parentPath: '430000,430100,430104',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 16:33:42',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530825900988837888',
|
||||||
|
code: '430103',
|
||||||
|
parentCode: '430100',
|
||||||
|
levelType: 3,
|
||||||
|
name: '天心区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: null,
|
||||||
|
village: null,
|
||||||
|
parentPath: '430000,430100,430103',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 16:33:42',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530826672489115648',
|
||||||
|
code: '430103002',
|
||||||
|
parentCode: '430103',
|
||||||
|
levelType: 4,
|
||||||
|
name: '坡子街街道',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: null,
|
||||||
|
parentPath: '430000,430100,430103,430103002',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-12-14 15:26:43',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241171607552',
|
||||||
|
code: '430103002001',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '八角亭社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '八角亭社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002001',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2021-01-20 14:07:23',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241200967680',
|
||||||
|
code: '430103002002',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '西牌楼社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '西牌楼社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002002',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241230327808',
|
||||||
|
code: '430103002003',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '太平街社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '太平街社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002003',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241259687936',
|
||||||
|
code: '430103002005',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '坡子街社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '坡子街社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002005',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241284853760',
|
||||||
|
code: '430103002006',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '青山祠社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '青山祠社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002006',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241310019584',
|
||||||
|
code: '430103002007',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '沙河社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '沙河社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002007',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241381322752',
|
||||||
|
code: '430103002008',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '碧湘社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '碧湘社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002008',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241410682880',
|
||||||
|
code: '430103002009',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '创远社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '创远社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002009',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241431654400',
|
||||||
|
code: '430103002010',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '楚湘社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '楚湘社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002010',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241465208832',
|
||||||
|
code: '430103002011',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '西湖社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '西湖社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002011',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241502957568',
|
||||||
|
code: '430103002012',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '登仁桥社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '登仁桥社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002012',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '530840241553289216',
|
||||||
|
code: '430103002013',
|
||||||
|
parentCode: '430103002',
|
||||||
|
levelType: 5,
|
||||||
|
name: '文庙坪社区',
|
||||||
|
province: '湖南省',
|
||||||
|
city: '长沙市',
|
||||||
|
district: '天心区',
|
||||||
|
town: '坡子街街道',
|
||||||
|
village: '文庙坪社区',
|
||||||
|
parentPath: '430000,430100,430103,430103002,430103002013',
|
||||||
|
createTime: '2020-11-30 15:47:31',
|
||||||
|
updateTime: '2020-11-30 17:30:41',
|
||||||
|
customized: false,
|
||||||
|
usable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/basic-api/cascader/getAreaRecord',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'post',
|
||||||
|
response: ({ body }) => {
|
||||||
|
const { parentCode } = body || {};
|
||||||
|
if (!parentCode) {
|
||||||
|
return resultSuccess(areaList.filter((it) => it.code === '430000'));
|
||||||
|
}
|
||||||
|
return resultSuccess(areaList.filter((it) => it.parentCode === parentCode));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as MockMethod[];
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { MockMethod } from 'vite-plugin-mock';
|
||||||
|
import { resultSuccess } from '../_util';
|
||||||
|
|
||||||
|
const demoList = (keyword, count = 20) => {
|
||||||
|
const result = {
|
||||||
|
list: [] as any[],
|
||||||
|
};
|
||||||
|
for (let index = 0; index < count; index++) {
|
||||||
|
result.list.push({
|
||||||
|
name: `${keyword ?? ''}选项${index}`,
|
||||||
|
id: `${index}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/basic-api/select/getDemoOptions',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'get',
|
||||||
|
response: ({ query }) => {
|
||||||
|
const { keyword, count } = query;
|
||||||
|
console.log(keyword);
|
||||||
|
return resultSuccess(demoList(keyword, count));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as MockMethod[];
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
import { MockMethod } from 'vite-plugin-mock';
|
||||||
|
import { resultError, resultPageSuccess, resultSuccess } from '../_util';
|
||||||
|
|
||||||
|
const accountList = (() => {
|
||||||
|
const result: any[] = [];
|
||||||
|
for (let index = 0; index < 20; index++) {
|
||||||
|
result.push({
|
||||||
|
id: `${index}`,
|
||||||
|
account: '@first',
|
||||||
|
email: '@email',
|
||||||
|
nickname: '@cname()',
|
||||||
|
role: '@first',
|
||||||
|
createTime: '@datetime',
|
||||||
|
remark: '@cword(10,20)',
|
||||||
|
'dept|0-2': 1,
|
||||||
|
'status|1': ['0', '1'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const roleList = (() => {
|
||||||
|
const result: any[] = [];
|
||||||
|
for (let index = 0; index < 4; index++) {
|
||||||
|
result.push({
|
||||||
|
id: index + 1,
|
||||||
|
orderNo: `${index + 1}`,
|
||||||
|
roleName: ['超级管理员', '管理员', '文章管理员', '普通用户'][index],
|
||||||
|
roleValue: '@first',
|
||||||
|
createTime: '@datetime',
|
||||||
|
remark: '@cword(10,20)',
|
||||||
|
menu: [['0', '1', '2'], ['0', '1'], ['0', '2'], ['2']][index],
|
||||||
|
'status|1': ['0', '1'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const deptList = (() => {
|
||||||
|
const result: any[] = [];
|
||||||
|
for (let index = 0; index < 3; index++) {
|
||||||
|
result.push({
|
||||||
|
id: `${index}`,
|
||||||
|
deptName: ['华东分部', '华南分部', '西北分部'][index],
|
||||||
|
orderNo: index + 1,
|
||||||
|
createTime: '@datetime',
|
||||||
|
remark: '@cword(10,20)',
|
||||||
|
'status|1': ['0', '0', '1'],
|
||||||
|
children: (() => {
|
||||||
|
const children: any[] = [];
|
||||||
|
for (let j = 0; j < 4; j++) {
|
||||||
|
children.push({
|
||||||
|
id: `${index}-${j}`,
|
||||||
|
deptName: ['研发部', '市场部', '商务部', '财务部'][j],
|
||||||
|
orderNo: j + 1,
|
||||||
|
createTime: '@datetime',
|
||||||
|
remark: '@cword(10,20)',
|
||||||
|
'status|1': ['0', '1'],
|
||||||
|
parentDept: `${index}`,
|
||||||
|
children: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
})(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const menuList = (() => {
|
||||||
|
const result: any[] = [];
|
||||||
|
for (let index = 0; index < 3; index++) {
|
||||||
|
result.push({
|
||||||
|
id: `${index}`,
|
||||||
|
icon: ['ion:layers-outline', 'ion:git-compare-outline', 'ion:tv-outline'][index],
|
||||||
|
component: 'LAYOUT',
|
||||||
|
type: '0',
|
||||||
|
menuName: ['Dashboard', '权限管理', '功能'][index],
|
||||||
|
permission: '',
|
||||||
|
orderNo: index + 1,
|
||||||
|
createTime: '@datetime',
|
||||||
|
'status|1': ['0', '0', '1'],
|
||||||
|
children: (() => {
|
||||||
|
const children: any[] = [];
|
||||||
|
for (let j = 0; j < 4; j++) {
|
||||||
|
children.push({
|
||||||
|
id: `${index}-${j}`,
|
||||||
|
type: '1',
|
||||||
|
menuName: ['菜单1', '菜单2', '菜单3', '菜单4'][j],
|
||||||
|
icon: 'ion:document',
|
||||||
|
permission: ['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index],
|
||||||
|
component: [
|
||||||
|
'/dashboard/welcome/index',
|
||||||
|
'/dashboard/analysis/index',
|
||||||
|
'/dashboard/workbench/index',
|
||||||
|
'/dashboard/test/index',
|
||||||
|
][j],
|
||||||
|
orderNo: j + 1,
|
||||||
|
createTime: '@datetime',
|
||||||
|
'status|1': ['0', '1'],
|
||||||
|
parentMenu: `${index}`,
|
||||||
|
children: (() => {
|
||||||
|
const children: any[] = [];
|
||||||
|
for (let k = 0; k < 4; k++) {
|
||||||
|
children.push({
|
||||||
|
id: `${index}-${j}-${k}`,
|
||||||
|
type: '2',
|
||||||
|
menuName: '按钮' + (j + 1) + '-' + (k + 1),
|
||||||
|
icon: '',
|
||||||
|
permission:
|
||||||
|
['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index] +
|
||||||
|
':btn' +
|
||||||
|
(k + 1),
|
||||||
|
component: [
|
||||||
|
'/dashboard/welcome/index',
|
||||||
|
'/dashboard/analysis/index',
|
||||||
|
'/dashboard/workbench/index',
|
||||||
|
'/dashboard/test/index',
|
||||||
|
][j],
|
||||||
|
orderNo: j + 1,
|
||||||
|
createTime: '@datetime',
|
||||||
|
'status|1': ['0', '1'],
|
||||||
|
parentMenu: `${index}-${j}`,
|
||||||
|
children: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
})(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
})(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/basic-api/system/getAccountList',
|
||||||
|
timeout: 100,
|
||||||
|
method: 'get',
|
||||||
|
response: ({ query }) => {
|
||||||
|
const { page = 1, pageSize = 20 } = query;
|
||||||
|
return resultPageSuccess(page, pageSize, accountList);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/system/getRoleListByPage',
|
||||||
|
timeout: 100,
|
||||||
|
method: 'get',
|
||||||
|
response: ({ query }) => {
|
||||||
|
const { page = 1, pageSize = 20 } = query;
|
||||||
|
return resultPageSuccess(page, pageSize, roleList);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/system/setRoleStatus',
|
||||||
|
timeout: 500,
|
||||||
|
method: 'post',
|
||||||
|
response: ({ query }) => {
|
||||||
|
const { id, status } = query;
|
||||||
|
return resultSuccess({ id, status });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/system/getAllRoleList',
|
||||||
|
timeout: 100,
|
||||||
|
method: 'get',
|
||||||
|
response: () => {
|
||||||
|
return resultSuccess(roleList);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/system/getDeptList',
|
||||||
|
timeout: 100,
|
||||||
|
method: 'get',
|
||||||
|
response: () => {
|
||||||
|
return resultSuccess(deptList);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/system/getMenuList',
|
||||||
|
timeout: 100,
|
||||||
|
method: 'get',
|
||||||
|
response: () => {
|
||||||
|
return resultSuccess(menuList);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/system/accountExist',
|
||||||
|
timeout: 500,
|
||||||
|
method: 'post',
|
||||||
|
response: ({ body }) => {
|
||||||
|
const { account } = body || {};
|
||||||
|
if (account && account.indexOf('admin') !== -1) {
|
||||||
|
return resultError('该字段不能包含admin');
|
||||||
|
} else {
|
||||||
|
return resultSuccess(`${account} can use`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as MockMethod[];
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { MockMethod } from 'vite-plugin-mock';
|
||||||
|
import { Random } from 'mockjs';
|
||||||
|
import { resultPageSuccess } from '../_util';
|
||||||
|
|
||||||
|
function getRandomPics(count = 10): string[] {
|
||||||
|
const arr: string[] = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
arr.push(Random.image('800x600', Random.color(), Random.color(), Random.title()));
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const demoList = (() => {
|
||||||
|
const result: any[] = [];
|
||||||
|
for (let index = 0; index < 200; index++) {
|
||||||
|
result.push({
|
||||||
|
id: `${index}`,
|
||||||
|
beginTime: '@datetime',
|
||||||
|
endTime: '@datetime',
|
||||||
|
address: '@city()',
|
||||||
|
name: '@cname()',
|
||||||
|
name1: '@cname()',
|
||||||
|
name2: '@cname()',
|
||||||
|
name3: '@cname()',
|
||||||
|
name4: '@cname()',
|
||||||
|
name5: '@cname()',
|
||||||
|
name6: '@cname()',
|
||||||
|
name7: '@cname()',
|
||||||
|
name8: '@cname()',
|
||||||
|
radio1: `选项${index + 1}`,
|
||||||
|
radio2: `选项${index + 1}`,
|
||||||
|
radio3: `选项${index + 1}`,
|
||||||
|
avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()),
|
||||||
|
imgArr: getRandomPics(Math.ceil(Math.random() * 3) + 1),
|
||||||
|
imgs: getRandomPics(Math.ceil(Math.random() * 3) + 1),
|
||||||
|
date: `@date('yyyy-MM-dd')`,
|
||||||
|
time: `@time('HH:mm')`,
|
||||||
|
'no|100000-10000000': 100000,
|
||||||
|
'status|1': ['normal', 'enable', 'disable'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/basic-api/table/getDemoList',
|
||||||
|
timeout: 100,
|
||||||
|
method: 'get',
|
||||||
|
response: ({ query }) => {
|
||||||
|
const { page = 1, pageSize = 20 } = query;
|
||||||
|
return resultPageSuccess(page, pageSize, demoList);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as MockMethod[];
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { MockMethod } from 'vite-plugin-mock';
|
||||||
|
import { resultSuccess } from '../_util';
|
||||||
|
|
||||||
|
const demoTreeList = (keyword) => {
|
||||||
|
const result = {
|
||||||
|
list: [] as Recordable[],
|
||||||
|
};
|
||||||
|
for (let index = 0; index < 5; index++) {
|
||||||
|
const children: Recordable[] = [];
|
||||||
|
for (let j = 0; j < 3; j++) {
|
||||||
|
children.push({
|
||||||
|
title: `${keyword ?? ''}选项${index}-${j}`,
|
||||||
|
value: `${index}-${j}`,
|
||||||
|
key: `${index}-${j}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
result.list.push({
|
||||||
|
title: `${keyword ?? ''}选项${index}`,
|
||||||
|
value: `${index}`,
|
||||||
|
key: `${index}`,
|
||||||
|
children,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/basic-api/tree/getDemoOptions',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'get',
|
||||||
|
response: ({ query }) => {
|
||||||
|
const { keyword } = query;
|
||||||
|
console.log(keyword);
|
||||||
|
return resultSuccess(demoTreeList(keyword));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as MockMethod[];
|
||||||
|
|
@ -0,0 +1,270 @@
|
||||||
|
import { resultSuccess, resultError, getRequestToken, requestParams } from '../_util';
|
||||||
|
import { MockMethod } from 'vite-plugin-mock';
|
||||||
|
import { createFakeUserList } from './user';
|
||||||
|
|
||||||
|
// single
|
||||||
|
const dashboardRoute = {
|
||||||
|
path: '/dashboard',
|
||||||
|
name: 'Dashboard',
|
||||||
|
component: 'LAYOUT',
|
||||||
|
redirect: '/dashboard/analysis',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.dashboard.dashboard',
|
||||||
|
hideChildrenInMenu: true,
|
||||||
|
icon: 'bx:bx-home',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'analysis',
|
||||||
|
name: 'Analysis',
|
||||||
|
component: '/dashboard/analysis/index',
|
||||||
|
meta: {
|
||||||
|
hideMenu: true,
|
||||||
|
hideBreadcrumb: true,
|
||||||
|
title: 'routes.dashboard.analysis',
|
||||||
|
currentActiveMenu: '/dashboard',
|
||||||
|
icon: 'bx:bx-home',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'workbench',
|
||||||
|
name: 'Workbench',
|
||||||
|
component: '/dashboard/workbench/index',
|
||||||
|
meta: {
|
||||||
|
hideMenu: true,
|
||||||
|
hideBreadcrumb: true,
|
||||||
|
title: 'routes.dashboard.workbench',
|
||||||
|
currentActiveMenu: '/dashboard',
|
||||||
|
icon: 'bx:bx-home',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const backRoute = {
|
||||||
|
path: 'back',
|
||||||
|
name: 'PermissionBackDemo',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.demo.permission.back',
|
||||||
|
},
|
||||||
|
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'page',
|
||||||
|
name: 'BackAuthPage',
|
||||||
|
component: '/demo/permission/back/index',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.demo.permission.backPage',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'btn',
|
||||||
|
name: 'BackAuthBtn',
|
||||||
|
component: '/demo/permission/back/Btn',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.demo.permission.backBtn',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const authRoute = {
|
||||||
|
path: '/permission',
|
||||||
|
name: 'Permission',
|
||||||
|
component: 'LAYOUT',
|
||||||
|
redirect: '/permission/front/page',
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:user-role',
|
||||||
|
title: 'routes.demo.permission.permission',
|
||||||
|
},
|
||||||
|
children: [backRoute],
|
||||||
|
};
|
||||||
|
|
||||||
|
const levelRoute = {
|
||||||
|
path: '/level',
|
||||||
|
name: 'Level',
|
||||||
|
component: 'LAYOUT',
|
||||||
|
redirect: '/level/menu1/menu1-1',
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:user-role',
|
||||||
|
title: 'routes.demo.level.level',
|
||||||
|
},
|
||||||
|
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'menu1',
|
||||||
|
name: 'Menu1Demo',
|
||||||
|
meta: {
|
||||||
|
title: 'Menu1',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'menu1-1',
|
||||||
|
name: 'Menu11Demo',
|
||||||
|
meta: {
|
||||||
|
title: 'Menu1-1',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'menu1-1-1',
|
||||||
|
name: 'Menu111Demo',
|
||||||
|
component: '/demo/level/Menu111',
|
||||||
|
meta: {
|
||||||
|
title: 'Menu111',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'menu1-2',
|
||||||
|
name: 'Menu12Demo',
|
||||||
|
component: '/demo/level/Menu12',
|
||||||
|
meta: {
|
||||||
|
title: 'Menu1-2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'menu2',
|
||||||
|
name: 'Menu2Demo',
|
||||||
|
component: '/demo/level/Menu2',
|
||||||
|
meta: {
|
||||||
|
title: 'Menu2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const sysRoute = {
|
||||||
|
path: '/system',
|
||||||
|
name: 'System',
|
||||||
|
component: 'LAYOUT',
|
||||||
|
redirect: '/system/account',
|
||||||
|
meta: {
|
||||||
|
icon: 'ion:settings-outline',
|
||||||
|
title: 'routes.demo.system.moduleName',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'account',
|
||||||
|
name: 'AccountManagement',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.demo.system.account',
|
||||||
|
ignoreKeepAlive: true,
|
||||||
|
},
|
||||||
|
component: '/demo/system/account/index',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'account_detail/:id',
|
||||||
|
name: 'AccountDetail',
|
||||||
|
meta: {
|
||||||
|
hideMenu: true,
|
||||||
|
title: 'routes.demo.system.account_detail',
|
||||||
|
ignoreKeepAlive: true,
|
||||||
|
showMenu: false,
|
||||||
|
currentActiveMenu: '/system/account',
|
||||||
|
},
|
||||||
|
component: '/demo/system/account/AccountDetail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'role',
|
||||||
|
name: 'RoleManagement',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.demo.system.role',
|
||||||
|
ignoreKeepAlive: true,
|
||||||
|
},
|
||||||
|
component: '/demo/system/role/index',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'menu',
|
||||||
|
name: 'MenuManagement',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.demo.system.menu',
|
||||||
|
ignoreKeepAlive: true,
|
||||||
|
},
|
||||||
|
component: '/demo/system/menu/index',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'dept',
|
||||||
|
name: 'DeptManagement',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.demo.system.dept',
|
||||||
|
ignoreKeepAlive: true,
|
||||||
|
},
|
||||||
|
component: '/demo/system/dept/index',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'changePassword',
|
||||||
|
name: 'ChangePassword',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.demo.system.password',
|
||||||
|
ignoreKeepAlive: true,
|
||||||
|
},
|
||||||
|
component: '/demo/system/password/index',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkRoute = {
|
||||||
|
path: '/link',
|
||||||
|
name: 'Link',
|
||||||
|
component: 'LAYOUT',
|
||||||
|
meta: {
|
||||||
|
icon: 'ion:tv-outline',
|
||||||
|
title: 'routes.demo.iframe.frame',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'doc',
|
||||||
|
name: 'Doc',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.demo.iframe.doc',
|
||||||
|
frameSrc: 'https://doc.vvbin.cn/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'https://doc.vvbin.cn/',
|
||||||
|
name: 'DocExternal',
|
||||||
|
component: 'LAYOUT',
|
||||||
|
meta: {
|
||||||
|
title: 'routes.demo.iframe.docExternal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/basic-api/getMenuList',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'get',
|
||||||
|
response: (request: requestParams) => {
|
||||||
|
const token = getRequestToken(request);
|
||||||
|
if (!token) {
|
||||||
|
return resultError('Invalid token!');
|
||||||
|
}
|
||||||
|
const checkUser = createFakeUserList().find((item) => item.token === token);
|
||||||
|
if (!checkUser) {
|
||||||
|
return resultError('Invalid user token!');
|
||||||
|
}
|
||||||
|
const id = checkUser.userId;
|
||||||
|
let menu: Object[];
|
||||||
|
switch (id) {
|
||||||
|
case '1':
|
||||||
|
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path;
|
||||||
|
menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute];
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path;
|
||||||
|
menu = [dashboardRoute, authRoute, levelRoute, linkRoute];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
menu = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultSuccess(menu);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as unknown as MockMethod[];
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
import { MockMethod } from 'vite-plugin-mock';
|
||||||
|
import { resultError, resultSuccess, getRequestToken, requestParams } from '../_util';
|
||||||
|
|
||||||
|
export function createFakeUserList() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
userId: '1',
|
||||||
|
username: 'vben',
|
||||||
|
realName: 'Vben Admin',
|
||||||
|
avatar: '',
|
||||||
|
desc: 'manager',
|
||||||
|
password: '123456',
|
||||||
|
token: 'fakeToken1',
|
||||||
|
homePath: '/dashboard/analysis',
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
roleName: 'Super Admin',
|
||||||
|
value: 'super',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: '2',
|
||||||
|
username: 'test',
|
||||||
|
password: '123456',
|
||||||
|
realName: 'test user',
|
||||||
|
avatar: '',
|
||||||
|
desc: 'tester',
|
||||||
|
token: 'fakeToken2',
|
||||||
|
homePath: '/dashboard/workbench',
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
roleName: 'Tester',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const fakeCodeList: any = {
|
||||||
|
'1': ['1000', '3000', '5000'],
|
||||||
|
|
||||||
|
'2': ['2000', '4000', '6000'],
|
||||||
|
};
|
||||||
|
export default [
|
||||||
|
// mock user login
|
||||||
|
{
|
||||||
|
url: '/basic-api/login',
|
||||||
|
timeout: 200,
|
||||||
|
method: 'post',
|
||||||
|
response: ({ body }) => {
|
||||||
|
const { username, password } = body;
|
||||||
|
const checkUser = createFakeUserList().find(
|
||||||
|
(item) => item.username === username && password === item.password,
|
||||||
|
);
|
||||||
|
if (!checkUser) {
|
||||||
|
return resultError('Incorrect account or password!');
|
||||||
|
}
|
||||||
|
const { userId, username: _username, token, realName, desc, roles } = checkUser;
|
||||||
|
return resultSuccess({
|
||||||
|
roles,
|
||||||
|
userId,
|
||||||
|
username: _username,
|
||||||
|
token,
|
||||||
|
realName,
|
||||||
|
desc,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/getUserInfo',
|
||||||
|
method: 'get',
|
||||||
|
response: (request: requestParams) => {
|
||||||
|
const token = getRequestToken(request);
|
||||||
|
if (!token) return resultError('Invalid token');
|
||||||
|
const checkUser = createFakeUserList().find((item) => item.token === token);
|
||||||
|
if (!checkUser) {
|
||||||
|
return resultError('The corresponding user information was not obtained!');
|
||||||
|
}
|
||||||
|
return resultSuccess(checkUser);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/getPermCode',
|
||||||
|
timeout: 200,
|
||||||
|
method: 'get',
|
||||||
|
response: (request: requestParams) => {
|
||||||
|
const token = getRequestToken(request);
|
||||||
|
if (!token) return resultError('Invalid token');
|
||||||
|
const checkUser = createFakeUserList().find((item) => item.token === token);
|
||||||
|
if (!checkUser) {
|
||||||
|
return resultError('Invalid token!');
|
||||||
|
}
|
||||||
|
const codeList = fakeCodeList[checkUser.userId];
|
||||||
|
|
||||||
|
return resultSuccess(codeList);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/logout',
|
||||||
|
timeout: 200,
|
||||||
|
method: 'get',
|
||||||
|
response: (request: requestParams) => {
|
||||||
|
const token = getRequestToken(request);
|
||||||
|
if (!token) return resultError('Invalid token');
|
||||||
|
const checkUser = createFakeUserList().find((item) => item.token === token);
|
||||||
|
if (!checkUser) {
|
||||||
|
return resultError('Invalid token!');
|
||||||
|
}
|
||||||
|
return resultSuccess(undefined, { message: 'Token has been destroyed' });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/basic-api/testRetry',
|
||||||
|
statusCode: 405,
|
||||||
|
method: 'get',
|
||||||
|
response: () => {
|
||||||
|
return resultError('Error!');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as MockMethod[];
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
{
|
||||||
|
"name": "vben-admin",
|
||||||
|
"version": "2.10.1",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"author": {
|
||||||
|
"name": "vben",
|
||||||
|
"email": "anncwb@126.com",
|
||||||
|
"url": "https://github.com/anncwb"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"bootstrap": "pnpm install",
|
||||||
|
"build": "vite build",
|
||||||
|
"build:analyze": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build --mode analyze",
|
||||||
|
"build:docker": "vite build --mode docker",
|
||||||
|
"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,mock}/**/*.{vue,ts,tsx}\" --fix",
|
||||||
|
"lint:prettier": "prettier --write .",
|
||||||
|
"lint:stylelint": "stylelint \"**/*.{vue,css,less,scss}\" --fix --cache --cache-location node_modules/.cache/stylelint/",
|
||||||
|
"prepare": "husky install",
|
||||||
|
"preview": "npm run build && 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",
|
||||||
|
"@icon-park/svg": "^1.4.2",
|
||||||
|
"@iconify/iconify": "^3.1.1",
|
||||||
|
"@logicflow/core": "^1.2.18",
|
||||||
|
"@logicflow/extension": "^1.2.19",
|
||||||
|
"@turf/turf": "^6.5.0",
|
||||||
|
"@vben/hooks": "workspace:*",
|
||||||
|
"@vue/shared": "^3.4.5",
|
||||||
|
"@vueuse/core": "^10.7.1",
|
||||||
|
"@zxcvbn-ts/core": "^3.0.4",
|
||||||
|
"ant-design-vue": "^4.0.8",
|
||||||
|
"axios": "^1.6.4",
|
||||||
|
"bpmn-js": "^17.0.2",
|
||||||
|
"bpmn-js-properties-panel": "^5.13.0",
|
||||||
|
"bpmn-js-token-simulation": "^0.33.1",
|
||||||
|
"codemirror": "^5.65.16",
|
||||||
|
"cropperjs": "^1.6.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"diagram-js": "^14.1.0",
|
||||||
|
"driver.js": "^1.3.1",
|
||||||
|
"echarts": "^5.4.3",
|
||||||
|
"element-plus": "^2.6.0",
|
||||||
|
"exceljs": "^4.4.0",
|
||||||
|
"highlight.js": "^11.9.0",
|
||||||
|
"js-md5": "^0.8.3",
|
||||||
|
"kml-geojson": "^1.2.2",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"mapbox-gl": "^3.3.0",
|
||||||
|
"mapbox-gl-utils": "^0.44.0",
|
||||||
|
"mars3d": "^3.7.0",
|
||||||
|
"mars3d-cesium": "^1.113.0",
|
||||||
|
"min-dash": "^4.2.1",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"path-to-regexp": "^6.2.1",
|
||||||
|
"pinia": "2.1.7",
|
||||||
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
|
"print-js": "^1.6.0",
|
||||||
|
"qrcode": "^1.5.3",
|
||||||
|
"qs": "^6.11.2",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
|
"showdown": "^2.1.0",
|
||||||
|
"sortablejs": "^1.15.1",
|
||||||
|
"tinymce": "^5.10.9",
|
||||||
|
"unocss": "0.58.3",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
|
"vditor": "^3.9.8",
|
||||||
|
"vue": "3.3.4",
|
||||||
|
"vue-color-kit": "^1.0.6",
|
||||||
|
"vue-i18n": "^9.8.0",
|
||||||
|
"vue-json-pretty": "^2.3.0",
|
||||||
|
"vue-router": "^4.2.5",
|
||||||
|
"vue-types": "^5.1.1",
|
||||||
|
"vuedraggable": "^4.1.0",
|
||||||
|
"vuex": "^4.1.0",
|
||||||
|
"vxe-table": "^4.5.17",
|
||||||
|
"vxe-table-plugin-export-xlsx": "^3.1.0",
|
||||||
|
"xe-utils": "^3.5.14",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
|
"vue-video-player": "^5.0.2",
|
||||||
|
"xml-js": "^1.6.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^18.4.4",
|
||||||
|
"@commitlint/config-conventional": "^18.4.4",
|
||||||
|
"@iconify/json": "^2.2.164",
|
||||||
|
"@purge-icons/generated": "^0.10.0",
|
||||||
|
"@types/codemirror": "^5.60.15",
|
||||||
|
"@types/crypto-js": "^4.2.1",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/mockjs": "^1.0.10",
|
||||||
|
"@types/node": "^20.10.6",
|
||||||
|
"@types/nprogress": "^0.2.3",
|
||||||
|
"@types/qrcode": "^1.5.5",
|
||||||
|
"@types/qs": "^6.9.11",
|
||||||
|
"@types/showdown": "^2.0.6",
|
||||||
|
"@types/sortablejs": "^1.15.7",
|
||||||
|
"@vben/eslint-config": "workspace:*",
|
||||||
|
"@vben/stylelint-config": "workspace:*",
|
||||||
|
"@vben/ts-config": "workspace:*",
|
||||||
|
"@vben/types": "workspace:*",
|
||||||
|
"@vben/vite-config": "workspace:*",
|
||||||
|
"@vue/compiler-sfc": "^3.4.5",
|
||||||
|
"@vue/test-utils": "^2.4.3",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"cz-git": "^1.8.0",
|
||||||
|
"czg": "^1.8.0",
|
||||||
|
"husky": "^8.0.3",
|
||||||
|
"lint-staged": "15.2.0",
|
||||||
|
"prettier": "^3.1.1",
|
||||||
|
"prettier-plugin-packagejson": "^2.4.8",
|
||||||
|
"rimraf": "^5.0.5",
|
||||||
|
"turbo": "^1.11.3",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"unbuild": "^2.0.0",
|
||||||
|
"vite": "^4.2.0",
|
||||||
|
"vite-plugin-mars3d": "^3.1.3",
|
||||||
|
"vite-plugin-mock": "^2.9.6",
|
||||||
|
"vue-tsc": "^1.8.27"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@8.10.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.12.0",
|
||||||
|
"pnpm": ">=8.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.7.1",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"vue": "3.3.4"
|
||||||
|
},
|
||||||
|
"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);
|
||||||
|
};
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue